[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Before you file an issue**\n- Make sure you specify the \"read\" dialect eg. `parse_one(sql, read=\"spark\")`\n- Make sure you specify the \"write\" dialect eg. `ast.sql(dialect=\"duckdb\")`\n- Check if the issue still exists on main\n\n**Fully reproducible code snippet**\nPlease include a fully reproducible code snippet or the input sql, dialect, and expected output.\n\n**Official Documentation**\nPlease include links to official SQL documentation related to your issue.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/scripts/get_integration_test_params.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nThis script is intended to be used as part of a GitHub Actions workflow in order to decide if the integration tests should:\n\na) be triggered at all\nb) if they should be triggered, should they be triggered for a subset of dialects or all dialects?\n\nThe tests can be triggered manually by using the following directive in the PR description:\n\n /integration-tests\n\nTo limit them to a certain dialect or dialects, you can specify:\n\n /integration-tests dialects=bigquery,duckdb\n\nIf you specify nothing, a `git diff` will be performed between your PR branch and the base branch.\nIf any files modified contain one of the SUPPORTED_DIALECTS in the filename, that dialect will be added to the\nlist of dialects to test. If no files match, the integration tests will be skipped.\n\nNote that integration tests in the remote workflow are only implemented for a subset of dialects.\nIf new ones are added, update the SUPPORTED_DIALECTS constant below.\n\nEach dialect is tested against itself (roundtrip) and duckdb (transpilation).\nSupplying a dialect not in this list will cause the tests to get skipped.\n\"\"\"\n\nimport typing as t\nimport os\nimport sys\nimport json\nimport subprocess\nfrom pathlib import Path\n\nTRIGGER = \"/integration-test\"\nSUPPORTED_DIALECTS = [\"duckdb\", \"bigquery\", \"snowflake\"]\n\n\ndef get_dialects_from_manual_trigger(trigger: str) -> t.Set[str]:\n    \"\"\"\n    Takes a trigger string and parses out the supported dialects\n\n    /integration_test -> []\n    /integration_test dialects=bigquery -> [\"bigquery\"]\n    /integration_test dialects=bigquery,duckdb -> [\"bigquery\",\"duckdb\"]\n    /integration_test dialects=exasol,duckdb -> [\"duckdb\"]\n    \"\"\"\n\n    if not trigger.startswith(TRIGGER):\n        raise ValueError(f\"Invalid trigger: {trigger}\")\n\n    # trim off start at first space (to cover both /integration-test and /integration-tests)\n    trigger_parts = trigger.split(\" \")[1:]\n\n    print(f\"Parsing trigger args: {trigger_parts}\")\n\n    dialects: t.List[str] = []\n    for part in trigger_parts:\n        # try to parse key=value pairs\n        maybe_kv = part.split(\"=\", maxsplit=1)\n        if len(maybe_kv) >= 2:\n            k, v = maybe_kv[0], maybe_kv[1]\n            if k.lower().startswith(\"dialect\"):\n                dialects.extend([d.lower().strip() for d in v.split(\",\")])\n\n    return {d for d in dialects if d in SUPPORTED_DIALECTS}\n\n\ndef get_dialects_from_git(base_ref: str, current_ref: str) -> t.Set[str]:\n    \"\"\"\n    Takes two git refs and runs `git diff --name-only <base_ref> <current_ref>`\n\n    If any of the returned file names contain a dialect from SUPPORTED_DIALECTS as\n    a substring, that dialect is included in the returned set\n    \"\"\"\n    print(f\"Checking for files changed between '{base_ref}' and '{current_ref}'\")\n\n    result = subprocess.run(\n        [\"git\", \"diff\", \"--name-only\", base_ref, current_ref],\n        stdout=subprocess.PIPE,\n        stderr=subprocess.STDOUT,\n    )\n    output = result.stdout.decode(\"utf8\")\n\n    if result.returncode != 0:\n        raise ValueError(f\"Git process failed with exit code {result.returncode}:\\n{output}\")\n\n    print(f\"Git output:\\n{output}\")\n\n    matching_dialects = []\n\n    for l in output.splitlines():\n        l = l.strip().lower()\n\n        matching_dialects.extend([d for d in SUPPORTED_DIALECTS if d in l])\n\n    return set(matching_dialects)\n\n\nif __name__ == \"__main__\":\n    github_event_path = os.environ.get(\"GITHUB_EVENT_PATH\")\n    github_output = os.environ.get(\"GITHUB_OUTPUT\")\n\n    if not os.environ.get(\"GITHUB_ACTIONS\") or not github_event_path or not github_output:\n        print(\"This script needs to run within GitHub Actions\")\n        sys.exit(1)\n\n    github_event_path = Path(github_event_path)\n    github_output = Path(github_output)\n\n    with github_event_path.open(\"r\") as f:\n        event: t.Dict[str, t.Any] = json.load(f)\n\n    print(\"Handling event: \\n\" + json.dumps(event, indent=2))\n\n    # for pull_request events, the body is located at github.event.pull_request.body\n    pr_description: str = event.get(\"pull_request\", {}).get(\"body\") or \"\"\n\n    dialects = []\n    should_run = False\n\n    pr_description_lines = [l.strip().lower() for l in pr_description.splitlines()]\n    if trigger_line := [l for l in pr_description_lines if l.startswith(TRIGGER)]:\n        # if the user has explicitly requested /integration-tests then use that\n        print(f\"Handling trigger line: {trigger_line[0]}\")\n        dialects = get_dialects_from_manual_trigger(trigger_line[0])\n        should_run = True\n    else:\n        # otherwise, do a git diff and inspect the changed files\n        print(\"Explicit trigger line not detected; performing git diff\")\n        pull_request_base_ref = event.get(\"pull_request\", {}).get(\"base\", {}).get(\"sha\")\n        if not pull_request_base_ref:\n            raise ValueError(\"Unable to determine base ref\")\n\n        current_ref = event.get(\"pull_request\", {}).get(\"head\", {}).get(\"sha\")\n\n        if not current_ref:\n            raise ValueError(\"Unable to determine current/head ref\")\n\n        print(f\"Comparing '{current_ref}' against '{pull_request_base_ref}'\")\n        # otherwise, look at git files changed and only trigger if a file relating\n        # to a supported dialect has changed\n        dialects = get_dialects_from_git(base_ref=pull_request_base_ref, current_ref=current_ref)\n        if dialects:\n            should_run = True\n\n    if should_run:\n        dialects_str = (\n            f\"the following dialects: {', '.join(dialects)}\"\n            if dialects\n            else \"all supported dialects\"\n        )\n        print(f\"Conclusion: should run tests for {dialects_str}\")\n    else:\n        print(\"Conclusion: No tests to run\")\n\n    # write output variables\n    lines = []\n    if should_run:\n        lines.append(\"skip=false\")\n        if dialects:\n            lines.append(f\"dialects={','.join(dialects)}\")\n    else:\n        lines.append(\"skip=true\")\n\n    with github_output.open(\"a\") as f:\n        f.writelines(f\"{l}\\n\" for l in lines)\n"
  },
  {
    "path": ".github/scripts/integration_tests_sync.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n# Unset git env vars that leak from parent hook context into submodule commands\nunset GIT_INDEX_FILE GIT_DIR\n\nSUBMODULE_DIR=\"sqlglot-integration-tests\"\n\n# Graceful no-op when the submodule is absent (public contributors)\n[ -e \"$SUBMODULE_DIR/.git\" ] || exit 0\n\n# Ensure the submodule is on a branch matching the parent's branch name.\n# Creates the branch if it doesn't exist.\nensure_branch() {\n  local branch\n  branch=$(git rev-parse --abbrev-ref HEAD)\n  [ \"$branch\" = \"HEAD\" ] && return 0\n\n  local current\n  current=$(git -C \"$SUBMODULE_DIR\" rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\")\n\n  if [ \"$current\" != \"$branch\" ]; then\n    # Stash any uncommitted/untracked changes so branch switch doesn't fail\n    local stashed=0\n    if [ -n \"$(git -C \"$SUBMODULE_DIR\" status --porcelain)\" ]; then\n      git -C \"$SUBMODULE_DIR\" stash push --include-untracked --quiet 2>/dev/null && stashed=1\n    fi\n\n    if git -C \"$SUBMODULE_DIR\" show-ref --verify --quiet \"refs/heads/$branch\"; then\n      git -C \"$SUBMODULE_DIR\" checkout \"$branch\" --quiet\n    else\n      git -C \"$SUBMODULE_DIR\" checkout -b \"$branch\" --quiet\n    fi\n\n    # Restore stashed changes on the new branch\n    if [ \"$stashed\" = \"1\" ]; then\n      git -C \"$SUBMODULE_DIR\" stash pop --quiet 2>/dev/null || true\n    fi\n  fi\n}\n\ncase \"${1:-}\" in\n  checkout)\n    # No-op: branch creation is deferred to commit/post-commit to avoid\n    # creating empty branches when switching parent branches.\n    ;;\n\n  commit)\n    # Skip if submodule has no changes\n    [ -n \"$(git -C \"$SUBMODULE_DIR\" status --porcelain)\" ] || exit 0\n\n    # Only auto-commit submodule changes when the submodule pointer is already\n    # staged in the parent (i.e. the user explicitly `git add`-ed it). This\n    # prevents surprise commits of unrelated WIP in the submodule working tree.\n    git diff --cached --quiet -- \"$SUBMODULE_DIR\" && exit 0\n\n    ensure_branch\n\n    if [ -n \"$(git -C \"$SUBMODULE_DIR\" status --porcelain)\" ]; then\n      BRANCH=$(git rev-parse --abbrev-ref HEAD)\n      git -C \"$SUBMODULE_DIR\" add -A\n      git -C \"$SUBMODULE_DIR\" commit -m \"Sync: $BRANCH\"\n    fi\n\n    # Stage the updated submodule pointer in the parent\n    if ! git diff --quiet -- \"$SUBMODULE_DIR\"; then\n      git add \"$SUBMODULE_DIR\"\n    fi\n    ;;\n\n  post-commit)\n    # The pre-commit framework's stashing undoes submodule changes made during\n    # pre-commit. Re-commit the submodule and amend the parent to include it.\n\n    # Only act if the most recent parent commit references the submodule. This\n    # prevents the post-commit hook from auto-committing unrelated submodule WIP\n    # (e.g. when switching branches or pulling on main).\n    if ! git diff-tree --no-commit-id --name-only -r HEAD | grep -q \"^${SUBMODULE_DIR}$\"; then\n      exit 0\n    fi\n\n    # Skip if submodule has no changes and pointer is up to date\n    if [ -z \"$(git -C \"$SUBMODULE_DIR\" status --porcelain)\" ] && git diff --quiet -- \"$SUBMODULE_DIR\"; then\n      exit 0\n    fi\n\n    ensure_branch\n\n    if [ -n \"$(git -C \"$SUBMODULE_DIR\" status --porcelain)\" ]; then\n      BRANCH=$(git rev-parse --abbrev-ref HEAD)\n      git -C \"$SUBMODULE_DIR\" add -A\n      git -C \"$SUBMODULE_DIR\" commit -m \"Sync: $BRANCH\"\n    fi\n\n    if ! git diff --quiet -- \"$SUBMODULE_DIR\"; then\n      git add \"$SUBMODULE_DIR\"\n      git commit --amend --no-edit --no-verify\n    fi\n    ;;\n\n  push)\n    BRANCH=$(git rev-parse --abbrev-ref HEAD)\n    case \"$BRANCH\" in main|master) exit 0 ;; esac\n\n    ensure_branch\n\n    cd \"$SUBMODULE_DIR\"\n\n    # Check if there are local commits to push\n    if git rev-parse --verify \"@{upstream}\" >/dev/null 2>&1; then\n      UNPUSHED=$(git rev-list --count \"@{upstream}..HEAD\")\n    else\n      UNPUSHED=$(git rev-list --count \"origin/main..HEAD\" 2>/dev/null || echo \"0\")\n    fi\n    [ \"$UNPUSHED\" = \"0\" ] && exit 0\n\n    # Push the branch, rebasing first if needed\n    git push -u origin \"$BRANCH\" 2>/dev/null || {\n      git pull --rebase --autostash || {\n        git rebase --abort 2>/dev/null || true\n        echo \"ERROR: Conflicts pushing integration tests. Resolve with:\" >&2\n        echo \"  cd $SUBMODULE_DIR && git pull --rebase\" >&2\n        exit 1\n      }\n      git push -u origin \"$BRANCH\"\n    }\n\n    # Create a PR if one doesn't exist (requires gh CLI)\n    if command -v gh >/dev/null 2>&1; then\n      EXISTING=$(gh pr list --head \"$BRANCH\" --json number --jq 'length' 2>/dev/null || echo \"0\")\n      if [ \"$EXISTING\" = \"0\" ]; then\n        PARENT_URL=$(git -C .. remote get-url origin 2>/dev/null | sed 's/\\.git$//' | sed 's|git@github.com:|https://github.com/|')\n        BODY=\"Parent branch: ${PARENT_URL}/tree/${BRANCH}\"\n        gh pr create \\\n          --title \"$BRANCH\" \\\n          --body \"$BODY\" \\\n          --head \"$BRANCH\" 2>/dev/null || true\n      fi\n    fi\n    ;;\n\n  merge)\n    # Sync submodule to the parent's pointer\n    git submodule update --init 2>/dev/null || true\n    ensure_branch\n\n    cd \"$SUBMODULE_DIR\"\n    BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"HEAD\")\n    [ \"$BRANCH\" = \"HEAD\" ] && exit 0\n\n    git fetch origin --quiet 2>/dev/null || exit 0\n\n    BEHIND=$(git rev-list --count \"HEAD..origin/$BRANCH\" 2>/dev/null || echo \"0\")\n    [ \"$BEHIND\" = \"0\" ] && exit 0\n\n    if ! git pull --rebase --autostash --quiet 2>/dev/null; then\n      git rebase --abort 2>/dev/null || true\n      echo \"WARNING: Rebase conflicts in $SUBMODULE_DIR.\" >&2\n      echo \"Resolve with: cd $SUBMODULE_DIR && git pull --rebase\" >&2\n    fi\n    ;;\n\n  *)\n    echo \"Usage: $0 {checkout|commit|post-commit|push|merge}\" >&2\n    exit 1\n    ;;\nesac\n"
  },
  {
    "path": ".github/workflows/benchmark-sqlglot.yml",
    "content": "name: Benchmark pull requests\n\non:\n  pull_request:\n    types: [opened]\n  issue_comment:\n    types: [created]\n\njobs:\n  benchmark:\n    name: benchmark\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n    if: >\n      (github.event_name == 'pull_request') ||\n      (github.event_name == 'issue_comment' &&\n       github.event.issue.pull_request &&\n       contains(github.event.comment.body, '/benchmark'))\n    steps:\n      - name: Checkout PR branch\n        uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n          ref: ${{ github.event.pull_request.head.sha || '' }}\n\n      - name: Checkout PR branch (comment trigger)\n        if: github.event_name == 'issue_comment'\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          PR_NUMBER=${{ github.event.issue.number }}\n          PR_HEAD=$(gh pr view $PR_NUMBER --json headRefName -q .headRefName)\n          git fetch origin \"$PR_HEAD\"\n          git checkout \"$PR_HEAD\"\n\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.14\"\n\n      - name: Install dependencies (PR)\n        run: |\n          python -m venv .venv\n          source .venv/bin/activate\n          pip install -e \".[dev]\"\n          make install-devc-release\n\n      - name: Benchmark PR branch\n        run: |\n          source .venv/bin/activate\n          python benchmarks/parse.py --json bench_pr.json --quiet --sqlglot-only\n\n      - name: Checkout main branch\n        run: |\n          git fetch origin main\n          git worktree add main-branch origin/main\n\n      - name: Install dependencies (main)\n        run: |\n          rm -rf .venv\n          python -m venv .venv\n          source .venv/bin/activate\n          cd main-branch\n          pip install -e \".[dev]\"\n          make install-devc-release\n\n      - name: Benchmark main branch\n        run: |\n          source .venv/bin/activate\n          cd main-branch\n          python benchmarks/parse.py --json ../bench_main.json --quiet --sqlglot-only\n\n      - name: Compare results\n        run: |\n          source .venv/bin/activate\n          python benchmarks/compare.py bench_main.json bench_pr.json > benchmark_comment.md\n\n      - name: Comment on PR\n        uses: peter-evans/create-or-update-comment@v4\n        with:\n          issue-number: ${{ github.event.issue.number || github.event.pull_request.number }}\n          body-path: benchmark_comment.md\n"
  },
  {
    "path": ".github/workflows/package-publish.yml",
    "content": "name: Publish sqlglot to PyPI\n\non:\n  push:\n    tags:\n      - \"v*\"\n\npermissions:\n  contents: read\n\njobs:\n  # Build mypyc wheels for each platform and Python version.\n  build-wheels:\n    name: Build wheels (${{ matrix.platform.os }}, ${{ matrix.platform.archs }}, ${{ matrix.python }})\n    runs-on: ${{ matrix.platform.os }}\n    strategy:\n      matrix:\n        python: [\"cp39\", \"cp310\", \"cp311\", \"cp312\", \"cp313\", \"cp314\"]\n        platform:\n          - os: ubuntu-latest\n            archs: x86_64\n          - os: ubuntu-24.04-arm\n            archs: aarch64\n          - os: macos-latest\n            archs: universal2\n          - os: windows-latest\n            archs: AMD64\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n      - uses: pypa/cibuildwheel@v3.3.1\n        with:\n          package-dir: sqlglotc\n          output-dir: wheelhouse\n        env:\n          CIBW_BUILD: ${{ matrix.python }}-*\n          CIBW_SKIP: \"*-musllinux_*\"\n          CIBW_ARCHS: ${{ matrix.platform.archs }}\n      - uses: actions/upload-artifact@v4\n        with:\n          name: wheels-${{ matrix.platform.os }}-${{ matrix.platform.archs }}-${{ matrix.python }}\n          path: ./wheelhouse/*.whl\n\n  # Build the sqlglotc sdist (source-only, no wheels — always compiles on install).\n  sdist-c:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-python@v5\n        with:\n          python-version: \"3.x\"\n      - name: Install build tools\n        run: pip install build\n      - name: Build sdist\n        run: |\n          cd sqlglotc\n          python -m build --sdist\n      - name: Upload sdist\n        uses: actions/upload-artifact@v4\n        with:\n          name: sqlglotc-sdist\n          path: sqlglotc/dist/*.tar.gz\n\n  # Publish sqlglotc wheels and sdist to PyPI.\n  publish-sqlglotc:\n    needs: [build-wheels, sdist-c]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/download-artifact@v4\n        with:\n          pattern: wheels-*\n          path: dist/\n          merge-multiple: true\n      - uses: actions/download-artifact@v4\n        with:\n          name: sqlglotc-sdist\n          path: dist/\n      - name: Publish to PyPI\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          password: ${{ secrets.PYPI_API_TOKEN }}\n\n  # Publish the main sqlglot package.\n  deploy:\n    needs: publish-sqlglotc\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/checkout@v5\n        with:\n          fetch-depth: 0\n      - name: Set up Python\n        uses: actions/setup-python@v3\n        with:\n          python-version: \"3.10\"\n      - name: Install dependencies\n        run: |\n          python -m venv .venv\n          source ./.venv/bin/activate\n          python -m pip install --upgrade pip\n          pip install build twine\n          make install-dev\n      - name: Build and publish\n        env:\n          TWINE_USERNAME: __token__\n          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}\n        run: |\n          source ./.venv/bin/activate\n          python -m build\n          twine upload dist/*\n      - name: Update CHANGELOG\n        id: changelog\n        continue-on-error: true\n        uses: requarks/changelog-action@v1\n        with:\n          token: ${{ github.token }}\n          tag: ${{ github.ref_name }}\n      - name: Commit CHANGELOG.md\n        continue-on-error: true\n        uses: stefanzweifel/git-auto-commit-action@v4\n        with:\n          branch: main\n          commit_message: \"Update CHANGELOG.md for ${{ github.ref_name }} [skip ci]\"\n          file_pattern: \"CHANGELOG.md\"\n      - name: Update API docs\n        run: |\n          source ./.venv/bin/activate\n          make docs\n          echo \"sqlglot.com\" > docs/CNAME\n          mv docs /tmp/generated-docs\n          git checkout -B api-docs origin/main\n          rm -rf docs\n          mv /tmp/generated-docs docs\n      - name: Commit API docs\n        uses: stefanzweifel/git-auto-commit-action@v4\n        with:\n          branch: api-docs\n          commit_message: 'Update API docs for ${{ github.ref_name }} [skip ci]'\n          file_pattern: 'docs'\n          push_options: '--force'\n"
  },
  {
    "path": ".github/workflows/package-test.yml",
    "content": "name: Run tests and linter checks\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  run-checks:\n    runs-on: ubuntu-22.04\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n    steps:\n    - uses: actions/checkout@v5\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n        cache: pip\n    - name: Create a virtual environment\n      run: |\n        python -m venv .venv\n    - name: Install dependencies\n      run: |\n        source ./.venv/bin/activate\n        python -m pip install --upgrade pip\n        make install-dev\n    - name: Run tests and linter checks\n      run: |\n        source ./.venv/bin/activate\n        make check\n"
  },
  {
    "path": ".github/workflows/run-integration-tests.yml",
    "content": "name: Run Integration Tests\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened, edited]\n\njobs:\n  should-run:\n    name: Check if integration tests should run\n    runs-on: ubuntu-latest\n    outputs:\n      skip: ${{ steps.test-parameters.outputs.skip }}\n      dialects: ${{ steps.test-parameters.outputs.dialects }}\n\n    steps:\n      - name: Print debugging info\n        run: |\n          cat <<'EOF'\n            Github event name: ${{ github.event_name }}\n\n            Github event ${{ toJSON(github.event) }}\n\n            Github event comment body: ${{ github.event.comment.body }}\n\n            Github event pr body: ${{ github.event.pull_request.body }}\n\n            Generic Number: ${{ github.event.number }}\n\n            PR number: ${{ github.event.pull_request.number }}\n\n            Issue number: ${{ github.event.issue.number }}\n\n            SHA: ${{ github.sha }}\n\n            Head Ref ${{ github.head_ref }}\n\n            Ref Name: ${{ github.ref_name }}\n          EOF\n\n      - name: Checkout Code\n        uses: actions/checkout@v5\n        with:\n          # we need to checkout all refs so we can run `git diff`\n          fetch-depth: 0\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: '3.13'\n\n      - name: Check if integration tests should be run\n        id: test-parameters\n        run: |\n          python .github/scripts/get_integration_test_params.py\n\n  run-integration-tests:\n    name: Run Integration Tests\n    runs-on: ubuntu-latest\n    needs: should-run\n    if: needs.should-run.outputs.skip == 'false' && github.event.pull_request.head.repo.full_name == github.repository\n    steps:\n      - name: Acquire credentials\n        id: app-token\n        uses: actions/create-github-app-token@v2\n        with:\n          app-id: ${{ vars.INTEGRATION_TEST_CLIENT_ID }}\n          private-key: ${{ secrets.INTEGRATION_TEST_PRIVATE_KEY }}\n          owner: fivetran\n          repositories: sqlglot-integration-tests\n\n      - name: Run integration tests\n        id: run-remote\n        env:\n          GH_TOKEN: ${{ steps.app-token.outputs.token }}\n        run: |\n          set -e\n\n          CORRELATION_ID=$(python -c 'import uuid;print(uuid.uuid4())')\n          REMOTE_REPO=\"fivetran/sqlglot-integration-tests\"\n\n          echo \"Triggering remote workflow\"\n\n          gh workflow run run-tests.yml \\\n            --repo $REMOTE_REPO \\\n            --ref main \\\n            -f sqlglot_ref=${{ github.sha }} \\\n            -f sqlglot_pr_number=${{ github.event.number || github.event.issue.number }} \\\n            -f sqlglot_branch_name=${{ github.head_ref || github.ref_name }} \\\n            -f correlation_id=\"$CORRELATION_ID\" \\\n            -f dialects=\"${{ needs.should-run.outputs.dialects }}\"\n\n          echo \"Triggered workflow using correlation id: $CORRELATION_ID\"\n\n          # poll for run id\n          RUN_ID=\"\"\n          ATTEMPTS=0\n\n          while [ \"$RUN_ID\" == \"\" ]; do\n            sleep 5\n            ATTEMPTS=$((ATTEMPTS + 1))\n            if [ $ATTEMPTS -gt 10 ]; then\n              echo \"Timed out waiting for matching run to start\"\n              exit 1\n            fi\n\n            echo \"Checking for run\"\n            RUN_ID=$(gh run list \\\n              --repo $REMOTE_REPO \\\n              --event workflow_dispatch \\\n              --workflow run-tests.yml \\\n              --user sqlglot-integration-tests \\\n              --json displayTitle,databaseId \\\n              --limit 20 \\\n              -q '.[] | select(.displayTitle | contains(\"Correlation ID: '\"$CORRELATION_ID\"'\")) | .databaseId')\n          done\n\n          echo \"Using Run ID: ${RUN_ID}\"\n          echo \"remote_run_id=$RUN_ID\" >> $GITHUB_OUTPUT\n\n          echo \"Waiting for completion\"\n          gh run watch $RUN_ID \\\n            --repo fivetran/sqlglot-integration-tests \\\n            --interval 10 \\\n            --compact \\\n            --exit-status\n\n          # Fail the workflow on this side if the remote workflow fails\n          exit $?\n\n      - name: Fetch outputs\n        id: fetch-outputs\n        uses: actions/download-artifact@v5\n        with:\n          github-token: ${{ steps.app-token.outputs.token }}\n          repository: fivetran/sqlglot-integration-tests\n          run-id: ${{ steps.run-remote.outputs.remote_run_id }}\n          name: summary\n\n      - name: Write summary as comment\n        uses: actions/github-script@v8\n        # only do this when on PR branches, main builds dont have anywhere to write comments\n        if: github.event_name == 'pull_request' || github.event.issue.pull_request\n        with:\n          script: |\n            // summary.json is downloaded from the remote workflow in the previous step\n            const summary = require(\"./summary.json\");\n\n            // Add a unique identifier to find this comment later\n            const commentIdentifier = \"<!-- integration-test-summary -->\";\n            const body = `${commentIdentifier}\\n${summary.msg}`;\n\n            // Find existing comment\n            const { data: comments } = await github.rest.issues.listComments({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n            });\n\n            const existingComment = comments.find(comment =>\n              comment.body.includes(commentIdentifier)\n            );\n\n            if (existingComment) {\n              // Update existing comment\n              await github.rest.issues.updateComment({\n                comment_id: existingComment.id,\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                body: body\n              });\n            } else {\n              // Create new comment\n              await github.rest.issues.createComment({\n                issue_number: context.issue.number,\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                body: body\n              });\n            }\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n*.ipynb\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nvenv*/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# PyCharm\n.idea/\n\n# Visual Studio Code\n.vscode\n\n.DS_STORE\nmetastore_db\nspark_warehouse\n\n# Version file\nsqlglot/_version.py\n\n# Emacs files\n*~"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"sqlglot-integration-tests\"]\n\tpath = sqlglot-integration-tests\n\turl = git@github.com:fivetran/sqlglot-integration-tests.git\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "image: gitpod/workspace-python-3.11\ntasks:\n  - name: sqlglot\n    init: |\n      python -m venv .venv\n      source .venv/bin/activate\n      make install-dev\n\n    command: |\n      clear\n      source .venv/bin/activate\n\n\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: local\n    hooks:\n      - id: sync-integration-tests-commit\n        name: sync-integration-tests-commit\n        entry: .github/scripts/integration_tests_sync.sh commit\n        language: script\n        stages: [pre-commit]\n        always_run: true\n        pass_filenames: false\n      - id: sync-integration-tests-checkout\n        name: sync-integration-tests-checkout\n        entry: .github/scripts/integration_tests_sync.sh checkout\n        language: script\n        stages: [post-checkout]\n        always_run: true\n        pass_filenames: false\n      - id: sync-integration-tests-push\n        name: sync-integration-tests-push\n        entry: .github/scripts/integration_tests_sync.sh push\n        language: script\n        stages: [pre-push]\n        always_run: true\n        pass_filenames: false\n      - id: sync-integration-tests-merge\n        name: sync-integration-tests-merge\n        entry: .github/scripts/integration_tests_sync.sh merge\n        language: script\n        stages: [post-merge]\n        always_run: true\n        pass_filenames: false\n      - id: ruff\n        name: ruff\n        description: \"Run 'ruff' for extremely fast Python linting\"\n        entry: ruff check\n          --force-exclude --fix\n        language: python\n        types_or: [python, pyi]\n        require_serial: true\n        additional_dependencies: []\n      - id: ruff-format\n        name: ruff-format\n        description: \"Run 'ruff format' for extremely fast Python formatting\"\n        entry: ruff format\n        language: python\n        types_or: [python, pyi]\n        require_serial: true\n      - id: mypy\n        name: mypy\n        entry: mypy sqlglot tests\n        language: system\n        types: [ python ]\n        files: ^(sqlglot/|tests/)\n        pass_filenames: false\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# Contributing to [SQLGlot](https://github.com/tobymao/sqlglot/blob/main/README.md)\n## About SQLGlot\n\nSQLGlot is a no-dependency SQL parser, transpiler, optimizer, and engine written in pure Python. It supports 31+ SQL dialects and can transpile between them while preserving semantics. The codebase is performance-critical despite being pure Python, with an optional mypyc-compiled C extension for speed improvements (`sqlglotc/`).\n\n## Development Commands\nBefore contributing read CONTRIBUTING.md\n\n### Installation\n```bash\n# Basic installation\nmake install\n\n# Development installation (Python only, no C extension)\nmake install-dev\n\n# Development installation with mypyc C extension\nmake install-devc\n\n# Install pre-commit hooks\nmake install-pre-commit\n\n# With uv (faster):\nUV=1 make install-dev\n```\n\n### Testing\n```bash\n# Run all tests (pure Python, hides .so files during run)\nmake test\n\n# Run all tests with mypyc C extension (builds extension first)\nmake testc\n\n# Run only unit tests (skip integration tests, pure Python)\nmake unit\n\n# Run only unit tests with C extension\nmake unitc\n\n# Run specific test file\npython -m unittest tests.test_expressions\n\n# Run specific test class\npython -m unittest tests.test_expressions.TestExpressions\n\n# Run specific test method\npython -m unittest tests.test_expressions.TestExpressions.test_alias\n```\n\n### Linting & Type Checking\n```bash\n# Run linter and formatter only\nmake style\n\n# Run full checks (style + pure Python tests + C extension tests)\nmake check\n```\n\n### Benchmarks\n```bash\n# Run parsing benchmark\nmake bench\n\n# Run optimization benchmark\nmake bench-optimize\n```\n\n## Architecture Overview\n\nSQLGlot follows a classic compiler architecture with three main phases:\n\n### 1. Tokenizer (`tokens.py`)\n- Converts SQL strings into a sequence of tokens (lexical analysis)\n- Pure Python implementation in `tokens.py`; core logic in `tokenizer_core.py` (mypyc-compiled when using `[c]` extra)\n- Maps lexemes to `TokenType` enum values via `KEYWORDS` and `SINGLE_TOKENS` dictionaries\n- Dialects can override tokenizer behavior by customizing these mappings\n\n### 2. Parser (`parser.py`)\n- Converts tokens into an Abstract Syntax Tree (AST)\n- Uses recursive descent parsing approach\n- Parsing methods follow `_parse_*` naming convention (e.g., `_parse_create()`, `_parse_select()`)\n- Token matching methods: `_match()`, `_match_set()`, `_match_text_seq()`, `_match_texts()`\n- Helper methods for common patterns: `_parse_csv()`, `_parse_wrapped()`, `_parse_wrapped_csv()`\n- Maintains index/cursor with `_advance()` and `_retreat()` methods\n- Falls back to `exp.Command` for unparseable SQL (preserves original text)\n\n### 3. Generator (`generator.py`)\n- Converts AST back to SQL strings\n- Traverses AST recursively, generating SQL for each expression node\n- Two ways to customize generation:\n  - `TRANSFORMS` dictionary for single-line generations\n  - `<expr_name>_sql()` methods for complex generations\n- Helper methods: `expressions()`, `func()`, `rename_func()`\n- Use `sep()` and `seg()` for proper whitespace/newline handling in pretty-printed output\n\n### 4. Expressions (`expressions.py`)\n- Defines all AST node types as Python classes inheriting from `Expression`\n- Each expression represents a semantic SQL concept (e.g., `Select`, `Join`, `Column`)\n- Expressions can be traversed using `.find()`, `.find_all()`, `.walk()`, `.transform()`\n- Building SQL programmatically: use helper functions like `select()`, `from_()`, `where()`, etc.\n\n### 5. Dialects (`dialects/`)\n- 34 dialect implementations in `dialects/<dialect>.py`\n- Each dialect subclasses base `Dialect` and can override Tokenizer, Parser, and Generator\n- Base \"sqlglot\" dialect acts as a superset to minimize duplication\n- Dialect customization via:\n  - Feature flags (e.g., `SUPPORTS_IMPLICIT_UNNEST`)\n  - Token sets (e.g., `RESERVED_TOKENS`)\n  - `token -> Callable` mappings (e.g., `FUNCTIONS`, `STATEMENTS`)\n  - `Expression -> str` mappings in Generator\n\n### 6. Optimizer (`optimizer/`)\n- Canonicalizes and optimizes queries while preserving semantics\n- Applies sequential optimization rules (order matters!)\n- Key rules:\n  - `qualify`: Normalizes identifiers and qualifies all tables/columns (most important rule)\n  - `annotate_types`: Infers data types throughout the AST\n  - `pushdown_predicates`, `pushdown_projections`: Optimization rewrites\n  - `simplify`: Simplifies boolean expressions and arithmetic\n- Rules depend on schema information for best results\n- Optimizer performs logical optimization only (not physical/performance)\n\n### 7. Schema (`schema.py`)\n- Represents database structure (tables, columns, types)\n- Used by optimizer and lineage analysis\n- `MappingSchema` takes nested dict: `{\"table\": {\"col\": \"type\"}}`\n\n### 8. Lineage (`lineage.py`)\n- Traces column-level lineage through queries\n- Requires target query, upstream queries, and root table schemas\n- Builds linked list of `Node` objects representing data flow\n- Can visualize with `node.to_html()`\n\n## Key Concepts\n\n### The \"sqlglot\" Dialect\n- Base dialect that accommodates common syntax across all dialects\n- All other dialects extend this base\n- When adding multi-dialect features, prefer adding to base dialect to avoid duplication\n- Only add dialect-specific features to individual dialect classes\n\n### AST-First Approach\n- SQLGlot preserves _semantics_ not syntax\n- Parse SQL → AST (semantic representation) → Generate SQL in target dialect\n- This enables accurate cross-dialect transpilation\n- Comments are preserved on best-effort basis\n- See `posts/ast_primer.md` for detailed AST tutorial\n\n### Testing Philosophy\n- Comprehensive test suite in `tests/` directory\n- Dialect-specific tests in `tests/dialects/`\n- Tests are critical - \"robust test suite\" is a core feature\n- Use `tests/fixtures/` for test data\n- `tests/helpers.py` contains test utilities\n\n### Parser/Generator Symmetry\n- Parser: `token -> Callable` mappings (builds AST from tokens)\n- Generator: `Expression -> str` mappings (builds SQL from AST)\n- Customization follows similar patterns in both\n\n### Type Annotations\n- Type inference is crucial for some transpilations (e.g., `+` can mean addition or concatenation)\n- Optimizer's `annotate_types` rule propagates type information through AST\n- Requires schema information to work effectively\n\n## Common Usage Patterns\n\n### Reading SQL\n```python\nimport sqlglot\nexpression = sqlglot.parse_one(\"SELECT * FROM table\", dialect=\"spark\")\n```\n\n### Validate Function Expression\n```python\nimport sqlglot\ntree = sqlglot.parse_one(\"SELECT NULLIF(1, 2)\", dialect=\"snowflake\")\nif \"Anonymous\" in repr(tree):\n    print(\"Function expression exists\")\nelse:\n    print(\"Function expression does not exist\")\n```\n\n### Writing SQL\n```python\nexpression.sql(dialect=\"duckdb\", pretty=True)\n```\n\n### Building SQL Programmatically\n```python\nfrom sqlglot import select, condition\nselect(\"*\").from_(\"y\").where(condition(\"x=1\").and_(\"y=1\")).sql()\n```\n\n### Traversing AST\n```python\nfrom sqlglot import parse_one, exp\ntree = parse_one(\"SELECT a, b + 1 AS c FROM d\", dialect=\"dialect\")\nfor column in tree.find_all(exp.Column):\n    print(column.alias_or_name)\n```\n\n### Transforming AST\n```python\ndef transformer(node):\n    if isinstance(node, exp.Column) and node.name == \"a\":\n        return parse_one(\"FUN(a)\", dialect=\"dialect\")\n    return node\n\ntransformed = tree.transform(transformer)\n```\n\n## Development Guidelines\n\n- Follow [Conventional Commits](https://www.conventionalcommits.org/) for PR titles\n- If you are an agent, you must add your model [CLAUDE, CODEX, etc...] to the end of PR titles and commit messages.\n- Keep PRs minimal in scope - one well-defined change per PR\n- Add tests for non-trivial changes\n- Update docstrings if APIs change\n- Run `make check` before submitting\n- Use comments for complex logic only\n- Don't change license or license files in setup.py, there's nothing wrong with it.\n\n## Important Files\n\n- `posts/ast_primer.md`: Detailed AST tutorial\n- `posts/onboarding.md`: Architecture deep-dive (HIGHLY RECOMMENDED)\n- `.pre-commit-config.yaml`: Pre-commit hooks (ruff, ruff-format, mypy)\n- `pyproject.toml`: Project metadata and build config\n- `Makefile`: All development commands\n\n## Performance Considerations\n\n- Pure Python implementation with optional mypyc-compiled C extension (`sqlglotc/`)\n- Install with `pip install \"sqlglot[c]\"` for C extension speed boost\n- The `[c]` extra compiles core modules (`expression_core`, `tokenizer_core`, `parser_core`, etc.) via mypyc\n- Performance is a key feature despite Python implementation\n- Benchmarks compare against other SQL parsers - see `benchmarks/`\n- Avoid use of typing.Protocol, prefer Union Type and Duck Typing\n- Never suggest Pratt parsing as an optimization for the parser. The recursive descent approach is intentional and preferred.\n\n---\n\n## SQLGlot Coding Rules\n\nThe following patterns are based on PR review feedback. Follow these to minimize review iterations.\n\n### 1. Use Automatic Naming Convention for Generator Methods\n\n**Don't do this (module-level function with TRANSFORMS):**\n```python\ndef _my_func_sql(self: MyDialect.Generator, expression: exp.MyFunc) -> str:\n    ...\n\nclass Generator:\n    TRANSFORMS = {\n        exp.MyFunc: _my_func_sql,\n    }\n```\n\n**Don't do this (method with TRANSFORMS):**\n```python\nclass Generator:\n    TRANSFORMS = {\n        exp.MyFunc: lambda self, e: self._my_func_sql(e),\n    }\n\n    def _my_func_sql(self, expression):\n        ...\n```\n\n**Do this (auto-discovered method):**\n```python\nclass Generator:\n    # No TRANSFORMS entry needed - automatic discovery by name\n\n    def myfunc_sql(self, expression: exp.MyFunc) -> str:\n        ...\n```\n\nGenerator methods named `<lowercase_expr_name>_sql` are automatically discovered.\n\nImportant: Only use TRANSFORMS for simple one-liners like `rename_func(\"OTHER_NAME\")` or lambdas or functions with multiple entry points. For any single entry point function, always use an auto-discovered method inside the Generator class.\n\nSQLGlot automatically applies transformations based on the structure of the name, but when this fails, you must rename the function.  This is only when the SQL name is not covered by auto mapping:\n\n**Do this:**\n```python\nclass Generator:\n    TRANSFORMS = {\n        exp.ArrayLength: rename_func(\"LENGTH\"),\n    }\n```\n\n**Don't do this:**\n```python\nexp.ArrayLength: lambda self, e: self.func(\"LENGTH\", e.this),\n```\n\n### 2. Use Existing Expression Classes, Not Anonymous\n\n**Don't do this:**\n```python\nfrom_base64 = exp.Anonymous(this=\"FROM_BASE64\", expressions=[input_expr])\n```\n\n**Do this:**\n```python\nfrom_base64 = exp.FromBase64(this=input_expr)\n```\n\nAlways check if an expression class exists in `expressions.py` before using `exp.Anonymous`. Anonymous should only be used for functions that don't have a dedicated class. Search for the function name in expressions.py first.\n\n### 3. SQL Generation: Choose the Right Approach\n\nUse the appropriate method based on complexity. From simplest to most complex:\n\n#### Level 1: Generator Helper Methods\nFor generating function calls in generator methods, use `self.func()`:\n```python\ndef myfunc_sql(self, expression):\n    # Don't: return self.sql(exp.Func(this=\"MY_FUNC\", expressions=[expression.this]))\n    # Do:\n    return self.func(\"MY_FUNC\", expression.this)\n```\n\n#### Level 2: Expression Builders\nFor building expressions, use helper functions instead of direct class construction:\n\n| Helper | Instead of | Benefits |\n|--------|-----------|----------|\n| `exp.func(\"name\", *args)` | `exp.Anonymous(...)` | Finds proper Func class |\n| `exp.array(e1, e2, ...)` | `exp.Array(expressions=[...])` | Parses automatically |\n| `exp.and_(e1, e2, ...)` | `exp.And(this=..., expression=...)` | Handles nesting |\n| `exp.or_(e1, e2, ...)` | `exp.Or(this=..., expression=...)` | Handles nesting |\n| `exp.case().when(cond, val).else_(default)` | `exp.Case(ifs=[...])` | Fluent interface |\n| `exp.cast(expr, \"TYPE\")` | `exp.Cast(this=..., to=...)` | Builds DataType |\n| `exp.column(\"col\", \"table\")` | `exp.Column(...)` | Handles identifiers |\n| `exp.null()` | `exp.Null()` | Simple factory |\n\nAlso use expression operators for cleaner code:\n```python\n# Arithmetic: exp.column(\"x\") + 1  instead of  exp.Add(this=..., expression=...)\n# Indexing:   arr[index]           instead of  exp.Bracket(this=arr, expressions=[index])\n# Comparison: arg.is_(exp.Null())  instead of  exp.Is(this=arg, expression=exp.Null())\n```\n\n#### Level 3: SQL Templates\nWhen expressions become complex, use templates with `exp.maybe_parse()` and `exp.replace_placeholders()`:\n\n```python\n# Define template with :placeholder syntax\nMY_TEMPLATE: exp.Expression = exp.maybe_parse(\n    \"CASE WHEN :arg IS NULL THEN NULL ELSE :result END\"\n)\n\n# In generator method\ndef myfunc_sql(self, expression):\n    result = exp.replace_placeholders(\n        self.MY_TEMPLATE.copy(),\n        arg=expression.this,\n        result=some_expression,\n    )\n    return self.sql(result)\n```\n\n#### Avoid: F-strings with SQL Fragments\nYou should rarely, if ever, build SQL with f-strings - it breaks quoting, escaping, and dialect handling:\n```python\n# NEVER do this:\ndef my_func_sql(self, expression):\n    return f\"CAST({self.sql(expression.this)} AS TIME)\"\n\n# Do this instead:\ndef my_func_sql(self, expression):\n    return self.sql(exp.cast(expression.this, \"TIME\"))\n```\n\n### 4. Type Checking: `is_string` vs `is_type()`\n\nThese serve **different purposes**:\n\n**`is_type()`** - Semantic type check:\n```python\n# Returns True if expression's type is text (columns, function results, etc.)\n# Requires annotate_types() to populate type info\nif arg.is_type(*exp.DataType.TEXT_TYPES):\n    ...\n```\n\n**`is_string`** - Syntactic check for string literals:\n```python\n# Returns True only for literal strings like 'hello'\n# Works without type annotation\nif arg.is_string:\n    value = arg.name  # Extract the string value\n```\n\n\n**When to use each:**\n\n| Use Case | Method |\n|----------|--------|\n| Check if node is a string literal to extract its value | `is_string` |\n| Check if node is a literal vs column/expression | `is_string` |\n| Check semantic type (works for columns, functions) | `is_type()` |\n| Cover both literals and typed expressions | `is_string or is_type()` |\n\n**Combined pattern (from `length_sql`):**\n```python\n# Fast check for string literals (no annotation needed)\nif arg.is_string:\n    return self.func(\"LENGTH\", arg)\n\n# For non-literals, get type info if needed\nif not arg.type:\n    arg = annotate_types(arg, dialect=self.dialect)\n\n# Then check semantic type\nif arg.is_type(*exp.DataType.TEXT_TYPES):\n    return self.func(\"LENGTH\", arg)\n```\n\n**Don't do direct type comparisons:**\n```python\n# Bad\nif input_expr.type and input_expr.type.this in exp.DataType.TEXT_TYPES:\n\n# Good\nif input_expr.is_type(*exp.DataType.TEXT_TYPES):\n```\n\n### 5. Use `to_py()` for Literal Value Extraction\n\n**Don't do this:**\n```python\nif isinstance(arg, exp.Literal):\n    value = int(arg.this.strip(\"'\"))\n```\n\n**Do this:**\n```python\nif isinstance(arg, exp.Literal) and arg.is_number:\n    value = int(arg.to_py())\n```\n\n### 6. Avoid Compile-Time NULL Checks\n\nDon't check for `exp.Null()` or literal NULL values in Python during transpilation. NULL handling should happen at query time in the generated SQL using `IS NULL` checks.\n\n**Don't do this:**\n```python\ndef myfunc_sql(self, expression):\n    # Bad: checking for literal NULL at transpile time\n    if any(isinstance(arg, exp.Null) for arg in expression.expressions):\n        return self.sql(exp.Null())\n```\n\n**Do this:**\n```python\n# Good: generate SQL that handles NULL at query time\nTEMPLATE = exp.maybe_parse(\"CASE WHEN :arg IS NULL THEN NULL ELSE ... END\")\n```\n\nCompile-time checks only handle literal `NULL` values in the SQL text, not NULL values that come from columns, parameters, or expressions at runtime. Generate SQL with `IS NULL` checks to handle all cases.\n\n### 7. Type Annotations in Tests\n\nWhen transpilation depends on `is_type()` checks, tests need `annotate_types()`:\n\n```python\nfrom sqlglot.optimizer import annotate_types\n\n# Without annotation - is_type() returns False for literals\nexpr = self.validate_identity(\"SELECT BASE64_ENCODE('Hello World')\")\n\n# With annotation - types are inferred, is_type() works\nannotated = annotate_types(expr, dialect=\"snowflake\")\nself.assertEqual(annotated.sql(\"duckdb\"), \"SELECT TO_BASE64(ENCODE('Hello World'))\")\n```\n\n### 8. Use `find_ancestor` with Scope Boundaries\n\nWhen searching for ancestors, include scope boundaries to avoid crossing into parent queries:\n\n```python\n# Stop at Select to stay within current query scope\nancestor = expression.find_ancestor(exp.Where, exp.Having, exp.Select)\nif ancestor and not isinstance(ancestor, exp.Select):\n    # Found restricted context within current scope\n    ...\n```\n\n### 9. Use @unsupported_args for unsupported arguments\n\nWhen arguments are not supported do this:\n\n```python\n        @unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\")\n        def levenshtein_sql(self, expression: exp.Levenshtein) -> str:\n```\n\n### 10. Keep Code Minimal\n\n- Remove unused imports, variables, and dead code\n- Don't add comments for obvious code\n- Don't add docstrings unless the function is complex or public API\n- Prefer inline expressions over intermediate variables when readable\n- Don't add backwards-compatibility shims for removed code\n\n### 11. Test Patterns\n\n- Add tests to the appropriate dialect test file (e.g., `tests/dialects/test_snowflake.py`)\n- Use `self.validate_all()` for cross-dialect tests\n- Use `self.validate_identity()` for round-trip tests\n- Don't add tests for functionality that already has coverage\n\n### 12. Ensure Test Validity\n\n- Make sure all tests added to tests/dialects/*.py actually run in the relevant databases, such as snowflake or duckdb\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Changelog\n=========\n\n## [v30.0.3] - 2026-03-19\n### :zap: Performance Improvements\n- [`f87ebe0`](https://github.com/tobymao/sqlglot/commit/f87ebe02103b249ec5fa2c93e019e465f77630be) - use mypyc i64 for parser index fields (~1.6% faster) *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`52bca33`](https://github.com/tobymao/sqlglot/commit/52bca33e9395c4f6f621649180f2576eb8591dba) - **lineage**: improve error message when column source index is out of range *(PR [#7336](https://github.com/tobymao/sqlglot/pull/7336) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#7332](https://github.com/tobymao/sqlglot/issues/7332) opened by [@paultiq](https://github.com/paultiq)*\n\n\n## [v30.0.2] - 2026-03-19\n### :boom: BREAKING CHANGES\n- due to [`936617e`](https://github.com/tobymao/sqlglot/commit/936617e749f969b04da318ec02e1086a01212e92) - escape comment markers in sanitize_comment for all dialects *(PR [#7301](https://github.com/tobymao/sqlglot/pull/7301) by [@llimllib](https://github.com/llimllib))*:\n\n  escape comment markers in sanitize_comment for all dialects (#7301)\n\n- due to [`4f6bcd3`](https://github.com/tobymao/sqlglot/commit/4f6bcd3d21cf34346db4c7fc9936302d34a802e2) - Add transpilation support for ARRAY_TO_STRING function *(PR [#7289](https://github.com/tobymao/sqlglot/pull/7289) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add transpilation support for ARRAY_TO_STRING function (#7289)\n\n\n### :sparkles: New Features\n- [`4f6bcd3`](https://github.com/tobymao/sqlglot/commit/4f6bcd3d21cf34346db4c7fc9936302d34a802e2) - **duckdb**: Add transpilation support for ARRAY_TO_STRING function *(PR [#7289](https://github.com/tobymao/sqlglot/pull/7289) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n### :bug: Bug Fixes\n- [`b41a99a`](https://github.com/tobymao/sqlglot/commit/b41a99a1405a5749a5876a64a559bd040ba618a5) - **duckdb**: FROM pipe syntax in subquery *(PR [#7311](https://github.com/tobymao/sqlglot/pull/7311) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7305](https://github.com/tobymao/sqlglot/issues/7305) opened by [@paultiq](https://github.com/paultiq)*\n- [`936617e`](https://github.com/tobymao/sqlglot/commit/936617e749f969b04da318ec02e1086a01212e92) - escape comment markers in sanitize_comment for all dialects *(PR [#7301](https://github.com/tobymao/sqlglot/pull/7301) by [@llimllib](https://github.com/llimllib))*\n- [`6ddaee3`](https://github.com/tobymao/sqlglot/commit/6ddaee3ccb92f660327501884acf103fad782e07) - **expressions**: restore Expression.alias behaviour for non-Identifier alias nodes *(PR [#7310](https://github.com/tobymao/sqlglot/pull/7310) by [@treff7es](https://github.com/treff7es))*\n- [`ce08047`](https://github.com/tobymao/sqlglot/commit/ce0804717876889c731f2ab64035c70a85f9b294) - **snowflake**: ILIKE/LIKE ANY/ALL with single element transpilation to duckdb *(PR [#7314](https://github.com/tobymao/sqlglot/pull/7314) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7306](https://github.com/tobymao/sqlglot/issues/7306) opened by [@ultrabear](https://github.com/ultrabear)*\n- [`8a52de6`](https://github.com/tobymao/sqlglot/commit/8a52de63fb908b55df0143e3ec63f3ae37aa4fd8) - **parser**: Add builder for ARRAY_INTERSECT *(PR [#7328](https://github.com/tobymao/sqlglot/pull/7328) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#7326](https://github.com/tobymao/sqlglot/issues/7326) opened by [@ADBond](https://github.com/ADBond)*\n- [`f8a7ab2`](https://github.com/tobymao/sqlglot/commit/f8a7ab2724cbb75d40355c79c8f68003ee2a5c7e) - **parser**: Do not consume constraints following UNIQUE *(PR [#7330](https://github.com/tobymao/sqlglot/pull/7330) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#7327](https://github.com/tobymao/sqlglot/issues/7327) opened by [@chunyangfeng](https://github.com/chunyangfeng)*\n- [`64509a2`](https://github.com/tobymao/sqlglot/commit/64509a2a3e7606eab57d714e63424f235c78a6b5) - sqlglotc sdist install fails when ../sqlglot dir doesn't exist *(PR [#7337](https://github.com/tobymao/sqlglot/pull/7337) by [@tobymao](https://github.com/tobymao))*\n  - :arrow_lower_right: *fixes issue [#7333](https://github.com/tobymao/sqlglot/issues/7333) opened by [@ZipBrandon](https://github.com/ZipBrandon)*\n\n### :zap: Performance Improvements\n- [`0ac52aa`](https://github.com/tobymao/sqlglot/commit/0ac52aa80241a8ed4049948f2ee0c19c8dc64279) - move instance variables to __init__ for perf *(commit by [@tobymao](https://github.com/tobymao))*\n- [`c95ae50`](https://github.com/tobymao/sqlglot/commit/c95ae50fa122e01c914c92e9d847ff390516e1a4) - optimize parser for nested function calls (-41% on nested_funct… *(PR [#7307](https://github.com/tobymao/sqlglot/pull/7307) by [@tobymao](https://github.com/tobymao))*\n- [`1697bc3`](https://github.com/tobymao/sqlglot/commit/1697bc3a67c10314c5640ed7d52c40063db70f10) - optimize parser fast path for simple table references *(commit by [@tobymao](https://github.com/tobymao))*\n- [`18f15ca`](https://github.com/tobymao/sqlglot/commit/18f15ca96c7bdebe5798e407993495c0a9e9bd43) - inline token parsing for massive gains *(PR [#7335](https://github.com/tobymao/sqlglot/pull/7335) by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`fe7874f`](https://github.com/tobymao/sqlglot/commit/fe7874f3cb5dd7899a438249f65b51b684a056f6) - update changelog with v30 release notes *(PR [#7309](https://github.com/tobymao/sqlglot/pull/7309) by [@georgesittas](https://github.com/georgesittas))*\n- [`8a914f0`](https://github.com/tobymao/sqlglot/commit/8a914f09ed3534ecc9998b6ce84f74e2f15909e4) - use ProcessPoolExecutor in test_executor *(PR [#7313](https://github.com/tobymao/sqlglot/pull/7313) by [@georgesittas](https://github.com/georgesittas))*\n- [`604fe3f`](https://github.com/tobymao/sqlglot/commit/604fe3f1770715baf8b461344cffa44eaf017fc2) - improve integration test submodule automations *(PR [#7320](https://github.com/tobymao/sqlglot/pull/7320) by [@georgesittas](https://github.com/georgesittas))*\n- [`c7b55c1`](https://github.com/tobymao/sqlglot/commit/c7b55c1998e7aaea7a4950e8ee347db7b0ec4af3) - Fix uv sync failing for sqlglotc *(PR [#7322](https://github.com/tobymao/sqlglot/pull/7322) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#7321](https://github.com/tobymao/sqlglot/issues/7321) opened by [@OutSquareCapital](https://github.com/OutSquareCapital)*\n- [`158424c`](https://github.com/tobymao/sqlglot/commit/158424cda0cc908c9f3687cc466194fa31cafb1b) - update readme *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v30.0.1] - 2026-03-16\n### :bug: Bug Fixes\n- [`7bcad5c`](https://github.com/tobymao/sqlglot/commit/7bcad5c61673a8567e9c22d059ed4326e6698457) - auto pin sqlglotc version to sqlglot so there's no version mismatch closes [#7304](https://github.com/tobymao/sqlglot/pull/7304) *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v30.0.0] - 2026-03-16\n\nSQLGlot v30 is a major release focused on performance and compilation. Some of the core components of the library are now fully compilable by [mypyc](https://mypyc.readthedocs.io/), delivering significant speedups when installed with the `[c]` extra. This required restructuring several internal modules, which introduces breaking changes for users who depend on internal APIs, subclass parsers, or import from internal paths.\n\n> **If you only use the public API** (`sqlglot.parse`, `sqlglot.parse_one`, `sqlglot.transpile`, `sqlglot.exp.*`, `sqlglot.optimizer.*`), most code will work without changes.\n---\n\n## Migration Guide\n\n### 1. Rust tokenizer removed — use `[c]` instead of `[rs]`\n\nThe Rust-based tokenizer (`sqlglotrs`) has been removed (since v29) and replaced with a mypyc-compiled C extension (`sqlglotc`).\n\n```bash\n# Before\npip install \"sqlglot[rs]\"\n\n# After\npip install \"sqlglot[c]\"\n```\n\nThe `[rs]` extra still installs but is now a deprecated no-op stub. The following APIs are removed:\n- `use_rs_tokenizer` parameter and attribute on `Tokenizer`\n- `RsTokenizer`, `RsTokenizerSettings`, `RsTokenTypeSettings` imports\n- `USE_RS_TOKENIZER` constant from `tokens.py`\n\n### 2. `expressions.py` split into a package\n\nThe monolithic `sqlglot/expressions.py` has been split into `sqlglot/expressions/` with submodules:\n\n| Module | Contents |\n|---|---|\n| `core.py` | `Expr`, `Expression`, `Condition`, `Func`, `AggFunc`, `Column`, `Literal`, etc. |\n| `datatypes.py` | `DataType`, `DType`, `DataTypeParam`, `Interval` |\n| `query.py` | `Select`, `Query`, `SetOperation`, `UDTF`, `Subquery` |\n| `ddl.py` | `Create`, `Alter`, `Drop`, DDL statements |\n| `dml.py` | `Insert`, `Update`, `Delete`, `Merge` |\n| `properties.py` | All `*Property` classes, `PropertiesLocation` |\n| `constraints.py` | All `*ColumnConstraint` classes |\n| `math.py` | Arithmetic operators (`Add`, `Sub`, `Mul`, `Div`, etc.) |\n| `string.py` | String functions (`Concat`, `Length`, `Upper`, etc.) |\n| `temporal.py` | Date/time functions (`DateAdd`, `DateDiff`, etc.) |\n| `aggregate.py` | Aggregate functions (`Count`, `Sum`, `Avg`, etc.) |\n| `array.py` | Array functions (`ArrayAgg`, `Explode`, etc.) |\n| `json.py` | JSON functions (`JSONExtract`, etc.) |\n| `functions.py` | Other functions (`Coalesce`, `If`, `Case`, `Cast`, etc.) |\n| `builders.py` | Builder helpers (`select()`, `from_()`, `condition()`, etc.) |\n\n**Backwards-compatibility:** `from sqlglot.expressions import *` and `from sqlglot import expressions as exp` still work, because everything is re-exported from `expressions/__init__.py`. However, if you were importing from `sqlglot.expressions` by relying on it being a single file (e.g., inspecting `__file__`), that will break.\n\n### 3. `Parser.expression()` no longer accepts `**kwargs`\n\nThis affects anyone subclassing `Parser` or calling `self.expression()` in custom parse methods.\n\n```python\n# Before\nself.expression(exp.Select, distinct=True, expressions=cols)\n\n# After\nself.expression(exp.Select(distinct=True, expressions=cols))\n```\n\nThe expression instance is now constructed by the caller and passed directly. This eliminates `**kwargs` dict allocation overhead.\n\n### 4. Scope traversal: `bfs` parameter removed\n\nThe `bfs` parameter has been removed from all scope traversal functions. Traversal is now always depth-first (DFS).\n\n```python\n# Before\nscope.walk(bfs=True)\nscope.find(exp.Column, bfs=False)\nwalk_in_scope(expr, bfs=True)\n\n# After\nscope.walk()\nscope.find(exp.Column)\nwalk_in_scope(expr)\n```\n\n**Behavioral change:** The old default was `bfs=True`. Now traversal is always DFS. Code that depended on BFS ordering from these functions will get results in a different order.\n\nAffected functions: `Scope.walk()`, `Scope.find()`, `Scope.find_all()`, `walk_in_scope()`, `find_in_scope()`, `find_all_in_scope()`.\n\n### 5. Dialect metaclass no longer mutates Parser token sets\n\nPreviously, the `_Dialect` metaclass dynamically modified parser token sets (`ID_VAR_TOKENS`, `TABLE_ALIAS_TOKENS`, `NO_PAREN_FUNCTIONS`) at class creation time based on dialect flags like `SUPPORTS_SEMI_ANTI_JOIN`. These mutations are removed — each parser now declares its token sets statically.\n\n- `Dialect.SUPPORTS_SEMI_ANTI_JOIN` has been removed.\n- `SHOW_TRIE` / `SET_TRIE` are no longer auto-computed from `SHOW_PARSERS` / `SET_PARSERS`.\n\n### 6. Use `Expr` instead of `Expression` for generic `isinstance` checks\n\nBase classes like `Func`, `Condition`, `Binary`, and other traits now inherit from `Expr` directly, not from `Expression`. This means `isinstance(node, exp.Expression)` will **not** match these trait classes. If your code uses `isinstance` to check for \"any AST node\", switch to `exp.Expr`:\n\n```python\n# Before\nisinstance(node, exp.Expression)\n\n# After\nisinstance(node, exp.Expr)\n```\n\n### 7. Compiled classes cannot be subclassed (when using `[c]`)\n\nWhen `sqlglot[c]` is installed, many core classes are compiled via mypyc. **Compiled classes cannot be subclassed at runtime** — class definition succeeds, but instantiation raises `TypeError: interpreted classes cannot inherit from compiled`.\n\n**Affected classes (compiled):**\n\n| Class | Subclassable? |\n|---|---|\n| All parsers (`BigQueryParser`, `SnowflakeParser`, etc.) | No |\n| `Parser` (base) | No |\n| `Expression`, `Expr`, and all AST nodes (`Select`, `Column`, `Func`, etc.) | No |\n| `MappingSchema`, `AbstractMappingSchema` | No |\n| `Scope` | No |\n| Optimizer rules (`scope.py`, `qualify.py`, `qualify_columns.py`, etc.) | No |\n\n**Not compiled (still subclassable):**\n\n| Class | Subclassable? |\n|---|---|\n| `Generator` and all dialect generators | Yes |\n| `Tokenizer` and all dialect tokenizers | Yes |\n| `Dialect` and all dialect classes | Yes |\n\nIf you need to subclass compiled classes (parsers, expressions, schema, etc.), install the pure Python version instead:\n\n```bash\npip install sqlglot        # pure Python — full subclassing support\npip install \"sqlglot[c]\"   # compiled — faster, but no subclassing\n```\n\n### :boom: BREAKING CHANGES\n- due to [`8ee0646`](https://github.com/tobymao/sqlglot/commit/8ee0646baa6dfae7e96ca86e2c1af5d53fc04290) - Transpile numeric literals with underscores from ClickHouse to other dialects *(PR [#7132](https://github.com/tobymao/sqlglot/pull/7132) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Transpile numeric literals with underscores from ClickHouse to other dialects (#7132)\n\n- due to [`81a3763`](https://github.com/tobymao/sqlglot/commit/81a37636c374690d12fe0b57d78adf2310daf3cb) - cast string literals to TIMESTAMP in TO_CHAR generation *(PR [#7127](https://github.com/tobymao/sqlglot/pull/7127) by [@marconae](https://github.com/marconae))*:\n\n  cast string literals to TIMESTAMP in TO_CHAR generation (#7127)\n\n- due to [`dff662a`](https://github.com/tobymao/sqlglot/commit/dff662a1389bdfbe6c331ca31dd37f76a6353429) - add transpilation support for ARRAY_GENERATE_RANGE function *(PR [#7107](https://github.com/tobymao/sqlglot/pull/7107) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  add transpilation support for ARRAY_GENERATE_RANGE function (#7107)\n\n- due to [`8e6b61f`](https://github.com/tobymao/sqlglot/commit/8e6b61f18e465b24ad9e20a8e1509486177fbb32) - transpilation support MAP_DELETE *(PR [#7139](https://github.com/tobymao/sqlglot/pull/7139) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support MAP_DELETE (#7139)\n\n- due to [`351e958`](https://github.com/tobymao/sqlglot/commit/351e958c3cd8a1395826ef624979275b246490f8) - fix parsing error in json_extract for exasol *(PR [#7098](https://github.com/tobymao/sqlglot/pull/7098) by [@nnamdi16](https://github.com/nnamdi16))*:\n\n  fix parsing error in json_extract for exasol (#7098)\n\n- due to [`1b1db57`](https://github.com/tobymao/sqlglot/commit/1b1db5728903d6468eba3d07da2a468395bf628b) - transpilation support MAP_SIZE *(PR [#7146](https://github.com/tobymao/sqlglot/pull/7146) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support MAP_SIZE (#7146)\n\n- due to [`0b4e26b`](https://github.com/tobymao/sqlglot/commit/0b4e26b8e7045bcf68992b6a2c8a5fd51b8262d7) - annotate EXTRACT(expr) for DuckDB *(PR [#7154](https://github.com/tobymao/sqlglot/pull/7154) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate EXTRACT(expr) for DuckDB (#7154)\n\n- due to [`95d7d20`](https://github.com/tobymao/sqlglot/commit/95d7d2052b1ed8fc64a99d557b8b085ad466100e) - annotate `TO_TIMESTAMP` as `TIMESTAMPTZ` fixes [#7155](https://github.com/tobymao/sqlglot/pull/7155) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  annotate `TO_TIMESTAMP` as `TIMESTAMPTZ` fixes #7155\n\n- due to [`7cc4332`](https://github.com/tobymao/sqlglot/commit/7cc43327ba72b3a1af6d8f2f489a97b997748ee9) - support transpilation of function RIGHT from Snowflake to DuckDB *(PR [#7148](https://github.com/tobymao/sqlglot/pull/7148) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of function RIGHT from Snowflake to DuckDB (#7148)\n\n- due to [`ad9d114`](https://github.com/tobymao/sqlglot/commit/ad9d114aea7f7553485631372d99ec5e5cf85045) - Enable transpilation for ARRAY_POSITION function *(PR [#7153](https://github.com/tobymao/sqlglot/pull/7153) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Enable transpilation for ARRAY_POSITION function (#7153)\n\n- due to [`684ff4a`](https://github.com/tobymao/sqlglot/commit/684ff4a13b1220fd5d3c0ec597cbdc630a3b9c03) - support arrayExcept for ClickHouse *(PR [#7161](https://github.com/tobymao/sqlglot/pull/7161) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support arrayExcept for ClickHouse (#7161)\n\n- due to [`74fd80c`](https://github.com/tobymao/sqlglot/commit/74fd80cdf055c828cedfac43b4b54132d18558bb) - split up expressions.py *(PR [#7160](https://github.com/tobymao/sqlglot/pull/7160) by [@tobymao](https://github.com/tobymao))*:\n\n  split up expressions.py (#7160)\n\n- due to [`d5840c5`](https://github.com/tobymao/sqlglot/commit/d5840c53f6a359def002c0b634a48706519b11e7) - support transpilation of RANDOM from Snowflake to DuckDB *(PR [#7163](https://github.com/tobymao/sqlglot/pull/7163) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of RANDOM from Snowflake to DuckDB (#7163)\n\n- due to [`07ef171`](https://github.com/tobymao/sqlglot/commit/07ef1711fdff0fa8f877b93f5be8921424eed438) - Add support for multiple-suffix combined aggregate functions in Clickhouse dialect *(PR [#7109](https://github.com/tobymao/sqlglot/pull/7109) by [@emanb29](https://github.com/emanb29))*:\n\n  Add support for multiple-suffix combined aggregate functions in Clickhouse dialect (#7109)\n\n- due to [`280e247`](https://github.com/tobymao/sqlglot/commit/280e24726be22a4f06261168d5dfc74b361dd04d) - Add transpilation support for NULLs and mutiset semantics in ARRAY_INTERSECTION *(PR [#7145](https://github.com/tobymao/sqlglot/pull/7145) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add transpilation support for NULLs and mutiset semantics in ARRAY_INTERSECTION (#7145)\n\n- due to [`542e392`](https://github.com/tobymao/sqlglot/commit/542e3920fb7d232b523e950820750e549a8d909a) - Add transpilation support for NULLs and mutiset semantics in ARRAY_INTERSECTION *(PR [#7145](https://github.com/tobymao/sqlglot/pull/7145) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add transpilation support for NULLs and mutiset semantics in ARRAY_INTERSECTION (#7145)\n\n- due to [`363167c`](https://github.com/tobymao/sqlglot/commit/363167c6609fa80b6014851b4415adca17b00df4) - parse FILE column *(PR [#7184](https://github.com/tobymao/sqlglot/pull/7184) by [@geooo109](https://github.com/geooo109))*:\n\n  parse FILE column (#7184)\n\n- due to [`f630d75`](https://github.com/tobymao/sqlglot/commit/f630d7579231f29fa5637b48f1be0b5665eb36b3) - support dotcolon with JSON *(PR [#7191](https://github.com/tobymao/sqlglot/pull/7191) by [@geooo109](https://github.com/geooo109))*:\n\n  support dotcolon with JSON (#7191)\n\n- due to [`29399bb`](https://github.com/tobymao/sqlglot/commit/29399bbed44a74d95257040fd36f0a0f6de7c7d8) - remove invalid group by distinct during custom transformation of group by all *(PR [#7197](https://github.com/tobymao/sqlglot/pull/7197) by [@nnamdi16](https://github.com/nnamdi16))*:\n\n  remove invalid group by distinct during custom transformation of group by all (#7197)\n\n- due to [`e0947ad`](https://github.com/tobymao/sqlglot/commit/e0947adcaeb1e3cb829e584e0b071c598c64cfa9) - set default window frame for certain Snowflake ranking functions during transpilation *(PR [#7195](https://github.com/tobymao/sqlglot/pull/7195) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  set default window frame for certain Snowflake ranking functions during transpilation (#7195)\n\n- due to [`4a3254f`](https://github.com/tobymao/sqlglot/commit/4a3254fbd1bfee7aa6787d3fc31832d8e9771932) - transpilation support MAP_PICK  *(PR [#7189](https://github.com/tobymao/sqlglot/pull/7189) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support MAP_PICK  (#7189)\n\n- due to [`9431fd4`](https://github.com/tobymao/sqlglot/commit/9431fd457dc4dbad7a963828c5303889573079d3) - annotate CURRENT_TIMESTAMP for TSQL *(PR [#7208](https://github.com/tobymao/sqlglot/pull/7208) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate CURRENT_TIMESTAMP for TSQL (#7208)\n\n- due to [`4087a15`](https://github.com/tobymao/sqlglot/commit/4087a152c5d4372ab53644061980b81a9174db1e) - properly handle GENERATED ALWAYS/BY DEFAULT *(PR [#7210](https://github.com/tobymao/sqlglot/pull/7210) by [@anna-stepien](https://github.com/anna-stepien))*:\n\n  properly handle GENERATED ALWAYS/BY DEFAULT (#7210)\n\n- due to [`a39d3e9`](https://github.com/tobymao/sqlglot/commit/a39d3e999e707114226edb806061b80e0164489a) - Implement transpilation for ARRAYS_OVERLAP function *(PR [#7200](https://github.com/tobymao/sqlglot/pull/7200) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Implement transpilation for ARRAYS_OVERLAP function (#7200)\n\n- due to [`ed5e179`](https://github.com/tobymao/sqlglot/commit/ed5e1792a30e5172620e263edcae65f2f892f55b) - Added tests for to_array *(PR [#7201](https://github.com/tobymao/sqlglot/pull/7201) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Added tests for to_array (#7201)\n\n- due to [`3ef5db9`](https://github.com/tobymao/sqlglot/commit/3ef5db9ca10527e237340cc24612d63d4d1bcf1b) - support DETACH *(PR [#7215](https://github.com/tobymao/sqlglot/pull/7215) by [@geooo109](https://github.com/geooo109))*:\n\n  support DETACH (#7215)\n\n- due to [`b5f888e`](https://github.com/tobymao/sqlglot/commit/b5f888e403b20dc9729eb7f01f5cc5227f173ce2) - handle NULL discrepancy during transpilation of SPLIT from Snowflake to DuckDB *(PR [#7216](https://github.com/tobymao/sqlglot/pull/7216) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  handle NULL discrepancy during transpilation of SPLIT from Snowflake to DuckDB (#7216)\n\n- due to [`5599478`](https://github.com/tobymao/sqlglot/commit/55994785bae54dd37ffc40b085878d186780f033) - Compile base & BigQuery's parser with mypyc *(PR [#7206](https://github.com/tobymao/sqlglot/pull/7206) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Compile base & BigQuery's parser with mypyc (#7206)\n\n- due to [`3c02ea8`](https://github.com/tobymao/sqlglot/commit/3c02ea8a2c515d53e89c0d7455392a2b8fac2d8a) - handle empty separator for SPLIT transpilation (Snowflake -> Duckdb) *(PR [#7224](https://github.com/tobymao/sqlglot/pull/7224) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  handle empty separator for SPLIT transpilation (Snowflake -> Duckdb) (#7224)\n\n- due to [`c34bc2d`](https://github.com/tobymao/sqlglot/commit/c34bc2d17c2467b433d49e8e84611ec6acb39580) - transpilation support MAP_INSERT *(PR [#7190](https://github.com/tobymao/sqlglot/pull/7190) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support MAP_INSERT (#7190)\n\n- due to [`8256e08`](https://github.com/tobymao/sqlglot/commit/8256e08e55bb12ac3598e2b1f936e5ef380e2cf8) - Extract Spark parser for mypyc compilation *(PR [#7235](https://github.com/tobymao/sqlglot/pull/7235) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Spark parser for mypyc compilation (#7235)\n\n- due to [`2379947`](https://github.com/tobymao/sqlglot/commit/237994706746d6a294bfae0b413a0ec479645c2c) - Extract SingleStore parser for mypyc compilation *(PR [#7250](https://github.com/tobymao/sqlglot/pull/7250) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract SingleStore parser for mypyc compilation (#7250)\n\n- due to [`27b6f56`](https://github.com/tobymao/sqlglot/commit/27b6f56a3871a0d2152e3eb26ac26dd56a4b5ff3) - Extract Doris parser for mypyc compilation *(PR [#7249](https://github.com/tobymao/sqlglot/pull/7249) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Doris parser for mypyc compilation (#7249)\n\n- due to [`5c8b003`](https://github.com/tobymao/sqlglot/commit/5c8b0037e2a6a8ccd5588234bb591d42904e8a02) - Extract StarRocks parser for mypyc compilation *(PR [#7248](https://github.com/tobymao/sqlglot/pull/7248) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract StarRocks parser for mypyc compilation (#7248)\n\n- due to [`669bc3f`](https://github.com/tobymao/sqlglot/commit/669bc3f7711253d4ecf044c2ec956c0f38a74463) - Extract Materialize parser for mypyc compilation *(PR [#7247](https://github.com/tobymao/sqlglot/pull/7247) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Materialize parser for mypyc compilation (#7247)\n\n- due to [`5b51c64`](https://github.com/tobymao/sqlglot/commit/5b51c64a0d620469e696fcb7a2e86915b7f9a925) - Extract RisingWave parser for mypyc compilation *(PR [#7246](https://github.com/tobymao/sqlglot/pull/7246) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract RisingWave parser for mypyc compilation (#7246)\n\n- due to [`6982d44`](https://github.com/tobymao/sqlglot/commit/6982d442ecebbeb38ee3cda1ab530b813dcff988) - Extract Solr parser for mypyc compilation *(PR [#7244](https://github.com/tobymao/sqlglot/pull/7244) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Solr parser for mypyc compilation (#7244)\n\n- due to [`a57b632`](https://github.com/tobymao/sqlglot/commit/a57b632c512ff8be65629ea30dc9cd0fe69cb1d3) - Extract Redshift parser for mypyc compilation *(PR [#7245](https://github.com/tobymao/sqlglot/pull/7245) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Redshift parser for mypyc compilation (#7245)\n\n- due to [`364fca7`](https://github.com/tobymao/sqlglot/commit/364fca74025eab10f8ba34f2498f9545321e8a3f) - Extract Tableau parser for mypyc compilation *(PR [#7243](https://github.com/tobymao/sqlglot/pull/7243) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Tableau parser for mypyc compilation (#7243)\n\n- due to [`ab6331f`](https://github.com/tobymao/sqlglot/commit/ab6331fc920636e47c6bb03825086642d9425b77) - Extract SQLite parser for mypyc compilation *(PR [#7240](https://github.com/tobymao/sqlglot/pull/7240) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract SQLite parser for mypyc compilation (#7240)\n\n- due to [`df6f052`](https://github.com/tobymao/sqlglot/commit/df6f05268e15f5de1685552ea87c69d2f5bd48c6) - Extract Drill parser for mypyc compilation *(PR [#7242](https://github.com/tobymao/sqlglot/pull/7242) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Drill parser for mypyc compilation (#7242)\n\n- due to [`125ea72`](https://github.com/tobymao/sqlglot/commit/125ea7216a01f0d5288517b1aa7203c43b4e737a) - Extract Dremio parser for mypyc compilation *(PR [#7241](https://github.com/tobymao/sqlglot/pull/7241) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Dremio parser for mypyc compilation (#7241)\n\n- due to [`a485038`](https://github.com/tobymao/sqlglot/commit/a485038a64dd560de093153b23c43f33526d4bf1) - Extract Exasol parser for mypyc compilation *(PR [#7239](https://github.com/tobymao/sqlglot/pull/7239) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Exasol parser for mypyc compilation (#7239)\n\n- due to [`113447f`](https://github.com/tobymao/sqlglot/commit/113447f587891d431af063fa4f18f021033bcb88) - Extract PRQL parser for mypyc compilation *(PR [#7238](https://github.com/tobymao/sqlglot/pull/7238) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract PRQL parser for mypyc compilation (#7238)\n\n- due to [`0872db7`](https://github.com/tobymao/sqlglot/commit/0872db71adc1da464d0d7022dbb58b064bd96abb) - Extract Teradata parser for mypyc compilation *(PR [#7237](https://github.com/tobymao/sqlglot/pull/7237) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Teradata parser for mypyc compilation (#7237)\n\n- due to [`0fe1afd`](https://github.com/tobymao/sqlglot/commit/0fe1afd39a5105bc9406ecac0ea78c26254d7f9e) - Extract Oracle parser for mypyc compilation *(PR [#7236](https://github.com/tobymao/sqlglot/pull/7236) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Oracle parser for mypyc compilation (#7236)\n\n- due to [`47e75e5`](https://github.com/tobymao/sqlglot/commit/47e75e5bb29c6ecd142ca4c51e27ac4ed9996f74) - Extract Snowflake parser for mypyc compilation *(PR [#7229](https://github.com/tobymao/sqlglot/pull/7229) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Snowflake parser for mypyc compilation (#7229)\n\n- due to [`ea035e1`](https://github.com/tobymao/sqlglot/commit/ea035e15793c047df58af9f9a901c7b7c8ed07e2) - Rename Parser to <Dialect>Parser and auto-discover parsers in setup.py *(PR [#7252](https://github.com/tobymao/sqlglot/pull/7252) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Rename Parser to <Dialect>Parser and auto-discover parsers in setup.py (#7252)\n\n- due to [`e8d0dab`](https://github.com/tobymao/sqlglot/commit/e8d0dabf10d4afe22f2277d46cf931c39409063e) - Extract Databricks parser for mypyc compilation *(PR [#7253](https://github.com/tobymao/sqlglot/pull/7253) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Databricks parser for mypyc compilation (#7253)\n\n- due to [`8c799ad`](https://github.com/tobymao/sqlglot/commit/8c799adf70cc0136f1c6647c3212239c83f6cbe1) - Extract Fabric parser for mypyc compilation *(PR [#7254](https://github.com/tobymao/sqlglot/pull/7254) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Extract Fabric parser for mypyc compilation (#7254)\n\n- due to [`b155a29`](https://github.com/tobymao/sqlglot/commit/b155a29574fbc57720637343529623c02b0db43a) - expression to not use **kwargs because it allocates multiple dicts *(PR [#7256](https://github.com/tobymao/sqlglot/pull/7256) by [@tobymao](https://github.com/tobymao))*:\n\n  expression to not use **kwargs because it allocates multiple dicts (#7256)\n\n- due to [`17c2fc7`](https://github.com/tobymao/sqlglot/commit/17c2fc774fda32eb2f1c1baed354db10d4d11e3d) - JSON path with brackets containing non literals *(PR [#7251](https://github.com/tobymao/sqlglot/pull/7251) by [@geooo109](https://github.com/geooo109))*:\n\n  JSON path with brackets containing non literals (#7251)\n\n- due to [`3d7bbb5`](https://github.com/tobymao/sqlglot/commit/3d7bbb5fd2689aed1b2f659abd8d1b18db421104) - parse single-arg TO_{GEOMETRY_GEOGRAPHY} as Cast *(PR [#7270](https://github.com/tobymao/sqlglot/pull/7270) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse single-arg TO_{GEOMETRY_GEOGRAPHY} as Cast (#7270)\n\n- due to [`5a9a522`](https://github.com/tobymao/sqlglot/commit/5a9a52212f6ba975e1f671b712a89befe9c2d606) - support transpilation of SPLIT_PART from snowflake to duckdb *(PR [#7258](https://github.com/tobymao/sqlglot/pull/7258) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of SPLIT_PART from snowflake to duckdb (#7258)\n\n- due to [`a13995e`](https://github.com/tobymao/sqlglot/commit/a13995e7c3441bcd8442b8a210cadd2055199492) - Move `ParserCore` back to `Parser` *(PR [#7268](https://github.com/tobymao/sqlglot/pull/7268) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Move `ParserCore` back to `Parser` (#7268)\n\n- due to [`07f6893`](https://github.com/tobymao/sqlglot/commit/07f68932e39f148c267ce1b12086b85d4d485bf7) - Fully compile schema *(PR [#7276](https://github.com/tobymao/sqlglot/pull/7276) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fully compile schema (#7276)\n\n- due to [`79d72db`](https://github.com/tobymao/sqlglot/commit/79d72dbd2c770c53f9ec9c36dd67ae63860ba4fe) - Transpilation support for to_variant *(PR [#7262](https://github.com/tobymao/sqlglot/pull/7262) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Transpilation support for to_variant (#7262)\n\n- due to [`3f94428`](https://github.com/tobymao/sqlglot/commit/3f94428507d7207ca99e76cebef9375bd3648f4d) - Transpilation support for HASH_AGG *(commit by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Transpilation support for HASH_AGG\n\n- due to [`1bd952b`](https://github.com/tobymao/sqlglot/commit/1bd952bfc68926405e5fe7efb1bb833dd6dd6dfb) - Transpilation support for HASH_AGG *(commit by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Transpilation support for HASH_AGG\n\n- due to [`bfa4818`](https://github.com/tobymao/sqlglot/commit/bfa48188e45601491f981c4cea8a4bd8a2c6a0a2) - Implement transpilation for ARRAY_SORT function *(PR [#7223](https://github.com/tobymao/sqlglot/pull/7223) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Implement transpilation for ARRAY_SORT function (#7223)\n\n- due to [`4b64898`](https://github.com/tobymao/sqlglot/commit/4b648985a166ac091b8a46af1590caf59f9bc31b) - robust support for IGNORE NULLS *(PR [#7288](https://github.com/tobymao/sqlglot/pull/7288) by [@geooo109](https://github.com/geooo109))*:\n\n  robust support for IGNORE NULLS (#7288)\n\n\n### :sparkles: New Features\n- [`8ee0646`](https://github.com/tobymao/sqlglot/commit/8ee0646baa6dfae7e96ca86e2c1af5d53fc04290) - **clickhouse**: Transpile numeric literals with underscores from ClickHouse to other dialects *(PR [#7132](https://github.com/tobymao/sqlglot/pull/7132) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`68da927`](https://github.com/tobymao/sqlglot/commit/68da927d3418eccfe261c22c6e39d8742f52f01c) - **exasol**: support REGEXP_LIKE binary predicate *(PR [#7136](https://github.com/tobymao/sqlglot/pull/7136) by [@marconae](https://github.com/marconae))*\n- [`1b5a7d7`](https://github.com/tobymao/sqlglot/commit/1b5a7d7e2a65665bb3cb0e8efc11062bf891ee28) - **exasol**: transpile FROM_UNIXTIME to FROM_POSIX_TIME *(PR [#7133](https://github.com/tobymao/sqlglot/pull/7133) by [@marconae](https://github.com/marconae))*\n- [`dff662a`](https://github.com/tobymao/sqlglot/commit/dff662a1389bdfbe6c331ca31dd37f76a6353429) - **duckdb**: add transpilation support for ARRAY_GENERATE_RANGE function *(PR [#7107](https://github.com/tobymao/sqlglot/pull/7107) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`8e6b61f`](https://github.com/tobymao/sqlglot/commit/8e6b61f18e465b24ad9e20a8e1509486177fbb32) - **snowflake**: transpilation support MAP_DELETE *(PR [#7139](https://github.com/tobymao/sqlglot/pull/7139) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`483791d`](https://github.com/tobymao/sqlglot/commit/483791d66302a463041094c668421a1d4cc8061e) - **clickhouse**: support nested JSON subcolumn access *(PR [#7140](https://github.com/tobymao/sqlglot/pull/7140) by [@jwhitaker-gridcog](https://github.com/jwhitaker-gridcog))*\n- [`260f116`](https://github.com/tobymao/sqlglot/commit/260f116df06a4e5e7eb81f87713b84e551e012c8) - **clickhouse**: support JSON type arguments *(PR [#7141](https://github.com/tobymao/sqlglot/pull/7141) by [@jwhitaker-gridcog](https://github.com/jwhitaker-gridcog))*\n- [`1b1db57`](https://github.com/tobymao/sqlglot/commit/1b1db5728903d6468eba3d07da2a468395bf628b) - **snowflake**: transpilation support MAP_SIZE *(PR [#7146](https://github.com/tobymao/sqlglot/pull/7146) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`0b4e26b`](https://github.com/tobymao/sqlglot/commit/0b4e26b8e7045bcf68992b6a2c8a5fd51b8262d7) - **optimizer**: annotate EXTRACT(expr) for DuckDB *(PR [#7154](https://github.com/tobymao/sqlglot/pull/7154) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b2e736c`](https://github.com/tobymao/sqlglot/commit/b2e736cd4ae1dd949b5ac59ae263599f8d6f259c) - postgres -> sqlite transpilation improvements *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c91e8d9`](https://github.com/tobymao/sqlglot/commit/c91e8d9dd8a9cdcfaec791144326cf6038edf414) - **clickhouse**: ANY/ALL joins *(PR [#7157](https://github.com/tobymao/sqlglot/pull/7157) by [@geooo109](https://github.com/geooo109))*\n- [`7cc4332`](https://github.com/tobymao/sqlglot/commit/7cc43327ba72b3a1af6d8f2f489a97b997748ee9) - **duckdb**: support transpilation of function RIGHT from Snowflake to DuckDB *(PR [#7148](https://github.com/tobymao/sqlglot/pull/7148) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`63f2981`](https://github.com/tobymao/sqlglot/commit/63f29812e41e1d7d4bb3de349f33c42ee8103498) - **trino**: Add support for ARRAY_FIRST(array, x -> predicate) *(PR [#7147](https://github.com/tobymao/sqlglot/pull/7147) by [@gertjanal](https://github.com/gertjanal))*\n- [`d76dc36`](https://github.com/tobymao/sqlglot/commit/d76dc36a0c2b15ff795725f1a71431ba247eda96) - **clickhouse**: add support for sql security property *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ad9d114`](https://github.com/tobymao/sqlglot/commit/ad9d114aea7f7553485631372d99ec5e5cf85045) - **DuckDB**: Enable transpilation for ARRAY_POSITION function *(PR [#7153](https://github.com/tobymao/sqlglot/pull/7153) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`684ff4a`](https://github.com/tobymao/sqlglot/commit/684ff4a13b1220fd5d3c0ec597cbdc630a3b9c03) - **clickhouse**: support arrayExcept for ClickHouse *(PR [#7161](https://github.com/tobymao/sqlglot/pull/7161) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b97da8d`](https://github.com/tobymao/sqlglot/commit/b97da8dea15c39867b23db01624464e7d15427e7) - **spark, dbx**: robust SET support *(PR [#7166](https://github.com/tobymao/sqlglot/pull/7166) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#7165](https://github.com/tobymao/sqlglot/issues/7165) opened by [@aersam](https://github.com/aersam)*\n- [`c74049c`](https://github.com/tobymao/sqlglot/commit/c74049c2c573522c398b3e179a09a7b49b52f54f) - **databricks**: Add support for HANDLER and PARAMETER STYLE properties *(PR [#7150](https://github.com/tobymao/sqlglot/pull/7150) by [@aersam](https://github.com/aersam))*\n- [`d5840c5`](https://github.com/tobymao/sqlglot/commit/d5840c53f6a359def002c0b634a48706519b11e7) - **duckdb**: support transpilation of RANDOM from Snowflake to DuckDB *(PR [#7163](https://github.com/tobymao/sqlglot/pull/7163) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`07ef171`](https://github.com/tobymao/sqlglot/commit/07ef1711fdff0fa8f877b93f5be8921424eed438) - **Clickhouse**: Add support for multiple-suffix combined aggregate functions in Clickhouse dialect *(PR [#7109](https://github.com/tobymao/sqlglot/pull/7109) by [@emanb29](https://github.com/emanb29))*\n- [`e18a24e`](https://github.com/tobymao/sqlglot/commit/e18a24e108976910e55b77d863fa4b5eeb622684) - **exasol**: Custom Transformation of GROUP BY ALL in exasol dialect *(PR [#7151](https://github.com/tobymao/sqlglot/pull/7151) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`280e247`](https://github.com/tobymao/sqlglot/commit/280e24726be22a4f06261168d5dfc74b361dd04d) - **duckdb**: Add transpilation support for NULLs and mutiset semantics in ARRAY_INTERSECTION *(PR [#7145](https://github.com/tobymao/sqlglot/pull/7145) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`542e392`](https://github.com/tobymao/sqlglot/commit/542e3920fb7d232b523e950820750e549a8d909a) - **duckdb**: Add transpilation support for NULLs and mutiset semantics in ARRAY_INTERSECTION *(PR [#7145](https://github.com/tobymao/sqlglot/pull/7145) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ff61214`](https://github.com/tobymao/sqlglot/commit/ff61214ab3f3ee48a413020867144a7f4d0b39c3) - **databricks**: Add support for DECLARE OR REPLACE *(PR [#7169](https://github.com/tobymao/sqlglot/pull/7169) by [@aersam](https://github.com/aersam))*\n  - :arrow_lower_right: *addresses issue [#7168](https://github.com/tobymao/sqlglot/issues/7168) opened by [@aersam](https://github.com/aersam)*\n- [`90e8cab`](https://github.com/tobymao/sqlglot/commit/90e8cab0a54c225698f1cfa33d0ddbc92793fa15) - **clickhouse**: support robust ASSUME/CHECK constraints *(PR [#7170](https://github.com/tobymao/sqlglot/pull/7170) by [@geooo109](https://github.com/geooo109))*\n- [`4273991`](https://github.com/tobymao/sqlglot/commit/427399151aa69ff934c802b3452bd4ad4f7010e7) - **clickhouse**: support DROP with SYNC *(PR [#7172](https://github.com/tobymao/sqlglot/pull/7172) by [@geooo109](https://github.com/geooo109))*\n- [`0c5c3f1`](https://github.com/tobymao/sqlglot/commit/0c5c3f17637df659004036c37c569ad42da21dd4) - **duckdb**: support GROUPS for WINDOW *(PR [#7185](https://github.com/tobymao/sqlglot/pull/7185) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#7176](https://github.com/tobymao/sqlglot/issues/7176) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`af05677`](https://github.com/tobymao/sqlglot/commit/af05677fe08a2830bb0a18587f0c0f8dfbc5b9bd) - make Query and DerivedTable inherit from Selectable *(commit by [@tobymao](https://github.com/tobymao))*\n- [`cab0f24`](https://github.com/tobymao/sqlglot/commit/cab0f24ad212eea592f532628f4fc10489fc32f7) - compile scope for mypy *(PR [#7192](https://github.com/tobymao/sqlglot/pull/7192) by [@tobymao](https://github.com/tobymao))*\n- [`8db0323`](https://github.com/tobymao/sqlglot/commit/8db03233f88f106f0339e53237a5f054c4e61b3a) - compile qualify and resolver *(PR [#7193](https://github.com/tobymao/sqlglot/pull/7193) by [@tobymao](https://github.com/tobymao))*\n- [`3de5d29`](https://github.com/tobymao/sqlglot/commit/3de5d29ef5fd19e2f1ac0b2681e29e8a6c6fbd48) - **duckdb**: Add transpilation support for ARRAY_SLICE function *(PR [#7188](https://github.com/tobymao/sqlglot/pull/7188) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`e0947ad`](https://github.com/tobymao/sqlglot/commit/e0947adcaeb1e3cb829e584e0b071c598c64cfa9) - **Snowflake**: set default window frame for certain Snowflake ranking functions during transpilation *(PR [#7195](https://github.com/tobymao/sqlglot/pull/7195) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`4a3254f`](https://github.com/tobymao/sqlglot/commit/4a3254fbd1bfee7aa6787d3fc31832d8e9771932) - **snowflake**: transpilation support MAP_PICK  *(PR [#7189](https://github.com/tobymao/sqlglot/pull/7189) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`9431fd4`](https://github.com/tobymao/sqlglot/commit/9431fd457dc4dbad7a963828c5303889573079d3) - **optimizer**: annotate CURRENT_TIMESTAMP for TSQL *(PR [#7208](https://github.com/tobymao/sqlglot/pull/7208) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`cc8724d`](https://github.com/tobymao/sqlglot/commit/cc8724d791938e440c8d72d72c4ec301ca02eee9) - **clickhouse**: support cityHash64 *(PR [#7209](https://github.com/tobymao/sqlglot/pull/7209) by [@geooo109](https://github.com/geooo109))*\n- [`a39d3e9`](https://github.com/tobymao/sqlglot/commit/a39d3e999e707114226edb806061b80e0164489a) - **duckdb**: Implement transpilation for ARRAYS_OVERLAP function *(PR [#7200](https://github.com/tobymao/sqlglot/pull/7200) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`3ef5db9`](https://github.com/tobymao/sqlglot/commit/3ef5db9ca10527e237340cc24612d63d4d1bcf1b) - **clickhouse**: support DETACH *(PR [#7215](https://github.com/tobymao/sqlglot/pull/7215) by [@geooo109](https://github.com/geooo109))*\n- [`b5f888e`](https://github.com/tobymao/sqlglot/commit/b5f888e403b20dc9729eb7f01f5cc5227f173ce2) - **Snowflake**: handle NULL discrepancy during transpilation of SPLIT from Snowflake to DuckDB *(PR [#7216](https://github.com/tobymao/sqlglot/pull/7216) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`5599478`](https://github.com/tobymao/sqlglot/commit/55994785bae54dd37ffc40b085878d186780f033) - Compile base & BigQuery's parser with mypyc *(PR [#7206](https://github.com/tobymao/sqlglot/pull/7206) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`319f359`](https://github.com/tobymao/sqlglot/commit/319f3591e53013b972c57057c65479d7024a4388) - **tsql**: Move parser out and enable compilation *(PR [#7221](https://github.com/tobymao/sqlglot/pull/7221) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`3c02ea8`](https://github.com/tobymao/sqlglot/commit/3c02ea8a2c515d53e89c0d7455392a2b8fac2d8a) - **snowflake**: handle empty separator for SPLIT transpilation (Snowflake -> Duckdb) *(PR [#7224](https://github.com/tobymao/sqlglot/pull/7224) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`c34bc2d`](https://github.com/tobymao/sqlglot/commit/c34bc2d17c2467b433d49e8e84611ec6acb39580) - **snowflake**: transpilation support MAP_INSERT *(PR [#7190](https://github.com/tobymao/sqlglot/pull/7190) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4ae6c3d`](https://github.com/tobymao/sqlglot/commit/4ae6c3d8fe774c57647491572b764a4c2767704a) - fast path for tokenizer *(PR [#7226](https://github.com/tobymao/sqlglot/pull/7226) by [@tobymao](https://github.com/tobymao))*\n- [`5a9a522`](https://github.com/tobymao/sqlglot/commit/5a9a52212f6ba975e1f671b712a89befe9c2d606) - **Snowflake**: support transpilation of SPLIT_PART from snowflake to duckdb *(PR [#7258](https://github.com/tobymao/sqlglot/pull/7258) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`dc4a7ef`](https://github.com/tobymao/sqlglot/commit/dc4a7ef7a1739ecc6b4ff8ff1ab018d61d7bcffa) - support multi-word DESCRIBE kinds in Snowflake *(PR [#7260](https://github.com/tobymao/sqlglot/pull/7260) by [@sabir-akhadov-localstack](https://github.com/sabir-akhadov-localstack))*\n- [`f15b8b0`](https://github.com/tobymao/sqlglot/commit/f15b8b018ddb0aa1bd26683c439ab2d063ca9cd8) - transpile postgres GREATEST(x,y) to MAX in sqlite *(PR [#7274](https://github.com/tobymao/sqlglot/pull/7274) by [@treysp](https://github.com/treysp))*\n- [`79d72db`](https://github.com/tobymao/sqlglot/commit/79d72dbd2c770c53f9ec9c36dd67ae63860ba4fe) - **snowflake**: Transpilation support for to_variant *(PR [#7262](https://github.com/tobymao/sqlglot/pull/7262) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`bfa4818`](https://github.com/tobymao/sqlglot/commit/bfa48188e45601491f981c4cea8a4bd8a2c6a0a2) - **duckdb**: Implement transpilation for ARRAY_SORT function *(PR [#7223](https://github.com/tobymao/sqlglot/pull/7223) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n### :bug: Bug Fixes\n- [`81a3763`](https://github.com/tobymao/sqlglot/commit/81a37636c374690d12fe0b57d78adf2310daf3cb) - **exasol**: cast string literals to TIMESTAMP in TO_CHAR generation *(PR [#7127](https://github.com/tobymao/sqlglot/pull/7127) by [@marconae](https://github.com/marconae))*\n- [`351e958`](https://github.com/tobymao/sqlglot/commit/351e958c3cd8a1395826ef624979275b246490f8) - **exasol**: fix parsing error in json_extract for exasol *(PR [#7098](https://github.com/tobymao/sqlglot/pull/7098) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`95d7d20`](https://github.com/tobymao/sqlglot/commit/95d7d2052b1ed8fc64a99d557b8b085ad466100e) - **redshift**: annotate `TO_TIMESTAMP` as `TIMESTAMPTZ` fixes [#7155](https://github.com/tobymao/sqlglot/pull/7155) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`50d58a5`](https://github.com/tobymao/sqlglot/commit/50d58a5843cfc02f11acf0fe28b2ad7c9a59e252) - **parser**: support more DICTIONARY properties *(PR [#7158](https://github.com/tobymao/sqlglot/pull/7158) by [@geooo109](https://github.com/geooo109))*\n- [`363167c`](https://github.com/tobymao/sqlglot/commit/363167c6609fa80b6014851b4415adca17b00df4) - **duckdb**: parse FILE column *(PR [#7184](https://github.com/tobymao/sqlglot/pull/7184) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7175](https://github.com/tobymao/sqlglot/issues/7175) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`baa9974`](https://github.com/tobymao/sqlglot/commit/baa9974b8042eaef7897537772b1002c30e503b8) - **duckdb**: fix IGNORE NULLS in AGG FUNC *(PR [#7187](https://github.com/tobymao/sqlglot/pull/7187) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7179](https://github.com/tobymao/sqlglot/issues/7179) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`f630d75`](https://github.com/tobymao/sqlglot/commit/f630d7579231f29fa5637b48f1be0b5665eb36b3) - **clickhouse**: support dotcolon with JSON *(PR [#7191](https://github.com/tobymao/sqlglot/pull/7191) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7183](https://github.com/tobymao/sqlglot/issues/7183) opened by [@telperions](https://github.com/telperions)*\n- [`eea5880`](https://github.com/tobymao/sqlglot/commit/eea58807411edf962a1cdc28e02337a428866665) - **snowflake**: suppor positional `GENERATOR` args *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`29399bb`](https://github.com/tobymao/sqlglot/commit/29399bbed44a74d95257040fd36f0a0f6de7c7d8) - **exasol**: remove invalid group by distinct during custom transformation of group by all *(PR [#7197](https://github.com/tobymao/sqlglot/pull/7197) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`3ee3db5`](https://github.com/tobymao/sqlglot/commit/3ee3db59de23de0bbaed85c8779b6663b435a1e9) - **clickhouse**: support nested field for INSERT *(PR [#7199](https://github.com/tobymao/sqlglot/pull/7199) by [@geooo109](https://github.com/geooo109))*\n- [`81bc810`](https://github.com/tobymao/sqlglot/commit/81bc8102a7fbb33188869694800e8db9ae84541a) - **clickhouse**: make ArrayDistinct transpilable *(commit by [@timoha](https://github.com/timoha))*\n- [`6b21d7a`](https://github.com/tobymao/sqlglot/commit/6b21d7a5932afdc6ba117c808203777db9f329a2) - **clickhouse**: empty brackets handling *(PR [#7211](https://github.com/tobymao/sqlglot/pull/7211) by [@geooo109](https://github.com/geooo109))*\n- [`4087a15`](https://github.com/tobymao/sqlglot/commit/4087a152c5d4372ab53644061980b81a9174db1e) - **databricks**: properly handle GENERATED ALWAYS/BY DEFAULT *(PR [#7210](https://github.com/tobymao/sqlglot/pull/7210) by [@anna-stepien](https://github.com/anna-stepien))*\n- [`37b0f9f`](https://github.com/tobymao/sqlglot/commit/37b0f9f6b897d4289d87dfd78d42ca3df64870ba) - **spark, dbx**: support RECURSIVE ctes *(PR [#7214](https://github.com/tobymao/sqlglot/pull/7214) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7213](https://github.com/tobymao/sqlglot/issues/7213) opened by [@aersam](https://github.com/aersam)*\n- [`e990b67`](https://github.com/tobymao/sqlglot/commit/e990b671120ce66036ffdc4c65e5db8b36eaec5f) - **redshift**: avoid warning for IGNORE/RESPECT NULLS *(PR [#7222](https://github.com/tobymao/sqlglot/pull/7222) by [@geooo109](https://github.com/geooo109))*\n- [`172e399`](https://github.com/tobymao/sqlglot/commit/172e399f6211f73708ba9e423cf90c9ff83ffba3) - **hive, spark, dbx**: dash in json path *(PR [#7257](https://github.com/tobymao/sqlglot/pull/7257) by [@geooo109](https://github.com/geooo109))*\n- [`17c2fc7`](https://github.com/tobymao/sqlglot/commit/17c2fc774fda32eb2f1c1baed354db10d4d11e3d) - **snowflake**: JSON path with brackets containing non literals *(PR [#7251](https://github.com/tobymao/sqlglot/pull/7251) by [@geooo109](https://github.com/geooo109))*\n- [`fd87f53`](https://github.com/tobymao/sqlglot/commit/fd87f53f7ab5d68904555e9fae0a025c134faeaf) - **duckdb**: transpile DATE_TRUNC from bigquery *(PR [#7263](https://github.com/tobymao/sqlglot/pull/7263) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7261](https://github.com/tobymao/sqlglot/issues/7261) opened by [@MaxHalford](https://github.com/MaxHalford)*\n- [`3d7bbb5`](https://github.com/tobymao/sqlglot/commit/3d7bbb5fd2689aed1b2f659abd8d1b18db421104) - **snowflake**: parse single-arg TO_{GEOMETRY_GEOGRAPHY} as Cast *(PR [#7270](https://github.com/tobymao/sqlglot/pull/7270) by [@georgesittas](https://github.com/georgesittas))*\n- [`3e16da6`](https://github.com/tobymao/sqlglot/commit/3e16da64c0c32bd970b2ab5fd5ff2fdc3a134feb) - Make unit silently failing to move .so *(PR [#7272](https://github.com/tobymao/sqlglot/pull/7272) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`c3eb8e2`](https://github.com/tobymao/sqlglot/commit/c3eb8e22c8ed83029a38640ae569d829c7258d0d) - **optimizer**: `qualify_tables` overwriting FQN alias mapping for duplicate tables *(PR [#7278](https://github.com/tobymao/sqlglot/pull/7278) by [@cg-roling](https://github.com/cg-roling))*\n- [`0527315`](https://github.com/tobymao/sqlglot/commit/0527315a23dc44c736de80fa753125da764e95ba) - Drop schema with if exists displacing catalog *(PR [#7285](https://github.com/tobymao/sqlglot/pull/7285) by [@themisvaltinos](https://github.com/themisvaltinos))*\n- [`a2964dd`](https://github.com/tobymao/sqlglot/commit/a2964ddb88ac518a57e19c620358e7e4a03e611e) - **duckdb**: complete ARRAY_SORT transpilation logic *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6d6250d`](https://github.com/tobymao/sqlglot/commit/6d6250db9b8bf257d86a59266c48a02fe10cd1b3) - **bigquery**: no warning for window funcs with NULL order *(PR [#7280](https://github.com/tobymao/sqlglot/pull/7280) by [@geooo109](https://github.com/geooo109))*\n- [`4b64898`](https://github.com/tobymao/sqlglot/commit/4b648985a166ac091b8a46af1590caf59f9bc31b) - **hive, spark**: robust support for IGNORE NULLS *(PR [#7288](https://github.com/tobymao/sqlglot/pull/7288) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7282](https://github.com/tobymao/sqlglot/issues/7282) opened by [@catlynkong](https://github.com/catlynkong)*\n- [`4cb1a7f`](https://github.com/tobymao/sqlglot/commit/4cb1a7faf3e5a431ea482f34e197485a1611d8c4) - handle single-element `RANGE` during duckdb->spark transpilation *(PR [#7294](https://github.com/tobymao/sqlglot/pull/7294) by [@ShubhamKapoor992](https://github.com/ShubhamKapoor992))*\n  - :arrow_lower_right: *fixes issue [#7291](https://github.com/tobymao/sqlglot/issues/7291) opened by [@huydo862003](https://github.com/huydo862003)*\n- [`002bdaf`](https://github.com/tobymao/sqlglot/commit/002bdafbd5315d7e7fa67903478ed888fb1b1229) - don't warn for sqlglotrs if sqlglotc is found *(PR [#7290](https://github.com/tobymao/sqlglot/pull/7290) by [@rolandwalker](https://github.com/rolandwalker))*\n- [`0eb5aae`](https://github.com/tobymao/sqlglot/commit/0eb5aae8e348774ae5f12bd7e0140da6f8e16da4) - **optimizer**: add forward-reference guard to pushdown_dnf, fixing cycle error *(PR [#7299](https://github.com/tobymao/sqlglot/pull/7299) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#7297](https://github.com/tobymao/sqlglot/issues/7297) opened by [@snovik75](https://github.com/snovik75)*\n- [`6f471f1`](https://github.com/tobymao/sqlglot/commit/6f471f1bfb466b32f04c814a0beb6fa23e045eff) - unnest_subqueries crashes when correlated subquery is inside a function in SELECT *(PR [#7300](https://github.com/tobymao/sqlglot/pull/7300) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#7295](https://github.com/tobymao/sqlglot/issues/7295) opened by [@snovik75](https://github.com/snovik75)*\n\n### :zap: Performance Improvements\n- [`489ba1d`](https://github.com/tobymao/sqlglot/commit/489ba1dd0b6024f2d876b474b451b5e6fa8d27de) - **parser**: fast path for column reference parsing *(PR [#7293](https://github.com/tobymao/sqlglot/pull/7293) by [@tobymao](https://github.com/tobymao))*\n\n### :recycle: Refactors\n- [`74fd80c`](https://github.com/tobymao/sqlglot/commit/74fd80cdf055c828cedfac43b4b54132d18558bb) - split up expressions.py *(PR [#7160](https://github.com/tobymao/sqlglot/pull/7160) by [@tobymao](https://github.com/tobymao))*\n- [`8256e08`](https://github.com/tobymao/sqlglot/commit/8256e08e55bb12ac3598e2b1f936e5ef380e2cf8) - Extract Spark parser for mypyc compilation *(PR [#7235](https://github.com/tobymao/sqlglot/pull/7235) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2379947`](https://github.com/tobymao/sqlglot/commit/237994706746d6a294bfae0b413a0ec479645c2c) - Extract SingleStore parser for mypyc compilation *(PR [#7250](https://github.com/tobymao/sqlglot/pull/7250) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`27b6f56`](https://github.com/tobymao/sqlglot/commit/27b6f56a3871a0d2152e3eb26ac26dd56a4b5ff3) - Extract Doris parser for mypyc compilation *(PR [#7249](https://github.com/tobymao/sqlglot/pull/7249) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5c8b003`](https://github.com/tobymao/sqlglot/commit/5c8b0037e2a6a8ccd5588234bb591d42904e8a02) - Extract StarRocks parser for mypyc compilation *(PR [#7248](https://github.com/tobymao/sqlglot/pull/7248) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`669bc3f`](https://github.com/tobymao/sqlglot/commit/669bc3f7711253d4ecf044c2ec956c0f38a74463) - Extract Materialize parser for mypyc compilation *(PR [#7247](https://github.com/tobymao/sqlglot/pull/7247) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5b51c64`](https://github.com/tobymao/sqlglot/commit/5b51c64a0d620469e696fcb7a2e86915b7f9a925) - Extract RisingWave parser for mypyc compilation *(PR [#7246](https://github.com/tobymao/sqlglot/pull/7246) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`6982d44`](https://github.com/tobymao/sqlglot/commit/6982d442ecebbeb38ee3cda1ab530b813dcff988) - Extract Solr parser for mypyc compilation *(PR [#7244](https://github.com/tobymao/sqlglot/pull/7244) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`a57b632`](https://github.com/tobymao/sqlglot/commit/a57b632c512ff8be65629ea30dc9cd0fe69cb1d3) - Extract Redshift parser for mypyc compilation *(PR [#7245](https://github.com/tobymao/sqlglot/pull/7245) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`364fca7`](https://github.com/tobymao/sqlglot/commit/364fca74025eab10f8ba34f2498f9545321e8a3f) - Extract Tableau parser for mypyc compilation *(PR [#7243](https://github.com/tobymao/sqlglot/pull/7243) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ab6331f`](https://github.com/tobymao/sqlglot/commit/ab6331fc920636e47c6bb03825086642d9425b77) - Extract SQLite parser for mypyc compilation *(PR [#7240](https://github.com/tobymao/sqlglot/pull/7240) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`df6f052`](https://github.com/tobymao/sqlglot/commit/df6f05268e15f5de1685552ea87c69d2f5bd48c6) - Extract Drill parser for mypyc compilation *(PR [#7242](https://github.com/tobymao/sqlglot/pull/7242) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`125ea72`](https://github.com/tobymao/sqlglot/commit/125ea7216a01f0d5288517b1aa7203c43b4e737a) - Extract Dremio parser for mypyc compilation *(PR [#7241](https://github.com/tobymao/sqlglot/pull/7241) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`a485038`](https://github.com/tobymao/sqlglot/commit/a485038a64dd560de093153b23c43f33526d4bf1) - Extract Exasol parser for mypyc compilation *(PR [#7239](https://github.com/tobymao/sqlglot/pull/7239) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`113447f`](https://github.com/tobymao/sqlglot/commit/113447f587891d431af063fa4f18f021033bcb88) - Extract PRQL parser for mypyc compilation *(PR [#7238](https://github.com/tobymao/sqlglot/pull/7238) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`0872db7`](https://github.com/tobymao/sqlglot/commit/0872db71adc1da464d0d7022dbb58b064bd96abb) - Extract Teradata parser for mypyc compilation *(PR [#7237](https://github.com/tobymao/sqlglot/pull/7237) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`0fe1afd`](https://github.com/tobymao/sqlglot/commit/0fe1afd39a5105bc9406ecac0ea78c26254d7f9e) - Extract Oracle parser for mypyc compilation *(PR [#7236](https://github.com/tobymao/sqlglot/pull/7236) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`47e75e5`](https://github.com/tobymao/sqlglot/commit/47e75e5bb29c6ecd142ca4c51e27ac4ed9996f74) - Extract Snowflake parser for mypyc compilation *(PR [#7229](https://github.com/tobymao/sqlglot/pull/7229) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ea035e1`](https://github.com/tobymao/sqlglot/commit/ea035e15793c047df58af9f9a901c7b7c8ed07e2) - Rename Parser to <Dialect>Parser and auto-discover parsers in setup.py *(PR [#7252](https://github.com/tobymao/sqlglot/pull/7252) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`e8d0dab`](https://github.com/tobymao/sqlglot/commit/e8d0dabf10d4afe22f2277d46cf931c39409063e) - Extract Databricks parser for mypyc compilation *(PR [#7253](https://github.com/tobymao/sqlglot/pull/7253) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`8c799ad`](https://github.com/tobymao/sqlglot/commit/8c799adf70cc0136f1c6647c3212239c83f6cbe1) - Extract Fabric parser for mypyc compilation *(PR [#7254](https://github.com/tobymao/sqlglot/pull/7254) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`b155a29`](https://github.com/tobymao/sqlglot/commit/b155a29574fbc57720637343529623c02b0db43a) - expression to not use **kwargs because it allocates multiple dicts *(PR [#7256](https://github.com/tobymao/sqlglot/pull/7256) by [@tobymao](https://github.com/tobymao))*\n- [`07f6893`](https://github.com/tobymao/sqlglot/commit/07f68932e39f148c267ce1b12086b85d4d485bf7) - Fully compile schema *(PR [#7276](https://github.com/tobymao/sqlglot/pull/7276) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :white_check_mark: Tests\n- [`ed5e179`](https://github.com/tobymao/sqlglot/commit/ed5e1792a30e5172620e263edcae65f2f892f55b) - **snowflake**: Added tests for to_array *(PR [#7201](https://github.com/tobymao/sqlglot/pull/7201) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`3f94428`](https://github.com/tobymao/sqlglot/commit/3f94428507d7207ca99e76cebef9375bd3648f4d) - **snowflake**: Transpilation support for HASH_AGG *(commit by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`1bd952b`](https://github.com/tobymao/sqlglot/commit/1bd952bfc68926405e5fe7efb1bb833dd6dd6dfb) - **snowflake**: Transpilation support for HASH_AGG *(commit by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n\n### :wrench: Chores\n- [`76c7eeb`](https://github.com/tobymao/sqlglot/commit/76c7eeb7657a5435f1086c16317927b2780c6ad9) - cleanup makefile *(commit by [@tobymao](https://github.com/tobymao))*\n- [`588e565`](https://github.com/tobymao/sqlglot/commit/588e5650467f5cb9903ac1fa5ece59c3bfb42c8e) - **exasol**: DAYS_BETWEEN tests *(PR [#7135](https://github.com/tobymao/sqlglot/pull/7135) by [@marconae](https://github.com/marconae))*\n- [`ea424bf`](https://github.com/tobymao/sqlglot/commit/ea424bf865aa4d7bbca62b834f6994c05232fdf0) - **tokenizer**: Replace SPACE_CHARS with str.isspace *(PR [#7134](https://github.com/tobymao/sqlglot/pull/7134) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`e1c26c5`](https://github.com/tobymao/sqlglot/commit/e1c26c5425a602030e5fefd5fe76614081fe3991) - keep api-docs in sync with main, only add docs/ on top *(PR [#7137](https://github.com/tobymao/sqlglot/pull/7137) by [@georgesittas](https://github.com/georgesittas))*\n- [`f69a152`](https://github.com/tobymao/sqlglot/commit/f69a152d51666abd832a497299db1fefdfaa3ec9) - add tests for transpiling LAG from snowflake to duckdb *(PR [#7138](https://github.com/tobymao/sqlglot/pull/7138) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`842462e`](https://github.com/tobymao/sqlglot/commit/842462e2fc36f7d69ecc4989d0c8d4600e013c61) - move ruff config to pyproject.toml *(PR [#7149](https://github.com/tobymao/sqlglot/pull/7149) by [@jwhitaker-gridcog](https://github.com/jwhitaker-gridcog))*\n- [`4a955cb`](https://github.com/tobymao/sqlglot/commit/4a955cb993ae24d91a5094b7849ceb60ab963419) - scan all modules under sqlglot/ for doc tests *(PR [#7162](https://github.com/tobymao/sqlglot/pull/7162) by [@georgesittas](https://github.com/georgesittas))*\n- [`3a930da`](https://github.com/tobymao/sqlglot/commit/3a930dad611743a4b6b1d647e27af2324db4f755) - add integration test automations *(PR [#7167](https://github.com/tobymao/sqlglot/pull/7167) by [@georgesittas](https://github.com/georgesittas))*\n- [`151f961`](https://github.com/tobymao/sqlglot/commit/151f961f6575e9cdd54a1cab3dad66fa2a12774c) - update parse_one description *(PR [#7181](https://github.com/tobymao/sqlglot/pull/7181) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#7173](https://github.com/tobymao/sqlglot/issues/7173) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`ab8331c`](https://github.com/tobymao/sqlglot/commit/ab8331c67dd43ee2add85520507211203e85817f) - consolidate `SecurityProperty` and `SqlSecurityProperty` *(PR [#7174](https://github.com/tobymao/sqlglot/pull/7174) by [@jwhitaker-gridcog](https://github.com/jwhitaker-gridcog))*\n- [`4f5abdb`](https://github.com/tobymao/sqlglot/commit/4f5abdb3fb0cfcaaca9763ced38a7920c2eab548) - refactor SQL SECURITY property location logic in MySQL *(PR [#7186](https://github.com/tobymao/sqlglot/pull/7186) by [@georgesittas](https://github.com/georgesittas))*\n- [`a3fecc5`](https://github.com/tobymao/sqlglot/commit/a3fecc5dbc827c0873cd83cfc8388b081bcec75b) - qol improvements to integration test workflow *(PR [#7198](https://github.com/tobymao/sqlglot/pull/7198) by [@georgesittas](https://github.com/georgesittas))*\n- [`95f6c35`](https://github.com/tobymao/sqlglot/commit/95f6c354b06482d202e496b3d7df3e1dd9bbdcaf) - minor refactor for snowflake window gen *(commit by [@geooo109](https://github.com/geooo109))*\n- [`175360e`](https://github.com/tobymao/sqlglot/commit/175360ecf17c839c67e6dc83a7da97823a406e0f) - clickhouse test ARRAY_DISTINCT refactor *(commit by [@geooo109](https://github.com/geooo109))*\n- [`55594ed`](https://github.com/tobymao/sqlglot/commit/55594edfa998f4174570268d88595f08a7078c66) - cleanup MAP_PICK tests in duckdb *(commit by [@geooo109](https://github.com/geooo109))*\n- [`c341a3d`](https://github.com/tobymao/sqlglot/commit/c341a3d7351e619d8935866611ffb23a32de24f4) - **test**: add test for SPLIT transpilation from Snowflake to DuckDB *(PR [#7212](https://github.com/tobymao/sqlglot/pull/7212) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`03d2a4b`](https://github.com/tobymao/sqlglot/commit/03d2a4bf87efed98df98927cca705cc9dcfc9d76) - cleanup duckdb ARRAY_OVERLAPS *(commit by [@geooo109](https://github.com/geooo109))*\n- [`7a74228`](https://github.com/tobymao/sqlglot/commit/7a7422896e75c9c260c3737982597438f5128e0b) - duckdb tests style *(commit by [@geooo109](https://github.com/geooo109))*\n- [`7df5bd4`](https://github.com/tobymao/sqlglot/commit/7df5bd487d942eeee3f6cf1ab26777405ce90b94) - **perf**: lineage cte memoization *(PR [#7207](https://github.com/tobymao/sqlglot/pull/7207) by [@treff7es](https://github.com/treff7es))*\n- [`0787c74`](https://github.com/tobymao/sqlglot/commit/0787c74f5351d7eeb825eba75e6112241aeb1d98) - remove read-only flag in lineage, always use shared refs *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7f3a18b`](https://github.com/tobymao/sqlglot/commit/7f3a18bcda811a5c259d6524e64eea2ed78ea83a) - **parser**: Add backwards compatibility tests *(PR [#7255](https://github.com/tobymao/sqlglot/pull/7255) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`fa42909`](https://github.com/tobymao/sqlglot/commit/fa4290956b922f173042170b7ff85fd29eb1f0d4) - do not run integration tests on make unit *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6da7d90`](https://github.com/tobymao/sqlglot/commit/6da7d9061508c2b04ae81a6de2af82736879da38) - replace greek characters in `DATETIME_DELTA` type variable fixes [#7264](https://github.com/tobymao/sqlglot/pull/7264) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`a13995e`](https://github.com/tobymao/sqlglot/commit/a13995e7c3441bcd8442b8a210cadd2055199492) - **parser**: Move `ParserCore` back to `Parser` *(PR [#7268](https://github.com/tobymao/sqlglot/pull/7268) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`4efd393`](https://github.com/tobymao/sqlglot/commit/4efd393b143fddc1e8a6481e36c333d89c0025a4) - update benchmarks *(commit by [@tobymao](https://github.com/tobymao))*\n- [`20ebebf`](https://github.com/tobymao/sqlglot/commit/20ebebf40805e25f7e2496326b595480e10b7a32) - get rid of unused `read_only` kwarg in lineage *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b8cbea0`](https://github.com/tobymao/sqlglot/commit/b8cbea02713989174a2bad32a17a85d763539f58) - make rs depend on sqlglotc *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d5e6d96`](https://github.com/tobymao/sqlglot/commit/d5e6d965288c0929e0a4ef9a9db292fb28bbf3d1) - clean up VARIANT tests for duckdb and sf *(commit by [@geooo109](https://github.com/geooo109))*\n- [`ea76ca5`](https://github.com/tobymao/sqlglot/commit/ea76ca55405efdb09a1ea0dbb2d02d1892260b71) - unpin ruff *(PR [#7287](https://github.com/tobymao/sqlglot/pull/7287) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v29.0.1] - 2026-02-23\n### :boom: BREAKING CHANGES\n- due to [`fdfdfb1`](https://github.com/tobymao/sqlglot/commit/fdfdfb1703f1f408ad01453147e3d269f0911fef) - support GET_CURRENT_TIME() for DuckDB *(PR [#7126](https://github.com/tobymao/sqlglot/pull/7126) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support GET_CURRENT_TIME() for DuckDB (#7126)\n\n\n### :sparkles: New Features\n- [`fdfdfb1`](https://github.com/tobymao/sqlglot/commit/fdfdfb1703f1f408ad01453147e3d269f0911fef) - **duckdb**: support GET_CURRENT_TIME() for DuckDB *(PR [#7126](https://github.com/tobymao/sqlglot/pull/7126) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n\n### :wrench: Chores\n- [`21a2a57`](https://github.com/tobymao/sqlglot/commit/21a2a5773717f675963dddbdba3df9343da60abe) - actually emit warning *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v29.0.0] - 2026-02-23\n### :boom: BREAKING CHANGES\n- due to [`c8ddcc3`](https://github.com/tobymao/sqlglot/commit/c8ddcc383bab07b807ed1d6b6f9bef91417e43c1) - Annotate COLLATION(expr) for Spark/DBX *(PR [#6957](https://github.com/tobymao/sqlglot/pull/6957) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate COLLATION(expr) for Spark/DBX (#6957)\n\n- due to [`fc4b332`](https://github.com/tobymao/sqlglot/commit/fc4b3326a14a1b42bc954914ce43b8dad7ef23b2) - Annotate BITMAP_COUNT(expr) for Spark/DBX *(PR [#6956](https://github.com/tobymao/sqlglot/pull/6956) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate BITMAP_COUNT(expr) for Spark/DBX (#6956)\n\n- due to [`8725010`](https://github.com/tobymao/sqlglot/commit/87250100eb2a1d2c206a26cce276f7babec0e409) - add exp.Trunc for numeric truncation *(PR [#6923](https://github.com/tobymao/sqlglot/pull/6923) by [@doripo](https://github.com/doripo))*:\n\n  add exp.Trunc for numeric truncation (#6923)\n\n- due to [`1418494`](https://github.com/tobymao/sqlglot/commit/1418494f777358f4b6bd1e05ee5cb02591d92c74) - Annotate FORMAT_STRING(expr) for Spark/DBX *(PR [#6962](https://github.com/tobymao/sqlglot/pull/6962) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate FORMAT_STRING(expr) for Spark/DBX (#6962)\n\n- due to [`37fa84e`](https://github.com/tobymao/sqlglot/commit/37fa84e389b6bcbc94326d3defb4664d0826fb3f) - support `CURRENT_VERSION()` transpilation for Spark *(PR [#6964](https://github.com/tobymao/sqlglot/pull/6964) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support `CURRENT_VERSION()` transpilation for Spark (#6964)\n\n- due to [`51d3ebd`](https://github.com/tobymao/sqlglot/commit/51d3ebdca83e114449590d9f337ae6902659a8b4) - transpile `CURRENT_VERSION()` to MySQL *(PR [#6965](https://github.com/tobymao/sqlglot/pull/6965) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  transpile `CURRENT_VERSION()` to MySQL (#6965)\n\n- due to [`9008553`](https://github.com/tobymao/sqlglot/commit/90085534eb8863f588003bdf65d96771729889aa) - transpile CURRENT_VERSION() to ClickHouse, Postgres, Trino, Redshift *(PR [#6966](https://github.com/tobymao/sqlglot/pull/6966) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  transpile CURRENT_VERSION() to ClickHouse, Postgres, Trino, Redshift (#6966)\n\n- due to [`e8b379e`](https://github.com/tobymao/sqlglot/commit/e8b379eb67d034f829d2fd50daefea2a98b83976) - Map SQLITE_VERSION() to exp.CurrentVersion expression *(PR [#6967](https://github.com/tobymao/sqlglot/pull/6967) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Map SQLITE_VERSION() to exp.CurrentVersion expression (#6967)\n\n- due to [`dfd299f`](https://github.com/tobymao/sqlglot/commit/dfd299fcfaf7a61d13b073e7b59d6bdd0748c7b8) - Annotate `RANDSTR(expr)` for Spark/DBX *(PR [#6971](https://github.com/tobymao/sqlglot/pull/6971) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `RANDSTR(expr)` for Spark/DBX (#6971)\n\n- due to [`969e45d`](https://github.com/tobymao/sqlglot/commit/969e45d3ba1db25f4561b122b9401b5608356f58) - Annotate REPEAT(expr) for Hive, Spark, DBX *(PR [#6974](https://github.com/tobymao/sqlglot/pull/6974) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate REPEAT(expr) for Hive, Spark, DBX (#6974)\n\n- due to [`ade3639`](https://github.com/tobymao/sqlglot/commit/ade3639b337d0222a00feec7ac9762571586f7ab) - transpilation support current_database *(PR [#6973](https://github.com/tobymao/sqlglot/pull/6973) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support current_database (#6973)\n\n- due to [`57093d1`](https://github.com/tobymao/sqlglot/commit/57093d15d5bbc2217366ace42db109e215dca79f) - Annotate `OVERLAY(expr)` for Spark/DBX *(PR [#6970](https://github.com/tobymao/sqlglot/pull/6970) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `OVERLAY(expr)` for Spark/DBX (#6970)\n\n- due to [`f5b2328`](https://github.com/tobymao/sqlglot/commit/f5b23281b6829bace426808f0a55e73590b70bbd) - Annotate RIGHT(expr) for Spark/DBX *(PR [#6980](https://github.com/tobymao/sqlglot/pull/6980) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate RIGHT(expr) for Spark/DBX (#6980)\n\n- due to [`61a0d3f`](https://github.com/tobymao/sqlglot/commit/61a0d3f05be478dd4552e6559b6781891d4a3447) - add support for JAROWINKLER_SIMILARITY *(PR [#6977](https://github.com/tobymao/sqlglot/pull/6977) by [@kyle-cheung](https://github.com/kyle-cheung))*:\n\n  add support for JAROWINKLER_SIMILARITY (#6977)\n\n- due to [`9d1f4e0`](https://github.com/tobymao/sqlglot/commit/9d1f4e0ea6f8b66b022a5263320275ed43efb5f3) - transpilation support current_schema *(PR [#6976](https://github.com/tobymao/sqlglot/pull/6976) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support current_schema (#6976)\n\n- due to [`0d345aa`](https://github.com/tobymao/sqlglot/commit/0d345aafd037b047808716dfdb60cc554d47941d) - Annotate REPLACE(expr) for Hive, Spark and DBX *(PR [#6975](https://github.com/tobymao/sqlglot/pull/6975) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate REPLACE(expr) for Hive, Spark and DBX (#6975)\n\n- due to [`19f9000`](https://github.com/tobymao/sqlglot/commit/19f900031c9abe26bebb541e8907ca263454055c) - transpilation support current_version *(PR [#6960](https://github.com/tobymao/sqlglot/pull/6960) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support current_version (#6960)\n\n- due to [`f103a16`](https://github.com/tobymao/sqlglot/commit/f103a166aca95da726ac9281816181e53b916dc3) - support parsing `VERSION()` for ClickHouse *(PR [#6986](https://github.com/tobymao/sqlglot/pull/6986) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support parsing `VERSION()` for ClickHouse (#6986)\n\n- due to [`2751c8f`](https://github.com/tobymao/sqlglot/commit/2751c8ff1d6c4acc1a0d407e601d572886ceffc3) - parse support for `VERSION()` *(PR [#6985](https://github.com/tobymao/sqlglot/pull/6985) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  parse support for `VERSION()` (#6985)\n\n- due to [`37db91c`](https://github.com/tobymao/sqlglot/commit/37db91c5cea14488654a2b69aceab13b6c6a98b7) - Annotate `REVERSE(expr)` for Hive, Spark and DBX *(PR [#6979](https://github.com/tobymao/sqlglot/pull/6979) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `REVERSE(expr)` for Hive, Spark and DBX (#6979)\n\n- due to [`f4d53a2`](https://github.com/tobymao/sqlglot/commit/f4d53a2d0f9aadf7fc63e484d64821cddf5d1f17) - support parsing VERSION() for Postgres/Redshift *(PR [#6987](https://github.com/tobymao/sqlglot/pull/6987) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support parsing VERSION() for Postgres/Redshift (#6987)\n\n- due to [`71bb0c3`](https://github.com/tobymao/sqlglot/commit/71bb0c3f7947c1959160c8b401354ceddee2e8ce) - support for version() for trino *(PR [#6988](https://github.com/tobymao/sqlglot/pull/6988) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support for version() for trino (#6988)\n\n- due to [`fbca704`](https://github.com/tobymao/sqlglot/commit/fbca7040cd3ae9eb0bc599b5ce656724fccafab1) - Annotate SPLIT(expr) for Hive/Spark/DBX *(PR [#6990](https://github.com/tobymao/sqlglot/pull/6990) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SPLIT(expr) for Hive/Spark/DBX (#6990)\n\n- due to [`264e95f`](https://github.com/tobymao/sqlglot/commit/264e95f04d95f2cd7bcf255ee7ae160db36882a7) - Move TRANSLATE(expr) annotator to base *(PR [#6992](https://github.com/tobymao/sqlglot/pull/6992) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Move TRANSLATE(expr) annotator to base (#6992)\n\n- due to [`eb8ad51`](https://github.com/tobymao/sqlglot/commit/eb8ad518142dc91e25d37310cb9cbfa33c44fe34) - Annotate FILTER(expr, func) for Spark/DBX *(PR [#6995](https://github.com/tobymao/sqlglot/pull/6995) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate FILTER(expr, func) for Spark/DBX (#6995)\n\n- due to [`12e0869`](https://github.com/tobymao/sqlglot/commit/12e0869ff6820b35884e189b2b4f29aef56c3a51) - annotate CURRENT_TIMESTAMP for MySQL *(PR [#7004](https://github.com/tobymao/sqlglot/pull/7004) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate CURRENT_TIMESTAMP for MySQL (#7004)\n\n- due to [`cbdad37`](https://github.com/tobymao/sqlglot/commit/cbdad3762dd6935d75405a0c33a1656cab8c2d1e) - support CURTIME() for MySQL/SingleStore *(PR [#7005](https://github.com/tobymao/sqlglot/pull/7005) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support CURTIME() for MySQL/SingleStore (#7005)\n\n- due to [`d16cc62`](https://github.com/tobymao/sqlglot/commit/d16cc62ea8342bec91092f8c7cf2504364581a7e) - annotate ADD_MONTH(expr) for Hive/Spark/DBX *(PR [#7003](https://github.com/tobymao/sqlglot/pull/7003) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate ADD_MONTH(expr) for Hive/Spark/DBX (#7003)\n\n- due to [`6381c48`](https://github.com/tobymao/sqlglot/commit/6381c4825c1929da56363035be2c4ae7a90336dd) - support NOW() for exasol *(PR [#7006](https://github.com/tobymao/sqlglot/pull/7006) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support NOW() for exasol (#7006)\n\n- due to [`d9ea168`](https://github.com/tobymao/sqlglot/commit/d9ea1683a98252ad43948be32fbd7cf77d17b67c) - annotate CURRENT_USER to base *(PR [#7007](https://github.com/tobymao/sqlglot/pull/7007) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate CURRENT_USER to base (#7007)\n\n- due to [`7a2a777`](https://github.com/tobymao/sqlglot/commit/7a2a777fb8c215b51436942645965792257b8dc9) - annotate FROM_UTC_TIMESTAMP(expr) for Spark/DBX *(PR [#7008](https://github.com/tobymao/sqlglot/pull/7008) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate FROM_UTC_TIMESTAMP(expr) for Spark/DBX (#7008)\n\n- due to [`8be32fd`](https://github.com/tobymao/sqlglot/commit/8be32fde55c8d256e73fd504246f695bf550f4cb) - support MAKE_TIMESTAMP(expr) for Spark/DBX *(PR [#7009](https://github.com/tobymao/sqlglot/pull/7009) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support MAKE_TIMESTAMP(expr) for Spark/DBX (#7009)\n\n- due to [`b951d74`](https://github.com/tobymao/sqlglot/commit/b951d740a934a8f46ce2c96caf7d8ae80b61604c) - annotate NEXT_DAY(expr) for Hive/Spark/DBX *(PR [#7010](https://github.com/tobymao/sqlglot/pull/7010) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate NEXT_DAY(expr) for Hive/Spark/DBX (#7010)\n\n- due to [`404797a`](https://github.com/tobymao/sqlglot/commit/404797acfb1a9f860bd87880fecacd79cb1b2161) - Move `CURRENT_SCHEMA()` to Base Annotator *(PR [#7021](https://github.com/tobymao/sqlglot/pull/7021) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Move `CURRENT_SCHEMA()` to Base Annotator (#7021)\n\n- due to [`994d3a3`](https://github.com/tobymao/sqlglot/commit/994d3a37983791d4e8484d6d39b819a1cff2f774) - robust STAR with EXCLUDE (redshift) *(PR [#6972](https://github.com/tobymao/sqlglot/pull/6972) by [@geooo109](https://github.com/geooo109))*:\n\n  robust STAR with EXCLUDE (redshift) (#6972)\n\n- due to [`3ea80fb`](https://github.com/tobymao/sqlglot/commit/3ea80fb86482e257c4565ab7876dd6cdd60a7be2) - annotate REVERSE(str) for DuckDB *(PR [#7018](https://github.com/tobymao/sqlglot/pull/7018) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate REVERSE(str) for DuckDB (#7018)\n\n- due to [`352fb94`](https://github.com/tobymao/sqlglot/commit/352fb94c46e5dd0dfd824b4472b03cccf21d3f56) - annotate ISODOW(expr) for DuckDB *(PR [#7016](https://github.com/tobymao/sqlglot/pull/7016) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate ISODOW(expr) for DuckDB (#7016)\n\n- due to [`c2e5954`](https://github.com/tobymao/sqlglot/commit/c2e59545d8030b1d2e7859631c9d75ea0f6df883) - annotate COUNTIF(expr) for DuckDB *(PR [#7012](https://github.com/tobymao/sqlglot/pull/7012) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate COUNTIF(expr) for DuckDB (#7012)\n\n- due to [`e35ee14`](https://github.com/tobymao/sqlglot/commit/e35ee143b08f671175e730e602e9a5dcd9155fde) - annotate CountIf(expr) for ClickHouse *(PR [#7013](https://github.com/tobymao/sqlglot/pull/7013) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate CountIf(expr) for ClickHouse (#7013)\n\n- due to [`b73be3e`](https://github.com/tobymao/sqlglot/commit/b73be3e8c95b44f2cd71498592bd4b5b63ba02d9) - support `today()` for duckdb *(PR [#7015](https://github.com/tobymao/sqlglot/pull/7015) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support `today()` for duckdb (#7015)\n\n- due to [`36e3310`](https://github.com/tobymao/sqlglot/commit/36e3310959260a7b1124a60589cdc90a3e631624) - support current_schema as no_param *(PR [#7000](https://github.com/tobymao/sqlglot/pull/7000) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support current_schema as no_param (#7000)\n\n- due to [`fd8860b`](https://github.com/tobymao/sqlglot/commit/fd8860b8b8d5e0c29c53597485c656923375e1d9) - annotate FORMAT(expr) for DuckDB *(PR [#7017](https://github.com/tobymao/sqlglot/pull/7017) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate FORMAT(expr) for DuckDB (#7017)\n\n- due to [`eecdfa1`](https://github.com/tobymao/sqlglot/commit/eecdfa1b15ac1808f93107d2ad6a51f52ffaf7cc) - Annotate `DAYOFWEEK(expr)`, `DAYOFMONTH(expr)` for Hive/Spark/DBX *(PR [#6996](https://github.com/tobymao/sqlglot/pull/6996) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `DAYOFWEEK(expr)`, `DAYOFMONTH(expr)` for Hive/Spark/DBX (#6996)\n\n- due to [`4fc26f0`](https://github.com/tobymao/sqlglot/commit/4fc26f086701cebb3d3974b762d12e1435f4a195) - annotate `TIME_BUCKET(expr)` for DuckDB *(PR [#7014](https://github.com/tobymao/sqlglot/pull/7014) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate `TIME_BUCKET(expr)` for DuckDB (#7014)\n\n- due to [`00e6d9a`](https://github.com/tobymao/sqlglot/commit/00e6d9af02971aad6e7102cae3af2a7192fa7070) - annotate `UNIX_DATE(expr)` for Spark/DBX *(PR [#7011](https://github.com/tobymao/sqlglot/pull/7011) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate `UNIX_DATE(expr)` for Spark/DBX (#7011)\n\n- due to [`86a5509`](https://github.com/tobymao/sqlglot/commit/86a5509bfcb8df6a8cf8b0971d9d12ae3204f2af) - support user for exasol *(PR [#7001](https://github.com/tobymao/sqlglot/pull/7001) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support user for exasol (#7001)\n\n- due to [`b27c163`](https://github.com/tobymao/sqlglot/commit/b27c163fcce0a4b0a4f75d131cdc105353e95464) - support `CURDATE` for Exasol *(PR [#6999](https://github.com/tobymao/sqlglot/pull/6999) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support `CURDATE` for Exasol (#6999)\n\n- due to [`47dc558`](https://github.com/tobymao/sqlglot/commit/47dc5589f8b165d9f0296e6aa48de337f556f1a4) - annotate ARRAY_COMPACT(expr) for Spark/DBX *(PR [#7034](https://github.com/tobymao/sqlglot/pull/7034) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate ARRAY_COMPACT(expr) for Spark/DBX (#7034)\n\n- due to [`9d7db06`](https://github.com/tobymao/sqlglot/commit/9d7db06cf8ef66583f11b6d54af573bb28f4434b) - Generator for ARRAY_INSERT(expr) *(PR [#7036](https://github.com/tobymao/sqlglot/pull/7036) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Generator for ARRAY_INSERT(expr) (#7036)\n\n- due to [`235b8ac`](https://github.com/tobymao/sqlglot/commit/235b8ac24d41324239d6581b5636ad19ec7b9376) - annotate `ARRAY_INTERSECT(expr)` for Hive/Spark/DBX *(PR [#7037](https://github.com/tobymao/sqlglot/pull/7037) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate `ARRAY_INTERSECT(expr)` for Hive/Spark/DBX (#7037)\n\n- due to [`f476d07`](https://github.com/tobymao/sqlglot/commit/f476d071a1412fb2d9cd6f39067380252ab4c15a) - update transpilation of SEQ functions and GENERATOR for DuckDB *(PR [#7029](https://github.com/tobymao/sqlglot/pull/7029) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  update transpilation of SEQ functions and GENERATOR for DuckDB (#7029)\n\n- due to [`643a6c7`](https://github.com/tobymao/sqlglot/commit/643a6c7d97292eb29ba1bac6523747e161544a1a) - Transpilation support for Snowflake REGEXP_LIKE to DuckDB *(PR [#7030](https://github.com/tobymao/sqlglot/pull/7030) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation support for Snowflake REGEXP_LIKE to DuckDB (#7030)\n\n- due to [`b1f0542`](https://github.com/tobymao/sqlglot/commit/b1f05428d8a4d441398c0f3d4a65b49b0eda2729) - tokenizer optimizations *(PR [#7038](https://github.com/tobymao/sqlglot/pull/7038) by [@geooo109](https://github.com/geooo109))*:\n\n  tokenizer optimizations (#7038)\n\n- due to [`70d6f2b`](https://github.com/tobymao/sqlglot/commit/70d6f2b5d8c0f37550ba7a288c5f7f7021c66bd7) - annotate `ARRAY_INSERT(expr)` for Spark/DBX *(PR [#7044](https://github.com/tobymao/sqlglot/pull/7044) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate `ARRAY_INSERT(expr)` for Spark/DBX (#7044)\n\n- due to [`3019d0a`](https://github.com/tobymao/sqlglot/commit/3019d0a0110a503b68b2a3cf7f93be1000f20a40) - Map BIT_GET to GETBIT for Spark/DBX *(PR [#7041](https://github.com/tobymao/sqlglot/pull/7041) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Map BIT_GET to GETBIT for Spark/DBX (#7041)\n\n- due to [`27ae429`](https://github.com/tobymao/sqlglot/commit/27ae42987864949f74b784d3dbb063bd3450e0dc) - transpile BIT_COUNT to DuckDB *(PR [#7039](https://github.com/tobymao/sqlglot/pull/7039) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  transpile BIT_COUNT to DuckDB (#7039)\n\n- due to [`f6cbd27`](https://github.com/tobymao/sqlglot/commit/f6cbd27fd61937efba6879f20f5ff0239e678469) - Add support for trigger DDL statements *(PR [#6978](https://github.com/tobymao/sqlglot/pull/6978) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add support for trigger DDL statements (#6978)\n\n- due to [`749cf18`](https://github.com/tobymao/sqlglot/commit/749cf18dbcb8d1d0c7d144e6481ecc3a443d4a0e) - require original SQL in `Parser.parse` *(PR [#7045](https://github.com/tobymao/sqlglot/pull/7045) by [@georgesittas](https://github.com/georgesittas))*:\n\n  require original SQL in `Parser.parse` (#7045)\n\n- due to [`f9d1f73`](https://github.com/tobymao/sqlglot/commit/f9d1f73b490e6694da0e800d9a5a70e1ba7f38d5) - refactor colon (extract) parsing precedence *(PR [#7046](https://github.com/tobymao/sqlglot/pull/7046) by [@georgesittas](https://github.com/georgesittas))*:\n\n  refactor colon (extract) parsing precedence (#7046)\n\n- due to [`c5939c1`](https://github.com/tobymao/sqlglot/commit/c5939c12c6816437f5abda3322f99cc597b1616c) - Map curdate to current_date for Spark/DBX *(PR [#7048](https://github.com/tobymao/sqlglot/pull/7048) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Map curdate to current_date for Spark/DBX (#7048)\n\n- due to [`58419e1`](https://github.com/tobymao/sqlglot/commit/58419e1f47119a135276a722b85ccfa92ae3d1f1) - move `SessionUser` to base *(PR [#7049](https://github.com/tobymao/sqlglot/pull/7049) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  move `SessionUser` to base (#7049)\n\n- due to [`13ee312`](https://github.com/tobymao/sqlglot/commit/13ee31281fe21c670f8ce1656520c18762e8402f) - use `IntEnum` instead of auto/string enums *(PR [#7050](https://github.com/tobymao/sqlglot/pull/7050) by [@georgesittas](https://github.com/georgesittas))*:\n\n  use `IntEnum` instead of auto/string enums (#7050)\n\n- due to [`f98cba1`](https://github.com/tobymao/sqlglot/commit/f98cba17caa8f3c2dcd5669f3525094c5dd58781) - move exp.Rand to base *(PR [#7065](https://github.com/tobymao/sqlglot/pull/7065) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  move exp.Rand to base (#7065)\n\n- due to [`4f8a49c`](https://github.com/tobymao/sqlglot/commit/4f8a49cd42ef37178cd0626554bfd263a140046e) - Transpilation support for Snowflake REGEXP_COUNT to DuckDB *(PR [#7054](https://github.com/tobymao/sqlglot/pull/7054) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation support for Snowflake REGEXP_COUNT to DuckDB (#7054)\n\n- due to [`eaba8dc`](https://github.com/tobymao/sqlglot/commit/eaba8dc26b2ec28b8074eb0cf2b3db086cf7ccc3) - move DEGREES(expr) to base *(PR [#7074](https://github.com/tobymao/sqlglot/pull/7074) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  move DEGREES(expr) to base (#7074)\n\n- due to [`a87c1b4`](https://github.com/tobymao/sqlglot/commit/a87c1b46b54ef7f6895c68666c05041924691576) - annotate DEGREES(expr) for T-SQL *(PR [#7077](https://github.com/tobymao/sqlglot/pull/7077) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate DEGREES(expr) for T-SQL (#7077)\n\n- due to [`f057a1b`](https://github.com/tobymao/sqlglot/commit/f057a1b912d473a77f111d33f40ecce2f6d54cf8) - move MONTHNAME to base *(PR [#7083](https://github.com/tobymao/sqlglot/pull/7083) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  move MONTHNAME to base (#7083)\n\n- due to [`dd2cf85`](https://github.com/tobymao/sqlglot/commit/dd2cf850134a2833d0da5eb58206accc298c9e0a) - support `UTCTimestamp` *(PR [#7082](https://github.com/tobymao/sqlglot/pull/7082) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support `UTCTimestamp` (#7082)\n\n- due to [`7ff13c6`](https://github.com/tobymao/sqlglot/commit/7ff13c690dddd9e1024b1a4ab6c9532f67bdece8) - transpilation support map_contains_key *(PR [#7070](https://github.com/tobymao/sqlglot/pull/7070) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support map_contains_key (#7070)\n\n- due to [`6fa494d`](https://github.com/tobymao/sqlglot/commit/6fa494da191753da8b85fb6a706b99f7f4950f43) - support `arrayCompact(expr)` function *(PR [#7084](https://github.com/tobymao/sqlglot/pull/7084) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support `arrayCompact(expr)` function (#7084)\n\n- due to [`a58c37d`](https://github.com/tobymao/sqlglot/commit/a58c37d24441f12af92df329269910bc7b5a0c8f) - transpilation of Snowflake REGEXP_REPLACE to DuckDB *(PR [#7078](https://github.com/tobymao/sqlglot/pull/7078) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  transpilation of Snowflake REGEXP_REPLACE to DuckDB (#7078)\n\n- due to [`dfcd5d7`](https://github.com/tobymao/sqlglot/commit/dfcd5d75f1caf3a32d5930a91f4fb1de598aa914) - Add Exasol reserved keywords to exasol dialect *(PR [#7086](https://github.com/tobymao/sqlglot/pull/7086) by [@nnamdi16](https://github.com/nnamdi16))*:\n\n  Add Exasol reserved keywords to exasol dialect (#7086)\n\n- due to [`7664358`](https://github.com/tobymao/sqlglot/commit/7664358b27599936dacb2b7d8e5329fe32425e62) - fix parsing error in json_value for exasol dialect *(PR [#7088](https://github.com/tobymao/sqlglot/pull/7088) by [@nnamdi16](https://github.com/nnamdi16))*:\n\n  fix parsing error in json_value for exasol dialect (#7088)\n\n- due to [`217e960`](https://github.com/tobymao/sqlglot/commit/217e960f57675cc5f5cb9ff9996c048a31d8004c) - annotate ARRAY_CONTAINS *(PR [#7099](https://github.com/tobymao/sqlglot/pull/7099) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate ARRAY_CONTAINS (#7099)\n\n- due to [`1074d66`](https://github.com/tobymao/sqlglot/commit/1074d66231d1de64b6b9aa43de6afbdc6717da5f) - transpilation of Snowflake REGEXP_INSTR to DuckDB *(PR [#7097](https://github.com/tobymao/sqlglot/pull/7097) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  transpilation of Snowflake REGEXP_INSTR to DuckDB (#7097)\n\n- due to [`ab1c2ab`](https://github.com/tobymao/sqlglot/commit/ab1c2ab44556a8f7ffe7dad09e4b50e75b122b5d) - annotate PERCENTILE/APPROX_PERCENTILE for hive, spark2, spark, dbx *(PR [#7100](https://github.com/tobymao/sqlglot/pull/7100) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate PERCENTILE/APPROX_PERCENTILE for hive, spark2, spark, dbx (#7100)\n\n- due to [`0f8287d`](https://github.com/tobymao/sqlglot/commit/0f8287d8e5ff1eee2aea29001443ee00a4b2ae47) - annotate BIT_OR(expr) for Spark/DBX *(PR [#7101](https://github.com/tobymao/sqlglot/pull/7101) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate BIT_OR(expr) for Spark/DBX (#7101)\n\n- due to [`e19abfd`](https://github.com/tobymao/sqlglot/commit/e19abfded7c159c30f53063d10ae57406553f75d) - annotate BIT_AND(expr) for Spark/DBX *(PR [#7103](https://github.com/tobymao/sqlglot/pull/7103) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate BIT_AND(expr) for Spark/DBX (#7103)\n\n- due to [`0c4f74f`](https://github.com/tobymao/sqlglot/commit/0c4f74f6574d3acf06935626615e2e1f14ae9c04) - parse and annotate ELEMENT_AT for spark2, spark, dbx *(PR [#7104](https://github.com/tobymao/sqlglot/pull/7104) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate ELEMENT_AT for spark2, spark, dbx (#7104)\n\n- due to [`8b8aef0`](https://github.com/tobymao/sqlglot/commit/8b8aef01197b670a727a59e46727b5a57f106a5d) - annotate BIT_XOR(expr) for Spark/DBX *(PR [#7106](https://github.com/tobymao/sqlglot/pull/7106) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate BIT_XOR(expr) for Spark/DBX (#7106)\n\n- due to [`1b1d57a`](https://github.com/tobymao/sqlglot/commit/1b1d57a3541f48291a1534a0b1a28948f8b8207e) - transpilation of Snowflake REGEXP_SUBSTR and REGEXP_SUBSTR_ALL to DuckDB *(PR [#7095](https://github.com/tobymao/sqlglot/pull/7095) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  transpilation of Snowflake REGEXP_SUBSTR and REGEXP_SUBSTR_ALL to DuckDB (#7095)\n\n- due to [`4f964db`](https://github.com/tobymao/sqlglot/commit/4f964db9bfbe42b31dc9f191e687cb2f5f7db0d8) - remove prefix from data type and cleanup *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  remove prefix from data type and cleanup\n\n- due to [`0858599`](https://github.com/tobymao/sqlglot/commit/0858599e274567f7a8c9361afab1526cd2d58eea) - support arrayConcat to clickhouse *(PR [#7108](https://github.com/tobymao/sqlglot/pull/7108) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support arrayConcat to clickhouse (#7108)\n\n- due to [`73ab9af`](https://github.com/tobymao/sqlglot/commit/73ab9af9c95274b3feac9278145432c27695054e) - support arrayDistinct(expr) for clickhouse *(PR [#7114](https://github.com/tobymao/sqlglot/pull/7114) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support arrayDistinct(expr) for clickhouse (#7114)\n\n- due to [`37fa8c7`](https://github.com/tobymao/sqlglot/commit/37fa8c7219a2f17a351f948d6eb314a543f8784d) - Split core functionality to TokenizerCore *(PR [#7116](https://github.com/tobymao/sqlglot/pull/7116) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Split core functionality to TokenizerCore (#7116)\n\n- due to [`1150e39`](https://github.com/tobymao/sqlglot/commit/1150e3911b0b82a9683f765de11605b14fd66ddb) - expression_core which is now compilable by mypyc *(PR [#7117](https://github.com/tobymao/sqlglot/pull/7117) by [@tobymao](https://github.com/tobymao))*:\n\n  expression_core which is now compilable by mypyc (#7117)\n\n- due to [`f796956`](https://github.com/tobymao/sqlglot/commit/f796956d3cc857995572d715b8db530b2d76b8d6) - annotate `ArrayDistinct` for Hive/Spark/DBX *(PR [#7119](https://github.com/tobymao/sqlglot/pull/7119) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate `ArrayDistinct` for Hive/Spark/DBX (#7119)\n\n- due to [`2069b06`](https://github.com/tobymao/sqlglot/commit/2069b06284d6998d94c43c16d4b46ac50ea0d84a) - annotate ARRAY_EXCEPT for Hive/Spark/DBX *(PR [#7123](https://github.com/tobymao/sqlglot/pull/7123) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate ARRAY_EXCEPT for Hive/Spark/DBX (#7123)\n\n- due to [`5a30754`](https://github.com/tobymao/sqlglot/commit/5a30754df09ddb1260b394c812596adb03c2710d) - support `current_localtimestamp()` for DuckDB *(PR [#7128](https://github.com/tobymao/sqlglot/pull/7128) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support `current_localtimestamp()` for DuckDB (#7128)\n\n- due to [`93bf337`](https://github.com/tobymao/sqlglot/commit/93bf337ca2af1b5d9b06d6bf3c50c5bcce680077) - annotate date_diff(expr) for DuckDB *(PR [#7125](https://github.com/tobymao/sqlglot/pull/7125) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate date_diff(expr) for DuckDB (#7125)\n\n\n### :sparkles: New Features\n- [`c8ddcc3`](https://github.com/tobymao/sqlglot/commit/c8ddcc383bab07b807ed1d6b6f9bef91417e43c1) - **optimizer**: Annotate COLLATION(expr) for Spark/DBX *(PR [#6957](https://github.com/tobymao/sqlglot/pull/6957) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`fc4b332`](https://github.com/tobymao/sqlglot/commit/fc4b3326a14a1b42bc954914ce43b8dad7ef23b2) - **optimizer**: Annotate BITMAP_COUNT(expr) for Spark/DBX *(PR [#6956](https://github.com/tobymao/sqlglot/pull/6956) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`8725010`](https://github.com/tobymao/sqlglot/commit/87250100eb2a1d2c206a26cce276f7babec0e409) - add exp.Trunc for numeric truncation *(PR [#6923](https://github.com/tobymao/sqlglot/pull/6923) by [@doripo](https://github.com/doripo))*\n- [`1418494`](https://github.com/tobymao/sqlglot/commit/1418494f777358f4b6bd1e05ee5cb02591d92c74) - **optimizer**: Annotate FORMAT_STRING(expr) for Spark/DBX *(PR [#6962](https://github.com/tobymao/sqlglot/pull/6962) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`ff1fd52`](https://github.com/tobymao/sqlglot/commit/ff1fd521147cb66acc36f2da7b1590d9e7f8140f) - **generator**: Add numeric TRUNC output for additional dialects *(PR [#6961](https://github.com/tobymao/sqlglot/pull/6961) by [@doripo](https://github.com/doripo))*\n- [`37fa84e`](https://github.com/tobymao/sqlglot/commit/37fa84e389b6bcbc94326d3defb4664d0826fb3f) - **snowflake**: support `CURRENT_VERSION()` transpilation for Spark *(PR [#6964](https://github.com/tobymao/sqlglot/pull/6964) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`51d3ebd`](https://github.com/tobymao/sqlglot/commit/51d3ebdca83e114449590d9f337ae6902659a8b4) - **snowflake**: transpile `CURRENT_VERSION()` to MySQL *(PR [#6965](https://github.com/tobymao/sqlglot/pull/6965) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`9008553`](https://github.com/tobymao/sqlglot/commit/90085534eb8863f588003bdf65d96771729889aa) - **snowflake**: transpile CURRENT_VERSION() to ClickHouse, Postgres, Trino, Redshift *(PR [#6966](https://github.com/tobymao/sqlglot/pull/6966) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`e8b379e`](https://github.com/tobymao/sqlglot/commit/e8b379eb67d034f829d2fd50daefea2a98b83976) - **sqlite**: Map SQLITE_VERSION() to exp.CurrentVersion expression *(PR [#6967](https://github.com/tobymao/sqlglot/pull/6967) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`dfd299f`](https://github.com/tobymao/sqlglot/commit/dfd299fcfaf7a61d13b073e7b59d6bdd0748c7b8) - **optimizer**: Annotate `RANDSTR(expr)` for Spark/DBX *(PR [#6971](https://github.com/tobymao/sqlglot/pull/6971) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`969e45d`](https://github.com/tobymao/sqlglot/commit/969e45d3ba1db25f4561b122b9401b5608356f58) - **optimizer**: Annotate REPEAT(expr) for Hive, Spark, DBX *(PR [#6974](https://github.com/tobymao/sqlglot/pull/6974) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`ade3639`](https://github.com/tobymao/sqlglot/commit/ade3639b337d0222a00feec7ac9762571586f7ab) - **snowflake**: transpilation support current_database *(PR [#6973](https://github.com/tobymao/sqlglot/pull/6973) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`57093d1`](https://github.com/tobymao/sqlglot/commit/57093d15d5bbc2217366ace42db109e215dca79f) - **optimizer**: Annotate `OVERLAY(expr)` for Spark/DBX *(PR [#6970](https://github.com/tobymao/sqlglot/pull/6970) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`f5b2328`](https://github.com/tobymao/sqlglot/commit/f5b23281b6829bace426808f0a55e73590b70bbd) - **optimizer**: Annotate RIGHT(expr) for Spark/DBX *(PR [#6980](https://github.com/tobymao/sqlglot/pull/6980) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`61a0d3f`](https://github.com/tobymao/sqlglot/commit/61a0d3f05be478dd4552e6559b6781891d4a3447) - **snowflake**: add support for JAROWINKLER_SIMILARITY *(PR [#6977](https://github.com/tobymao/sqlglot/pull/6977) by [@kyle-cheung](https://github.com/kyle-cheung))*\n- [`9d1f4e0`](https://github.com/tobymao/sqlglot/commit/9d1f4e0ea6f8b66b022a5263320275ed43efb5f3) - **snowflake**: transpilation support current_schema *(PR [#6976](https://github.com/tobymao/sqlglot/pull/6976) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`0d345aa`](https://github.com/tobymao/sqlglot/commit/0d345aafd037b047808716dfdb60cc554d47941d) - **optimizer**: Annotate REPLACE(expr) for Hive, Spark and DBX *(PR [#6975](https://github.com/tobymao/sqlglot/pull/6975) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`19f9000`](https://github.com/tobymao/sqlglot/commit/19f900031c9abe26bebb541e8907ca263454055c) - **snowflake**: transpilation support current_version *(PR [#6960](https://github.com/tobymao/sqlglot/pull/6960) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`f103a16`](https://github.com/tobymao/sqlglot/commit/f103a166aca95da726ac9281816181e53b916dc3) - **optimizer**: support parsing `VERSION()` for ClickHouse *(PR [#6986](https://github.com/tobymao/sqlglot/pull/6986) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2751c8f`](https://github.com/tobymao/sqlglot/commit/2751c8ff1d6c4acc1a0d407e601d572886ceffc3) - **mysql**: parse support for `VERSION()` *(PR [#6985](https://github.com/tobymao/sqlglot/pull/6985) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`37db91c`](https://github.com/tobymao/sqlglot/commit/37db91c5cea14488654a2b69aceab13b6c6a98b7) - **optimizer**: Annotate `REVERSE(expr)` for Hive, Spark and DBX *(PR [#6979](https://github.com/tobymao/sqlglot/pull/6979) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`f4d53a2`](https://github.com/tobymao/sqlglot/commit/f4d53a2d0f9aadf7fc63e484d64821cddf5d1f17) - **postgres**: support parsing VERSION() for Postgres/Redshift *(PR [#6987](https://github.com/tobymao/sqlglot/pull/6987) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`71bb0c3`](https://github.com/tobymao/sqlglot/commit/71bb0c3f7947c1959160c8b401354ceddee2e8ce) - **trino**: support for version() for trino *(PR [#6988](https://github.com/tobymao/sqlglot/pull/6988) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`fbca704`](https://github.com/tobymao/sqlglot/commit/fbca7040cd3ae9eb0bc599b5ce656724fccafab1) - **optimizer**: Annotate SPLIT(expr) for Hive/Spark/DBX *(PR [#6990](https://github.com/tobymao/sqlglot/pull/6990) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`264e95f`](https://github.com/tobymao/sqlglot/commit/264e95f04d95f2cd7bcf255ee7ae160db36882a7) - **optimizer**: Move TRANSLATE(expr) annotator to base *(PR [#6992](https://github.com/tobymao/sqlglot/pull/6992) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`eb8ad51`](https://github.com/tobymao/sqlglot/commit/eb8ad518142dc91e25d37310cb9cbfa33c44fe34) - **optimizer**: Annotate FILTER(expr, func) for Spark/DBX *(PR [#6995](https://github.com/tobymao/sqlglot/pull/6995) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`12e0869`](https://github.com/tobymao/sqlglot/commit/12e0869ff6820b35884e189b2b4f29aef56c3a51) - **optimizer**: annotate CURRENT_TIMESTAMP for MySQL *(PR [#7004](https://github.com/tobymao/sqlglot/pull/7004) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`cbdad37`](https://github.com/tobymao/sqlglot/commit/cbdad3762dd6935d75405a0c33a1656cab8c2d1e) - **mysql**: support CURTIME() for MySQL/SingleStore *(PR [#7005](https://github.com/tobymao/sqlglot/pull/7005) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`d16cc62`](https://github.com/tobymao/sqlglot/commit/d16cc62ea8342bec91092f8c7cf2504364581a7e) - **optimizer**: annotate ADD_MONTH(expr) for Hive/Spark/DBX *(PR [#7003](https://github.com/tobymao/sqlglot/pull/7003) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`6381c48`](https://github.com/tobymao/sqlglot/commit/6381c4825c1929da56363035be2c4ae7a90336dd) - **exasol**: support NOW() for exasol *(PR [#7006](https://github.com/tobymao/sqlglot/pull/7006) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`d9ea168`](https://github.com/tobymao/sqlglot/commit/d9ea1683a98252ad43948be32fbd7cf77d17b67c) - **optimizer**: annotate CURRENT_USER to base *(PR [#7007](https://github.com/tobymao/sqlglot/pull/7007) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`7a2a777`](https://github.com/tobymao/sqlglot/commit/7a2a777fb8c215b51436942645965792257b8dc9) - **optimizer**: annotate FROM_UTC_TIMESTAMP(expr) for Spark/DBX *(PR [#7008](https://github.com/tobymao/sqlglot/pull/7008) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`8be32fd`](https://github.com/tobymao/sqlglot/commit/8be32fde55c8d256e73fd504246f695bf550f4cb) - **spark**: support MAKE_TIMESTAMP(expr) for Spark/DBX *(PR [#7009](https://github.com/tobymao/sqlglot/pull/7009) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b951d74`](https://github.com/tobymao/sqlglot/commit/b951d740a934a8f46ce2c96caf7d8ae80b61604c) - **optimizer**: annotate NEXT_DAY(expr) for Hive/Spark/DBX *(PR [#7010](https://github.com/tobymao/sqlglot/pull/7010) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`404797a`](https://github.com/tobymao/sqlglot/commit/404797acfb1a9f860bd87880fecacd79cb1b2161) - **optimizer**: Move `CURRENT_SCHEMA()` to Base Annotator *(PR [#7021](https://github.com/tobymao/sqlglot/pull/7021) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`994d3a3`](https://github.com/tobymao/sqlglot/commit/994d3a37983791d4e8484d6d39b819a1cff2f774) - **parser**: robust STAR with EXCLUDE (redshift) *(PR [#6972](https://github.com/tobymao/sqlglot/pull/6972) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#6963](https://github.com/tobymao/sqlglot/issues/6963) opened by [@iboland](https://github.com/iboland)*\n- [`3ea80fb`](https://github.com/tobymao/sqlglot/commit/3ea80fb86482e257c4565ab7876dd6cdd60a7be2) - **optimizer**: annotate REVERSE(str) for DuckDB *(PR [#7018](https://github.com/tobymao/sqlglot/pull/7018) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`352fb94`](https://github.com/tobymao/sqlglot/commit/352fb94c46e5dd0dfd824b4472b03cccf21d3f56) - **optimizer**: annotate ISODOW(expr) for DuckDB *(PR [#7016](https://github.com/tobymao/sqlglot/pull/7016) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`c2e5954`](https://github.com/tobymao/sqlglot/commit/c2e59545d8030b1d2e7859631c9d75ea0f6df883) - **optimizer**: annotate COUNTIF(expr) for DuckDB *(PR [#7012](https://github.com/tobymao/sqlglot/pull/7012) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`e35ee14`](https://github.com/tobymao/sqlglot/commit/e35ee143b08f671175e730e602e9a5dcd9155fde) - **optimizer**: annotate CountIf(expr) for ClickHouse *(PR [#7013](https://github.com/tobymao/sqlglot/pull/7013) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`6971c17`](https://github.com/tobymao/sqlglot/commit/6971c1730b14b0516ff3aca3780f6d84203e6993) - **snowflake**: support aliases in semantic view dimensions *(PR [#6994](https://github.com/tobymao/sqlglot/pull/6994) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6993](https://github.com/tobymao/sqlglot/issues/6993) opened by [@sgomezvillamor](https://github.com/sgomezvillamor)*\n- [`b73be3e`](https://github.com/tobymao/sqlglot/commit/b73be3e8c95b44f2cd71498592bd4b5b63ba02d9) - **duckdb**: support `today()` for duckdb *(PR [#7015](https://github.com/tobymao/sqlglot/pull/7015) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`36e3310`](https://github.com/tobymao/sqlglot/commit/36e3310959260a7b1124a60589cdc90a3e631624) - **exasol**: support current_schema as no_param *(PR [#7000](https://github.com/tobymao/sqlglot/pull/7000) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`fd8860b`](https://github.com/tobymao/sqlglot/commit/fd8860b8b8d5e0c29c53597485c656923375e1d9) - **optimizer**: annotate FORMAT(expr) for DuckDB *(PR [#7017](https://github.com/tobymao/sqlglot/pull/7017) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`eecdfa1`](https://github.com/tobymao/sqlglot/commit/eecdfa1b15ac1808f93107d2ad6a51f52ffaf7cc) - **optimizer**: Annotate `DAYOFWEEK(expr)`, `DAYOFMONTH(expr)` for Hive/Spark/DBX *(PR [#6996](https://github.com/tobymao/sqlglot/pull/6996) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`4fc26f0`](https://github.com/tobymao/sqlglot/commit/4fc26f086701cebb3d3974b762d12e1435f4a195) - **optimizer**: annotate `TIME_BUCKET(expr)` for DuckDB *(PR [#7014](https://github.com/tobymao/sqlglot/pull/7014) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`00e6d9a`](https://github.com/tobymao/sqlglot/commit/00e6d9af02971aad6e7102cae3af2a7192fa7070) - **optimizer**: annotate `UNIX_DATE(expr)` for Spark/DBX *(PR [#7011](https://github.com/tobymao/sqlglot/pull/7011) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`86a5509`](https://github.com/tobymao/sqlglot/commit/86a5509bfcb8df6a8cf8b0971d9d12ae3204f2af) - **exasol**: support user for exasol *(PR [#7001](https://github.com/tobymao/sqlglot/pull/7001) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b27c163`](https://github.com/tobymao/sqlglot/commit/b27c163fcce0a4b0a4f75d131cdc105353e95464) - **optimizer**: support `CURDATE` for Exasol *(PR [#6999](https://github.com/tobymao/sqlglot/pull/6999) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`47dc558`](https://github.com/tobymao/sqlglot/commit/47dc5589f8b165d9f0296e6aa48de337f556f1a4) - **optimizer**: annotate ARRAY_COMPACT(expr) for Spark/DBX *(PR [#7034](https://github.com/tobymao/sqlglot/pull/7034) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`235b8ac`](https://github.com/tobymao/sqlglot/commit/235b8ac24d41324239d6581b5636ad19ec7b9376) - **optimizer**: annotate `ARRAY_INTERSECT(expr)` for Hive/Spark/DBX *(PR [#7037](https://github.com/tobymao/sqlglot/pull/7037) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`643a6c7`](https://github.com/tobymao/sqlglot/commit/643a6c7d97292eb29ba1bac6523747e161544a1a) - **snowflake**: Transpilation support for Snowflake REGEXP_LIKE to DuckDB *(PR [#7030](https://github.com/tobymao/sqlglot/pull/7030) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`70d6f2b`](https://github.com/tobymao/sqlglot/commit/70d6f2b5d8c0f37550ba7a288c5f7f7021c66bd7) - **optimizer**: annotate `ARRAY_INSERT(expr)` for Spark/DBX *(PR [#7044](https://github.com/tobymao/sqlglot/pull/7044) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`1011fca`](https://github.com/tobymao/sqlglot/commit/1011fca568e29db2b13d88da29dfbf6df3f41af4) - **duckdb**: transpile Snowflake's current_schemas *(PR [#7042](https://github.com/tobymao/sqlglot/pull/7042) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`3019d0a`](https://github.com/tobymao/sqlglot/commit/3019d0a0110a503b68b2a3cf7f93be1000f20a40) - **spark**: Map BIT_GET to GETBIT for Spark/DBX *(PR [#7041](https://github.com/tobymao/sqlglot/pull/7041) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`27ae429`](https://github.com/tobymao/sqlglot/commit/27ae42987864949f74b784d3dbb063bd3450e0dc) - **spark**: transpile BIT_COUNT to DuckDB *(PR [#7039](https://github.com/tobymao/sqlglot/pull/7039) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`f6cbd27`](https://github.com/tobymao/sqlglot/commit/f6cbd27fd61937efba6879f20f5ff0239e678469) - **postgres**: Add support for trigger DDL statements *(PR [#6978](https://github.com/tobymao/sqlglot/pull/6978) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n  - :arrow_lower_right: *addresses issue [#6926](https://github.com/tobymao/sqlglot/issues/6926) opened by [@Badg](https://github.com/Badg)*\n- [`c5939c1`](https://github.com/tobymao/sqlglot/commit/c5939c12c6816437f5abda3322f99cc597b1616c) - **spark**: Map curdate to current_date for Spark/DBX *(PR [#7048](https://github.com/tobymao/sqlglot/pull/7048) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`58419e1`](https://github.com/tobymao/sqlglot/commit/58419e1f47119a135276a722b85ccfa92ae3d1f1) - **optimizer**: move `SessionUser` to base *(PR [#7049](https://github.com/tobymao/sqlglot/pull/7049) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`3c67ec0`](https://github.com/tobymao/sqlglot/commit/3c67ec0007b509ec58297bf2c61f17d095b694b6) - **duckdb**: Add traspilation support for NULL values in ARRAY_CONTAINS function *(PR [#7055](https://github.com/tobymao/sqlglot/pull/7055) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`f98cba1`](https://github.com/tobymao/sqlglot/commit/f98cba17caa8f3c2dcd5669f3525094c5dd58781) - **optimizer**: move exp.Rand to base *(PR [#7065](https://github.com/tobymao/sqlglot/pull/7065) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`4f8a49c`](https://github.com/tobymao/sqlglot/commit/4f8a49cd42ef37178cd0626554bfd263a140046e) - **snowflake**: Transpilation support for Snowflake REGEXP_COUNT to DuckDB *(PR [#7054](https://github.com/tobymao/sqlglot/pull/7054) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`eaba8dc`](https://github.com/tobymao/sqlglot/commit/eaba8dc26b2ec28b8074eb0cf2b3db086cf7ccc3) - **optimizer**: move DEGREES(expr) to base *(PR [#7074](https://github.com/tobymao/sqlglot/pull/7074) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a87c1b4`](https://github.com/tobymao/sqlglot/commit/a87c1b46b54ef7f6895c68666c05041924691576) - **optimizer**: annotate DEGREES(expr) for T-SQL *(PR [#7077](https://github.com/tobymao/sqlglot/pull/7077) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`f057a1b`](https://github.com/tobymao/sqlglot/commit/f057a1b912d473a77f111d33f40ecce2f6d54cf8) - **optimizer**: move MONTHNAME to base *(PR [#7083](https://github.com/tobymao/sqlglot/pull/7083) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`dd2cf85`](https://github.com/tobymao/sqlglot/commit/dd2cf850134a2833d0da5eb58206accc298c9e0a) - **clickhouse**: support `UTCTimestamp` *(PR [#7082](https://github.com/tobymao/sqlglot/pull/7082) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`7ff13c6`](https://github.com/tobymao/sqlglot/commit/7ff13c690dddd9e1024b1a4ab6c9532f67bdece8) - **snowflake**: transpilation support map_contains_key *(PR [#7070](https://github.com/tobymao/sqlglot/pull/7070) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`6fa494d`](https://github.com/tobymao/sqlglot/commit/6fa494da191753da8b85fb6a706b99f7f4950f43) - **clickhouse**: support `arrayCompact(expr)` function *(PR [#7084](https://github.com/tobymao/sqlglot/pull/7084) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a58c37d`](https://github.com/tobymao/sqlglot/commit/a58c37d24441f12af92df329269910bc7b5a0c8f) - **snowflake**: transpilation of Snowflake REGEXP_REPLACE to DuckDB *(PR [#7078](https://github.com/tobymao/sqlglot/pull/7078) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`3a453d0`](https://github.com/tobymao/sqlglot/commit/3a453d045a261b82ed6a84b661da0c9a86e3161d) - **duckdb**: Add transpilation support for NULLs in ARRAY_DISTINCT *(PR [#7069](https://github.com/tobymao/sqlglot/pull/7069) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`dfcd5d7`](https://github.com/tobymao/sqlglot/commit/dfcd5d75f1caf3a32d5930a91f4fb1de598aa914) - **exasol**: Add Exasol reserved keywords to exasol dialect *(PR [#7086](https://github.com/tobymao/sqlglot/pull/7086) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`b569a3d`](https://github.com/tobymao/sqlglot/commit/b569a3d7628600608144d1d93e17eab4cafe4217) - **DuckDb**: Add transpilation support for ARRAY_MAX and ARRAY_MIN functions *(PR [#7080](https://github.com/tobymao/sqlglot/pull/7080) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`1074d66`](https://github.com/tobymao/sqlglot/commit/1074d66231d1de64b6b9aa43de6afbdc6717da5f) - **snowflake**: transpilation of Snowflake REGEXP_INSTR to DuckDB *(PR [#7097](https://github.com/tobymao/sqlglot/pull/7097) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ab1c2ab`](https://github.com/tobymao/sqlglot/commit/ab1c2ab44556a8f7ffe7dad09e4b50e75b122b5d) - **optimizer**: annotate PERCENTILE/APPROX_PERCENTILE for hive, spark2, spark, dbx *(PR [#7100](https://github.com/tobymao/sqlglot/pull/7100) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#7096](https://github.com/tobymao/sqlglot/issues/7096) opened by [@sunilmishra-amp](https://github.com/sunilmishra-amp)*\n- [`0f8287d`](https://github.com/tobymao/sqlglot/commit/0f8287d8e5ff1eee2aea29001443ee00a4b2ae47) - **optimizer**: annotate BIT_OR(expr) for Spark/DBX *(PR [#7101](https://github.com/tobymao/sqlglot/pull/7101) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`e19abfd`](https://github.com/tobymao/sqlglot/commit/e19abfded7c159c30f53063d10ae57406553f75d) - **optimizer**: annotate BIT_AND(expr) for Spark/DBX *(PR [#7103](https://github.com/tobymao/sqlglot/pull/7103) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`0c4f74f`](https://github.com/tobymao/sqlglot/commit/0c4f74f6574d3acf06935626615e2e1f14ae9c04) - **optimizer**: parse and annotate ELEMENT_AT for spark2, spark, dbx *(PR [#7104](https://github.com/tobymao/sqlglot/pull/7104) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#7096](https://github.com/tobymao/sqlglot/issues/7096) opened by [@sunilmishra-amp](https://github.com/sunilmishra-amp)*\n- [`8b8aef0`](https://github.com/tobymao/sqlglot/commit/8b8aef01197b670a727a59e46727b5a57f106a5d) - **optimizer**: annotate BIT_XOR(expr) for Spark/DBX *(PR [#7106](https://github.com/tobymao/sqlglot/pull/7106) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`ce09e81`](https://github.com/tobymao/sqlglot/commit/ce09e81bcda5c439825af54ac4e6eef124694833) - **DuckDB**: Enable transpilation for ARRAY_EXCEPT function *(PR [#7094](https://github.com/tobymao/sqlglot/pull/7094) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`1b1d57a`](https://github.com/tobymao/sqlglot/commit/1b1d57a3541f48291a1534a0b1a28948f8b8207e) - **snowflake**: transpilation of Snowflake REGEXP_SUBSTR and REGEXP_SUBSTR_ALL to DuckDB *(PR [#7095](https://github.com/tobymao/sqlglot/pull/7095) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`0858599`](https://github.com/tobymao/sqlglot/commit/0858599e274567f7a8c9361afab1526cd2d58eea) - **clickhouse**: support arrayConcat to clickhouse *(PR [#7108](https://github.com/tobymao/sqlglot/pull/7108) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`9100ea7`](https://github.com/tobymao/sqlglot/commit/9100ea742cd83fd70aaf3192ceeba8abeda61b47) - **parser**: support DECLARE spark, dbx *(PR [#7113](https://github.com/tobymao/sqlglot/pull/7113) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#7112](https://github.com/tobymao/sqlglot/issues/7112) opened by [@aersam](https://github.com/aersam)*\n- [`73ab9af`](https://github.com/tobymao/sqlglot/commit/73ab9af9c95274b3feac9278145432c27695054e) - **clickhouse**: support arrayDistinct(expr) for clickhouse *(PR [#7114](https://github.com/tobymao/sqlglot/pull/7114) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`f796956`](https://github.com/tobymao/sqlglot/commit/f796956d3cc857995572d715b8db530b2d76b8d6) - **optimizer**: annotate `ArrayDistinct` for Hive/Spark/DBX *(PR [#7119](https://github.com/tobymao/sqlglot/pull/7119) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2069b06`](https://github.com/tobymao/sqlglot/commit/2069b06284d6998d94c43c16d4b46ac50ea0d84a) - **optimizer**: annotate ARRAY_EXCEPT for Hive/Spark/DBX *(PR [#7123](https://github.com/tobymao/sqlglot/pull/7123) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`5a30754`](https://github.com/tobymao/sqlglot/commit/5a30754df09ddb1260b394c812596adb03c2710d) - **duckdb**: support `current_localtimestamp()` for DuckDB *(PR [#7128](https://github.com/tobymao/sqlglot/pull/7128) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n\n### :bug: Bug Fixes\n- [`836cc60`](https://github.com/tobymao/sqlglot/commit/836cc60c8177339e15bfef3bd3b7d98569400385) - **parser**: Limit named PK parsing to MySQL only *(PR [#6991](https://github.com/tobymao/sqlglot/pull/6991) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6989](https://github.com/tobymao/sqlglot/issues/6989) opened by [@sgomezvillamor](https://github.com/sgomezvillamor)*\n- [`495920e`](https://github.com/tobymao/sqlglot/commit/495920eb3c447949e0c787adf347c8bcd7035764) - **snowflake**: Parse MODIFY as ALTER in ALTER TABLE *(PR [#7024](https://github.com/tobymao/sqlglot/pull/7024) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#7023](https://github.com/tobymao/sqlglot/issues/7023) opened by [@abhaysharma419](https://github.com/abhaysharma419)*\n- [`1e753a3`](https://github.com/tobymao/sqlglot/commit/1e753a35405edaf9b6a1b90fccd74eaef23a028b) - **parser**: parse cast argument using _parse_assignment closes [#7027](https://github.com/tobymao/sqlglot/pull/7027) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`67e47a4`](https://github.com/tobymao/sqlglot/commit/67e47a47582cf0970ee6a9e40c9014ba04a0c065) - Support missing meta when updating position metadata for an expression *(PR [#7032](https://github.com/tobymao/sqlglot/pull/7032) by [@izeigerman](https://github.com/izeigerman))*\n- [`9bffc99`](https://github.com/tobymao/sqlglot/commit/9bffc99efec77605292e3332dfd626754441f9d8) - **snowflake**: wrap jarowinkler similarity in UPPER for case-insensitivty *(PR [#7022](https://github.com/tobymao/sqlglot/pull/7022) by [@kyle-cheung](https://github.com/kyle-cheung))*\n- [`9d7db06`](https://github.com/tobymao/sqlglot/commit/9d7db06cf8ef66583f11b6d54af573bb28f4434b) - **spark**: Generator for ARRAY_INSERT(expr) *(PR [#7036](https://github.com/tobymao/sqlglot/pull/7036) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n  - :arrow_lower_right: *fixes issue [#7035](https://github.com/tobymao/sqlglot/issues/7035) opened by [@AbhishekASLK](https://github.com/AbhishekASLK)*\n- [`f476d07`](https://github.com/tobymao/sqlglot/commit/f476d071a1412fb2d9cd6f39067380252ab4c15a) - **duckdb**: update transpilation of SEQ functions and GENERATOR for DuckDB *(PR [#7029](https://github.com/tobymao/sqlglot/pull/7029) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n  - :arrow_lower_right: *fixes issue [#6998](https://github.com/tobymao/sqlglot/issues/6998) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`aa14297`](https://github.com/tobymao/sqlglot/commit/aa142974382fa4115234834e082ca594e00eefa4) - **duckdb**: transpile LATERAL VIEW INLINE from spark to DuckDB *(PR [#7033](https://github.com/tobymao/sqlglot/pull/7033) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`f9d1f73`](https://github.com/tobymao/sqlglot/commit/f9d1f73b490e6694da0e800d9a5a70e1ba7f38d5) - **snowflake**: refactor colon (extract) parsing precedence *(PR [#7046](https://github.com/tobymao/sqlglot/pull/7046) by [@georgesittas](https://github.com/georgesittas))*\n- [`5ef8bef`](https://github.com/tobymao/sqlglot/commit/5ef8befd435fa644ae7e8ca80c8564207f7ad014) - **bigquery**: Parse & generate table alias before its version *(PR [#7075](https://github.com/tobymao/sqlglot/pull/7075) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#7073](https://github.com/tobymao/sqlglot/issues/7073) opened by [@chelsea-lin](https://github.com/chelsea-lin)*\n- [`bd4db6b`](https://github.com/tobymao/sqlglot/commit/bd4db6bef103c2d810e661948d2e3ddda0bf2c67) - **parser**: INTERVAL units with DCOLON *(PR [#7076](https://github.com/tobymao/sqlglot/pull/7076) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#7072](https://github.com/tobymao/sqlglot/issues/7072) opened by [@ligfx](https://github.com/ligfx)*\n- [`7664358`](https://github.com/tobymao/sqlglot/commit/7664358b27599936dacb2b7d8e5329fe32425e62) - **exasol**: fix parsing error in json_value for exasol dialect *(PR [#7088](https://github.com/tobymao/sqlglot/pull/7088) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`af8c3e9`](https://github.com/tobymao/sqlglot/commit/af8c3e9222224ee4878bc81dac0dee032e4863a7) - missing comments in subquery *(commit by [@tobymao](https://github.com/tobymao))*\n- [`217e960`](https://github.com/tobymao/sqlglot/commit/217e960f57675cc5f5cb9ff9996c048a31d8004c) - **optimizer**: annotate ARRAY_CONTAINS *(PR [#7099](https://github.com/tobymao/sqlglot/pull/7099) by [@geooo109](https://github.com/geooo109))*\n- [`3ff4e0d`](https://github.com/tobymao/sqlglot/commit/3ff4e0d0b97041f72b759f1fd2bd0ec561c96423) - **deploy**: Fix _version not exists *(PR [#7129](https://github.com/tobymao/sqlglot/pull/7129) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`93bf337`](https://github.com/tobymao/sqlglot/commit/93bf337ca2af1b5d9b06d6bf3c50c5bcce680077) - **optimizer**: annotate date_diff(expr) for DuckDB *(PR [#7125](https://github.com/tobymao/sqlglot/pull/7125) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`4a38462`](https://github.com/tobymao/sqlglot/commit/4a3846280d00b94cc677baff63d90e3f1361cd8a) - **deploy**: Use GA ARM machine *(PR [#7131](https://github.com/tobymao/sqlglot/pull/7131) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :zap: Performance Improvements\n- [`a8aa371`](https://github.com/tobymao/sqlglot/commit/a8aa371f3f8d5964b8625a2f92f302ae1be297ea) - improve `DataType.build` perf by removing unnecessary constructor *(PR [#7092](https://github.com/tobymao/sqlglot/pull/7092) by [@georgesittas](https://github.com/georgesittas))*\n- [`f12a58f`](https://github.com/tobymao/sqlglot/commit/f12a58f86501ef726335f147b7171d01b162ea79) - extract Column instance check out of _set_type in annotate_types *(PR [#7091](https://github.com/tobymao/sqlglot/pull/7091) by [@georgesittas](https://github.com/georgesittas))*\n- [`ddbf64c`](https://github.com/tobymao/sqlglot/commit/ddbf64ceef63150ffef5e60b5601af0e84a9f54b) - optimize large query performance in optimizer pipeline *(PR [#7090](https://github.com/tobymao/sqlglot/pull/7090) by [@sabrikaragonen](https://github.com/sabrikaragonen))*\n  - :arrow_lower_right: *addresses issue [#5112](https://github.com/tobymao/sqlglot/issues/5112) opened by [@karakanb](https://github.com/karakanb)*\n\n### :recycle: Refactors\n- [`749cf18`](https://github.com/tobymao/sqlglot/commit/749cf18dbcb8d1d0c7d144e6481ecc3a443d4a0e) - require original SQL in `Parser.parse` *(PR [#7045](https://github.com/tobymao/sqlglot/pull/7045) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#7031](https://github.com/tobymao/sqlglot/issues/7031) opened by [@ultrabear](https://github.com/ultrabear)*\n- [`13ee312`](https://github.com/tobymao/sqlglot/commit/13ee31281fe21c670f8ce1656520c18762e8402f) - use `IntEnum` instead of auto/string enums *(PR [#7050](https://github.com/tobymao/sqlglot/pull/7050) by [@georgesittas](https://github.com/georgesittas))*\n- [`4f964db`](https://github.com/tobymao/sqlglot/commit/4f964db9bfbe42b31dc9f191e687cb2f5f7db0d8) - remove prefix from data type and cleanup *(commit by [@tobymao](https://github.com/tobymao))*\n- [`1150e39`](https://github.com/tobymao/sqlglot/commit/1150e3911b0b82a9683f765de11605b14fd66ddb) - expression_core which is now compilable by mypyc *(PR [#7117](https://github.com/tobymao/sqlglot/pull/7117) by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`1c5e168`](https://github.com/tobymao/sqlglot/commit/1c5e1684dbb6f508f08b30e9ea96393b25ef0185) - generate CNAME file at docs build time *(PR [#6958](https://github.com/tobymao/sqlglot/pull/6958) by [@georgesittas](https://github.com/georgesittas))*\n- [`fd80f4b`](https://github.com/tobymao/sqlglot/commit/fd80f4b9dbbb2a3709e4ea09118b826677b0d3e8) - transpilation test for LEAD from Snowflake to DuckDB *(PR [#6968](https://github.com/tobymao/sqlglot/pull/6968) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`4f82c90`](https://github.com/tobymao/sqlglot/commit/4f82c901bcc829d142248306202c89d440cbed86) - duckdb version() tests *(commit by [@geooo109](https://github.com/geooo109))*\n- [`eabd68f`](https://github.com/tobymao/sqlglot/commit/eabd68fa952b0b7ce86927a22e5fb03f94915a21) - used dictionary comprehension in annotators *(PR [#7025](https://github.com/tobymao/sqlglot/pull/7025) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`11da02f`](https://github.com/tobymao/sqlglot/commit/11da02f2bf7128c191576f68ffb0b5d881a1e8f2) - **optimizer**: move `CurrentVersion` annotator to base *(PR [#6997](https://github.com/tobymao/sqlglot/pull/6997) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b1f0542`](https://github.com/tobymao/sqlglot/commit/b1f05428d8a4d441398c0f3d4a65b49b0eda2729) - tokenizer optimizations *(PR [#7038](https://github.com/tobymao/sqlglot/pull/7038) by [@geooo109](https://github.com/geooo109))*\n- [`214043c`](https://github.com/tobymao/sqlglot/commit/214043c5a9f3215d2811603c62701b59b0e61f05) - update license version *(PR [#7047](https://github.com/tobymao/sqlglot/pull/7047) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`5172f1a`](https://github.com/tobymao/sqlglot/commit/5172f1a5ff62ebc0bcca4f27ab5b79c0aefbdf6a) - refactor _scan *(PR [#7051](https://github.com/tobymao/sqlglot/pull/7051) by [@geooo109](https://github.com/geooo109))*\n- [`6c08a77`](https://github.com/tobymao/sqlglot/commit/6c08a77598dd672ae0f05eb4b36edfba520f4002) - improve scope module perf by replacing instance checks with type checks *(PR [#7066](https://github.com/tobymao/sqlglot/pull/7066) by [@georgesittas](https://github.com/georgesittas))*\n- [`c2f7761`](https://github.com/tobymao/sqlglot/commit/c2f7761f4f75ceabf7df781e5431b19b6eade3d6) - improve schema module perf by caching normalized tables/names *(PR [#7068](https://github.com/tobymao/sqlglot/pull/7068) by [@georgesittas](https://github.com/georgesittas))*\n- [`13f1b06`](https://github.com/tobymao/sqlglot/commit/13f1b06570a20d2f8e6a9cc22b5a20e2d1af8674) - **snowflake**: add transpilation test for LAST_VALUE from snowflake to duckdb *(PR [#7079](https://github.com/tobymao/sqlglot/pull/7079) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`92c364a`](https://github.com/tobymao/sqlglot/commit/92c364ae64cf68fc60d9418cfea17a8414e37333) - refactor map_contains_key from sf to duckdb *(commit by [@geooo109](https://github.com/geooo109))*\n- [`9b4524a`](https://github.com/tobymao/sqlglot/commit/9b4524aa4c2deac4dcc46a628370101e9677a4c2) - small opts for generator sql method *(PR [#7085](https://github.com/tobymao/sqlglot/pull/7085) by [@geooo109](https://github.com/geooo109))*\n- [`aba0db1`](https://github.com/tobymao/sqlglot/commit/aba0db107a0cf02471234c54ffb982f8dca9cfcd) - remove `classproperty` as it is dead code *(PR [#7087](https://github.com/tobymao/sqlglot/pull/7087) by [@georgesittas](https://github.com/georgesittas))*\n- [`f927329`](https://github.com/tobymao/sqlglot/commit/f927329f3f47d2ed7835715103015d5316d2943a) - add some type hints *(PR [#7089](https://github.com/tobymao/sqlglot/pull/7089) by [@georgesittas](https://github.com/georgesittas))*\n- [`2935e95`](https://github.com/tobymao/sqlglot/commit/2935e957116e584287572a770a1ecd48a2407a9d) - refactor RANK/DENSE_RANK for exasol *(commit by [@geooo109](https://github.com/geooo109))*\n- [`37fa8c7`](https://github.com/tobymao/sqlglot/commit/37fa8c7219a2f17a351f948d6eb314a543f8784d) - **tokenizer**: Split core functionality to TokenizerCore *(PR [#7116](https://github.com/tobymao/sqlglot/pull/7116) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`3e8f551`](https://github.com/tobymao/sqlglot/commit/3e8f551addd2240f8c79d88692180c4ca27a4149) - update claude.md *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v28.10.0] - 2026-02-04\n### :boom: BREAKING CHANGES\n- due to [`55698db`](https://github.com/tobymao/sqlglot/commit/55698dbca84078160248e412cf595dd26aababef) - Annotate MAKE_TIME(expr) for DuckDB *(PR [#6931](https://github.com/tobymao/sqlglot/pull/6931) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate MAKE_TIME(expr) for DuckDB (#6931)\n\n- due to [`e750ce7`](https://github.com/tobymao/sqlglot/commit/e750ce7c4ac8235e395fe077c6c9b6d5572affaf) - Transpilation for SHA2 and SHA2_BINARY from Snowflake to DuckDB *(PR [#6929](https://github.com/tobymao/sqlglot/pull/6929) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation for SHA2 and SHA2_BINARY from Snowflake to DuckDB (#6929)\n\n- due to [`9b05968`](https://github.com/tobymao/sqlglot/commit/9b05968e23fe94f804d22d77bf91ab44071aea73) - Annotate BIT_LENGTH(expr) for DuckDB *(PR [#6932](https://github.com/tobymao/sqlglot/pull/6932) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate BIT_LENGTH(expr) for DuckDB (#6932)\n\n- due to [`2050362`](https://github.com/tobymao/sqlglot/commit/20503623debdc11d739746461e8bfb8c13514a58) - Annotate LENGTH(expr) for DuckDB *(PR [#6937](https://github.com/tobymao/sqlglot/pull/6937) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate LENGTH(expr) for DuckDB (#6937)\n\n- due to [`e803c7f`](https://github.com/tobymao/sqlglot/commit/e803c7f86e518dccfc19c2543394cd9758c59899) - Moved SIN, COS, TAN, COT to Base *(PR [#6936](https://github.com/tobymao/sqlglot/pull/6936) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Moved SIN, COS, TAN, COT to Base (#6936)\n\n- due to [`973d25d`](https://github.com/tobymao/sqlglot/commit/973d25dac469934394af4b1a6e0a11e04ad8524f) - support transpilation of ARRAY_REMOVE_AT *(PR [#6930](https://github.com/tobymao/sqlglot/pull/6930) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  support transpilation of ARRAY_REMOVE_AT (#6930)\n\n- due to [`2e2ff03`](https://github.com/tobymao/sqlglot/commit/2e2ff0363d72cc7cc80b308c6c62496969199b50) - Refactor RPAD/LPAD  *(PR [#6869](https://github.com/tobymao/sqlglot/pull/6869) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Refactor RPAD/LPAD  (#6869)\n\n- due to [`62aeff8`](https://github.com/tobymao/sqlglot/commit/62aeff8b978f372615a113cbd2ea86e26dd3ba55) - Annotate CURRENT_CATALOG to Base *(PR [#6940](https://github.com/tobymao/sqlglot/pull/6940) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate CURRENT_CATALOG to Base (#6940)\n\n- due to [`04002ae`](https://github.com/tobymao/sqlglot/commit/04002aedb48ee1d11f077b66a63722127f027243) - transpile NTH_VALUE from Snowflake to DuckDB *(PR [#6882](https://github.com/tobymao/sqlglot/pull/6882) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  transpile NTH_VALUE from Snowflake to DuckDB (#6882)\n\n- due to [`bdce7c7`](https://github.com/tobymao/sqlglot/commit/bdce7c722efa37d44ee1ba85aa4c77f958e0b19f) - Annotate DAYOFMONTH(expr), DAYOFYEAR(expr) for MySQL *(PR [#6941](https://github.com/tobymao/sqlglot/pull/6941) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate DAYOFMONTH(expr), DAYOFYEAR(expr) for MySQL (#6941)\n\n- due to [`edbbb59`](https://github.com/tobymao/sqlglot/commit/edbbb597998dbdb77fa89e9a98d6ae56f0915b00) - Annotate WEEK(expr) for MySQL *(PR [#6942](https://github.com/tobymao/sqlglot/pull/6942) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate WEEK(expr) for MySQL (#6942)\n\n- due to [`ccb484b`](https://github.com/tobymao/sqlglot/commit/ccb484b82bc665b39e6a0700a885567d19882623) - Annotate HOUR(expr) for MySQL, Hive, Spark, DBX *(PR [#6943](https://github.com/tobymao/sqlglot/pull/6943) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate HOUR(expr) for MySQL, Hive, Spark, DBX (#6943)\n\n- due to [`852adec`](https://github.com/tobymao/sqlglot/commit/852adeca09c7776810eb691a04a570e0cf673aa3) - Move `MD5(expr)` to Base *(PR [#6944](https://github.com/tobymao/sqlglot/pull/6944) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Move `MD5(expr)` to Base (#6944)\n\n- due to [`2326eae`](https://github.com/tobymao/sqlglot/commit/2326eae50e6ecd47d4a8b2c848a93b720538187a) - Move ASIN, ACOS, ATAN to Base *(PR [#6945](https://github.com/tobymao/sqlglot/pull/6945) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Move ASIN, ACOS, ATAN to Base (#6945)\n\n- due to [`7989906`](https://github.com/tobymao/sqlglot/commit/79899060ed3331a55ca1c935e00376a1c137840c) - Move ASINH, ACOSH, ATANH to Base *(PR [#6946](https://github.com/tobymao/sqlglot/pull/6946) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Move ASINH, ACOSH, ATANH to Base (#6946)\n\n- due to [`713d22f`](https://github.com/tobymao/sqlglot/commit/713d22f0d44790f8dc7d80ba12ae920815a28c51) - Annotate LENGTH, LEVENSHTEIN_DISTANCE for Presto/Trino *(PR [#6947](https://github.com/tobymao/sqlglot/pull/6947) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate LENGTH, LEVENSHTEIN_DISTANCE for Presto/Trino (#6947)\n\n- due to [`0e872dc`](https://github.com/tobymao/sqlglot/commit/0e872dcb5337d13befe981261466ca14a788ef1e) - Annotate POSITION and STRPOS for Trino/Presto *(PR [#6948](https://github.com/tobymao/sqlglot/pull/6948) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate POSITION and STRPOS for Trino/Presto (#6948)\n\n- due to [`d01657a`](https://github.com/tobymao/sqlglot/commit/d01657a581a42f1588436882d190b20f4ea004a0) - Annotate WIDTH_BUCKET(expr) for Presto/Trino *(PR [#6950](https://github.com/tobymao/sqlglot/pull/6950) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate WIDTH_BUCKET(expr) for Presto/Trino (#6950)\n\n- due to [`cec0f27`](https://github.com/tobymao/sqlglot/commit/cec0f27f17f01c7b1355f1c1306a0f08af639331) - Annotate BITWISE OPERATORS for Presto/Trino *(PR [#6951](https://github.com/tobymao/sqlglot/pull/6951) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate BITWISE OPERATORS for Presto/Trino (#6951)\n\n- due to [`2292d0d`](https://github.com/tobymao/sqlglot/commit/2292d0d477ea9aaa7016539deb1435164ee749da) - Move SINH, COSH, TANH to Base *(PR [#6954](https://github.com/tobymao/sqlglot/pull/6954) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Move SINH, COSH, TANH to Base (#6954)\n\n- due to [`686ab6a`](https://github.com/tobymao/sqlglot/commit/686ab6ab6da7a1bc7652846989aedd014a9a6d41) - Use replace instead of set for JSON dot access identifiers *(PR [#6953](https://github.com/tobymao/sqlglot/pull/6953) by [@georgesittas](https://github.com/georgesittas))*:\n\n  Use replace instead of set for JSON dot access identifiers (#6953)\n\n\n### :sparkles: New Features\n- [`55698db`](https://github.com/tobymao/sqlglot/commit/55698dbca84078160248e412cf595dd26aababef) - **optimizer**: Annotate MAKE_TIME(expr) for DuckDB *(PR [#6931](https://github.com/tobymao/sqlglot/pull/6931) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`e750ce7`](https://github.com/tobymao/sqlglot/commit/e750ce7c4ac8235e395fe077c6c9b6d5572affaf) - **snowflake**: Transpilation for SHA2 and SHA2_BINARY from Snowflake to DuckDB *(PR [#6929](https://github.com/tobymao/sqlglot/pull/6929) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`9b05968`](https://github.com/tobymao/sqlglot/commit/9b05968e23fe94f804d22d77bf91ab44071aea73) - **optimizer**: Annotate BIT_LENGTH(expr) for DuckDB *(PR [#6932](https://github.com/tobymao/sqlglot/pull/6932) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2050362`](https://github.com/tobymao/sqlglot/commit/20503623debdc11d739746461e8bfb8c13514a58) - **optimizer**: Annotate LENGTH(expr) for DuckDB *(PR [#6937](https://github.com/tobymao/sqlglot/pull/6937) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`e803c7f`](https://github.com/tobymao/sqlglot/commit/e803c7f86e518dccfc19c2543394cd9758c59899) - **optimizer**: Moved SIN, COS, TAN, COT to Base *(PR [#6936](https://github.com/tobymao/sqlglot/pull/6936) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`973d25d`](https://github.com/tobymao/sqlglot/commit/973d25dac469934394af4b1a6e0a11e04ad8524f) - **duckdb**: support transpilation of ARRAY_REMOVE_AT *(PR [#6930](https://github.com/tobymao/sqlglot/pull/6930) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`2e2ff03`](https://github.com/tobymao/sqlglot/commit/2e2ff0363d72cc7cc80b308c6c62496969199b50) - **snowflake**: Refactor RPAD/LPAD  *(PR [#6869](https://github.com/tobymao/sqlglot/pull/6869) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`62aeff8`](https://github.com/tobymao/sqlglot/commit/62aeff8b978f372615a113cbd2ea86e26dd3ba55) - **optimizer**: Annotate CURRENT_CATALOG to Base *(PR [#6940](https://github.com/tobymao/sqlglot/pull/6940) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`04002ae`](https://github.com/tobymao/sqlglot/commit/04002aedb48ee1d11f077b66a63722127f027243) - **snowflake**: transpile NTH_VALUE from Snowflake to DuckDB *(PR [#6882](https://github.com/tobymao/sqlglot/pull/6882) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`ccb484b`](https://github.com/tobymao/sqlglot/commit/ccb484b82bc665b39e6a0700a885567d19882623) - **optimizer**: Annotate HOUR(expr) for MySQL, Hive, Spark, DBX *(PR [#6943](https://github.com/tobymao/sqlglot/pull/6943) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`852adec`](https://github.com/tobymao/sqlglot/commit/852adeca09c7776810eb691a04a570e0cf673aa3) - **optimizer**: Move `MD5(expr)` to Base *(PR [#6944](https://github.com/tobymao/sqlglot/pull/6944) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2326eae`](https://github.com/tobymao/sqlglot/commit/2326eae50e6ecd47d4a8b2c848a93b720538187a) - **optimizer**: Move ASIN, ACOS, ATAN to Base *(PR [#6945](https://github.com/tobymao/sqlglot/pull/6945) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`7989906`](https://github.com/tobymao/sqlglot/commit/79899060ed3331a55ca1c935e00376a1c137840c) - **optimizer**: Move ASINH, ACOSH, ATANH to Base *(PR [#6946](https://github.com/tobymao/sqlglot/pull/6946) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`713d22f`](https://github.com/tobymao/sqlglot/commit/713d22f0d44790f8dc7d80ba12ae920815a28c51) - **optimizer**: Annotate LENGTH, LEVENSHTEIN_DISTANCE for Presto/Trino *(PR [#6947](https://github.com/tobymao/sqlglot/pull/6947) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`0e872dc`](https://github.com/tobymao/sqlglot/commit/0e872dcb5337d13befe981261466ca14a788ef1e) - **optimizer**: Annotate POSITION and STRPOS for Trino/Presto *(PR [#6948](https://github.com/tobymao/sqlglot/pull/6948) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`d01657a`](https://github.com/tobymao/sqlglot/commit/d01657a581a42f1588436882d190b20f4ea004a0) - **optimizer**: Annotate WIDTH_BUCKET(expr) for Presto/Trino *(PR [#6950](https://github.com/tobymao/sqlglot/pull/6950) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`cec0f27`](https://github.com/tobymao/sqlglot/commit/cec0f27f17f01c7b1355f1c1306a0f08af639331) - **optimizer**: Annotate BITWISE OPERATORS for Presto/Trino *(PR [#6951](https://github.com/tobymao/sqlglot/pull/6951) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2292d0d`](https://github.com/tobymao/sqlglot/commit/2292d0d477ea9aaa7016539deb1435164ee749da) - **optimizer**: Move SINH, COSH, TANH to Base *(PR [#6954](https://github.com/tobymao/sqlglot/pull/6954) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n\n### :bug: Bug Fixes\n- [`12c7cf4`](https://github.com/tobymao/sqlglot/commit/12c7cf46e47d7be0a54881db171fb07e6793507a) - **dremio**: Generate exp.TryCast as CAST *(PR [#6928](https://github.com/tobymao/sqlglot/pull/6928) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6927](https://github.com/tobymao/sqlglot/issues/6927) opened by [@AyushPatel101](https://github.com/AyushPatel101)*\n- [`087f70b`](https://github.com/tobymao/sqlglot/commit/087f70b8f7a0c7a7858580a20b0d7542a0b53c6b) - **tsql**: datepart when part is quoted *(PR [#6934](https://github.com/tobymao/sqlglot/pull/6934) by [@flow3d](https://github.com/flow3d))*\n- [`bdce7c7`](https://github.com/tobymao/sqlglot/commit/bdce7c722efa37d44ee1ba85aa4c77f958e0b19f) - **optimizer**: Annotate DAYOFMONTH(expr), DAYOFYEAR(expr) for MySQL *(PR [#6941](https://github.com/tobymao/sqlglot/pull/6941) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`edbbb59`](https://github.com/tobymao/sqlglot/commit/edbbb597998dbdb77fa89e9a98d6ae56f0915b00) - **optimizer**: Annotate WEEK(expr) for MySQL *(PR [#6942](https://github.com/tobymao/sqlglot/pull/6942) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`686ab6a`](https://github.com/tobymao/sqlglot/commit/686ab6ab6da7a1bc7652846989aedd014a9a6d41) - **optimizer**: Use replace instead of set for JSON dot access identifiers *(PR [#6953](https://github.com/tobymao/sqlglot/pull/6953) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v28.9.0] - 2026-02-02\n### :boom: BREAKING CHANGES\n- due to [`e9ff474`](https://github.com/tobymao/sqlglot/commit/e9ff4743e63c332ae8a4a101f976d4909918992a) - Annotate MINUTE, MONTH for DuckDB *(PR [#6919](https://github.com/tobymao/sqlglot/pull/6919) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate MINUTE, MONTH for DuckDB (#6919)\n\n- due to [`96dc339`](https://github.com/tobymao/sqlglot/commit/96dc339e0811c70dd12f92c297d2ff25456c71b7) - Annotate DAYOFWEEK, DAYOFYEAR for DuckDB *(PR [#6920](https://github.com/tobymao/sqlglot/pull/6920) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate DAYOFWEEK, DAYOFYEAR for DuckDB (#6920)\n\n- due to [`b56f685`](https://github.com/tobymao/sqlglot/commit/b56f685193982590ea03b681cf542c0157e751d4) - Annotate DAY, HOUR, SECOND and DAYOFMONTH *(PR [#6922](https://github.com/tobymao/sqlglot/pull/6922) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate DAY, HOUR, SECOND and DAYOFMONTH (#6922)\n\n- due to [`6e418ec`](https://github.com/tobymao/sqlglot/commit/6e418ecc95085e41a4fe4fed856bc3a08f4c46f8) - Annotate EPOCH(expr) for DuckDB *(PR [#6924](https://github.com/tobymao/sqlglot/pull/6924) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate EPOCH(expr) for DuckDB (#6924)\n\n- due to [`abd8d1b`](https://github.com/tobymao/sqlglot/commit/abd8d1bc5d892299dbe46a8208e23a4b2c1c833b) - Transpilation of SHA1 from Snowflake to DuckDB  *(PR [#6888](https://github.com/tobymao/sqlglot/pull/6888) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation of SHA1 from Snowflake to DuckDB  (#6888)\n\n- due to [`b2f5430`](https://github.com/tobymao/sqlglot/commit/b2f543030789ccf889fc6e065985fbeb821c26b7) - Annotate TO_DAYS(expr) for DuckDB *(PR [#6925](https://github.com/tobymao/sqlglot/pull/6925) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate TO_DAYS(expr) for DuckDB (#6925)\n\n\n### :sparkles: New Features\n- [`e9ff474`](https://github.com/tobymao/sqlglot/commit/e9ff4743e63c332ae8a4a101f976d4909918992a) - **optimizer**: Annotate MINUTE, MONTH for DuckDB *(PR [#6919](https://github.com/tobymao/sqlglot/pull/6919) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`96dc339`](https://github.com/tobymao/sqlglot/commit/96dc339e0811c70dd12f92c297d2ff25456c71b7) - **optimizer**: Annotate DAYOFWEEK, DAYOFYEAR for DuckDB *(PR [#6920](https://github.com/tobymao/sqlglot/pull/6920) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b56f685`](https://github.com/tobymao/sqlglot/commit/b56f685193982590ea03b681cf542c0157e751d4) - **optimizer**: Annotate DAY, HOUR, SECOND and DAYOFMONTH *(PR [#6922](https://github.com/tobymao/sqlglot/pull/6922) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`6e418ec`](https://github.com/tobymao/sqlglot/commit/6e418ecc95085e41a4fe4fed856bc3a08f4c46f8) - **optimizer**: Annotate EPOCH(expr) for DuckDB *(PR [#6924](https://github.com/tobymao/sqlglot/pull/6924) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`abd8d1b`](https://github.com/tobymao/sqlglot/commit/abd8d1bc5d892299dbe46a8208e23a4b2c1c833b) - **snowflake**: Transpilation of SHA1 from Snowflake to DuckDB  *(PR [#6888](https://github.com/tobymao/sqlglot/pull/6888) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`b2f5430`](https://github.com/tobymao/sqlglot/commit/b2f543030789ccf889fc6e065985fbeb821c26b7) - **optimizer**: Annotate TO_DAYS(expr) for DuckDB *(PR [#6925](https://github.com/tobymao/sqlglot/pull/6925) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n\n### :wrench: Chores\n- [`dfb2d3b`](https://github.com/tobymao/sqlglot/commit/dfb2d3b17d20f69536620976676a2b7248fdb699) - generate API docs before checking out api-docs branch *(PR [#6921](https://github.com/tobymao/sqlglot/pull/6921) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v28.8.0] - 2026-02-02\n### :boom: BREAKING CHANGES\n- due to [`9d2a12a`](https://github.com/tobymao/sqlglot/commit/9d2a12a650afcdaffe780144af26a0f21a6ec4e6) - Annotate SIN for DuckDB *(PR [#6892](https://github.com/tobymao/sqlglot/pull/6892) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SIN for DuckDB (#6892)\n\n- due to [`d8e13ae`](https://github.com/tobymao/sqlglot/commit/d8e13ae8c3f14495fd7ea356bf53e338e6a5347e) - Annotate COS for DuckDB *(PR [#6893](https://github.com/tobymao/sqlglot/pull/6893) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate COS for DuckDB (#6893)\n\n- due to [`25f718c`](https://github.com/tobymao/sqlglot/commit/25f718cea3a62034d6a5c263e80e5b0363e3f394) - Annotate STUFF for TSQL *(PR [#6890](https://github.com/tobymao/sqlglot/pull/6890) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate STUFF for TSQL (#6890)\n\n- due to [`bec45a5`](https://github.com/tobymao/sqlglot/commit/bec45a55377e9802fe5c572371834e12d760f180) - Annotate `ISINF(expr)` for DuckDB *(PR [#6894](https://github.com/tobymao/sqlglot/pull/6894) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `ISINF(expr)` for DuckDB (#6894)\n\n- due to [`aab8243`](https://github.com/tobymao/sqlglot/commit/aab8243a19d776c65473e67a2dcb1fb71af19175) - Annotate ISNAN(expr) for Base *(PR [#6895](https://github.com/tobymao/sqlglot/pull/6895) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ISNAN(expr) for Base (#6895)\n\n- due to [`482128e`](https://github.com/tobymao/sqlglot/commit/482128e30aa0d607b7e5fcd2bde142eefcf02c4a) - Annotate TAN for DuckDB *(PR [#6896](https://github.com/tobymao/sqlglot/pull/6896) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate TAN for DuckDB (#6896)\n\n- due to [`a6d7f6e`](https://github.com/tobymao/sqlglot/commit/a6d7f6e1ef9cd5d22598a3e21cc69162b07c28a1) - Annotate `COT` for DuckDB *(PR [#6897](https://github.com/tobymao/sqlglot/pull/6897) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `COT` for DuckDB (#6897)\n\n- due to [`2ec7c2b`](https://github.com/tobymao/sqlglot/commit/2ec7c2b4a58bad3e736d021e7e414d00e7c16187) - Annotate RANDOM() for DuckDB *(PR [#6898](https://github.com/tobymao/sqlglot/pull/6898) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate RANDOM() for DuckDB (#6898)\n\n- due to [`a0b053d`](https://github.com/tobymao/sqlglot/commit/a0b053d10c5d7303f0f335be8ffe235f5a8727d9) - Annotate ATAN(expr) for DuckDB *(PR [#6900](https://github.com/tobymao/sqlglot/pull/6900) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ATAN(expr) for DuckDB (#6900)\n\n- due to [`f39b514`](https://github.com/tobymao/sqlglot/commit/f39b514936e6188799bf1c392937050d8aef6ac8) - Annotate ASIN(expr) for DuckDB *(PR [#6901](https://github.com/tobymao/sqlglot/pull/6901) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ASIN(expr) for DuckDB (#6901)\n\n- due to [`5fb98a1`](https://github.com/tobymao/sqlglot/commit/5fb98a1a0106b2e4740f8ae72fabeb424dacd07e) - Annotate ACOS(expr) for DuckDB *(PR [#6902](https://github.com/tobymao/sqlglot/pull/6902) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ACOS(expr) for DuckDB (#6902)\n\n- due to [`9e95d95`](https://github.com/tobymao/sqlglot/commit/9e95d95578ac8cb07076322c9f099467f17efb3f) - Annotate ASINH(expr), ACOSH(expr), ATANH(expr) for DuckDB *(PR [#6903](https://github.com/tobymao/sqlglot/pull/6903) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ASINH(expr), ACOSH(expr), ATANH(expr) for DuckDB (#6903)\n\n- due to [`a8fef30`](https://github.com/tobymao/sqlglot/commit/a8fef30ed6760bd095bd2c6b156ea7cd80c322d0) - Annotate DEGREES(expr) for MySQL *(PR [#6913](https://github.com/tobymao/sqlglot/pull/6913) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate DEGREES(expr) for MySQL (#6913)\n\n- due to [`887d03a`](https://github.com/tobymao/sqlglot/commit/887d03af0fa10aef492cb54d8b48e5fc3a1ee6d1) - Annotate arc trignometric func for MySQL *(PR [#6912](https://github.com/tobymao/sqlglot/pull/6912) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate arc trignometric func for MySQL (#6912)\n\n- due to [`dc6bc5a`](https://github.com/tobymao/sqlglot/commit/dc6bc5af83dc6c2d9dee3ae82b7792fdc285450e) - Annotate SIN, COS, TAN, COT for MySQL *(PR [#6911](https://github.com/tobymao/sqlglot/pull/6911) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SIN, COS, TAN, COT for MySQL (#6911)\n\n- due to [`6cae76f`](https://github.com/tobymao/sqlglot/commit/6cae76fb6de11b2b49db0bb5409495ddb676da05) - Annotate `SECOND(expr)` to `INT` *(PR [#6910](https://github.com/tobymao/sqlglot/pull/6910) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `SECOND(expr)` to `INT` (#6910)\n\n- due to [`bd595a6`](https://github.com/tobymao/sqlglot/commit/bd595a6afb724ed1e5ca64122bf1dd69a3adc473) - Annotate QUARTER(expr) for DuckDB *(PR [#6905](https://github.com/tobymao/sqlglot/pull/6905) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate QUARTER(expr) for DuckDB (#6905)\n\n- due to [`8e625b5`](https://github.com/tobymao/sqlglot/commit/8e625b5fae51659c2317a9c7a732114e047da9e0) - Annotate ATAN2 for DuckDB *(PR [#6904](https://github.com/tobymao/sqlglot/pull/6904) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ATAN2 for DuckDB (#6904)\n\n- due to [`ea678d2`](https://github.com/tobymao/sqlglot/commit/ea678d26dee0bfb223660b587744c6635c036f2f) - support transpilation of CURRENT_TIME from snowflake to duckdb *(PR [#6909](https://github.com/tobymao/sqlglot/pull/6909) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of CURRENT_TIME from snowflake to duckdb (#6909)\n\n- due to [`8e4f4b3`](https://github.com/tobymao/sqlglot/commit/8e4f4b386cd5f7484bbe32c9d8921e2fef4b02c1) - Annotate QUARTER(expr) to INT instead of TINYINT *(PR [#6906](https://github.com/tobymao/sqlglot/pull/6906) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate QUARTER(expr) to INT instead of TINYINT (#6906)\n\n- due to [`a94e45a`](https://github.com/tobymao/sqlglot/commit/a94e45a3c55f744b20c35c2a5cc61bab0a3678d7) - Annotate MONTH(expr) to INT instead of TINYINT *(PR [#6907](https://github.com/tobymao/sqlglot/pull/6907) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate MONTH(expr) to INT instead of TINYINT (#6907)\n\n- due to [`db51b75`](https://github.com/tobymao/sqlglot/commit/db51b7517229df2c6cf446962a9732e548a168f5) - Moved `YEAR`, `QUARTER`, `WEEK` to snowflake *(PR [#6918](https://github.com/tobymao/sqlglot/pull/6918) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Moved `YEAR`, `QUARTER`, `WEEK` to snowflake (#6918)\n\n\n### :sparkles: New Features\n- [`9d2a12a`](https://github.com/tobymao/sqlglot/commit/9d2a12a650afcdaffe780144af26a0f21a6ec4e6) - **duckdb**: Annotate SIN for DuckDB *(PR [#6892](https://github.com/tobymao/sqlglot/pull/6892) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`d8e13ae`](https://github.com/tobymao/sqlglot/commit/d8e13ae8c3f14495fd7ea356bf53e338e6a5347e) - **optimizer**: Annotate COS for DuckDB *(PR [#6893](https://github.com/tobymao/sqlglot/pull/6893) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`25f718c`](https://github.com/tobymao/sqlglot/commit/25f718cea3a62034d6a5c263e80e5b0363e3f394) - **tsql**: Annotate STUFF for TSQL *(PR [#6890](https://github.com/tobymao/sqlglot/pull/6890) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`bec45a5`](https://github.com/tobymao/sqlglot/commit/bec45a55377e9802fe5c572371834e12d760f180) - **optimizer**: Annotate `ISINF(expr)` for DuckDB *(PR [#6894](https://github.com/tobymao/sqlglot/pull/6894) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`aab8243`](https://github.com/tobymao/sqlglot/commit/aab8243a19d776c65473e67a2dcb1fb71af19175) - **optimmizer**: Annotate ISNAN(expr) for Base *(PR [#6895](https://github.com/tobymao/sqlglot/pull/6895) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`482128e`](https://github.com/tobymao/sqlglot/commit/482128e30aa0d607b7e5fcd2bde142eefcf02c4a) - **optimizer**: Annotate TAN for DuckDB *(PR [#6896](https://github.com/tobymao/sqlglot/pull/6896) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a6d7f6e`](https://github.com/tobymao/sqlglot/commit/a6d7f6e1ef9cd5d22598a3e21cc69162b07c28a1) - **optimizer**: Annotate `COT` for DuckDB *(PR [#6897](https://github.com/tobymao/sqlglot/pull/6897) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2ec7c2b`](https://github.com/tobymao/sqlglot/commit/2ec7c2b4a58bad3e736d021e7e414d00e7c16187) - **optimizer**: Annotate RANDOM() for DuckDB *(PR [#6898](https://github.com/tobymao/sqlglot/pull/6898) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a0b053d`](https://github.com/tobymao/sqlglot/commit/a0b053d10c5d7303f0f335be8ffe235f5a8727d9) - **optimizer**: Annotate ATAN(expr) for DuckDB *(PR [#6900](https://github.com/tobymao/sqlglot/pull/6900) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`f39b514`](https://github.com/tobymao/sqlglot/commit/f39b514936e6188799bf1c392937050d8aef6ac8) - **optimmizer**: Annotate ASIN(expr) for DuckDB *(PR [#6901](https://github.com/tobymao/sqlglot/pull/6901) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`5fb98a1`](https://github.com/tobymao/sqlglot/commit/5fb98a1a0106b2e4740f8ae72fabeb424dacd07e) - **optimizer**: Annotate ACOS(expr) for DuckDB *(PR [#6902](https://github.com/tobymao/sqlglot/pull/6902) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`9e95d95`](https://github.com/tobymao/sqlglot/commit/9e95d95578ac8cb07076322c9f099467f17efb3f) - **optimizer**: Annotate ASINH(expr), ACOSH(expr), ATANH(expr) for DuckDB *(PR [#6903](https://github.com/tobymao/sqlglot/pull/6903) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a8fef30`](https://github.com/tobymao/sqlglot/commit/a8fef30ed6760bd095bd2c6b156ea7cd80c322d0) - **optimizer**: Annotate DEGREES(expr) for MySQL *(PR [#6913](https://github.com/tobymao/sqlglot/pull/6913) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`887d03a`](https://github.com/tobymao/sqlglot/commit/887d03af0fa10aef492cb54d8b48e5fc3a1ee6d1) - **optimizer**: Annotate arc trignometric func for MySQL *(PR [#6912](https://github.com/tobymao/sqlglot/pull/6912) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`dc6bc5a`](https://github.com/tobymao/sqlglot/commit/dc6bc5af83dc6c2d9dee3ae82b7792fdc285450e) - **optimizer**: Annotate SIN, COS, TAN, COT for MySQL *(PR [#6911](https://github.com/tobymao/sqlglot/pull/6911) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`6cae76f`](https://github.com/tobymao/sqlglot/commit/6cae76fb6de11b2b49db0bb5409495ddb676da05) - **mysql**: Annotate `SECOND(expr)` to `INT` *(PR [#6910](https://github.com/tobymao/sqlglot/pull/6910) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`bd595a6`](https://github.com/tobymao/sqlglot/commit/bd595a6afb724ed1e5ca64122bf1dd69a3adc473) - **optimizer**: Annotate QUARTER(expr) for DuckDB *(PR [#6905](https://github.com/tobymao/sqlglot/pull/6905) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`8e625b5`](https://github.com/tobymao/sqlglot/commit/8e625b5fae51659c2317a9c7a732114e047da9e0) - **optimizer**: Annotate ATAN2 for DuckDB *(PR [#6904](https://github.com/tobymao/sqlglot/pull/6904) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`ea678d2`](https://github.com/tobymao/sqlglot/commit/ea678d26dee0bfb223660b587744c6635c036f2f) - **snowflake**: support transpilation of CURRENT_TIME from snowflake to duckdb *(PR [#6909](https://github.com/tobymao/sqlglot/pull/6909) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`db51b75`](https://github.com/tobymao/sqlglot/commit/db51b7517229df2c6cf446962a9732e548a168f5) - **optimizer**: Moved `YEAR`, `QUARTER`, `WEEK` to snowflake *(PR [#6918](https://github.com/tobymao/sqlglot/pull/6918) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n\n### :bug: Bug Fixes\n- [`8e4f4b3`](https://github.com/tobymao/sqlglot/commit/8e4f4b386cd5f7484bbe32c9d8921e2fef4b02c1) - **optimizer**: Annotate QUARTER(expr) to INT instead of TINYINT *(PR [#6906](https://github.com/tobymao/sqlglot/pull/6906) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a94e45a`](https://github.com/tobymao/sqlglot/commit/a94e45a3c55f744b20c35c2a5cc61bab0a3678d7) - **mysql**: Annotate MONTH(expr) to INT instead of TINYINT *(PR [#6907](https://github.com/tobymao/sqlglot/pull/6907) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`1fd5914`](https://github.com/tobymao/sqlglot/commit/1fd591403ad306912ac448a761540662c7a7f487) - **parser**: Literal number strings *(PR [#6916](https://github.com/tobymao/sqlglot/pull/6916) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6908](https://github.com/tobymao/sqlglot/issues/6908) opened by [@Matt711](https://github.com/Matt711)*\n- [`0a065be`](https://github.com/tobymao/sqlglot/commit/0a065be1e00739f47f52166b7cbc890f1a4aea41) - **postgres**: Allow reserved tokens too in EXCLUDE WITH constraint *(PR [#6917](https://github.com/tobymao/sqlglot/pull/6917) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6914](https://github.com/tobymao/sqlglot/issues/6914) opened by [@Badg](https://github.com/Badg)*\n\n### :wrench: Chores\n- [`a65c870`](https://github.com/tobymao/sqlglot/commit/a65c8701a30652bfadd4d39cf729a9e13c1fa769) - add CLAUDE.md to document guidelines for SQLGlot coding *(PR [#6899](https://github.com/tobymao/sqlglot/pull/6899) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n\n\n## [v28.7.0] - 2026-01-30\n### :boom: BREAKING CHANGES\n- due to [`ed4ba08`](https://github.com/tobymao/sqlglot/commit/ed4ba08940212f7ed9b67ea01b51f8df38fe85d2) - add support for Bitwise NOT *(PR [#6740](https://github.com/tobymao/sqlglot/pull/6740) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  add support for Bitwise NOT (#6740)\n\n- due to [`894c581`](https://github.com/tobymao/sqlglot/commit/894c5817fea304b16589710f266b3176f768aab6) - annotate cot for spark and dbx *(PR [#6739](https://github.com/tobymao/sqlglot/pull/6739) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate cot for spark and dbx (#6739)\n\n- due to [`cc18c55`](https://github.com/tobymao/sqlglot/commit/cc18c55c0acf0546607187e8910cdd2a9559f15f) - add COSH function annotation for Hive and related dialects *(PR [#6738](https://github.com/tobymao/sqlglot/pull/6738) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  add COSH function annotation for Hive and related dialects (#6738)\n\n- due to [`03dd8bd`](https://github.com/tobymao/sqlglot/commit/03dd8bd6ec9bdf1a8dfe77130bb1eb968d3cf3d8) - add SINH function annotation for Hive and related dialects *(PR [#6736](https://github.com/tobymao/sqlglot/pull/6736) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  add SINH function annotation for Hive and related dialects (#6736)\n\n- due to [`1c9cf7b`](https://github.com/tobymao/sqlglot/commit/1c9cf7bfed3f819957110964f5c44794a3e9a8bb) - annotate snowflake ARRAY_COMPACT *(PR [#6735](https://github.com/tobymao/sqlglot/pull/6735) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate snowflake ARRAY_COMPACT (#6735)\n\n- due to [`9b1a634`](https://github.com/tobymao/sqlglot/commit/9b1a6343e2ed862241d5e1a7aee8e766e74c83eb) - cast APPROX_QUANTILE results to DOUBLE to respect Snowflake's typing during transpilation *(PR [#6734](https://github.com/tobymao/sqlglot/pull/6734) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  cast APPROX_QUANTILE results to DOUBLE to respect Snowflake's typing during transpilation (#6734)\n\n- due to [`f644541`](https://github.com/tobymao/sqlglot/commit/f644541b2b27896f370e253ac4b5751ac5892f28) - add TO_BINARY function annotation for Spark and DBX dialect *(PR [#6743](https://github.com/tobymao/sqlglot/pull/6743) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  add TO_BINARY function annotation for Spark and DBX dialect (#6743)\n\n- due to [`aeaa43d`](https://github.com/tobymao/sqlglot/commit/aeaa43d16fb3fc01f3d4297badf3953c6d18ae9c) - Preserve key name in STRUCT for all identifiers *(PR [#6744](https://github.com/tobymao/sqlglot/pull/6744) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Preserve key name in STRUCT for all identifiers (#6744)\n\n- due to [`27a7b68`](https://github.com/tobymao/sqlglot/commit/27a7b6838d7a06d3ba335a937f7b158415c28b40) - add annotation for ACOS function *(PR [#6747](https://github.com/tobymao/sqlglot/pull/6747) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  add annotation for ACOS function (#6747)\n\n- due to [`7b5279c`](https://github.com/tobymao/sqlglot/commit/7b5279ccda0bd8947b9b244c221f03883e8865cf) - Fix optimizer for generate series *(PR [#6679](https://github.com/tobymao/sqlglot/pull/6679) by [@chrisqu777](https://github.com/chrisqu777))*:\n\n  Fix optimizer for generate series (#6679)\n\n- due to [`a5ccfbb`](https://github.com/tobymao/sqlglot/commit/a5ccfbb1dd2fe7a1738e36fbc15dafdd00d25036) - add SHA function annotations for Hive *(PR [#6750](https://github.com/tobymao/sqlglot/pull/6750) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  add SHA function annotations for Hive (#6750)\n\n- due to [`2191273`](https://github.com/tobymao/sqlglot/commit/219127309652ecd5a32940b09a29e10a00171866) - Transpilation support for Snowflake's BITMAP_CONSTRUCT_AGG function to DuckDB *(PR [#6745](https://github.com/tobymao/sqlglot/pull/6745) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation support for Snowflake's BITMAP_CONSTRUCT_AGG function to DuckDB (#6745)\n\n- due to [`ee0b213`](https://github.com/tobymao/sqlglot/commit/ee0b21355106861c74c3f67de5c1e6b0bb2a7f15) - Annotate RANDN function for Spark and DBX *(PR [#6751](https://github.com/tobymao/sqlglot/pull/6751) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate RANDN function for Spark and DBX (#6751)\n\n- due to [`e643817`](https://github.com/tobymao/sqlglot/commit/e6438170298e8dd90ccc3debe5065af7e0bcaa5e) - Annotate `SPACE` function to Hive *(PR [#6752](https://github.com/tobymao/sqlglot/pull/6752) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `SPACE` function to Hive (#6752)\n\n- due to [`48336d0`](https://github.com/tobymao/sqlglot/commit/48336d00d2ad15ba1056868aab99d7e8f9ddb496) - Exclude table-valued functions from unnest_subqueries *(PR [#6755](https://github.com/tobymao/sqlglot/pull/6755) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Exclude table-valued functions from unnest_subqueries (#6755)\n\n- due to [`6befad0`](https://github.com/tobymao/sqlglot/commit/6befad02d724a46feda8145d4ce092a534c18d99) - Annotate `BIT_LENGTH` for Spark and DBX *(PR [#6754](https://github.com/tobymao/sqlglot/pull/6754) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `BIT_LENGTH` for Spark and DBX (#6754)\n\n- due to [`076058d`](https://github.com/tobymao/sqlglot/commit/076058d9d808cda6b6ca08138afa7f26ee9f6a7c) - Annotate `SHA1` and `SHA256` function for DuckDB *(PR [#6753](https://github.com/tobymao/sqlglot/pull/6753) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `SHA1` and `SHA256` function for DuckDB (#6753)\n\n- due to [`ff1b5da`](https://github.com/tobymao/sqlglot/commit/ff1b5da9a0f69b664064155bc51ff41d1c928204) - Annotate KURTOSIS function *(PR [#6757](https://github.com/tobymao/sqlglot/pull/6757) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate KURTOSIS function (#6757)\n\n- due to [`af008bd`](https://github.com/tobymao/sqlglot/commit/af008bd51482c69b2c0c9ef01008ec0e657d6c9b) - Annotate SIN, COS, TAN for Hive and inherited dialects *(PR [#6759](https://github.com/tobymao/sqlglot/pull/6759) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SIN, COS, TAN for Hive and inherited dialects (#6759)\n\n- due to [`700fbe9`](https://github.com/tobymao/sqlglot/commit/700fbe9b648339342ef60e1ed2ec729de24b6229) - Annotate CORR for Hive and inherited dialects *(PR [#6769](https://github.com/tobymao/sqlglot/pull/6769) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate CORR for Hive and inherited dialects (#6769)\n\n- due to [`b87be58`](https://github.com/tobymao/sqlglot/commit/b87be5878524bf82df804926a59c2ffd94fa5adc) - Annotate `SEC` for Spark and DBX *(PR [#6768](https://github.com/tobymao/sqlglot/pull/6768) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `SEC` for Spark and DBX (#6768)\n\n- due to [`5a594ed`](https://github.com/tobymao/sqlglot/commit/5a594edd0bc079ef8e2e27ee07033b1bb5bcbd3e) - Annotate ATANH for Spark and DBX *(PR [#6767](https://github.com/tobymao/sqlglot/pull/6767) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ATANH for Spark and DBX (#6767)\n\n- due to [`1f9672f`](https://github.com/tobymao/sqlglot/commit/1f9672f390d05754260797beed0a5b1e0ea76358) - Annotate `ATAN` for Hive and inherited dialects *(PR [#6766](https://github.com/tobymao/sqlglot/pull/6766) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `ATAN` for Hive and inherited dialects (#6766)\n\n- due to [`38d6816`](https://github.com/tobymao/sqlglot/commit/38d6816e39c07c50f6e5c0b9f4763091b54f9e19) - Support type inference for BQ SAFE functions *(PR [#6765](https://github.com/tobymao/sqlglot/pull/6765) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  Support type inference for BQ SAFE functions (#6765)\n\n- due to [`c89a127`](https://github.com/tobymao/sqlglot/commit/c89a127008f581a2ca49132a171e2b51d6e1e7b2) - Implements transpilation for IS_NULL_VALUE *(PR [#6756](https://github.com/tobymao/sqlglot/pull/6756) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Implements transpilation for IS_NULL_VALUE (#6756)\n\n- due to [`428d676`](https://github.com/tobymao/sqlglot/commit/428d6766bc752ed2beb363936584766964da6bcc) - avoid redundant cast when transpiling trunc from snowflake *(PR [#6771](https://github.com/tobymao/sqlglot/pull/6771) by [@georgesittas](https://github.com/georgesittas))*:\n\n  avoid redundant cast when transpiling trunc from snowflake (#6771)\n\n- due to [`8163ffa`](https://github.com/tobymao/sqlglot/commit/8163ffa2438e98567be67610ea33918489d36d18) - Implements transpilation for Snowflake's EQUAL_NULL *(PR [#6763](https://github.com/tobymao/sqlglot/pull/6763) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Implements transpilation for Snowflake's EQUAL_NULL (#6763)\n\n- due to [`4c01cbe`](https://github.com/tobymao/sqlglot/commit/4c01cbe8ff020d7d52e399de56874a99797e2484) - Annotate CBRT for Hive and inherited dialects *(PR [#6772](https://github.com/tobymao/sqlglot/pull/6772) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate CBRT for Hive and inherited dialects (#6772)\n\n- due to [`237aec0`](https://github.com/tobymao/sqlglot/commit/237aec0c53c8417e628d4b4ecd7ad4436843b55d) - Annotate CURRENT_CATALOG() for Hive, Spark, and DBX *(PR [#6773](https://github.com/tobymao/sqlglot/pull/6773) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate CURRENT_CATALOG() for Hive, Spark, and DBX (#6773)\n\n- due to [`82d533e`](https://github.com/tobymao/sqlglot/commit/82d533ea5bde8637c71520334174a6fa04ad021d) - Annotate CURRENT_DATABASE() for Hive, Spark and DBX *(PR [#6774](https://github.com/tobymao/sqlglot/pull/6774) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate CURRENT_DATABASE() for Hive, Spark and DBX (#6774)\n\n- due to [`69c874f`](https://github.com/tobymao/sqlglot/commit/69c874f317621c572c9b5f91e563f50afaa38bba) - properly support safe functions *(PR [#6775](https://github.com/tobymao/sqlglot/pull/6775) by [@georgesittas](https://github.com/georgesittas))*:\n\n  properly support safe functions (#6775)\n\n- due to [`b5674f6`](https://github.com/tobymao/sqlglot/commit/b5674f6371aeac02716085095d9edb22e559aaa8) - robust correlated subqueries annotation *(PR [#6764](https://github.com/tobymao/sqlglot/pull/6764) by [@geooo109](https://github.com/geooo109))*:\n\n  robust correlated subqueries annotation (#6764)\n\n- due to [`8634a8a`](https://github.com/tobymao/sqlglot/commit/8634a8a737d5ee6c40b4d33545e9f928e1e07df4) - Added Snowflake to DuckDB transpilation for EXTRACT *(PR [#6706](https://github.com/tobymao/sqlglot/pull/6706) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Added Snowflake to DuckDB transpilation for EXTRACT (#6706)\n\n- due to [`8cda928`](https://github.com/tobymao/sqlglot/commit/8cda928b3807685f6de5e89eac12522433cfddd6) - ARRAY_APPEND null propagation *(PR [#6762](https://github.com/tobymao/sqlglot/pull/6762) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  ARRAY_APPEND null propagation (#6762)\n\n- due to [`2ab4376`](https://github.com/tobymao/sqlglot/commit/2ab43769092840da802ece227d4c13cc95a2108a) - Annotate CURRENT_USER() for Hive, Spark and DBX *(PR [#6790](https://github.com/tobymao/sqlglot/pull/6790) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate CURRENT_USER() for Hive, Spark and DBX (#6790)\n\n- due to [`ee08d77`](https://github.com/tobymao/sqlglot/commit/ee08d777eb79e8632d3a23e40095fd1f760a77a6) - resolve parsing issue in substr with FROM/FOR syntax *(PR [#6791](https://github.com/tobymao/sqlglot/pull/6791) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  resolve parsing issue in substr with FROM/FOR syntax (#6791)\n\n- due to [`fca6a94`](https://github.com/tobymao/sqlglot/commit/fca6a947c27959d4b8f6f3453a941d31ceccf5a4) - Annotate CURRENT_SCHEMA() for Hive, Spark and DBX *(PR [#6792](https://github.com/tobymao/sqlglot/pull/6792) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate CURRENT_SCHEMA() for Hive, Spark and DBX (#6792)\n\n- due to [`d6ecc73`](https://github.com/tobymao/sqlglot/commit/d6ecc7367783d81aab7b6341cd3400ff0edf4794) - add support for grouping_id() *(PR [#6793](https://github.com/tobymao/sqlglot/pull/6793) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  add support for grouping_id() (#6793)\n\n- due to [`7f4a150`](https://github.com/tobymao/sqlglot/commit/7f4a1502dd6e039677979b67958e618a15867ed5) - parse and annotate bq NET.REG_DOMAIN *(PR [#6777](https://github.com/tobymao/sqlglot/pull/6777) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate bq NET.REG_DOMAIN (#6777)\n\n- due to [`ce0bbcf`](https://github.com/tobymao/sqlglot/commit/ce0bbcf0d6d85c59827438bd711e9aa59ac1d9ef) - Annotate MONTHNAME for Spark and DBX *(PR [#6794](https://github.com/tobymao/sqlglot/pull/6794) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate MONTHNAME for Spark and DBX (#6794)\n\n- due to [`bc0a43c`](https://github.com/tobymao/sqlglot/commit/bc0a43cc83e21763d12ca671b03392ce555ce14b) - Annotate MONTH for Hive, Spark and DBX *(PR [#6795](https://github.com/tobymao/sqlglot/pull/6795) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate MONTH for Hive, Spark and DBX (#6795)\n\n- due to [`86ca0b6`](https://github.com/tobymao/sqlglot/commit/86ca0b6bf757e77ded99ffaaed641f8a092d6354) - Annotate MONTHS_BETWEEN for Hive, Spark and DBX *(PR [#6796](https://github.com/tobymao/sqlglot/pull/6796) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate MONTHS_BETWEEN for Hive, Spark and DBX (#6796)\n\n- due to [`590bcf1`](https://github.com/tobymao/sqlglot/commit/590bcf1de5e6ad15188224f5e2c1dce0398a9ecd) - Annotate DATE_FROM_UNIX_DATE for Spark and DBX *(PR [#6797](https://github.com/tobymao/sqlglot/pull/6797) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate DATE_FROM_UNIX_DATE for Spark and DBX (#6797)\n\n- due to [`7e3df62`](https://github.com/tobymao/sqlglot/commit/7e3df62f3e480f9e42c055a00b1113f219348561) - parse DISTINCT as separate arg from quantile for PERCENTILE func *(PR [#6799](https://github.com/tobymao/sqlglot/pull/6799) by [@geooo109](https://github.com/geooo109))*:\n\n  parse DISTINCT as separate arg from quantile for PERCENTILE func (#6799)\n\n- due to [`a2c4b08`](https://github.com/tobymao/sqlglot/commit/a2c4b08468729cbb1bd39545630a8feca9643b10) - Annotate UNHEX for Hive, Spark and DBX *(PR [#6800](https://github.com/tobymao/sqlglot/pull/6800) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate UNHEX for Hive, Spark and DBX (#6800)\n\n- due to [`3ab49b6`](https://github.com/tobymao/sqlglot/commit/3ab49b64d97b4212696e207e38ecd647421ada2b) - Annotate ASIN for Hive, Spark and DBX *(PR [#6807](https://github.com/tobymao/sqlglot/pull/6807) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ASIN for Hive, Spark and DBX (#6807)\n\n- due to [`d86e28a`](https://github.com/tobymao/sqlglot/commit/d86e28a05ea068f4a254883714f352ebee89ea55) - Annotate ASINH for Spark and DBX *(PR [#6808](https://github.com/tobymao/sqlglot/pull/6808) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ASINH for Spark and DBX (#6808)\n\n- due to [`8fe8d64`](https://github.com/tobymao/sqlglot/commit/8fe8d64373ec3cad3b4e519c728e1674daa64dac) - ARRAY_PREPEND null propagation *(PR [#6809](https://github.com/tobymao/sqlglot/pull/6809) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  ARRAY_PREPEND null propagation (#6809)\n\n- due to [`99b2b6a`](https://github.com/tobymao/sqlglot/commit/99b2b6ae0c6746ae8456968c98d96a31ac51d26a) - robust parsing of ALL/DISTINCT for PERCENTILE_APPROX func *(PR [#6812](https://github.com/tobymao/sqlglot/pull/6812) by [@geooo109](https://github.com/geooo109))*:\n\n  robust parsing of ALL/DISTINCT for PERCENTILE_APPROX func (#6812)\n\n- due to [`e6eff62`](https://github.com/tobymao/sqlglot/commit/e6eff62309b51192e732e0ae18eaa5cda5a7257a) - Annotate `GET_BIT` for DuckDB *(PR [#6816](https://github.com/tobymao/sqlglot/pull/6816) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `GET_BIT` for DuckDB (#6816)\n\n- due to [`70c2097`](https://github.com/tobymao/sqlglot/commit/70c2097ba2a26e5bad04347c8cc974b5056f2c19) - Annotate DAYNAME for Base Dialect *(PR [#6817](https://github.com/tobymao/sqlglot/pull/6817) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate DAYNAME for Base Dialect (#6817)\n\n- due to [`2d5c3aa`](https://github.com/tobymao/sqlglot/commit/2d5c3aabdec756c3fe43392cb26181856acea7d6) - Annotate `CBRT` for Base Dialect *(PR [#6819](https://github.com/tobymao/sqlglot/pull/6819) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `CBRT` for Base Dialect (#6819)\n\n- due to [`0a478ad`](https://github.com/tobymao/sqlglot/commit/0a478adf096f4890f03991e2bd33257d0c2d3ad4) - introduce `BYTE_STRING_ESCAPES` concept for postgres/duckdb e-strings *(PR [#6818](https://github.com/tobymao/sqlglot/pull/6818) by [@georgesittas](https://github.com/georgesittas))*:\n\n  introduce `BYTE_STRING_ESCAPES` concept for postgres/duckdb e-strings (#6818)\n\n- due to [`5673b09`](https://github.com/tobymao/sqlglot/commit/5673b09dd899289f866df3c30bc9b435ba30d34f) - support transpilation of try_to_date from snowflake to duckdb *(PR [#6806](https://github.com/tobymao/sqlglot/pull/6806) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of try_to_date from snowflake to duckdb (#6806)\n\n- due to [`c229079`](https://github.com/tobymao/sqlglot/commit/c229079299f3d79fdc8f5bbd9506f6594bbdbe12) - support transpilation try_to_double snowflake to duck db *(PR [#6821](https://github.com/tobymao/sqlglot/pull/6821) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation try_to_double snowflake to duck db (#6821)\n\n- due to [`dcf2cb9`](https://github.com/tobymao/sqlglot/commit/dcf2cb98ecef098368d1c4aa6d12164c341ea227) - annotate fields of  UNNEST(STRUCT) with ALIAS for bq *(PR [#6830](https://github.com/tobymao/sqlglot/pull/6830) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate fields of  UNNEST(STRUCT) with ALIAS for bq (#6830)\n\n- due to [`378349d`](https://github.com/tobymao/sqlglot/commit/378349ddbe738438e5ad565f6f7d243ee779815a) - Annotate SOUNDEX for Hive, Spark and DBX *(PR [#6832](https://github.com/tobymao/sqlglot/pull/6832) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SOUNDEX for Hive, Spark and DBX (#6832)\n\n- due to [`3972a6c`](https://github.com/tobymao/sqlglot/commit/3972a6cc9c6a0b7fcb13948cbc56d5e48502552b) - Transpile BASE64_ENCODE from Snowflake to DuckDB *(PR [#6826](https://github.com/tobymao/sqlglot/pull/6826) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpile BASE64_ENCODE from Snowflake to DuckDB (#6826)\n\n- due to [`a9daff7`](https://github.com/tobymao/sqlglot/commit/a9daff7028112aba5a4023c11d9dd96a4dba3d92) - Transpilation of Snowflake SEQ1/2/4/8 and GENERATOR to DuckDB *(PR [#6810](https://github.com/tobymao/sqlglot/pull/6810) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation of Snowflake SEQ1/2/4/8 and GENERATOR to DuckDB (#6810)\n\n- due to [`317d496`](https://github.com/tobymao/sqlglot/commit/317d4968938b8df301d4e55cfdd96af1a304f88d) - Annotate SESSION_USER() for Spark and DBX *(PR [#6834](https://github.com/tobymao/sqlglot/pull/6834) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SESSION_USER() for Spark and DBX (#6834)\n\n- due to [`98cc685`](https://github.com/tobymao/sqlglot/commit/98cc685253e8011b9d4e2e78137a8b505192724f) - Annotate FACTORIAL(expr) for Hive, Spark and DBX *(PR [#6835](https://github.com/tobymao/sqlglot/pull/6835) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate FACTORIAL(expr) for Hive, Spark and DBX (#6835)\n\n- due to [`f8024e0`](https://github.com/tobymao/sqlglot/commit/f8024e0ae3fe66076e78295868934203b03a7d49) - Annotate QUARTER for Hive, Spark and DBX *(PR [#6840](https://github.com/tobymao/sqlglot/pull/6840) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate QUARTER for Hive, Spark and DBX (#6840)\n\n- due to [`b3b36ba`](https://github.com/tobymao/sqlglot/commit/b3b36baf86f705901cf1b3509203203772907879) - robust representation of negative numbers *(PR [#6833](https://github.com/tobymao/sqlglot/pull/6833) by [@geooo109](https://github.com/geooo109))*:\n\n  robust representation of negative numbers (#6833)\n\n- due to [`6ebe5cc`](https://github.com/tobymao/sqlglot/commit/6ebe5cc397c598e865c360f89097271c11173af1) - ARRAY_CAT null propagation  *(PR [#6829](https://github.com/tobymao/sqlglot/pull/6829) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  ARRAY_CAT null propagation  (#6829)\n\n- due to [`36211c2`](https://github.com/tobymao/sqlglot/commit/36211c223f062519a1b561dfb23df56fb11a39fc) - transpile BASE64_DECODE_STRING/BINARY to DuckDB *(PR [#6837](https://github.com/tobymao/sqlglot/pull/6837) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  transpile BASE64_DECODE_STRING/BINARY to DuckDB (#6837)\n\n- due to [`541abfe`](https://github.com/tobymao/sqlglot/commit/541abfe0afc8f3e080746bfa87fea99abf07fb1c) - annotate type for bq DATE_ADD *(PR [#6842](https://github.com/tobymao/sqlglot/pull/6842) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq DATE_ADD (#6842)\n\n- due to [`8797e12`](https://github.com/tobymao/sqlglot/commit/8797e124900a31a4701ba425ae56773acf503471) - add support for transpiling ARRAY_COMPACT *(PR [#6839](https://github.com/tobymao/sqlglot/pull/6839) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  add support for transpiling ARRAY_COMPACT (#6839)\n\n- due to [`89f583a`](https://github.com/tobymao/sqlglot/commit/89f583a35f36ff9a1caab760273576e05b926572) - Annotate SECOND for Hive, Spark and DBX *(PR [#6853](https://github.com/tobymao/sqlglot/pull/6853) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SECOND for Hive, Spark and DBX (#6853)\n\n- due to [`3852e1d`](https://github.com/tobymao/sqlglot/commit/3852e1d6e9bab29b4a9678d06e0583d38232165f) - Annotate ARRAY_SIZE(array) correctly for Spark and DBX *(PR [#6852](https://github.com/tobymao/sqlglot/pull/6852) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ARRAY_SIZE(array) correctly for Spark and DBX (#6852)\n\n- due to [`4a7e5a1`](https://github.com/tobymao/sqlglot/commit/4a7e5a1050b3704295dab11aaec22b238be1659d) - Transpilation for Snowflake EDITDISTANCE to Duckdb *(PR [#6846](https://github.com/tobymao/sqlglot/pull/6846) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation for Snowflake EDITDISTANCE to Duckdb (#6846)\n\n- due to [`6f3b76b`](https://github.com/tobymao/sqlglot/commit/6f3b76b94ea1fe76ef85291634cde53cc2408d02) - Annotate SIN, COS, TAN, COT for T-SQL *(PR [#6851](https://github.com/tobymao/sqlglot/pull/6851) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SIN, COS, TAN, COT for T-SQL (#6851)\n\n- due to [`f1c4fa0`](https://github.com/tobymao/sqlglot/commit/f1c4fa0c62dc33d850eac4db190320a651717f77) - input rounding issue when transpiling boolean logic functions to DuckDB *(PR [#6849](https://github.com/tobymao/sqlglot/pull/6849) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  input rounding issue when transpiling boolean logic functions to DuckDB (#6849)\n\n- due to [`eb9887f`](https://github.com/tobymao/sqlglot/commit/eb9887f4fcc7e8c9d48cee7bd78b4804fb215ae4) - support ATN2 function AST *(PR [#6862](https://github.com/tobymao/sqlglot/pull/6862) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support ATN2 function AST (#6862)\n\n- due to [`5922ba6`](https://github.com/tobymao/sqlglot/commit/5922ba69be7cc82f45439c818f2a1f2901fe6310) - Annotate inverse trigonometric functions for `TSQL` *(PR [#6865](https://github.com/tobymao/sqlglot/pull/6865) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate inverse trigonometric functions for `TSQL` (#6865)\n\n- due to [`fc55b98`](https://github.com/tobymao/sqlglot/commit/fc55b9889bcb1e0dad404dc15d357d8c755d85e6) - Transpilation of MINHASH functions from Snowflake to DuckDB *(PR [#6859](https://github.com/tobymao/sqlglot/pull/6859) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation of MINHASH functions from Snowflake to DuckDB (#6859)\n\n- due to [`3728646`](https://github.com/tobymao/sqlglot/commit/372864672b1f576d7e80d5b4df368742a79f8222) - Annotate `CURRENT_TIMEZONE()` for TSQL *(PR [#6871](https://github.com/tobymao/sqlglot/pull/6871) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate `CURRENT_TIMEZONE()` for TSQL (#6871)\n\n- due to [`c6bfe61`](https://github.com/tobymao/sqlglot/commit/c6bfe61c59f06c6ce7cdb93a65082cd0a81018ef) - improve some starrocks properties generation *(PR [#6827](https://github.com/tobymao/sqlglot/pull/6827) by [@jaogoy](https://github.com/jaogoy))*:\n\n  improve some starrocks properties generation (#6827)\n\n- due to [`2103d1c`](https://github.com/tobymao/sqlglot/commit/2103d1c08dc36a7a6eb050149d730dcf2ea77dba) - Annotate MD5 for Hive, Spark and DBX *(PR [#6878](https://github.com/tobymao/sqlglot/pull/6878) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate MD5 for Hive, Spark and DBX (#6878)\n\n- due to [`6f49775`](https://github.com/tobymao/sqlglot/commit/6f49775edbae748fc3692e20562e5dad9d77b631) - Transpilation of ARRAY_CONSTRUCT_COMPACT to duckdb *(PR [#6875](https://github.com/tobymao/sqlglot/pull/6875) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  Transpilation of ARRAY_CONSTRUCT_COMPACT to duckdb (#6875)\n\n- due to [`37dc9c7`](https://github.com/tobymao/sqlglot/commit/37dc9c7b08169149af7fa7baa5cbf567a2688008) - support transpilation of ARRAY_INSERT *(PR [#6863](https://github.com/tobymao/sqlglot/pull/6863) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  support transpilation of ARRAY_INSERT (#6863)\n\n- due to [`3a769d4`](https://github.com/tobymao/sqlglot/commit/3a769d404ba35f4a9b26766e0b614d0e24763efc) - Added transpilation of Snowflake ARRAYS_ZIP to DuckDB *(PR [#6874](https://github.com/tobymao/sqlglot/pull/6874) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Added transpilation of Snowflake ARRAYS_ZIP to DuckDB (#6874)\n\n- due to [`9c39f08`](https://github.com/tobymao/sqlglot/commit/9c39f085924a9a59cd4a32322f3e45c710467563) - Annotate DAYOFWEEK for MySQL *(PR [#6885](https://github.com/tobymao/sqlglot/pull/6885) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate DAYOFWEEK for MySQL (#6885)\n\n- due to [`9a0aaab`](https://github.com/tobymao/sqlglot/commit/9a0aaab6277590966ce4258444649f573d88bd9e) - Annotate SOUNDEX(expr) for TSQL *(PR [#6887](https://github.com/tobymao/sqlglot/pull/6887) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate SOUNDEX(expr) for TSQL (#6887)\n\n- due to [`3e94c60`](https://github.com/tobymao/sqlglot/commit/3e94c603923ce27666ce23b0f5c985c93031b13e) - support transpilation of ARRAY_REMOVE *(PR [#6886](https://github.com/tobymao/sqlglot/pull/6886) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  support transpilation of ARRAY_REMOVE (#6886)\n\n- due to [`bce1b1f`](https://github.com/tobymao/sqlglot/commit/bce1b1f9a75db83b71eebc097065e8c8d5ee6051) - Transpilation support for Snowflake MAP_CAT to DuckDB *(PR [#6881](https://github.com/tobymao/sqlglot/pull/6881) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpilation support for Snowflake MAP_CAT to DuckDB (#6881)\n\n- due to [`b49a656`](https://github.com/tobymao/sqlglot/commit/b49a65696637937530eb0efe9b0de46c41a3436f) - Annotate FACTORIAL(expr) for DuckDB *(PR [#6891](https://github.com/tobymao/sqlglot/pull/6891) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate FACTORIAL(expr) for DuckDB (#6891)\n\n- due to [`6ce073b`](https://github.com/tobymao/sqlglot/commit/6ce073bec5864c562854cd5a9848dba56c79bdcc) - transpilation support for IS_ARRAY *(PR [#6877](https://github.com/tobymao/sqlglot/pull/6877) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpilation support for IS_ARRAY (#6877)\n\n- due to [`5f67a14`](https://github.com/tobymao/sqlglot/commit/5f67a149635cd000249eb1fc26b18493f29c4974) - bump sqlglotrs to 0.12.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.12.0\n\n\n### :sparkles: New Features\n- [`ed4ba08`](https://github.com/tobymao/sqlglot/commit/ed4ba08940212f7ed9b67ea01b51f8df38fe85d2) - **postgres**: add support for Bitwise NOT *(PR [#6740](https://github.com/tobymao/sqlglot/pull/6740) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n  - :arrow_lower_right: *addresses issue [#6730](https://github.com/tobymao/sqlglot/issues/6730) opened by [@Xynonners](https://github.com/Xynonners)*\n- [`894c581`](https://github.com/tobymao/sqlglot/commit/894c5817fea304b16589710f266b3176f768aab6) - **optimizer**: annotate cot for spark and dbx *(PR [#6739](https://github.com/tobymao/sqlglot/pull/6739) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`cc18c55`](https://github.com/tobymao/sqlglot/commit/cc18c55c0acf0546607187e8910cdd2a9559f15f) - **optimizer**: add COSH function annotation for Hive and related dialects *(PR [#6738](https://github.com/tobymao/sqlglot/pull/6738) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`03dd8bd`](https://github.com/tobymao/sqlglot/commit/03dd8bd6ec9bdf1a8dfe77130bb1eb968d3cf3d8) - **optimizer**: add SINH function annotation for Hive and related dialects *(PR [#6736](https://github.com/tobymao/sqlglot/pull/6736) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`9b1a634`](https://github.com/tobymao/sqlglot/commit/9b1a6343e2ed862241d5e1a7aee8e766e74c83eb) - **duckdb**: cast APPROX_QUANTILE results to DOUBLE to respect Snowflake's typing during transpilation *(PR [#6734](https://github.com/tobymao/sqlglot/pull/6734) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f644541`](https://github.com/tobymao/sqlglot/commit/f644541b2b27896f370e253ac4b5751ac5892f28) - **optimizer**: add TO_BINARY function annotation for Spark and DBX dialect *(PR [#6743](https://github.com/tobymao/sqlglot/pull/6743) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`5a50d05`](https://github.com/tobymao/sqlglot/commit/5a50d05b363802e785c8f9d3b8d73a68c8b046b0) - **duckdb**: Add transpilation support for NEXT_DAY function *(PR [#6728](https://github.com/tobymao/sqlglot/pull/6728) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`27a7b68`](https://github.com/tobymao/sqlglot/commit/27a7b6838d7a06d3ba335a937f7b158415c28b40) - **optimizer**: add annotation for ACOS function *(PR [#6747](https://github.com/tobymao/sqlglot/pull/6747) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`c1995b2`](https://github.com/tobymao/sqlglot/commit/c1995b20b22bae4d1633050cdbc19be3f960b223) - **spark**: add ACOSH function annotation *(PR [#6748](https://github.com/tobymao/sqlglot/pull/6748) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a5ccfbb`](https://github.com/tobymao/sqlglot/commit/a5ccfbb1dd2fe7a1738e36fbc15dafdd00d25036) - **optimizer**: add SHA function annotations for Hive *(PR [#6750](https://github.com/tobymao/sqlglot/pull/6750) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2191273`](https://github.com/tobymao/sqlglot/commit/219127309652ecd5a32940b09a29e10a00171866) - **snowflake**: Transpilation support for Snowflake's BITMAP_CONSTRUCT_AGG function to DuckDB *(PR [#6745](https://github.com/tobymao/sqlglot/pull/6745) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ee0b213`](https://github.com/tobymao/sqlglot/commit/ee0b21355106861c74c3f67de5c1e6b0bb2a7f15) - **optimizer**: Annotate RANDN function for Spark and DBX *(PR [#6751](https://github.com/tobymao/sqlglot/pull/6751) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`e643817`](https://github.com/tobymao/sqlglot/commit/e6438170298e8dd90ccc3debe5065af7e0bcaa5e) - **optimizer**: Annotate `SPACE` function to Hive *(PR [#6752](https://github.com/tobymao/sqlglot/pull/6752) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`6befad0`](https://github.com/tobymao/sqlglot/commit/6befad02d724a46feda8145d4ce092a534c18d99) - **optimizer**: Annotate `BIT_LENGTH` for Spark and DBX *(PR [#6754](https://github.com/tobymao/sqlglot/pull/6754) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`076058d`](https://github.com/tobymao/sqlglot/commit/076058d9d808cda6b6ca08138afa7f26ee9f6a7c) - **optimizer**: Annotate `SHA1` and `SHA256` function for DuckDB *(PR [#6753](https://github.com/tobymao/sqlglot/pull/6753) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`ff1b5da`](https://github.com/tobymao/sqlglot/commit/ff1b5da9a0f69b664064155bc51ff41d1c928204) - **optimizer**: Annotate KURTOSIS function *(PR [#6757](https://github.com/tobymao/sqlglot/pull/6757) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`af008bd`](https://github.com/tobymao/sqlglot/commit/af008bd51482c69b2c0c9ef01008ec0e657d6c9b) - **optimizer**: Annotate SIN, COS, TAN for Hive and inherited dialects *(PR [#6759](https://github.com/tobymao/sqlglot/pull/6759) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b87be58`](https://github.com/tobymao/sqlglot/commit/b87be5878524bf82df804926a59c2ffd94fa5adc) - **optimizer**: Annotate `SEC` for Spark and DBX *(PR [#6768](https://github.com/tobymao/sqlglot/pull/6768) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`5a594ed`](https://github.com/tobymao/sqlglot/commit/5a594edd0bc079ef8e2e27ee07033b1bb5bcbd3e) - **optimizer**: Annotate ATANH for Spark and DBX *(PR [#6767](https://github.com/tobymao/sqlglot/pull/6767) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`1f9672f`](https://github.com/tobymao/sqlglot/commit/1f9672f390d05754260797beed0a5b1e0ea76358) - **optimizer**: Annotate `ATAN` for Hive and inherited dialects *(PR [#6766](https://github.com/tobymao/sqlglot/pull/6766) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`38d6816`](https://github.com/tobymao/sqlglot/commit/38d6816e39c07c50f6e5c0b9f4763091b54f9e19) - **bigquery**: Support type inference for BQ SAFE functions *(PR [#6765](https://github.com/tobymao/sqlglot/pull/6765) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`c89a127`](https://github.com/tobymao/sqlglot/commit/c89a127008f581a2ca49132a171e2b51d6e1e7b2) - **snowflake**: Implements transpilation for IS_NULL_VALUE *(PR [#6756](https://github.com/tobymao/sqlglot/pull/6756) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`428d676`](https://github.com/tobymao/sqlglot/commit/428d6766bc752ed2beb363936584766964da6bcc) - **duckdb**: avoid redundant cast when transpiling trunc from snowflake *(PR [#6771](https://github.com/tobymao/sqlglot/pull/6771) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6770](https://github.com/tobymao/sqlglot/issues/6770) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`8163ffa`](https://github.com/tobymao/sqlglot/commit/8163ffa2438e98567be67610ea33918489d36d18) - **snowflake**: Implements transpilation for Snowflake's EQUAL_NULL *(PR [#6763](https://github.com/tobymao/sqlglot/pull/6763) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4245205`](https://github.com/tobymao/sqlglot/commit/42452050bfdeb77fe197ffddbec901f9a3ea2d8a) - **duckdb**: Add transpilation support for TIME_FROM_PARTS function for overflow case *(PR [#6761](https://github.com/tobymao/sqlglot/pull/6761) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`4c01cbe`](https://github.com/tobymao/sqlglot/commit/4c01cbe8ff020d7d52e399de56874a99797e2484) - **optimizer**: Annotate CBRT for Hive and inherited dialects *(PR [#6772](https://github.com/tobymao/sqlglot/pull/6772) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`237aec0`](https://github.com/tobymao/sqlglot/commit/237aec0c53c8417e628d4b4ecd7ad4436843b55d) - **optimizer**: Annotate CURRENT_CATALOG() for Hive, Spark, and DBX *(PR [#6773](https://github.com/tobymao/sqlglot/pull/6773) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`82d533e`](https://github.com/tobymao/sqlglot/commit/82d533ea5bde8637c71520334174a6fa04ad021d) - **optimizer**: Annotate CURRENT_DATABASE() for Hive, Spark and DBX *(PR [#6774](https://github.com/tobymao/sqlglot/pull/6774) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b5674f6`](https://github.com/tobymao/sqlglot/commit/b5674f6371aeac02716085095d9edb22e559aaa8) - **optimizer**: robust correlated subqueries annotation *(PR [#6764](https://github.com/tobymao/sqlglot/pull/6764) by [@geooo109](https://github.com/geooo109))*\n- [`8634a8a`](https://github.com/tobymao/sqlglot/commit/8634a8a737d5ee6c40b4d33545e9f928e1e07df4) - **snowflake**: Added Snowflake to DuckDB transpilation for EXTRACT *(PR [#6706](https://github.com/tobymao/sqlglot/pull/6706) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`8cda928`](https://github.com/tobymao/sqlglot/commit/8cda928b3807685f6de5e89eac12522433cfddd6) - **databricks,duckdb,postgres,spark,snowflake**: ARRAY_APPEND null propagation *(PR [#6762](https://github.com/tobymao/sqlglot/pull/6762) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`2ab4376`](https://github.com/tobymao/sqlglot/commit/2ab43769092840da802ece227d4c13cc95a2108a) - **optimizer**: Annotate CURRENT_USER() for Hive, Spark and DBX *(PR [#6790](https://github.com/tobymao/sqlglot/pull/6790) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`fca6a94`](https://github.com/tobymao/sqlglot/commit/fca6a947c27959d4b8f6f3453a941d31ceccf5a4) - **optimizer**: Annotate CURRENT_SCHEMA() for Hive, Spark and DBX *(PR [#6792](https://github.com/tobymao/sqlglot/pull/6792) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`7f4a150`](https://github.com/tobymao/sqlglot/commit/7f4a1502dd6e039677979b67958e618a15867ed5) - **optimizer**: parse and annotate bq NET.REG_DOMAIN *(PR [#6777](https://github.com/tobymao/sqlglot/pull/6777) by [@geooo109](https://github.com/geooo109))*\n- [`ce0bbcf`](https://github.com/tobymao/sqlglot/commit/ce0bbcf0d6d85c59827438bd711e9aa59ac1d9ef) - **optimizer**: Annotate MONTHNAME for Spark and DBX *(PR [#6794](https://github.com/tobymao/sqlglot/pull/6794) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`bc0a43c`](https://github.com/tobymao/sqlglot/commit/bc0a43cc83e21763d12ca671b03392ce555ce14b) - **optimizer**: Annotate MONTH for Hive, Spark and DBX *(PR [#6795](https://github.com/tobymao/sqlglot/pull/6795) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`86ca0b6`](https://github.com/tobymao/sqlglot/commit/86ca0b6bf757e77ded99ffaaed641f8a092d6354) - **optimizer**: Annotate MONTHS_BETWEEN for Hive, Spark and DBX *(PR [#6796](https://github.com/tobymao/sqlglot/pull/6796) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`590bcf1`](https://github.com/tobymao/sqlglot/commit/590bcf1de5e6ad15188224f5e2c1dce0398a9ecd) - **optimizer**: Annotate DATE_FROM_UNIX_DATE for Spark and DBX *(PR [#6797](https://github.com/tobymao/sqlglot/pull/6797) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a2c4b08`](https://github.com/tobymao/sqlglot/commit/a2c4b08468729cbb1bd39545630a8feca9643b10) - **optimizer**: Annotate UNHEX for Hive, Spark and DBX *(PR [#6800](https://github.com/tobymao/sqlglot/pull/6800) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`39db28e`](https://github.com/tobymao/sqlglot/commit/39db28e28290acfd2e3f416a8e057670194e9e20) - **duckdb**: Add transpilation support for TIMESTAMP_FROM_PARTS function *(PR [#6801](https://github.com/tobymao/sqlglot/pull/6801) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`3ab49b6`](https://github.com/tobymao/sqlglot/commit/3ab49b64d97b4212696e207e38ecd647421ada2b) - **optimizer**: Annotate ASIN for Hive, Spark and DBX *(PR [#6807](https://github.com/tobymao/sqlglot/pull/6807) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`d86e28a`](https://github.com/tobymao/sqlglot/commit/d86e28a05ea068f4a254883714f352ebee89ea55) - **optimizer**: Annotate ASINH for Spark and DBX *(PR [#6808](https://github.com/tobymao/sqlglot/pull/6808) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`8fe8d64`](https://github.com/tobymao/sqlglot/commit/8fe8d64373ec3cad3b4e519c728e1674daa64dac) - **databricks,duckdb,postgres,spark,snowflake**: ARRAY_PREPEND null propagation *(PR [#6809](https://github.com/tobymao/sqlglot/pull/6809) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`1c31ae3`](https://github.com/tobymao/sqlglot/commit/1c31ae3883c8755fc6046b661bcf87c1aa76cd5c) - **duckdb**: Add transpilation support for TIME_SLICE function *(PR [#6805](https://github.com/tobymao/sqlglot/pull/6805) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`88412cb`](https://github.com/tobymao/sqlglot/commit/88412cb431c9cc64953274fd78df80c3aa70f081) - **starrocks**: add support for ROLLUP index property *(PR [#6814](https://github.com/tobymao/sqlglot/pull/6814) by [@petrikoro](https://github.com/petrikoro))*\n- [`e6eff62`](https://github.com/tobymao/sqlglot/commit/e6eff62309b51192e732e0ae18eaa5cda5a7257a) - **optimizer**: Annotate `GET_BIT` for DuckDB *(PR [#6816](https://github.com/tobymao/sqlglot/pull/6816) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`70c2097`](https://github.com/tobymao/sqlglot/commit/70c2097ba2a26e5bad04347c8cc974b5056f2c19) - **optimizer**: Annotate DAYNAME for Base Dialect *(PR [#6817](https://github.com/tobymao/sqlglot/pull/6817) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2d5c3aa`](https://github.com/tobymao/sqlglot/commit/2d5c3aabdec756c3fe43392cb26181856acea7d6) - **optimizer**: Annotate `CBRT` for Base Dialect *(PR [#6819](https://github.com/tobymao/sqlglot/pull/6819) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`5673b09`](https://github.com/tobymao/sqlglot/commit/5673b09dd899289f866df3c30bc9b435ba30d34f) - **snowflake**: support transpilation of try_to_date from snowflake to duckdb *(PR [#6806](https://github.com/tobymao/sqlglot/pull/6806) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`c229079`](https://github.com/tobymao/sqlglot/commit/c229079299f3d79fdc8f5bbd9506f6594bbdbe12) - **snowflake**: support transpilation try_to_double snowflake to duck db *(PR [#6821](https://github.com/tobymao/sqlglot/pull/6821) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`378349d`](https://github.com/tobymao/sqlglot/commit/378349ddbe738438e5ad565f6f7d243ee779815a) - **optimizer**: Annotate SOUNDEX for Hive, Spark and DBX *(PR [#6832](https://github.com/tobymao/sqlglot/pull/6832) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`3972a6c`](https://github.com/tobymao/sqlglot/commit/3972a6cc9c6a0b7fcb13948cbc56d5e48502552b) - **snowflake**: Transpile BASE64_ENCODE from Snowflake to DuckDB *(PR [#6826](https://github.com/tobymao/sqlglot/pull/6826) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`a9daff7`](https://github.com/tobymao/sqlglot/commit/a9daff7028112aba5a4023c11d9dd96a4dba3d92) - **snowflake**: Transpilation of Snowflake SEQ1/2/4/8 and GENERATOR to DuckDB *(PR [#6810](https://github.com/tobymao/sqlglot/pull/6810) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`317d496`](https://github.com/tobymao/sqlglot/commit/317d4968938b8df301d4e55cfdd96af1a304f88d) - **optimizer**: Annotate SESSION_USER() for Spark and DBX *(PR [#6834](https://github.com/tobymao/sqlglot/pull/6834) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`98cc685`](https://github.com/tobymao/sqlglot/commit/98cc685253e8011b9d4e2e78137a8b505192724f) - **optimizer**: Annotate FACTORIAL(expr) for Hive, Spark and DBX *(PR [#6835](https://github.com/tobymao/sqlglot/pull/6835) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`db057bb`](https://github.com/tobymao/sqlglot/commit/db057bb088183ff09da72f0fbe13515f057d4e3b) - **postgres**: support `VARIADIC` *(PR [#6841](https://github.com/tobymao/sqlglot/pull/6841) by [@syubogdanov](https://github.com/syubogdanov))*\n- [`7dade98`](https://github.com/tobymao/sqlglot/commit/7dade9843e9a7cc5887a83f51b4d7ff1650de6de) - **duckdb**: Add transpilation support for REVERSE function *(PR [#6838](https://github.com/tobymao/sqlglot/pull/6838) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`36211c2`](https://github.com/tobymao/sqlglot/commit/36211c223f062519a1b561dfb23df56fb11a39fc) - **snowflake**: transpile BASE64_DECODE_STRING/BINARY to DuckDB *(PR [#6837](https://github.com/tobymao/sqlglot/pull/6837) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`541abfe`](https://github.com/tobymao/sqlglot/commit/541abfe0afc8f3e080746bfa87fea99abf07fb1c) - **optimizer**: annotate type for bq DATE_ADD *(PR [#6842](https://github.com/tobymao/sqlglot/pull/6842) by [@geooo109](https://github.com/geooo109))*\n- [`8797e12`](https://github.com/tobymao/sqlglot/commit/8797e124900a31a4701ba425ae56773acf503471) - **duckdb**: add support for transpiling ARRAY_COMPACT *(PR [#6839](https://github.com/tobymao/sqlglot/pull/6839) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`89f583a`](https://github.com/tobymao/sqlglot/commit/89f583a35f36ff9a1caab760273576e05b926572) - **optimizer**: Annotate SECOND for Hive, Spark and DBX *(PR [#6853](https://github.com/tobymao/sqlglot/pull/6853) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`4a7e5a1`](https://github.com/tobymao/sqlglot/commit/4a7e5a1050b3704295dab11aaec22b238be1659d) - **snowflake**: Transpilation for Snowflake EDITDISTANCE to Duckdb *(PR [#6846](https://github.com/tobymao/sqlglot/pull/6846) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`6f3b76b`](https://github.com/tobymao/sqlglot/commit/6f3b76b94ea1fe76ef85291634cde53cc2408d02) - **optimizer**: Annotate SIN, COS, TAN, COT for T-SQL *(PR [#6851](https://github.com/tobymao/sqlglot/pull/6851) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`2a455ca`](https://github.com/tobymao/sqlglot/commit/2a455caf6426f4d4ba2e6801e131804b7eba8a01) - **duckdb**: Add transpilation support for FLATTEN (ARRAY_FLATTEN) *(PR [#6848](https://github.com/tobymao/sqlglot/pull/6848) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`e1b6529`](https://github.com/tobymao/sqlglot/commit/e1b6529c0d413f515c3a8d0902ea6a2995e7d92d) - **snowflake**: support directed joins closes [#6850](https://github.com/tobymao/sqlglot/pull/6850) *(PR [#6856](https://github.com/tobymao/sqlglot/pull/6856) by [@georgesittas](https://github.com/georgesittas))*\n- [`c94284f`](https://github.com/tobymao/sqlglot/commit/c94284f19961645212c9b38169bc2341988debf5) - **optimizer**: UDF annotation *(PR [#6843](https://github.com/tobymao/sqlglot/pull/6843) by [@georgesittas](https://github.com/georgesittas))*\n- [`394bba4`](https://github.com/tobymao/sqlglot/commit/394bba402f99ffacee999f236602b27470e8c95d) - **starrocks**: add full support for partitions *(PR [#6804](https://github.com/tobymao/sqlglot/pull/6804) by [@petrikoro](https://github.com/petrikoro))*\n  - :arrow_lower_right: *addresses issue [#6803](https://github.com/tobymao/sqlglot/issues/6803) opened by [@petrikoro](https://github.com/petrikoro)*\n- [`eb9887f`](https://github.com/tobymao/sqlglot/commit/eb9887f4fcc7e8c9d48cee7bd78b4804fb215ae4) - **tsql**: support ATN2 function AST *(PR [#6862](https://github.com/tobymao/sqlglot/pull/6862) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`5922ba6`](https://github.com/tobymao/sqlglot/commit/5922ba69be7cc82f45439c818f2a1f2901fe6310) - **tsql**: Annotate inverse trigonometric functions for `TSQL` *(PR [#6865](https://github.com/tobymao/sqlglot/pull/6865) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`f0618b9`](https://github.com/tobymao/sqlglot/commit/f0618b97ae09e51e81e1d7c23a34afdc497f5419) - **spark**: support AS JSON suffix in describe statement closes [#6866](https://github.com/tobymao/sqlglot/pull/6866) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bf90b5d`](https://github.com/tobymao/sqlglot/commit/bf90b5dd5b4b48359cfa18b992bbb7a307169cb4) - **duckdb**: Add transpilation support for SPACE function *(PR [#6867](https://github.com/tobymao/sqlglot/pull/6867) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fc55b98`](https://github.com/tobymao/sqlglot/commit/fc55b9889bcb1e0dad404dc15d357d8c755d85e6) - **snowflake**: Transpilation of MINHASH functions from Snowflake to DuckDB *(PR [#6859](https://github.com/tobymao/sqlglot/pull/6859) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`3728646`](https://github.com/tobymao/sqlglot/commit/372864672b1f576d7e80d5b4df368742a79f8222) - **tsql**: Annotate `CURRENT_TIMEZONE()` for TSQL *(PR [#6871](https://github.com/tobymao/sqlglot/pull/6871) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`c6bfe61`](https://github.com/tobymao/sqlglot/commit/c6bfe61c59f06c6ce7cdb93a65082cd0a81018ef) - **starrocks**: improve some starrocks properties generation *(PR [#6827](https://github.com/tobymao/sqlglot/pull/6827) by [@jaogoy](https://github.com/jaogoy))*\n- [`2103d1c`](https://github.com/tobymao/sqlglot/commit/2103d1c08dc36a7a6eb050149d730dcf2ea77dba) - **optimizer**: Annotate MD5 for Hive, Spark and DBX *(PR [#6878](https://github.com/tobymao/sqlglot/pull/6878) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`6f49775`](https://github.com/tobymao/sqlglot/commit/6f49775edbae748fc3692e20562e5dad9d77b631) - **duckdb**: Transpilation of ARRAY_CONSTRUCT_COMPACT to duckdb *(PR [#6875](https://github.com/tobymao/sqlglot/pull/6875) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`37dc9c7`](https://github.com/tobymao/sqlglot/commit/37dc9c7b08169149af7fa7baa5cbf567a2688008) - **duckdb**: support transpilation of ARRAY_INSERT *(PR [#6863](https://github.com/tobymao/sqlglot/pull/6863) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`3a769d4`](https://github.com/tobymao/sqlglot/commit/3a769d404ba35f4a9b26766e0b614d0e24763efc) - **snowflake**: Added transpilation of Snowflake ARRAYS_ZIP to DuckDB *(PR [#6874](https://github.com/tobymao/sqlglot/pull/6874) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`9a0aaab`](https://github.com/tobymao/sqlglot/commit/9a0aaab6277590966ce4258444649f573d88bd9e) - **optimizer**: Annotate SOUNDEX(expr) for TSQL *(PR [#6887](https://github.com/tobymao/sqlglot/pull/6887) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`3e94c60`](https://github.com/tobymao/sqlglot/commit/3e94c603923ce27666ce23b0f5c985c93031b13e) - **duckdb**: support transpilation of ARRAY_REMOVE *(PR [#6886](https://github.com/tobymao/sqlglot/pull/6886) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`bce1b1f`](https://github.com/tobymao/sqlglot/commit/bce1b1f9a75db83b71eebc097065e8c8d5ee6051) - **snowflake**: Transpilation support for Snowflake MAP_CAT to DuckDB *(PR [#6881](https://github.com/tobymao/sqlglot/pull/6881) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ad2e9d3`](https://github.com/tobymao/sqlglot/commit/ad2e9d31f00d370d93ae925134e89d15cfd701a1) - **postgres**: support function parameter mode (IN, OUT, INOUT, VARIADIC) *(PR [#6876](https://github.com/tobymao/sqlglot/pull/6876) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n  - :arrow_lower_right: *addresses issue [#6860](https://github.com/tobymao/sqlglot/issues/6860) opened by [@Badg](https://github.com/Badg)*\n- [`b49a656`](https://github.com/tobymao/sqlglot/commit/b49a65696637937530eb0efe9b0de46c41a3436f) - **optimizer**: Annotate FACTORIAL(expr) for DuckDB *(PR [#6891](https://github.com/tobymao/sqlglot/pull/6891) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`6ce073b`](https://github.com/tobymao/sqlglot/commit/6ce073bec5864c562854cd5a9848dba56c79bdcc) - **snowflake**: transpilation support for IS_ARRAY *(PR [#6877](https://github.com/tobymao/sqlglot/pull/6877) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n\n### :bug: Bug Fixes\n- [`aeaa43d`](https://github.com/tobymao/sqlglot/commit/aeaa43d16fb3fc01f3d4297badf3953c6d18ae9c) - **duckdb**: Preserve key name in STRUCT for all identifiers *(PR [#6744](https://github.com/tobymao/sqlglot/pull/6744) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6729](https://github.com/tobymao/sqlglot/issues/6729) opened by [@nikchha](https://github.com/nikchha)*\n- [`7b5279c`](https://github.com/tobymao/sqlglot/commit/7b5279ccda0bd8947b9b244c221f03883e8865cf) - **optimizer**: Fix optimizer for generate series *(PR [#6679](https://github.com/tobymao/sqlglot/pull/6679) by [@chrisqu777](https://github.com/chrisqu777))*\n  - :arrow_lower_right: *fixes issue [#6657](https://github.com/tobymao/sqlglot/issues/6657) opened by [@metahexane](https://github.com/metahexane)*\n- [`48336d0`](https://github.com/tobymao/sqlglot/commit/48336d00d2ad15ba1056868aab99d7e8f9ddb496) - **optimizer**: Exclude table-valued functions from unnest_subqueries *(PR [#6755](https://github.com/tobymao/sqlglot/pull/6755) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`700fbe9`](https://github.com/tobymao/sqlglot/commit/700fbe9b648339342ef60e1ed2ec729de24b6229) - **optimizer**: Annotate CORR for Hive and inherited dialects *(PR [#6769](https://github.com/tobymao/sqlglot/pull/6769) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`69c874f`](https://github.com/tobymao/sqlglot/commit/69c874f317621c572c9b5f91e563f50afaa38bba) - **bigquery**: properly support safe functions *(PR [#6775](https://github.com/tobymao/sqlglot/pull/6775) by [@georgesittas](https://github.com/georgesittas))*\n- [`ee08d77`](https://github.com/tobymao/sqlglot/commit/ee08d777eb79e8632d3a23e40095fd1f760a77a6) - **parser**: resolve parsing issue in substr with FROM/FOR syntax *(PR [#6791](https://github.com/tobymao/sqlglot/pull/6791) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n  - :arrow_lower_right: *fixes issue [#6787](https://github.com/tobymao/sqlglot/issues/6787) opened by [@AbhishekASLK](https://github.com/AbhishekASLK)*\n- [`d6ecc73`](https://github.com/tobymao/sqlglot/commit/d6ecc7367783d81aab7b6341cd3400ff0edf4794) - **parser**: add support for grouping_id() *(PR [#6793](https://github.com/tobymao/sqlglot/pull/6793) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n  - :arrow_lower_right: *fixes issue [#6784](https://github.com/tobymao/sqlglot/issues/6784) opened by [@AbhishekASLK](https://github.com/AbhishekASLK)*\n- [`7e3df62`](https://github.com/tobymao/sqlglot/commit/7e3df62f3e480f9e42c055a00b1113f219348561) - **hive, spark, databriicks**: parse DISTINCT as separate arg from quantile for PERCENTILE func *(PR [#6799](https://github.com/tobymao/sqlglot/pull/6799) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6786](https://github.com/tobymao/sqlglot/issues/6786) opened by [@AbhishekASLK](https://github.com/AbhishekASLK)*\n- [`c2069b7`](https://github.com/tobymao/sqlglot/commit/c2069b77d7316877e69051b62802d02d862780ba) - **starrocks**: omit TABLE keyword for INSERT OVERWRITE fixes [#6803](https://github.com/tobymao/sqlglot/pull/6803) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`99b2b6a`](https://github.com/tobymao/sqlglot/commit/99b2b6ae0c6746ae8456968c98d96a31ac51d26a) - **hive, spark2, spark, databricks**: robust parsing of ALL/DISTINCT for PERCENTILE_APPROX func *(PR [#6812](https://github.com/tobymao/sqlglot/pull/6812) by [@geooo109](https://github.com/geooo109))*\n- [`ba1d99f`](https://github.com/tobymao/sqlglot/commit/ba1d99fc5da8b075c24554d57ed8b8092d56d58a) - **starrocks**: rewrite BETWEEN as comparison operators for DELETE *(PR [#6815](https://github.com/tobymao/sqlglot/pull/6815) by [@petrikoro](https://github.com/petrikoro))*\n- [`7e46829`](https://github.com/tobymao/sqlglot/commit/7e468291eaac59cd4a150772b5e0cc56f0c0bcf8) - quote integration tests GHA heredoc delimiter *(PR [#6820](https://github.com/tobymao/sqlglot/pull/6820) by [@treysp](https://github.com/treysp))*\n- [`0a478ad`](https://github.com/tobymao/sqlglot/commit/0a478adf096f4890f03991e2bd33257d0c2d3ad4) - introduce `BYTE_STRING_ESCAPES` concept for postgres/duckdb e-strings *(PR [#6818](https://github.com/tobymao/sqlglot/pull/6818) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6813](https://github.com/tobymao/sqlglot/issues/6813) opened by [@trouver](https://github.com/trouver)*\n- [`d8f266b`](https://github.com/tobymao/sqlglot/commit/d8f266b45f98c1eafbf09f4090a13357aef3efa0) - **merge_subqueries**: Do not replace literals in GROUP BY *(PR [#6828](https://github.com/tobymao/sqlglot/pull/6828) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6823](https://github.com/tobymao/sqlglot/issues/6823) opened by [@karta0807913](https://github.com/karta0807913)*\n- [`dcf2cb9`](https://github.com/tobymao/sqlglot/commit/dcf2cb98ecef098368d1c4aa6d12164c341ea227) - **optimizer**: annotate fields of  UNNEST(STRUCT) with ALIAS for bq *(PR [#6830](https://github.com/tobymao/sqlglot/pull/6830) by [@geooo109](https://github.com/geooo109))*\n- [`f8024e0`](https://github.com/tobymao/sqlglot/commit/f8024e0ae3fe66076e78295868934203b03a7d49) - **optimizer**: Annotate QUARTER for Hive, Spark and DBX *(PR [#6840](https://github.com/tobymao/sqlglot/pull/6840) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b3b36ba`](https://github.com/tobymao/sqlglot/commit/b3b36baf86f705901cf1b3509203203772907879) - **parser**: robust representation of negative numbers *(PR [#6833](https://github.com/tobymao/sqlglot/pull/6833) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6831](https://github.com/tobymao/sqlglot/issues/6831) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`6ebe5cc`](https://github.com/tobymao/sqlglot/commit/6ebe5cc397c598e865c360f89097271c11173af1) - **duckdb,postgres,redshift,snowflake**: ARRAY_CAT null propagation  *(PR [#6829](https://github.com/tobymao/sqlglot/pull/6829) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`5a1028a`](https://github.com/tobymao/sqlglot/commit/5a1028ad937782ff54bcd6e7ee8029d24abb7eff) - **snowflake**: match_condition edge case when offset/limit are present *(PR [#6847](https://github.com/tobymao/sqlglot/pull/6847) by [@georgesittas](https://github.com/georgesittas))*\n- [`3852e1d`](https://github.com/tobymao/sqlglot/commit/3852e1d6e9bab29b4a9678d06e0583d38232165f) - **optimizer**: Annotate ARRAY_SIZE(array) correctly for Spark and DBX *(PR [#6852](https://github.com/tobymao/sqlglot/pull/6852) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`7abc6b5`](https://github.com/tobymao/sqlglot/commit/7abc6b5645ee3f0324e77f8c853d55893083ab7c) - Fix LATERAL VIEW POSEXPLODE transpilation *(PR [#6844](https://github.com/tobymao/sqlglot/pull/6844) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6836](https://github.com/tobymao/sqlglot/issues/6836) opened by [@nickhand](https://github.com/nickhand)*\n- [`f1c4fa0`](https://github.com/tobymao/sqlglot/commit/f1c4fa0c62dc33d850eac4db190320a651717f77) - **snowflake**: input rounding issue when transpiling boolean logic functions to DuckDB *(PR [#6849](https://github.com/tobymao/sqlglot/pull/6849) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`ddec250`](https://github.com/tobymao/sqlglot/commit/ddec2500e994dc4b7cc4a80b501dc9bc1f2fb4d1) - **presto**: don't overwrite_types when annotating types in struct_sql() *(PR [#6870](https://github.com/tobymao/sqlglot/pull/6870) by [@NickCrews](https://github.com/NickCrews))*\n- [`acec48e`](https://github.com/tobymao/sqlglot/commit/acec48e0e10a2298f8e4d981feed883905150b76) - allow check to appear as an identifier in a ddl column def fixes [#6872](https://github.com/tobymao/sqlglot/pull/6872) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`0e48e25`](https://github.com/tobymao/sqlglot/commit/0e48e255b28352ebed06253b25ef049215330624) - **optimizer**: qualify columns with circular dependency *(PR [#6873](https://github.com/tobymao/sqlglot/pull/6873) by [@geooo109](https://github.com/geooo109))*\n- [`9c39f08`](https://github.com/tobymao/sqlglot/commit/9c39f085924a9a59cd4a32322f3e45c710467563) - **optimizer**: Annotate DAYOFWEEK for MySQL *(PR [#6885](https://github.com/tobymao/sqlglot/pull/6885) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`8ae8ef7`](https://github.com/tobymao/sqlglot/commit/8ae8ef74f2402d0c5c6f2c3bb704f9ea2311b720) - **postgres**: missing ON TRUE required when transpiling APPLY  *(PR [#6884](https://github.com/tobymao/sqlglot/pull/6884) by [@c3us-dev](https://github.com/c3us-dev))*\n  - :arrow_lower_right: *fixes issue [#6883](https://github.com/tobymao/sqlglot/issues/6883) opened by [@c3us-dev](https://github.com/c3us-dev)*\n\n### :wrench: Chores\n- [`167b670`](https://github.com/tobymao/sqlglot/commit/167b6708ba7bb37bb826cb1a4ceec39f0719e773) - AB-sort typing dicts *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`1c9cf7b`](https://github.com/tobymao/sqlglot/commit/1c9cf7bfed3f819957110964f5c44794a3e9a8bb) - **optimizer**: annotate snowflake ARRAY_COMPACT *(PR [#6735](https://github.com/tobymao/sqlglot/pull/6735) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`aefd6c5`](https://github.com/tobymao/sqlglot/commit/aefd6c508664c39d7f8ab7e79f34151a285e1b04) - Add Spark & DBX tests for ARRAY_COMPACT for PR6735 *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`e56ff6b`](https://github.com/tobymao/sqlglot/commit/e56ff6bb52997e2c9cc12bd4985dd977d44c7507) - Rename TokenType.TILDA to TokenType.TILDE *(PR [#6742](https://github.com/tobymao/sqlglot/pull/6742) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`931fbce`](https://github.com/tobymao/sqlglot/commit/931fbce6543e84fb938cff91215cfefaab048f3d) - add YDB plugin to dialects list *(PR [#6741](https://github.com/tobymao/sqlglot/pull/6741) by [@vgvoleg](https://github.com/vgvoleg))*\n- [`3b5fb43`](https://github.com/tobymao/sqlglot/commit/3b5fb43885277d1ffda9926e5a9b58312d8840de) - Refactor PR 6679 *(PR [#6749](https://github.com/tobymao/sqlglot/pull/6749) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`8c6d01e`](https://github.com/tobymao/sqlglot/commit/8c6d01e8eb409e750070055f65ac52e0ec921103) - refactor LENGTH type annotation *(PR [#6758](https://github.com/tobymao/sqlglot/pull/6758) by [@geooo109](https://github.com/geooo109))*\n- [`33d9e63`](https://github.com/tobymao/sqlglot/commit/33d9e63af776b6b51816774f712402f6cde6498c) - clean up redundant annotators *(PR [#6776](https://github.com/tobymao/sqlglot/pull/6776) by [@georgesittas](https://github.com/georgesittas))*\n- [`8fa7198`](https://github.com/tobymao/sqlglot/commit/8fa71980c2282d346cbedef1a8c330b7e5722379) - annotation tests for HEX func *(commit by [@geooo109](https://github.com/geooo109))*\n- [`d3a0ffa`](https://github.com/tobymao/sqlglot/commit/d3a0ffa1e49cb69403cef331e1d5704cef5d63f8) - refactor boolean function generators for duckdb *(PR [#6857](https://github.com/tobymao/sqlglot/pull/6857) by [@georgesittas](https://github.com/georgesittas))*\n- [`6fc77ce`](https://github.com/tobymao/sqlglot/commit/6fc77ce335f0f2d5099285bf17ae81576aac89fb) - refactor mysql, doris, starrocks partition by syntax *(PR [#6858](https://github.com/tobymao/sqlglot/pull/6858) by [@geooo109](https://github.com/geooo109))*\n- [`16959de`](https://github.com/tobymao/sqlglot/commit/16959de3118302fef2ad228dc233da46fce73dca) - fix style for starrocks *(commit by [@geooo109](https://github.com/geooo109))*\n- [`fa3c944`](https://github.com/tobymao/sqlglot/commit/fa3c9448d2fb132dd837b0dd5d6d4eb02297ba1a) - refactor starrocks *(PR [#6880](https://github.com/tobymao/sqlglot/pull/6880) by [@geooo109](https://github.com/geooo109))*\n- [`1438115`](https://github.com/tobymao/sqlglot/commit/14381150e5b28a0134899d36935b8c99aca9058c) - clean up sf IS_ARRAY tests *(commit by [@geooo109](https://github.com/geooo109))*\n- [`5f67a14`](https://github.com/tobymao/sqlglot/commit/5f67a149635cd000249eb1fc26b18493f29c4974) - bump sqlglotrs to 0.12.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v28.6.0] - 2026-01-13\n### :boom: BREAKING CHANGES\n- due to [`06ce65a`](https://github.com/tobymao/sqlglot/commit/06ce65ab4235d180d30c59a74756bdd8026fddc7) - support transpilation of bitwise agg functions *(PR [#6580](https://github.com/tobymao/sqlglot/pull/6580) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  support transpilation of bitwise agg functions (#6580)\n\n- due to [`1497ee6`](https://github.com/tobymao/sqlglot/commit/1497ee6197dd1bd15926ab40bb09f03f72a4da34) - corrected handling of ToChar for Postgres *(commit by [@dhawkins1234](https://github.com/dhawkins1234))*:\n\n  corrected handling of ToChar for Postgres\n\n- due to [`c52705d`](https://github.com/tobymao/sqlglot/commit/c52705dec16390e0651d8a68c3500d8fa11dff12) - annotate EXISTS, ALL, ANY as BOOLEAN *(PR [#6590](https://github.com/tobymao/sqlglot/pull/6590) by [@doripo](https://github.com/doripo))*:\n\n  annotate EXISTS, ALL, ANY as BOOLEAN (#6590)\n\n- due to [`8ef9d7b`](https://github.com/tobymao/sqlglot/commit/8ef9d7b2183589217df10004798fd751d4d618d0) - support transpilation of GREATEST, GREATEST_IGNORE_NULLS, LEAST, LEAST_IGNORE_NULLS from snowflake to duckdb *(PR [#6579](https://github.com/tobymao/sqlglot/pull/6579) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of GREATEST, GREATEST_IGNORE_NULLS, LEAST, LEAST_IGNORE_NULLS from snowflake to duckdb (#6579)\n\n- due to [`b93291b`](https://github.com/tobymao/sqlglot/commit/b93291bee7ada32b4d686db919d8d3d683d95425) - support transpilation of TRY_TO_BOOLEAN from Snowflake to DuckDB *(PR [#6594](https://github.com/tobymao/sqlglot/pull/6594) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of TRY_TO_BOOLEAN from Snowflake to DuckDB (#6594)\n\n- due to [`00e596d`](https://github.com/tobymao/sqlglot/commit/00e596d1a7a6b806cd1c632afd6b09f4f5270086) - support transpilation of BOOLXOR_AGG, BOOLAND_AGG, and BOOLOR_AGG from Snowflake to DuckDB *(PR [#6592](https://github.com/tobymao/sqlglot/pull/6592) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  support transpilation of BOOLXOR_AGG, BOOLAND_AGG, and BOOLOR_AGG from Snowflake to DuckDB (#6592)\n\n- due to [`0ba33ad`](https://github.com/tobymao/sqlglot/commit/0ba33ad69abcb718f761090cdd867e45d9481b80) - Add transpilation support for DAYNAME and MONTHNAME functions. *(PR [#6603](https://github.com/tobymao/sqlglot/pull/6603) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add transpilation support for DAYNAME and MONTHNAME functions. (#6603)\n\n- due to [`62c0ef0`](https://github.com/tobymao/sqlglot/commit/62c0ef0ad4579a55d65498eddd4e649ae464b559) - Fix BQ's exp.Date transpilation *(PR [#6595](https://github.com/tobymao/sqlglot/pull/6595) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix BQ's exp.Date transpilation (#6595)\n\n- due to [`77ff9d0`](https://github.com/tobymao/sqlglot/commit/77ff9d0c5cec8fdba7e64fe02c0db0a63a64f1e0) - annotate types for MAP_* functions *(PR [#6605](https://github.com/tobymao/sqlglot/pull/6605) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  annotate types for MAP_* functions (#6605)\n\n- due to [`e7c1574`](https://github.com/tobymao/sqlglot/commit/e7c1574f89314a304933a2b3b391528d530ea596) - Add transpilation support for DATE_DIFF function *(PR [#6609](https://github.com/tobymao/sqlglot/pull/6609) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add transpilation support for DATE_DIFF function (#6609)\n\n- due to [`d8f0bbd`](https://github.com/tobymao/sqlglot/commit/d8f0bbdbd208acc0eccb7e9be331d2661169ad97) - conditionally consume +/- in scientific literal notation *(PR [#6610](https://github.com/tobymao/sqlglot/pull/6610) by [@georgesittas](https://github.com/georgesittas))*:\n\n  conditionally consume +/- in scientific literal notation (#6610)\n\n- due to [`13014e0`](https://github.com/tobymao/sqlglot/commit/13014e0f01d10f0a078ef8aed4569d3aa8bd741b) - move postgres range parsers to global level *(PR [#6591](https://github.com/tobymao/sqlglot/pull/6591) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  move postgres range parsers to global level (#6591)\n\n- due to [`d26ff44`](https://github.com/tobymao/sqlglot/commit/d26ff4444fba6697f25efa3dea5ab751fe560f6b) - support adjacent ranges operator *(PR [#6611](https://github.com/tobymao/sqlglot/pull/6611) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support adjacent ranges operator (#6611)\n\n- due to [`1f3436b`](https://github.com/tobymao/sqlglot/commit/1f3436bfe7ddbbf212d61616153f150d32824450) - bq robust literal/non-literal type annotation *(PR [#6600](https://github.com/tobymao/sqlglot/pull/6600) by [@geooo109](https://github.com/geooo109))*:\n\n  bq robust literal/non-literal type annotation (#6600)\n\n- due to [`8f38887`](https://github.com/tobymao/sqlglot/commit/8f3888746a1f36446544483e920e1287ebf76b4f) - annotate snowflake array construct *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  annotate snowflake array construct\n\n- due to [`870dba4`](https://github.com/tobymao/sqlglot/commit/870dba41cc88dc9e4802f3695f3f715e0c35e0ed) - support transpilation of LAST_DAY from Snowflake to Duckdb *(PR [#6614](https://github.com/tobymao/sqlglot/pull/6614) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of LAST_DAY from Snowflake to Duckdb (#6614)\n\n- due to [`870d600`](https://github.com/tobymao/sqlglot/commit/870d600a93108b1a1d68936244564548dec5f683) - support: postgres point *(PR [#6615](https://github.com/tobymao/sqlglot/pull/6615) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support: postgres point (#6615)\n\n- due to [`302fda0`](https://github.com/tobymao/sqlglot/commit/302fda0151094bc074b98847390cd554414258bb) - support and, or *(PR [#6625](https://github.com/tobymao/sqlglot/pull/6625) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support and, or (#6625)\n\n- due to [`fc5800d`](https://github.com/tobymao/sqlglot/commit/fc5800d1359f9b0933622cb355fdb7a34f2f486a) - support Snowflake to DuckDB transpilation of ZIPF  *(PR [#6618](https://github.com/tobymao/sqlglot/pull/6618) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  support Snowflake to DuckDB transpilation of ZIPF  (#6618)\n\n- due to [`dea22ca`](https://github.com/tobymao/sqlglot/commit/dea22ca07076c1ef6a08f06f9fdc6070ca0fecb8) - support `RECORD` type *(PR [#6635](https://github.com/tobymao/sqlglot/pull/6635) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support `RECORD` type (#6635)\n\n- due to [`77783da`](https://github.com/tobymao/sqlglot/commit/77783da1329ad5d681a7d37928e4dbedd68ab365) - support: var-args in `xor` *(PR [#6634](https://github.com/tobymao/sqlglot/pull/6634) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support: var-args in `xor` (#6634)\n\n- due to [`b75a3e3`](https://github.com/tobymao/sqlglot/commit/b75a3e3b5d948f55df5b0096d116edf3b898e5e1) - handle BINARY keyword *(PR [#6636](https://github.com/tobymao/sqlglot/pull/6636) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  handle BINARY keyword (#6636)\n\n- due to [`e7b5d6f`](https://github.com/tobymao/sqlglot/commit/e7b5d6f1b8031fbf25e1fd7c3b42309c4aae8810) - support transpilation of TRY_TO_BINARY from Snowflake to DuckDB *(PR [#6629](https://github.com/tobymao/sqlglot/pull/6629) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of TRY_TO_BINARY from Snowflake to DuckDB (#6629)\n\n- due to [`2bf9405`](https://github.com/tobymao/sqlglot/commit/2bf9405adf9c9c72c77f7aa8ab792779a3b9c5f3) - USING keyword in chr *(PR [#6637](https://github.com/tobymao/sqlglot/pull/6637) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  USING keyword in chr (#6637)\n\n- due to [`235fc14`](https://github.com/tobymao/sqlglot/commit/235fc14f40c422e6339da0a6b17252ab9eb18ec2) - support charset *(PR [#6633](https://github.com/tobymao/sqlglot/pull/6633) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support charset (#6633)\n\n- due to [`d46969d`](https://github.com/tobymao/sqlglot/commit/d46969db50ff52b8657d5b33f0a106b69dbd1e2a) - annotate snowflake ARRAY_APPEND and ARRAY_PREPEND *(PR [#6645](https://github.com/tobymao/sqlglot/pull/6645) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate snowflake ARRAY_APPEND and ARRAY_PREPEND (#6645)\n\n- due to [`9c54329`](https://github.com/tobymao/sqlglot/commit/9c543291a0e28ad044523747d19262243fed1f5d) - Type annotation for Snowflake ENCRYPT and DECRYPT functions *(PR [#6643](https://github.com/tobymao/sqlglot/pull/6643) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Type annotation for Snowflake ENCRYPT and DECRYPT functions (#6643)\n\n- due to [`7870bd0`](https://github.com/tobymao/sqlglot/commit/7870bd0dc7503d0a1863d7623be7fc12bb9412e4) - type annotation for COVAR_POP and COVAR_SAMP *(PR [#6656](https://github.com/tobymao/sqlglot/pull/6656) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  type annotation for COVAR_POP and COVAR_SAMP (#6656)\n\n- due to [`2fa29b1`](https://github.com/tobymao/sqlglot/commit/2fa29b1e0feb6d8f9290c105e5fa0f349a011e22) - Type annotation for ARRAY_REMOVE function *(PR [#6653](https://github.com/tobymao/sqlglot/pull/6653) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  Type annotation for ARRAY_REMOVE function (#6653)\n\n- due to [`3d59af5`](https://github.com/tobymao/sqlglot/commit/3d59af557caf5c0fd109ae687b8408264543f9ea) - Added UNIFORM transpilation for Snowflake to DuckDB *(PR [#6640](https://github.com/tobymao/sqlglot/pull/6640) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Added UNIFORM transpilation for Snowflake to DuckDB (#6640)\n\n- due to [`5552f12`](https://github.com/tobymao/sqlglot/commit/5552f121f9c9a8103fc3ccf58fd5eed076fe0575) - annotation support for LOCALTIME function *(PR [#6651](https://github.com/tobymao/sqlglot/pull/6651) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotation support for LOCALTIME function (#6651)\n\n- due to [`a669651`](https://github.com/tobymao/sqlglot/commit/a6696518efefabaf815d7c61f1ae923c39ce6107) - Type annotation for Snowflake LOCALTIMESTAMP *(PR [#6652](https://github.com/tobymao/sqlglot/pull/6652) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Type annotation for Snowflake LOCALTIMESTAMP (#6652)\n\n- due to [`40ccce4`](https://github.com/tobymao/sqlglot/commit/40ccce492746cc969467c234b9c7d345454da683) - Transpile Snowflake NORMAL to DuckDB using Box-Muller transform *(PR [#6654](https://github.com/tobymao/sqlglot/pull/6654) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpile Snowflake NORMAL to DuckDB using Box-Muller transform (#6654)\n\n- due to [`d6bf569`](https://github.com/tobymao/sqlglot/commit/d6bf569ef2442d5055ab788a2078adf485cc1120) - updates for Snowflake to DuckDB transpilation of TO_TIMESTAMP functions *(PR [#6622](https://github.com/tobymao/sqlglot/pull/6622) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  updates for Snowflake to DuckDB transpilation of TO_TIMESTAMP functions (#6622)\n\n- due to [`13251fd`](https://github.com/tobymao/sqlglot/commit/13251fd7f03d31af69b86a4ef5bb6e940cc9318b) - annotation support for ELT function *(PR [#6659](https://github.com/tobymao/sqlglot/pull/6659) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotation support for ELT function (#6659)\n\n- due to [`b287e4e`](https://github.com/tobymao/sqlglot/commit/b287e4ef9e05c0ba1fadc3638977e994eb911834) - Support transpilation for BITMAP_BUCKET_NUMBER *(PR [#6668](https://github.com/tobymao/sqlglot/pull/6668) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Support transpilation for BITMAP_BUCKET_NUMBER (#6668)\n\n- due to [`d5b84cb`](https://github.com/tobymao/sqlglot/commit/d5b84cbbd1406ebd9f5e2373e12b0d15dcb85d7f) - ignore comments in the semantic differ *(PR [#6675](https://github.com/tobymao/sqlglot/pull/6675) by [@georgesittas](https://github.com/georgesittas))*:\n\n  ignore comments in the semantic differ (#6675)\n\n- due to [`399c80f`](https://github.com/tobymao/sqlglot/commit/399c80f2e66a1acdcd7d567e44c297db7071dc8f) - rename args for CovarSamp/Pop, stop inheriting from Binary *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  rename args for CovarSamp/Pop, stop inheriting from Binary\n\n- due to [`c9a70c0`](https://github.com/tobymao/sqlglot/commit/c9a70c004e0ec563e8a712668a30da9856cb0516) - update transpilation of DATE_TRUNC to duckdb *(PR [#6644](https://github.com/tobymao/sqlglot/pull/6644) by [@toriwei](https://github.com/toriwei))*:\n\n  update transpilation of DATE_TRUNC to duckdb (#6644)\n\n- due to [`460b3a2`](https://github.com/tobymao/sqlglot/commit/460b3a2ae62d8294c57068453b5a11e4a7e12a91) - Allow varlen args in exp.MD5Digest *(PR [#6685](https://github.com/tobymao/sqlglot/pull/6685) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Allow varlen args in exp.MD5Digest (#6685)\n\n- due to [`dcdee68`](https://github.com/tobymao/sqlglot/commit/dcdee68cb1a77286232865de9df8d8a01898fcc7) - Allow non aggregation functions in PIVOT *(PR [#6687](https://github.com/tobymao/sqlglot/pull/6687) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Allow non aggregation functions in PIVOT (#6687)\n\n- due to [`a4be3fa`](https://github.com/tobymao/sqlglot/commit/a4be3faf63c981a2b10b1f8c709581acf59277ce) - support SYSTIMESTAMP as NO_PAREN_FUNCTION *(PR [#6677](https://github.com/tobymao/sqlglot/pull/6677) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support SYSTIMESTAMP as NO_PAREN_FUNCTION (#6677)\n\n- due to [`243448c`](https://github.com/tobymao/sqlglot/commit/243448ca5ccc6a26d680661dcb7d921a055dbfa9) - Transpile date extraction from Snowflake to DuckDB (YEAR*, WEEK*, DAY*, etc) *(PR [#6666](https://github.com/tobymao/sqlglot/pull/6666) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Transpile date extraction from Snowflake to DuckDB (YEAR*, WEEK*, DAY*, etc) (#6666)\n\n- due to [`29cff1f`](https://github.com/tobymao/sqlglot/commit/29cff1fbe0011476b22e1b1442af857499f871a6) - bq robust literal/non-literal binary annotation *(PR [#6688](https://github.com/tobymao/sqlglot/pull/6688) by [@geooo109](https://github.com/geooo109))*:\n\n  bq robust literal/non-literal binary annotation (#6688)\n\n- due to [`65acd6c`](https://github.com/tobymao/sqlglot/commit/65acd6c00f023c3279947b23e04139bc9554db85) - transpile snowflake SYSDATE *(PR [#6693](https://github.com/tobymao/sqlglot/pull/6693) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpile snowflake SYSDATE (#6693)\n\n- due to [`36ff96c`](https://github.com/tobymao/sqlglot/commit/36ff96c727e40dbe467d6338128f87a74b99a980) - support transpilation of BITSHIFTLEFT and BITSHIFTRIGHT *(PR [#6586](https://github.com/tobymao/sqlglot/pull/6586) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  support transpilation of BITSHIFTLEFT and BITSHIFTRIGHT (#6586)\n\n- due to [`fb8f57a`](https://github.com/tobymao/sqlglot/commit/fb8f57ac54a4e0213941f3bd057ccb03c4e125b7) - Fix UPDATE statement for multi tables *(PR [#6700](https://github.com/tobymao/sqlglot/pull/6700) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix UPDATE statement for multi tables (#6700)\n\n- due to [`21f9d3c`](https://github.com/tobymao/sqlglot/commit/21f9d3ca7c6fa0d38f0f63000f904589b91c7d0a) - Normalize struct field names when annotating types *(PR [#6674](https://github.com/tobymao/sqlglot/pull/6674) by [@vchan](https://github.com/vchan))*:\n\n  Normalize struct field names when annotating types (#6674)\n\n- due to [`7362c23`](https://github.com/tobymao/sqlglot/commit/7362c2357eba540a12eea079e9f4212f3545c8d1) - interval expressions *(PR [#6648](https://github.com/tobymao/sqlglot/pull/6648) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  interval expressions (#6648)\n\n- due to [`a503a7a`](https://github.com/tobymao/sqlglot/commit/a503a7adbfe3352924e94a21303a2ac23903833c) - annotate Encode for hive dialect hierarchy *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  annotate Encode for hive dialect hierarchy\n\n- due to [`c447df7`](https://github.com/tobymao/sqlglot/commit/c447df71a645f55e4798887bde4d42285f5dea4a) - annotate the LOCALTIMESTAMP *(PR [#6709](https://github.com/tobymao/sqlglot/pull/6709) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate the LOCALTIMESTAMP (#6709)\n\n- due to [`e91a14b`](https://github.com/tobymao/sqlglot/commit/e91a14b5b325d3d71fbdbe701e531588723bcd2e) - annotate the CURRENT_TIMEZONE *(PR [#6708](https://github.com/tobymao/sqlglot/pull/6708) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate the CURRENT_TIMEZONE (#6708)\n\n- due to [`b53c8d4`](https://github.com/tobymao/sqlglot/commit/b53c8d47481d735c74ae8704a90594f33263eee8) - annotate the UNIX_TIMESTAMP *(PR [#6717](https://github.com/tobymao/sqlglot/pull/6717) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  annotate the UNIX_TIMESTAMP (#6717)\n\n- due to [`cfb06ab`](https://github.com/tobymao/sqlglot/commit/cfb06ab58c606715e688d39e704b1587fcd256e8) - add support for transpiling ARRAY_AGG with ORDER BY *(PR [#6691](https://github.com/tobymao/sqlglot/pull/6691) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  add support for transpiling ARRAY_AGG with ORDER BY (#6691)\n\n- due to [`2965077`](https://github.com/tobymao/sqlglot/commit/296507773fc4476862cc1ee374f88c7d73cb147f) - add support for JSON_KEYS func *(PR [#6718](https://github.com/tobymao/sqlglot/pull/6718) by [@geooo109](https://github.com/geooo109))*:\n\n  add support for JSON_KEYS func (#6718)\n\n- due to [`bbaba5f`](https://github.com/tobymao/sqlglot/commit/bbaba5fd7d0111947035534168593a5a3ee6839a) - annotate type for snowflake ARRAY_CAT *(PR [#6721](https://github.com/tobymao/sqlglot/pull/6721) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake ARRAY_CAT (#6721)\n\n- due to [`0a1c7ab`](https://github.com/tobymao/sqlglot/commit/0a1c7abddc0cbd2f853a431848c1ae45e6876ba2) - support transpilation of GETBIT from snowflake to duckdb *(PR [#6692](https://github.com/tobymao/sqlglot/pull/6692) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of GETBIT from snowflake to duckdb (#6692)\n\n- due to [`cb320c4`](https://github.com/tobymao/sqlglot/commit/cb320c41361f0bb7ad71522366ff4cb8691607bf) - bq annotate type for raw strings *(PR [#6723](https://github.com/tobymao/sqlglot/pull/6723) by [@geooo109](https://github.com/geooo109))*:\n\n  bq annotate type for raw strings (#6723)\n\n- due to [`09fa467`](https://github.com/tobymao/sqlglot/commit/09fa467461656d5c4d4e57c7044c46ff4fcf3f7f) - Annotate ATAN2 for Spark & DBX *(PR [#6725](https://github.com/tobymao/sqlglot/pull/6725) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate ATAN2 for Spark & DBX (#6725)\n\n- due to [`b59b3bf`](https://github.com/tobymao/sqlglot/commit/b59b3bfd06c18120db2938748c561495dd885ab4) - Annotate TANH for Spark & DBX  *(PR [#6726](https://github.com/tobymao/sqlglot/pull/6726) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  Annotate TANH for Spark & DBX  (#6726)\n\n- due to [`917071b`](https://github.com/tobymao/sqlglot/commit/917071b42b91f5111d88d599a1caed83e6d7661c) - Support transpilation of TO_TIME and TRY_TO_TIME from snowflake to duckdb *(PR [#6690](https://github.com/tobymao/sqlglot/pull/6690) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Support transpilation of TO_TIME and TRY_TO_TIME from snowflake to duckdb (#6690)\n\n- due to [`e50a97e`](https://github.com/tobymao/sqlglot/commit/e50a97ed05d50be9fbea7720511a760c11f4a86e) - Type annotate for Snowflake Kurtosis *(PR [#6720](https://github.com/tobymao/sqlglot/pull/6720) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Type annotate for Snowflake Kurtosis (#6720)\n\n- due to [`33b8a5d`](https://github.com/tobymao/sqlglot/commit/33b8a5d25bf49791fb95ec20132ab6ff0bb885e0) - bump sqlglotrs to 0.11.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.11.0\n\n\n### :sparkles: New Features\n- [`06ce65a`](https://github.com/tobymao/sqlglot/commit/06ce65ab4235d180d30c59a74756bdd8026fddc7) - **snowflake**: support transpilation of bitwise agg functions *(PR [#6580](https://github.com/tobymao/sqlglot/pull/6580) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`82abf40`](https://github.com/tobymao/sqlglot/commit/82abf4085dd93d23b1612304704fcb89b2cb09e2) - **duckdb**: Add tranwspilation support for CEIL function *(PR [#6589](https://github.com/tobymao/sqlglot/pull/6589) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`9f9994d`](https://github.com/tobymao/sqlglot/commit/9f9994df1582a1b3e16dcf27c618dadfdffd8745) - **duckdb**: Add transpilation support for float/decimal numbers and preserve end-of-month logic & type *(PR [#6576](https://github.com/tobymao/sqlglot/pull/6576) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`8ef9d7b`](https://github.com/tobymao/sqlglot/commit/8ef9d7b2183589217df10004798fd751d4d618d0) - **snowflake**: support transpilation of GREATEST, GREATEST_IGNORE_NULLS, LEAST, LEAST_IGNORE_NULLS from snowflake to duckdb *(PR [#6579](https://github.com/tobymao/sqlglot/pull/6579) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`b93291b`](https://github.com/tobymao/sqlglot/commit/b93291bee7ada32b4d686db919d8d3d683d95425) - **snowflake**: support transpilation of TRY_TO_BOOLEAN from Snowflake to DuckDB *(PR [#6594](https://github.com/tobymao/sqlglot/pull/6594) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`00e596d`](https://github.com/tobymao/sqlglot/commit/00e596d1a7a6b806cd1c632afd6b09f4f5270086) - **snowflake**: support transpilation of BOOLXOR_AGG, BOOLAND_AGG, and BOOLOR_AGG from Snowflake to DuckDB *(PR [#6592](https://github.com/tobymao/sqlglot/pull/6592) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`0ba33ad`](https://github.com/tobymao/sqlglot/commit/0ba33ad69abcb718f761090cdd867e45d9481b80) - **duckdb**: Add transpilation support for DAYNAME and MONTHNAME functions. *(PR [#6603](https://github.com/tobymao/sqlglot/pull/6603) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`77ff9d0`](https://github.com/tobymao/sqlglot/commit/77ff9d0c5cec8fdba7e64fe02c0db0a63a64f1e0) - **snowflake**: annotate types for MAP_* functions *(PR [#6605](https://github.com/tobymao/sqlglot/pull/6605) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`e7c1574`](https://github.com/tobymao/sqlglot/commit/e7c1574f89314a304933a2b3b391528d530ea596) - **duckdb**: Add transpilation support for DATE_DIFF function *(PR [#6609](https://github.com/tobymao/sqlglot/pull/6609) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`d26ff44`](https://github.com/tobymao/sqlglot/commit/d26ff4444fba6697f25efa3dea5ab751fe560f6b) - **postgres**: support adjacent ranges operator *(PR [#6611](https://github.com/tobymao/sqlglot/pull/6611) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`8f38887`](https://github.com/tobymao/sqlglot/commit/8f3888746a1f36446544483e920e1287ebf76b4f) - **optimizer**: annotate snowflake array construct *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`870dba4`](https://github.com/tobymao/sqlglot/commit/870dba41cc88dc9e4802f3695f3f715e0c35e0ed) - **snowflake**: support transpilation of LAST_DAY from Snowflake to Duckdb *(PR [#6614](https://github.com/tobymao/sqlglot/pull/6614) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`fc5800d`](https://github.com/tobymao/sqlglot/commit/fc5800d1359f9b0933622cb355fdb7a34f2f486a) - **snowflake**: support Snowflake to DuckDB transpilation of ZIPF  *(PR [#6618](https://github.com/tobymao/sqlglot/pull/6618) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`4aea018`](https://github.com/tobymao/sqlglot/commit/4aea018c2d4b701ac1b895445ef390307666693f) - **duckdb**: Add transpilation support for nanoseconds used in date/time functions. *(PR [#6617](https://github.com/tobymao/sqlglot/pull/6617) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`dea22ca`](https://github.com/tobymao/sqlglot/commit/dea22ca07076c1ef6a08f06f9fdc6070ca0fecb8) - **singlestore**: support `RECORD` type *(PR [#6635](https://github.com/tobymao/sqlglot/pull/6635) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`77783da`](https://github.com/tobymao/sqlglot/commit/77783da1329ad5d681a7d37928e4dbedd68ab365) - **clickhouse**: support: var-args in `xor` *(PR [#6634](https://github.com/tobymao/sqlglot/pull/6634) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b75a3e3`](https://github.com/tobymao/sqlglot/commit/b75a3e3b5d948f55df5b0096d116edf3b898e5e1) - **mysql**: handle BINARY keyword *(PR [#6636](https://github.com/tobymao/sqlglot/pull/6636) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`c17878a`](https://github.com/tobymao/sqlglot/commit/c17878a35c761824b58b0e0743585e3a5717d1b4) - add ability to create dialect plugins *(PR [#6627](https://github.com/tobymao/sqlglot/pull/6627) by [@vgvoleg](https://github.com/vgvoleg))*\n  - :arrow_lower_right: *addresses issue [#6626](https://github.com/tobymao/sqlglot/issues/6626) opened by [@vgvoleg](https://github.com/vgvoleg)*\n- [`e7b5d6f`](https://github.com/tobymao/sqlglot/commit/e7b5d6f1b8031fbf25e1fd7c3b42309c4aae8810) - **snowflake**: support transpilation of TRY_TO_BINARY from Snowflake to DuckDB *(PR [#6629](https://github.com/tobymao/sqlglot/pull/6629) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`9c54329`](https://github.com/tobymao/sqlglot/commit/9c543291a0e28ad044523747d19262243fed1f5d) - **snowflake**: Type annotation for Snowflake ENCRYPT and DECRYPT functions *(PR [#6643](https://github.com/tobymao/sqlglot/pull/6643) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`7870bd0`](https://github.com/tobymao/sqlglot/commit/7870bd0dc7503d0a1863d7623be7fc12bb9412e4) - **snowflake**: type annotation for COVAR_POP and COVAR_SAMP *(PR [#6656](https://github.com/tobymao/sqlglot/pull/6656) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`2fa29b1`](https://github.com/tobymao/sqlglot/commit/2fa29b1e0feb6d8f9290c105e5fa0f349a011e22) - **snowflake**: Type annotation for ARRAY_REMOVE function *(PR [#6653](https://github.com/tobymao/sqlglot/pull/6653) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`3d59af5`](https://github.com/tobymao/sqlglot/commit/3d59af557caf5c0fd109ae687b8408264543f9ea) - **snowflake**: Added UNIFORM transpilation for Snowflake to DuckDB *(PR [#6640](https://github.com/tobymao/sqlglot/pull/6640) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`5552f12`](https://github.com/tobymao/sqlglot/commit/5552f121f9c9a8103fc3ccf58fd5eed076fe0575) - annotation support for LOCALTIME function *(PR [#6651](https://github.com/tobymao/sqlglot/pull/6651) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a669651`](https://github.com/tobymao/sqlglot/commit/a6696518efefabaf815d7c61f1ae923c39ce6107) - **snowflake**: Type annotation for Snowflake LOCALTIMESTAMP *(PR [#6652](https://github.com/tobymao/sqlglot/pull/6652) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`40ccce4`](https://github.com/tobymao/sqlglot/commit/40ccce492746cc969467c234b9c7d345454da683) - **duckdb**: Transpile Snowflake NORMAL to DuckDB using Box-Muller transform *(PR [#6654](https://github.com/tobymao/sqlglot/pull/6654) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`d6bf569`](https://github.com/tobymao/sqlglot/commit/d6bf569ef2442d5055ab788a2078adf485cc1120) - **snowflake**: updates for Snowflake to DuckDB transpilation of TO_TIMESTAMP functions *(PR [#6622](https://github.com/tobymao/sqlglot/pull/6622) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`51af50e`](https://github.com/tobymao/sqlglot/commit/51af50e937dafb80d42cd639b7fb8e139d811ea0) - **snowflake**: support out of range values for DATE_FROM_PARTS when transpiling to DuckDB *(PR [#6671](https://github.com/tobymao/sqlglot/pull/6671) by [@toriwei](https://github.com/toriwei))*\n- [`13251fd`](https://github.com/tobymao/sqlglot/commit/13251fd7f03d31af69b86a4ef5bb6e940cc9318b) - **mysql**: annotation support for ELT function *(PR [#6659](https://github.com/tobymao/sqlglot/pull/6659) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b287e4e`](https://github.com/tobymao/sqlglot/commit/b287e4ef9e05c0ba1fadc3638977e994eb911834) - **snowflake**: Support transpilation for BITMAP_BUCKET_NUMBER *(PR [#6668](https://github.com/tobymao/sqlglot/pull/6668) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d5b84cb`](https://github.com/tobymao/sqlglot/commit/d5b84cbbd1406ebd9f5e2373e12b0d15dcb85d7f) - **diff**: ignore comments in the semantic differ *(PR [#6675](https://github.com/tobymao/sqlglot/pull/6675) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6673](https://github.com/tobymao/sqlglot/issues/6673) opened by [@GaryLiuGTA](https://github.com/GaryLiuGTA)*\n- [`26c16f2`](https://github.com/tobymao/sqlglot/commit/26c16f2f4bb9aaa91253a06c337b8320dc4609c9) - **snowflake**: transpile CORR with NaN-->NULL *(PR [#6619](https://github.com/tobymao/sqlglot/pull/6619) by [@treysp](https://github.com/treysp))*\n- [`c9a70c0`](https://github.com/tobymao/sqlglot/commit/c9a70c004e0ec563e8a712668a30da9856cb0516) - **snowflake**: update transpilation of DATE_TRUNC to duckdb *(PR [#6644](https://github.com/tobymao/sqlglot/pull/6644) by [@toriwei](https://github.com/toriwei))*\n- [`a4be3fa`](https://github.com/tobymao/sqlglot/commit/a4be3faf63c981a2b10b1f8c709581acf59277ce) - **oracle,exasol**: support SYSTIMESTAMP as NO_PAREN_FUNCTION *(PR [#6677](https://github.com/tobymao/sqlglot/pull/6677) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n  - :arrow_lower_right: *addresses issue [#6686](https://github.com/tobymao/sqlglot/issues/6686) opened by [@Hfuegl](https://github.com/Hfuegl)*\n- [`243448c`](https://github.com/tobymao/sqlglot/commit/243448ca5ccc6a26d680661dcb7d921a055dbfa9) - **duckdb**: Transpile date extraction from Snowflake to DuckDB (YEAR*, WEEK*, DAY*, etc) *(PR [#6666](https://github.com/tobymao/sqlglot/pull/6666) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`efa77df`](https://github.com/tobymao/sqlglot/commit/efa77df3734f2e8534ca911d39c9a5c6103a82fa) - **mysql**: translate UPDATE … FROM … syntax to UPDATE … JOIN … when generating MySQL *(PR [#6655](https://github.com/tobymao/sqlglot/pull/6655) by [@brdbry](https://github.com/brdbry))*\n- [`65acd6c`](https://github.com/tobymao/sqlglot/commit/65acd6c00f023c3279947b23e04139bc9554db85) - **duckdb**: transpile snowflake SYSDATE *(PR [#6693](https://github.com/tobymao/sqlglot/pull/6693) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`36ff96c`](https://github.com/tobymao/sqlglot/commit/36ff96c727e40dbe467d6338128f87a74b99a980) - **snowflake**: support transpilation of BITSHIFTLEFT and BITSHIFTRIGHT *(PR [#6586](https://github.com/tobymao/sqlglot/pull/6586) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`142acd2`](https://github.com/tobymao/sqlglot/commit/142acd21ae9e1b3b1495707b2b6811db830fa61f) - pretty formatting for nested data types *(PR [#6707](https://github.com/tobymao/sqlglot/pull/6707) by [@treysp](https://github.com/treysp))*\n- [`a503a7a`](https://github.com/tobymao/sqlglot/commit/a503a7adbfe3352924e94a21303a2ac23903833c) - **optimizer**: annotate Encode for hive dialect hierarchy *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c447df7`](https://github.com/tobymao/sqlglot/commit/c447df71a645f55e4798887bde4d42285f5dea4a) - **spark,databricks**: annotate the LOCALTIMESTAMP *(PR [#6709](https://github.com/tobymao/sqlglot/pull/6709) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`e91a14b`](https://github.com/tobymao/sqlglot/commit/e91a14b5b325d3d71fbdbe701e531588723bcd2e) - **spark,databricks**: annotate the CURRENT_TIMEZONE *(PR [#6708](https://github.com/tobymao/sqlglot/pull/6708) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b53c8d4`](https://github.com/tobymao/sqlglot/commit/b53c8d47481d735c74ae8704a90594f33263eee8) - **hive,spark,databricks**: annotate the UNIX_TIMESTAMP *(PR [#6717](https://github.com/tobymao/sqlglot/pull/6717) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`cfb06ab`](https://github.com/tobymao/sqlglot/commit/cfb06ab58c606715e688d39e704b1587fcd256e8) - **duckdb**: add support for transpiling ARRAY_AGG with ORDER BY *(PR [#6691](https://github.com/tobymao/sqlglot/pull/6691) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`2965077`](https://github.com/tobymao/sqlglot/commit/296507773fc4476862cc1ee374f88c7d73cb147f) - **parser**: add support for JSON_KEYS func *(PR [#6718](https://github.com/tobymao/sqlglot/pull/6718) by [@geooo109](https://github.com/geooo109))*\n- [`52de0a5`](https://github.com/tobymao/sqlglot/commit/52de0a5c9ebc14c738041e47a385981f072291a9) - **duckdb**: Add transpilation support for float/decimal numbers for TIME functions *(PR [#6719](https://github.com/tobymao/sqlglot/pull/6719) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`bbeb881`](https://github.com/tobymao/sqlglot/commit/bbeb881016e4b84dc5cb6a29846224784591abba) - **oracle**: Added support for IN/OUT keywords in stored procedure parameters *(PR [#6710](https://github.com/tobymao/sqlglot/pull/6710) by [@rsanchez-xtillion](https://github.com/rsanchez-xtillion))*\n- [`0a1c7ab`](https://github.com/tobymao/sqlglot/commit/0a1c7abddc0cbd2f853a431848c1ae45e6876ba2) - **snowflake**: support transpilation of GETBIT from snowflake to duckdb *(PR [#6692](https://github.com/tobymao/sqlglot/pull/6692) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`cb320c4`](https://github.com/tobymao/sqlglot/commit/cb320c41361f0bb7ad71522366ff4cb8691607bf) - **optimizer**: bq annotate type for raw strings *(PR [#6723](https://github.com/tobymao/sqlglot/pull/6723) by [@geooo109](https://github.com/geooo109))*\n- [`09fa467`](https://github.com/tobymao/sqlglot/commit/09fa467461656d5c4d4e57c7044c46ff4fcf3f7f) - **optimizer**: Annotate ATAN2 for Spark & DBX *(PR [#6725](https://github.com/tobymao/sqlglot/pull/6725) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b59b3bf`](https://github.com/tobymao/sqlglot/commit/b59b3bfd06c18120db2938748c561495dd885ab4) - **optimizer**: Annotate TANH for Spark & DBX  *(PR [#6726](https://github.com/tobymao/sqlglot/pull/6726) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`917071b`](https://github.com/tobymao/sqlglot/commit/917071b42b91f5111d88d599a1caed83e6d7661c) - **snowflake**: Support transpilation of TO_TIME and TRY_TO_TIME from snowflake to duckdb *(PR [#6690](https://github.com/tobymao/sqlglot/pull/6690) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`e50a97e`](https://github.com/tobymao/sqlglot/commit/e50a97ed05d50be9fbea7720511a760c11f4a86e) - **snowflake**: Type annotate for Snowflake Kurtosis *(PR [#6720](https://github.com/tobymao/sqlglot/pull/6720) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`197943f`](https://github.com/tobymao/sqlglot/commit/197943fb56e997cbb7e77a8b2d9f0c2453d052c7) - **postgres**: support index predicate in conflict `INSERT` clause closes [#6727](https://github.com/tobymao/sqlglot/pull/6727) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`8f16463`](https://github.com/tobymao/sqlglot/commit/8f16463ebdbb7f51ab778cf34ab89709e018691f) - **presto**: Add support for WEEK function *(PR [#6593](https://github.com/tobymao/sqlglot/pull/6593) by [@chrisqu777](https://github.com/chrisqu777))*\n- [`1497ee6`](https://github.com/tobymao/sqlglot/commit/1497ee6197dd1bd15926ab40bb09f03f72a4da34) - **postgres**: corrected handling of ToChar for Postgres *(commit by [@dhawkins1234](https://github.com/dhawkins1234))*\n- [`c52705d`](https://github.com/tobymao/sqlglot/commit/c52705dec16390e0651d8a68c3500d8fa11dff12) - **optimizer**: annotate EXISTS, ALL, ANY as BOOLEAN *(PR [#6590](https://github.com/tobymao/sqlglot/pull/6590) by [@doripo](https://github.com/doripo))*\n- [`eab6f72`](https://github.com/tobymao/sqlglot/commit/eab6f72a2829def3fd6958f47353ba60ed0e0334) - **parser**: only parse CREATE TABLE pk key ordering in tsql *(PR [#6604](https://github.com/tobymao/sqlglot/pull/6604) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6596](https://github.com/tobymao/sqlglot/issues/6596) opened by [@osmith42](https://github.com/osmith42)*\n- [`62c0ef0`](https://github.com/tobymao/sqlglot/commit/62c0ef0ad4579a55d65498eddd4e649ae464b559) - **duckdb**: Fix BQ's exp.Date transpilation *(PR [#6595](https://github.com/tobymao/sqlglot/pull/6595) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6581](https://github.com/tobymao/sqlglot/issues/6581) opened by [@nikchha](https://github.com/nikchha)*\n- [`d8f0bbd`](https://github.com/tobymao/sqlglot/commit/d8f0bbdbd208acc0eccb7e9be331d2661169ad97) - **tokenizer**: conditionally consume +/- in scientific literal notation *(PR [#6610](https://github.com/tobymao/sqlglot/pull/6610) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6608](https://github.com/tobymao/sqlglot/issues/6608) opened by [@pjpjean](https://github.com/pjpjean)*\n- [`c0b3e5c`](https://github.com/tobymao/sqlglot/commit/c0b3e5c06c7d0ec3335146eab1a86f202298c94a) - **spark**: make_interval week *(PR [#6612](https://github.com/tobymao/sqlglot/pull/6612) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`13014e0`](https://github.com/tobymao/sqlglot/commit/13014e0f01d10f0a078ef8aed4569d3aa8bd741b) - **postgres**: move postgres range parsers to global level *(PR [#6591](https://github.com/tobymao/sqlglot/pull/6591) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`1f3436b`](https://github.com/tobymao/sqlglot/commit/1f3436bfe7ddbbf212d61616153f150d32824450) - **optimizer**: bq robust literal/non-literal type annotation *(PR [#6600](https://github.com/tobymao/sqlglot/pull/6600) by [@geooo109](https://github.com/geooo109))*\n- [`870d600`](https://github.com/tobymao/sqlglot/commit/870d600a93108b1a1d68936244564548dec5f683) - **postgres**: support: postgres point *(PR [#6615](https://github.com/tobymao/sqlglot/pull/6615) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b8b22fa`](https://github.com/tobymao/sqlglot/commit/b8b22fae5cf17ebfe119f815c4cc0ef2dc8132bc) - **trino**: mark as supporting `...EXCEPT ALL` *(PR [#6616](https://github.com/tobymao/sqlglot/pull/6616) by [@NickCrews](https://github.com/NickCrews))*\n- [`9382ebd`](https://github.com/tobymao/sqlglot/commit/9382ebdd79c89e9c92ae98a29147c1523cec415f) - **postgres**: Fix exp.WidthBucket required args *(PR [#6621](https://github.com/tobymao/sqlglot/pull/6621) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6620](https://github.com/tobymao/sqlglot/issues/6620) opened by [@codetalker-ai](https://github.com/codetalker-ai)*\n- [`302fda0`](https://github.com/tobymao/sqlglot/commit/302fda0151094bc074b98847390cd554414258bb) - **clickhouse**: support and, or *(PR [#6625](https://github.com/tobymao/sqlglot/pull/6625) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`00c80b1`](https://github.com/tobymao/sqlglot/commit/00c80b12936811dde7e661720c1bd17c6ba9271a) - Parse joins with derived tables in UPDATE *(PR [#6632](https://github.com/tobymao/sqlglot/pull/6632) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6628](https://github.com/tobymao/sqlglot/issues/6628) opened by [@marktdodds](https://github.com/marktdodds)*\n- [`2bf9405`](https://github.com/tobymao/sqlglot/commit/2bf9405adf9c9c72c77f7aa8ab792779a3b9c5f3) - **oracle**: USING keyword in chr *(PR [#6637](https://github.com/tobymao/sqlglot/pull/6637) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`235fc14`](https://github.com/tobymao/sqlglot/commit/235fc14f40c422e6339da0a6b17252ab9eb18ec2) - **mysql,singlestore**: support charset *(PR [#6633](https://github.com/tobymao/sqlglot/pull/6633) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`b0afbac`](https://github.com/tobymao/sqlglot/commit/b0afbaca100e73d094f247371fff13b5a4b5290a) - **exasol**: fix TO_CHAR parsing leaking canonical datetime format tokens *(PR [#6650](https://github.com/tobymao/sqlglot/pull/6650) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`6ecbb01`](https://github.com/tobymao/sqlglot/commit/6ecbb01a37708347cc6595ed8f17eb1a623d37eb) - **druid**: array expression should use square brackets *(PR [#6664](https://github.com/tobymao/sqlglot/pull/6664) by [@its-felix](https://github.com/its-felix))*\n- [`af50c1c`](https://github.com/tobymao/sqlglot/commit/af50c1ce464d42d4df1fac8876ae25aea556912a) - **duckdb**: Fix NOT precedence for JSON extractions *(PR [#6670](https://github.com/tobymao/sqlglot/pull/6670) by [@kyle-cheung](https://github.com/kyle-cheung))*\n- [`460b3a2`](https://github.com/tobymao/sqlglot/commit/460b3a2ae62d8294c57068453b5a11e4a7e12a91) - **exasol**: Allow varlen args in exp.MD5Digest *(PR [#6685](https://github.com/tobymao/sqlglot/pull/6685) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6683](https://github.com/tobymao/sqlglot/issues/6683) opened by [@Hfuegl](https://github.com/Hfuegl)*\n- [`dcdee68`](https://github.com/tobymao/sqlglot/commit/dcdee68cb1a77286232865de9df8d8a01898fcc7) - **spark**: Allow non aggregation functions in PIVOT *(PR [#6687](https://github.com/tobymao/sqlglot/pull/6687) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6684](https://github.com/tobymao/sqlglot/issues/6684) opened by [@gauravdawar-e6](https://github.com/gauravdawar-e6)*\n- [`29cff1f`](https://github.com/tobymao/sqlglot/commit/29cff1fbe0011476b22e1b1442af857499f871a6) - **optimizer**: bq robust literal/non-literal binary annotation *(PR [#6688](https://github.com/tobymao/sqlglot/pull/6688) by [@geooo109](https://github.com/geooo109))*\n- [`f866c83`](https://github.com/tobymao/sqlglot/commit/f866c835b84e9503b2f335a0da4b89e3426aa9e7) - **oracle**: properly parse xmlelement *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`91b3678`](https://github.com/tobymao/sqlglot/commit/91b3678c4e2380b3b344cc37643d12528c3e142b) - **resolver**: correctly resolve unnest alias shadowing for BigQuery *(PR [#6665](https://github.com/tobymao/sqlglot/pull/6665) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`2a3f1fe`](https://github.com/tobymao/sqlglot/commit/2a3f1fe854746141299303fdae36ce33688722e1) - **spark, databricks**: parse LTRIM/RTRIM *(PR [#6699](https://github.com/tobymao/sqlglot/pull/6699) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6696](https://github.com/tobymao/sqlglot/issues/6696) opened by [@avinmathew](https://github.com/avinmathew)*\n- [`fb8f57a`](https://github.com/tobymao/sqlglot/commit/fb8f57ac54a4e0213941f3bd057ccb03c4e125b7) - **doris, starrocks**: Fix UPDATE statement for multi tables *(PR [#6700](https://github.com/tobymao/sqlglot/pull/6700) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`21f9d3c`](https://github.com/tobymao/sqlglot/commit/21f9d3ca7c6fa0d38f0f63000f904589b91c7d0a) - **optimizer**: Normalize struct field names when annotating types *(PR [#6674](https://github.com/tobymao/sqlglot/pull/6674) by [@vchan](https://github.com/vchan))*\n- [`7362c23`](https://github.com/tobymao/sqlglot/commit/7362c2357eba540a12eea079e9f4212f3545c8d1) - **oracle**: interval expressions *(PR [#6648](https://github.com/tobymao/sqlglot/pull/6648) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a84dbc2`](https://github.com/tobymao/sqlglot/commit/a84dbc2de051539ac4dfe10e0fa991c988840874) - **parser**: prevent INTERVAL from consuming GENERATED as interval unit *(PR [#6714](https://github.com/tobymao/sqlglot/pull/6714) by [@harshsinh](https://github.com/harshsinh))*\n  - :arrow_lower_right: *fixes issue [#6713](https://github.com/tobymao/sqlglot/issues/6713) opened by [@harshsinh](https://github.com/harshsinh)*\n\n### :recycle: Refactors\n- [`399c80f`](https://github.com/tobymao/sqlglot/commit/399c80f2e66a1acdcd7d567e44c297db7071dc8f) - rename args for CovarSamp/Pop, stop inheriting from Binary *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`14b03ae`](https://github.com/tobymao/sqlglot/commit/14b03ae492c9cac67ae1e78b67a83427e4fe6681) - flip Getbit lsb flag to msb since more dialects match lsb *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`e0193ea`](https://github.com/tobymao/sqlglot/commit/e0193eaeea0036bbe835c49a64a4120365525b89) - Fix make style of 6593 *(PR [#6597](https://github.com/tobymao/sqlglot/pull/6597) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`0534db1`](https://github.com/tobymao/sqlglot/commit/0534db11022c9cbaf91015b38039da48945aac2a) - Fix integration test failing with empty PR description *(PR [#6598](https://github.com/tobymao/sqlglot/pull/6598) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`c523e2a`](https://github.com/tobymao/sqlglot/commit/c523e2a4ee3b347eacf6f4f9a3629b649e902f47) - Follow up of 6551 *(PR [#6599](https://github.com/tobymao/sqlglot/pull/6599) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`26c3c1f`](https://github.com/tobymao/sqlglot/commit/26c3c1fcea5f105432430de3d1e2b00627191706) - Fix integration test condition for skipping fork PRs *(PR [#6606](https://github.com/tobymao/sqlglot/pull/6606) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`d0965ba`](https://github.com/tobymao/sqlglot/commit/d0965baa8224f72f01a04f2d9b72399f04b6103e) - Add test for PR 6616 *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`020fb05`](https://github.com/tobymao/sqlglot/commit/020fb05971f95ba3962d051c1ef795147ee9e19d) - refactor duckdb `ZIPF` transpilation logic *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`a4e1dec`](https://github.com/tobymao/sqlglot/commit/a4e1dec87fc854ee15789672189753cf2e4450e1) - Refactor PR 6617 *(PR [#6630](https://github.com/tobymao/sqlglot/pull/6630) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`7f2a74c`](https://github.com/tobymao/sqlglot/commit/7f2a74c1025ab2c9627889db174a7f6404c2d585) - refactor `RANDSTR` duckdb transpilation logic *(PR [#6631](https://github.com/tobymao/sqlglot/pull/6631) by [@georgesittas](https://github.com/georgesittas))*\n- [`9a41cfc`](https://github.com/tobymao/sqlglot/commit/9a41cfcc1b57488373d7a63d17b6a0d91f90c9e8) - Refactor PR 6637 *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2f8ffcf`](https://github.com/tobymao/sqlglot/commit/2f8ffcfac03a09460a4603d66ee7b13affbc5ebf) - **snowflake**: Adding type annotation tests for Snowflake's STDDEV / STDDEV_SAMP, STDDEV_POP  *(PR [#6641](https://github.com/tobymao/sqlglot/pull/6641) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`d46969d`](https://github.com/tobymao/sqlglot/commit/d46969db50ff52b8657d5b33f0a106b69dbd1e2a) - **optimizer**: annotate snowflake ARRAY_APPEND and ARRAY_PREPEND *(PR [#6645](https://github.com/tobymao/sqlglot/pull/6645) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`4deaabc`](https://github.com/tobymao/sqlglot/commit/4deaabcedb823ff29174674fa3d398cb7966e219) - array_append/array_prepend tests clean up *(commit by [@geooo109](https://github.com/geooo109))*\n- [`b3ea996`](https://github.com/tobymao/sqlglot/commit/b3ea99607b4947905bfb55994309c0ee3e646c62) - array_append/array_prepend databricks tests *(commit by [@geooo109](https://github.com/geooo109))*\n- [`7f6b9f2`](https://github.com/tobymao/sqlglot/commit/7f6b9f2eda20509b442d28f7fc470e7a0aee63b7) - snowflake remove aead arg gen for DECRYPT *(commit by [@geooo109](https://github.com/geooo109))*\n- [`1fdcd63`](https://github.com/tobymao/sqlglot/commit/1fdcd6389587da95dbda34227ec688181055dcf9) - Fix onboarding md paragraph *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5d76a45`](https://github.com/tobymao/sqlglot/commit/5d76a45ff71feb19eee4b294f2c128e30976cb5b) - Follow up 6677 *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`8d49255`](https://github.com/tobymao/sqlglot/commit/8d492558ff18b6af53a1aaea47cc0a863fbfe482) - Remove incorrect test of PR6655 *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`94e6fe0`](https://github.com/tobymao/sqlglot/commit/94e6fe03752500ddeace317081dc758d36c78c1f) - Follow up of 6693 *(PR [#6698](https://github.com/tobymao/sqlglot/pull/6698) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`dd4d55a`](https://github.com/tobymao/sqlglot/commit/dd4d55af58a7602a1924f6b1f9a93f58636f172c) - _annotate_by_args UNKNOWN *(PR [#6703](https://github.com/tobymao/sqlglot/pull/6703) by [@geooo109](https://github.com/geooo109))*\n- [`2ecfc27`](https://github.com/tobymao/sqlglot/commit/2ecfc27daf3570bca442797e0b4f4dc4e5363dd6) - Follow up 6648 *(PR [#6716](https://github.com/tobymao/sqlglot/pull/6716) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`bbaba5f`](https://github.com/tobymao/sqlglot/commit/bbaba5fd7d0111947035534168593a5a3ee6839a) - **optimizer**: annotate type for snowflake ARRAY_CAT *(PR [#6721](https://github.com/tobymao/sqlglot/pull/6721) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`4968ccd`](https://github.com/tobymao/sqlglot/commit/4968ccd1d74d58dace3793d4dbf7a0bff22d3300) - add deployment instructions in README *(PR [#6722](https://github.com/tobymao/sqlglot/pull/6722) by [@georgesittas](https://github.com/georgesittas))*\n- [`2893ac3`](https://github.com/tobymao/sqlglot/commit/2893ac399d24846482b45fd79e99b90f5ef2cca6) - **optimizer**: Remove duplicate INITCAP annotation from Snowflake *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`33b8a5d`](https://github.com/tobymao/sqlglot/commit/33b8a5d25bf49791fb95ec20132ab6ff0bb885e0) - bump sqlglotrs to 0.11.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v28.5.0] - 2025-12-17\n### :boom: BREAKING CHANGES\n- due to [`4dfc810`](https://github.com/tobymao/sqlglot/commit/4dfc810f45d5a617ada2ba4ed57002549c8d1853) - support transpilation of BOOLNOT from snowflake to duckdb *(PR [#6577](https://github.com/tobymao/sqlglot/pull/6577) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of BOOLNOT from snowflake to duckdb (#6577)\n\n- due to [`b857185`](https://github.com/tobymao/sqlglot/commit/b8571850ca55802671484d118560a7b90e893c39) - remove Sysdate in favor of CurrentTimestamp with sysdate arg *(PR [#6584](https://github.com/tobymao/sqlglot/pull/6584) by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove Sysdate in favor of CurrentTimestamp with sysdate arg (#6584)\n\n- due to [`bf217d6`](https://github.com/tobymao/sqlglot/commit/bf217d69f92efcbce5b69d637976e915ca63998d) - make `JSONArrayAgg` an `AggFunc` *(PR [#6585](https://github.com/tobymao/sqlglot/pull/6585) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  make `JSONArrayAgg` an `AggFunc` (#6585)\n\n- due to [`604efe5`](https://github.com/tobymao/sqlglot/commit/604efe5cf5812d0b1dd9d625ed278907d0d7fb8f) - Type annotation fixes for TO_TIMESTAMP* *(PR [#6557](https://github.com/tobymao/sqlglot/pull/6557) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Type annotation fixes for TO_TIMESTAMP* (#6557)\n\n\n### :sparkles: New Features\n- [`4dfc810`](https://github.com/tobymao/sqlglot/commit/4dfc810f45d5a617ada2ba4ed57002549c8d1853) - **snowflake**: support transpilation of BOOLNOT from snowflake to duckdb *(PR [#6577](https://github.com/tobymao/sqlglot/pull/6577) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7077981`](https://github.com/tobymao/sqlglot/commit/707798166c1b45e633bd0e8d02d1c0146598b03a) - **snowflake**: Transpilation of Snowflake MONTHS_BETWEEN to DuckDB *(PR [#6561](https://github.com/tobymao/sqlglot/pull/6561) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`604efe5`](https://github.com/tobymao/sqlglot/commit/604efe5cf5812d0b1dd9d625ed278907d0d7fb8f) - **snowflake**: Type annotation fixes for TO_TIMESTAMP* *(PR [#6557](https://github.com/tobymao/sqlglot/pull/6557) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`3567880`](https://github.com/tobymao/sqlglot/commit/35678808dafb37c5d37c806682e6af9b6351bced) - add tokens to functions *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`b857185`](https://github.com/tobymao/sqlglot/commit/b8571850ca55802671484d118560a7b90e893c39) - **snowflake**: remove Sysdate in favor of CurrentTimestamp with sysdate arg *(PR [#6584](https://github.com/tobymao/sqlglot/pull/6584) by [@georgesittas](https://github.com/georgesittas))*\n- [`bf217d6`](https://github.com/tobymao/sqlglot/commit/bf217d69f92efcbce5b69d637976e915ca63998d) - make `JSONArrayAgg` an `AggFunc` *(PR [#6585](https://github.com/tobymao/sqlglot/pull/6585) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`48f5e99`](https://github.com/tobymao/sqlglot/commit/48f5e999d3d3f6ad51c30e7a33a3a574d0e50d2b) - **duckdb**: preserve l/r-trim syntax *(PR [#6588](https://github.com/tobymao/sqlglot/pull/6588) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6587](https://github.com/tobymao/sqlglot/issues/6587) opened by [@baruchoxman](https://github.com/baruchoxman)*\n\n### :wrench: Chores\n- [`ea0263a`](https://github.com/tobymao/sqlglot/commit/ea0263aa555591b03b06a4b6dee093fe42b545f9) - Skip integration tests GA for external contributors & fix `git diff` *(PR [#6582](https://github.com/tobymao/sqlglot/pull/6582) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v28.4.1] - 2025-12-16\n### :boom: BREAKING CHANGES\n- due to [`cfc9346`](https://github.com/tobymao/sqlglot/commit/cfc9346ba0477523d3de8f923d83fd09814b22ac) - bump sqlglotrs to 0.10.0 *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  bump sqlglotrs to 0.10.0\n\n\n### :wrench: Chores\n- [`cfc9346`](https://github.com/tobymao/sqlglot/commit/cfc9346ba0477523d3de8f923d83fd09814b22ac) - bump sqlglotrs to 0.10.0 *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v28.4.0] - 2025-12-16\n### :boom: BREAKING CHANGES\n- due to [`938f4b6`](https://github.com/tobymao/sqlglot/commit/938f4b6ebc1c0d26bd3c1400883978c79a435189) - annotate type for LAST_DAY *(PR [#5528](https://github.com/tobymao/sqlglot/pull/5528) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for LAST_DAY (#5528)\n\n- due to [`7d12dac`](https://github.com/tobymao/sqlglot/commit/7d12dac613ba5119334408f2c52cb270067156d9) - annotate type for bigquery GENERATE_TIMESTAMP_ARRAY *(PR [#5529](https://github.com/tobymao/sqlglot/pull/5529) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery GENERATE_TIMESTAMP_ARRAY (#5529)\n\n- due to [`d50ebe2`](https://github.com/tobymao/sqlglot/commit/d50ebe286dd8e2836b9eb2a3406f15976db3aa05) - annotate type for bigquery TIME_TRUNC *(PR [#5530](https://github.com/tobymao/sqlglot/pull/5530) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery TIME_TRUNC (#5530)\n\n- due to [`29748be`](https://github.com/tobymao/sqlglot/commit/29748be7dfc10edc9f29665c98327883dd25c13d) - annotate type for bigquery TIME *(PR [#5531](https://github.com/tobymao/sqlglot/pull/5531) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery TIME (#5531)\n\n- due to [`7003b3f`](https://github.com/tobymao/sqlglot/commit/7003b3fa39cd455e3643066364696708d1ac4f38) - parse and annotate type for bigquery DATE_FROM_UNIX_DATE *(PR [#5532](https://github.com/tobymao/sqlglot/pull/5532) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery DATE_FROM_UNIX_DATE (#5532)\n\n- due to [`a276ca6`](https://github.com/tobymao/sqlglot/commit/a276ca6fd5f9d47fa8c90fcfa19f9864e7a28f8f) - parse and annotate type for bigquery JUSTIFY funcs *(PR [#5534](https://github.com/tobymao/sqlglot/pull/5534) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery JUSTIFY funcs (#5534)\n\n- due to [`374178e`](https://github.com/tobymao/sqlglot/commit/374178e22fe8d2d2275b65fe08e27ef66c611220) - parse and annotate type for bigquery UNIX_MICROS and UNIX_MILLIS *(PR [#5535](https://github.com/tobymao/sqlglot/pull/5535) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery UNIX_MICROS and UNIX_MILLIS (#5535)\n\n- due to [`1d8d1ab`](https://github.com/tobymao/sqlglot/commit/1d8d1abe459053a135a46525d0a13bb861220927) - annotate type for bigquery DATE_TRUNC *(PR [#5540](https://github.com/tobymao/sqlglot/pull/5540) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery DATE_TRUNC (#5540)\n\n- due to [`306ba65`](https://github.com/tobymao/sqlglot/commit/306ba6531839ea2823f5165de7bde01d17560845) - annotate type for bigquery TIMESTAMP_TRUNC *(PR [#5541](https://github.com/tobymao/sqlglot/pull/5541) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery TIMESTAMP_TRUNC (#5541)\n\n- due to [`d799c5a`](https://github.com/tobymao/sqlglot/commit/d799c5af23010a67c29edb6d45a40fb24903e1a3) - preserve projection names when merging subqueries *(commit by [@snovik75](https://github.com/snovik75))*:\n\n  preserve projection names when merging subqueries\n\n- due to [`8130bd4`](https://github.com/tobymao/sqlglot/commit/8130bd40815803a6781ee8f20fccd30987516192) - WEEKDAY of WEEK as VAR *(PR [#5552](https://github.com/tobymao/sqlglot/pull/5552) by [@geooo109](https://github.com/geooo109))*:\n\n  WEEKDAY of WEEK as VAR (#5552)\n\n- due to [`f3ffe19`](https://github.com/tobymao/sqlglot/commit/f3ffe19ec01533c5f27b9d3a7b6704b83c005118) - annotate type for bigquery format_time *(PR [#5559](https://github.com/tobymao/sqlglot/pull/5559) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery format_time (#5559)\n\n- due to [`6872b43`](https://github.com/tobymao/sqlglot/commit/6872b43ba17a39137172fd2fa9f0d059ce595ef9) - use dialect in DataType.build fixes [#5560](https://github.com/tobymao/sqlglot/pull/5560) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  use dialect in DataType.build fixes #5560\n\n- due to [`3ab3690`](https://github.com/tobymao/sqlglot/commit/3ab369096313b418699b7942b1c513c0c66a5331) - parse and annotate type for bigquery PARSE_DATETIME *(PR [#5558](https://github.com/tobymao/sqlglot/pull/5558) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery PARSE_DATETIME (#5558)\n\n- due to [`e5da951`](https://github.com/tobymao/sqlglot/commit/e5da951542eb55691bc43fbbfbec4a30100de038) - parse and annotate type for bigquery PARSE_TIME *(PR [#5561](https://github.com/tobymao/sqlglot/pull/5561) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery PARSE_TIME (#5561)\n\n- due to [`798e213`](https://github.com/tobymao/sqlglot/commit/798e213fd10c3b61afbd8cef621546de65fa6f26) - improve transpilability of ANY_VALUE closes [#5563](https://github.com/tobymao/sqlglot/pull/5563) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve transpilability of ANY_VALUE closes #5563\n\n- due to [`8c0cb76`](https://github.com/tobymao/sqlglot/commit/8c0cb764fd825062fb7334032b8eeffbc39627d5) - more robust CREATE SEQUENCE *(PR [#5566](https://github.com/tobymao/sqlglot/pull/5566) by [@geooo109](https://github.com/geooo109))*:\n\n  more robust CREATE SEQUENCE (#5566)\n\n- due to [`c7041c7`](https://github.com/tobymao/sqlglot/commit/c7041c71250b17192c2f25fb8f33407324d332c2) - parse and annotate type for bigquery BYTE_LENGHT *(PR [#5568](https://github.com/tobymao/sqlglot/pull/5568) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery BYTE_LENGHT (#5568)\n\n- due to [`a6c61c3`](https://github.com/tobymao/sqlglot/commit/a6c61c34f1e168c97dd5c2b8ec071372ba593992) - parse and annotate type for bigquery CODE_POINTS_TO_STRING *(PR [#5569](https://github.com/tobymao/sqlglot/pull/5569) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery CODE_POINTS_TO_STRING (#5569)\n\n- due to [`51e0335`](https://github.com/tobymao/sqlglot/commit/51e0335377fe2bc2e2a94a623475791e9dd19fb9) - parse and annotate type for bigquery REVERSE *(PR [#5571](https://github.com/tobymao/sqlglot/pull/5571) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery REVERSE (#5571)\n\n- due to [`2a33339`](https://github.com/tobymao/sqlglot/commit/2a333395cde71936df911488afcff92cae735e11) - annotate type for bigquery REPLACE *(PR [#5572](https://github.com/tobymao/sqlglot/pull/5572) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery REPLACE (#5572)\n\n- due to [`1e6f813`](https://github.com/tobymao/sqlglot/commit/1e6f81343de641e588f1a05ce7dc01bed72bd849) - annotate type for bigquery REGEXP_EXTRACT_ALL *(PR [#5573](https://github.com/tobymao/sqlglot/pull/5573) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery REGEXP_EXTRACT_ALL (#5573)\n\n- due to [`d0d62ed`](https://github.com/tobymao/sqlglot/commit/d0d62ede6320b3fd0eee04b7073f5708676dc58c) - support `TO_CHAR` with numeric inputs *(PR [#5570](https://github.com/tobymao/sqlglot/pull/5570) by [@jasonthomassql](https://github.com/jasonthomassql))*:\n\n  support `TO_CHAR` with numeric inputs (#5570)\n\n- due to [`7928985`](https://github.com/tobymao/sqlglot/commit/7928985a655c3d0244bc9175a37f502b19a5c5f0) - allow dashes in JSONPath keys *(PR [#5574](https://github.com/tobymao/sqlglot/pull/5574) by [@georgesittas](https://github.com/georgesittas))*:\n\n  allow dashes in JSONPath keys (#5574)\n\n- due to [`eb09e6e`](https://github.com/tobymao/sqlglot/commit/eb09e6e32491a05846488de7b72b1dca0e0a2669) - parse and annotate type for bigquery TRANSLATE *(PR [#5575](https://github.com/tobymao/sqlglot/pull/5575) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery TRANSLATE (#5575)\n\n- due to [`f9a522b`](https://github.com/tobymao/sqlglot/commit/f9a522b26cd5d643b8b18fa64d70f2a3f0ff2d2c) - parse and annotate type for bigquery SOUNDEX *(PR [#5576](https://github.com/tobymao/sqlglot/pull/5576) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery SOUNDEX (#5576)\n\n- due to [`51da41b`](https://github.com/tobymao/sqlglot/commit/51da41b90ce421b154e45add28353ac044640a1c) - annotate type for bigquery MD5 *(PR [#5577](https://github.com/tobymao/sqlglot/pull/5577) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery MD5 (#5577)\n\n- due to [`bcf302f`](https://github.com/tobymao/sqlglot/commit/bcf302ff6ad2d0adfc29f708a8b53b5c0e547619) - annotate type for bigquery MIN/MAX BY *(PR [#5579](https://github.com/tobymao/sqlglot/pull/5579) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery MIN/MAX BY (#5579)\n\n- due to [`c501d9e`](https://github.com/tobymao/sqlglot/commit/c501d9e6f58e4880e4d23f21f53f72dcb5fdaa8c) - parse and annotate type for bigquery GROUPING *(PR [#5581](https://github.com/tobymao/sqlglot/pull/5581) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery GROUPING (#5581)\n\n- due to [`7b180bd`](https://github.com/tobymao/sqlglot/commit/7b180bdc3da9e39946c22970bd2523f7d8beaf29) - raise if query modifier is specified multiple times *(PR [#5608](https://github.com/tobymao/sqlglot/pull/5608) by [@georgesittas](https://github.com/georgesittas))*:\n\n  raise if query modifier is specified multiple times (#5608)\n\n- due to [`36602a2`](https://github.com/tobymao/sqlglot/commit/36602a2ecc9ffca98e89044d23e40f33c6ed71e4) - parse LIST_FILTER into ArrayFilter closes [#5633](https://github.com/tobymao/sqlglot/pull/5633) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse LIST_FILTER into ArrayFilter closes #5633\n\n- due to [`0188d21`](https://github.com/tobymao/sqlglot/commit/0188d21d443c991a528eb9d220459890b7dca477) - parse LIST_TRANSFORM into Transform closes [#5634](https://github.com/tobymao/sqlglot/pull/5634) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse LIST_TRANSFORM into Transform closes #5634\n\n- due to [`3ab1d44`](https://github.com/tobymao/sqlglot/commit/3ab1d4487279cab3be2d3764e51516c6db21629d) - Wrap CONCAT items with COALESCE less aggressively *(PR [#5641](https://github.com/tobymao/sqlglot/pull/5641) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Wrap CONCAT items with COALESCE less aggressively (#5641)\n\n- due to [`af0b299`](https://github.com/tobymao/sqlglot/commit/af0b299561914953b30ab36004e53dcb92d39e1c) - Qualify columns generated by exp.Aliases *(PR [#5647](https://github.com/tobymao/sqlglot/pull/5647) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Qualify columns generated by exp.Aliases (#5647)\n\n- due to [`53aa8fe`](https://github.com/tobymao/sqlglot/commit/53aa8fe7f188012f765066f32c4179035fff036d) - support alter table with check closes [#5649](https://github.com/tobymao/sqlglot/pull/5649) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  support alter table with check closes #5649\n\n- due to [`1a60a5a`](https://github.com/tobymao/sqlglot/commit/1a60a5a845c7431d7d3d7ccb71119699316f4b41) - Added parsing/generation of JSON_ARRAY_CONTAINS function *(PR [#5661](https://github.com/tobymao/sqlglot/pull/5661) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*:\n\n  Added parsing/generation of JSON_ARRAY_CONTAINS function (#5661)\n\n- due to [`e0db0a9`](https://github.com/tobymao/sqlglot/commit/e0db0a95d3cb7614242dbd1b439d408e7e7bd475) - add parse and annotate type for bigquery FARM_FINGERPRINT *(PR [#5667](https://github.com/tobymao/sqlglot/pull/5667) by [@geooo109](https://github.com/geooo109))*:\n\n  add parse and annotate type for bigquery FARM_FINGERPRINT (#5667)\n\n- due to [`56588c7`](https://github.com/tobymao/sqlglot/commit/56588c7e22b4db4f0e44696a460483ca1e549163) - Add support for vector_search function. Move predict to BigQuery dialect. *(PR [#5660](https://github.com/tobymao/sqlglot/pull/5660) by [@rloredo](https://github.com/rloredo))*:\n\n  Add support for vector_search function. Move predict to BigQuery dialect. (#5660)\n\n- due to [`a688a0f`](https://github.com/tobymao/sqlglot/commit/a688a0f0d70f87139e531d1419b338b695bec384) - parse and annotate type for bigquery APPROX_TOP_COUNT *(PR [#5670](https://github.com/tobymao/sqlglot/pull/5670) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery APPROX_TOP_COUNT (#5670)\n\n- due to [`3c93fcc`](https://github.com/tobymao/sqlglot/commit/3c93fcce96ec82e78753f6c9dd5fb0e730a82058) - parse and annotate type for bigquery APPROX_TOP_SUM *(PR [#5675](https://github.com/tobymao/sqlglot/pull/5675) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery APPROX_TOP_SUM (#5675)\n\n- due to [`741d45a`](https://github.com/tobymao/sqlglot/commit/741d45a0ca7c1bad67da4393cd10cc9cfa49ea68) - parse and annotate type for bigquery FROM/TO_BASE32 *(PR [#5676](https://github.com/tobymao/sqlglot/pull/5676) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery FROM/TO_BASE32 (#5676)\n\n- due to [`9ae045c`](https://github.com/tobymao/sqlglot/commit/9ae045c0405e43b148e3b9261825288ebf09100c) - parse and annotate type for bigquery FROM_HEX *(PR [#5679](https://github.com/tobymao/sqlglot/pull/5679) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery FROM_HEX (#5679)\n\n- due to [`5a22a25`](https://github.com/tobymao/sqlglot/commit/5a22a254143978989027f6e7f6163019a34f112a) - annotate type for bigquery TO_HEX *(PR [#5680](https://github.com/tobymao/sqlglot/pull/5680) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery TO_HEX (#5680)\n\n- due to [`5c1eb2d`](https://github.com/tobymao/sqlglot/commit/5c1eb2df5dd3dcc6ed2c8204cec56b5c3d276f87) - parse and annotate type for bq PARSE_BIG/NUMERIC *(PR [#5690](https://github.com/tobymao/sqlglot/pull/5690) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq PARSE_BIG/NUMERIC (#5690)\n\n- due to [`311373d`](https://github.com/tobymao/sqlglot/commit/311373d22134de906d1c1cef019541e85e2f7c9f) - parse and annotate type for bq CODE_POINTS_TO_BYTES *(PR [#5686](https://github.com/tobymao/sqlglot/pull/5686) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq CODE_POINTS_TO_BYTES (#5686)\n\n- due to [`79d9de1`](https://github.com/tobymao/sqlglot/commit/79d9de1745598f8f3ae2c82c1389dd455c946a09) - parse and annotate type for bq TO_CODE_POINTS *(PR [#5685](https://github.com/tobymao/sqlglot/pull/5685) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq TO_CODE_POINTS (#5685)\n\n- due to [`5df3ea9`](https://github.com/tobymao/sqlglot/commit/5df3ea92f59125955124ea1883b777b489db3042) - parse and annotate type for bq SAFE_CONVERT_BYTES_TO_STRING *(PR [#5681](https://github.com/tobymao/sqlglot/pull/5681) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq SAFE_CONVERT_BYTES_TO_STRING (#5681)\n\n- due to [`c832746`](https://github.com/tobymao/sqlglot/commit/c832746018fbc2c531d5b2a7c7f8cd5d78e511ff) - parse and annotate type for bigquery APPROX_QUANTILES *(PR [#5678](https://github.com/tobymao/sqlglot/pull/5678) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery APPROX_QUANTILES (#5678)\n\n- due to [`99e169e`](https://github.com/tobymao/sqlglot/commit/99e169ea13d5be3712a47f6b55b98a4764a3c24d) - parse and annotate type for bq BOOL *(PR [#5697](https://github.com/tobymao/sqlglot/pull/5697) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq BOOL (#5697)\n\n- due to [`3f31770`](https://github.com/tobymao/sqlglot/commit/3f31770c793f464fcac1ce2b8dfa03d4b7f0231c) - parse and annotate type for bq FLOAT64 *(PR [#5700](https://github.com/tobymao/sqlglot/pull/5700) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq FLOAT64 (#5700)\n\n- due to [`de2fe15`](https://github.com/tobymao/sqlglot/commit/de2fe1503b5bb003431d1f0c7b9ae87932a6cc1c) - annotate type for bq CONTAINS_SUBSTR *(PR [#5705](https://github.com/tobymao/sqlglot/pull/5705) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq CONTAINS_SUBSTR (#5705)\n\n- due to [`770888f`](https://github.com/tobymao/sqlglot/commit/770888f4e9a9061329e3c416f968f7dd9639fb81) - annotate type for bq NORMALIZE *(PR [#5711](https://github.com/tobymao/sqlglot/pull/5711) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq NORMALIZE (#5711)\n\n- due to [`506033f`](https://github.com/tobymao/sqlglot/commit/506033f299f7a4c28f6efd8bf715be5dcf73e929) - parse and annotate type for bq NORMALIZE_AND_CASEFOLD *(PR [#5712](https://github.com/tobymao/sqlglot/pull/5712) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq NORMALIZE_AND_CASEFOLD (#5712)\n\n- due to [`848aea1`](https://github.com/tobymao/sqlglot/commit/848aea1dbaaeb580b633796dcca06c28314b9c3e) - parse and annotate type for bq OCTET_LENGTH *(PR [#5713](https://github.com/tobymao/sqlglot/pull/5713) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq OCTET_LENGTH (#5713)\n\n- due to [`727bf83`](https://github.com/tobymao/sqlglot/commit/727bf8378f232188d35834d980b035552999ea3b) - add support for REVOKE DDL *(PR [#5703](https://github.com/tobymao/sqlglot/pull/5703) by [@newtonapple](https://github.com/newtonapple))*:\n\n  add support for REVOKE DDL (#5703)\n\n- due to [`baffd2c`](https://github.com/tobymao/sqlglot/commit/baffd2c0be9657683781f3f8831c47e32dbf68bb) - parse and annotate type for bq REGEXP_INSTR *(PR [#5710](https://github.com/tobymao/sqlglot/pull/5710) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq REGEXP_INSTR (#5710)\n\n- due to [`b79eb19`](https://github.com/tobymao/sqlglot/commit/b79eb198cc21203efa82128b357d435338e9133d) - annotate type for bq ROW_NUMBER *(PR [#5716](https://github.com/tobymao/sqlglot/pull/5716) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq ROW_NUMBER (#5716)\n\n- due to [`f709bef`](https://github.com/tobymao/sqlglot/commit/f709bef3af7cd0daa25fe3d58b1753c3e65720ef) - annotate type for bq FIRST_VALUE *(PR [#5718](https://github.com/tobymao/sqlglot/pull/5718) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq FIRST_VALUE (#5718)\n\n- due to [`15a9061`](https://github.com/tobymao/sqlglot/commit/15a906170e5d5cdaa207ec7607edfdd7d4a8b774) - annotate type for bq PERCENTILE_DISC *(PR [#5722](https://github.com/tobymao/sqlglot/pull/5722) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq PERCENTILE_DISC (#5722)\n\n- due to [`7d49609`](https://github.com/tobymao/sqlglot/commit/7d4960963f0ef70b96f5b969bb008d2742e833ea) - annotate type for bq NTH_VALUE *(PR [#5720](https://github.com/tobymao/sqlglot/pull/5720) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq NTH_VALUE (#5720)\n\n- due to [`d41acf1`](https://github.com/tobymao/sqlglot/commit/d41acf11221bee30a5ae089cbac9b158ed3dd515) - annotate type for bq LEAD *(PR [#5719](https://github.com/tobymao/sqlglot/pull/5719) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq LEAD (#5719)\n\n- due to [`ff12130`](https://github.com/tobymao/sqlglot/commit/ff12130c23a215917f20fda7d50322f1cb7de599) - annotate type for bq PERNCENTILE_CONT *(PR [#5729](https://github.com/tobymao/sqlglot/pull/5729) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq PERNCENTILE_CONT (#5729)\n\n- due to [`fdb8a0a`](https://github.com/tobymao/sqlglot/commit/fdb8a0a6d0d74194255f313bd934db7fc1ce0d3f) - parse and annotate type for bq FORMAT *(PR [#5715](https://github.com/tobymao/sqlglot/pull/5715) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq FORMAT (#5715)\n\n- due to [`012bdd3`](https://github.com/tobymao/sqlglot/commit/012bdd3c8aeff180f85354ffd403fc1aa5815dcf) - parse and annotate type for bq CUME_DIST *(PR [#5735](https://github.com/tobymao/sqlglot/pull/5735) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq CUME_DIST (#5735)\n\n- due to [`b99eaeb`](https://github.com/tobymao/sqlglot/commit/b99eaeb0c6eb3dc613e76d205e02632bd6af353b) - parse and annotate type for bq DENSE_RANK *(PR [#5736](https://github.com/tobymao/sqlglot/pull/5736) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq DENSE_RANK (#5736)\n\n- due to [`bb95c73`](https://github.com/tobymao/sqlglot/commit/bb95c7312c942ef987955f01e060604d60e32e83) - parse and annotate type for bq RANK *(PR [#5738](https://github.com/tobymao/sqlglot/pull/5738) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq RANK (#5738)\n\n- due to [`8713c08`](https://github.com/tobymao/sqlglot/commit/8713c082b0aa8454a5773fc2a85e08a132dc6ce3) - parse and annotate type for bq PERCENT_RANK *(PR [#5739](https://github.com/tobymao/sqlglot/pull/5739) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq PERCENT_RANK (#5739)\n\n- due to [`9ce4e31`](https://github.com/tobymao/sqlglot/commit/9ce4e31aecbde6ea1f227a7166c0f3dc9e302a66) - annotate type for bq JSON_OBJECT *(PR [#5740](https://github.com/tobymao/sqlglot/pull/5740) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq JSON_OBJECT (#5740)\n\n- due to [`d35ec6e`](https://github.com/tobymao/sqlglot/commit/d35ec6e37e21cf3cec848ed55bd73128c4633cd2) - annotate type for bq JSON_QUERY/JSON_QUERY_ARRAY *(PR [#5741](https://github.com/tobymao/sqlglot/pull/5741) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq JSON_QUERY/JSON_QUERY_ARRAY (#5741)\n\n- due to [`4753642`](https://github.com/tobymao/sqlglot/commit/4753642cfcfb1f192ec4d21a492737b27affef09) - annotate type for bq JSON_EXTRACT_SCALAR *(commit by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq JSON_EXTRACT_SCALAR\n\n- due to [`113a530`](https://github.com/tobymao/sqlglot/commit/113a5308d050fd5ceacab4c6188e5eea5dd740b1) - parse and annotate type for bq JSON_ARRAY_APPEND *(PR [#5747](https://github.com/tobymao/sqlglot/pull/5747) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_ARRAY_APPEND (#5747)\n\n- due to [`268e2c6`](https://github.com/tobymao/sqlglot/commit/268e2c694d1eb99f1fe64477bc38ed4946bf1c32) - parse and annotate type for bq JSON_ARRAY_INSERT *(PR [#5748](https://github.com/tobymao/sqlglot/pull/5748) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_ARRAY_INSERT (#5748)\n\n- due to [`455ec1f`](https://github.com/tobymao/sqlglot/commit/455ec1f4f8aecb5435fa4cb2912bfc21db8dd44d) - parse and annotate type for bq JSON_KEYS *(PR [#5749](https://github.com/tobymao/sqlglot/pull/5749) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_KEYS (#5749)\n\n- due to [`59895fa`](https://github.com/tobymao/sqlglot/commit/59895faa23ebe1b27938c37a7b39df87de609844) - parse and annotate type for bq JSON_REMOVE *(PR [#5750](https://github.com/tobymao/sqlglot/pull/5750) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_REMOVE (#5750)\n\n- due to [`06d7df7`](https://github.com/tobymao/sqlglot/commit/06d7df7a05f2824cabf48e8d1e8a4ebca8fda496) - parse and annotate type for bq JSON_SET *(PR [#5751](https://github.com/tobymao/sqlglot/pull/5751) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_SET (#5751)\n\n- due to [`e72b341`](https://github.com/tobymao/sqlglot/commit/e72b3419c8a367caa0e5e80030979cd94e87a40d) - parse and annotate type for bq JSON_STRIP_NULLS *(PR [#5753](https://github.com/tobymao/sqlglot/pull/5753) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_STRIP_NULLS (#5753)\n\n- due to [`5de61a7`](https://github.com/tobymao/sqlglot/commit/5de61a7ab850d4e68fde4d76ee396d30d7bdef33) - parse and annotate type for bq JSON_EXTRACT_STRING_ARRAY *(PR [#5758](https://github.com/tobymao/sqlglot/pull/5758) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_EXTRACT_STRING_ARRAY (#5758)\n\n- due to [`36c9393`](https://github.com/tobymao/sqlglot/commit/36c93939575a19bd611269719c39d3d216be8cde) - parse and annotate type for bq JSON LAX funcs *(PR [#5760](https://github.com/tobymao/sqlglot/pull/5760) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON LAX funcs (#5760)\n\n- due to [`88862b5`](https://github.com/tobymao/sqlglot/commit/88862b56bc29c8a600b4d0e4693d5846d3a577ff) - annotate type for bq TO_JSON_STRING *(PR [#5762](https://github.com/tobymao/sqlglot/pull/5762) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq TO_JSON_STRING (#5762)\n\n- due to [`1c551d5`](https://github.com/tobymao/sqlglot/commit/1c551d5ed3315e314013c1f063deabd9d8613e5d) - parse and annotate type for bq TO_JSON *(PR [#5768](https://github.com/tobymao/sqlglot/pull/5768) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq TO_JSON (#5768)\n\n- due to [`1707f2d`](https://github.com/tobymao/sqlglot/commit/1707f2d7f9d3b58e8c216db638f8e572f9fe6f13) - annotate type for ABS *(PR [#5770](https://github.com/tobymao/sqlglot/pull/5770) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for ABS (#5770)\n\n- due to [`69acc51`](https://github.com/tobymao/sqlglot/commit/69acc5142b2d4f0b30832c350aa49f16d1adabef) - annotate type for bq IS_INF, IS_NAN *(PR [#5771](https://github.com/tobymao/sqlglot/pull/5771) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq IS_INF, IS_NAN (#5771)\n\n- due to [`0da2076`](https://github.com/tobymao/sqlglot/commit/0da207652331920416b29e2cc67bdc3c3f964466) - annotate type for bq CBRT *(PR [#5772](https://github.com/tobymao/sqlglot/pull/5772) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq CBRT (#5772)\n\n- due to [`a4968cb`](https://github.com/tobymao/sqlglot/commit/a4968cb5693670c1a2e9cd2c86404dd90fd76160) - annotate type for bq RAND *(PR [#5774](https://github.com/tobymao/sqlglot/pull/5774) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq RAND (#5774)\n\n- due to [`3e63350`](https://github.com/tobymao/sqlglot/commit/3e63350bd1d58b510cecd1a573d27be3fd2565ce) - parse and annotate type for bq ACOS *(PR [#5776](https://github.com/tobymao/sqlglot/pull/5776) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq ACOS (#5776)\n\n- due to [`2be9d01`](https://github.com/tobymao/sqlglot/commit/2be9d01830c778186dc274c94c6db0dd6c4116d1) - parse and annotate type for bq ACOSH *(PR [#5779](https://github.com/tobymao/sqlglot/pull/5779) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq ACOSH (#5779)\n\n- due to [`b77d3da`](https://github.com/tobymao/sqlglot/commit/b77d3da8f2548858d2b9d8590fcde83e1ec62b8a) - remove `\"EXCLUDE\" -> TokenType.EXCEPT` in DuckDB, Snowflake *(PR [#5766](https://github.com/tobymao/sqlglot/pull/5766) by [@treysp](https://github.com/treysp))*:\n\n  remove `\"EXCLUDE\" -> TokenType.EXCEPT` in DuckDB, Snowflake (#5766)\n\n- due to [`7da2f31`](https://github.com/tobymao/sqlglot/commit/7da2f31d6613f16585e98c3fa1f592c617ae40c9) - parse and annotate type for bq ASIN/H *(PR [#5783](https://github.com/tobymao/sqlglot/pull/5783) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq ASIN/H (#5783)\n\n- due to [`341ea83`](https://github.com/tobymao/sqlglot/commit/341ea83a07c707fdbf565b8d9ef4b9b6341ed1d5) - parse and annotate type for bq ATAN/H/2 *(PR [#5784](https://github.com/tobymao/sqlglot/pull/5784) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq ATAN/H/2 (#5784)\n\n- due to [`aa360cb`](https://github.com/tobymao/sqlglot/commit/aa360cb0e204aa056557ff8b15aa2d4f678430e6) - use regexp_like as it exists *(PR [#5781](https://github.com/tobymao/sqlglot/pull/5781) by [@jasonthomassql](https://github.com/jasonthomassql))*:\n\n  use regexp_like as it exists (#5781)\n\n- due to [`c2a1ad4`](https://github.com/tobymao/sqlglot/commit/c2a1ad4050771401a5b26bcadd90060e4527fbff) - parse and annotate type for bq COT/H *(PR [#5786](https://github.com/tobymao/sqlglot/pull/5786) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq COT/H (#5786)\n\n- due to [`316ae91`](https://github.com/tobymao/sqlglot/commit/316ae913d8b1a63f3071ebb1b826328108d74cef) - Added handling of UTC_DATE and exp.CurrentDate *(PR [#5785](https://github.com/tobymao/sqlglot/pull/5785) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*:\n\n  Added handling of UTC_DATE and exp.CurrentDate (#5785)\n\n- due to [`2c6d237`](https://github.com/tobymao/sqlglot/commit/2c6d23742ea9fcc2b9c784315d3d5364e360fea5) - parse and annotate type for bq CSC/H *(PR [#5787](https://github.com/tobymao/sqlglot/pull/5787) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq CSC/H (#5787)\n\n- due to [`8a35076`](https://github.com/tobymao/sqlglot/commit/8a350763c2337f6910a5f0e19af387ba488fcb70) - parse and annotate type for bq SEC/H *(PR [#5788](https://github.com/tobymao/sqlglot/pull/5788) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq SEC/H (#5788)\n\n- due to [`79901cb`](https://github.com/tobymao/sqlglot/commit/79901cb506737ae1932fa44a705858d2597ee587) - parse and annotate type for bq SIN\\H *(PR [#5790](https://github.com/tobymao/sqlglot/pull/5790) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq SIN\\H (#5790)\n\n- due to [`74fb547`](https://github.com/tobymao/sqlglot/commit/74fb5476def1b389da425885db56bd6592fd7f78) - parse and annotate type for bq RANGE_BUCKET *(PR [#5793](https://github.com/tobymao/sqlglot/pull/5793) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq RANGE_BUCKET (#5793)\n\n- due to [`eca65e8`](https://github.com/tobymao/sqlglot/commit/eca65e8b79f65850b014a4cb7913ba4a5861dbe9) - parse and annotate type for bq COSINE/EUCLIDEAN_DISTANCE *(PR [#5792](https://github.com/tobymao/sqlglot/pull/5792) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq COSINE/EUCLIDEAN_DISTANCE (#5792)\n\n- due to [`a180d3f`](https://github.com/tobymao/sqlglot/commit/a180d3f2f9f3938611027269028c03274aa1889c) - parse and annotate type for bq SAFE math funcs *(PR [#5797](https://github.com/tobymao/sqlglot/pull/5797) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq SAFE math funcs (#5797)\n\n- due to [`fc7ad7a`](https://github.com/tobymao/sqlglot/commit/fc7ad7a4d953424b56542eacfe1835f5789921c7) - parse ALTER SESSION  *(PR [#5734](https://github.com/tobymao/sqlglot/pull/5734) by [@tekumara](https://github.com/tekumara))*:\n\n  parse ALTER SESSION  (#5734)\n\n- due to [`8ec1a6c`](https://github.com/tobymao/sqlglot/commit/8ec1a6cf5a8edc2d834c713ce0fd8d87237f11ed) - annotate type for bq STRING_AGG *(PR [#5798](https://github.com/tobymao/sqlglot/pull/5798) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq STRING_AGG (#5798)\n\n- due to [`dd97bfa`](https://github.com/tobymao/sqlglot/commit/dd97bfa1dc2f86b727c55b06b3c54b18c02e360d) - annotate type for bq DATETIME_TRUNC *(PR [#5799](https://github.com/tobymao/sqlglot/pull/5799) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq DATETIME_TRUNC (#5799)\n\n- due to [`d3e9dda`](https://github.com/tobymao/sqlglot/commit/d3e9dda183695dd1e4a9832a6671bccc6db561a0) - annotate type for bq GENERATE_UUID *(commit by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq GENERATE_UUID\n\n- due to [`3726b33`](https://github.com/tobymao/sqlglot/commit/3726b33bb6b4ab286617f510e96e1fbd27c429f3) - support nulls_first arg for array_sort *(PR [#5802](https://github.com/tobymao/sqlglot/pull/5802) by [@treysp](https://github.com/treysp))*:\n\n  support nulls_first arg for array_sort (#5802)\n\n- due to [`cf1d1e3`](https://github.com/tobymao/sqlglot/commit/cf1d1e3e0ef9e6cd1b1c6128c63ddf06c30f1339) - annotate type for snowflake's REVERSE function *(PR [#5803](https://github.com/tobymao/sqlglot/pull/5803) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for snowflake's REVERSE function (#5803)\n\n- due to [`ad0b407`](https://github.com/tobymao/sqlglot/commit/ad0b407098e1611d4fc0e1f0916511337b9aefdb) - Mark 'BEGIN' as TokenType.BEGIN for transactions *(PR [#5826](https://github.com/tobymao/sqlglot/pull/5826) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Mark 'BEGIN' as TokenType.BEGIN for transactions (#5826)\n\n- due to [`0198282`](https://github.com/tobymao/sqlglot/commit/0198282a82bbf3e81476e164718d63fd1210acdc) - : Update tests for concat string function *(PR [#5809](https://github.com/tobymao/sqlglot/pull/5809) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  : Update tests for concat string function (#5809)\n\n- due to [`db2c430`](https://github.com/tobymao/sqlglot/commit/db2c4303237a1244070c359245c398a724df6de2) - annoate the \"contains\" function *(PR [#5829](https://github.com/tobymao/sqlglot/pull/5829) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  annoate the \"contains\" function (#5829)\n\n- due to [`9c8a600`](https://github.com/tobymao/sqlglot/commit/9c8a6001f41816035f391d046eb9692d6f13cefc) - correct parsing of TO_VARCHAR *(PR [#5840](https://github.com/tobymao/sqlglot/pull/5840) by [@geooo109](https://github.com/geooo109))*:\n\n  correct parsing of TO_VARCHAR (#5840)\n\n- due to [`1e9aef1`](https://github.com/tobymao/sqlglot/commit/1e9aef1bb20f4dc5e9c03d59cb3165c235c11ce1) - convert NULL annotations to UNKNOWN *(PR [#5842](https://github.com/tobymao/sqlglot/pull/5842) by [@georgesittas](https://github.com/georgesittas))*:\n\n  convert NULL annotations to UNKNOWN (#5842)\n\n- due to [`44c9e70`](https://github.com/tobymao/sqlglot/commit/44c9e70bd8c9421035eb0e87e4286061ec5d2fa8) - add tests for snowflake STARTSWITH function *(PR [#5847](https://github.com/tobymao/sqlglot/pull/5847) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  add tests for snowflake STARTSWITH function (#5847)\n\n- due to [`0779c2d`](https://github.com/tobymao/sqlglot/commit/0779c2d4e8ce0228592de6882763940783fa5e87) - support BIT_X aggregates again for duckdb, postgres *(PR [#5851](https://github.com/tobymao/sqlglot/pull/5851) by [@georgesittas](https://github.com/georgesittas))*:\n\n  support BIT_X aggregates again for duckdb, postgres (#5851)\n\n- due to [`c50d6e3`](https://github.com/tobymao/sqlglot/commit/c50d6e3c7b96f00d27c34a02c8e0dced21e6c373) - annotate type for snowflake LEFT, RIGHT and SUBSTRING functions *(PR [#5849](https://github.com/tobymao/sqlglot/pull/5849) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  annotate type for snowflake LEFT, RIGHT and SUBSTRING functions (#5849)\n\n- due to [`e441e16`](https://github.com/tobymao/sqlglot/commit/e441e16991626c2da2d38bc9c3a2b408e3f773bd) - make dump/pickling non-recursive to avoid hitting stack limits *(PR [#5850](https://github.com/tobymao/sqlglot/pull/5850) by [@tobymao](https://github.com/tobymao))*:\n\n  make dump/pickling non-recursive to avoid hitting stack limits (#5850)\n\n- due to [`b128339`](https://github.com/tobymao/sqlglot/commit/b12833977e2a395712481cf11e293fdbd70fd4ce) - annotate and add tests for snowflake LENGTH and LOWER functions *(PR [#5856](https://github.com/tobymao/sqlglot/pull/5856) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  annotate and add tests for snowflake LENGTH and LOWER functions (#5856)\n\n- due to [`134957a`](https://github.com/tobymao/sqlglot/commit/134957af11c55a4ab16f58d0725d6bb8ab23eb28) - annotate types for Snowflake TRIM function *(PR [#5811](https://github.com/tobymao/sqlglot/pull/5811) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake TRIM function (#5811)\n\n- due to [`d3cd6bf`](https://github.com/tobymao/sqlglot/commit/d3cd6bf6e5fbaa490868ee3cd2cc99dd5e40a396) - Annotate and add tests for snowflake REPLACE and SPACE functions *(PR [#5871](https://github.com/tobymao/sqlglot/pull/5871) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate and add tests for snowflake REPLACE and SPACE functions (#5871)\n\n- due to [`96ae7a3`](https://github.com/tobymao/sqlglot/commit/96ae7a3bcbf9de1932150baa0bd704d4ce05c9f7) - Annotate and add tests for snowflake REPEAT and SPLIT functions *(PR [#5875](https://github.com/tobymao/sqlglot/pull/5875) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate and add tests for snowflake REPEAT and SPLIT functions (#5875)\n\n- due to [`f2d3bf7`](https://github.com/tobymao/sqlglot/commit/f2d3bf74e804e5a5e2ac6ca94210ba04df07e7f3) - annotate types for Snowflake UUID_STRING function *(PR [#5881](https://github.com/tobymao/sqlglot/pull/5881) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake UUID_STRING function (#5881)\n\n- due to [`ec80ff3`](https://github.com/tobymao/sqlglot/commit/ec80ff34957c3e3f80c44175383b06cf72988a68) - make dump a list instead of a nested dict to avoid all recursion errors *(PR [#5885](https://github.com/tobymao/sqlglot/pull/5885) by [@tobymao](https://github.com/tobymao))*:\n\n  make dump a list instead of a nested dict to avoid all recursion errors (#5885)\n\n- due to [`2fdaccd`](https://github.com/tobymao/sqlglot/commit/2fdaccd1a9045bda3d529025a4706c397b8a836f) - annotate types for Snowflake SHA1, SHA2 functions *(PR [#5884](https://github.com/tobymao/sqlglot/pull/5884) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake SHA1, SHA2 functions (#5884)\n\n- due to [`faba309`](https://github.com/tobymao/sqlglot/commit/faba30905390e5efaf0ba9a05aab9ac2724b1b85) - annotate types for Snowflake AI_AGG function *(PR [#5894](https://github.com/tobymao/sqlglot/pull/5894) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake AI_AGG function (#5894)\n\n- due to [`304bec5`](https://github.com/tobymao/sqlglot/commit/304bec5f7342501ad28ea4cd0a4b9aa092f2192f) - Annotate snowflake MD5 functions *(PR [#5883](https://github.com/tobymao/sqlglot/pull/5883) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate snowflake MD5 functions (#5883)\n\n- due to [`c0180ec`](https://github.com/tobymao/sqlglot/commit/c0180ec163a43836fed754efcb6f26ad37cdae50) - annotate types for Snowflake AI_SUMMARIZE_AGG function *(PR [#5902](https://github.com/tobymao/sqlglot/pull/5902) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake AI_SUMMARIZE_AGG function (#5902)\n\n- due to [`f5409df`](https://github.com/tobymao/sqlglot/commit/f5409df64ed6069880669878db687e4b98c3e280) - use column name in struct type annotation *(PR [#5903](https://github.com/tobymao/sqlglot/pull/5903) by [@georgesittas](https://github.com/georgesittas))*:\n\n  use column name in struct type annotation (#5903)\n\n- due to [`5a973e9`](https://github.com/tobymao/sqlglot/commit/5a973e9a88fa7f522a9bf91dc60fb0f6effef53d) - annotate types for Snowflake AI_CLASSIFY function *(PR [#5909](https://github.com/tobymao/sqlglot/pull/5909) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake AI_CLASSIFY function (#5909)\n\n- due to [`2d0d908`](https://github.com/tobymao/sqlglot/commit/2d0d908b5bbc32ff3bc92eb1ae9fc6e5ac3409bc) - produce TableAlias instead of Alias for USING in merge builder *(PR [#5911](https://github.com/tobymao/sqlglot/pull/5911) by [@georgesittas](https://github.com/georgesittas))*:\n\n  produce TableAlias instead of Alias for USING in merge builder (#5911)\n\n- due to [`f4ad258`](https://github.com/tobymao/sqlglot/commit/f4ad25882951de4e4442dfd5189a56d5a1c5e630) - Annotate types for Snowflake BASE64_DECODE_BINARY function *(PR [#5917](https://github.com/tobymao/sqlglot/pull/5917) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate types for Snowflake BASE64_DECODE_BINARY function (#5917)\n\n- due to [`6d0e3f8`](https://github.com/tobymao/sqlglot/commit/6d0e3f8dcae7ed1a7659ece69b1f94cec5e7300e) - Add parser support to ilike like function versions. *(PR [#5915](https://github.com/tobymao/sqlglot/pull/5915) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add parser support to ilike like function versions. (#5915)\n\n- due to [`22c7ed7`](https://github.com/tobymao/sqlglot/commit/22c7ed7734b41ca544bb67bcc1ca4151f6d5f05f) - parse tuple *(PR [#5920](https://github.com/tobymao/sqlglot/pull/5920) by [@geooo109](https://github.com/geooo109))*:\n\n  parse tuple (#5920)\n\n- due to [`fc5624e`](https://github.com/tobymao/sqlglot/commit/fc5624eca43d2855ac350c92d85b184a6893d5ca) - annotate types for Snowflake ASCII function *(PR [#5926](https://github.com/tobymao/sqlglot/pull/5926) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake ASCII function (#5926)\n\n- due to [`4e81690`](https://github.com/tobymao/sqlglot/commit/4e8169045edcaa28ae43abeb07370df63846fbfd) - annotate type for Snowflake COLLATE function *(PR [#5931](https://github.com/tobymao/sqlglot/pull/5931) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake COLLATE function (#5931)\n\n- due to [`f07d35d`](https://github.com/tobymao/sqlglot/commit/f07d35d29104c6203efaab738118d1903614b83c) - annotate type for Snowflake CHR function *(PR [#5929](https://github.com/tobymao/sqlglot/pull/5929) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake CHR function (#5929)\n\n- due to [`f8c0ee4`](https://github.com/tobymao/sqlglot/commit/f8c0ee4d3c1a4d4a92b897d1cc85f9904c8e566b) - Add function and annotate snowflake hex decode string and binary functions *(PR [#5928](https://github.com/tobymao/sqlglot/pull/5928) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add function and annotate snowflake hex decode string and binary functions (#5928)\n\n- due to [`66f9501`](https://github.com/tobymao/sqlglot/commit/66f9501d76d087798bad93e578273ab2a45e2575) - annotate types for Snowflake BIT_LENGTH function *(PR [#5927](https://github.com/tobymao/sqlglot/pull/5927) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake BIT_LENGTH function (#5927)\n\n- due to [`7878437`](https://github.com/tobymao/sqlglot/commit/78784370712df65a2e1e79a1c2b441131ed7222a) - annotate snowflake's `BASE64_DECODE_STRING`, `BASE64_ENCODE` *(PR [#5922](https://github.com/tobymao/sqlglot/pull/5922) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  annotate snowflake's `BASE64_DECODE_STRING`, `BASE64_ENCODE` (#5922)\n\n- due to [`9bcad04`](https://github.com/tobymao/sqlglot/commit/9bcad040bd51dd03821c68eea1a73534fc7a81b7) - Annotate type for HEX ENCODE function. *(PR [#5936](https://github.com/tobymao/sqlglot/pull/5936) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for HEX ENCODE function. (#5936)\n\n- due to [`590928f`](https://github.com/tobymao/sqlglot/commit/590928f4637306e8cf3f1302d5dd5d5dbc76e7e0) - annotate type for Snowflake INITCAP function *(PR [#5941](https://github.com/tobymao/sqlglot/pull/5941) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake INITCAP function (#5941)\n\n- due to [`ac04de1`](https://github.com/tobymao/sqlglot/commit/ac04de1944c7a976406581b489b3cf9b11dafb77) - annotate type for Snowflake EDITDISTANCE function *(PR [#5940](https://github.com/tobymao/sqlglot/pull/5940) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake EDITDISTANCE function (#5940)\n\n- due to [`9e28af8`](https://github.com/tobymao/sqlglot/commit/9e28af8a52ced951ecf7f4e85a6305e20a13de1f) - Annotate type for snowflake COMPRESS function *(PR [#5938](https://github.com/tobymao/sqlglot/pull/5938) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake COMPRESS function (#5938)\n\n- due to [`7f13eaf`](https://github.com/tobymao/sqlglot/commit/7f13eaf7769a3381a56c9209af590835be2f95cd) - Annotate type for snowflake DECOMPRESS_BINARY function *(PR [#5945](https://github.com/tobymao/sqlglot/pull/5945) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake DECOMPRESS_BINARY function (#5945)\n\n- due to [`be12b29`](https://github.com/tobymao/sqlglot/commit/be12b29b5a7bd6d6e09dbd8c17086bd77c19abc0) - Annotate type for snowflake DECOMPRESS_STRING function *(PR [#5947](https://github.com/tobymao/sqlglot/pull/5947) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake DECOMPRESS_STRING function (#5947)\n\n- due to [`1573fef`](https://github.com/tobymao/sqlglot/commit/1573fefac27b5b1215e3d458f8ccf1b9dadbb772) - annotate types for Snowflake JAROWINKLER_SIMILARITY function *(PR [#5950](https://github.com/tobymao/sqlglot/pull/5950) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake JAROWINKLER_SIMILARITY function (#5950)\n\n- due to [`883c6ab`](https://github.com/tobymao/sqlglot/commit/883c6abe589865f478d95604e8d670e57afd04af) - annotate type for Snowflake COLLATION function *(PR [#5939](https://github.com/tobymao/sqlglot/pull/5939) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake COLLATION function (#5939)\n\n- due to [`68473ac`](https://github.com/tobymao/sqlglot/commit/68473ac3ec8dc76512dc76819892a1b0324c7ddc) - Annotate type for snowflake PARSE_URL function *(PR [#5962](https://github.com/tobymao/sqlglot/pull/5962) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake PARSE_URL function (#5962)\n\n- due to [`b015a9d`](https://github.com/tobymao/sqlglot/commit/b015a9d944d0a87069a7750ad74953c399d7da34) - annotate type for Snowflake REGEXP_INSTR function *(commit by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake REGEXP_INSTR function\n\n- due to [`1f29ba7`](https://github.com/tobymao/sqlglot/commit/1f29ba710f4213beb1a2f993244d7d824f3536ce) - annotate type for Snowflake PARSE_IP function *(PR [#5961](https://github.com/tobymao/sqlglot/pull/5961) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake PARSE_IP function (#5961)\n\n- due to [`bf45d5d`](https://github.com/tobymao/sqlglot/commit/bf45d5d3cb0c0f380824019eb32ec29049268a61) - annotate types for Snowflake RTRIMMED_LENGTH function *(PR [#5968](https://github.com/tobymao/sqlglot/pull/5968) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake RTRIMMED_LENGTH function (#5968)\n\n- due to [`13caa69`](https://github.com/tobymao/sqlglot/commit/13caa6991f003ad7abb590073451e591b6fd888c) - Annotate type for snowflake POSITION function *(PR [#5964](https://github.com/tobymao/sqlglot/pull/5964) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake POSITION function (#5964)\n\n- due to [`13a30df`](https://github.com/tobymao/sqlglot/commit/13a30dfa37096df5bfc2c31538325c40a49f7917) - Annotate type for snowflake TRY_BASE64_DECODE_BINARY function *(PR [#5972](https://github.com/tobymao/sqlglot/pull/5972) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TRY_BASE64_DECODE_BINARY function (#5972)\n\n- due to [`1f5fdd7`](https://github.com/tobymao/sqlglot/commit/1f5fdd799c047de167a4572f7ac26b7ad92167f2) - Annotate type for snowflake TRY_BASE64_DECODE_STRING function *(PR [#5974](https://github.com/tobymao/sqlglot/pull/5974) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TRY_BASE64_DECODE_STRING function (#5974)\n\n- due to [`324e82f`](https://github.com/tobymao/sqlglot/commit/324e82fe1fb11722f91341010602a743b151e055) - Annotate type for snowflake TRY_HEX_DECODE_BINARY function *(PR [#5975](https://github.com/tobymao/sqlglot/pull/5975) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TRY_HEX_DECODE_BINARY function (#5975)\n\n- due to [`6caf99d`](https://github.com/tobymao/sqlglot/commit/6caf99d556a3357ffaa6c294a9babcd30dd5fac5) - Annotate type for snowflake TRY_HEX_DECODE_STRING function *(PR [#5976](https://github.com/tobymao/sqlglot/pull/5976) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TRY_HEX_DECODE_STRING function (#5976)\n\n- due to [`73186a8`](https://github.com/tobymao/sqlglot/commit/73186a812ce422c108ee81b3de11da6ee9a9e902) - annotate type for Snowflake REGEXP_COUNT function *(PR [#5963](https://github.com/tobymao/sqlglot/pull/5963) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake REGEXP_COUNT function (#5963)\n\n- due to [`c3bdb3c`](https://github.com/tobymao/sqlglot/commit/c3bdb3cd1af1809ed82be0ae40744d9fffc8ce18) - array start index is 1, support array_flatten, fixes [#5983](https://github.com/tobymao/sqlglot/pull/5983) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  array start index is 1, support array_flatten, fixes #5983\n\n- due to [`244fb48`](https://github.com/tobymao/sqlglot/commit/244fb48fc9c4776f427c08b825d139b1c172fd26) - annotate type for Snowflake SPLIT_PART function *(PR [#5988](https://github.com/tobymao/sqlglot/pull/5988) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake SPLIT_PART function (#5988)\n\n- due to [`0d772e0`](https://github.com/tobymao/sqlglot/commit/0d772e0b9d687b24d49203c05d7a90cc1dce02d5) - add ast node for `DIRECTORY` source *(PR [#5990](https://github.com/tobymao/sqlglot/pull/5990) by [@georgesittas](https://github.com/georgesittas))*:\n\n  add ast node for `DIRECTORY` source (#5990)\n\n- due to [`3c7b5c0`](https://github.com/tobymao/sqlglot/commit/3c7b5c0e2dc071b7b9f6da308ba58a3a43da93dc) - Annotate type for snowflake SOUNDEX_P123 function *(PR [#5987](https://github.com/tobymao/sqlglot/pull/5987) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake SOUNDEX_P123 function (#5987)\n\n- due to [`f25e42e`](https://github.com/tobymao/sqlglot/commit/f25e42e3f5b3b7b671bd724ba7b09a9b07d13995) - annotate type for Snowflake REGEXP_INSTR function *(PR [#5978](https://github.com/tobymao/sqlglot/pull/5978) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake REGEXP_INSTR function (#5978)\n\n- due to [`13cb26e`](https://github.com/tobymao/sqlglot/commit/13cb26e2f29373538d60a8124ddebf95fd22a8d8) - annotate type for Snowflake REGEXP_SUBSTR_ALL function *(PR [#5979](https://github.com/tobymao/sqlglot/pull/5979) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake REGEXP_SUBSTR_ALL function (#5979)\n\n- due to [`4ce683e`](https://github.com/tobymao/sqlglot/commit/4ce683eb8ac5716a334cbd7625438b9f89623c7a) - Annotate type for snowflake UNICODE function *(PR [#5993](https://github.com/tobymao/sqlglot/pull/5993) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake UNICODE function (#5993)\n\n- due to [`c7657fb`](https://github.com/tobymao/sqlglot/commit/c7657fbd27a4350c424ef65947471ab9ec086831) - remove `unalias_group_by` transformation since it is unsafe *(PR [#5997](https://github.com/tobymao/sqlglot/pull/5997) by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove `unalias_group_by` transformation since it is unsafe (#5997)\n\n- due to [`587196c`](https://github.com/tobymao/sqlglot/commit/587196c9c2d122f73f9deb7e87c2831f27f6ed02) - Annotate type for snowflake STRTOK_TO_ARRAY function *(PR [#5994](https://github.com/tobymao/sqlglot/pull/5994) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake STRTOK_TO_ARRAY function (#5994)\n\n- due to [`bced710`](https://github.com/tobymao/sqlglot/commit/bced71084ffb3a8f7a11db843777f05b68f367da) - Annotate type for snowflake STRTOK function. *(PR [#5991](https://github.com/tobymao/sqlglot/pull/5991) by [@georgesittas](https://github.com/georgesittas))*:\n\n  Annotate type for snowflake STRTOK function. (#5991)\n\n- due to [`be1cdc8`](https://github.com/tobymao/sqlglot/commit/be1cdc81b511d462b710b50941d5c2770d901e91) - Fix roundtrip of ~ operator *(PR [#6017](https://github.com/tobymao/sqlglot/pull/6017) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix roundtrip of ~ operator (#6017)\n\n- due to [`74a13f2`](https://github.com/tobymao/sqlglot/commit/74a13f2a548b9cd41061e835cb3cd9dd2a5a9fb3) - Annotate type for snowflake DIV0 and DIVNULL functions *(PR [#6008](https://github.com/tobymao/sqlglot/pull/6008) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake DIV0 and DIVNULL functions (#6008)\n\n- due to [`fec2b31`](https://github.com/tobymao/sqlglot/commit/fec2b31956f2debdad7c53744a577894cd8d747c) - Annotate type for snowflake SEARCH function *(PR [#5985](https://github.com/tobymao/sqlglot/pull/5985) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake SEARCH function (#5985)\n\n- due to [`27a76cd`](https://github.com/tobymao/sqlglot/commit/27a76cdfe4212f16f945521eb3997580eacf1d61) - Annotate type for snowflake COT, SIN and TAN functions *(PR [#6022](https://github.com/tobymao/sqlglot/pull/6022) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake COT, SIN and TAN functions (#6022)\n\n- due to [`0911276`](https://github.com/tobymao/sqlglot/commit/091127663ab4cb94b02be5aa40c6a46dd7f89243) - annotate type for Snowflake EXP function *(PR [#6007](https://github.com/tobymao/sqlglot/pull/6007) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake EXP function (#6007)\n\n- due to [`a96d50e`](https://github.com/tobymao/sqlglot/commit/a96d50e14bed5e87ff2dce9c545e0c48897b64d6) - annotate type for Snowflake COSH function *(PR [#6006](https://github.com/tobymao/sqlglot/pull/6006) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake COSH function (#6006)\n\n- due to [`4df58e0`](https://github.com/tobymao/sqlglot/commit/4df58e0f0b8985590fb29a8ab6ba0ced987ac5b9) - annotate type for Snowflake DEGREES function *(PR [#6027](https://github.com/tobymao/sqlglot/pull/6027) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake DEGREES function (#6027)\n\n- due to [`db71a20`](https://github.com/tobymao/sqlglot/commit/db71a2023aaeca2ffda782ae7b91fdee356c402e) - annotate type for Snowflake COS function *(PR [#6028](https://github.com/tobymao/sqlglot/pull/6028) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake COS function (#6028)\n\n- due to [`5dd2ed3`](https://github.com/tobymao/sqlglot/commit/5dd2ed3c69cf9e8c3e327297e0cc932f0954e108) - bump sqlglotrs to 0.7.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.7.0\n\n- due to [`6beb917`](https://github.com/tobymao/sqlglot/commit/6beb9172dffd0aaea46b75477485060737e774b9) - Annotate type for snowflake ROUND function *(PR [#6032](https://github.com/tobymao/sqlglot/pull/6032) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake ROUND function (#6032)\n\n- due to [`0939d69`](https://github.com/tobymao/sqlglot/commit/0939d69223a860581b1c30cc2f762294946b93f3) - move odbc date literal handling in t-sql closes [#6037](https://github.com/tobymao/sqlglot/pull/6037) *(PR [#6044](https://github.com/tobymao/sqlglot/pull/6044) by [@georgesittas](https://github.com/georgesittas))*:\n\n  move odbc date literal handling in t-sql closes #6037 (#6044)\n\n- due to [`56c8b3b`](https://github.com/tobymao/sqlglot/commit/56c8b3bbff7451b9049e1a168716bb41222a86ed) - Support CHANGE COLUMN statements in Hive and CHANGE/ALTER COLUMN statements in Spark *(PR [#6004](https://github.com/tobymao/sqlglot/pull/6004) by [@tsamaras](https://github.com/tsamaras))*:\n\n  Support CHANGE COLUMN statements in Hive and CHANGE/ALTER COLUMN statements in Spark (#6004)\n\n- due to [`7ac01c2`](https://github.com/tobymao/sqlglot/commit/7ac01c2ae9bc4375efb63c60e3221e85088fdd1f) - bump sqlglotrs to 0.7.1 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.7.1\n\n- due to [`6f31b86`](https://github.com/tobymao/sqlglot/commit/6f31b86599258afe156aa3d9ccc42389cac37021) - Annotate type for snowflake FLOOR function *(PR [#6030](https://github.com/tobymao/sqlglot/pull/6030) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake FLOOR function (#6030)\n\n- due to [`cecab2f`](https://github.com/tobymao/sqlglot/commit/cecab2fd66d578ddc765b5fd0e7b155971280a0c) - annotate type for Snowflake ATANH function *(PR [#6054](https://github.com/tobymao/sqlglot/pull/6054) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake ATANH function (#6054)\n\n- due to [`08339a9`](https://github.com/tobymao/sqlglot/commit/08339a902138211f67cfb009d2576b22ea8d8e42) - annotate type for Snowflake FACTORIAL function *(PR [#6053](https://github.com/tobymao/sqlglot/pull/6053) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake FACTORIAL function (#6053)\n\n- due to [`9060f60`](https://github.com/tobymao/sqlglot/commit/9060f603818db863b7570a2c3c50c3eb88155e76) - Annotate type for snowflake ATAN2 function. *(PR [#6060](https://github.com/tobymao/sqlglot/pull/6060) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake ATAN2 function. (#6060)\n\n- due to [`b3eb2e4`](https://github.com/tobymao/sqlglot/commit/b3eb2e4ca6177ee61b27675e8ec8b4815587df31) - annotate type for Snowflake SINH function *(PR [#6052](https://github.com/tobymao/sqlglot/pull/6052) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake SINH function (#6052)\n\n- due to [`157d2fa`](https://github.com/tobymao/sqlglot/commit/157d2fa06ab110ebc760aa7567d7fda801a5ced9) - annotate type for Snowflake CEIL function *(PR [#6051](https://github.com/tobymao/sqlglot/pull/6051) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake CEIL function (#6051)\n\n- due to [`e7833de`](https://github.com/tobymao/sqlglot/commit/e7833de9744a4aa69d244285e7f6f7281af178ba) - support DELETE with USING and multiple VALUES *(PR [#6072](https://github.com/tobymao/sqlglot/pull/6072) by [@geooo109](https://github.com/geooo109))*:\n\n  support DELETE with USING and multiple VALUES (#6072)\n\n- due to [`354140d`](https://github.com/tobymao/sqlglot/commit/354140d0a279f317439bdb247e1ab9578f9a035d) - Annotate type for snowflake TANH and ATAN functions *(PR [#6069](https://github.com/tobymao/sqlglot/pull/6069) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TANH and ATAN functions (#6069)\n\n- due to [`c67276d`](https://github.com/tobymao/sqlglot/commit/c67276d5be970252e14d1817d8498fc9985222d9) - Annotate type for snowflake RADIANS function. *(PR [#6064](https://github.com/tobymao/sqlglot/pull/6064) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake RADIANS function. (#6064)\n\n- due to [`2238ac2`](https://github.com/tobymao/sqlglot/commit/2238ac27478bd272ba39928bbec1075c4191ee1b) - transpile timestamp literals in datediff fixes [#6083](https://github.com/tobymao/sqlglot/pull/6083) *(PR [#6086](https://github.com/tobymao/sqlglot/pull/6086) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile timestamp literals in datediff fixes #6083 (#6086)\n\n- due to [`c49ba0e`](https://github.com/tobymao/sqlglot/commit/c49ba0eee21f7776703d2a26c6641b4a32a1cff7) - Annotate type for snowflake WIDTH_BUCKET function *(PR [#6078](https://github.com/tobymao/sqlglot/pull/6078) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake WIDTH_BUCKET function (#6078)\n\n- due to [`fbc1f13`](https://github.com/tobymao/sqlglot/commit/fbc1f1335eecaaaab4fc93ddbb74611a4df0aea7) - annotate type for Snowflake CONVERT_TIMEZONE function *(PR [#6076](https://github.com/tobymao/sqlglot/pull/6076) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake CONVERT_TIMEZONE function (#6076)\n\n- due to [`70e977c`](https://github.com/tobymao/sqlglot/commit/70e977c5edfb495529d38a9096cb40762a9b5d7b) - annotate type for Snowflake DATE_TRUNC function *(PR [#6080](https://github.com/tobymao/sqlglot/pull/6080) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake DATE_TRUNC function (#6080)\n\n- due to [`e9cf146`](https://github.com/tobymao/sqlglot/commit/e9cf146a4a6cd78f6a59c195e7ec12240b836e5e) - annotate type for Snowflake DATE_PART function *(PR [#6079](https://github.com/tobymao/sqlglot/pull/6079) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake DATE_PART function (#6079)\n\n- due to [`5109890`](https://github.com/tobymao/sqlglot/commit/510989043d18baa17502a971262462814a2eb5be) - VALUES with ORDER BY/LIMIT/OFFSET *(PR [#6094](https://github.com/tobymao/sqlglot/pull/6094) by [@geooo109](https://github.com/geooo109))*:\n\n  VALUES with ORDER BY/LIMIT/OFFSET (#6094)\n\n- due to [`6fe5824`](https://github.com/tobymao/sqlglot/commit/6fe58247888c326093618657fb027e482d82d107) - Annotate type for hour, minute, second functions *(PR [#6100](https://github.com/tobymao/sqlglot/pull/6100) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for hour, minute, second functions (#6100)\n\n- due to [`a4d07a0`](https://github.com/tobymao/sqlglot/commit/a4d07a07eefbdaf88d30df2310a9533afdc75a82) - Annotate type for snowflake EXTRACT function *(PR [#6099](https://github.com/tobymao/sqlglot/pull/6099) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake EXTRACT function (#6099)\n\n- due to [`483770b`](https://github.com/tobymao/sqlglot/commit/483770b816fab14b7eb7222974ed2c99045302a7) - Annotate type for snowflake TIME_SLICE function *(PR [#6098](https://github.com/tobymao/sqlglot/pull/6098) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TIME_SLICE function (#6098)\n\n- due to [`06f40f9`](https://github.com/tobymao/sqlglot/commit/06f40f900ce693ba4203514e422cba8cda0dbb07) - don't simplify x XOR x due to NULL semantics *(PR [#6115](https://github.com/tobymao/sqlglot/pull/6115) by [@geooo109](https://github.com/geooo109))*:\n\n  don't simplify x XOR x due to NULL semantics (#6115)\n\n- due to [`c286cee`](https://github.com/tobymao/sqlglot/commit/c286cee54ab93e1fd0b3be658f7e767e3e00afe9) - Annotate type for snowflake MONTHNAME function *(PR [#6116](https://github.com/tobymao/sqlglot/pull/6116) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake MONTHNAME function (#6116)\n\n- due to [`1a34788`](https://github.com/tobymao/sqlglot/commit/1a34788025bdd8a018c4bb9214f72152e68bdd14) - Annotate type for snowflake PREVIOUS_DAY function *(PR [#6117](https://github.com/tobymao/sqlglot/pull/6117) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake PREVIOUS_DAY function (#6117)\n\n- due to [`533faf8`](https://github.com/tobymao/sqlglot/commit/533faf87b6df351070b565dd1fe9ce4e13b6c46e) - transpile duckdb `READ_PARQUET` to `parquet.<path>` closes [#6122](https://github.com/tobymao/sqlglot/pull/6122) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile duckdb `READ_PARQUET` to `parquet.<path>` closes #6122\n\n- due to [`cd4e557`](https://github.com/tobymao/sqlglot/commit/cd4e557658b1384f36c9a1ef9da5a09b893229b1) - Annotate type for snowflake RANDOM function *(PR [#6124](https://github.com/tobymao/sqlglot/pull/6124) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  Annotate type for snowflake RANDOM function (#6124)\n\n- due to [`fe63d84`](https://github.com/tobymao/sqlglot/commit/fe63d84f1bd365b22221f348d79c0546aa3118b0) - annotate type for Snowflake MONTHS_BETWEEN function *(PR [#6120](https://github.com/tobymao/sqlglot/pull/6120) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for Snowflake MONTHS_BETWEEN function (#6120)\n\n- due to [`598d09b`](https://github.com/tobymao/sqlglot/commit/598d09b036d938c90a44955d67175ea868090ba2) - annotate type for Snowflake DATEADD function *(PR [#6089](https://github.com/tobymao/sqlglot/pull/6089) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake DATEADD function (#6089)\n\n- due to [`b98bcee`](https://github.com/tobymao/sqlglot/commit/b98bcee148ba426816e166dbfa9ba8e0979aae21) - Annotate type for snowflake next_day function *(PR [#6125](https://github.com/tobymao/sqlglot/pull/6125) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Annotate type for snowflake next_day function (#6125)\n\n- due to [`e2129c6`](https://github.com/tobymao/sqlglot/commit/e2129c6766ca1f10ff6663bec98be984abb33c91) - Do not consider BIT_COUNT an aggregate function *(PR [#6135](https://github.com/tobymao/sqlglot/pull/6135) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Do not consider BIT_COUNT an aggregate function (#6135)\n\n- due to [`d136414`](https://github.com/tobymao/sqlglot/commit/d136414e520270ac9ab2fd8e9df4691d269b3af0) - avoid simplifying AND with NULL *(PR [#6148](https://github.com/tobymao/sqlglot/pull/6148) by [@geooo109](https://github.com/geooo109))*:\n\n  avoid simplifying AND with NULL (#6148)\n\n- due to [`3a334f3`](https://github.com/tobymao/sqlglot/commit/3a334f376b9766b6b99fdf195ae763bb44976ec4) - annotate type for boolnot snowflake function *(PR [#6141](https://github.com/tobymao/sqlglot/pull/6141) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate type for boolnot snowflake function (#6141)\n\n- due to [`99949cc`](https://github.com/tobymao/sqlglot/commit/99949ccd3ff81b524edeae437d874b86250dbb5b) - avoid needlessly copying in lineage *(PR [#6150](https://github.com/tobymao/sqlglot/pull/6150) by [@georgesittas](https://github.com/georgesittas))*:\n\n  avoid needlessly copying in lineage (#6150)\n\n- due to [`4e36f9d`](https://github.com/tobymao/sqlglot/commit/4e36f9dd6a854b378c9bbf6b2e9811045affc63d) - Annotate type for snowflake TIMEADD function *(PR [#6134](https://github.com/tobymao/sqlglot/pull/6134) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TIMEADD function (#6134)\n\n- due to [`5242cdd`](https://github.com/tobymao/sqlglot/commit/5242cddf487e367e7f543ca19d9bccae858f36ac) - annotate type for bq LENGTH *(commit by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq LENGTH\n\n- due to [`0fc6dbf`](https://github.com/tobymao/sqlglot/commit/0fc6dbf2e7b611fa0977e3c3e61be1cc84bcf4a9) - add GREATEST_IGNORE_NULLS function support *(PR [#6161](https://github.com/tobymao/sqlglot/pull/6161) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add GREATEST_IGNORE_NULLS function support (#6161)\n\n- due to [`d382a31`](https://github.com/tobymao/sqlglot/commit/d382a3106d5ce2e9b75527aacd4a37d1f8e16d18) - simplify double negation only if the inner expr is BOOLEAN *(PR [#6151](https://github.com/tobymao/sqlglot/pull/6151) by [@geooo109](https://github.com/geooo109))*:\n\n  simplify double negation only if the inner expr is BOOLEAN (#6151)\n\n- due to [`bcf6c89`](https://github.com/tobymao/sqlglot/commit/bcf6c89a47abd3c2c4383d1c908f892b6619b6fa) - add type annotation tests for snowflake BOOLAND *(PR [#6153](https://github.com/tobymao/sqlglot/pull/6153) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  add type annotation tests for snowflake BOOLAND (#6153)\n\n- due to [`52d1eec`](https://github.com/tobymao/sqlglot/commit/52d1eecaad505703e8b22dcfe8954652f57985b6) - Annotate type for snowflake TIMESTAMP_FROM_PARTS function *(PR [#6139](https://github.com/tobymao/sqlglot/pull/6139) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TIMESTAMP_FROM_PARTS function (#6139)\n\n- due to [`8651fe6`](https://github.com/tobymao/sqlglot/commit/8651fe6526dea865c0d54d6d53086359a7835d32) - annotate types for BOOLOR *(PR [#6159](https://github.com/tobymao/sqlglot/pull/6159) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BOOLOR (#6159)\n\n- due to [`812ba9a`](https://github.com/tobymao/sqlglot/commit/812ba9abad8247df81c8f8b514336c8766292112) - Annotate type for snowflake date parts functions *(PR [#6158](https://github.com/tobymao/sqlglot/pull/6158) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  Annotate type for snowflake date parts functions (#6158)\n\n- due to [`9f8c123`](https://github.com/tobymao/sqlglot/commit/9f8c123ae44249e274334d0aa551ac33814f2b32) - make qualify table callback more generic *(PR [#6171](https://github.com/tobymao/sqlglot/pull/6171) by [@tobymao](https://github.com/tobymao))*:\n\n  make qualify table callback more generic (#6171)\n\n- due to [`74b4e7c`](https://github.com/tobymao/sqlglot/commit/74b4e7c311e9d4ff39ce2e4d91940eced96aa32f) - fix type annotation for Snowflake BOOLOR and BOOLAND *(PR [#6169](https://github.com/tobymao/sqlglot/pull/6169) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  fix type annotation for Snowflake BOOLOR and BOOLAND (#6169)\n\n- due to [`ef87520`](https://github.com/tobymao/sqlglot/commit/ef875204596b8529f3358025c7a61d757a999bdc) - Transpile `REGEXP_REPLACE` with 'g' option *(PR [#6174](https://github.com/tobymao/sqlglot/pull/6174) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Transpile `REGEXP_REPLACE` with 'g' option (#6174)\n\n- due to [`93071e2`](https://github.com/tobymao/sqlglot/commit/93071e255406f62ea83dd89a3be4871b7edfb3fe) - Fix simplify_parens from removing negated *(PR [#6194](https://github.com/tobymao/sqlglot/pull/6194) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix simplify_parens from removing negated (#6194)\n\n- due to [`e90168a`](https://github.com/tobymao/sqlglot/commit/e90168a6829b85534edcecec7d0df2a8b1b56fc4) - annotate type for Snowflake's `IS_NULL_VALUE` function *(PR [#6186](https://github.com/tobymao/sqlglot/pull/6186) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotate type for Snowflake's `IS_NULL_VALUE` function (#6186)\n\n- due to [`c93b535`](https://github.com/tobymao/sqlglot/commit/c93b5354827282c806899c36b11e7a7598e96e38) - annotate type for LEAST_IGNORE_NULLS *(PR [#6196](https://github.com/tobymao/sqlglot/pull/6196) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotate type for LEAST_IGNORE_NULLS (#6196)\n\n- due to [`f60c71f`](https://github.com/tobymao/sqlglot/commit/f60c71fb03db91bfe90430d032ac16f4945d5dff) - annotate types for REGR_VALX *(PR [#6198](https://github.com/tobymao/sqlglot/pull/6198) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for REGR_VALX (#6198)\n\n- due to [`b82c571`](https://github.com/tobymao/sqlglot/commit/b82c57131707297abe174539023b9cb62b7cd6c7) - annotate types for REGR_VALY *(PR [#6206](https://github.com/tobymao/sqlglot/pull/6206) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for REGR_VALY (#6206)\n\n- due to [`39d8e19`](https://github.com/tobymao/sqlglot/commit/39d8e19419c2adbb80465be414d1cc3bbc6d007b) - include VARIABLE kind in SET transpilation to DuckDB *(PR [#6201](https://github.com/tobymao/sqlglot/pull/6201) by [@toriwei](https://github.com/toriwei))*:\n\n  include VARIABLE kind in SET transpilation to DuckDB (#6201)\n\n- due to [`e7ddad1`](https://github.com/tobymao/sqlglot/commit/e7ddad10b5edf9b801d2151e3e5fca448754df0d) - ensure `NULL` coerces into any type *(PR [#6211](https://github.com/tobymao/sqlglot/pull/6211) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  ensure `NULL` coerces into any type (#6211)\n\n- due to [`0037266`](https://github.com/tobymao/sqlglot/commit/00372664bf6acf2b0fff9ad4b206b597ef5378f7) - annotate types for GETBIT *(PR [#6219](https://github.com/tobymao/sqlglot/pull/6219) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for GETBIT (#6219)\n\n- due to [`a5458ce`](https://github.com/tobymao/sqlglot/commit/a5458ceca3bc239fb611791e38020632dd0824c8) - add type annotation for DECODE function support *(PR [#6199](https://github.com/tobymao/sqlglot/pull/6199) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for DECODE function support (#6199)\n\n- due to [`417f1e8`](https://github.com/tobymao/sqlglot/commit/417f1e8ee50fb8f4377fad261660ffbd7444a429) - annotate types for BITNOT *(PR [#6234](https://github.com/tobymao/sqlglot/pull/6234) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BITNOT (#6234)\n\n- due to [`fe8ab40`](https://github.com/tobymao/sqlglot/commit/fe8ab40e8e0559201e0b1896a6f1a8fb6b5b932d) - 1st-class parsing support for BITAND, BIT_AND, BIT_NOT *(PR [#6243](https://github.com/tobymao/sqlglot/pull/6243) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  1st-class parsing support for BITAND, BIT_AND, BIT_NOT (#6243)\n\n- due to [`5ae3c47`](https://github.com/tobymao/sqlglot/commit/5ae3c47b1c6993b87341472c08714f4a0f738168) - add type annotation for GROUPING() function *(PR [#6244](https://github.com/tobymao/sqlglot/pull/6244) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for GROUPING() function (#6244)\n\n- due to [`4133265`](https://github.com/tobymao/sqlglot/commit/413326514507ef06537dcc3d4b80a3fcbcd26f66) - parse `has` function into an `ArrayContains` expression *(PR [#6245](https://github.com/tobymao/sqlglot/pull/6245) by [@joeyutong](https://github.com/joeyutong))*:\n\n  parse `has` function into an `ArrayContains` expression (#6245)\n\n- due to [`cdd45b9`](https://github.com/tobymao/sqlglot/commit/cdd45b949fd1eefb147053424279b56b8effcbcf) - annotate types for GROUPING_ID function. *(PR [#6249](https://github.com/tobymao/sqlglot/pull/6249) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotate types for GROUPING_ID function. (#6249)\n\n- due to [`080ff3b`](https://github.com/tobymao/sqlglot/commit/080ff3bd93b36291d5bb0092d722f8307f0ae082) - annotate types for BITAND_AGG *(PR [#6248](https://github.com/tobymao/sqlglot/pull/6248) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BITAND_AGG (#6248)\n\n- due to [`87a818a`](https://github.com/tobymao/sqlglot/commit/87a818a899f61a675c22c697f468b3f6f7e2787f) - annotate types for BITOR_AGG  *(PR [#6251](https://github.com/tobymao/sqlglot/pull/6251) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BITOR_AGG  (#6251)\n\n- due to [`4c4189b`](https://github.com/tobymao/sqlglot/commit/4c4189b4083d272a6e678d83b5c567a2e9c0d672) - Transpile CONCAT function to double pipe operators when source … *(PR [#6241](https://github.com/tobymao/sqlglot/pull/6241) by [@vchan](https://github.com/vchan))*:\n\n  Transpile CONCAT function to double pipe operators when source … (#6241)\n\n- due to [`a1b884d`](https://github.com/tobymao/sqlglot/commit/a1b884dc9ddfd2185de48cc9451a39f152879d39) - annotate types for BITXOR_AGG *(PR [#6253](https://github.com/tobymao/sqlglot/pull/6253) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BITXOR_AGG (#6253)\n\n- due to [`fc78d20`](https://github.com/tobymao/sqlglot/commit/fc78d2016d8f7d20c094df791f746de323cd3639) - Unwrap subqueries without modifiers *(PR [#6247](https://github.com/tobymao/sqlglot/pull/6247) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Unwrap subqueries without modifiers (#6247)\n\n- due to [`ad2ad23`](https://github.com/tobymao/sqlglot/commit/ad2ad234b5a508040dce4f3920439be052742573) - add missing return type mapping for MAX_BY and MAX_BY function *(PR [#6250](https://github.com/tobymao/sqlglot/pull/6250) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add missing return type mapping for MAX_BY and MAX_BY function (#6250)\n\n- due to [`39c1d81`](https://github.com/tobymao/sqlglot/commit/39c1d81174f2390b6b0c9dd14c0e550ad452a1df) - annotate types for BOOLXOR_AGG *(PR [#6261](https://github.com/tobymao/sqlglot/pull/6261) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BOOLXOR_AGG (#6261)\n\n- due to [`71590d2`](https://github.com/tobymao/sqlglot/commit/71590d22cdb05594e2173a1500f763dc1a32a81d) - add type annotation for SKEW function. *(PR [#6262](https://github.com/tobymao/sqlglot/pull/6262) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for SKEW function. (#6262)\n\n- due to [`5fd366d`](https://github.com/tobymao/sqlglot/commit/5fd366d9e6f7b3f1eb7a9cf41975cf13ce890ffe) - annotate types for OBJECT_AGG *(PR [#6265](https://github.com/tobymao/sqlglot/pull/6265) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for OBJECT_AGG (#6265)\n\n- due to [`00abc39`](https://github.com/tobymao/sqlglot/commit/00abc393c9042e839457c5a6582e95cdb74356f3) - handle casting for bytestrings  *(PR [#6252](https://github.com/tobymao/sqlglot/pull/6252) by [@toriwei](https://github.com/toriwei))*:\n\n  handle casting for bytestrings  (#6252)\n\n- due to [`3dae0fb`](https://github.com/tobymao/sqlglot/commit/3dae0fbb528762e5d5fd446350d42e9c841e2959) - Support position and occurrence args for REGEXP_EXTRACT *(PR [#6266](https://github.com/tobymao/sqlglot/pull/6266) by [@vchan](https://github.com/vchan))*:\n\n  Support position and occurrence args for REGEXP_EXTRACT (#6266)\n\n- due to [`ddea61d`](https://github.com/tobymao/sqlglot/commit/ddea61d83f6699c97cc7b25aabe01a138138bdb1) - simplify connector complements only for non-null operands *(PR [#6214](https://github.com/tobymao/sqlglot/pull/6214) by [@geooo109](https://github.com/geooo109))*:\n\n  simplify connector complements only for non-null operands (#6214)\n\n- due to [`771732d`](https://github.com/tobymao/sqlglot/commit/771732d81459cc576f11eccc49794f33e62d14af) - annotate types for REGR_AVGY *(PR [#6271](https://github.com/tobymao/sqlglot/pull/6271) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for REGR_AVGY (#6271)\n\n- due to [`8470be0`](https://github.com/tobymao/sqlglot/commit/8470be00731a4d79518a533a5f7ba884fa2f047e) - add type annotation for BITMAP_COUNT function. *(PR [#6274](https://github.com/tobymao/sqlglot/pull/6274) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for BITMAP_COUNT function. (#6274)\n\n- due to [`98f25f9`](https://github.com/tobymao/sqlglot/commit/98f25f92cc1175ac7b2118a5a342db82adade13a) - support splitBy function *(PR [#6278](https://github.com/tobymao/sqlglot/pull/6278) by [@joeyutong](https://github.com/joeyutong))*:\n\n  support splitBy function (#6278)\n\n- due to [`fabbf05`](https://github.com/tobymao/sqlglot/commit/fabbf057aba88f30205767d8c339727de45991c8) - Add support for shorthand struct array literals in duckDB. *(PR [#6233](https://github.com/tobymao/sqlglot/pull/6233) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add support for shorthand struct array literals in duckDB. (#6233)\n\n- due to [`c02b64c`](https://github.com/tobymao/sqlglot/commit/c02b64c3524dd074c2108baaca668ab2607ac843) - Handle pseudocolumns differently than columns *(PR [#6273](https://github.com/tobymao/sqlglot/pull/6273) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Handle pseudocolumns differently than columns (#6273)\n\n- due to [`05c5181`](https://github.com/tobymao/sqlglot/commit/05c5181b36a7ada32b96fc91bdfbf73b38a1a408) - refactor `Connector` simplification to factor in types *(PR [#6152](https://github.com/tobymao/sqlglot/pull/6152) by [@geooo109](https://github.com/geooo109))*:\n\n  refactor `Connector` simplification to factor in types (#6152)\n\n- due to [`9c1a222`](https://github.com/tobymao/sqlglot/commit/9c1a2221b0327ba6848542c7b906e92f25a05bea) - add type annotation for BITMAP_CONSTRUCT_AGG function. *(PR [#6285](https://github.com/tobymao/sqlglot/pull/6285) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for BITMAP_CONSTRUCT_AGG function. (#6285)\n\n- due to [`cb0bcff`](https://github.com/tobymao/sqlglot/commit/cb0bcff310e9acdf806fc98e99cb9938b747c771) - cast UUID() output to varchar when source dialect UUID() returns string *(PR [#6284](https://github.com/tobymao/sqlglot/pull/6284) by [@toriwei](https://github.com/toriwei))*:\n\n  cast UUID() output to varchar when source dialect UUID() returns string (#6284)\n\n- due to [`358105d`](https://github.com/tobymao/sqlglot/commit/358105d1296c7425e071ccf3189a31a02c00c923) - type annotation for BITMAP_BIT_POSITION function *(PR [#6301](https://github.com/tobymao/sqlglot/pull/6301) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  type annotation for BITMAP_BIT_POSITION function (#6301)\n\n- due to [`4ee7a50`](https://github.com/tobymao/sqlglot/commit/4ee7a500cc460b6f6a1ed103a12dca72e6d01c18) - type inference for BITMAP_OR_AGG *(PR [#6297](https://github.com/tobymao/sqlglot/pull/6297) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  type inference for BITMAP_OR_AGG (#6297)\n\n- due to [`fcd537d`](https://github.com/tobymao/sqlglot/commit/fcd537de2c993ad0bd18acd84dbae354165f7d3f) - conflict resolution. type annotation for BITMAP_BUCKET_NUMBER function. Tests added all dialects that support BITMAP_BUCKET_NUMBER *(PR [#6299](https://github.com/tobymao/sqlglot/pull/6299) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  conflict resolution. type annotation for BITMAP_BUCKET_NUMBER function. Tests added all dialects that support BITMAP_BUCKET_NUMBER (#6299)\n\n- due to [`3dffd59`](https://github.com/tobymao/sqlglot/commit/3dffd598496a9f2d94caec9d7f3dcb9791c94019) - annotate types for PERCENTILE_DISC and WithinGroup *(PR [#6300](https://github.com/tobymao/sqlglot/pull/6300) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for PERCENTILE_DISC and WithinGroup (#6300)\n\n- due to [`f9287f7`](https://github.com/tobymao/sqlglot/commit/f9287f7d596a6d8a1e1cd2c48978a4dec77a96cb) - robust deduplication of connectors *(PR [#6296](https://github.com/tobymao/sqlglot/pull/6296) by [@geooo109](https://github.com/geooo109))*:\n\n  robust deduplication of connectors (#6296)\n\n- due to [`ea0ea79`](https://github.com/tobymao/sqlglot/commit/ea0ea79c1c611b62c79f82f744fe0c98803598a3) - Parse `LIKE` functions *(PR [#6314](https://github.com/tobymao/sqlglot/pull/6314) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Parse `LIKE` functions (#6314)\n\n- due to [`e903883`](https://github.com/tobymao/sqlglot/commit/e90388328fcf5b8061c99e325b87d5beb0046ffc) - type annotation for APPROX_TOP_K_ACCUMULATE functio… *(PR [#6309](https://github.com/tobymao/sqlglot/pull/6309) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  type annotation for APPROX_TOP_K_ACCUMULATE functio… (#6309)\n\n- due to [`d3fefad`](https://github.com/tobymao/sqlglot/commit/d3fefad80d25ff5a6dd02426667ff0ea8478a1b2) - support `DATEDIFF_BIG` *(PR [#6323](https://github.com/tobymao/sqlglot/pull/6323) by [@lBilali](https://github.com/lBilali))*:\n\n  support `DATEDIFF_BIG` (#6323)\n\n- due to [`21d1468`](https://github.com/tobymao/sqlglot/commit/21d1468377b9c8ad48c6cca1ae3b3744a807c29e) - annotate type for APPROX_TOP_K *(PR [#6286](https://github.com/tobymao/sqlglot/pull/6286) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for APPROX_TOP_K (#6286)\n\n- due to [`85ddcc5`](https://github.com/tobymao/sqlglot/commit/85ddcc5eca22ac726582de454f2f12b9d4877634) - Do not normalize JSON fields in dot notation *(PR [#6320](https://github.com/tobymao/sqlglot/pull/6320) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Do not normalize JSON fields in dot notation (#6320)\n\n- due to [`933e981`](https://github.com/tobymao/sqlglot/commit/933e98102fb39d24ae0350da13337d981287130a) - more robust NULL reduction *(PR [#6327](https://github.com/tobymao/sqlglot/pull/6327) by [@geooo109](https://github.com/geooo109))*:\n\n  more robust NULL reduction (#6327)\n\n- due to [`e4d1a4f`](https://github.com/tobymao/sqlglot/commit/e4d1a4fcd6741d679c5444bf023077d2aaa8f980) - map date/timestamp `TRUNC` to `DATE_TRUNC` *(PR [#6328](https://github.com/tobymao/sqlglot/pull/6328) by [@nnamdi16](https://github.com/nnamdi16))*:\n\n  map date/timestamp `TRUNC` to `DATE_TRUNC` (#6328)\n\n- due to [`e1b6558`](https://github.com/tobymao/sqlglot/commit/e1b6558cb1a860bbd695f25b66e52064b57c0a84) - handle all datepart alternatives *(PR [#6324](https://github.com/tobymao/sqlglot/pull/6324) by [@lBilali](https://github.com/lBilali))*:\n\n  handle all datepart alternatives (#6324)\n\n- due to [`06daa47`](https://github.com/tobymao/sqlglot/commit/06daa47dedebac672548e1db230b89f5c9eae84e) - update annotated type of ARRAY_AGG to untyped array *(PR [#6347](https://github.com/tobymao/sqlglot/pull/6347) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  update annotated type of ARRAY_AGG to untyped array (#6347)\n\n- due to [`7484c06`](https://github.com/tobymao/sqlglot/commit/7484c06be4534cd22dee14da542d5e29ff2c13a2) - Support rounding mode argument for ROUND function *(PR [#6350](https://github.com/tobymao/sqlglot/pull/6350) by [@vchan](https://github.com/vchan))*:\n\n  Support rounding mode argument for ROUND function (#6350)\n\n- due to [`c495a40`](https://github.com/tobymao/sqlglot/commit/c495a40ee4c1a69b14892e8455ae1bd2ceb5ea4f) - annotate type for MINHASH *(PR [#6355](https://github.com/tobymao/sqlglot/pull/6355) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for MINHASH (#6355)\n\n- due to [`b1f9a97`](https://github.com/tobymao/sqlglot/commit/b1f9a976be3c0bcd895bef5bcdb95a013eeb28b7) - annotate type for APPROXIMATE_SIMILARITY *(PR [#6360](https://github.com/tobymao/sqlglot/pull/6360) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for APPROXIMATE_SIMILARITY (#6360)\n\n- due to [`3aafca7`](https://github.com/tobymao/sqlglot/commit/3aafca74546b932cea93ed830c021f347ae03ded) - annotate type for MINHASH_COMBINE *(PR [#6362](https://github.com/tobymao/sqlglot/pull/6362) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for MINHASH_COMBINE (#6362)\n\n- due to [`df13a65`](https://github.com/tobymao/sqlglot/commit/df13a655646bd2ef5d8b4613670bb5fe48845b73) - unnest deep stuff *(PR [#6366](https://github.com/tobymao/sqlglot/pull/6366) by [@tobymao](https://github.com/tobymao))*:\n\n  unnest deep stuff (#6366)\n\n- due to [`d4c2256`](https://github.com/tobymao/sqlglot/commit/d4c2256fb493ed2f16c29694ae5c31517123d419) - at time zone precedence *(PR [#6383](https://github.com/tobymao/sqlglot/pull/6383) by [@geooo109](https://github.com/geooo109))*:\n\n  at time zone precedence (#6383)\n\n- due to [`4fb4d08`](https://github.com/tobymao/sqlglot/commit/4fb4d08ef8896bda434d4f89c21c669c6146fd02) - properly support table alias in the `INSERT` DML *(PR [#6374](https://github.com/tobymao/sqlglot/pull/6374) by [@snovik75](https://github.com/snovik75))*:\n\n  properly support table alias in the `INSERT` DML (#6374)\n\n- due to [`bf07abd`](https://github.com/tobymao/sqlglot/commit/bf07abd4ee9eb0f5510cb7d1f232bdcaea88941e) - annotation support for  APPROX_TOP_K_COMBINE  *(PR [#6378](https://github.com/tobymao/sqlglot/pull/6378) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for  APPROX_TOP_K_COMBINE  (#6378)\n\n- due to [`50348ac`](https://github.com/tobymao/sqlglot/commit/50348ac31f784aa97bd09d5d6c6613fbd68402ee) - support order by clause for mysql delete statement *(PR [#6381](https://github.com/tobymao/sqlglot/pull/6381) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support order by clause for mysql delete statement (#6381)\n\n- due to [`21d3859`](https://github.com/tobymao/sqlglot/commit/21d38590fec6cb55a1a03aeb2621bd9fca677496) - Disable STRING_AGG sep canonicalization *(PR [#6395](https://github.com/tobymao/sqlglot/pull/6395) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Disable STRING_AGG sep canonicalization (#6395)\n\n- due to [`95727f6`](https://github.com/tobymao/sqlglot/commit/95727f60d601796b34c850dee9366d79f6e4a24b) - canonicalize table aliases *(PR [#6369](https://github.com/tobymao/sqlglot/pull/6369) by [@georgesittas](https://github.com/georgesittas))*:\n\n  canonicalize table aliases (#6369)\n\n- due to [`c7cb098`](https://github.com/tobymao/sqlglot/commit/c7cb0983a0fa463c43d2c4ee925816e9a1628c79) - Fix underscore separator with scientific notation *(PR [#6401](https://github.com/tobymao/sqlglot/pull/6401) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix underscore separator with scientific notation (#6401)\n\n- due to [`bb4eda1`](https://github.com/tobymao/sqlglot/commit/bb4eda1beb68b92de9ab014a63c67797a07df2fa) - support transpiling SHA1 from BigQuery to DuckDB *(PR [#6404](https://github.com/tobymao/sqlglot/pull/6404) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpiling SHA1 from BigQuery to DuckDB (#6404)\n\n- due to [`d038ad7`](https://github.com/tobymao/sqlglot/commit/d038ad7f036a140f3eae4bdde15824437d4e44ee) - support named primary keys for mysql *(PR [#6389](https://github.com/tobymao/sqlglot/pull/6389) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support named primary keys for mysql (#6389)\n\n- due to [`05e83b5`](https://github.com/tobymao/sqlglot/commit/05e83b56f1bf9323cfa819a7f1beb542524c1219) - support transpilation of LEAST from BigQuery to DuckDB *(PR [#6415](https://github.com/tobymao/sqlglot/pull/6415) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of LEAST from BigQuery to DuckDB (#6415)\n\n- due to [`4f3bb0d`](https://github.com/tobymao/sqlglot/commit/4f3bb0d6714bf89ff72e13e1398d8f01cefafb00) - Correct transpilation of BigQuery's JSON_EXTRACT_SCALAR… *(PR [#6414](https://github.com/tobymao/sqlglot/pull/6414) by [@vchan](https://github.com/vchan))*:\n\n  Correct transpilation of BigQuery's JSON_EXTRACT_SCALAR… (#6414)\n\n- due to [`8c314a8`](https://github.com/tobymao/sqlglot/commit/8c314a8b457a5c3ed470ac8fcff022fec881c248) - support cte pivot for duckdb *(PR [#6413](https://github.com/tobymao/sqlglot/pull/6413) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support cte pivot for duckdb (#6413)\n\n- due to [`c6b0a63`](https://github.com/tobymao/sqlglot/commit/c6b0a6342a21d79635a26d40001c916d05d47cf7) - change version to be a tuple so that it can be pickled, also simpler *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  change version to be a tuple so that it can be pickled, also simpler\n\n- due to [`07d9958`](https://github.com/tobymao/sqlglot/commit/07d99583b4aebdc682bb7604ccdf45bddb89f9c3) - replace direct comparison with dialect properties *(PR [#6398](https://github.com/tobymao/sqlglot/pull/6398) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  replace direct comparison with dialect properties (#6398)\n\n- due to [`38472ce`](https://github.com/tobymao/sqlglot/commit/38472ce14bce731ba4c309d515223ae99e2575ac) - transpile bigquery's %x format literal *(PR [#6375](https://github.com/tobymao/sqlglot/pull/6375) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpile bigquery's %x format literal (#6375)\n\n- due to [`92ee124`](https://github.com/tobymao/sqlglot/commit/92ee1241ea3088d4e63c094404252339c54ad0c1) - postgres qualify GENERATE_SERIES and table projection *(PR [#6373](https://github.com/tobymao/sqlglot/pull/6373) by [@geooo109](https://github.com/geooo109))*:\n\n  postgres qualify GENERATE_SERIES and table projection (#6373)\n\n- due to [`0b9d8ac`](https://github.com/tobymao/sqlglot/commit/0b9d8acbe75457424436e8c0acc047ab66e9fdc0) - Annotate type for snowflake MAX function *(PR [#6422](https://github.com/tobymao/sqlglot/pull/6422) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake MAX function (#6422)\n\n- due to [`68e9414`](https://github.com/tobymao/sqlglot/commit/68e9414725a60b2842d870fa222d8466057a94f6) - Annotate type for snowflake MIN function *(PR [#6427](https://github.com/tobymao/sqlglot/pull/6427) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  Annotate type for snowflake MIN function (#6427)\n\n- due to [`1318de7`](https://github.com/tobymao/sqlglot/commit/1318de77a8aa514ec7eb9f9b8c03228e3f8eb008) - Annotate type for snowflake NORMAL *(PR [#6434](https://github.com/tobymao/sqlglot/pull/6434) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake NORMAL (#6434)\n\n- due to [`ffbb5c7`](https://github.com/tobymao/sqlglot/commit/ffbb5c7e40aa064ffcd4827e96ea66cfd045118e) - annotate type for HASH_AGG in Snowflake *(PR [#6438](https://github.com/tobymao/sqlglot/pull/6438) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate type for HASH_AGG in Snowflake (#6438)\n\n- due to [`161255f`](https://github.com/tobymao/sqlglot/commit/161255f6c90b9c3ed2074e734f6d074db1d7a6dd) - Add support for `LOCALTIME` function *(PR [#6443](https://github.com/tobymao/sqlglot/pull/6443) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for `LOCALTIME` function (#6443)\n\n- due to [`ca329f0`](https://github.com/tobymao/sqlglot/commit/ca329f037a230c315437d830638b514190764c5a) - support transpilation of SHA256 from bigquery to duckdb *(PR [#6421](https://github.com/tobymao/sqlglot/pull/6421) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of SHA256 from bigquery to duckdb (#6421)\n\n- due to [`e18ae24`](https://github.com/tobymao/sqlglot/commit/e18ae248423dbbca78a24a60ea0193da2ee7f68c) - Annotate type for snowflake REGR_SLOPE function *(PR [#6425](https://github.com/tobymao/sqlglot/pull/6425) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake REGR_SLOPE function (#6425)\n\n- due to [`1d847f0`](https://github.com/tobymao/sqlglot/commit/1d847f0a1f88fce5df340ab646a72c8abbc12a86) - parse & annotate `CHECK_JSON`, `CHECK_XML` *(PR [#6439](https://github.com/tobymao/sqlglot/pull/6439) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  parse & annotate `CHECK_JSON`, `CHECK_XML` (#6439)\n\n- due to [`cb3080d`](https://github.com/tobymao/sqlglot/commit/cb3080d4bed18b1bfbbd08380ed60deeefd15530) - annotation support for APPROX_TOP_K_ESTIMATE . Return type ARRAY *(PR [#6445](https://github.com/tobymao/sqlglot/pull/6445) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for APPROX_TOP_K_ESTIMATE . Return type ARRAY (#6445)\n\n- due to [`313afe5`](https://github.com/tobymao/sqlglot/commit/313afe540aa2cdc4cc179c4852c6ef37362bcb3e) - annotate type for snowflake func ARRAY_UNION_AGG *(PR [#6446](https://github.com/tobymao/sqlglot/pull/6446) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake func ARRAY_UNION_AGG (#6446)\n\n- due to [`cd9f037`](https://github.com/tobymao/sqlglot/commit/cd9f037882eef253e86fdb1d51521e0acd7db3f9) - store pk name if provided *(PR [#6424](https://github.com/tobymao/sqlglot/pull/6424) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  store pk name if provided (#6424)\n\n- due to [`65194e4`](https://github.com/tobymao/sqlglot/commit/65194e465489151aa51859a6e3f5672f7d4c5f3b) - Annotate type for snowflake RANDSTR function *(PR [#6436](https://github.com/tobymao/sqlglot/pull/6436) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake RANDSTR function (#6436)\n\n- due to [`a56262e`](https://github.com/tobymao/sqlglot/commit/a56262e6b4276baae144855478807c173db77ab9) - Annotate type for snowflake MEDIAN *(PR [#6426](https://github.com/tobymao/sqlglot/pull/6426) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  Annotate type for snowflake MEDIAN (#6426)\n\n- due to [`2c56567`](https://github.com/tobymao/sqlglot/commit/2c56567755c8a6571d8b7d410c9de943e54df58b) - Annotate type for snowflake SEARCH_IP  *(PR [#6440](https://github.com/tobymao/sqlglot/pull/6440) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake SEARCH_IP  (#6440)\n\n- due to [`ac86568`](https://github.com/tobymao/sqlglot/commit/ac86568a939f692b99813da100297b61fb54e044) - Added decfloat type *(PR [#6444](https://github.com/tobymao/sqlglot/pull/6444) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Added decfloat type (#6444)\n\n- due to [`b321ca6`](https://github.com/tobymao/sqlglot/commit/b321ca6191fefc88da1a6de83a465886b5754b7a) - bump sqlglotrs to 0.8.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.8.0\n\n- due to [`ebe718a`](https://github.com/tobymao/sqlglot/commit/ebe718a72d5b5871a8d6e67754ff50e873d55b41) - Add support for format elements used in date/time functions like FORMAT_DATETIME *(PR [#6428](https://github.com/tobymao/sqlglot/pull/6428) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add support for format elements used in date/time functions like FORMAT_DATETIME (#6428)\n\n- due to [`c111f64`](https://github.com/tobymao/sqlglot/commit/c111f643d61064280024b4cc5c0fc250581fbe55) - annotation support for APPROX_PERCENTILE_ACCUMULATE *(PR [#6455](https://github.com/tobymao/sqlglot/pull/6455) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for APPROX_PERCENTILE_ACCUMULATE (#6455)\n\n- due to [`f305305`](https://github.com/tobymao/sqlglot/commit/f305305e5cf3ef45afba822542aebeb944c00e0b) - Annotate types for BigQuery's AVG function *(PR [#6459](https://github.com/tobymao/sqlglot/pull/6459) by [@vchan](https://github.com/vchan))*:\n\n  Annotate types for BigQuery's AVG function (#6459)\n\n- due to [`910349f`](https://github.com/tobymao/sqlglot/commit/910349f3c30af59ce1820e48cae0cbb77539877d) - Annotate types for BigQuery's SAFE_DIVIDE function *(PR [#6464](https://github.com/tobymao/sqlglot/pull/6464) by [@vchan](https://github.com/vchan))*:\n\n  Annotate types for BigQuery's SAFE_DIVIDE function (#6464)\n\n- due to [`5e75621`](https://github.com/tobymao/sqlglot/commit/5e75621e90defd50076383485f6a4689a8c551ac) - annotate type for snowflake func ARRAY_UNIQUE_AGG *(PR [#6465](https://github.com/tobymao/sqlglot/pull/6465) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake func ARRAY_UNIQUE_AGG (#6465)\n\n- due to [`94d46b8`](https://github.com/tobymao/sqlglot/commit/94d46b8eafd5abe252407d2bbe306ca579a29b20) - annotation support for APPROX_PERCENTILE_ESTIMATE. Return type DOUBLE *(PR [#6461](https://github.com/tobymao/sqlglot/pull/6461) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for APPROX_PERCENTILE_ESTIMATE. Return type DOUBLE (#6461)\n\n- due to [`2ac30b0`](https://github.com/tobymao/sqlglot/commit/2ac30b08bd663bbaf00ae075c4db0c3d27ab6640) - annotation support for APPROX_PERCENTILE_COMBINE *(PR [#6460](https://github.com/tobymao/sqlglot/pull/6460) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for APPROX_PERCENTILE_COMBINE (#6460)\n\n- due to [`d44bda3`](https://github.com/tobymao/sqlglot/commit/d44bda376c06956947a09a9f279cce886a63b981) - Annotate type for ZIPF *(PR [#6453](https://github.com/tobymao/sqlglot/pull/6453) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for ZIPF (#6453)\n\n- due to [`34dbd47`](https://github.com/tobymao/sqlglot/commit/34dbd478957c1796998d0b263f63c8ce1db7a320) - Annotate type for XMLGET *(PR [#6457](https://github.com/tobymao/sqlglot/pull/6457) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for XMLGET (#6457)\n\n- due to [`0d211f2`](https://github.com/tobymao/sqlglot/commit/0d211f2b36167cfb7856b8ec25f597f70317a9c7) - annotate type for MODE function snowflake *(PR [#6447](https://github.com/tobymao/sqlglot/pull/6447) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate type for MODE function snowflake (#6447)\n\n- due to [`cc4c8ab`](https://github.com/tobymao/sqlglot/commit/cc4c8ab43ab71790bc2bb9f8f3c06e34f89f999f) - annotate type for PERCENTILE_CONT in Snowflake *(PR [#6470](https://github.com/tobymao/sqlglot/pull/6470) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate type for PERCENTILE_CONT in Snowflake (#6470)\n\n- due to [`7dbc242`](https://github.com/tobymao/sqlglot/commit/7dbc242a637a8890511cc14f22bce4d425f1f55d) - annotation support for CURRENT REGION. Return type VARCHAR *(PR [#6473](https://github.com/tobymao/sqlglot/pull/6473) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT REGION. Return type VARCHAR (#6473)\n\n- due to [`43a6a5c`](https://github.com/tobymao/sqlglot/commit/43a6a5c601421e15a7f94dd489cb4fbcf9d2c8c3) - annotation support for CURRENT_ORGANIZATION_NAME. Return type VARCHAR *(PR [#6475](https://github.com/tobymao/sqlglot/pull/6475) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ORGANIZATION_NAME. Return type VARCHAR (#6475)\n\n- due to [`f1f7c6a`](https://github.com/tobymao/sqlglot/commit/f1f7c6ae6b6aa3f6f2251d0f81ee667440ca53d1) - annotation support for CURRENT_ORGANIZATION_USER. *(PR [#6476](https://github.com/tobymao/sqlglot/pull/6476) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ORGANIZATION_USER. (#6476)\n\n- due to [`88dfd26`](https://github.com/tobymao/sqlglot/commit/88dfd26b832d13e517fe7c18d2c086885bf4954d) - annotate type for snowflake func TO_BINARY *(PR [#6474](https://github.com/tobymao/sqlglot/pull/6474) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake func TO_BINARY (#6474)\n\n- due to [`d268203`](https://github.com/tobymao/sqlglot/commit/d268203e1dbae4e3aff863108f6d09a6f8274db5) - annotation support for CURRENT_ROLE_TYPE *(PR [#6479](https://github.com/tobymao/sqlglot/pull/6479) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ROLE_TYPE (#6479)\n\n- due to [`fd4431b`](https://github.com/tobymao/sqlglot/commit/fd4431bf9550c03aa761c642a68a21a146fd8548) - annotate type for VECTOR_L1_DISTANCE, VECTOR_L2_DISTANCE, VECTOR_COSINE_SIMILARITY functions *(PR [#6468](https://github.com/tobymao/sqlglot/pull/6468) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  annotate type for VECTOR_L1_DISTANCE, VECTOR_L2_DISTANCE, VECTOR_COSINE_SIMILARITY functions (#6468)\n\n- due to [`e6adba7`](https://github.com/tobymao/sqlglot/commit/e6adba76cc2f27633a9d38bfaea3356e71d00a4c) - Add support for coercing STRING literals to temporal types *(PR [#6482](https://github.com/tobymao/sqlglot/pull/6482) by [@vchan](https://github.com/vchan))*:\n\n  Add support for coercing STRING literals to temporal types (#6482)\n\n- due to [`68a5e61`](https://github.com/tobymao/sqlglot/commit/68a5e615b24e518cb90c9b80cf25355fcabdb468) - annotate type for REGR_* functions *(PR [#6452](https://github.com/tobymao/sqlglot/pull/6452) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  annotate type for REGR_* functions (#6452)\n\n- due to [`f7458a4`](https://github.com/tobymao/sqlglot/commit/f7458a40d3b09a2e212f6705ac4a77c99714508e) - annotate type for snowflake func TO_BOOLEAN *(PR [#6481](https://github.com/tobymao/sqlglot/pull/6481) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake func TO_BOOLEAN (#6481)\n\n- due to [`1531a67`](https://github.com/tobymao/sqlglot/commit/1531a67ac7806f3b4582f6cf1ea02342a517de74) - annotate type for VECTOR_INNER_PRODUCT *(PR [#6486](https://github.com/tobymao/sqlglot/pull/6486) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  annotate type for VECTOR_INNER_PRODUCT (#6486)\n\n- due to [`df4c1d3`](https://github.com/tobymao/sqlglot/commit/df4c1d37ff77151a74b5de3d119c7e03f5db85f4) - REGEXP_EXTRACT position arg overflow *(PR [#6458](https://github.com/tobymao/sqlglot/pull/6458) by [@treysp](https://github.com/treysp))*:\n\n  REGEXP_EXTRACT position arg overflow (#6458)\n\n- due to [`f6b2b3b`](https://github.com/tobymao/sqlglot/commit/f6b2b3bc6e1c95340149be65d80ef7e177b28d82) - support padside argument for BIT[OR|AND|XOR] *(PR [#6487](https://github.com/tobymao/sqlglot/pull/6487) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support padside argument for BIT[OR|AND|XOR] (#6487)\n\n- due to [`5a49c3f`](https://github.com/tobymao/sqlglot/commit/5a49c3f7a7619ad9e711ff2cd9e85b8606969b36) - support ORDER / LIMIT expressions for BigQuery ARRAY_AGG / STRING_AGG functions *(PR [#6463](https://github.com/tobymao/sqlglot/pull/6463) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support ORDER / LIMIT expressions for BigQuery ARRAY_AGG / STRING_AGG functions (#6463)\n\n- due to [`ef130f1`](https://github.com/tobymao/sqlglot/commit/ef130f1b944b4be835d4a6831fec9a333a825a34) - Annotated type for ARRAY_CONSTRUCT_COMPACT [#6496](https://github.com/tobymao/sqlglot/pull/6496) *(commit by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotated type for ARRAY_CONSTRUCT_COMPACT #6496\n\n- due to [`1b6076b`](https://github.com/tobymao/sqlglot/commit/1b6076bd5a64b044f52f5366244ba0746aca75e1) - wrap connectives generated due to transpiling LIKE ANY closes [#6493](https://github.com/tobymao/sqlglot/pull/6493) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  wrap connectives generated due to transpiling LIKE ANY closes #6493\n\n- due to [`36ad534`](https://github.com/tobymao/sqlglot/commit/36ad534b14eabe9ee197017f5087e8e5190f8526) - qualified select list with \"LOCAL\" *(PR [#6450](https://github.com/tobymao/sqlglot/pull/6450) by [@nnamdi16](https://github.com/nnamdi16))*:\n\n  qualified select list with \"LOCAL\" (#6450)\n\n- due to [`36cf0bf`](https://github.com/tobymao/sqlglot/commit/36cf0bf6671f622344afee52d7aafe30f19ecf9a) - annotation support for CURRENT_ROLE. *(PR [#6478](https://github.com/tobymao/sqlglot/pull/6478) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ROLE. (#6478)\n\n- due to [`cbba04c`](https://github.com/tobymao/sqlglot/commit/cbba04cb292fe8b3fd38c87d9ccb624cdcb52843) - support comma-separated syntax for OVERLAY function *(PR [#6497](https://github.com/tobymao/sqlglot/pull/6497) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support comma-separated syntax for OVERLAY function (#6497)\n\n- due to [`dc8f26a`](https://github.com/tobymao/sqlglot/commit/dc8f26a3a5e023a0e54caa345b129fb1b4fe805f) - bq annotate type for NULL *(PR [#6491](https://github.com/tobymao/sqlglot/pull/6491) by [@geooo109](https://github.com/geooo109))*:\n\n  bq annotate type for NULL (#6491)\n\n- due to [`52aceaa`](https://github.com/tobymao/sqlglot/commit/52aceaaa887dddb35f8ede5c2d9577fdeee35c48) - annotate `HavingMax` by `this` *(PR [#6499](https://github.com/tobymao/sqlglot/pull/6499) by [@georgesittas](https://github.com/georgesittas))*:\n\n  annotate `HavingMax` by `this` (#6499)\n\n- due to [`c97a81d`](https://github.com/tobymao/sqlglot/commit/c97a81d68a1584fad48475725665a7678fcad9d1) - annotate TO_HEX(MD5(...)) in BigQuery *(PR [#6500](https://github.com/tobymao/sqlglot/pull/6500) by [@georgesittas](https://github.com/georgesittas))*:\n\n  annotate TO_HEX(MD5(...)) in BigQuery (#6500)\n\n- due to [`a5797a1`](https://github.com/tobymao/sqlglot/commit/a5797a1c867c4ade71ae4ddf93232576993cf5bc) - handle named arguments and non-integer scale input for ROUND *(PR [#6495](https://github.com/tobymao/sqlglot/pull/6495) by [@toriwei](https://github.com/toriwei))*:\n\n  handle named arguments and non-integer scale input for ROUND (#6495)\n\n- due to [`3224235`](https://github.com/tobymao/sqlglot/commit/3224235c1b7a80511af11f7dbffe608a747a3df0) - make CTE builder produce AST consistent with parser closes [#6503](https://github.com/tobymao/sqlglot/pull/6503) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  make CTE builder produce AST consistent with parser closes #6503\n\n- due to [`8b5298a`](https://github.com/tobymao/sqlglot/commit/8b5298a6578af80fd9676eb222422862d5468859) - Transpile BQ's WEEK based `DATE_DIFF` *(PR [#6507](https://github.com/tobymao/sqlglot/pull/6507) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Transpile BQ's WEEK based `DATE_DIFF` (#6507)\n\n- due to [`9454a18`](https://github.com/tobymao/sqlglot/commit/9454a18cca41a510e61522f6b785d646980e2100) - uppercase join method, side, kind for consistency fixes [#6510](https://github.com/tobymao/sqlglot/pull/6510) *(PR [#6511](https://github.com/tobymao/sqlglot/pull/6511) by [@georgesittas](https://github.com/georgesittas))*:\n\n  uppercase join method, side, kind for consistency fixes #6510 (#6511)\n\n- due to [`41b776b`](https://github.com/tobymao/sqlglot/commit/41b776bdc6936f18accd9f7308b55acd383bb596) - added support for current_catalog *(PR [#6492](https://github.com/tobymao/sqlglot/pull/6492) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  added support for current_catalog (#6492)\n\n- due to [`dd19bea`](https://github.com/tobymao/sqlglot/commit/dd19beae95f077cfd8b6e315eca7ff212817b250) - annotation support for CURRENT_ACCOUNT *(PR [#6512](https://github.com/tobymao/sqlglot/pull/6512) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ACCOUNT (#6512)\n\n- due to [`2e8105e`](https://github.com/tobymao/sqlglot/commit/2e8105eebaec25fc8f94f1e68951198660f404e1) - Annotate type for VAR_POP, VAR_SAMP, DuckDB consistency fix for VAR_SAMP *(PR [#6488](https://github.com/tobymao/sqlglot/pull/6488) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for VAR_POP, VAR_SAMP, DuckDB consistency fix for VAR_SAMP (#6488)\n\n- due to [`cfb02c1`](https://github.com/tobymao/sqlglot/commit/cfb02c1aa676e801b2d13a84467b4904cd834ffe) - annotation support for CURRENT_ACCOUNT_NAME *(PR [#6513](https://github.com/tobymao/sqlglot/pull/6513) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ACCOUNT_NAME (#6513)\n\n- due to [`1004e31`](https://github.com/tobymao/sqlglot/commit/1004e31cce62cce2e2afb7eab85ed8bdecaede3b) - annotation support for CURRENT_AVAILABLE_ROLES *(PR [#6514](https://github.com/tobymao/sqlglot/pull/6514) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_AVAILABLE_ROLES (#6514)\n\n- due to [`ff201fe`](https://github.com/tobymao/sqlglot/commit/ff201febd27937a97674dd091928456dde733254) - annotation support for CURRENT_CLIENT *(PR [#6515](https://github.com/tobymao/sqlglot/pull/6515) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_CLIENT (#6515)\n\n- due to [`d777a9c`](https://github.com/tobymao/sqlglot/commit/d777a9c0feef15ac036f7b413112de4d7cc8bea4) - annotation support for CURRENT_IP_ADDRESS *(PR [#6518](https://github.com/tobymao/sqlglot/pull/6518) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_IP_ADDRESS (#6518)\n\n- due to [`c296061`](https://github.com/tobymao/sqlglot/commit/c2960615a3bd279b7c5f775d5b93ae12aa27a3b8) - Transpilation of TO_BINARY from snowflake to duckdb *(PR [#6504](https://github.com/tobymao/sqlglot/pull/6504) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  Transpilation of TO_BINARY from snowflake to duckdb (#6504)\n\n- due to [`7a70164`](https://github.com/tobymao/sqlglot/commit/7a70164d8cf361cf4c0a7d5789bb51676f772959) - transpile Snowflake's `RANDSTR` function *(PR [#6502](https://github.com/tobymao/sqlglot/pull/6502) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  transpile Snowflake's `RANDSTR` function (#6502)\n\n- due to [`a26d419`](https://github.com/tobymao/sqlglot/commit/a26d4191e5468e39eafdf7a981e7b890d438b2c9) - annotation support for CURRENT_DATABASE *(PR [#6516](https://github.com/tobymao/sqlglot/pull/6516) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_DATABASE (#6516)\n\n- due to [`0acdf7f`](https://github.com/tobymao/sqlglot/commit/0acdf7fc783f2722536ec24dcf8600957febf7ca) - annotation support for CURRENT_SCHEMAS *(PR [#6519](https://github.com/tobymao/sqlglot/pull/6519) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_SCHEMAS (#6519)\n\n- due to [`43cce89`](https://github.com/tobymao/sqlglot/commit/43cce895da80d21abc89d40de5d7fddd68871bf0) - annotation support for CURRENT_SECONDARY_ROLES *(PR [#6520](https://github.com/tobymao/sqlglot/pull/6520) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_SECONDARY_ROLES (#6520)\n\n- due to [`c21b4b1`](https://github.com/tobymao/sqlglot/commit/c21b4b1134b368ee5144339b59e70ddcc54f3dbc) - annotation support for CURRENT_SESSION *(PR [#6521](https://github.com/tobymao/sqlglot/pull/6521) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_SESSION (#6521)\n\n- due to [`57a83c0`](https://github.com/tobymao/sqlglot/commit/57a83c018dace690f7bb363c25ee6bde33c3d60f) - annotation support for CURRENT_STATEMENT *(PR [#6522](https://github.com/tobymao/sqlglot/pull/6522) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_STATEMENT (#6522)\n\n- due to [`4b240e4`](https://github.com/tobymao/sqlglot/commit/4b240e40a8809a6eea2a279370a884f4a7b03dfa) - annotation support for CURRENT_VERSION *(PR [#6524](https://github.com/tobymao/sqlglot/pull/6524) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_VERSION (#6524)\n\n- due to [`c1a831f`](https://github.com/tobymao/sqlglot/commit/c1a831f5bf662ab8d8e07dc2bb949f2adcbe7d7c) - annotation support for CURRENT_TRANSACTION *(PR [#6523](https://github.com/tobymao/sqlglot/pull/6523) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_TRANSACTION (#6523)\n\n- due to [`2e162b0`](https://github.com/tobymao/sqlglot/commit/2e162b0d34066e7aa7edac3156739bcd31a634fc) - annotation support for CURRENT_WAREHOUSE *(PR [#6525](https://github.com/tobymao/sqlglot/pull/6525) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_WAREHOUSE (#6525)\n\n- due to [`9d06859`](https://github.com/tobymao/sqlglot/commit/9d0685923209c04747fa6fa2b35ee2e516453abc) - annotate bigquery ARRAY when arg contains set operations *(PR [#6517](https://github.com/tobymao/sqlglot/pull/6517) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate bigquery ARRAY when arg contains set operations (#6517)\n\n- due to [`2fd14ed`](https://github.com/tobymao/sqlglot/commit/2fd14ed32b3793444405005fb98342222b4d7956) - query schema directly when type annotation fails for processing UNNEST source *(PR [#6451](https://github.com/tobymao/sqlglot/pull/6451) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  query schema directly when type annotation fails for processing UNNEST source (#6451)\n\n- due to [`41a9e88`](https://github.com/tobymao/sqlglot/commit/41a9e88bb9800205df0b3e10a1976699dc4fe4f9) - Add support to transpile binary args for bitwise operators *(PR [#6508](https://github.com/tobymao/sqlglot/pull/6508) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add support to transpile binary args for bitwise operators (#6508)\n\n- due to [`06c7ffb`](https://github.com/tobymao/sqlglot/commit/06c7ffbe14985a4da35a97d47322021e79525adf) - cleanup bitwise operator fixes *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  cleanup bitwise operator fixes\n\n- due to [`62b348c`](https://github.com/tobymao/sqlglot/commit/62b348ce46d014895bd17d89ccb0b3e186e46d15) - add support for noop string escapes *(PR [#6526](https://github.com/tobymao/sqlglot/pull/6526) by [@nian0114](https://github.com/nian0114))*:\n\n  add support for noop string escapes (#6526)\n\n- due to [`1876c5a`](https://github.com/tobymao/sqlglot/commit/1876c5a86c3b737b7360c4fef25c44dc010b66db) - consolidate can_quote logic and fix an issue with identify=False *(PR [#6534](https://github.com/tobymao/sqlglot/pull/6534) by [@tobymao](https://github.com/tobymao))*:\n\n  consolidate can_quote logic and fix an issue with identify=False (#6534)\n\n- due to [`edb8964`](https://github.com/tobymao/sqlglot/commit/edb8964ed064a687e52323143d52281eaa391c9a) - bump sqlglotrs to 0.9.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.9.0\n\n- due to [`2555856`](https://github.com/tobymao/sqlglot/commit/2555856cac7434ef91cc1584d52610178e45c4b9) - annotate scalar subqueries *(PR [#6536](https://github.com/tobymao/sqlglot/pull/6536) by [@georgesittas](https://github.com/georgesittas))*:\n\n  annotate scalar subqueries (#6536)\n\n- due to [`71e7630`](https://github.com/tobymao/sqlglot/commit/71e763096462aa888a353ac1ad3675a9e5b4841a) - normalize FLOAT to DOUBLE *(PR [#6501](https://github.com/tobymao/sqlglot/pull/6501) by [@toriwei](https://github.com/toriwei))*:\n\n  normalize FLOAT to DOUBLE (#6501)\n\n- due to [`9badf6a`](https://github.com/tobymao/sqlglot/commit/9badf6a6b1972fc37164b29aa416bb897d7ec6a6) - Annotate type for TRY_* functions *(PR [#6509](https://github.com/tobymao/sqlglot/pull/6509) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for TRY_* functions (#6509)\n\n- due to [`aad1332`](https://github.com/tobymao/sqlglot/commit/aad1332fee7c82c29dae3caed9a6a1c882c1d4a0) - support transpilation of BITMAP_BIT_POSITION from snowflake to duckdb *(PR [#6541](https://github.com/tobymao/sqlglot/pull/6541) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support transpilation of BITMAP_BIT_POSITION from snowflake to duckdb (#6541)\n\n- due to [`f21cf76`](https://github.com/tobymao/sqlglot/commit/f21cf763575b67084ea81a377c5bdb3e86041e4c) - bq annotate SAFE_DIVIDE with both args as INT64 *(PR [#6543](https://github.com/tobymao/sqlglot/pull/6543) by [@geooo109](https://github.com/geooo109))*:\n\n  bq annotate SAFE_DIVIDE with both args as INT64 (#6543)\n\n- due to [`4a57302`](https://github.com/tobymao/sqlglot/commit/4a5730242787920d0a2412aef495eb2eeaaa2119) - ensure structs are annotated as unknown if any argument is unknown *(PR [#6544](https://github.com/tobymao/sqlglot/pull/6544) by [@georgesittas](https://github.com/georgesittas))*:\n\n  ensure structs are annotated as unknown if any argument is unknown (#6544)\n\n- due to [`8a12611`](https://github.com/tobymao/sqlglot/commit/8a12611e9499497d0c8b1e1e418986b2d91a6505) - New type + type annotation for TO_FILE  *(PR [#6548](https://github.com/tobymao/sqlglot/pull/6548) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  New type + type annotation for TO_FILE  (#6548)\n\n- due to [`63a2e49`](https://github.com/tobymao/sqlglot/commit/63a2e49485f237e1c7e16358c412acb5df50e22c) - stop treating `None` args as leaves to be diffed *(PR [#6556](https://github.com/tobymao/sqlglot/pull/6556) by [@georgesittas](https://github.com/georgesittas))*:\n\n  stop treating `None` args as leaves to be diffed (#6556)\n\n- due to [`906c933`](https://github.com/tobymao/sqlglot/commit/906c933235c82598b0d08f8c66dd3db0b8f409a5) - overlap operator *(PR [#6545](https://github.com/tobymao/sqlglot/pull/6545) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  overlap operator (#6545)\n\n- due to [`370b1f6`](https://github.com/tobymao/sqlglot/commit/370b1f621844d3ac8831c998ea2046f1e1b91b65) - add support for session_user *(PR [#6555](https://github.com/tobymao/sqlglot/pull/6555) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  add support for session_user (#6555)\n\n- due to [`dbbace0`](https://github.com/tobymao/sqlglot/commit/dbbace01cd5f1fc44f5ad278def25f547686f9c5) - remove transpilation support of APPROX_TOP_K to duckdb *(PR [#6560](https://github.com/tobymao/sqlglot/pull/6560) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  remove transpilation support of APPROX_TOP_K to duckdb (#6560)\n\n- due to [`2bc2506`](https://github.com/tobymao/sqlglot/commit/2bc2506e0e0b26e82661a08217855d693f30dc25) - support SAFE.TIMESTAMP annotation *(PR [#6550](https://github.com/tobymao/sqlglot/pull/6550) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support SAFE.TIMESTAMP annotation (#6550)\n\n- due to [`a51cc7b`](https://github.com/tobymao/sqlglot/commit/a51cc7b6e02c5b37bf43b82a0d76b83d41248ac9) - elt function in mysql *(PR [#6568](https://github.com/tobymao/sqlglot/pull/6568) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  elt function in mysql (#6568)\n\n- due to [`14dc1e5`](https://github.com/tobymao/sqlglot/commit/14dc1e5bc74b3b8907ba02bf89ad1763940c9ea2) - make `DATE_PART` roundtrip *(PR [#6573](https://github.com/tobymao/sqlglot/pull/6573) by [@georgesittas](https://github.com/georgesittas))*:\n\n  make `DATE_PART` roundtrip (#6573)\n\n- due to [`4339b26`](https://github.com/tobymao/sqlglot/commit/4339b26db546862b10a0e8d746506b406ecfa306) - expose struct fields using UNNEST without aliases *(PR [#6566](https://github.com/tobymao/sqlglot/pull/6566) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  expose struct fields using UNNEST without aliases (#6566)\n\n- due to [`8a44ad5`](https://github.com/tobymao/sqlglot/commit/8a44ad560cb65a34a722b257a82e69a41e7e45e0) - Mark _DBT_MAX_PARTITION as pseudocolumn *(PR [#6572](https://github.com/tobymao/sqlglot/pull/6572) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Mark _DBT_MAX_PARTITION as pseudocolumn (#6572)\n\n- due to [`7bfffe5`](https://github.com/tobymao/sqlglot/commit/7bfffe5d894c60bd0139d57c53bb1816c2739d74) - support transpilation of TO_BOOLEAN from snowflake to duckdb *(PR [#6564](https://github.com/tobymao/sqlglot/pull/6564) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of TO_BOOLEAN from snowflake to duckdb (#6564)\n\n\n### :sparkles: New Features\n- [`1fb90db`](https://github.com/tobymao/sqlglot/commit/1fb90db52b59e6e3a40597c6f611d0476b72025b) - **teradata**: Add support for Teradata set query band expression *(PR [#5519](https://github.com/tobymao/sqlglot/pull/5519) by [@treff7es](https://github.com/treff7es))*\n- [`a49baaf`](https://github.com/tobymao/sqlglot/commit/a49baaf717cb41abb25ca51ae5adddc8473baa8b) - **doris**: Override table_sql to avoid AS keyword in UPDATE and DELETE statements *(PR [#5517](https://github.com/tobymao/sqlglot/pull/5517) by [@peterylh](https://github.com/peterylh))*\n- [`75fd6d2`](https://github.com/tobymao/sqlglot/commit/75fd6d21fb7bc8399432e73d10b4837ae62d2ab5) - **exasol**: Add support for date difference functions in Exasol dialect *(PR [#5510](https://github.com/tobymao/sqlglot/pull/5510) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`2a91bb4`](https://github.com/tobymao/sqlglot/commit/2a91bb4f17c7569a5b409cc07e970e5d68235149) - **teradata**: Add support for Teradata locking select *(PR [#5524](https://github.com/tobymao/sqlglot/pull/5524) by [@treff7es](https://github.com/treff7es))*\n- [`938f4b6`](https://github.com/tobymao/sqlglot/commit/938f4b6ebc1c0d26bd3c1400883978c79a435189) - **optimizer**: annotate type for LAST_DAY *(PR [#5528](https://github.com/tobymao/sqlglot/pull/5528) by [@geooo109](https://github.com/geooo109))*\n- [`7d12dac`](https://github.com/tobymao/sqlglot/commit/7d12dac613ba5119334408f2c52cb270067156d9) - **optimizer**: annotate type for bigquery GENERATE_TIMESTAMP_ARRAY *(PR [#5529](https://github.com/tobymao/sqlglot/pull/5529) by [@geooo109](https://github.com/geooo109))*\n- [`d50ebe2`](https://github.com/tobymao/sqlglot/commit/d50ebe286dd8e2836b9eb2a3406f15976db3aa05) - **optimizer**: annotate type for bigquery TIME_TRUNC *(PR [#5530](https://github.com/tobymao/sqlglot/pull/5530) by [@geooo109](https://github.com/geooo109))*\n- [`29748be`](https://github.com/tobymao/sqlglot/commit/29748be7dfc10edc9f29665c98327883dd25c13d) - **optimizer**: annotate type for bigquery TIME *(PR [#5531](https://github.com/tobymao/sqlglot/pull/5531) by [@geooo109](https://github.com/geooo109))*\n- [`7003b3f`](https://github.com/tobymao/sqlglot/commit/7003b3fa39cd455e3643066364696708d1ac4f38) - **optimizer**: parse and annotate type for bigquery DATE_FROM_UNIX_DATE *(PR [#5532](https://github.com/tobymao/sqlglot/pull/5532) by [@geooo109](https://github.com/geooo109))*\n- [`a276ca6`](https://github.com/tobymao/sqlglot/commit/a276ca6fd5f9d47fa8c90fcfa19f9864e7a28f8f) - **optimizer**: parse and annotate type for bigquery JUSTIFY funcs *(PR [#5534](https://github.com/tobymao/sqlglot/pull/5534) by [@geooo109](https://github.com/geooo109))*\n- [`374178e`](https://github.com/tobymao/sqlglot/commit/374178e22fe8d2d2275b65fe08e27ef66c611220) - **optimizer**: parse and annotate type for bigquery UNIX_MICROS and UNIX_MILLIS *(PR [#5535](https://github.com/tobymao/sqlglot/pull/5535) by [@geooo109](https://github.com/geooo109))*\n- [`1d8d1ab`](https://github.com/tobymao/sqlglot/commit/1d8d1abe459053a135a46525d0a13bb861220927) - **optimizer**: annotate type for bigquery DATE_TRUNC *(PR [#5540](https://github.com/tobymao/sqlglot/pull/5540) by [@geooo109](https://github.com/geooo109))*\n- [`306ba65`](https://github.com/tobymao/sqlglot/commit/306ba6531839ea2823f5165de7bde01d17560845) - **optimizer**: annotate type for bigquery TIMESTAMP_TRUNC *(PR [#5541](https://github.com/tobymao/sqlglot/pull/5541) by [@geooo109](https://github.com/geooo109))*\n- [`6a68cca`](https://github.com/tobymao/sqlglot/commit/6a68cca97ad4bdd75c544ada0a5af0fa92ec4664) - **dremio**: support lowercase `TIME_MAPPING` formats *(PR [#5556](https://github.com/tobymao/sqlglot/pull/5556) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`f3ffe19`](https://github.com/tobymao/sqlglot/commit/f3ffe19ec01533c5f27b9d3a7b6704b83c005118) - **optimizer**: annotate type for bigquery format_time *(PR [#5559](https://github.com/tobymao/sqlglot/pull/5559) by [@geooo109](https://github.com/geooo109))*\n- [`3ab3690`](https://github.com/tobymao/sqlglot/commit/3ab369096313b418699b7942b1c513c0c66a5331) - **optimizer**: parse and annotate type for bigquery PARSE_DATETIME *(PR [#5558](https://github.com/tobymao/sqlglot/pull/5558) by [@geooo109](https://github.com/geooo109))*\n- [`e5da951`](https://github.com/tobymao/sqlglot/commit/e5da951542eb55691bc43fbbfbec4a30100de038) - **optimizer**: parse and annotate type for bigquery PARSE_TIME *(PR [#5561](https://github.com/tobymao/sqlglot/pull/5561) by [@geooo109](https://github.com/geooo109))*\n- [`902a0cd`](https://github.com/tobymao/sqlglot/commit/902a0cdfe46f693aa55612d45a2de2def21f0b8c) - **singlestore**: Added parsing/generation of UNIXTIME functions *(PR [#5562](https://github.com/tobymao/sqlglot/pull/5562) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`798e213`](https://github.com/tobymao/sqlglot/commit/798e213fd10c3b61afbd8cef621546de65fa6f26) - **duckdb**: improve transpilability of ANY_VALUE closes [#5563](https://github.com/tobymao/sqlglot/pull/5563) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c7041c7`](https://github.com/tobymao/sqlglot/commit/c7041c71250b17192c2f25fb8f33407324d332c2) - **optimizer**: parse and annotate type for bigquery BYTE_LENGHT *(PR [#5568](https://github.com/tobymao/sqlglot/pull/5568) by [@geooo109](https://github.com/geooo109))*\n- [`a6c61c3`](https://github.com/tobymao/sqlglot/commit/a6c61c34f1e168c97dd5c2b8ec071372ba593992) - **optimizer**: parse and annotate type for bigquery CODE_POINTS_TO_STRING *(PR [#5569](https://github.com/tobymao/sqlglot/pull/5569) by [@geooo109](https://github.com/geooo109))*\n- [`2a33339`](https://github.com/tobymao/sqlglot/commit/2a333395cde71936df911488afcff92cae735e11) - **optimizer**: annotate type for bigquery REPLACE *(PR [#5572](https://github.com/tobymao/sqlglot/pull/5572) by [@geooo109](https://github.com/geooo109))*\n- [`1e6f813`](https://github.com/tobymao/sqlglot/commit/1e6f81343de641e588f1a05ce7dc01bed72bd849) - **optimizer**: annotate type for bigquery REGEXP_EXTRACT_ALL *(PR [#5573](https://github.com/tobymao/sqlglot/pull/5573) by [@geooo109](https://github.com/geooo109))*\n- [`eb09e6e`](https://github.com/tobymao/sqlglot/commit/eb09e6e32491a05846488de7b72b1dca0e0a2669) - **optimizer**: parse and annotate type for bigquery TRANSLATE *(PR [#5575](https://github.com/tobymao/sqlglot/pull/5575) by [@geooo109](https://github.com/geooo109))*\n- [`f9a522b`](https://github.com/tobymao/sqlglot/commit/f9a522b26cd5d643b8b18fa64d70f2a3f0ff2d2c) - **optimizer**: parse and annotate type for bigquery SOUNDEX *(PR [#5576](https://github.com/tobymao/sqlglot/pull/5576) by [@geooo109](https://github.com/geooo109))*\n- [`51da41b`](https://github.com/tobymao/sqlglot/commit/51da41b90ce421b154e45add28353ac044640a1c) - **optimizer**: annotate type for bigquery MD5 *(PR [#5577](https://github.com/tobymao/sqlglot/pull/5577) by [@geooo109](https://github.com/geooo109))*\n- [`bcf302f`](https://github.com/tobymao/sqlglot/commit/bcf302ff6ad2d0adfc29f708a8b53b5c0e547619) - **optimizer**: annotate type for bigquery MIN/MAX BY *(PR [#5579](https://github.com/tobymao/sqlglot/pull/5579) by [@geooo109](https://github.com/geooo109))*\n- [`c501d9e`](https://github.com/tobymao/sqlglot/commit/c501d9e6f58e4880e4d23f21f53f72dcb5fdaa8c) - **optimizer**: parse and annotate type for bigquery GROUPING *(PR [#5581](https://github.com/tobymao/sqlglot/pull/5581) by [@geooo109](https://github.com/geooo109))*\n- [`8612825`](https://github.com/tobymao/sqlglot/commit/86128253f911b733d45b073356e3b8ddf261c22b) - **spark**: generate date/time ops as interval binary ops *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`8fda774`](https://github.com/tobymao/sqlglot/commit/8fda774b7a9b0c66948349dfe030d3c122ff6eee) - **singlestore**: Added parsing and generation of JSON_EXTRACT *(PR [#5555](https://github.com/tobymao/sqlglot/pull/5555) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`82cc954`](https://github.com/tobymao/sqlglot/commit/82cc9549a875211a400e5c4e818b05ca48a0a9f4) - **exasol**: map div function to IntDiv in exasol dialect *(PR [#5593](https://github.com/tobymao/sqlglot/pull/5593) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`eb0fe68`](https://github.com/tobymao/sqlglot/commit/eb0fe68d6b5977053c871badf2f5c1895b3e1c66) - **trino**: add JSON_VALUE function support with RETURNING clause *(PR [#5590](https://github.com/tobymao/sqlglot/pull/5590) by [@rev-rwasilewski](https://github.com/rev-rwasilewski))*\n- [`9e95c11`](https://github.com/tobymao/sqlglot/commit/9e95c115ea0304d9ccb4cb0be8389f5ff5f2a952) - **exasol**: mapped weekofyear to week in Exasol dialect *(PR [#5594](https://github.com/tobymao/sqlglot/pull/5594) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`8f013c3`](https://github.com/tobymao/sqlglot/commit/8f013c37a412ca5978889c1e47b0c6f7add0715d) - **singlestore**: Fixed parsing of DATE function *(PR [#5601](https://github.com/tobymao/sqlglot/pull/5601) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`a4a299a`](https://github.com/tobymao/sqlglot/commit/a4a299acbaf4461f0c2b470bc4e9e9590515eda7) - transpile `TO_CHAR` from Dremio to Databricks *(PR [#5598](https://github.com/tobymao/sqlglot/pull/5598) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`093f35c`](https://github.com/tobymao/sqlglot/commit/093f35c201c3c22c3a14c6f8de26c06246bdf19c) - **dremio**: handle `DATE_FORMAT`, `TO_DATE`, and `TO_TIMESTAMP` *(PR [#5597](https://github.com/tobymao/sqlglot/pull/5597) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`02e60e7`](https://github.com/tobymao/sqlglot/commit/02e60e73fc0c2dae815aa225be247a17ccdf4b82) - **singlestore**: desugarize DAYNAME into DATE_FORMAT *(PR [#5610](https://github.com/tobymao/sqlglot/pull/5610) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`7b180bd`](https://github.com/tobymao/sqlglot/commit/7b180bdc3da9e39946c22970bd2523f7d8beaf29) - **parser**: raise if query modifier is specified multiple times *(PR [#5608](https://github.com/tobymao/sqlglot/pull/5608) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5604](https://github.com/tobymao/sqlglot/issues/5604) opened by [@bricct](https://github.com/bricct)*\n- [`442eafc`](https://github.com/tobymao/sqlglot/commit/442eafcb00a2650930bd6023aa9a5febfebbe796) - **singlestore**: Added parsing of HOUR function *(PR [#5612](https://github.com/tobymao/sqlglot/pull/5612) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`5320359`](https://github.com/tobymao/sqlglot/commit/532035978605efd1d43de75aafca750e2894c0b9) - **singlestore**: Added parsing of MICROSECOND function *(PR [#5619](https://github.com/tobymao/sqlglot/pull/5619) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`db1db97`](https://github.com/tobymao/sqlglot/commit/db1db9732352187629df853ad937ebaf4abfe487) - **doris**: update exp.UniqueKeyProperty SQL generation logic *(PR [#5613](https://github.com/tobymao/sqlglot/pull/5613) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`54623a6`](https://github.com/tobymao/sqlglot/commit/54623a6b85432272703f12a197b05ced78529f90) - **singlestore**: Added parsing of MINUTE function *(PR [#5620](https://github.com/tobymao/sqlglot/pull/5620) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`565c9f8`](https://github.com/tobymao/sqlglot/commit/565c9f8c55cfbef5d3a9e1470551f1dc4416825e) - **singlestore**: Added generation of DAYOFWEEK_ISO function *(PR [#5627](https://github.com/tobymao/sqlglot/pull/5627) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`8db916e`](https://github.com/tobymao/sqlglot/commit/8db916e2f2ce241bdff130d626f98df182b48f3e) - **singlestore**: Added parsing of WEEKDAY function *(PR [#5624](https://github.com/tobymao/sqlglot/pull/5624) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`aa6274a`](https://github.com/tobymao/sqlglot/commit/aa6274a0ea647df1251563945635260a6ddd4972) - **singlestore**: Fixed generation of DAY_OF_MONTH function *(PR [#5629](https://github.com/tobymao/sqlglot/pull/5629) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`dee44b8`](https://github.com/tobymao/sqlglot/commit/dee44b8c1d70ca6079867896fb68cad256909dad) - **singlestore**: Added parsing of MONTHNAME function *(PR [#5623](https://github.com/tobymao/sqlglot/pull/5623) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`deebf0c`](https://github.com/tobymao/sqlglot/commit/deebf0c3cc379e28c4ab66b6bb7a9c84c14e88c6) - **singlestore**: Added parsing of SECOND function *(PR [#5621](https://github.com/tobymao/sqlglot/pull/5621) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`12a60b9`](https://github.com/tobymao/sqlglot/commit/12a60b99b6b2b0673b57218c691794deb67aa3a5) - **singlestore**: Removed redundant deletions from TRANSFORMS *(PR [#5632](https://github.com/tobymao/sqlglot/pull/5632) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`36602a2`](https://github.com/tobymao/sqlglot/commit/36602a2ecc9ffca98e89044d23e40f33c6ed71e4) - **duckdb**: parse LIST_FILTER into ArrayFilter closes [#5633](https://github.com/tobymao/sqlglot/pull/5633) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`0188d21`](https://github.com/tobymao/sqlglot/commit/0188d21d443c991a528eb9d220459890b7dca477) - **duckdb**: parse LIST_TRANSFORM into Transform closes [#5634](https://github.com/tobymao/sqlglot/pull/5634) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b117d59`](https://github.com/tobymao/sqlglot/commit/b117d59f3c43f6f44cd0ccdf22717f7bcd990889) - **dremio**: add dremio date_add and date_sub parsing *(PR [#5617](https://github.com/tobymao/sqlglot/pull/5617) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`999b9e7`](https://github.com/tobymao/sqlglot/commit/999b9e793c0819a4d2af6400fc924946d26b3e6f) - **singlestore**: Changed generation of exp.TsOrDsToDate to handle case when format is not provided *(PR [#5639](https://github.com/tobymao/sqlglot/pull/5639) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`b556e97`](https://github.com/tobymao/sqlglot/commit/b556e97f8cfbde21c0a921ac1c01c9e4f2ec2535) - **singlestore**: Marked exp.All as unsupported *(PR [#5640](https://github.com/tobymao/sqlglot/pull/5640) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`c076694`](https://github.com/tobymao/sqlglot/commit/c0766946e6799fb61c38e855fd18812d08a5c251) - **clickhouse**: support custom partition key expressions *(PR [#5645](https://github.com/tobymao/sqlglot/pull/5645) by [@GaliFFun](https://github.com/GaliFFun))*\n- [`cab62b0`](https://github.com/tobymao/sqlglot/commit/cab62b06ce926e3116a6a45a9c57e4901cd8a281) - **doris**: add support for BUILD and REFRESH properties in materialized view *(PR [#5614](https://github.com/tobymao/sqlglot/pull/5614) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`af0b299`](https://github.com/tobymao/sqlglot/commit/af0b299561914953b30ab36004e53dcb92d39e1c) - **optimizer**: Qualify columns generated by exp.Aliases *(PR [#5647](https://github.com/tobymao/sqlglot/pull/5647) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5638](https://github.com/tobymao/sqlglot/issues/5638) opened by [@catlynkong](https://github.com/catlynkong)*\n- [`981e0e7`](https://github.com/tobymao/sqlglot/commit/981e0e70a304665e746158c859bcc81f99384685) - **doris**: add support for PARTITION BY LIST *(PR [#5615](https://github.com/tobymao/sqlglot/pull/5615) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`53aa8fe`](https://github.com/tobymao/sqlglot/commit/53aa8fe7f188012f765066f32c4179035fff036d) - **tsql**: support alter table with check closes [#5649](https://github.com/tobymao/sqlglot/pull/5649) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`23cac6c`](https://github.com/tobymao/sqlglot/commit/23cac6c58099a9ac818ac5d3970a427ca3579cca) - **exasol**: Add support for GROUP_CONCAT and LISTAGG functions *(PR [#5646](https://github.com/tobymao/sqlglot/pull/5646) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`d087ac8`](https://github.com/tobymao/sqlglot/commit/d087ac89376df5ab16de99c8b67f99060f0a6170) - **bigquery**: Add support for ml.generate_embedding function *(PR [#5652](https://github.com/tobymao/sqlglot/pull/5652) by [@rloredo](https://github.com/rloredo))*\n- [`e71bcb5`](https://github.com/tobymao/sqlglot/commit/e71bcb51181de63c8ad13004216506529fcf9644) - **dremio**: support array_generate_range *(PR [#5653](https://github.com/tobymao/sqlglot/pull/5653) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`edbd04b`](https://github.com/tobymao/sqlglot/commit/edbd04b6a91b1a6f76e4fa938098ba5ed581ba72) - **singlestore**: Fixed generation of exp.RegexpLike *(PR [#5663](https://github.com/tobymao/sqlglot/pull/5663) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`4992edb`](https://github.com/tobymao/sqlglot/commit/4992edbb79f4922917cc5ce5aa687e6f7da7798c) - **singlestore**: Fixed exp.Xor generation *(PR [#5662](https://github.com/tobymao/sqlglot/pull/5662) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`20de3d3`](https://github.com/tobymao/sqlglot/commit/20de3d37cdae0705c67f80fbacbe024a62f34657) - **singlestore**: Fixed parsing/generation of exp.Hll *(PR [#5664](https://github.com/tobymao/sqlglot/pull/5664) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`1a60a5a`](https://github.com/tobymao/sqlglot/commit/1a60a5a845c7431d7d3d7ccb71119699316f4b41) - **singlestore**: Added parsing/generation of JSON_ARRAY_CONTAINS function *(PR [#5661](https://github.com/tobymao/sqlglot/pull/5661) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`f662dc0`](https://github.com/tobymao/sqlglot/commit/f662dc0b47fd14d00899c14a899756a5ba1fe9da) - **singlestore**: Fixed generation of exp.ApproxDistinct *(PR [#5666](https://github.com/tobymao/sqlglot/pull/5666) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e0db0a9`](https://github.com/tobymao/sqlglot/commit/e0db0a95d3cb7614242dbd1b439d408e7e7bd475) - **optimizer**: add parse and annotate type for bigquery FARM_FINGERPRINT *(PR [#5667](https://github.com/tobymao/sqlglot/pull/5667) by [@geooo109](https://github.com/geooo109))*\n- [`dcd4ef7`](https://github.com/tobymao/sqlglot/commit/dcd4ef769727ed1227911f2d9a85244d61173003) - **singlestore**: Fixed exp.CountIf generation *(PR [#5668](https://github.com/tobymao/sqlglot/pull/5668) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e431e85`](https://github.com/tobymao/sqlglot/commit/e431e851c2c5d20f049adbc38e370a64d39c346f) - **singlestore**: Fixed generation of exp.LogicalOr *(PR [#5669](https://github.com/tobymao/sqlglot/pull/5669) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`56588c7`](https://github.com/tobymao/sqlglot/commit/56588c7e22b4db4f0e44696a460483ca1e549163) - **bigquery**: Add support for vector_search function. Move predict to BigQuery dialect. *(PR [#5660](https://github.com/tobymao/sqlglot/pull/5660) by [@rloredo](https://github.com/rloredo))*\n- [`f0d2cc2`](https://github.com/tobymao/sqlglot/commit/f0d2cc2b0f72340172ecd154f632aa6a24c15512) - **singlestore**: Fixed generation of exp.LogicalAnd *(PR [#5671](https://github.com/tobymao/sqlglot/pull/5671) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`a688a0f`](https://github.com/tobymao/sqlglot/commit/a688a0f0d70f87139e531d1419b338b695bec384) - **optimizer**: parse and annotate type for bigquery APPROX_TOP_COUNT *(PR [#5670](https://github.com/tobymao/sqlglot/pull/5670) by [@geooo109](https://github.com/geooo109))*\n- [`fa8d571`](https://github.com/tobymao/sqlglot/commit/fa8d57132b1d21d92eb5de3ba88b41f880e14889) - **singlestore**: Fixed generation/parsing of exp.ApproxQuantile *(PR [#5672](https://github.com/tobymao/sqlglot/pull/5672) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`9955ebe`](https://github.com/tobymao/sqlglot/commit/9955ebe90d3421815738ecb643806add755c5df3) - **singlestore**: Fixed parsing/generation of exp.Variance *(PR [#5673](https://github.com/tobymao/sqlglot/pull/5673) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`3c93fcc`](https://github.com/tobymao/sqlglot/commit/3c93fcce96ec82e78753f6c9dd5fb0e730a82058) - **optimizer**: parse and annotate type for bigquery APPROX_TOP_SUM *(PR [#5675](https://github.com/tobymao/sqlglot/pull/5675) by [@geooo109](https://github.com/geooo109))*\n- [`60cbb9d`](https://github.com/tobymao/sqlglot/commit/60cbb9d0e3c9b5a36c1368c9b5bb05def8ce8658) - **dremio**: add CURRENT_DATE_UTC *(PR [#5674](https://github.com/tobymao/sqlglot/pull/5674) by [@jasonthomassql](https://github.com/jasonthomassql))*\n  - :arrow_lower_right: *addresses issue [#5655](https://github.com/tobymao/sqlglot/issues/5655) opened by [@jasonthomassql](https://github.com/jasonthomassql)*\n- [`741d45a`](https://github.com/tobymao/sqlglot/commit/741d45a0ca7c1bad67da4393cd10cc9cfa49ea68) - **optimizer**: parse and annotate type for bigquery FROM/TO_BASE32 *(PR [#5676](https://github.com/tobymao/sqlglot/pull/5676) by [@geooo109](https://github.com/geooo109))*\n- [`9ae045c`](https://github.com/tobymao/sqlglot/commit/9ae045c0405e43b148e3b9261825288ebf09100c) - **optimizer**: parse and annotate type for bigquery FROM_HEX *(PR [#5679](https://github.com/tobymao/sqlglot/pull/5679) by [@geooo109](https://github.com/geooo109))*\n- [`5a22a25`](https://github.com/tobymao/sqlglot/commit/5a22a254143978989027f6e7f6163019a34f112a) - **optimizer**: annotate type for bigquery TO_HEX *(PR [#5680](https://github.com/tobymao/sqlglot/pull/5680) by [@geooo109](https://github.com/geooo109))*\n- [`d920ac3`](https://github.com/tobymao/sqlglot/commit/d920ac3886ce006d76616bc31884ee2f5c4162bc) - **singlestore**: Fixed parsing/generation of exp.RegexpExtractAll *(PR [#5692](https://github.com/tobymao/sqlglot/pull/5692) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`260c72b`](https://github.com/tobymao/sqlglot/commit/260c72befc0510ebe1d007284c0eef9343de20d7) - **singlestore**: Fixed parsing/generation of exp.Contains *(PR [#5684](https://github.com/tobymao/sqlglot/pull/5684) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`081dc67`](https://github.com/tobymao/sqlglot/commit/081dc673b89d3d8d0709b29e359142297ff64536) - **singlestore**: Fixed generaion/parsing of exp.VariancePop *(PR [#5682](https://github.com/tobymao/sqlglot/pull/5682) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`eb538bf`](https://github.com/tobymao/sqlglot/commit/eb538bf225645d0a54d614733e447c13cf91a37a) - **singlestore**: Fixed generation of exp.Chr *(PR [#5683](https://github.com/tobymao/sqlglot/pull/5683) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`32d9dd1`](https://github.com/tobymao/sqlglot/commit/32d9dd1309ce0876114f57993596c4456aa1d50f) - **singlestore**: Fixed exp.MD5Digest generation *(PR [#5688](https://github.com/tobymao/sqlglot/pull/5688) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`5c1eb2d`](https://github.com/tobymao/sqlglot/commit/5c1eb2df5dd3dcc6ed2c8204cec56b5c3d276f87) - **optimizer**: parse and annotate type for bq PARSE_BIG/NUMERIC *(PR [#5690](https://github.com/tobymao/sqlglot/pull/5690) by [@geooo109](https://github.com/geooo109))*\n- [`6f88500`](https://github.com/tobymao/sqlglot/commit/6f885007a075339cf20034459571a6ae821c61c0) - **singlestore**: Fixed exp.IsAscii generation *(PR [#5687](https://github.com/tobymao/sqlglot/pull/5687) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`311373d`](https://github.com/tobymao/sqlglot/commit/311373d22134de906d1c1cef019541e85e2f7c9f) - **optimizer**: parse and annotate type for bq CODE_POINTS_TO_BYTES *(PR [#5686](https://github.com/tobymao/sqlglot/pull/5686) by [@geooo109](https://github.com/geooo109))*\n- [`79d9de1`](https://github.com/tobymao/sqlglot/commit/79d9de1745598f8f3ae2c82c1389dd455c946a09) - **optimizer**: parse and annotate type for bq TO_CODE_POINTS *(PR [#5685](https://github.com/tobymao/sqlglot/pull/5685) by [@geooo109](https://github.com/geooo109))*\n- [`5df3ea9`](https://github.com/tobymao/sqlglot/commit/5df3ea92f59125955124ea1883b777b489db3042) - **optimizer**: parse and annotate type for bq SAFE_CONVERT_BYTES_TO_STRING *(PR [#5681](https://github.com/tobymao/sqlglot/pull/5681) by [@geooo109](https://github.com/geooo109))*\n- [`c832746`](https://github.com/tobymao/sqlglot/commit/c832746018fbc2c531d5b2a7c7f8cd5d78e511ff) - **optimizer**: parse and annotate type for bigquery APPROX_QUANTILES *(PR [#5678](https://github.com/tobymao/sqlglot/pull/5678) by [@geooo109](https://github.com/geooo109))*\n- [`8fa5ae8`](https://github.com/tobymao/sqlglot/commit/8fa5ae8a61c698abaea265b4950390ea3ddfa7e9) - **singlestore**: Fixed generation/parsing of exp.RegexpExtract *(PR [#5691](https://github.com/tobymao/sqlglot/pull/5691) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`d6d409a`](https://github.com/tobymao/sqlglot/commit/d6d409a548042063f80d02dfaf5b61a0096d1d50) - **singlestore**: Fixed generaion of exp.Repeat *(PR [#5693](https://github.com/tobymao/sqlglot/pull/5693) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`b7db08b`](https://github.com/tobymao/sqlglot/commit/b7db08b96c7d7d02ec54f26b8749b3d57f021d8b) - **singlestore**: Fixed generation of exp.StartsWith *(PR [#5694](https://github.com/tobymao/sqlglot/pull/5694) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`87b04ef`](https://github.com/tobymao/sqlglot/commit/87b04ef0fc2df5064be9e6b75b264cff0639face) - **singlestore**: Fixed generation of exp.FromBase *(PR [#5695](https://github.com/tobymao/sqlglot/pull/5695) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`9c1d0fd`](https://github.com/tobymao/sqlglot/commit/9c1d0fdac9acd3fb3109ca3d3cae9c9ffaed1a7d) - **duckdb**: transpile array unique aggregation closes [#5689](https://github.com/tobymao/sqlglot/pull/5689) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`99e169e`](https://github.com/tobymao/sqlglot/commit/99e169ea13d5be3712a47f6b55b98a4764a3c24d) - **optimizer**: parse and annotate type for bq BOOL *(PR [#5697](https://github.com/tobymao/sqlglot/pull/5697) by [@geooo109](https://github.com/geooo109))*\n- [`3f31770`](https://github.com/tobymao/sqlglot/commit/3f31770c793f464fcac1ce2b8dfa03d4b7f0231c) - **optimizer**: parse and annotate type for bq FLOAT64 *(PR [#5700](https://github.com/tobymao/sqlglot/pull/5700) by [@geooo109](https://github.com/geooo109))*\n- [`f6f8f56`](https://github.com/tobymao/sqlglot/commit/f6f8f56a59d550dfc7dfcab0c3b9a6885c7e758a) - **singlestore**: Fixed parsing/generation of exp.JSONFormat *(PR [#5706](https://github.com/tobymao/sqlglot/pull/5706) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`de2fe15`](https://github.com/tobymao/sqlglot/commit/de2fe1503b5bb003431d1f0c7b9ae87932a6cc1c) - **optimizer**: annotate type for bq CONTAINS_SUBSTR *(PR [#5705](https://github.com/tobymao/sqlglot/pull/5705) by [@geooo109](https://github.com/geooo109))*\n- [`a78146e`](https://github.com/tobymao/sqlglot/commit/a78146e37bfc972050b4467c39769407061e9bc3) - **singlestore**: Fixed parsing/generation of exp.DateBin *(PR [#5709](https://github.com/tobymao/sqlglot/pull/5709) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`ab0c985`](https://github.com/tobymao/sqlglot/commit/ab0c985424ae9d9340eafd15ecdc9b31bdd8837c) - **singlestore**: Marked exp.Reduce finish argument as unsupported *(PR [#5707](https://github.com/tobymao/sqlglot/pull/5707) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`770888f`](https://github.com/tobymao/sqlglot/commit/770888f4e9a9061329e3c416f968f7dd9639fb81) - **optimizer**: annotate type for bq NORMALIZE *(PR [#5711](https://github.com/tobymao/sqlglot/pull/5711) by [@geooo109](https://github.com/geooo109))*\n- [`506033f`](https://github.com/tobymao/sqlglot/commit/506033f299f7a4c28f6efd8bf715be5dcf73e929) - **optimizer**: parse and annotate type for bq NORMALIZE_AND_CASEFOLD *(PR [#5712](https://github.com/tobymao/sqlglot/pull/5712) by [@geooo109](https://github.com/geooo109))*\n- [`848aea1`](https://github.com/tobymao/sqlglot/commit/848aea1dbaaeb580b633796dcca06c28314b9c3e) - **optimizer**: parse and annotate type for bq OCTET_LENGTH *(PR [#5713](https://github.com/tobymao/sqlglot/pull/5713) by [@geooo109](https://github.com/geooo109))*\n- [`727bf83`](https://github.com/tobymao/sqlglot/commit/727bf8378f232188d35834d980b035552999ea3b) - add support for REVOKE DDL *(PR [#5703](https://github.com/tobymao/sqlglot/pull/5703) by [@newtonapple](https://github.com/newtonapple))*\n- [`baffd2c`](https://github.com/tobymao/sqlglot/commit/baffd2c0be9657683781f3f8831c47e32dbf68bb) - **optimizer**: parse and annotate type for bq REGEXP_INSTR *(PR [#5710](https://github.com/tobymao/sqlglot/pull/5710) by [@geooo109](https://github.com/geooo109))*\n- [`b79eb19`](https://github.com/tobymao/sqlglot/commit/b79eb198cc21203efa82128b357d435338e9133d) - **optimizer**: annotate type for bq ROW_NUMBER *(PR [#5716](https://github.com/tobymao/sqlglot/pull/5716) by [@geooo109](https://github.com/geooo109))*\n- [`f709bef`](https://github.com/tobymao/sqlglot/commit/f709bef3af7cd0daa25fe3d58b1753c3e65720ef) - **optimizer**: annotate type for bq FIRST_VALUE *(PR [#5718](https://github.com/tobymao/sqlglot/pull/5718) by [@geooo109](https://github.com/geooo109))*\n- [`b9ae9e5`](https://github.com/tobymao/sqlglot/commit/b9ae9e534dee1e32fccbf22cab9bc17fbd920629) - **singlestore**: Implemeted generation of exp.TsOrDiToDi *(PR [#5724](https://github.com/tobymao/sqlglot/pull/5724) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`9b14fff`](https://github.com/tobymao/sqlglot/commit/9b14fffd2c9404f76a3faced2ec9d6eaac8feb01) - **singlestore**: Implemented generation of exp.DateToDi *(PR [#5717](https://github.com/tobymao/sqlglot/pull/5717) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`07d8c23`](https://github.com/tobymao/sqlglot/commit/07d8c2347baba6523310c4d31cddfb0e5c0eddc1) - **singlestore**: Implemented generation of exp.DiToDate *(PR [#5721](https://github.com/tobymao/sqlglot/pull/5721) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`ad34a85`](https://github.com/tobymao/sqlglot/commit/ad34a855a433bc0f51a707cbcb66f8dce667a562) - **singlestore**: Implemented generation of exp.FromTimeZone *(PR [#5723](https://github.com/tobymao/sqlglot/pull/5723) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`29d5e4f`](https://github.com/tobymao/sqlglot/commit/29d5e4f62a799f35c0904a23cedacc6efa95a63b) - **singlestore**: Implemented generation of exp.DatetimeAdd *(PR [#5728](https://github.com/tobymao/sqlglot/pull/5728) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`15a9061`](https://github.com/tobymao/sqlglot/commit/15a906170e5d5cdaa207ec7607edfdd7d4a8b774) - **optimizer**: annotate type for bq PERCENTILE_DISC *(PR [#5722](https://github.com/tobymao/sqlglot/pull/5722) by [@geooo109](https://github.com/geooo109))*\n- [`7d49609`](https://github.com/tobymao/sqlglot/commit/7d4960963f0ef70b96f5b969bb008d2742e833ea) - **optimizer**: annotate type for bq NTH_VALUE *(PR [#5720](https://github.com/tobymao/sqlglot/pull/5720) by [@geooo109](https://github.com/geooo109))*\n- [`d41acf1`](https://github.com/tobymao/sqlglot/commit/d41acf11221bee30a5ae089cbac9b158ed3dd515) - **optimizer**: annotate type for bq LEAD *(PR [#5719](https://github.com/tobymao/sqlglot/pull/5719) by [@geooo109](https://github.com/geooo109))*\n- [`113809a`](https://github.com/tobymao/sqlglot/commit/113809a07efee0f12758bd2571c8515885568466) - **singlestore**: Implemented exp.TimeStrToDate generation *(PR [#5725](https://github.com/tobymao/sqlglot/pull/5725) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`cf63d0d`](https://github.com/tobymao/sqlglot/commit/cf63d0df4c2f58b2cf0c87e2a3a6f63f836a50a1) - **dremio**: add regexp_like and alias regexp_matches *(PR [#5731](https://github.com/tobymao/sqlglot/pull/5731) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`e42160f`](https://github.com/tobymao/sqlglot/commit/e42160f27fa68828898969073f2f4a0014f5e3e9) - **dremio**: support alias repeatstr *(PR [#5730](https://github.com/tobymao/sqlglot/pull/5730) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`ff12130`](https://github.com/tobymao/sqlglot/commit/ff12130c23a215917f20fda7d50322f1cb7de599) - **optimizer**: annotate type for bq PERNCENTILE_CONT *(PR [#5729](https://github.com/tobymao/sqlglot/pull/5729) by [@geooo109](https://github.com/geooo109))*\n- [`fdb8a0a`](https://github.com/tobymao/sqlglot/commit/fdb8a0a6d0d74194255f313bd934db7fc1ce0d3f) - **optimizer**: parse and annotate type for bq FORMAT *(PR [#5715](https://github.com/tobymao/sqlglot/pull/5715) by [@geooo109](https://github.com/geooo109))*\n- [`e272292`](https://github.com/tobymao/sqlglot/commit/e272292197f2bb81ccfad1de06a95f321f0b565f) - **singlestore**: Implemented generation of exp.Time *(PR [#5727](https://github.com/tobymao/sqlglot/pull/5727) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`012bdd3`](https://github.com/tobymao/sqlglot/commit/012bdd3c8aeff180f85354ffd403fc1aa5815dcf) - **optimizer**: parse and annotate type for bq CUME_DIST *(PR [#5735](https://github.com/tobymao/sqlglot/pull/5735) by [@geooo109](https://github.com/geooo109))*\n- [`b99eaeb`](https://github.com/tobymao/sqlglot/commit/b99eaeb0c6eb3dc613e76d205e02632bd6af353b) - **optimizer**: parse and annotate type for bq DENSE_RANK *(PR [#5736](https://github.com/tobymao/sqlglot/pull/5736) by [@geooo109](https://github.com/geooo109))*\n- [`8cf6ef9`](https://github.com/tobymao/sqlglot/commit/8cf6ef92a0f43943efb0fe380f41dc09f43aca85) - **optimizer**: parse and annotate_type for bq NTILE *(PR [#5737](https://github.com/tobymao/sqlglot/pull/5737) by [@geooo109](https://github.com/geooo109))*\n- [`bb95c73`](https://github.com/tobymao/sqlglot/commit/bb95c7312c942ef987955f01e060604d60e32e83) - **optimizer**: parse and annotate type for bq RANK *(PR [#5738](https://github.com/tobymao/sqlglot/pull/5738) by [@geooo109](https://github.com/geooo109))*\n- [`8713c08`](https://github.com/tobymao/sqlglot/commit/8713c082b0aa8454a5773fc2a85e08a132dc6ce3) - **optimizer**: parse and annotate type for bq PERCENT_RANK *(PR [#5739](https://github.com/tobymao/sqlglot/pull/5739) by [@geooo109](https://github.com/geooo109))*\n- [`9ce4e31`](https://github.com/tobymao/sqlglot/commit/9ce4e31aecbde6ea1f227a7166c0f3dc9e302a66) - **optimizer**: annotate type for bq JSON_OBJECT *(PR [#5740](https://github.com/tobymao/sqlglot/pull/5740) by [@geooo109](https://github.com/geooo109))*\n- [`d35ec6e`](https://github.com/tobymao/sqlglot/commit/d35ec6e37e21cf3cec848ed55bd73128c4633cd2) - **optimizer**: annotate type for bq JSON_QUERY/JSON_QUERY_ARRAY *(PR [#5741](https://github.com/tobymao/sqlglot/pull/5741) by [@geooo109](https://github.com/geooo109))*\n- [`4753642`](https://github.com/tobymao/sqlglot/commit/4753642cfcfb1f192ec4d21a492737b27affef09) - **optimizer**: annotate type for bq JSON_EXTRACT_SCALAR *(commit by [@geooo109](https://github.com/geooo109))*\n- [`6249dbe`](https://github.com/tobymao/sqlglot/commit/6249dbe4173ad5278adf84452dcf7253a2395b91) - **singlestore**: Added generation of exp.DatetimeDiff *(PR [#5743](https://github.com/tobymao/sqlglot/pull/5743) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`113a530`](https://github.com/tobymao/sqlglot/commit/113a5308d050fd5ceacab4c6188e5eea5dd740b1) - **optimizer**: parse and annotate type for bq JSON_ARRAY_APPEND *(PR [#5747](https://github.com/tobymao/sqlglot/pull/5747) by [@geooo109](https://github.com/geooo109))*\n- [`8603705`](https://github.com/tobymao/sqlglot/commit/8603705a8e5513699adc2499389c67412eee70cb) - **singlestore**: feat(singlestore): Implemented generation of exp.DatetimeSub *(PR [#5744](https://github.com/tobymao/sqlglot/pull/5744) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`7d71c0b`](https://github.com/tobymao/sqlglot/commit/7d71c0bb576f9de3447b4780ab64a3f4d92c6432) - **singlestore**: Fixed generation of exp.DatetimeTrunc and exp.DateTrunc *(PR [#5745](https://github.com/tobymao/sqlglot/pull/5745) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`268e2c6`](https://github.com/tobymao/sqlglot/commit/268e2c694d1eb99f1fe64477bc38ed4946bf1c32) - **optimizer**: parse and annotate type for bq JSON_ARRAY_INSERT *(PR [#5748](https://github.com/tobymao/sqlglot/pull/5748) by [@geooo109](https://github.com/geooo109))*\n- [`455ec1f`](https://github.com/tobymao/sqlglot/commit/455ec1f4f8aecb5435fa4cb2912bfc21db8dd44d) - **optimizer**: parse and annotate type for bq JSON_KEYS *(PR [#5749](https://github.com/tobymao/sqlglot/pull/5749) by [@geooo109](https://github.com/geooo109))*\n- [`59895fa`](https://github.com/tobymao/sqlglot/commit/59895faa23ebe1b27938c37a7b39df87de609844) - **optimizer**: parse and annotate type for bq JSON_REMOVE *(PR [#5750](https://github.com/tobymao/sqlglot/pull/5750) by [@geooo109](https://github.com/geooo109))*\n- [`06d7df7`](https://github.com/tobymao/sqlglot/commit/06d7df7a05f2824cabf48e8d1e8a4ebca8fda496) - **optimizer**: parse and annotate type for bq JSON_SET *(PR [#5751](https://github.com/tobymao/sqlglot/pull/5751) by [@geooo109](https://github.com/geooo109))*\n- [`7f5079a`](https://github.com/tobymao/sqlglot/commit/7f5079a1b71c4dd28e98b77b5b749e074fce862c) - **singlestore**: Improved geneation of exp.DataType *(PR [#5746](https://github.com/tobymao/sqlglot/pull/5746) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`ad9405c`](https://github.com/tobymao/sqlglot/commit/ad9405cd43108ff80d16711f8b33ff57430ed686) - **singlestore**: fixed generation of exp.TimestampTrunc *(PR [#5754](https://github.com/tobymao/sqlglot/pull/5754) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`a1852f9`](https://github.com/tobymao/sqlglot/commit/a1852f93fdfe926072c12954c95796d038e15140) - **dremio**: parse date_part *(PR [#5756](https://github.com/tobymao/sqlglot/pull/5756) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`0db1df6`](https://github.com/tobymao/sqlglot/commit/0db1df617ec4f05b1ee6cf1d606272f6e799a9b9) - **singlestore**: Fixed generation of exp.DateDiff *(PR [#5752](https://github.com/tobymao/sqlglot/pull/5752) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e72b341`](https://github.com/tobymao/sqlglot/commit/e72b3419c8a367caa0e5e80030979cd94e87a40d) - **optimizer**: parse and annotate type for bq JSON_STRIP_NULLS *(PR [#5753](https://github.com/tobymao/sqlglot/pull/5753) by [@geooo109](https://github.com/geooo109))*\n- [`5de61a7`](https://github.com/tobymao/sqlglot/commit/5de61a7ab850d4e68fde4d76ee396d30d7bdef33) - **optimizer**: parse and annotate type for bq JSON_EXTRACT_STRING_ARRAY *(PR [#5758](https://github.com/tobymao/sqlglot/pull/5758) by [@geooo109](https://github.com/geooo109))*\n- [`36c9393`](https://github.com/tobymao/sqlglot/commit/36c93939575a19bd611269719c39d3d216be8cde) - **optimizer**: parse and annotate type for bq JSON LAX funcs *(PR [#5760](https://github.com/tobymao/sqlglot/pull/5760) by [@geooo109](https://github.com/geooo109))*\n- [`c443d5c`](https://github.com/tobymao/sqlglot/commit/c443d5caf2d9695856103eebfff21cb215777112) - **dremio**: parse datetype *(PR [#5759](https://github.com/tobymao/sqlglot/pull/5759) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`5172a99`](https://github.com/tobymao/sqlglot/commit/5172a99fc4d5e21a1dbe4509d6d7ab1ccfe8bff7) - **singlestore**: Fixed parsing of columns with table name *(PR [#5767](https://github.com/tobymao/sqlglot/pull/5767) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`88862b5`](https://github.com/tobymao/sqlglot/commit/88862b56bc29c8a600b4d0e4693d5846d3a577ff) - **optimizer**: annotate type for bq TO_JSON_STRING *(PR [#5762](https://github.com/tobymao/sqlglot/pull/5762) by [@geooo109](https://github.com/geooo109))*\n- [`1c551d5`](https://github.com/tobymao/sqlglot/commit/1c551d5ed3315e314013c1f063deabd9d8613e5d) - **optimizer**: parse and annotate type for bq TO_JSON *(PR [#5768](https://github.com/tobymao/sqlglot/pull/5768) by [@geooo109](https://github.com/geooo109))*\n- [`a024d48`](https://github.com/tobymao/sqlglot/commit/a024d48fedd049796329050a1f51822dd1388695) - **singlestore**: Added generation of exp.TsOrDsDiff *(PR [#5769](https://github.com/tobymao/sqlglot/pull/5769) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`1707f2d`](https://github.com/tobymao/sqlglot/commit/1707f2d7f9d3b58e8c216db638f8e572f9fe6f13) - **optimizer**: annotate type for ABS *(PR [#5770](https://github.com/tobymao/sqlglot/pull/5770) by [@geooo109](https://github.com/geooo109))*\n- [`69acc51`](https://github.com/tobymao/sqlglot/commit/69acc5142b2d4f0b30832c350aa49f16d1adabef) - **optimizer**: annotate type for bq IS_INF, IS_NAN *(PR [#5771](https://github.com/tobymao/sqlglot/pull/5771) by [@geooo109](https://github.com/geooo109))*\n- [`0da2076`](https://github.com/tobymao/sqlglot/commit/0da207652331920416b29e2cc67bdc3c3f964466) - **optimizer**: annotate type for bq CBRT *(PR [#5772](https://github.com/tobymao/sqlglot/pull/5772) by [@geooo109](https://github.com/geooo109))*\n- [`a4968cb`](https://github.com/tobymao/sqlglot/commit/a4968cb5693670c1a2e9cd2c86404dd90fd76160) - **optimizer**: annotate type for bq RAND *(PR [#5774](https://github.com/tobymao/sqlglot/pull/5774) by [@geooo109](https://github.com/geooo109))*\n- [`dd7781a`](https://github.com/tobymao/sqlglot/commit/dd7781a15b842a5826714958ed7af9024903cd1e) - **singlestore**: Fixed generation of exp.Collate *(PR [#5775](https://github.com/tobymao/sqlglot/pull/5775) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`fb684cb`](https://github.com/tobymao/sqlglot/commit/fb684cbdb6178ddc441f598cc1a6e914291cd00e) - **singelstore**: Fixed generation of exp.RegexpILike *(PR [#5777](https://github.com/tobymao/sqlglot/pull/5777) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`3e63350`](https://github.com/tobymao/sqlglot/commit/3e63350bd1d58b510cecd1a573d27be3fd2565ce) - **optimizer**: parse and annotate type for bq ACOS *(PR [#5776](https://github.com/tobymao/sqlglot/pull/5776) by [@geooo109](https://github.com/geooo109))*\n- [`8705a78`](https://github.com/tobymao/sqlglot/commit/8705a787df034b4cecb4ba95e9599772c5561ba9) - **singlestore**: Fixed generation of exp.CastToStrType *(PR [#5778](https://github.com/tobymao/sqlglot/pull/5778) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e3c35ad`](https://github.com/tobymao/sqlglot/commit/e3c35ade797f46549cc803e1acd8816041713a10) - **singlestore**: Fixed generation of exp.UnicodeString *(PR [#5773](https://github.com/tobymao/sqlglot/pull/5773) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`2be9d01`](https://github.com/tobymao/sqlglot/commit/2be9d01830c778186dc274c94c6db0dd6c4116d1) - **optimizer**: parse and annotate type for bq ACOSH *(PR [#5779](https://github.com/tobymao/sqlglot/pull/5779) by [@geooo109](https://github.com/geooo109))*\n- [`7da2f31`](https://github.com/tobymao/sqlglot/commit/7da2f31d6613f16585e98c3fa1f592c617ae40c9) - **optimizer**: parse and annotate type for bq ASIN/H *(PR [#5783](https://github.com/tobymao/sqlglot/pull/5783) by [@geooo109](https://github.com/geooo109))*\n- [`341ea83`](https://github.com/tobymao/sqlglot/commit/341ea83a07c707fdbf565b8d9ef4b9b6341ed1d5) - **optimizer**: parse and annotate type for bq ATAN/H/2 *(PR [#5784](https://github.com/tobymao/sqlglot/pull/5784) by [@geooo109](https://github.com/geooo109))*\n- [`be54a45`](https://github.com/tobymao/sqlglot/commit/be54a458413ce3be6c321e5f4feb3e5df5ee6d08) - **singlestore**: Implemented generation of exp.Cbrt *(PR [#5782](https://github.com/tobymao/sqlglot/pull/5782) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`aa360cb`](https://github.com/tobymao/sqlglot/commit/aa360cb0e204aa056557ff8b15aa2d4f678430e6) - **databricks**: use regexp_like as it exists *(PR [#5781](https://github.com/tobymao/sqlglot/pull/5781) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`c2a1ad4`](https://github.com/tobymao/sqlglot/commit/c2a1ad4050771401a5b26bcadd90060e4527fbff) - **optimizer**: parse and annotate type for bq COT/H *(PR [#5786](https://github.com/tobymao/sqlglot/pull/5786) by [@geooo109](https://github.com/geooo109))*\n- [`316ae91`](https://github.com/tobymao/sqlglot/commit/316ae913d8b1a63f3071ebb1b826328108d74cef) - **singlestore**: Added handling of UTC_DATE and exp.CurrentDate *(PR [#5785](https://github.com/tobymao/sqlglot/pull/5785) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`2c6d237`](https://github.com/tobymao/sqlglot/commit/2c6d23742ea9fcc2b9c784315d3d5364e360fea5) - **optimizer**: parse and annotate type for bq CSC/H *(PR [#5787](https://github.com/tobymao/sqlglot/pull/5787) by [@geooo109](https://github.com/geooo109))*\n- [`8a35076`](https://github.com/tobymao/sqlglot/commit/8a350763c2337f6910a5f0e19af387ba488fcb70) - **optimizer**: parse and annotate type for bq SEC/H *(PR [#5788](https://github.com/tobymao/sqlglot/pull/5788) by [@geooo109](https://github.com/geooo109))*\n- [`566bfb2`](https://github.com/tobymao/sqlglot/commit/566bfb2a64a64b74da63b3a89d68caf702ab6522) - **singlestore**: Added support of UTC_TIME and CURRENT_TIME *(PR [#5789](https://github.com/tobymao/sqlglot/pull/5789) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`79901cb`](https://github.com/tobymao/sqlglot/commit/79901cb506737ae1932fa44a705858d2597ee587) - **optimizer**: parse and annotate type for bq SIN\\H *(PR [#5790](https://github.com/tobymao/sqlglot/pull/5790) by [@geooo109](https://github.com/geooo109))*\n- [`74fb547`](https://github.com/tobymao/sqlglot/commit/74fb5476def1b389da425885db56bd6592fd7f78) - **optimizer**: parse and annotate type for bq RANGE_BUCKET *(PR [#5793](https://github.com/tobymao/sqlglot/pull/5793) by [@geooo109](https://github.com/geooo109))*\n- [`eca65e8`](https://github.com/tobymao/sqlglot/commit/eca65e8b79f65850b014a4cb7913ba4a5861dbe9) - **optimizer**: parse and annotate type for bq COSINE/EUCLIDEAN_DISTANCE *(PR [#5792](https://github.com/tobymao/sqlglot/pull/5792) by [@geooo109](https://github.com/geooo109))*\n- [`a180d3f`](https://github.com/tobymao/sqlglot/commit/a180d3f2f9f3938611027269028c03274aa1889c) - **optimizer**: parse and annotate type for bq SAFE math funcs *(PR [#5797](https://github.com/tobymao/sqlglot/pull/5797) by [@geooo109](https://github.com/geooo109))*\n- [`fc7ad7a`](https://github.com/tobymao/sqlglot/commit/fc7ad7a4d953424b56542eacfe1835f5789921c7) - **snowflake**: parse ALTER SESSION  *(PR [#5734](https://github.com/tobymao/sqlglot/pull/5734) by [@tekumara](https://github.com/tekumara))*\n- [`8ec1a6c`](https://github.com/tobymao/sqlglot/commit/8ec1a6cf5a8edc2d834c713ce0fd8d87237f11ed) - **optimizer**: annotate type for bq STRING_AGG *(PR [#5798](https://github.com/tobymao/sqlglot/pull/5798) by [@geooo109](https://github.com/geooo109))*\n- [`dd97bfa`](https://github.com/tobymao/sqlglot/commit/dd97bfa1dc2f86b727c55b06b3c54b18c02e360d) - **optimizer**: annotate type for bq DATETIME_TRUNC *(PR [#5799](https://github.com/tobymao/sqlglot/pull/5799) by [@geooo109](https://github.com/geooo109))*\n- [`d3e9dda`](https://github.com/tobymao/sqlglot/commit/d3e9dda183695dd1e4a9832a6671bccc6db561a0) - **optimizer**: annotate type for bq GENERATE_UUID *(commit by [@geooo109](https://github.com/geooo109))*\n- [`cf1d1e3`](https://github.com/tobymao/sqlglot/commit/cf1d1e3e0ef9e6cd1b1c6128c63ddf06c30f1339) - **optimizer**: annotate type for snowflake's REVERSE function *(PR [#5803](https://github.com/tobymao/sqlglot/pull/5803) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`1d07c52`](https://github.com/tobymao/sqlglot/commit/1d07c52badb2e392e6895cbb275d2224789366c9) - **SingleStore**: Implemented generation of CURRENT_DATETIME *(PR [#5816](https://github.com/tobymao/sqlglot/pull/5816) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`cad4fd0`](https://github.com/tobymao/sqlglot/commit/cad4fd0c5b0ec90e693fa6883af0ab287b921019) - **singlestore**: Added handling of exp.JSONObject *(PR [#5817](https://github.com/tobymao/sqlglot/pull/5817) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e3cb076`](https://github.com/tobymao/sqlglot/commit/e3cb0766bd5c3ccb31ea52cfc76201f548798dc1) - **singlestore**: Implemented generation of exp.StandardHash *(PR [#5823](https://github.com/tobymao/sqlglot/pull/5823) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`0198282`](https://github.com/tobymao/sqlglot/commit/0198282a82bbf3e81476e164718d63fd1210acdc) - **optimizer**: : Update tests for concat string function *(PR [#5809](https://github.com/tobymao/sqlglot/pull/5809) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`4e8a436`](https://github.com/tobymao/sqlglot/commit/4e8a436c16f487a72bd1ac2432bcb1c46599d901) - **singlestore**: Added generation of exp.JSONExists *(PR [#5820](https://github.com/tobymao/sqlglot/pull/5820) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`82bea49`](https://github.com/tobymao/sqlglot/commit/82bea49978ae459492b5127a2a52049826e2fd06) - **singlestore**: Refactored parsing of JSON_BUILD_OBJECT *(PR [#5828](https://github.com/tobymao/sqlglot/pull/5828) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`f7d38c3`](https://github.com/tobymao/sqlglot/commit/f7d38c3a10c505346f04e39a2712d60b4c96370f) - **singlestore**: Implemented generation of exp.Stuff *(PR [#5825](https://github.com/tobymao/sqlglot/pull/5825) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`030a5b5`](https://github.com/tobymao/sqlglot/commit/030a5b5ea03ecee869b07cfd27f4ea044732822e) - **singlestore**: Added generation of exp.JSONBExists *(PR [#5821](https://github.com/tobymao/sqlglot/pull/5821) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e58fef1`](https://github.com/tobymao/sqlglot/commit/e58fef1d6dc654a3b36461bcbea21c99cdc96477) - **singlestore**: Implemented parsing and generation of exp.MatchAgainst *(PR [#5822](https://github.com/tobymao/sqlglot/pull/5822) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e94f530`](https://github.com/tobymao/sqlglot/commit/e94f530af0e0cdad995b4c8dc5ed86953490d37f) - **singlestore**: Added handling of exp.JSONArray *(PR [#5818](https://github.com/tobymao/sqlglot/pull/5818) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`1c42ef4`](https://github.com/tobymao/sqlglot/commit/1c42ef4374aeab8a1ee9848892d7f8c4511c7f04) - **singlestore**: Fixed parsing/generation of exp.JSONArrayAgg *(PR [#5819](https://github.com/tobymao/sqlglot/pull/5819) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`67219f0`](https://github.com/tobymao/sqlglot/commit/67219f0606231514f430e146e2fdb99e796f718b) - **singlestore**: Added support of UTC_TIMESTAMP and CURRENT_TIMESTAMP *(PR [#5808](https://github.com/tobymao/sqlglot/pull/5808) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`db2c430`](https://github.com/tobymao/sqlglot/commit/db2c4303237a1244070c359245c398a724df6de2) - **optimizer**: annoate the \"contains\" function *(PR [#5829](https://github.com/tobymao/sqlglot/pull/5829) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`a398fb4`](https://github.com/tobymao/sqlglot/commit/a398fb4df28c868f4cfc34530044b9d7b78e2e90) - **singlestore**: Splitted truncation of multiple tables into several queries *(PR [#5839](https://github.com/tobymao/sqlglot/pull/5839) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`cd27c96`](https://github.com/tobymao/sqlglot/commit/cd27c96fe85aba5f54116f38649edd8db064a5e6) - **snowflake**: transpile `TO_HEX` from bigquery *(PR [#5838](https://github.com/tobymao/sqlglot/pull/5838) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`d2e4ab7`](https://github.com/tobymao/sqlglot/commit/d2e4ab7df41ae3601e9b66e1338db3d851729339) - **snowflake**: add tests for endswith function *(PR [#5846](https://github.com/tobymao/sqlglot/pull/5846) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c50d6e3`](https://github.com/tobymao/sqlglot/commit/c50d6e3c7b96f00d27c34a02c8e0dced21e6c373) - **optimizer**: annotate type for snowflake LEFT, RIGHT and SUBSTRING functions *(PR [#5849](https://github.com/tobymao/sqlglot/pull/5849) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ca6c8f7`](https://github.com/tobymao/sqlglot/commit/ca6c8f753ba8458544439e20671f0981c98d168d) - **singlestore**: Improved parsting/generation of exp.Show *(PR [#5853](https://github.com/tobymao/sqlglot/pull/5853) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`722eceb`](https://github.com/tobymao/sqlglot/commit/722ecebfa43aa5948031edd1828b6482a241d9ef) - **snowflake**: MD5Digest transpiling to MD5_BINARY *(PR [#5855](https://github.com/tobymao/sqlglot/pull/5855) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`b128339`](https://github.com/tobymao/sqlglot/commit/b12833977e2a395712481cf11e293fdbd70fd4ce) - **optimizer**: annotate and add tests for snowflake LENGTH and LOWER functions *(PR [#5856](https://github.com/tobymao/sqlglot/pull/5856) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`134957a`](https://github.com/tobymao/sqlglot/commit/134957af11c55a4ab16f58d0725d6bb8ab23eb28) - **optimizer**: annotate types for Snowflake TRIM function *(PR [#5811](https://github.com/tobymao/sqlglot/pull/5811) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`0475dae`](https://github.com/tobymao/sqlglot/commit/0475dae21231b85407bf778fd9f1abaecdeb68de) - **singlestore**: Marked several exp.Describe args as unsupported *(PR [#5861](https://github.com/tobymao/sqlglot/pull/5861) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`7a07b41`](https://github.com/tobymao/sqlglot/commit/7a07b41b2357149adc6afb50bb98e37e6a3175f1) - **optimizer**: Add tests for snowflake LTRIM and RTRIM functions *(PR [#5857](https://github.com/tobymao/sqlglot/pull/5857) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fb90666`](https://github.com/tobymao/sqlglot/commit/fb90666ff3e710d70815a68defde3dc85aeef7b3) - **singlestore**: Added collate handling to exp.AlterColumn *(PR [#5864](https://github.com/tobymao/sqlglot/pull/5864) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`2f27692`](https://github.com/tobymao/sqlglot/commit/2f276929d6b6f788eb5b3ee0b1a8a8c108833474) - **snowflake**: JSONFormat transpiling to TO_JSON  *(PR [#5860](https://github.com/tobymao/sqlglot/pull/5860) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`487c811`](https://github.com/tobymao/sqlglot/commit/487c8119cbfaf2783f5f17ec90c8e69e4432a4fa) - **singlestore**: Fixed parsing/generation of exp.RenameColumn *(PR [#5865](https://github.com/tobymao/sqlglot/pull/5865) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`76cf4d8`](https://github.com/tobymao/sqlglot/commit/76cf4d892a6d011a2e0020fb1ea82518d4f49e71) - **bigquery**: add support for ML.TRANSLATE func *(PR [#5859](https://github.com/tobymao/sqlglot/pull/5859) by [@geooo109](https://github.com/geooo109))*\n- [`a899eb1`](https://github.com/tobymao/sqlglot/commit/a899eb188d5e354d3ed56d1e7c32861eecf3e906) - **singlestore**: Fixed parsing and generation of VECTOR type *(PR [#5854](https://github.com/tobymao/sqlglot/pull/5854) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`0acf076`](https://github.com/tobymao/sqlglot/commit/0acf0769773061fca3ec03125a5d43a4aa9c8e4b) - **postgres**: Support `?|` JSONB operator *(PR [#5866](https://github.com/tobymao/sqlglot/pull/5866) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`bd4b278`](https://github.com/tobymao/sqlglot/commit/bd4b2780c32ee52d25b6539d7b4479b6a7f80d18) - **optimizer**: annotate types for Snowflake UPPER function *(PR [#5812](https://github.com/tobymao/sqlglot/pull/5812) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`edab189`](https://github.com/tobymao/sqlglot/commit/edab1890e2c790b737be4995a31667448eff148e) - **postgres**: Support ?& JSONB operator *(PR [#5867](https://github.com/tobymao/sqlglot/pull/5867) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`960ec06`](https://github.com/tobymao/sqlglot/commit/960ec069eb275b7b8cc6705dbbb1143159f06237) - **postgres**: Support #- JSONB operator *(PR [#5868](https://github.com/tobymao/sqlglot/pull/5868) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`d3cd6bf`](https://github.com/tobymao/sqlglot/commit/d3cd6bf6e5fbaa490868ee3cd2cc99dd5e40a396) - **optimizer**: Annotate and add tests for snowflake REPLACE and SPACE functions *(PR [#5871](https://github.com/tobymao/sqlglot/pull/5871) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ba22531`](https://github.com/tobymao/sqlglot/commit/ba2253113ea5a7c76c8df7ec9b6faf37da698fa4) - **bigquery**: Add support for ML.FORECAST(...) *(PR [#5873](https://github.com/tobymao/sqlglot/pull/5873) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`cd818ba`](https://github.com/tobymao/sqlglot/commit/cd818bad51e93ec349b97675e4c1f5bd7c4c1522) - **singlestore**: Fixed generation/parsing of computed collumns *(PR [#5878](https://github.com/tobymao/sqlglot/pull/5878) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`5d1f241`](https://github.com/tobymao/sqlglot/commit/5d1f241209197419111e9eda37fb6f2a5ec2bc4b) - **tsql**: support JSON_ARRAYAGG *(PR [#5879](https://github.com/tobymao/sqlglot/pull/5879) by [@geooo109](https://github.com/geooo109))*\n- [`96ae7a3`](https://github.com/tobymao/sqlglot/commit/96ae7a3bcbf9de1932150baa0bd704d4ce05c9f7) - **optimizer**: Annotate and add tests for snowflake REPEAT and SPLIT functions *(PR [#5875](https://github.com/tobymao/sqlglot/pull/5875) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`0fe6a25`](https://github.com/tobymao/sqlglot/commit/0fe6a25e366dcbc5a4a0878b285d147a6aa00412) - **postgres**: support JSON_AGG *(PR [#5880](https://github.com/tobymao/sqlglot/pull/5880) by [@geooo109](https://github.com/geooo109))*\n- [`854eeeb`](https://github.com/tobymao/sqlglot/commit/854eeeb5b25954cc26b91135d58eb8370271f1de) - **optimizer**: annotate types for Snowflake REGEXP_LIKE, REGEXP_REPLACE, REGEXP_SUBSTR functions *(PR [#5876](https://github.com/tobymao/sqlglot/pull/5876) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f2d3bf7`](https://github.com/tobymao/sqlglot/commit/f2d3bf74e804e5a5e2ac6ca94210ba04df07e7f3) - **optimizer**: annotate types for Snowflake UUID_STRING function *(PR [#5881](https://github.com/tobymao/sqlglot/pull/5881) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`5b9463a`](https://github.com/tobymao/sqlglot/commit/5b9463ad11a49c821585985c35394ebb30e827dd) - **mysql**: add support for binary `MOD` operator fixes [#5887](https://github.com/tobymao/sqlglot/pull/5887) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d24eabc`](https://github.com/tobymao/sqlglot/commit/d24eabcbe30dc0f7c2dbae346e429efef58b5680) - **bigquery**: Add support for ML.GENERATE_TEXT_EMBEDDING(...) *(PR [#5891](https://github.com/tobymao/sqlglot/pull/5891) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`950a3fa`](https://github.com/tobymao/sqlglot/commit/950a3fa6d6307f7713f40117655da2f9710ebfa9) - **mysql**: SOUNDS LIKE, SUBSTR *(PR [#5886](https://github.com/tobymao/sqlglot/pull/5886) by [@vuvova](https://github.com/vuvova))*\n- [`688afc5`](https://github.com/tobymao/sqlglot/commit/688afc55ab08588636eba92893c603ca68e43e6e) - **singlestore**: Fixed generation of exp.National *(PR [#5890](https://github.com/tobymao/sqlglot/pull/5890) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`c77147e`](https://github.com/tobymao/sqlglot/commit/c77147ebaafa6942f80af75dd6c2d7a62a7e6fe2) - **parser**: Extend support for `IS UNKOWN` across all dialects *(PR [#5888](https://github.com/tobymao/sqlglot/pull/5888) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ec80ff3`](https://github.com/tobymao/sqlglot/commit/ec80ff34957c3e3f80c44175383b06cf72988a68) - make dump a list instead of a nested dict to avoid all recursion errors *(PR [#5885](https://github.com/tobymao/sqlglot/pull/5885) by [@tobymao](https://github.com/tobymao))*\n- [`2fdaccd`](https://github.com/tobymao/sqlglot/commit/2fdaccd1a9045bda3d529025a4706c397b8a836f) - **optimizer**: annotate types for Snowflake SHA1, SHA2 functions *(PR [#5884](https://github.com/tobymao/sqlglot/pull/5884) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`faba309`](https://github.com/tobymao/sqlglot/commit/faba30905390e5efaf0ba9a05aab9ac2724b1b85) - **optimizer**: annotate types for Snowflake AI_AGG function *(PR [#5894](https://github.com/tobymao/sqlglot/pull/5894) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`dd27844`](https://github.com/tobymao/sqlglot/commit/dd2784435c7bdd2ceaaaaa359fcd112ad1f8190c) - **snowflake**: transpile `BYTE_LENGTH` *(PR [#5899](https://github.com/tobymao/sqlglot/pull/5899) by [@ozadari](https://github.com/ozadari))*\n- [`304bec5`](https://github.com/tobymao/sqlglot/commit/304bec5f7342501ad28ea4cd0a4b9aa092f2192f) - **optimizer**: Annotate snowflake MD5 functions *(PR [#5883](https://github.com/tobymao/sqlglot/pull/5883) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ec3006d`](https://github.com/tobymao/sqlglot/commit/ec3006d815951fdc1a80d6722ce6f1176417d595) - **optimizer**: Add tests for snowflake NOT ILIKE and NOT LIKE *(PR [#5901](https://github.com/tobymao/sqlglot/pull/5901) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c0180ec`](https://github.com/tobymao/sqlglot/commit/c0180ec163a43836fed754efcb6f26ad37cdae50) - **optimizer**: annotate types for Snowflake AI_SUMMARIZE_AGG function *(PR [#5902](https://github.com/tobymao/sqlglot/pull/5902) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`1ee026d`](https://github.com/tobymao/sqlglot/commit/1ee026d22d4f6c3613c1809a6738cdea846c48a9) - **postgres**: support `SUBSTRING(value FOR length FROM start)` variant *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d5cf114`](https://github.com/tobymao/sqlglot/commit/d5cf1149932850a91cb5f1ebecda2652616729ef) - **duckdb**: support INSTALL *(PR [#5904](https://github.com/tobymao/sqlglot/pull/5904) by [@geooo109](https://github.com/geooo109))*\n- [`73e05bb`](https://github.com/tobymao/sqlglot/commit/73e05bb15bb86e4a07cc09bf02028a6cf7fa1e6f) - **snowflake**: properly generate `BITNOT` *(PR [#5906](https://github.com/tobymao/sqlglot/pull/5906) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`16f317c`](https://github.com/tobymao/sqlglot/commit/16f317c04f7c0a398c38b461e05f4d4c30baf98b) - **snowflake**: add support for `<model>!<attribute>` syntax *(PR [#5907](https://github.com/tobymao/sqlglot/pull/5907) by [@georgesittas](https://github.com/georgesittas))*\n- [`5a973e9`](https://github.com/tobymao/sqlglot/commit/5a973e9a88fa7f522a9bf91dc60fb0f6effef53d) - **optimizer**: annotate types for Snowflake AI_CLASSIFY function *(PR [#5909](https://github.com/tobymao/sqlglot/pull/5909) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f4ad258`](https://github.com/tobymao/sqlglot/commit/f4ad25882951de4e4442dfd5189a56d5a1c5e630) - **optimizer**: Annotate types for Snowflake BASE64_DECODE_BINARY function *(PR [#5917](https://github.com/tobymao/sqlglot/pull/5917) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`6d0e3f8`](https://github.com/tobymao/sqlglot/commit/6d0e3f8dcae7ed1a7659ece69b1f94cec5e7300e) - **optimizer**: Add parser support to ilike like function versions. *(PR [#5915](https://github.com/tobymao/sqlglot/pull/5915) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fc5624e`](https://github.com/tobymao/sqlglot/commit/fc5624eca43d2855ac350c92d85b184a6893d5ca) - **optimizer**: annotate types for Snowflake ASCII function *(PR [#5926](https://github.com/tobymao/sqlglot/pull/5926) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`4e81690`](https://github.com/tobymao/sqlglot/commit/4e8169045edcaa28ae43abeb07370df63846fbfd) - **optimizer**: annotate type for Snowflake COLLATE function *(PR [#5931](https://github.com/tobymao/sqlglot/pull/5931) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f07d35d`](https://github.com/tobymao/sqlglot/commit/f07d35d29104c6203efaab738118d1903614b83c) - **optimizer**: annotate type for Snowflake CHR function *(PR [#5929](https://github.com/tobymao/sqlglot/pull/5929) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f8c0ee4`](https://github.com/tobymao/sqlglot/commit/f8c0ee4d3c1a4d4a92b897d1cc85f9904c8e566b) - **optimizer**: Add function and annotate snowflake hex decode string and binary functions *(PR [#5928](https://github.com/tobymao/sqlglot/pull/5928) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`66f9501`](https://github.com/tobymao/sqlglot/commit/66f9501d76d087798bad93e578273ab2a45e2575) - **optimizer**: annotate types for Snowflake BIT_LENGTH function *(PR [#5927](https://github.com/tobymao/sqlglot/pull/5927) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f4c810e`](https://github.com/tobymao/sqlglot/commit/f4c810e043d9379e94efb185e368e27ad9c15715) - transpile Trino `FORMAT` to DuckDB and Snowflake, closes [#5933](https://github.com/tobymao/sqlglot/pull/5933) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7878437`](https://github.com/tobymao/sqlglot/commit/78784370712df65a2e1e79a1c2b441131ed7222a) - **optimizer**: annotate snowflake's `BASE64_DECODE_STRING`, `BASE64_ENCODE` *(PR [#5922](https://github.com/tobymao/sqlglot/pull/5922) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`9bcad04`](https://github.com/tobymao/sqlglot/commit/9bcad040bd51dd03821c68eea1a73534fc7a81b7) - **optimizer**: Annotate type for HEX ENCODE function. *(PR [#5936](https://github.com/tobymao/sqlglot/pull/5936) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`590928f`](https://github.com/tobymao/sqlglot/commit/590928f4637306e8cf3f1302d5dd5d5dbc76e7e0) - **optimizer**: annotate type for Snowflake INITCAP function *(PR [#5941](https://github.com/tobymao/sqlglot/pull/5941) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`ac04de1`](https://github.com/tobymao/sqlglot/commit/ac04de1944c7a976406581b489b3cf9b11dafb77) - **optimizer**: annotate type for Snowflake EDITDISTANCE function *(PR [#5940](https://github.com/tobymao/sqlglot/pull/5940) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`a385990`](https://github.com/tobymao/sqlglot/commit/a38599080932a8b54a169df8b7a69650cb47b6bc) - **parser**: support wrapped aggregate functions *(PR [#5943](https://github.com/tobymao/sqlglot/pull/5943) by [@geooo109](https://github.com/geooo109))*\n- [`9e28af8`](https://github.com/tobymao/sqlglot/commit/9e28af8a52ced951ecf7f4e85a6305e20a13de1f) - **optimizer**: Annotate type for snowflake COMPRESS function *(PR [#5938](https://github.com/tobymao/sqlglot/pull/5938) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`7f13eaf`](https://github.com/tobymao/sqlglot/commit/7f13eaf7769a3381a56c9209af590835be2f95cd) - **optimizer**: Annotate type for snowflake DECOMPRESS_BINARY function *(PR [#5945](https://github.com/tobymao/sqlglot/pull/5945) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`be12b29`](https://github.com/tobymao/sqlglot/commit/be12b29b5a7bd6d6e09dbd8c17086bd77c19abc0) - **optimizer**: Annotate type for snowflake DECOMPRESS_STRING function *(PR [#5947](https://github.com/tobymao/sqlglot/pull/5947) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`a55fce5`](https://github.com/tobymao/sqlglot/commit/a55fce5310a50af132c5d06bb299fe3f025442c4) - **optimizer**: Annotate type for snowflake LPAD function *(PR [#5948](https://github.com/tobymao/sqlglot/pull/5948) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`05e07aa`](https://github.com/tobymao/sqlglot/commit/05e07aa740d7977a6b42ec15ae4fa9c2168a15f5) - **optimizer**: annotate type for Snowflake INSERT function *(PR [#5942](https://github.com/tobymao/sqlglot/pull/5942) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`6268e10`](https://github.com/tobymao/sqlglot/commit/6268e107a947badaa00508544f5389412806ecd0) - **solr**: initial dialect implementation *(PR [#5946](https://github.com/tobymao/sqlglot/pull/5946) by [@aadel](https://github.com/aadel))*\n- [`1573fef`](https://github.com/tobymao/sqlglot/commit/1573fefac27b5b1215e3d458f8ccf1b9dadbb772) - **optimizer**: annotate types for Snowflake JAROWINKLER_SIMILARITY function *(PR [#5950](https://github.com/tobymao/sqlglot/pull/5950) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`883c6ab`](https://github.com/tobymao/sqlglot/commit/883c6abe589865f478d95604e8d670e57afd04af) - **optimizer**: annotate type for Snowflake COLLATION function *(PR [#5939](https://github.com/tobymao/sqlglot/pull/5939) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`627c18d`](https://github.com/tobymao/sqlglot/commit/627c18d7da6bf644bc14c0f17963dea0be20604a) - **mysql**: add valid INTERVAL units *(PR [#5951](https://github.com/tobymao/sqlglot/pull/5951) by [@geooo109](https://github.com/geooo109))*\n- [`88e4e4c`](https://github.com/tobymao/sqlglot/commit/88e4e4c55f3a113127eb3c82c0be46c29bcf15ab) - **optimizer**: Annotate type for OCTET_LENGTH function *(PR [#5960](https://github.com/tobymao/sqlglot/pull/5960) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`68473ac`](https://github.com/tobymao/sqlglot/commit/68473ac3ec8dc76512dc76819892a1b0324c7ddc) - **optimizer**: Annotate type for snowflake PARSE_URL function *(PR [#5962](https://github.com/tobymao/sqlglot/pull/5962) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`b015a9d`](https://github.com/tobymao/sqlglot/commit/b015a9d944d0a87069a7750ad74953c399d7da34) - **optimizer**: annotate type for Snowflake REGEXP_INSTR function *(commit by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`1f29ba7`](https://github.com/tobymao/sqlglot/commit/1f29ba710f4213beb1a2f993244d7d824f3536ce) - **optimizer**: annotate type for Snowflake PARSE_IP function *(PR [#5961](https://github.com/tobymao/sqlglot/pull/5961) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`bf45d5d`](https://github.com/tobymao/sqlglot/commit/bf45d5d3cb0c0f380824019eb32ec29049268a61) - **optimizer**: annotate types for Snowflake RTRIMMED_LENGTH function *(PR [#5968](https://github.com/tobymao/sqlglot/pull/5968) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`13caa69`](https://github.com/tobymao/sqlglot/commit/13caa6991f003ad7abb590073451e591b6fd888c) - **optimizer**: Annotate type for snowflake POSITION function *(PR [#5964](https://github.com/tobymao/sqlglot/pull/5964) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`1471306`](https://github.com/tobymao/sqlglot/commit/1471306ed317830c294e3654075f55424d14bf5a) - support parse into grant principal and privilege *(PR [#5971](https://github.com/tobymao/sqlglot/pull/5971) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`13a30df`](https://github.com/tobymao/sqlglot/commit/13a30dfa37096df5bfc2c31538325c40a49f7917) - **optimizer**: Annotate type for snowflake TRY_BASE64_DECODE_BINARY function *(PR [#5972](https://github.com/tobymao/sqlglot/pull/5972) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`1f5fdd7`](https://github.com/tobymao/sqlglot/commit/1f5fdd799c047de167a4572f7ac26b7ad92167f2) - **optimizer**: Annotate type for snowflake TRY_BASE64_DECODE_STRING function *(PR [#5974](https://github.com/tobymao/sqlglot/pull/5974) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`324e82f`](https://github.com/tobymao/sqlglot/commit/324e82fe1fb11722f91341010602a743b151e055) - **optimizer**: Annotate type for snowflake TRY_HEX_DECODE_BINARY function *(PR [#5975](https://github.com/tobymao/sqlglot/pull/5975) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`6caf99d`](https://github.com/tobymao/sqlglot/commit/6caf99d556a3357ffaa6c294a9babcd30dd5fac5) - **optimizer**: Annotate type for snowflake TRY_HEX_DECODE_STRING function *(PR [#5976](https://github.com/tobymao/sqlglot/pull/5976) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`73186a8`](https://github.com/tobymao/sqlglot/commit/73186a812ce422c108ee81b3de11da6ee9a9e902) - **optimizer**: annotate type for Snowflake REGEXP_COUNT function *(PR [#5963](https://github.com/tobymao/sqlglot/pull/5963) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`6124de7`](https://github.com/tobymao/sqlglot/commit/6124de76fa6d6725e844cd37e09ebfe99469b0ec) - **optimizer**: Annotate type for snowflake SOUNDEX function *(PR [#5986](https://github.com/tobymao/sqlglot/pull/5986) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`244fb48`](https://github.com/tobymao/sqlglot/commit/244fb48fc9c4776f427c08b825d139b1c172fd26) - **optimizer**: annotate type for Snowflake SPLIT_PART function *(PR [#5988](https://github.com/tobymao/sqlglot/pull/5988) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`0d772e0`](https://github.com/tobymao/sqlglot/commit/0d772e0b9d687b24d49203c05d7a90cc1dce02d5) - **snowflake**: add ast node for `DIRECTORY` source *(PR [#5990](https://github.com/tobymao/sqlglot/pull/5990) by [@georgesittas](https://github.com/georgesittas))*\n- [`3c7b5c0`](https://github.com/tobymao/sqlglot/commit/3c7b5c0e2dc071b7b9f6da308ba58a3a43da93dc) - **optimizer**: Annotate type for snowflake SOUNDEX_P123 function *(PR [#5987](https://github.com/tobymao/sqlglot/pull/5987) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`475c09b`](https://github.com/tobymao/sqlglot/commit/475c09bd27179db4d186638645698dd4ad6553cd) - **optimizer**: Annotate type for snowflake TRANSLATE function *(PR [#5992](https://github.com/tobymao/sqlglot/pull/5992) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`f25e42e`](https://github.com/tobymao/sqlglot/commit/f25e42e3f5b3b7b671bd724ba7b09a9b07d13995) - **optimizer**: annotate type for Snowflake REGEXP_INSTR function *(PR [#5978](https://github.com/tobymao/sqlglot/pull/5978) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`13cb26e`](https://github.com/tobymao/sqlglot/commit/13cb26e2f29373538d60a8124ddebf95fd22a8d8) - **optimizer**: annotate type for Snowflake REGEXP_SUBSTR_ALL function *(PR [#5979](https://github.com/tobymao/sqlglot/pull/5979) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`4ce683e`](https://github.com/tobymao/sqlglot/commit/4ce683eb8ac5716a334cbd7625438b9f89623c7a) - **optimizer**: Annotate type for snowflake UNICODE function *(PR [#5993](https://github.com/tobymao/sqlglot/pull/5993) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`587196c`](https://github.com/tobymao/sqlglot/commit/587196c9c2d122f73f9deb7e87c2831f27f6ed02) - **optimizer**: Annotate type for snowflake STRTOK_TO_ARRAY function *(PR [#5994](https://github.com/tobymao/sqlglot/pull/5994) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`bced710`](https://github.com/tobymao/sqlglot/commit/bced71084ffb3a8f7a11db843777f05b68f367da) - **optimizer**: Annotate type for snowflake STRTOK function. *(PR [#5991](https://github.com/tobymao/sqlglot/pull/5991) by [@georgesittas](https://github.com/georgesittas))*\n- [`74a13f2`](https://github.com/tobymao/sqlglot/commit/74a13f2a548b9cd41061e835cb3cd9dd2a5a9fb3) - **optimizer**: Annotate type for snowflake DIV0 and DIVNULL functions *(PR [#6008](https://github.com/tobymao/sqlglot/pull/6008) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fec2b31`](https://github.com/tobymao/sqlglot/commit/fec2b31956f2debdad7c53744a577894cd8d747c) - **optimizer**: Annotate type for snowflake SEARCH function *(PR [#5985](https://github.com/tobymao/sqlglot/pull/5985) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`27a76cd`](https://github.com/tobymao/sqlglot/commit/27a76cdfe4212f16f945521eb3997580eacf1d61) - **optimizer**: Annotate type for snowflake COT, SIN and TAN functions *(PR [#6022](https://github.com/tobymao/sqlglot/pull/6022) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`8b48f7b`](https://github.com/tobymao/sqlglot/commit/8b48f7b985342cfcc45bc2b94540a1a2bf5995c4) - **optimizer**: Annotate type for snowflake SIGN and ABS functions *(PR [#6025](https://github.com/tobymao/sqlglot/pull/6025) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`0911276`](https://github.com/tobymao/sqlglot/commit/091127663ab4cb94b02be5aa40c6a46dd7f89243) - **optimizer**: annotate type for Snowflake EXP function *(PR [#6007](https://github.com/tobymao/sqlglot/pull/6007) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`a96d50e`](https://github.com/tobymao/sqlglot/commit/a96d50e14bed5e87ff2dce9c545e0c48897b64d6) - **optimizer**: annotate type for Snowflake COSH function *(PR [#6006](https://github.com/tobymao/sqlglot/pull/6006) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`4df58e0`](https://github.com/tobymao/sqlglot/commit/4df58e0f0b8985590fb29a8ab6ba0ced987ac5b9) - **optimizer**: annotate type for Snowflake DEGREES function *(PR [#6027](https://github.com/tobymao/sqlglot/pull/6027) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`db71a20`](https://github.com/tobymao/sqlglot/commit/db71a2023aaeca2ffda782ae7b91fdee356c402e) - **optimizer**: annotate type for Snowflake COS function *(PR [#6028](https://github.com/tobymao/sqlglot/pull/6028) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`6beb917`](https://github.com/tobymao/sqlglot/commit/6beb9172dffd0aaea46b75477485060737e774b9) - **optimizer**: Annotate type for snowflake ROUND function *(PR [#6032](https://github.com/tobymao/sqlglot/pull/6032) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`8e03ad9`](https://github.com/tobymao/sqlglot/commit/8e03ad9dd087ebc72bf58cb6383607c0ce2e8f8f) - **optimizer**: Annotate type for snowflake MOD function *(PR [#6031](https://github.com/tobymao/sqlglot/pull/6031) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`15b3fac`](https://github.com/tobymao/sqlglot/commit/15b3fac3dd5efd4c347ac40055f07a9be5906802) - **mysql**: support `FOR ORDINALITY` clause in `COLUMN` expression *(PR [#6046](https://github.com/tobymao/sqlglot/pull/6046) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6039](https://github.com/tobymao/sqlglot/issues/6039) opened by [@jdddog](https://github.com/jdddog)*\n- [`56c8b3b`](https://github.com/tobymao/sqlglot/commit/56c8b3bbff7451b9049e1a168716bb41222a86ed) - **hive,spark**: Support CHANGE COLUMN statements in Hive and CHANGE/ALTER COLUMN statements in Spark *(PR [#6004](https://github.com/tobymao/sqlglot/pull/6004) by [@tsamaras](https://github.com/tsamaras))*\n- [`6f31b86`](https://github.com/tobymao/sqlglot/commit/6f31b86599258afe156aa3d9ccc42389cac37021) - **optimizer**: Annotate type for snowflake FLOOR function *(PR [#6030](https://github.com/tobymao/sqlglot/pull/6030) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`b7463d5`](https://github.com/tobymao/sqlglot/commit/b7463d5b0a1e286498d7ccfd9a07ef7edfa80bb2) - **optimizer**: Annotate type for snowflake ASIN function. *(PR [#6049](https://github.com/tobymao/sqlglot/pull/6049) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fe959a5`](https://github.com/tobymao/sqlglot/commit/fe959a5598508526ed5910a4c62372116b5d3c30) - **optimizer**: Annotate type for snowflake CBRT function *(PR [#6050](https://github.com/tobymao/sqlglot/pull/6050) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`cecab2f`](https://github.com/tobymao/sqlglot/commit/cecab2fd66d578ddc765b5fd0e7b155971280a0c) - **optimizer**: annotate type for Snowflake ATANH function *(PR [#6054](https://github.com/tobymao/sqlglot/pull/6054) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`08339a9`](https://github.com/tobymao/sqlglot/commit/08339a902138211f67cfb009d2576b22ea8d8e42) - **optimizer**: annotate type for Snowflake FACTORIAL function *(PR [#6053](https://github.com/tobymao/sqlglot/pull/6053) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`9060f60`](https://github.com/tobymao/sqlglot/commit/9060f603818db863b7570a2c3c50c3eb88155e76) - **optimizer**: Annotate type for snowflake ATAN2 function. *(PR [#6060](https://github.com/tobymao/sqlglot/pull/6060) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`b3eb2e4`](https://github.com/tobymao/sqlglot/commit/b3eb2e4ca6177ee61b27675e8ec8b4815587df31) - **optimizer**: annotate type for Snowflake SINH function *(PR [#6052](https://github.com/tobymao/sqlglot/pull/6052) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`440b960`](https://github.com/tobymao/sqlglot/commit/440b960529801674fa23708212485fda95749699) - **duckdb**: support `USING KEY (...)` in recursive DuckDB CTEs *(PR [#6068](https://github.com/tobymao/sqlglot/pull/6068) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6066](https://github.com/tobymao/sqlglot/issues/6066) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`157d2fa`](https://github.com/tobymao/sqlglot/commit/157d2fa06ab110ebc760aa7567d7fda801a5ced9) - **optimizer**: annotate type for Snowflake CEIL function *(PR [#6051](https://github.com/tobymao/sqlglot/pull/6051) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`eb6d6e7`](https://github.com/tobymao/sqlglot/commit/eb6d6e7ccde37456ab56ad976e7d95cea23c14e3) - **duckdb**: support `DEFAULT VALUES` clause in `INSERT` DML *(PR [#6067](https://github.com/tobymao/sqlglot/pull/6067) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6065](https://github.com/tobymao/sqlglot/issues/6065) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`354140d`](https://github.com/tobymao/sqlglot/commit/354140d0a279f317439bdb247e1ab9578f9a035d) - **optimizer**: Annotate type for snowflake TANH and ATAN functions *(PR [#6069](https://github.com/tobymao/sqlglot/pull/6069) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c94e3e0`](https://github.com/tobymao/sqlglot/commit/c94e3e0e4e20bd76d4cf630123d2c05a0e3044c3) - add ColumnDef expression parser *(PR [#6075](https://github.com/tobymao/sqlglot/pull/6075) by [@geooo109](https://github.com/geooo109))*\n- [`c67276d`](https://github.com/tobymao/sqlglot/commit/c67276d5be970252e14d1817d8498fc9985222d9) - **optimizer**: Annotate type for snowflake RADIANS function. *(PR [#6064](https://github.com/tobymao/sqlglot/pull/6064) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c49ba0e`](https://github.com/tobymao/sqlglot/commit/c49ba0eee21f7776703d2a26c6641b4a32a1cff7) - **optimizer**: Annotate type for snowflake WIDTH_BUCKET function *(PR [#6078](https://github.com/tobymao/sqlglot/pull/6078) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fbc1f13`](https://github.com/tobymao/sqlglot/commit/fbc1f1335eecaaaab4fc93ddbb74611a4df0aea7) - **optimizer**: annotate type for Snowflake CONVERT_TIMEZONE function *(PR [#6076](https://github.com/tobymao/sqlglot/pull/6076) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`70e977c`](https://github.com/tobymao/sqlglot/commit/70e977c5edfb495529d38a9096cb40762a9b5d7b) - **optimizer**: annotate type for Snowflake DATE_TRUNC function *(PR [#6080](https://github.com/tobymao/sqlglot/pull/6080) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`e9cf146`](https://github.com/tobymao/sqlglot/commit/e9cf146a4a6cd78f6a59c195e7ec12240b836e5e) - **optimizer**: annotate type for Snowflake DATE_PART function *(PR [#6079](https://github.com/tobymao/sqlglot/pull/6079) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`cdf3b1b`](https://github.com/tobymao/sqlglot/commit/cdf3b1b34dc044064d0a5ba7ff22723b8ae33e5d) - **optimizer**: Annotate type for snowflake add_months function *(PR [#6097](https://github.com/tobymao/sqlglot/pull/6097) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`6fe5824`](https://github.com/tobymao/sqlglot/commit/6fe58247888c326093618657fb027e482d82d107) - **optimizer**: Annotate type for hour, minute, second functions *(PR [#6100](https://github.com/tobymao/sqlglot/pull/6100) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`483770b`](https://github.com/tobymao/sqlglot/commit/483770b816fab14b7eb7222974ed2c99045302a7) - **optimizer**: Annotate type for snowflake TIME_SLICE function *(PR [#6098](https://github.com/tobymao/sqlglot/pull/6098) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`071a995`](https://github.com/tobymao/sqlglot/commit/071a9954aad220c1e13ba7a6714a083058a8e03f) - **tsql**: add support for iso_week on DATEPART *(PR [#6111](https://github.com/tobymao/sqlglot/pull/6111) by [@lBilali](https://github.com/lBilali))*\n  - :arrow_lower_right: *addresses issue [#6110](https://github.com/tobymao/sqlglot/issues/6110) opened by [@lBilali](https://github.com/lBilali)*\n- [`c286cee`](https://github.com/tobymao/sqlglot/commit/c286cee54ab93e1fd0b3be658f7e767e3e00afe9) - **optimizer**: Annotate type for snowflake MONTHNAME function *(PR [#6116](https://github.com/tobymao/sqlglot/pull/6116) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`1a34788`](https://github.com/tobymao/sqlglot/commit/1a34788025bdd8a018c4bb9214f72152e68bdd14) - **optimizer**: Annotate type for snowflake PREVIOUS_DAY function *(PR [#6117](https://github.com/tobymao/sqlglot/pull/6117) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`533faf8`](https://github.com/tobymao/sqlglot/commit/533faf87b6df351070b565dd1fe9ce4e13b6c46e) - **spark**: transpile duckdb `READ_PARQUET` to `parquet.<path>` closes [#6122](https://github.com/tobymao/sqlglot/pull/6122) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`cd4e557`](https://github.com/tobymao/sqlglot/commit/cd4e557658b1384f36c9a1ef9da5a09b893229b1) - **optimizer**: Annotate type for snowflake RANDOM function *(PR [#6124](https://github.com/tobymao/sqlglot/pull/6124) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`fe63d84`](https://github.com/tobymao/sqlglot/commit/fe63d84f1bd365b22221f348d79c0546aa3118b0) - **optimizer**: annotate type for Snowflake MONTHS_BETWEEN function *(PR [#6120](https://github.com/tobymao/sqlglot/pull/6120) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`7cb7598`](https://github.com/tobymao/sqlglot/commit/7cb7598e13260aa45c851dc620b4994ddfa089fe) - **optimizer**: Annotate type for snowflake TIME_FROM_PARTS function *(PR [#6119](https://github.com/tobymao/sqlglot/pull/6119) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`598d09b`](https://github.com/tobymao/sqlglot/commit/598d09b036d938c90a44955d67175ea868090ba2) - **optimizer**: annotate type for Snowflake DATEADD function *(PR [#6089](https://github.com/tobymao/sqlglot/pull/6089) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`b98bcee`](https://github.com/tobymao/sqlglot/commit/b98bcee148ba426816e166dbfa9ba8e0979aae21) - **optimizer**: Annotate type for snowflake next_day function *(PR [#6125](https://github.com/tobymao/sqlglot/pull/6125) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`fe1927f`](https://github.com/tobymao/sqlglot/commit/fe1927f28600e2d8863a4e7f06e6a21bf6ff7f9c) - **duckdb**: Transpile unix_micros to epoch_us *(PR [#6127](https://github.com/tobymao/sqlglot/pull/6127) by [@vchan](https://github.com/vchan))*\n- [`a531f10`](https://github.com/tobymao/sqlglot/commit/a531f107235c29ac6a7e627a323f00b8ecf7023d) - **duckdb**: transpile TimeSub *(PR [#6142](https://github.com/tobymao/sqlglot/pull/6142) by [@toriwei](https://github.com/toriwei))*\n- [`b1a9dff`](https://github.com/tobymao/sqlglot/commit/b1a9dfff52a0ffbb0b7c8bfedb0a90e245b97851) - make qualify faster by owly resetting scope when needed *(PR [#6081](https://github.com/tobymao/sqlglot/pull/6081) by [@tobymao](https://github.com/tobymao))*\n- [`3a334f3`](https://github.com/tobymao/sqlglot/commit/3a334f376b9766b6b99fdf195ae763bb44976ec4) - **optimizer**: annotate type for boolnot snowflake function *(PR [#6141](https://github.com/tobymao/sqlglot/pull/6141) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`4e36f9d`](https://github.com/tobymao/sqlglot/commit/4e36f9dd6a854b378c9bbf6b2e9811045affc63d) - **optimizer**: Annotate type for snowflake TIMEADD function *(PR [#6134](https://github.com/tobymao/sqlglot/pull/6134) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`5242cdd`](https://github.com/tobymao/sqlglot/commit/5242cddf487e367e7f543ca19d9bccae858f36ac) - **optimizer**: annotate type for bq LENGTH *(commit by [@geooo109](https://github.com/geooo109))*\n- [`0fc6dbf`](https://github.com/tobymao/sqlglot/commit/0fc6dbf2e7b611fa0977e3c3e61be1cc84bcf4a9) - **snowflake**: add GREATEST_IGNORE_NULLS function support *(PR [#6161](https://github.com/tobymao/sqlglot/pull/6161) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`54ecadc`](https://github.com/tobymao/sqlglot/commit/54ecadc57b8f1e87fd2a2ba35a5366d75231ea85) - **duckdb**: support `KV_METADATA` in `COPY` statement closes [#6165](https://github.com/tobymao/sqlglot/pull/6165) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`e241964`](https://github.com/tobymao/sqlglot/commit/e2419642a4966a4da194147aa488793eae152af4) - **duckdb**: support `USING` condition for `MERGE` closes [#6162](https://github.com/tobymao/sqlglot/pull/6162) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bcf6c89`](https://github.com/tobymao/sqlglot/commit/bcf6c89a47abd3c2c4383d1c908f892b6619b6fa) - **optimizer**: add type annotation tests for snowflake BOOLAND *(PR [#6153](https://github.com/tobymao/sqlglot/pull/6153) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`52d1eec`](https://github.com/tobymao/sqlglot/commit/52d1eecaad505703e8b22dcfe8954652f57985b6) - **optimizer**: Annotate type for snowflake TIMESTAMP_FROM_PARTS function *(PR [#6139](https://github.com/tobymao/sqlglot/pull/6139) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`8651fe6`](https://github.com/tobymao/sqlglot/commit/8651fe6526dea865c0d54d6d53086359a7835d32) - **optimizer**: annotate types for BOOLOR *(PR [#6159](https://github.com/tobymao/sqlglot/pull/6159) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`812ba9a`](https://github.com/tobymao/sqlglot/commit/812ba9abad8247df81c8f8b514336c8766292112) - **optimizer**: Annotate type for snowflake date parts functions *(PR [#6158](https://github.com/tobymao/sqlglot/pull/6158) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`9f8c123`](https://github.com/tobymao/sqlglot/commit/9f8c123ae44249e274334d0aa551ac33814f2b32) - make qualify table callback more generic *(PR [#6171](https://github.com/tobymao/sqlglot/pull/6171) by [@tobymao](https://github.com/tobymao))*\n- [`74b4e7c`](https://github.com/tobymao/sqlglot/commit/74b4e7c311e9d4ff39ce2e4d91940eced96aa32f) - **optimizer**: fix type annotation for Snowflake BOOLOR and BOOLAND *(PR [#6169](https://github.com/tobymao/sqlglot/pull/6169) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`e90168a`](https://github.com/tobymao/sqlglot/commit/e90168a6829b85534edcecec7d0df2a8b1b56fc4) - **optimizer**: annotate type for Snowflake's `IS_NULL_VALUE` function *(PR [#6186](https://github.com/tobymao/sqlglot/pull/6186) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`cea2595`](https://github.com/tobymao/sqlglot/commit/cea25952c98e70f2a4c35e675fe7ee4df0af02cd) - **duckdb**: Transpile DATE function from BQ->DuckDB *(PR [#6178](https://github.com/tobymao/sqlglot/pull/6178) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`00aaa47`](https://github.com/tobymao/sqlglot/commit/00aaa47feff1cf9e69320074c35d9adfc8538026) - **duckDB**: Transpile BigQuery's CURRENT_DATE (Conversion) function to DuckDB *(PR [#6189](https://github.com/tobymao/sqlglot/pull/6189) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c93b535`](https://github.com/tobymao/sqlglot/commit/c93b5354827282c806899c36b11e7a7598e96e38) - **snowflake**: annotate type for LEAST_IGNORE_NULLS *(PR [#6196](https://github.com/tobymao/sqlglot/pull/6196) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d2162fb`](https://github.com/tobymao/sqlglot/commit/d2162fbece0747b8ee42fa1f78e26baa0c944d41) - check same ref on Expression.__eq__ *(PR [#6200](https://github.com/tobymao/sqlglot/pull/6200) by [@georgesittas](https://github.com/georgesittas))*\n- [`f60c71f`](https://github.com/tobymao/sqlglot/commit/f60c71fb03db91bfe90430d032ac16f4945d5dff) - **optimizer**: annotate types for REGR_VALX *(PR [#6198](https://github.com/tobymao/sqlglot/pull/6198) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`42e0ae4`](https://github.com/tobymao/sqlglot/commit/42e0ae43b3531bf6c593bcac2ece2ab1d969e5e1) - **duckdb**: transpile BigQuery function TIMESTAMP_SUB to DuckDB *(PR [#6202](https://github.com/tobymao/sqlglot/pull/6202) by [@toriwei](https://github.com/toriwei))*\n- [`b82c571`](https://github.com/tobymao/sqlglot/commit/b82c57131707297abe174539023b9cb62b7cd6c7) - **snowflake**: annotate types for REGR_VALY *(PR [#6206](https://github.com/tobymao/sqlglot/pull/6206) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`39d8e19`](https://github.com/tobymao/sqlglot/commit/39d8e19419c2adbb80465be414d1cc3bbc6d007b) - **snowflake**: include VARIABLE kind in SET transpilation to DuckDB *(PR [#6201](https://github.com/tobymao/sqlglot/pull/6201) by [@toriwei](https://github.com/toriwei))*\n  - :arrow_lower_right: *addresses issue [#6177](https://github.com/tobymao/sqlglot/issues/6177) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`0037266`](https://github.com/tobymao/sqlglot/commit/00372664bf6acf2b0fff9ad4b206b597ef5378f7) - **snowflake**: annotate types for GETBIT *(PR [#6219](https://github.com/tobymao/sqlglot/pull/6219) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`a5458ce`](https://github.com/tobymao/sqlglot/commit/a5458ceca3bc239fb611791e38020632dd0824c8) - **snowflake**: add type annotation for DECODE function support *(PR [#6199](https://github.com/tobymao/sqlglot/pull/6199) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`a9d0f63`](https://github.com/tobymao/sqlglot/commit/a9d0f6333c38ffb0b5afc3c213ac7bf008d98ad6) - **DuckDB**: Transpile unix_millis to epoch_ms *(PR [#6224](https://github.com/tobymao/sqlglot/pull/6224) by [@vchan](https://github.com/vchan))*\n- [`238f705`](https://github.com/tobymao/sqlglot/commit/238f705940751f09464ee0f8260186f3f8124374) - **DuckDB**: Transpile unix_seconds to epoch *(PR [#6225](https://github.com/tobymao/sqlglot/pull/6225) by [@vchan](https://github.com/vchan))*\n- [`c8b0129`](https://github.com/tobymao/sqlglot/commit/c8b0129380df389be6ff22cafb4251181e919d23) - **exasol**: support bracket-delimited identifiers *(PR [#6231](https://github.com/tobymao/sqlglot/pull/6231) by [@JoepvandenHoven-Bluemine](https://github.com/JoepvandenHoven-Bluemine))*\n- [`417f1e8`](https://github.com/tobymao/sqlglot/commit/417f1e8ee50fb8f4377fad261660ffbd7444a429) - **snowflake**: annotate types for BITNOT *(PR [#6234](https://github.com/tobymao/sqlglot/pull/6234) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`fe8ab40`](https://github.com/tobymao/sqlglot/commit/fe8ab40e8e0559201e0b1896a6f1a8fb6b5b932d) - **snowflake**: 1st-class parsing support for BITAND, BIT_AND, BIT_NOT *(PR [#6243](https://github.com/tobymao/sqlglot/pull/6243) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`5ae3c47`](https://github.com/tobymao/sqlglot/commit/5ae3c47b1c6993b87341472c08714f4a0f738168) - **snowflake**: add type annotation for GROUPING() function *(PR [#6244](https://github.com/tobymao/sqlglot/pull/6244) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4133265`](https://github.com/tobymao/sqlglot/commit/413326514507ef06537dcc3d4b80a3fcbcd26f66) - **clickhouse**: parse `has` function into an `ArrayContains` expression *(PR [#6245](https://github.com/tobymao/sqlglot/pull/6245) by [@joeyutong](https://github.com/joeyutong))*\n- [`b722aa2`](https://github.com/tobymao/sqlglot/commit/b722aa2d4b65c698921066426838f080a31bdc35) - **duckdb**: cast LOWER() result to BLOB if input is bytes *(PR [#6218](https://github.com/tobymao/sqlglot/pull/6218) by [@toriwei](https://github.com/toriwei))*\n- [`cdd45b9`](https://github.com/tobymao/sqlglot/commit/cdd45b949fd1eefb147053424279b56b8effcbcf) - **optimizer**: annotate types for GROUPING_ID function. *(PR [#6249](https://github.com/tobymao/sqlglot/pull/6249) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`080ff3b`](https://github.com/tobymao/sqlglot/commit/080ff3bd93b36291d5bb0092d722f8307f0ae082) - **snowflake**: annotate types for BITAND_AGG *(PR [#6248](https://github.com/tobymao/sqlglot/pull/6248) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`87a818a`](https://github.com/tobymao/sqlglot/commit/87a818a899f61a675c22c697f468b3f6f7e2787f) - **snowflake**: annotate types for BITOR_AGG  *(PR [#6251](https://github.com/tobymao/sqlglot/pull/6251) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`a1b884d`](https://github.com/tobymao/sqlglot/commit/a1b884dc9ddfd2185de48cc9451a39f152879d39) - **snowflake**: annotate types for BITXOR_AGG *(PR [#6253](https://github.com/tobymao/sqlglot/pull/6253) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`71d93b1`](https://github.com/tobymao/sqlglot/commit/71d93b181d2aa3a77a022820446d6fec0133291f) - **duckdb**: implement casting to blob for UPPER() and move to helper method *(PR [#6254](https://github.com/tobymao/sqlglot/pull/6254) by [@toriwei](https://github.com/toriwei))*\n- [`ad2ad23`](https://github.com/tobymao/sqlglot/commit/ad2ad234b5a508040dce4f3920439be052742573) - **snowflake**: add missing return type mapping for MAX_BY and MAX_BY function *(PR [#6250](https://github.com/tobymao/sqlglot/pull/6250) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`39c1d81`](https://github.com/tobymao/sqlglot/commit/39c1d81174f2390b6b0c9dd14c0e550ad452a1df) - **snowflake**: annotate types for BOOLXOR_AGG *(PR [#6261](https://github.com/tobymao/sqlglot/pull/6261) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`71590d2`](https://github.com/tobymao/sqlglot/commit/71590d22cdb05594e2173a1500f763dc1a32a81d) - **snowflake**: add type annotation for SKEW function. *(PR [#6262](https://github.com/tobymao/sqlglot/pull/6262) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`5fd366d`](https://github.com/tobymao/sqlglot/commit/5fd366d9e6f7b3f1eb7a9cf41975cf13ce890ffe) - **snowflake**: annotate types for OBJECT_AGG *(PR [#6265](https://github.com/tobymao/sqlglot/pull/6265) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`3dae0fb`](https://github.com/tobymao/sqlglot/commit/3dae0fbb528762e5d5fd446350d42e9c841e2959) - **duckdb**: Support position and occurrence args for REGEXP_EXTRACT *(PR [#6266](https://github.com/tobymao/sqlglot/pull/6266) by [@vchan](https://github.com/vchan))*\n- [`dba0414`](https://github.com/tobymao/sqlglot/commit/dba04145c4bcda8c55890b4d7173dd6c0a64c37e) - **clickhouse**: Parse toStartOfxxx into exp.TimestampTrunc *(PR [#6268](https://github.com/tobymao/sqlglot/pull/6268) by [@joeyutong](https://github.com/joeyutong))*\n- [`d959ad0`](https://github.com/tobymao/sqlglot/commit/d959ad02140d692483a63b67d69d2a5d49954ea3) - transpile DuckDB exclusive end RANGE to SEQUENCE *(PR [#6270](https://github.com/tobymao/sqlglot/pull/6270) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6267](https://github.com/tobymao/sqlglot/issues/6267) opened by [@joeyutong](https://github.com/joeyutong)*\n- [`771732d`](https://github.com/tobymao/sqlglot/commit/771732d81459cc576f11eccc49794f33e62d14af) - **snowflake**: annotate types for REGR_AVGY *(PR [#6271](https://github.com/tobymao/sqlglot/pull/6271) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`8470be0`](https://github.com/tobymao/sqlglot/commit/8470be00731a4d79518a533a5f7ba884fa2f047e) - **snowflake**: add type annotation for BITMAP_COUNT function. *(PR [#6274](https://github.com/tobymao/sqlglot/pull/6274) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`98f25f9`](https://github.com/tobymao/sqlglot/commit/98f25f92cc1175ac7b2118a5a342db82adade13a) - **clickhouse**: support splitBy function *(PR [#6278](https://github.com/tobymao/sqlglot/pull/6278) by [@joeyutong](https://github.com/joeyutong))*\n- [`fabbf05`](https://github.com/tobymao/sqlglot/commit/fabbf057aba88f30205767d8c339727de45991c8) - **duckDB**: Add support for shorthand struct array literals in duckDB. *(PR [#6233](https://github.com/tobymao/sqlglot/pull/6233) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`a909fde`](https://github.com/tobymao/sqlglot/commit/a909fde068919823dc4cccc2655af48e4290137a) - **duckdb**: Add support for CREATE MACRO *(PR [#6292](https://github.com/tobymao/sqlglot/pull/6292) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#6290](https://github.com/tobymao/sqlglot/issues/6290) opened by [@francescomucio](https://github.com/francescomucio)*\n- [`11989be`](https://github.com/tobymao/sqlglot/commit/11989be34153ccdedeab3ab18ccf735f86e8b822) - add more expressions with positional meta *(PR [#6289](https://github.com/tobymao/sqlglot/pull/6289) by [@tobymao](https://github.com/tobymao))*\n- [`87651a6`](https://github.com/tobymao/sqlglot/commit/87651a671db2fe6162f06e2dcdef0b98e229bea5) - semantic facts closes [#6287](https://github.com/tobymao/sqlglot/pull/6287) *(PR [#6288](https://github.com/tobymao/sqlglot/pull/6288) by [@tobymao](https://github.com/tobymao))*\n- [`9c1a222`](https://github.com/tobymao/sqlglot/commit/9c1a2221b0327ba6848542c7b906e92f25a05bea) - **snowflake**: add type annotation for BITMAP_CONSTRUCT_AGG function. *(PR [#6285](https://github.com/tobymao/sqlglot/pull/6285) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`358105d`](https://github.com/tobymao/sqlglot/commit/358105d1296c7425e071ccf3189a31a02c00c923) - **snowflake**: type annotation for BITMAP_BIT_POSITION function *(PR [#6301](https://github.com/tobymao/sqlglot/pull/6301) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4ee7a50`](https://github.com/tobymao/sqlglot/commit/4ee7a500cc460b6f6a1ed103a12dca72e6d01c18) - **snowflake**: type inference for BITMAP_OR_AGG *(PR [#6297](https://github.com/tobymao/sqlglot/pull/6297) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`fcd537d`](https://github.com/tobymao/sqlglot/commit/fcd537de2c993ad0bd18acd84dbae354165f7d3f) - **snowflake**: conflict resolution. type annotation for BITMAP_BUCKET_NUMBER function. Tests added all dialects that support BITMAP_BUCKET_NUMBER *(PR [#6299](https://github.com/tobymao/sqlglot/pull/6299) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`3dffd59`](https://github.com/tobymao/sqlglot/commit/3dffd598496a9f2d94caec9d7f3dcb9791c94019) - **snowflake**: annotate types for PERCENTILE_DISC and WithinGroup *(PR [#6300](https://github.com/tobymao/sqlglot/pull/6300) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`6ce3cd7`](https://github.com/tobymao/sqlglot/commit/6ce3cd7de958d9f3773579ab22ae6cbbcb56ceb0) - **sqlite**: support binary `MATCH` operator closes [#6305](https://github.com/tobymao/sqlglot/pull/6305) *(PR [#6306](https://github.com/tobymao/sqlglot/pull/6306) by [@georgesittas](https://github.com/georgesittas))*\n- [`e903883`](https://github.com/tobymao/sqlglot/commit/e90388328fcf5b8061c99e325b87d5beb0046ffc) - **snowflake**: type annotation for APPROX_TOP_K_ACCUMULATE functio… *(PR [#6309](https://github.com/tobymao/sqlglot/pull/6309) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`afc0242`](https://github.com/tobymao/sqlglot/commit/afc0242c564f8de53e11865c2fba43fb36df0694) - **duckDB**: Cast inputs (BLOB → VARCHAR) for duckDB STARTS_WITH *(PR [#6240](https://github.com/tobymao/sqlglot/pull/6240) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`d170bbd`](https://github.com/tobymao/sqlglot/commit/d170bbde800a0308aaf8c81e59152c65be312155) - **duckdb**: transpile bigquery's `BYTES` variant of `REPLACE` *(PR [#6312](https://github.com/tobymao/sqlglot/pull/6312) by [@toriwei](https://github.com/toriwei))*\n- [`d3fefad`](https://github.com/tobymao/sqlglot/commit/d3fefad80d25ff5a6dd02426667ff0ea8478a1b2) - **tsql**: support `DATEDIFF_BIG` *(PR [#6323](https://github.com/tobymao/sqlglot/pull/6323) by [@lBilali](https://github.com/lBilali))*\n- [`21d1468`](https://github.com/tobymao/sqlglot/commit/21d1468377b9c8ad48c6cca1ae3b3744a807c29e) - **optimizer**: annotate type for APPROX_TOP_K *(PR [#6286](https://github.com/tobymao/sqlglot/pull/6286) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`93b4039`](https://github.com/tobymao/sqlglot/commit/93b4039f957f3eefbaaed2cb147bfa8c8c2a304e) - **duckdb**: preserve time zone and timestamp in DATE_TRUNC() *(PR [#6318](https://github.com/tobymao/sqlglot/pull/6318) by [@toriwei](https://github.com/toriwei))*\n- [`b71990f`](https://github.com/tobymao/sqlglot/commit/b71990f528d55c845f5771bfc4c5f6098eb97ad7) - **duckdb**: Add transpilation support for ANY_VALUE function with HAVING MAX and MIN clauses *(PR [#6325](https://github.com/tobymao/sqlglot/pull/6325) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`64c0d55`](https://github.com/tobymao/sqlglot/commit/64c0d554207ad40bcd6a93c20d15020752a5929d) - **sqlite**: support indexed table clause closes [#6331](https://github.com/tobymao/sqlglot/pull/6331) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6725217`](https://github.com/tobymao/sqlglot/commit/6725217d4058b5202006576bdf6ff4ec7230a9b9) - **sqlite**: support `NOT NULL` operator closes [#6334](https://github.com/tobymao/sqlglot/pull/6334) closes [#6335](https://github.com/tobymao/sqlglot/pull/6335) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ca81217`](https://github.com/tobymao/sqlglot/commit/ca812171ab800e3faa73ea1874dd6814c8d6f701) - **duckdb**: Transpile INITCAP with custom delimiters *(PR [#6302](https://github.com/tobymao/sqlglot/pull/6302) by [@treysp](https://github.com/treysp))*\n- [`7484c06`](https://github.com/tobymao/sqlglot/commit/7484c06be4534cd22dee14da542d5e29ff2c13a2) - **DuckDB**: Support rounding mode argument for ROUND function *(PR [#6350](https://github.com/tobymao/sqlglot/pull/6350) by [@vchan](https://github.com/vchan))*\n- [`79e314d`](https://github.com/tobymao/sqlglot/commit/79e314df76161319ba8495b95f54603cfef0c08a) - **duckdb**: handle casting BLOB input for TRIM() *(PR [#6353](https://github.com/tobymao/sqlglot/pull/6353) by [@toriwei](https://github.com/toriwei))*\n- [`c495a40`](https://github.com/tobymao/sqlglot/commit/c495a40ee4c1a69b14892e8455ae1bd2ceb5ea4f) - **optimizer**: annotate type for MINHASH *(PR [#6355](https://github.com/tobymao/sqlglot/pull/6355) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`f16f8a0`](https://github.com/tobymao/sqlglot/commit/f16f8a08072556fd617b5125300262d9bb8c1e48) - improve validate qualify column message closes [#6348](https://github.com/tobymao/sqlglot/pull/6348) *(PR [#6356](https://github.com/tobymao/sqlglot/pull/6356) by [@tobymao](https://github.com/tobymao))*\n- [`17abe23`](https://github.com/tobymao/sqlglot/commit/17abe231bc4d59912952f266ad4df86ece22c8d2) - make simplify more efficient in number of iterations *(PR [#6351](https://github.com/tobymao/sqlglot/pull/6351) by [@tobymao](https://github.com/tobymao))*\n- [`b1f9a97`](https://github.com/tobymao/sqlglot/commit/b1f9a976be3c0bcd895bef5bcdb95a013eeb28b7) - **optimizer**: annotate type for APPROXIMATE_SIMILARITY *(PR [#6360](https://github.com/tobymao/sqlglot/pull/6360) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`3aafca7`](https://github.com/tobymao/sqlglot/commit/3aafca74546b932cea93ed830c021f347ae03ded) - **optimizer**: annotate type for MINHASH_COMBINE *(PR [#6362](https://github.com/tobymao/sqlglot/pull/6362) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`09a4bd8`](https://github.com/tobymao/sqlglot/commit/09a4bd8870a075e641c6e3e4cee74d73a39e760a) - Trigger integration tests *(PR [#6339](https://github.com/tobymao/sqlglot/pull/6339) by [@erindru](https://github.com/erindru))*\n- [`7769129`](https://github.com/tobymao/sqlglot/commit/7769129eba7ae5f3594e0061bdb1079fedc5aafd) - bignum and time_ns to duckdb closes [#6379](https://github.com/tobymao/sqlglot/pull/6379) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`90a3fa9`](https://github.com/tobymao/sqlglot/commit/90a3fa9f6ddf0aa32b41118c59d4facd9fdb3398) - mark IgnoreNulls and RespectNulls as unsupported on postgres and mysql *(PR [#6377](https://github.com/tobymao/sqlglot/pull/6377) by [@NickCrews](https://github.com/NickCrews))*\n  - :arrow_lower_right: *addresses issue [#6376](https://github.com/tobymao/sqlglot/issues/6376) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`5bb1170`](https://github.com/tobymao/sqlglot/commit/5bb117082caeee719442d783ce6742d027b1492e) - transpile bigquery `greatest` null handling to duckdb *(PR [#6361](https://github.com/tobymao/sqlglot/pull/6361) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`bf07abd`](https://github.com/tobymao/sqlglot/commit/bf07abd4ee9eb0f5510cb7d1f232bdcaea88941e) - **snowflake**: annotation support for  APPROX_TOP_K_COMBINE  *(PR [#6378](https://github.com/tobymao/sqlglot/pull/6378) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`01890eb`](https://github.com/tobymao/sqlglot/commit/01890eb16d6624de4f26b7d8eadf850df6f2a042) - **trino**: support refresh materialized view statement closes [#6387](https://github.com/tobymao/sqlglot/pull/6387) *(PR [#6388](https://github.com/tobymao/sqlglot/pull/6388) by [@georgesittas](https://github.com/georgesittas))*\n- [`e4ea6cc`](https://github.com/tobymao/sqlglot/commit/e4ea6ccf08c0ff4063424bf538bc3b22f4b4cfaf) - transpile BQ APPROX_QUANTILES to DuckDB *(PR [#6349](https://github.com/tobymao/sqlglot/pull/6349) by [@treysp](https://github.com/treysp))*\n- [`95727f6`](https://github.com/tobymao/sqlglot/commit/95727f60d601796b34c850dee9366d79f6e4a24b) - **optimizer**: canonicalize table aliases *(PR [#6369](https://github.com/tobymao/sqlglot/pull/6369) by [@georgesittas](https://github.com/georgesittas))*\n- [`3b6855b`](https://github.com/tobymao/sqlglot/commit/3b6855b9787111f27225108241fbe4f389443e29) - **mysql**: support ZEROFILL column attribute *(PR [#6400](https://github.com/tobymao/sqlglot/pull/6400) by [@nian0114](https://github.com/nian0114))*\n  - :arrow_lower_right: *addresses issue [#6399](https://github.com/tobymao/sqlglot/issues/6399) opened by [@nian0114](https://github.com/nian0114)*\n- [`bb4eda1`](https://github.com/tobymao/sqlglot/commit/bb4eda1beb68b92de9ab014a63c67797a07df2fa) - **duckdb**: support transpiling SHA1 from BigQuery to DuckDB *(PR [#6404](https://github.com/tobymao/sqlglot/pull/6404) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`05e83b5`](https://github.com/tobymao/sqlglot/commit/05e83b56f1bf9323cfa819a7f1beb542524c1219) - **duckdb**: support transpilation of LEAST from BigQuery to DuckDB *(PR [#6415](https://github.com/tobymao/sqlglot/pull/6415) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`38472ce`](https://github.com/tobymao/sqlglot/commit/38472ce14bce731ba4c309d515223ae99e2575ac) - **duckdb**: transpile bigquery's %x format literal *(PR [#6375](https://github.com/tobymao/sqlglot/pull/6375) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`a6e1581`](https://github.com/tobymao/sqlglot/commit/a6e15811cf5643bcc18e1e227fea20922b05c54a) - **DuckDB**: Cast BIGNUMERIC and BIGDECIMAL types to DECIMAL(38, 5) *(PR [#6419](https://github.com/tobymao/sqlglot/pull/6419) by [@vchan](https://github.com/vchan))*\n- [`0b9d8ac`](https://github.com/tobymao/sqlglot/commit/0b9d8acbe75457424436e8c0acc047ab66e9fdc0) - **snowflake**: Annotate type for snowflake MAX function *(PR [#6422](https://github.com/tobymao/sqlglot/pull/6422) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`68e9414`](https://github.com/tobymao/sqlglot/commit/68e9414725a60b2842d870fa222d8466057a94f6) - **snowflake**: Annotate type for snowflake MIN function *(PR [#6427](https://github.com/tobymao/sqlglot/pull/6427) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`1318de7`](https://github.com/tobymao/sqlglot/commit/1318de77a8aa514ec7eb9f9b8c03228e3f8eb008) - **snowflake**: Annotate type for snowflake NORMAL *(PR [#6434](https://github.com/tobymao/sqlglot/pull/6434) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ffbb5c7`](https://github.com/tobymao/sqlglot/commit/ffbb5c7e40aa064ffcd4827e96ea66cfd045118e) - **snowflake**: annotate type for HASH_AGG in Snowflake *(PR [#6438](https://github.com/tobymao/sqlglot/pull/6438) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`161255f`](https://github.com/tobymao/sqlglot/commit/161255f6c90b9c3ed2074e734f6d074db1d7a6dd) - Add support for `LOCALTIME` function *(PR [#6443](https://github.com/tobymao/sqlglot/pull/6443) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ca329f0`](https://github.com/tobymao/sqlglot/commit/ca329f037a230c315437d830638b514190764c5a) - **duckdb**: support transpilation of SHA256 from bigquery to duckdb *(PR [#6421](https://github.com/tobymao/sqlglot/pull/6421) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`e18ae24`](https://github.com/tobymao/sqlglot/commit/e18ae248423dbbca78a24a60ea0193da2ee7f68c) - **snowflake**: Annotate type for snowflake REGR_SLOPE function *(PR [#6425](https://github.com/tobymao/sqlglot/pull/6425) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`1d847f0`](https://github.com/tobymao/sqlglot/commit/1d847f0a1f88fce5df340ab646a72c8abbc12a86) - **snowflake**: parse & annotate `CHECK_JSON`, `CHECK_XML` *(PR [#6439](https://github.com/tobymao/sqlglot/pull/6439) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`6843812`](https://github.com/tobymao/sqlglot/commit/68438129ceeea70f801e0ae728c51c19291fc7d8) - add correlation id to remote workflow trigger *(PR [#6441](https://github.com/tobymao/sqlglot/pull/6441) by [@erindru](https://github.com/erindru))*\n- [`cb3080d`](https://github.com/tobymao/sqlglot/commit/cb3080d4bed18b1bfbbd08380ed60deeefd15530) - **snowflake**: annotation support for APPROX_TOP_K_ESTIMATE . Return type ARRAY *(PR [#6445](https://github.com/tobymao/sqlglot/pull/6445) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`cd9f037`](https://github.com/tobymao/sqlglot/commit/cd9f037882eef253e86fdb1d51521e0acd7db3f9) - **optimizer**: store pk name if provided *(PR [#6424](https://github.com/tobymao/sqlglot/pull/6424) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`65194e4`](https://github.com/tobymao/sqlglot/commit/65194e465489151aa51859a6e3f5672f7d4c5f3b) - **snowflake**: Annotate type for snowflake RANDSTR function *(PR [#6436](https://github.com/tobymao/sqlglot/pull/6436) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`351d783`](https://github.com/tobymao/sqlglot/commit/351d7834915e02a9f4949f9925437e2731f3a8b4) - add support for LOCALTIMESTAMP *(PR [#6448](https://github.com/tobymao/sqlglot/pull/6448) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a56262e`](https://github.com/tobymao/sqlglot/commit/a56262e6b4276baae144855478807c173db77ab9) - **snowflake**: Annotate type for snowflake MEDIAN *(PR [#6426](https://github.com/tobymao/sqlglot/pull/6426) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`2c56567`](https://github.com/tobymao/sqlglot/commit/2c56567755c8a6571d8b7d410c9de943e54df58b) - **snowflake**: Annotate type for snowflake SEARCH_IP  *(PR [#6440](https://github.com/tobymao/sqlglot/pull/6440) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ac86568`](https://github.com/tobymao/sqlglot/commit/ac86568a939f692b99813da100297b61fb54e044) - **snowflake**: Added decfloat type *(PR [#6444](https://github.com/tobymao/sqlglot/pull/6444) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ebe718a`](https://github.com/tobymao/sqlglot/commit/ebe718a72d5b5871a8d6e67754ff50e873d55b41) - **duckdb**: Add support for format elements used in date/time functions like FORMAT_DATETIME *(PR [#6428](https://github.com/tobymao/sqlglot/pull/6428) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c111f64`](https://github.com/tobymao/sqlglot/commit/c111f643d61064280024b4cc5c0fc250581fbe55) - **snowflake**: annotation support for APPROX_PERCENTILE_ACCUMULATE *(PR [#6455](https://github.com/tobymao/sqlglot/pull/6455) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`a7d211e`](https://github.com/tobymao/sqlglot/commit/a7d211e6fdce968c64b050c77e026cc23fdc07e5) - **duckdb**: transpile DECFLOAT type to DECIMAL(38, 5) *(PR [#6462](https://github.com/tobymao/sqlglot/pull/6462) by [@toriwei](https://github.com/toriwei))*\n- [`94d46b8`](https://github.com/tobymao/sqlglot/commit/94d46b8eafd5abe252407d2bbe306ca579a29b20) - **snowflake**: annotation support for APPROX_PERCENTILE_ESTIMATE. Return type DOUBLE *(PR [#6461](https://github.com/tobymao/sqlglot/pull/6461) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`2ac30b0`](https://github.com/tobymao/sqlglot/commit/2ac30b08bd663bbaf00ae075c4db0c3d27ab6640) - **snowflake**: annotation support for APPROX_PERCENTILE_COMBINE *(PR [#6460](https://github.com/tobymao/sqlglot/pull/6460) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d44bda3`](https://github.com/tobymao/sqlglot/commit/d44bda376c06956947a09a9f279cce886a63b981) - **optimizer**: Annotate type for ZIPF *(PR [#6453](https://github.com/tobymao/sqlglot/pull/6453) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`34dbd47`](https://github.com/tobymao/sqlglot/commit/34dbd478957c1796998d0b263f63c8ce1db7a320) - **optimizer**: Annotate type for XMLGET *(PR [#6457](https://github.com/tobymao/sqlglot/pull/6457) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ff3f0f9`](https://github.com/tobymao/sqlglot/commit/ff3f0f998674f5b2741c3f6cadbe24fa8fb607ad) - **databricks**: add support for ?:: operator *(PR [#6469](https://github.com/tobymao/sqlglot/pull/6469) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`0d211f2`](https://github.com/tobymao/sqlglot/commit/0d211f2b36167cfb7856b8ec25f597f70317a9c7) - **snowflake**: annotate type for MODE function snowflake *(PR [#6447](https://github.com/tobymao/sqlglot/pull/6447) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`cc4c8ab`](https://github.com/tobymao/sqlglot/commit/cc4c8ab43ab71790bc2bb9f8f3c06e34f89f999f) - **snowflake**: annotate type for PERCENTILE_CONT in Snowflake *(PR [#6470](https://github.com/tobymao/sqlglot/pull/6470) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7dbc242`](https://github.com/tobymao/sqlglot/commit/7dbc242a637a8890511cc14f22bce4d425f1f55d) - **snowflake**: annotation support for CURRENT REGION. Return type VARCHAR *(PR [#6473](https://github.com/tobymao/sqlglot/pull/6473) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`43a6a5c`](https://github.com/tobymao/sqlglot/commit/43a6a5c601421e15a7f94dd489cb4fbcf9d2c8c3) - **snowflake**: annotation support for CURRENT_ORGANIZATION_NAME. Return type VARCHAR *(PR [#6475](https://github.com/tobymao/sqlglot/pull/6475) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`f1f7c6a`](https://github.com/tobymao/sqlglot/commit/f1f7c6ae6b6aa3f6f2251d0f81ee667440ca53d1) - **snowflake**: annotation support for CURRENT_ORGANIZATION_USER. *(PR [#6476](https://github.com/tobymao/sqlglot/pull/6476) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d268203`](https://github.com/tobymao/sqlglot/commit/d268203e1dbae4e3aff863108f6d09a6f8274db5) - **snowflake**: annotation support for CURRENT_ROLE_TYPE *(PR [#6479](https://github.com/tobymao/sqlglot/pull/6479) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`fd4431b`](https://github.com/tobymao/sqlglot/commit/fd4431bf9550c03aa761c642a68a21a146fd8548) - **snowflake**: annotate type for VECTOR_L1_DISTANCE, VECTOR_L2_DISTANCE, VECTOR_COSINE_SIMILARITY functions *(PR [#6468](https://github.com/tobymao/sqlglot/pull/6468) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`e6adba7`](https://github.com/tobymao/sqlglot/commit/e6adba76cc2f27633a9d38bfaea3356e71d00a4c) - **BigQuery**: Add support for coercing STRING literals to temporal types *(PR [#6482](https://github.com/tobymao/sqlglot/pull/6482) by [@vchan](https://github.com/vchan))*\n- [`68a5e61`](https://github.com/tobymao/sqlglot/commit/68a5e615b24e518cb90c9b80cf25355fcabdb468) - **snowflake**: annotate type for REGR_* functions *(PR [#6452](https://github.com/tobymao/sqlglot/pull/6452) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`f7458a4`](https://github.com/tobymao/sqlglot/commit/f7458a40d3b09a2e212f6705ac4a77c99714508e) - **optimizer**: annotate type for snowflake func TO_BOOLEAN *(PR [#6481](https://github.com/tobymao/sqlglot/pull/6481) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`1531a67`](https://github.com/tobymao/sqlglot/commit/1531a67ac7806f3b4582f6cf1ea02342a517de74) - **snowflake**: annotate type for VECTOR_INNER_PRODUCT *(PR [#6486](https://github.com/tobymao/sqlglot/pull/6486) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`f6b2b3b`](https://github.com/tobymao/sqlglot/commit/f6b2b3bc6e1c95340149be65d80ef7e177b28d82) - **snowflake**: support padside argument for BIT[OR|AND|XOR] *(PR [#6487](https://github.com/tobymao/sqlglot/pull/6487) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`e891397`](https://github.com/tobymao/sqlglot/commit/e89139714aefd8a6481a90d9753c81910c9f88e9) - **BigQuery**: Add support for the NET.HOST function *(PR [#6480](https://github.com/tobymao/sqlglot/pull/6480) by [@vchan](https://github.com/vchan))*\n- [`2cc67cd`](https://github.com/tobymao/sqlglot/commit/2cc67cd7386914043a9cb4eb322fb1fa9af15c8b) - **singlestore**: support dcolonqmark *(PR [#6485](https://github.com/tobymao/sqlglot/pull/6485) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`7d485c7`](https://github.com/tobymao/sqlglot/commit/7d485c7cffe7b6d0113cfcfcf0736de0383bd380) - **duckdb**: Add transpilation support for the negative integer args for BITNOT *(PR [#6490](https://github.com/tobymao/sqlglot/pull/6490) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ef130f1`](https://github.com/tobymao/sqlglot/commit/ef130f1b944b4be835d4a6831fec9a333a825a34) - **snowflake**: Annotated type for ARRAY_CONSTRUCT_COMPACT [#6496](https://github.com/tobymao/sqlglot/pull/6496) *(commit by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`36cf0bf`](https://github.com/tobymao/sqlglot/commit/36cf0bf6671f622344afee52d7aafe30f19ecf9a) - **snowflake**: annotation support for CURRENT_ROLE. *(PR [#6478](https://github.com/tobymao/sqlglot/pull/6478) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`cbba04c`](https://github.com/tobymao/sqlglot/commit/cbba04cb292fe8b3fd38c87d9ccb624cdcb52843) - **databricks**: support comma-separated syntax for OVERLAY function *(PR [#6497](https://github.com/tobymao/sqlglot/pull/6497) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`dc8f26a`](https://github.com/tobymao/sqlglot/commit/dc8f26a3a5e023a0e54caa345b129fb1b4fe805f) - **optimizer**: bq annotate type for NULL *(PR [#6491](https://github.com/tobymao/sqlglot/pull/6491) by [@geooo109](https://github.com/geooo109))*\n- [`c97a81d`](https://github.com/tobymao/sqlglot/commit/c97a81d68a1584fad48475725665a7678fcad9d1) - **optimizer**: annotate TO_HEX(MD5(...)) in BigQuery *(PR [#6500](https://github.com/tobymao/sqlglot/pull/6500) by [@georgesittas](https://github.com/georgesittas))*\n- [`a5797a1`](https://github.com/tobymao/sqlglot/commit/a5797a1c867c4ade71ae4ddf93232576993cf5bc) - **duckdb**: handle named arguments and non-integer scale input for ROUND *(PR [#6495](https://github.com/tobymao/sqlglot/pull/6495) by [@toriwei](https://github.com/toriwei))*\n- [`8b5298a`](https://github.com/tobymao/sqlglot/commit/8b5298a6578af80fd9676eb222422862d5468859) - **duckdb**: Transpile BQ's WEEK based `DATE_DIFF` *(PR [#6507](https://github.com/tobymao/sqlglot/pull/6507) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2c013a5`](https://github.com/tobymao/sqlglot/commit/2c013a5cc8e37cde8a8f9443e0397191ce82f0f5) - **exasol**: qualify bare stars to facilitate transpilation *(PR [#6431](https://github.com/tobymao/sqlglot/pull/6431) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`41b776b`](https://github.com/tobymao/sqlglot/commit/41b776bdc6936f18accd9f7308b55acd383bb596) - **postgres,trino,duckdb**: added support for current_catalog *(PR [#6492](https://github.com/tobymao/sqlglot/pull/6492) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`dd19bea`](https://github.com/tobymao/sqlglot/commit/dd19beae95f077cfd8b6e315eca7ff212817b250) - **snowflake**: annotation support for CURRENT_ACCOUNT *(PR [#6512](https://github.com/tobymao/sqlglot/pull/6512) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`2e8105e`](https://github.com/tobymao/sqlglot/commit/2e8105eebaec25fc8f94f1e68951198660f404e1) - **snowflake**: Annotate type for VAR_POP, VAR_SAMP, DuckDB consistency fix for VAR_SAMP *(PR [#6488](https://github.com/tobymao/sqlglot/pull/6488) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`cfb02c1`](https://github.com/tobymao/sqlglot/commit/cfb02c1aa676e801b2d13a84467b4904cd834ffe) - **snowflake**: annotation support for CURRENT_ACCOUNT_NAME *(PR [#6513](https://github.com/tobymao/sqlglot/pull/6513) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`1004e31`](https://github.com/tobymao/sqlglot/commit/1004e31cce62cce2e2afb7eab85ed8bdecaede3b) - **snowflake**: annotation support for CURRENT_AVAILABLE_ROLES *(PR [#6514](https://github.com/tobymao/sqlglot/pull/6514) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`ff201fe`](https://github.com/tobymao/sqlglot/commit/ff201febd27937a97674dd091928456dde733254) - **snowflake**: annotation support for CURRENT_CLIENT *(PR [#6515](https://github.com/tobymao/sqlglot/pull/6515) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d777a9c`](https://github.com/tobymao/sqlglot/commit/d777a9c0feef15ac036f7b413112de4d7cc8bea4) - **snowflake**: annotation support for CURRENT_IP_ADDRESS *(PR [#6518](https://github.com/tobymao/sqlglot/pull/6518) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`c296061`](https://github.com/tobymao/sqlglot/commit/c2960615a3bd279b7c5f775d5b93ae12aa27a3b8) - **snowflake**: Transpilation of TO_BINARY from snowflake to duckdb *(PR [#6504](https://github.com/tobymao/sqlglot/pull/6504) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7a70164`](https://github.com/tobymao/sqlglot/commit/7a70164d8cf361cf4c0a7d5789bb51676f772959) - **duckdb**: transpile Snowflake's `RANDSTR` function *(PR [#6502](https://github.com/tobymao/sqlglot/pull/6502) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`a26d419`](https://github.com/tobymao/sqlglot/commit/a26d4191e5468e39eafdf7a981e7b890d438b2c9) - **snowflake**: annotation support for CURRENT_DATABASE *(PR [#6516](https://github.com/tobymao/sqlglot/pull/6516) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`0acdf7f`](https://github.com/tobymao/sqlglot/commit/0acdf7fc783f2722536ec24dcf8600957febf7ca) - **snowflake**: annotation support for CURRENT_SCHEMAS *(PR [#6519](https://github.com/tobymao/sqlglot/pull/6519) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`43cce89`](https://github.com/tobymao/sqlglot/commit/43cce895da80d21abc89d40de5d7fddd68871bf0) - **snowflake**: annotation support for CURRENT_SECONDARY_ROLES *(PR [#6520](https://github.com/tobymao/sqlglot/pull/6520) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`c21b4b1`](https://github.com/tobymao/sqlglot/commit/c21b4b1134b368ee5144339b59e70ddcc54f3dbc) - **snowflake**: annotation support for CURRENT_SESSION *(PR [#6521](https://github.com/tobymao/sqlglot/pull/6521) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`57a83c0`](https://github.com/tobymao/sqlglot/commit/57a83c018dace690f7bb363c25ee6bde33c3d60f) - **snowflake**: annotation support for CURRENT_STATEMENT *(PR [#6522](https://github.com/tobymao/sqlglot/pull/6522) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4b240e4`](https://github.com/tobymao/sqlglot/commit/4b240e40a8809a6eea2a279370a884f4a7b03dfa) - **snowflake**: annotation support for CURRENT_VERSION *(PR [#6524](https://github.com/tobymao/sqlglot/pull/6524) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`c1a831f`](https://github.com/tobymao/sqlglot/commit/c1a831f5bf662ab8d8e07dc2bb949f2adcbe7d7c) - **snowflake**: annotation support for CURRENT_TRANSACTION *(PR [#6523](https://github.com/tobymao/sqlglot/pull/6523) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`2e162b0`](https://github.com/tobymao/sqlglot/commit/2e162b0d34066e7aa7edac3156739bcd31a634fc) - **snowflake**: annotation support for CURRENT_WAREHOUSE *(PR [#6525](https://github.com/tobymao/sqlglot/pull/6525) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`18e9814`](https://github.com/tobymao/sqlglot/commit/18e98145906eaa5b769af49cf46b58a1d9448aee) - **snowflake**: support DAYOFWEEK_ISO date part *(PR [#6531](https://github.com/tobymao/sqlglot/pull/6531) by [@toriwei](https://github.com/toriwei))*\n- [`ee5e7b9`](https://github.com/tobymao/sqlglot/commit/ee5e7b931ca745a000dc8a720b56aee7b44186b2) - Automatically trigger integration tests scoped to modified dialects *(PR [#6505](https://github.com/tobymao/sqlglot/pull/6505) by [@erindru](https://github.com/erindru))*\n- [`e60634f`](https://github.com/tobymao/sqlglot/commit/e60634f0e1c396b54ad357132606286bd21d3e36) - **clickhouse**: Add support for quantilesExactExclusive agg func *(PR [#6535](https://github.com/tobymao/sqlglot/pull/6535) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#6533](https://github.com/tobymao/sqlglot/issues/6533) opened by [@vargasj-ms](https://github.com/vargasj-ms)*\n- [`41a9e88`](https://github.com/tobymao/sqlglot/commit/41a9e88bb9800205df0b3e10a1976699dc4fe4f9) - **duckdb**: Add support to transpile binary args for bitwise operators *(PR [#6508](https://github.com/tobymao/sqlglot/pull/6508) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`2555856`](https://github.com/tobymao/sqlglot/commit/2555856cac7434ef91cc1584d52610178e45c4b9) - **optimizer**: annotate scalar subqueries *(PR [#6536](https://github.com/tobymao/sqlglot/pull/6536) by [@georgesittas](https://github.com/georgesittas))*\n- [`71e7630`](https://github.com/tobymao/sqlglot/commit/71e763096462aa888a353ac1ad3675a9e5b4841a) - **snowflake**: normalize FLOAT to DOUBLE *(PR [#6501](https://github.com/tobymao/sqlglot/pull/6501) by [@toriwei](https://github.com/toriwei))*\n- [`9badf6a`](https://github.com/tobymao/sqlglot/commit/9badf6a6b1972fc37164b29aa416bb897d7ec6a6) - **snowflake**: Annotate type for TRY_* functions *(PR [#6509](https://github.com/tobymao/sqlglot/pull/6509) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`aad1332`](https://github.com/tobymao/sqlglot/commit/aad1332fee7c82c29dae3caed9a6a1c882c1d4a0) - **duckdb**: support transpilation of BITMAP_BIT_POSITION from snowflake to duckdb *(PR [#6541](https://github.com/tobymao/sqlglot/pull/6541) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`980f538`](https://github.com/tobymao/sqlglot/commit/980f53852444ec38c45483cbbf63a286244262a6) - **databricks, snowflake**: support UNIFORM function *(PR [#6547](https://github.com/tobymao/sqlglot/pull/6547) by [@toriwei](https://github.com/toriwei))*\n- [`8a12611`](https://github.com/tobymao/sqlglot/commit/8a12611e9499497d0c8b1e1e418986b2d91a6505) - **snowflake**: New type + type annotation for TO_FILE  *(PR [#6548](https://github.com/tobymao/sqlglot/pull/6548) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`906c933`](https://github.com/tobymao/sqlglot/commit/906c933235c82598b0d08f8c66dd3db0b8f409a5) - **postgres**: overlap operator *(PR [#6545](https://github.com/tobymao/sqlglot/pull/6545) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`6fa5e9c`](https://github.com/tobymao/sqlglot/commit/6fa5e9ce017185d721fab23d271f2fcb6098190e) - **snowflake**: transpile FLOOR with scale arg to DuckDB *(PR [#6549](https://github.com/tobymao/sqlglot/pull/6549) by [@treysp](https://github.com/treysp))*\n- [`487b1db`](https://github.com/tobymao/sqlglot/commit/487b1dbff86f05acbd3caa769cd330a8a373480b) - make parser more conservative as assignments are relatively rare *(commit by [@tobymao](https://github.com/tobymao))*\n- [`370b1f6`](https://github.com/tobymao/sqlglot/commit/370b1f621844d3ac8831c998ea2046f1e1b91b65) - **postgres,tsql,duckdb**: add support for session_user *(PR [#6555](https://github.com/tobymao/sqlglot/pull/6555) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`8d83ee2`](https://github.com/tobymao/sqlglot/commit/8d83ee207070cc17ce0e433d636464255e09748f) - **duckdb**: Transpiled REGR_VALX and VALY *(PR [#6538](https://github.com/tobymao/sqlglot/pull/6538) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ad546b3`](https://github.com/tobymao/sqlglot/commit/ad546b30d40abe0da97b38bd300452e0354231e3) - **databricks**: add support for getdate(), now(), and current_timezone() *(PR [#6567](https://github.com/tobymao/sqlglot/pull/6567) by [@toriwei](https://github.com/toriwei))*\n- [`dbbace0`](https://github.com/tobymao/sqlglot/commit/dbbace01cd5f1fc44f5ad278def25f547686f9c5) - **snowflake**: remove transpilation support of APPROX_TOP_K to duckdb *(PR [#6560](https://github.com/tobymao/sqlglot/pull/6560) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`2bc2506`](https://github.com/tobymao/sqlglot/commit/2bc2506e0e0b26e82661a08217855d693f30dc25) - **bigquery**: support SAFE.TIMESTAMP annotation *(PR [#6550](https://github.com/tobymao/sqlglot/pull/6550) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`a51cc7b`](https://github.com/tobymao/sqlglot/commit/a51cc7b6e02c5b37bf43b82a0d76b83d41248ac9) - **mysql**: elt function in mysql *(PR [#6568](https://github.com/tobymao/sqlglot/pull/6568) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`4339b26`](https://github.com/tobymao/sqlglot/commit/4339b26db546862b10a0e8d746506b406ecfa306) - **optimizer**: expose struct fields using UNNEST without aliases *(PR [#6566](https://github.com/tobymao/sqlglot/pull/6566) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`7bfffe5`](https://github.com/tobymao/sqlglot/commit/7bfffe5d894c60bd0139d57c53bb1816c2739d74) - **snowflake**: support transpilation of TO_BOOLEAN from snowflake to duckdb *(PR [#6564](https://github.com/tobymao/sqlglot/pull/6564) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`d1cd9d6`](https://github.com/tobymao/sqlglot/commit/d1cd9d65be7779ea442260a92978d569f3d6ccd8) - **databricks**: support CURDATE *(PR [#6578](https://github.com/tobymao/sqlglot/pull/6578) by [@toriwei](https://github.com/toriwei))*\n- [`c906a96`](https://github.com/tobymao/sqlglot/commit/c906a96b33d28dfc1191840817dbf2bab0731d17) - **snowflake**: support GETDATE, SYSDATE, SYSTIMESTAMP *(PR [#6575](https://github.com/tobymao/sqlglot/pull/6575) by [@toriwei](https://github.com/toriwei))*\n\n### :bug: Bug Fixes\n- [`9020684`](https://github.com/tobymao/sqlglot/commit/9020684a7e984a10fa4775339596ac5a0d6a6d93) - nested natural join performance closes [#5514](https://github.com/tobymao/sqlglot/pull/5514) *(PR [#5515](https://github.com/tobymao/sqlglot/pull/5515) by [@tobymao](https://github.com/tobymao))*\n- [`394870a`](https://github.com/tobymao/sqlglot/commit/394870a7ee9bb3bc814b7c3847193687f06b432b) - **duckdb**: transpile ADD_MONTHS *(PR [#5523](https://github.com/tobymao/sqlglot/pull/5523) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5505](https://github.com/tobymao/sqlglot/issues/5505) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`249692c`](https://github.com/tobymao/sqlglot/commit/249692c67450a1fe3775e1f35b6f62fdb0a62e1a) - **duckdb**: put guard in AddMonths generator before annotating it *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d799c5a`](https://github.com/tobymao/sqlglot/commit/d799c5af23010a67c29edb6d45a40fb24903e1a3) - **optimizer**: preserve projection names when merging subqueries *(commit by [@snovik75](https://github.com/snovik75))*\n- [`8130bd4`](https://github.com/tobymao/sqlglot/commit/8130bd40815803a6781ee8f20fccd30987516192) - **parser**: WEEKDAY of WEEK as VAR *(PR [#5552](https://github.com/tobymao/sqlglot/pull/5552) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5547](https://github.com/tobymao/sqlglot/issues/5547) opened by [@rloredo](https://github.com/rloredo)*\n- [`4e1373f`](https://github.com/tobymao/sqlglot/commit/4e1373f301cbea3cb5762fc1430b65deae3f9d04) - **doris**: Rename Table *(PR [#5549](https://github.com/tobymao/sqlglot/pull/5549) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`16f544d`](https://github.com/tobymao/sqlglot/commit/16f544dc25d5d61277d32f02e4be18c10d16cf9f) - **doris**: fix DATE_TRUNC and partition by *(PR [#5553](https://github.com/tobymao/sqlglot/pull/5553) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`6295414`](https://github.com/tobymao/sqlglot/commit/6295414fb41401f92993e661b880a0727e74c087) - convert unit to Var instead of choosing default in `unit_to_var` *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6872b43`](https://github.com/tobymao/sqlglot/commit/6872b43ba17a39137172fd2fa9f0d059ce595ef9) - **parser**: use dialect in DataType.build fixes [#5560](https://github.com/tobymao/sqlglot/pull/5560) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6f354d9`](https://github.com/tobymao/sqlglot/commit/6f354d958fb9ca9242b7fc1d2da86af74d57fedc) - **clickhouse**: add ROWS keyword in OFFSET followed by FETCH fixes [#5564](https://github.com/tobymao/sqlglot/pull/5564) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`8c0cb76`](https://github.com/tobymao/sqlglot/commit/8c0cb764fd825062fb7334032b8eeffbc39627d5) - **parser**: more robust CREATE SEQUENCE *(PR [#5566](https://github.com/tobymao/sqlglot/pull/5566) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5537](https://github.com/tobymao/sqlglot/issues/5537) opened by [@tekumara](https://github.com/tekumara)*\n- [`7e9df88`](https://github.com/tobymao/sqlglot/commit/7e9df880bc118d0dbb2dbd6344f805f79af2fe5e) - **doris**: CURRENT_DATE *(PR [#5567](https://github.com/tobymao/sqlglot/pull/5567) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`51e0335`](https://github.com/tobymao/sqlglot/commit/51e0335377fe2bc2e2a94a623475791e9dd19fb9) - **optimizer**: parse and annotate type for bigquery REVERSE *(PR [#5571](https://github.com/tobymao/sqlglot/pull/5571) by [@geooo109](https://github.com/geooo109))*\n- [`d0d62ed`](https://github.com/tobymao/sqlglot/commit/d0d62ede6320b3fd0eee04b7073f5708676dc58c) - **dremio**: support `TO_CHAR` with numeric inputs *(PR [#5570](https://github.com/tobymao/sqlglot/pull/5570) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`7928985`](https://github.com/tobymao/sqlglot/commit/7928985a655c3d0244bc9175a37f502b19a5c5f0) - **bigquery**: allow dashes in JSONPath keys *(PR [#5574](https://github.com/tobymao/sqlglot/pull/5574) by [@georgesittas](https://github.com/georgesittas))*\n- [`866042d`](https://github.com/tobymao/sqlglot/commit/866042d0268da0cebce042c0868878c0fb39c3d1) - Remove TokenType.APPLY from table alias tokens *(PR [#5592](https://github.com/tobymao/sqlglot/pull/5592) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5591](https://github.com/tobymao/sqlglot/issues/5591) opened by [@saadbelgi](https://github.com/saadbelgi)*\n- [`b485f66`](https://github.com/tobymao/sqlglot/commit/b485f6666fa8625b7da45ef832b5d666fbb707ea) - **dremio**: improve `TO_CHAR` transpilability *(PR [#5580](https://github.com/tobymao/sqlglot/pull/5580) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`81874e9`](https://github.com/tobymao/sqlglot/commit/81874e9c3aafcc2cf8fb443f65146c5b3598b9b3) - handle unknown types in `unit_to_str` *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`f1269f5`](https://github.com/tobymao/sqlglot/commit/f1269f5ecfccfee4cdeeda5bfd10eb1c47994fad) - **tsql**: do not attach limit modifier to set operation *(PR [#5609](https://github.com/tobymao/sqlglot/pull/5609) by [@georgesittas](https://github.com/georgesittas))*\n- [`a6edf8e`](https://github.com/tobymao/sqlglot/commit/a6edf8ee3273a7736ed801ef8dea302613b119da) - **tsql**: Remove ORDER from set op modifiers too *(PR [#5626](https://github.com/tobymao/sqlglot/pull/5626) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5618](https://github.com/tobymao/sqlglot/issues/5618) opened by [@MQMMMQM](https://github.com/MQMMMQM)*\n- [`ce5840e`](https://github.com/tobymao/sqlglot/commit/ce5840ed615e162a93cd911ab6207160878fcc64) - **exasol**: update several dialect properties to correctly reflect semantics *(PR [#5642](https://github.com/tobymao/sqlglot/pull/5642) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`3ab1d44`](https://github.com/tobymao/sqlglot/commit/3ab1d4487279cab3be2d3764e51516c6db21629d) - **generator**: Wrap CONCAT items with COALESCE less aggressively *(PR [#5641](https://github.com/tobymao/sqlglot/pull/5641) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`045d2f0`](https://github.com/tobymao/sqlglot/commit/045d2f02649b0e6dc178c079e4e0db201ed9bf08) - **duckdb**: Transpile Spark's FIRST(col, TRUE) *(PR [#5644](https://github.com/tobymao/sqlglot/pull/5644) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5643](https://github.com/tobymao/sqlglot/issues/5643) opened by [@michal-clutch](https://github.com/michal-clutch)*\n- [`0427c7b`](https://github.com/tobymao/sqlglot/commit/0427c7b7aa9f8161324085a98c5f531fa35c8b0c) - **optimizer**: qualify columns for AggFunc with DISTINCT *(PR [#5708](https://github.com/tobymao/sqlglot/pull/5708) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5698](https://github.com/tobymao/sqlglot/issues/5698) opened by [@georgesittas](https://github.com/georgesittas)*\n- [`ec93497`](https://github.com/tobymao/sqlglot/commit/ec93497bac82090b88c6e749ec2adc99bbc23a61) - **bigquery**: support commands inside for loops *(PR [#5732](https://github.com/tobymao/sqlglot/pull/5732) by [@treysp](https://github.com/treysp))*\n- [`85845bb`](https://github.com/tobymao/sqlglot/commit/85845bb941ac9a4ee090a89cd3d3dab4ab5835a7) - **snowflake**: allow exclude as id var *(PR [#5764](https://github.com/tobymao/sqlglot/pull/5764) by [@treysp](https://github.com/treysp))*\n- [`db2d9cc`](https://github.com/tobymao/sqlglot/commit/db2d9cca9718fb196066dbf60840124917d1f8ac) - **tokenizer**: handle empty hex strings *(PR [#5763](https://github.com/tobymao/sqlglot/pull/5763) by [@paulolieuthier](https://github.com/paulolieuthier))*\n  - :arrow_lower_right: *fixes issue [#5761](https://github.com/tobymao/sqlglot/issues/5761) opened by [@paulolieuthier](https://github.com/paulolieuthier)*\n- [`982257b`](https://github.com/tobymao/sqlglot/commit/982257b40973cdfc20a8d6dd9a1674cda7eb75c4) - **bigquery**: Crash when ARRAY_CONCAT is called with no expressions *(PR [#5755](https://github.com/tobymao/sqlglot/pull/5755) by [@ozadari](https://github.com/ozadari))*\n- [`24ca504`](https://github.com/tobymao/sqlglot/commit/24ca504360779c8a20a58accf506eb9600ac9bf8) - **bigquery**: Crash when ARRAY_CONCAT is called with no expressions *(PR [#5755](https://github.com/tobymao/sqlglot/pull/5755) by [@ozadari](https://github.com/ozadari))*\n- [`d8f6a37`](https://github.com/tobymao/sqlglot/commit/d8f6a376ba1fcca48e4a65923dd7a319ce6cfb91) - **optimizer**: allow aliased negative integer literal as group by column *(PR [#5791](https://github.com/tobymao/sqlglot/pull/5791) by [@treysp](https://github.com/treysp))*\n- [`1259576`](https://github.com/tobymao/sqlglot/commit/1259576283f1d45abb70ec40c60e500214a27b6f) - **hive**: DATE_SUB to DATE_ADD use parens if needed *(PR [#5796](https://github.com/tobymao/sqlglot/pull/5796) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5794](https://github.com/tobymao/sqlglot/issues/5794) opened by [@mingelchan](https://github.com/mingelchan)*\n- [`b0516b4`](https://github.com/tobymao/sqlglot/commit/b0516b4bc9cf2bba2cb57e6bb79ff09b5e2244e3) - **optimizer**: Do not qualify columns if a projection coflicts with a source *(PR [#5780](https://github.com/tobymao/sqlglot/pull/5780) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5262](https://github.com/TobikoData/sqlmesh/issues/5262) opened by [@mChlopek](https://github.com/mChlopek)*\n- [`8af0d40`](https://github.com/tobymao/sqlglot/commit/8af0d40055450f71b7e36e576f4a9a1104bc02b2) - **parser**: address edge case where `values` is used as an identifier *(PR [#5801](https://github.com/tobymao/sqlglot/pull/5801) by [@georgesittas](https://github.com/georgesittas))*\n- [`3726b33`](https://github.com/tobymao/sqlglot/commit/3726b33bb6b4ab286617f510e96e1fbd27c429f3) - **snowflake**: support nulls_first arg for array_sort *(PR [#5802](https://github.com/tobymao/sqlglot/pull/5802) by [@treysp](https://github.com/treysp))*\n- [`3408de0`](https://github.com/tobymao/sqlglot/commit/3408de09e50d2510c1a6f511dc2dec357059044f) - parsing quoted built-in data types *(PR [#5810](https://github.com/tobymao/sqlglot/pull/5810) by [@treysp](https://github.com/treysp))*\n- [`ad0b407`](https://github.com/tobymao/sqlglot/commit/ad0b407098e1611d4fc0e1f0916511337b9aefdb) - **postgres**: Mark 'BEGIN' as TokenType.BEGIN for transactions *(PR [#5826](https://github.com/tobymao/sqlglot/pull/5826) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5815](https://github.com/tobymao/sqlglot/issues/5815) opened by [@karakanb](https://github.com/karakanb)*\n- [`e1a1b5b`](https://github.com/tobymao/sqlglot/commit/e1a1b5befefb0ca30ac1310cecb82a44f6089034) - **snowflake**: transpile BigQuery's `&` to `BITAND` *(PR [#5827](https://github.com/tobymao/sqlglot/pull/5827) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`32d0278`](https://github.com/tobymao/sqlglot/commit/32d027827eaa7aa0cd9faf2ac1f84739f914050f) - parse and generation of BITWISE AGG funcs across dialects *(PR [#5831](https://github.com/tobymao/sqlglot/pull/5831) by [@geooo109](https://github.com/geooo109))*\n- [`5f39a83`](https://github.com/tobymao/sqlglot/commit/5f39a83f1ff957aca57eb4745f83c296436acaac) - **bigquery**: properly generate `LIMIT` for `STRING_AGG` *(PR [#5830](https://github.com/tobymao/sqlglot/pull/5830) by [@georgesittas](https://github.com/georgesittas))*\n- [`f3d55c0`](https://github.com/tobymao/sqlglot/commit/f3d55c05c8411c9871f8ca4d23f726f976c9236b) - remove always token *(PR [#5832](https://github.com/tobymao/sqlglot/pull/5832) by [@tobymao](https://github.com/tobymao))*\n- [`1724775`](https://github.com/tobymao/sqlglot/commit/1724775429f66c2768864c8f96ace861eaa435fd) - suppert types() with no args *(PR [#5833](https://github.com/tobymao/sqlglot/pull/5833) by [@tobymao](https://github.com/tobymao))*\n- [`31c82c6`](https://github.com/tobymao/sqlglot/commit/31c82c6d6cd402e59cb59a94daafd22410eae0f6) - support `case.*` *(PR [#5835](https://github.com/tobymao/sqlglot/pull/5835) by [@georgesittas](https://github.com/georgesittas))*\n- [`c00f73b`](https://github.com/tobymao/sqlglot/commit/c00f73bac2530a62c25093c60bf02d0a4231bb0b) - window spec no and only exclude *(PR [#5834](https://github.com/tobymao/sqlglot/pull/5834) by [@tobymao](https://github.com/tobymao))*\n- [`5e7979f`](https://github.com/tobymao/sqlglot/commit/5e7979f3cf5f7996e198ddd81069d49a4a3b9391) - select session *(PR [#5836](https://github.com/tobymao/sqlglot/pull/5836) by [@tobymao](https://github.com/tobymao))*\n- [`9c8a600`](https://github.com/tobymao/sqlglot/commit/9c8a6001f41816035f391d046eb9692d6f13cefc) - **snowflake**: correct parsing of TO_VARCHAR *(PR [#5840](https://github.com/tobymao/sqlglot/pull/5840) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5837](https://github.com/tobymao/sqlglot/issues/5837) opened by [@ultrabear](https://github.com/ultrabear)*\n- [`f3d07fd`](https://github.com/tobymao/sqlglot/commit/f3d07fd8a106b034f64bb100291671c0fe39a106) - **snowflake**: Enable parsing of COPY INTO without files list *(PR [#5841](https://github.com/tobymao/sqlglot/pull/5841) by [@whummer](https://github.com/whummer))*\n- [`0ffb1fa`](https://github.com/tobymao/sqlglot/commit/0ffb1faac3b32aad845306eed0e000ff0d055554) - **duckdb**: transpile joins without ON/USING to CROSS JOIN *(PR [#5804](https://github.com/tobymao/sqlglot/pull/5804) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5795](https://github.com/tobymao/sqlglot/issues/5795) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`1e9aef1`](https://github.com/tobymao/sqlglot/commit/1e9aef1bb20f4dc5e9c03d59cb3165c235c11ce1) - **optimizer**: convert NULL annotations to UNKNOWN *(PR [#5842](https://github.com/tobymao/sqlglot/pull/5842) by [@georgesittas](https://github.com/georgesittas))*\n- [`bbcf0d4`](https://github.com/tobymao/sqlglot/commit/bbcf0d4404ea014f08319c44313719b4377adcdb) - **duckdb**: support trailing commas before `FOR` in pivot, fixes [#5843](https://github.com/tobymao/sqlglot/pull/5843) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ad8a408`](https://github.com/tobymao/sqlglot/commit/ad8a408a4e3e26e32472fc55c67b44687992ae47) - **parser**: more robust nested pipe syntax *(PR [#5845](https://github.com/tobymao/sqlglot/pull/5845) by [@geooo109](https://github.com/geooo109))*\n- [`44c9e70`](https://github.com/tobymao/sqlglot/commit/44c9e70bd8c9421035eb0e87e4286061ec5d2fa8) - **optimizer**: add tests for snowflake STARTSWITH function *(PR [#5847](https://github.com/tobymao/sqlglot/pull/5847) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`0779c2d`](https://github.com/tobymao/sqlglot/commit/0779c2d4e8ce0228592de6882763940783fa5e87) - support BIT_X aggregates again for duckdb, postgres *(PR [#5851](https://github.com/tobymao/sqlglot/pull/5851) by [@georgesittas](https://github.com/georgesittas))*\n- [`d131aab`](https://github.com/tobymao/sqlglot/commit/d131aab6815bf77d444a763d9bb4028d8f0e742d) - **redshift**: convert FETCH clauses to LIMIT for Redshift dialect *(PR [#5848](https://github.com/tobymao/sqlglot/pull/5848) by [@tomasmontielp](https://github.com/tomasmontielp))*\n- [`b22c4ec`](https://github.com/tobymao/sqlglot/commit/b22c4ecf4c032d89ca737f01d614102aa9c2b1ed) - **fabric**: UUID to UNIQUEIDENTIFIER *(PR [#5863](https://github.com/tobymao/sqlglot/pull/5863) by [@fresioAS](https://github.com/fresioAS))*\n- [`03d4f49`](https://github.com/tobymao/sqlglot/commit/03d4f49d92cd034d37074359b8c2cf96c5c3f5cf) - **clickhouse**: arrays are 1-indexed *(PR [#5862](https://github.com/tobymao/sqlglot/pull/5862) by [@joeyutong](https://github.com/joeyutong))*\n- [`1d9e357`](https://github.com/tobymao/sqlglot/commit/1d9e357fb7549635ca25c6c42299880d7864e074) - **optimizer**: expand columns on the LHS of recursive CTEs *(PR [#5872](https://github.com/tobymao/sqlglot/pull/5872) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5814](https://github.com/tobymao/sqlglot/issues/5814) opened by [@suresh-summation](https://github.com/suresh-summation)*\n- [`7fcc52a`](https://github.com/tobymao/sqlglot/commit/7fcc52a22241c480c22b3e6f843e7a210c75a0ec) - **parser**: Require an explicit alias in EXCLUDE/RENAME/REPLACE star ops *(PR [#5892](https://github.com/tobymao/sqlglot/pull/5892) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5fdcc65`](https://github.com/tobymao/sqlglot/commit/5fdcc651277ba4e86e11d0c5952a56e40299a998) - **snowflake**: parse OCTET_LENGTH *(PR [#5900](https://github.com/tobymao/sqlglot/pull/5900) by [@geooo109](https://github.com/geooo109))*\n- [`f5409df`](https://github.com/tobymao/sqlglot/commit/f5409df64ed6069880669878db687e4b98c3e280) - **optimizer**: use column name in struct type annotation *(PR [#5903](https://github.com/tobymao/sqlglot/pull/5903) by [@georgesittas](https://github.com/georgesittas))*\n- [`74886d8`](https://github.com/tobymao/sqlglot/commit/74886d82f70c9317af51c77b322e67a6aa260a5e) - **snowflake**: transpile BQ UNNEST with alias *(PR [#5897](https://github.com/tobymao/sqlglot/pull/5897) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5895](https://github.com/tobymao/sqlglot/issues/5895) opened by [@YuvalOmerRep](https://github.com/YuvalOmerRep)*\n- [`bd3e965`](https://github.com/tobymao/sqlglot/commit/bd3e9655aa72ffef8a9e0221205fa2c3915ef58b) - allow `lock` to be used as an identifier *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`2d0d908`](https://github.com/tobymao/sqlglot/commit/2d0d908b5bbc32ff3bc92eb1ae9fc6e5ac3409bc) - produce TableAlias instead of Alias for USING in merge builder *(PR [#5911](https://github.com/tobymao/sqlglot/pull/5911) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5910](https://github.com/tobymao/sqlglot/issues/5910) opened by [@deepyaman](https://github.com/deepyaman)*\n- [`0e256b3`](https://github.com/tobymao/sqlglot/commit/0e256b3f864bc2d026817bd08e89ee89f44ad256) - edge case with parsing `interval` as identifier *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d127051`](https://github.com/tobymao/sqlglot/commit/d1270517c3e124ca59caf29e4506eb3848f7452e) - precedence issue with column operator parsing *(PR [#5914](https://github.com/tobymao/sqlglot/pull/5914) by [@georgesittas](https://github.com/georgesittas))*\n- [`6807a32`](https://github.com/tobymao/sqlglot/commit/6807a32cccf984dc13a30b815750b2c41374b845) - escape byte string delimiters *(PR [#5916](https://github.com/tobymao/sqlglot/pull/5916) by [@georgesittas](https://github.com/georgesittas))*\n- [`22c7ed7`](https://github.com/tobymao/sqlglot/commit/22c7ed7734b41ca544bb67bcc1ca4151f6d5f05f) - **clickhouse**: parse tuple *(PR [#5920](https://github.com/tobymao/sqlglot/pull/5920) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5913](https://github.com/tobymao/sqlglot/issues/5913) opened by [@tiagoskaneta](https://github.com/tiagoskaneta)*\n- [`223160b`](https://github.com/tobymao/sqlglot/commit/223160bd7914d51e9ec1abb8d0f1053e13a65c98) - **parser**: NULLABLE as an identifier *(PR [#5921](https://github.com/tobymao/sqlglot/pull/5921) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5919](https://github.com/tobymao/sqlglot/issues/5919) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`42cfc79`](https://github.com/tobymao/sqlglot/commit/42cfc79ce120dee83084e2bb6b8bbd19f45bf06f) - **snowflake**: parse DAYOFWEEKISO *(PR [#5925](https://github.com/tobymao/sqlglot/pull/5925) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5924](https://github.com/tobymao/sqlglot/issues/5924) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`0be2cb4`](https://github.com/tobymao/sqlglot/commit/0be2cb448ee1a5ac020ac47e9944875c30e42632) - **postgres**: support `DISTINCT` qualifier in `JSON_AGG` fixes [#5935](https://github.com/tobymao/sqlglot/pull/5935) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`e34b2e1`](https://github.com/tobymao/sqlglot/commit/e34b2e14d1f87d095955765173a5e17fc9985220) - allow grouping set parser to consume more syntax fixes [#5937](https://github.com/tobymao/sqlglot/pull/5937) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`3846d4d`](https://github.com/tobymao/sqlglot/commit/3846d4dcdf8cbf8e90b2661083a567ab0547ad3c) - **solr**: properly support OR alternative operator *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`df428d5`](https://github.com/tobymao/sqlglot/commit/df428d516113a47ae50d04cd50a250830589c072) - **parser**: interval identifier followed by END *(PR [#5944](https://github.com/tobymao/sqlglot/pull/5944) by [@geooo109](https://github.com/geooo109))*\n- [`e178d16`](https://github.com/tobymao/sqlglot/commit/e178d1674a71e6f35a6acfa8f4a317f0fe2e4516) - **duckdb**: UNNEST as table *(PR [#5953](https://github.com/tobymao/sqlglot/pull/5953) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5952](https://github.com/tobymao/sqlglot/issues/5952) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`24feb8e`](https://github.com/tobymao/sqlglot/commit/24feb8ee0bc43f3f14fd768c9a0d986355becea2) - **parser**: parse `UPDATE` clauses in any order *(PR [#5958](https://github.com/tobymao/sqlglot/pull/5958) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5956](https://github.com/tobymao/sqlglot/issues/5956) opened by [@sfc-gh-clathrope](https://github.com/sfc-gh-clathrope)*\n- [`980f99a`](https://github.com/tobymao/sqlglot/commit/980f99a4cc0613012a189ee5636af37ec736040c) - **snowflake**: properly generate inferred `STRUCT` data types *(PR [#5954](https://github.com/tobymao/sqlglot/pull/5954) by [@georgesittas](https://github.com/georgesittas))*\n- [`5432976`](https://github.com/tobymao/sqlglot/commit/543297680755344185e0f306843bc4909f4f75ed) - **bigquery**: allow GRANT as an id var *(PR [#5965](https://github.com/tobymao/sqlglot/pull/5965) by [@treysp](https://github.com/treysp))*\n- [`7a3744f`](https://github.com/tobymao/sqlglot/commit/7a3744f203b93211e5dd97e6730b6bf59d6d96e0) - **sqlite**: support `RANGE CURRENT ROW` in window spec *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c3bdb3c`](https://github.com/tobymao/sqlglot/commit/c3bdb3cd1af1809ed82be0ae40744d9fffc8ce18) - **starrocks**: array start index is 1, support array_flatten, fixes [#5983](https://github.com/tobymao/sqlglot/pull/5983) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`51b1bb1`](https://github.com/tobymao/sqlglot/commit/51b1bb178fa952edc13b2cbc6f624d30b0bde798) - move `WATERMARK` logic to risingwave fixes [#5989](https://github.com/tobymao/sqlglot/pull/5989) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`033ddf0`](https://github.com/tobymao/sqlglot/commit/033ddf04da895f1f5d38aff5361b2ae0793fefea) - **optimizer**: convert INNER JOINs to LEFT JOINs when merging LEFT JOIN subqueries *(PR [#5980](https://github.com/tobymao/sqlglot/pull/5980) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5969](https://github.com/tobymao/sqlglot/issues/5969) opened by [@karta0807913](https://github.com/karta0807913)*\n- [`c7657fb`](https://github.com/tobymao/sqlglot/commit/c7657fbd27a4350c424ef65947471ab9ec086831) - remove `unalias_group_by` transformation since it is unsafe *(PR [#5997](https://github.com/tobymao/sqlglot/pull/5997) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5995](https://github.com/tobymao/sqlglot/issues/5995) opened by [@capricornsky0119](https://github.com/capricornsky0119)*\n- [`b6f9694`](https://github.com/tobymao/sqlglot/commit/b6f9694c535cdd1403a63036cc246fda4e6d4d22) - **optimizer**: avoid merging subquery with JOIN when outer query uses JOIN *(PR [#5999](https://github.com/tobymao/sqlglot/pull/5999) by [@geooo109](https://github.com/geooo109))*\n- [`23fd7b9`](https://github.com/tobymao/sqlglot/commit/23fd7b9116541b96e5d89389e862c6004e92d109) - respect multi-part Column units instead of converting to Var *(PR [#6005](https://github.com/tobymao/sqlglot/pull/6005) by [@georgesittas](https://github.com/georgesittas))*\n- [`be1cdc8`](https://github.com/tobymao/sqlglot/commit/be1cdc81b511d462b710b50941d5c2770d901e91) - **duckdb**: Fix roundtrip of ~ operator *(PR [#6017](https://github.com/tobymao/sqlglot/pull/6017) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6016](https://github.com/tobymao/sqlglot/issues/6016) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`27c278f`](https://github.com/tobymao/sqlglot/commit/27c278f562f5ce98a1a4d31f8e66f148a1f42236) - **parser**: Allow LIMIT with % percentage *(PR [#6019](https://github.com/tobymao/sqlglot/pull/6019) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`39bf3f8`](https://github.com/tobymao/sqlglot/commit/39bf3f893389663796cdd799ef0f1e684f315a01) - **parser**: Allow CUBE & ROLLUP inside GROUPING SETS *(PR [#6018](https://github.com/tobymao/sqlglot/pull/6018) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6015](https://github.com/tobymao/sqlglot/issues/6015) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`ba7ad34`](https://github.com/tobymao/sqlglot/commit/ba7ad341d5ee1298b8fe54be11ca6252c1a44c99) - **duckdb**: Parse ROW type as STRUCT *(PR [#6020](https://github.com/tobymao/sqlglot/pull/6020) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6012](https://github.com/tobymao/sqlglot/issues/6012) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`718d6bb`](https://github.com/tobymao/sqlglot/commit/718d6bbf7f40e5b3e99563e2f1ac9eadeff57c3d) - handle unicode heredoc tags & Rust grapheme clusters properly *(PR [#6024](https://github.com/tobymao/sqlglot/pull/6024) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6010](https://github.com/tobymao/sqlglot/issues/6010) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`c8cfb9d`](https://github.com/tobymao/sqlglot/commit/c8cfb9db2e789be2dc7f8a154082a9210b736502) - **snowflake**: transpile ARRAY_CONTAINS with VARIANT CAST *(PR [#6029](https://github.com/tobymao/sqlglot/pull/6029) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6026](https://github.com/tobymao/sqlglot/issues/6026) opened by [@Birkman](https://github.com/Birkman)*\n- [`6a6ca92`](https://github.com/tobymao/sqlglot/commit/6a6ca927c4e6e06f5cb38ad1153a8b556999ef90) - **parser**: Allow nested GROUPING SETS *(PR [#6041](https://github.com/tobymao/sqlglot/pull/6041) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6038](https://github.com/tobymao/sqlglot/issues/6038) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`41baeaa`](https://github.com/tobymao/sqlglot/commit/41baeaa1530c5419c945409133e3b7caa5250ec7) - **optimizer**: more robust CROSS JOIN substitution and JOIN reordering *(PR [#6021](https://github.com/tobymao/sqlglot/pull/6021) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6009](https://github.com/tobymao/sqlglot/issues/6009) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`e2f299f`](https://github.com/tobymao/sqlglot/commit/e2f299f5ad18d75a394e55bd1ee59ed243d77e54) - allow subqueries to have modifiers closes [#6014](https://github.com/tobymao/sqlglot/pull/6014) *(PR [#6034](https://github.com/tobymao/sqlglot/pull/6034) by [@tobymao](https://github.com/tobymao))*\n- [`0d65266`](https://github.com/tobymao/sqlglot/commit/0d6526693f8e7dda9b7c180d31c364bde91afc72) - parse lambda for arg_min/max arguments closes [#6036](https://github.com/tobymao/sqlglot/pull/6036) *(PR [#6042](https://github.com/tobymao/sqlglot/pull/6042) by [@georgesittas](https://github.com/georgesittas))*\n- [`0939d69`](https://github.com/tobymao/sqlglot/commit/0939d69223a860581b1c30cc2f762294946b93f3) - move odbc date literal handling in t-sql closes [#6037](https://github.com/tobymao/sqlglot/pull/6037) *(PR [#6044](https://github.com/tobymao/sqlglot/pull/6044) by [@georgesittas](https://github.com/georgesittas))*\n- [`65848e5`](https://github.com/tobymao/sqlglot/commit/65848e5a3e4c1cb26e6ca4deb7819a282838c3c2) - **tsql**: UPDATE with OPTIONS *(PR [#6043](https://github.com/tobymao/sqlglot/pull/6043) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6033](https://github.com/tobymao/sqlglot/issues/6033) opened by [@ligfx](https://github.com/ligfx)*\n- [`3bb6bb3`](https://github.com/tobymao/sqlglot/commit/3bb6bb3e5193ed53c803c3786a1791f15cd2f89a) - **parser**: support :: cast operator after IS NULL/IS NOT NULL *(PR [#6056](https://github.com/tobymao/sqlglot/pull/6056) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6055](https://github.com/tobymao/sqlglot/issues/6055) opened by [@vchan](https://github.com/vchan)*\n- [`2c7cc29`](https://github.com/tobymao/sqlglot/commit/2c7cc29a329dcbaaa90a6f857d2383d2967ea6cc) - **duckdb**: Transform exp.HexString to BLOB in hex notation *(PR [#6045](https://github.com/tobymao/sqlglot/pull/6045) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6035](https://github.com/tobymao/sqlglot/issues/6035) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`e7833de`](https://github.com/tobymao/sqlglot/commit/e7833de9744a4aa69d244285e7f6f7281af178ba) - **parser**: support DELETE with USING and multiple VALUES *(PR [#6072](https://github.com/tobymao/sqlglot/pull/6072) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6070](https://github.com/tobymao/sqlglot/issues/6070) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`2238ac2`](https://github.com/tobymao/sqlglot/commit/2238ac27478bd272ba39928bbec1075c4191ee1b) - **duckdb**: transpile timestamp literals in datediff fixes [#6083](https://github.com/tobymao/sqlglot/pull/6083) *(PR [#6086](https://github.com/tobymao/sqlglot/pull/6086) by [@georgesittas](https://github.com/georgesittas))*\n- [`bef541c`](https://github.com/tobymao/sqlglot/commit/bef541cec36f8c4295f815c3f5cd22491738901b) - **parser**: query mods and set ops in FROM-first syntax *(PR [#6092](https://github.com/tobymao/sqlglot/pull/6092) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6088](https://github.com/tobymao/sqlglot/issues/6088) opened by [@denis-komarov](https://github.com/denis-komarov)*\n  - :arrow_lower_right: *fixes issue [#6091](https://github.com/tobymao/sqlglot/issues/6091) opened by [@denis-komarov](https://github.com/denis-komarov)*\n  - :arrow_lower_right: *fixes issue [#6093](https://github.com/tobymao/sqlglot/issues/6093) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`5109890`](https://github.com/tobymao/sqlglot/commit/510989043d18baa17502a971262462814a2eb5be) - **parser**: VALUES with ORDER BY/LIMIT/OFFSET *(PR [#6094](https://github.com/tobymao/sqlglot/pull/6094) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6087](https://github.com/tobymao/sqlglot/issues/6087) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`4b062c8`](https://github.com/tobymao/sqlglot/commit/4b062c850bd9867be0d622f3f526762fa2b72302) - consume more syntax for cubes/rollups fixes [#6101](https://github.com/tobymao/sqlglot/pull/6101) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`f00866a`](https://github.com/tobymao/sqlglot/commit/f00866aeb8b7f51e27173c688225fe16d777eb1a) - **duckdb**: 1 arg FORMAT func *(PR [#6109](https://github.com/tobymao/sqlglot/pull/6109) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6108](https://github.com/tobymao/sqlglot/issues/6108) opened by [@erindru](https://github.com/erindru)*\n- [`77dfd5a`](https://github.com/tobymao/sqlglot/commit/77dfd5a41bb9ce5450e0f6b7a78c953c8ade14d5) - lineage does not modify sql input if expression *(PR [#6113](https://github.com/tobymao/sqlglot/pull/6113) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#6112](https://github.com/tobymao/sqlglot/issues/6112) opened by [@snovik75](https://github.com/snovik75)*\n- [`06f40f9`](https://github.com/tobymao/sqlglot/commit/06f40f900ce693ba4203514e422cba8cda0dbb07) - **optimizer**: don't simplify x XOR x due to NULL semantics *(PR [#6115](https://github.com/tobymao/sqlglot/pull/6115) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6104](https://github.com/tobymao/sqlglot/issues/6104) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`03e2dff`](https://github.com/tobymao/sqlglot/commit/03e2dff9b074dc228cf3854ff1f4357e091aa9b3) - allow parsing `analyze` as an identifier fixes [#6123](https://github.com/tobymao/sqlglot/pull/6123) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`8744431`](https://github.com/tobymao/sqlglot/commit/874443148c8ec2a773dfaca5da10d3587a49de3e) - transpile bigquery DATETIME_DIFF to duckdb *(PR [#6126](https://github.com/tobymao/sqlglot/pull/6126) by [@toriwei](https://github.com/toriwei))*\n  - :arrow_lower_right: *fixes issue [#6107](https://github.com/tobymao/sqlglot/issues/6107) opened by [@izeigerman](https://github.com/izeigerman)*\n- [`b94e81b`](https://github.com/tobymao/sqlglot/commit/b94e81b42b89c75625b2da779c0f53777d9b6b48) - **optimizer**: avoid removing string literals from WHERE clause *(PR [#6131](https://github.com/tobymao/sqlglot/pull/6131) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6128](https://github.com/tobymao/sqlglot/issues/6128) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`e2129c6`](https://github.com/tobymao/sqlglot/commit/e2129c6766ca1f10ff6663bec98be984abb33c91) - **optimizer**: Do not consider BIT_COUNT an aggregate function *(PR [#6135](https://github.com/tobymao/sqlglot/pull/6135) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6130](https://github.com/tobymao/sqlglot/issues/6130) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`03bfeed`](https://github.com/tobymao/sqlglot/commit/03bfeed56c5c2f143ce2e1be38d519f902d19961) - **starrocks**: disable IS TRUE/FALSE syntax support *(PR [#6145](https://github.com/tobymao/sqlglot/pull/6145) by [@petrikoro](https://github.com/petrikoro))*\n  - :arrow_lower_right: *fixes issue [#6144](https://github.com/tobymao/sqlglot/issues/6144) opened by [@petrikoro](https://github.com/petrikoro)*\n- [`d136414`](https://github.com/tobymao/sqlglot/commit/d136414e520270ac9ab2fd8e9df4691d269b3af0) - **optimizer**: avoid simplifying AND with NULL *(PR [#6148](https://github.com/tobymao/sqlglot/pull/6148) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6136](https://github.com/tobymao/sqlglot/issues/6136) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`1fd9991`](https://github.com/tobymao/sqlglot/commit/1fd99911a60f0543fbc79221a8c6a6f232ed0a2a) - **clickhouse**: NOT + IN precedence *(PR [#6149](https://github.com/tobymao/sqlglot/pull/6149) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6143](https://github.com/tobymao/sqlglot/issues/6143) opened by [@mlipiev](https://github.com/mlipiev)*\n- [`3acf796`](https://github.com/tobymao/sqlglot/commit/3acf7965105a098fea6336df0c304d94acbd05ec) - **duckdb**: Allow ESCAPE NULL *(PR [#6164](https://github.com/tobymao/sqlglot/pull/6164) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6160](https://github.com/tobymao/sqlglot/issues/6160) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`f7f1fca`](https://github.com/tobymao/sqlglot/commit/f7f1fca39a75df16ebb93f038e6277a25b8be6b9) - **duckdb**: Support positional index in list comprehension *(PR [#6163](https://github.com/tobymao/sqlglot/pull/6163) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6156](https://github.com/tobymao/sqlglot/issues/6156) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`d382a31`](https://github.com/tobymao/sqlglot/commit/d382a3106d5ce2e9b75527aacd4a37d1f8e16d18) - **optimizer**: simplify double negation only if the inner expr is BOOLEAN *(PR [#6151](https://github.com/tobymao/sqlglot/pull/6151) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6129](https://github.com/tobymao/sqlglot/issues/6129) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`dfe6b3c`](https://github.com/tobymao/sqlglot/commit/dfe6b3c8e6db40e22e626e2d56e9a7008dd75c32) - **optimizer**: Disambiguate JOIN ON columns during qualify *(PR [#6155](https://github.com/tobymao/sqlglot/pull/6155) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6132](https://github.com/tobymao/sqlglot/issues/6132) opened by [@Fosly](https://github.com/Fosly)*\n- [`f267ece`](https://github.com/tobymao/sqlglot/commit/f267ecea92b0751f6b35a4ad0c70fe6754e49038) - normalize before qualifying tables *(PR [#6176](https://github.com/tobymao/sqlglot/pull/6176) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6167](https://github.com/tobymao/sqlglot/issues/6167) opened by [@schelip](https://github.com/schelip)*\n- [`ef87520`](https://github.com/tobymao/sqlglot/commit/ef875204596b8529f3358025c7a61d757a999bdc) - **postgres, duckdb**: Transpile `REGEXP_REPLACE` with 'g' option *(PR [#6174](https://github.com/tobymao/sqlglot/pull/6174) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6170](https://github.com/tobymao/sqlglot/issues/6170) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`51a8d70`](https://github.com/tobymao/sqlglot/commit/51a8d700a9602278d1e98425af0fa87d02c739fe) - **parser**: allow LIMIT % OFFSET *(PR [#6184](https://github.com/tobymao/sqlglot/pull/6184) by [@toriwei](https://github.com/toriwei))*\n  - :arrow_lower_right: *fixes issue [#6166](https://github.com/tobymao/sqlglot/issues/6166) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`8bf0a9f`](https://github.com/tobymao/sqlglot/commit/8bf0a9fe8e167984dc2e7b43d52d3850e063da3f) - **duckdb**: Cast literal arg to timestamp for epoch_us function *(PR [#6190](https://github.com/tobymao/sqlglot/pull/6190) by [@vchan](https://github.com/vchan))*\n- [`93071e2`](https://github.com/tobymao/sqlglot/commit/93071e255406f62ea83dd89a3be4871b7edfb3fe) - **optimizer**: Fix simplify_parens from removing negated *(PR [#6194](https://github.com/tobymao/sqlglot/pull/6194) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6179](https://github.com/tobymao/sqlglot/issues/6179) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`2ac3a03`](https://github.com/tobymao/sqlglot/commit/2ac3a03409d9239d0cf7fb265843d7837a0a3fcd) - **lineage**: correct star detection and add join star tests *(PR [#6185](https://github.com/tobymao/sqlglot/pull/6185) by [@lancewl](https://github.com/lancewl))*\n- [`c9ae2eb`](https://github.com/tobymao/sqlglot/commit/c9ae2ebdb86abdb767f2fcb00da0b6277b4aea45) - **duckdb**: transpile BigQuery TIMESTAMP_ADD to duckdb *(PR [#6188](https://github.com/tobymao/sqlglot/pull/6188) by [@toriwei](https://github.com/toriwei))*\n- [`ba0e17a`](https://github.com/tobymao/sqlglot/commit/ba0e17a25af417e24162bfab49c3074454a5c1a8) - **snowflake**: Transpile `ARRAY_CONCAT_AGG` to `ARRAY_FLATTEN(ARRAY_AGG(...))` *(PR [#6192](https://github.com/tobymao/sqlglot/pull/6192) by [@ozadari](https://github.com/ozadari))*\n- [`730e4cc`](https://github.com/tobymao/sqlglot/commit/730e4cc5b77bff9135667193cc0a65c24cdfb6b5) - **trino**: Allow 2nd arg for FIRST/LAST functions *(PR [#6205](https://github.com/tobymao/sqlglot/pull/6205) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6204](https://github.com/tobymao/sqlglot/issues/6204) opened by [@Harmuth94](https://github.com/Harmuth94)*\n- [`e7ddad1`](https://github.com/tobymao/sqlglot/commit/e7ddad10b5edf9b801d2151e3e5fca448754df0d) - **optimizer**: ensure `NULL` coerces into any type *(PR [#6211](https://github.com/tobymao/sqlglot/pull/6211) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4c4189b`](https://github.com/tobymao/sqlglot/commit/4c4189b4083d272a6e678d83b5c567a2e9c0d672) - Transpile CONCAT function to double pipe operators when source … *(PR [#6241](https://github.com/tobymao/sqlglot/pull/6241) by [@vchan](https://github.com/vchan))*\n- [`fc78d20`](https://github.com/tobymao/sqlglot/commit/fc78d2016d8f7d20c094df791f746de323cd3639) - **parser**: Unwrap subqueries without modifiers *(PR [#6247](https://github.com/tobymao/sqlglot/pull/6247) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6237](https://github.com/tobymao/sqlglot/issues/6237) opened by [@preet-sheth](https://github.com/preet-sheth)*\n- [`7ad4c17`](https://github.com/tobymao/sqlglot/commit/7ad4c177fbf8dda78aa8de1ca112f606b2fd5456) - **databricks**: Support table names in FROM STREAM *(PR [#6259](https://github.com/tobymao/sqlglot/pull/6259) by [@roveo](https://github.com/roveo))*\n- [`00abc39`](https://github.com/tobymao/sqlglot/commit/00abc393c9042e839457c5a6582e95cdb74356f3) - **generator**: handle casting for bytestrings  *(PR [#6252](https://github.com/tobymao/sqlglot/pull/6252) by [@toriwei](https://github.com/toriwei))*\n- [`bcf2eac`](https://github.com/tobymao/sqlglot/commit/bcf2eace0baf1d85047841f36cb5c0082c61b29c) - **duckdb**: map int8 to bigint instead of tinyint fixes [#6269](https://github.com/tobymao/sqlglot/pull/6269) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ddea61d`](https://github.com/tobymao/sqlglot/commit/ddea61d83f6699c97cc7b25aabe01a138138bdb1) - **optimizer**: simplify connector complements only for non-null operands *(PR [#6214](https://github.com/tobymao/sqlglot/pull/6214) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6213](https://github.com/tobymao/sqlglot/issues/6213) opened by [@geooo109](https://github.com/geooo109)*\n- [`e17320e`](https://github.com/tobymao/sqlglot/commit/e17320ee3bdd0ef541d616c447b4973d12780dae) - Handle edge cases in  for DuckDB RANGE to Spark SEQUENCE transpilation *(PR [#6276](https://github.com/tobymao/sqlglot/pull/6276) by [@joeyutong](https://github.com/joeyutong))*\n- [`33b6218`](https://github.com/tobymao/sqlglot/commit/33b62183a15cdedf0b1ebd96fcb856afbe8879a0) - sqlsecurityproperty parseerror *(PR [#6280](https://github.com/tobymao/sqlglot/pull/6280) by [@ds-cbo](https://github.com/ds-cbo))*\n  - :arrow_lower_right: *fixes issue [#6279](https://github.com/tobymao/sqlglot/issues/6279) opened by [@ds-cbo](https://github.com/ds-cbo)*\n- [`c02b64c`](https://github.com/tobymao/sqlglot/commit/c02b64c3524dd074c2108baaca668ab2607ac843) - **optimizer**: Handle pseudocolumns differently than columns *(PR [#6273](https://github.com/tobymao/sqlglot/pull/6273) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6256](https://github.com/tobymao/sqlglot/issues/6256) opened by [@azilya](https://github.com/azilya)*\n- [`05c5181`](https://github.com/tobymao/sqlglot/commit/05c5181b36a7ada32b96fc91bdfbf73b38a1a408) - **optimizer**: refactor `Connector` simplification to factor in types *(PR [#6152](https://github.com/tobymao/sqlglot/pull/6152) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6137](https://github.com/tobymao/sqlglot/issues/6137) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`cb0bcff`](https://github.com/tobymao/sqlglot/commit/cb0bcff310e9acdf806fc98e99cb9938b747c771) - **duckdb**: cast UUID() output to varchar when source dialect UUID() returns string *(PR [#6284](https://github.com/tobymao/sqlglot/pull/6284) by [@toriwei](https://github.com/toriwei))*\n- [`f9287f7`](https://github.com/tobymao/sqlglot/commit/f9287f7d596a6d8a1e1cd2c48978a4dec77a96cb) - **optimizer**: robust deduplication of connectors *(PR [#6296](https://github.com/tobymao/sqlglot/pull/6296) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6182](https://github.com/tobymao/sqlglot/issues/6182) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`ea0ea79`](https://github.com/tobymao/sqlglot/commit/ea0ea79c1c611b62c79f82f744fe0c98803598a3) - **clickhouse**: Parse `LIKE` functions *(PR [#6314](https://github.com/tobymao/sqlglot/pull/6314) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6313](https://github.com/tobymao/sqlglot/issues/6313) opened by [@CainYang](https://github.com/CainYang)*\n- [`bbd4c90`](https://github.com/tobymao/sqlglot/commit/bbd4c901a9550beb363758e6be1e1877d4e56f2c) - **sqlite**: support IS with identifier as RHS *(PR [#6316](https://github.com/tobymao/sqlglot/pull/6316) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6315](https://github.com/tobymao/sqlglot/issues/6315) opened by [@VLDB2026](https://github.com/VLDB2026)*\n- [`65d213a`](https://github.com/tobymao/sqlglot/commit/65d213a7662962d4226368590508fbf61675c055) - **dialect**: fix typo from millenium to millennium [#6321](https://github.com/tobymao/sqlglot/pull/6321) *(commit by [@lBilali](https://github.com/lBilali))*\n- [`c9d1615`](https://github.com/tobymao/sqlglot/commit/c9d16150a408a41daf704d2d0b0ebfce57425b81) - **tsql**: map iso_week with the correct python directive from strftime *(PR [#6322](https://github.com/tobymao/sqlglot/pull/6322) by [@lBilali](https://github.com/lBilali))*\n- [`85ddcc5`](https://github.com/tobymao/sqlglot/commit/85ddcc5eca22ac726582de454f2f12b9d4877634) - **bigquery**: Do not normalize JSON fields in dot notation *(PR [#6320](https://github.com/tobymao/sqlglot/pull/6320) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`933e981`](https://github.com/tobymao/sqlglot/commit/933e98102fb39d24ae0350da13337d981287130a) - **optimizer**: more robust NULL reduction *(PR [#6327](https://github.com/tobymao/sqlglot/pull/6327) by [@geooo109](https://github.com/geooo109))*\n- [`e1c6d57`](https://github.com/tobymao/sqlglot/commit/e1c6d5716f80eb24b6d0a9c93e187a8c9f05e555) - **parser**: improve between .. preceding .. following parser fixes [#6332](https://github.com/tobymao/sqlglot/pull/6332) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`65706e8`](https://github.com/tobymao/sqlglot/commit/65706e8c7edeb7de674d427718eac181df206dc9) - avoid full traversal for pushdown_cte_alias_columns *(commit by [@tobymao](https://github.com/tobymao))*\n- [`c81258e`](https://github.com/tobymao/sqlglot/commit/c81258e9c26f637f6f8520051c159685c8b1cb7e) - **parser**: allow using OVER token as unquoted identifier *(PR [#6338](https://github.com/tobymao/sqlglot/pull/6338) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6337](https://github.com/tobymao/sqlglot/issues/6337) opened by [@VLDB2026](https://github.com/VLDB2026)*\n- [`73abfac`](https://github.com/tobymao/sqlglot/commit/73abfac4cec27350754c942be71175fa7bdfd1d0) - **redshift**: do not inherit postgres `ROUND` generator closes [#6340](https://github.com/tobymao/sqlglot/pull/6340) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`0f79f2a`](https://github.com/tobymao/sqlglot/commit/0f79f2a55c4ba14d4a5fcfd01a0a727271992b8c) - **snowflake**: MAX_BY and MIN_BY with count should return plain `ARRAY` *(PR [#6343](https://github.com/tobymao/sqlglot/pull/6343) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`e1b6558`](https://github.com/tobymao/sqlglot/commit/e1b6558cb1a860bbd695f25b66e52064b57c0a84) - **tsql**: handle all datepart alternatives *(PR [#6324](https://github.com/tobymao/sqlglot/pull/6324) by [@lBilali](https://github.com/lBilali))*\n- [`06daa47`](https://github.com/tobymao/sqlglot/commit/06daa47dedebac672548e1db230b89f5c9eae84e) - **optimizer**: update annotated type of ARRAY_AGG to untyped array *(PR [#6347](https://github.com/tobymao/sqlglot/pull/6347) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`826db4d`](https://github.com/tobymao/sqlglot/commit/826db4d3c413941e3b0b31e1f907fabd017bd461) - **redshift**: properly parse default IAM_ROLE and AVRO/JSON formats in COPY *(PR [#6346](https://github.com/tobymao/sqlglot/pull/6346) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6345](https://github.com/tobymao/sqlglot/issues/6345) opened by [@zachary-povey](https://github.com/zachary-povey)*\n- [`c367bac`](https://github.com/tobymao/sqlglot/commit/c367bac878a3c17773009b54b9836e7b9a5b84fe) - **duckdb**: Support update without set in DuckDB merge when matched *(PR [#6357](https://github.com/tobymao/sqlglot/pull/6357) by [@themisvaltinos](https://github.com/themisvaltinos))*\n- [`df13a65`](https://github.com/tobymao/sqlglot/commit/df13a655646bd2ef5d8b4613670bb5fe48845b73) - unnest deep stuff *(PR [#6366](https://github.com/tobymao/sqlglot/pull/6366) by [@tobymao](https://github.com/tobymao))*\n- [`20e33fd`](https://github.com/tobymao/sqlglot/commit/20e33fd0d1bc1899727d023411e604f1ea9347b8) - **duckdb**: regexp_extract_all closes [#6380](https://github.com/tobymao/sqlglot/pull/6380) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d4c2256`](https://github.com/tobymao/sqlglot/commit/d4c2256fb493ed2f16c29694ae5c31517123d419) - **parser**: at time zone precedence *(PR [#6383](https://github.com/tobymao/sqlglot/pull/6383) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6359](https://github.com/tobymao/sqlglot/issues/6359) opened by [@parth-wisdom](https://github.com/parth-wisdom)*\n- [`4fb4d08`](https://github.com/tobymao/sqlglot/commit/4fb4d08ef8896bda434d4f89c21c669c6146fd02) - **oracle**: properly support table alias in the `INSERT` DML *(PR [#6374](https://github.com/tobymao/sqlglot/pull/6374) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#6371](https://github.com/tobymao/sqlglot/issues/6371) opened by [@snovik75](https://github.com/snovik75)*\n- [`2169f5b`](https://github.com/tobymao/sqlglot/commit/2169f5b8f30b6c8be1635bb5648a1abf636e49a6) - **parser**: support SET with := *(PR [#6385](https://github.com/tobymao/sqlglot/pull/6385) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6384](https://github.com/tobymao/sqlglot/issues/6384) opened by [@AndyVW77](https://github.com/AndyVW77)*\n- [`50348ac`](https://github.com/tobymao/sqlglot/commit/50348ac31f784aa97bd09d5d6c6613fbd68402ee) - **mysql**: support order by clause for mysql delete statement *(PR [#6381](https://github.com/tobymao/sqlglot/pull/6381) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n  - :arrow_lower_right: *fixes issue [#6372](https://github.com/tobymao/sqlglot/issues/6372) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`21d3859`](https://github.com/tobymao/sqlglot/commit/21d38590fec6cb55a1a03aeb2621bd9fca677496) - **bigquery**: Disable STRING_AGG sep canonicalization *(PR [#6395](https://github.com/tobymao/sqlglot/pull/6395) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6392](https://github.com/tobymao/sqlglot/issues/6392) opened by [@erindru](https://github.com/erindru)*\n- [`67f499d`](https://github.com/tobymao/sqlglot/commit/67f499dd497efdf4f3fc49dd75e49a77e036ee63) - **duckdb**: Make exp.DateFromParts more lenient *(PR [#6397](https://github.com/tobymao/sqlglot/pull/6397) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6394](https://github.com/tobymao/sqlglot/issues/6394) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`39f8c37`](https://github.com/tobymao/sqlglot/commit/39f8c37aca755d97e1e41f232042d1c649e58908) - **parser**: support FROM-syntax with joins *(PR [#6402](https://github.com/tobymao/sqlglot/pull/6402) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6396](https://github.com/tobymao/sqlglot/issues/6396) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`9ddae4d`](https://github.com/tobymao/sqlglot/commit/9ddae4d56d1e3a15fc3b4b76ce3b3040683c220f) - **duckdb**: support IN with no paren *(PR [#6409](https://github.com/tobymao/sqlglot/pull/6409) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6407](https://github.com/tobymao/sqlglot/issues/6407) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`c7cb098`](https://github.com/tobymao/sqlglot/commit/c7cb0983a0fa463c43d2c4ee925816e9a1628c79) - **tokenizer**: Fix underscore separator with scientific notation *(PR [#6401](https://github.com/tobymao/sqlglot/pull/6401) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6393](https://github.com/tobymao/sqlglot/issues/6393) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`f5635d2`](https://github.com/tobymao/sqlglot/commit/f5635d2cc2a5612d6403bbf508b545f2a4e8f773) - **duckdb**: splice with col named after type closes [#6411](https://github.com/tobymao/sqlglot/pull/6411) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`097d865`](https://github.com/tobymao/sqlglot/commit/097d865554d9ba2e226962fa71778ae0a6c596cb) - **duckdb**: pivot using cast closes [#6410](https://github.com/tobymao/sqlglot/pull/6410) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d038ad7`](https://github.com/tobymao/sqlglot/commit/d038ad7f036a140f3eae4bdde15824437d4e44ee) - **mysql**: support named primary keys for mysql *(PR [#6389](https://github.com/tobymao/sqlglot/pull/6389) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n  - :arrow_lower_right: *fixes issue [#6382](https://github.com/tobymao/sqlglot/issues/6382) opened by [@AndyVW77](https://github.com/AndyVW77)*\n- [`4f3bb0d`](https://github.com/tobymao/sqlglot/commit/4f3bb0d6714bf89ff72e13e1398d8f01cefafb00) - **DuckDB**: Correct transpilation of BigQuery's JSON_EXTRACT_SCALAR… *(PR [#6414](https://github.com/tobymao/sqlglot/pull/6414) by [@vchan](https://github.com/vchan))*\n- [`e2f306f`](https://github.com/tobymao/sqlglot/commit/e2f306f1893a3f565cbbf7857ffd9795850aba7b) - interval column ops closes [#6416](https://github.com/tobymao/sqlglot/pull/6416) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`8c314a8`](https://github.com/tobymao/sqlglot/commit/8c314a8b457a5c3ed470ac8fcff022fec881c248) - **duckdb**: support cte pivot for duckdb *(PR [#6413](https://github.com/tobymao/sqlglot/pull/6413) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n  - :arrow_lower_right: *fixes issue [#6405](https://github.com/tobymao/sqlglot/issues/6405) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`92ee124`](https://github.com/tobymao/sqlglot/commit/92ee1241ea3088d4e63c094404252339c54ad0c1) - **optimizer**: postgres qualify GENERATE_SERIES and table projection *(PR [#6373](https://github.com/tobymao/sqlglot/pull/6373) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6358](https://github.com/tobymao/sqlglot/issues/6358) opened by [@metahexane](https://github.com/metahexane)*\n- [`7021d54`](https://github.com/tobymao/sqlglot/commit/7021d54ecf0ceab3c3606642cbfca8e080cc8613) - **tsql**: CEILING generation *(PR [#6477](https://github.com/tobymao/sqlglot/pull/6477) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6472](https://github.com/tobymao/sqlglot/issues/6472) opened by [@ricky-ho](https://github.com/ricky-ho)*\n- [`df4c1d3`](https://github.com/tobymao/sqlglot/commit/df4c1d37ff77151a74b5de3d119c7e03f5db85f4) - REGEXP_EXTRACT position arg overflow *(PR [#6458](https://github.com/tobymao/sqlglot/pull/6458) by [@treysp](https://github.com/treysp))*\n  - :arrow_lower_right: *fixes issue [#6442](https://github.com/tobymao/sqlglot/issues/6442) opened by [@erindru](https://github.com/erindru)*\n- [`5a49c3f`](https://github.com/tobymao/sqlglot/commit/5a49c3f7a7619ad9e711ff2cd9e85b8606969b36) - **optimizer**: support ORDER / LIMIT expressions for BigQuery ARRAY_AGG / STRING_AGG functions *(PR [#6463](https://github.com/tobymao/sqlglot/pull/6463) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`1b6076b`](https://github.com/tobymao/sqlglot/commit/1b6076bd5a64b044f52f5366244ba0746aca75e1) - wrap connectives generated due to transpiling LIKE ANY closes [#6493](https://github.com/tobymao/sqlglot/pull/6493) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`36ad534`](https://github.com/tobymao/sqlglot/commit/36ad534b14eabe9ee197017f5087e8e5190f8526) - **exasol**: qualified select list with \"LOCAL\" *(PR [#6450](https://github.com/tobymao/sqlglot/pull/6450) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`52aceaa`](https://github.com/tobymao/sqlglot/commit/52aceaaa887dddb35f8ede5c2d9577fdeee35c48) - **optimizer**: annotate `HavingMax` by `this` *(PR [#6499](https://github.com/tobymao/sqlglot/pull/6499) by [@georgesittas](https://github.com/georgesittas))*\n- [`ce5487e`](https://github.com/tobymao/sqlglot/commit/ce5487ef2ec0a3de8fa79b9febf41236c05c04cc) - sources doesn't store columns, clean up this old code *(commit by [@tobymao](https://github.com/tobymao))*\n- [`3224235`](https://github.com/tobymao/sqlglot/commit/3224235c1b7a80511af11f7dbffe608a747a3df0) - make CTE builder produce AST consistent with parser closes [#6503](https://github.com/tobymao/sqlglot/pull/6503) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`9454a18`](https://github.com/tobymao/sqlglot/commit/9454a18cca41a510e61522f6b785d646980e2100) - uppercase join method, side, kind for consistency fixes [#6510](https://github.com/tobymao/sqlglot/pull/6510) *(PR [#6511](https://github.com/tobymao/sqlglot/pull/6511) by [@georgesittas](https://github.com/georgesittas))*\n- [`a6ec4b6`](https://github.com/tobymao/sqlglot/commit/a6ec4b688891691b26ab874a3401e370c0b8d574) - reorder join mark check in eliminate_join_marks *(PR [#6528](https://github.com/tobymao/sqlglot/pull/6528) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#6527](https://github.com/tobymao/sqlglot/issues/6527) opened by [@snovik75](https://github.com/snovik75)*\n- [`9d06859`](https://github.com/tobymao/sqlglot/commit/9d0685923209c04747fa6fa2b35ee2e516453abc) - **optimizer**: annotate bigquery ARRAY when arg contains set operations *(PR [#6517](https://github.com/tobymao/sqlglot/pull/6517) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`241073d`](https://github.com/tobymao/sqlglot/commit/241073d886e0b4ad7b2252a8c8c394e717ef700a) - on_qualify type *(commit by [@tobymao](https://github.com/tobymao))*\n- [`2fd14ed`](https://github.com/tobymao/sqlglot/commit/2fd14ed32b3793444405005fb98342222b4d7956) - **optimizer**: query schema directly when type annotation fails for processing UNNEST source *(PR [#6451](https://github.com/tobymao/sqlglot/pull/6451) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`62b348c`](https://github.com/tobymao/sqlglot/commit/62b348ce46d014895bd17d89ccb0b3e186e46d15) - **tokenizer**: add support for noop string escapes *(PR [#6526](https://github.com/tobymao/sqlglot/pull/6526) by [@nian0114](https://github.com/nian0114))*\n- [`724e4b3`](https://github.com/tobymao/sqlglot/commit/724e4b3657018430e23976cf6a69989298521180) - **snowflake**: don't simplify match_condition closes [#6537](https://github.com/tobymao/sqlglot/pull/6537) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`12a6f7d`](https://github.com/tobymao/sqlglot/commit/12a6f7dc1b8604a2c9f4937654bd8bc458336110) - Only trigger integration tests on PR events *(PR [#6539](https://github.com/tobymao/sqlglot/pull/6539) by [@erindru](https://github.com/erindru))*\n- [`f21cf76`](https://github.com/tobymao/sqlglot/commit/f21cf763575b67084ea81a377c5bdb3e86041e4c) - **optimizer**: bq annotate SAFE_DIVIDE with both args as INT64 *(PR [#6543](https://github.com/tobymao/sqlglot/pull/6543) by [@geooo109](https://github.com/geooo109))*\n- [`4a57302`](https://github.com/tobymao/sqlglot/commit/4a5730242787920d0a2412aef495eb2eeaaa2119) - **optimizer**: ensure structs are annotated as unknown if any argument is unknown *(PR [#6544](https://github.com/tobymao/sqlglot/pull/6544) by [@georgesittas](https://github.com/georgesittas))*\n- [`63a2e49`](https://github.com/tobymao/sqlglot/commit/63a2e49485f237e1c7e16358c412acb5df50e22c) - **diff**: stop treating `None` args as leaves to be diffed *(PR [#6556](https://github.com/tobymao/sqlglot/pull/6556) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6554](https://github.com/tobymao/sqlglot/issues/6554) opened by [@GaryLiuGTA](https://github.com/GaryLiuGTA)*\n- [`c118af2`](https://github.com/tobymao/sqlglot/commit/c118af2f78af3e557f569c31b1561802338a48c4) - **lineage**: Fix GraphHTML edge 'from' key *(PR [#6571](https://github.com/tobymao/sqlglot/pull/6571) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6570](https://github.com/tobymao/sqlglot/issues/6570) opened by [@PhilHenson82](https://github.com/PhilHenson82)*\n- [`baeb656`](https://github.com/tobymao/sqlglot/commit/baeb656ee2ae354f91f36ebcaee60848e09f43b4) - **optimizer**: fallback scenario in get_table of resolver raises with wrong message *(PR [#6563](https://github.com/tobymao/sqlglot/pull/6563) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#6562](https://github.com/tobymao/sqlglot/issues/6562) opened by [@snovik75](https://github.com/snovik75)*\n- [`14dc1e5`](https://github.com/tobymao/sqlglot/commit/14dc1e5bc74b3b8907ba02bf89ad1763940c9ea2) - **snowflake**: make `DATE_PART` roundtrip *(PR [#6573](https://github.com/tobymao/sqlglot/pull/6573) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6565](https://github.com/tobymao/sqlglot/issues/6565) opened by [@erindru](https://github.com/erindru)*\n- [`8a44ad5`](https://github.com/tobymao/sqlglot/commit/8a44ad560cb65a34a722b257a82e69a41e7e45e0) - **bigquery**: Mark _DBT_MAX_PARTITION as pseudocolumn *(PR [#6572](https://github.com/tobymao/sqlglot/pull/6572) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :recycle: Refactors\n- [`e441e16`](https://github.com/tobymao/sqlglot/commit/e441e16991626c2da2d38bc9c3a2b408e3f773bd) - make dump/pickling non-recursive to avoid hitting stack limits *(PR [#5850](https://github.com/tobymao/sqlglot/pull/5850) by [@tobymao](https://github.com/tobymao))*\n- [`095b2ac`](https://github.com/tobymao/sqlglot/commit/095b2ac3af230eff86d9bc1b0fd3a0a2095f151c) - clean up duckdb INSTALL tests *(commit by [@geooo109](https://github.com/geooo109))*\n- [`d425ba2`](https://github.com/tobymao/sqlglot/commit/d425ba26b96b368801f8f486fa375cd75105993d) - make hash and eq non recursive *(PR [#5966](https://github.com/tobymao/sqlglot/pull/5966) by [@tobymao](https://github.com/tobymao))*\n- [`8f00c80`](https://github.com/tobymao/sqlglot/commit/8f00c804a67209a5eca1fcb28aeb95941c58e583) - _parse_in expr len check *(commit by [@geooo109](https://github.com/geooo109))*\n- [`2c9d15c`](https://github.com/tobymao/sqlglot/commit/2c9d15c92da25c8456b2463c69aa56c8ec47c453) - replace direct arg manipulation *(PR [#6073](https://github.com/tobymao/sqlglot/pull/6073) by [@geooo109](https://github.com/geooo109))*\n- [`58dbce3`](https://github.com/tobymao/sqlglot/commit/58dbce30da5ab94af82247ab8a7eb85200d9b8af) - bq static type annotators *(PR [#6103](https://github.com/tobymao/sqlglot/pull/6103) by [@geooo109](https://github.com/geooo109))*\n- [`c970235`](https://github.com/tobymao/sqlglot/commit/c97023549623fe5974d6bff57e64339eff74187e) - clean up MONTHNAME test *(commit by [@geooo109](https://github.com/geooo109))*\n- [`6d775fd`](https://github.com/tobymao/sqlglot/commit/6d775fdb6091cb866c27c0f1141514b23d689284) - snowflake GREATEST type checks *(commit by [@geooo109](https://github.com/geooo109))*\n- [`e797fb1`](https://github.com/tobymao/sqlglot/commit/e797fb105f7fa4e7bd42698eda71037cae9fd155) - update `LIKE` operator when using functional syntax with spark dialect *(PR [#6173](https://github.com/tobymao/sqlglot/pull/6173) by [@themattmorris](https://github.com/themattmorris))*\n  - :arrow_lower_right: *addresses issue [#6172](https://github.com/tobymao/sqlglot/issues/6172) opened by [@themattmorris](https://github.com/themattmorris)*\n- [`9c98fc2`](https://github.com/tobymao/sqlglot/commit/9c98fc2b39fef2bd052b60ba4e15a4b93fd66c00) - **optimizer**: avoid extra copy in simplify *(commit by [@geooo109](https://github.com/geooo109))*\n- [`43985fb`](https://github.com/tobymao/sqlglot/commit/43985fbcb9edea088119951c5c245a9606cf92ae) - **snowflake**: remove redundant tests for ANY_VALUE *(commit by [@geooo109](https://github.com/geooo109))*\n- [`bf7b032`](https://github.com/tobymao/sqlglot/commit/bf7b032baae0c0fd112054a7bed6fa2f56f32890) - clean up struct name inheritance *(PR [#6295](https://github.com/tobymao/sqlglot/pull/6295) by [@georgesittas](https://github.com/georgesittas))*\n- [`49e0f43`](https://github.com/tobymao/sqlglot/commit/49e0f43ba19739575987f2e9c52c2061a6f59717) - extra test for spark approx_top_k_accumulate *(commit by [@geooo109](https://github.com/geooo109))*\n- [`e4d1a4f`](https://github.com/tobymao/sqlglot/commit/e4d1a4fcd6741d679c5444bf023077d2aaa8f980) - **exasol**: map date/timestamp `TRUNC` to `DATE_TRUNC` *(PR [#6328](https://github.com/tobymao/sqlglot/pull/6328) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c6b0a63`](https://github.com/tobymao/sqlglot/commit/c6b0a6342a21d79635a26d40001c916d05d47cf7) - change version to be a tuple so that it can be pickled, also simpler *(commit by [@tobymao](https://github.com/tobymao))*\n- [`625654a`](https://github.com/tobymao/sqlglot/commit/625654a9623cc5407bfde922c29f32a8ee905a3b) - move resolver to own file *(commit by [@tobymao](https://github.com/tobymao))*\n- [`2d380e7`](https://github.com/tobymao/sqlglot/commit/2d380e72c9e3b842a8fe57c191f494c8872c00ee) - add test to make sure callback doesn't trigger ctes *(commit by [@tobymao](https://github.com/tobymao))*\n- [`1876c5a`](https://github.com/tobymao/sqlglot/commit/1876c5a86c3b737b7360c4fef25c44dc010b66db) - consolidate can_quote logic and fix an issue with identify=False *(PR [#6534](https://github.com/tobymao/sqlglot/pull/6534) by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`720f634`](https://github.com/tobymao/sqlglot/commit/720f6343f6144e8986ec6b7e50419c3d7a331f0a) - Fix style on main, refactor exasol tests *(PR [#5527](https://github.com/tobymao/sqlglot/pull/5527) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5653501`](https://github.com/tobymao/sqlglot/commit/5653501606f041282b6315c3efa33b9a3baf8d98) - Refactor PR 5517 *(PR [#5526](https://github.com/tobymao/sqlglot/pull/5526) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`d15dfe3`](https://github.com/tobymao/sqlglot/commit/d15dfe3f0f4444e4999ad65051b2474e62f422b3) - build type using dialect for bigquery *(PR [#5539](https://github.com/tobymao/sqlglot/pull/5539) by [@geooo109](https://github.com/geooo109))*\n- [`173e442`](https://github.com/tobymao/sqlglot/commit/173e4425b692728abffa8542324690823f984303) - refactor JSON_VALUE handling for MySQL and Trino *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`4c04c0c`](https://github.com/tobymao/sqlglot/commit/4c04c0ce859ab8314ed36fb8779f14c0fc2f1094) - use a valid SPDX identifier as license classifier *(PR [#5606](https://github.com/tobymao/sqlglot/pull/5606) by [@ecederstrand](https://github.com/ecederstrand))*\n- [`249f638`](https://github.com/tobymao/sqlglot/commit/249f638877ddd2a1732d1e6bc859793f3bc0622d) - add table to document dialect support level *(PR [#5628](https://github.com/tobymao/sqlglot/pull/5628) by [@georgesittas](https://github.com/georgesittas))*\n- [`3357125`](https://github.com/tobymao/sqlglot/commit/33571250d172d64a3e0450738b3ad330e5c0a795) - **doris**: refactor unique key prop generation *(PR [#5625](https://github.com/tobymao/sqlglot/pull/5625) by [@georgesittas](https://github.com/georgesittas))*\n- [`545f1ac`](https://github.com/tobymao/sqlglot/commit/545f1acd76bdc4e537209266984137f6c69ce622) - Clean up of PR5614 *(PR [#5648](https://github.com/tobymao/sqlglot/pull/5648) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`41521e3`](https://github.com/tobymao/sqlglot/commit/41521e31b465acd51ab02b1ac4e5512b98175b7e) - bump sqlglotrs to 0.6.2 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b77d3da`](https://github.com/tobymao/sqlglot/commit/b77d3da8f2548858d2b9d8590fcde83e1ec62b8a) - remove `\"EXCLUDE\" -> TokenType.EXCEPT` in DuckDB, Snowflake *(PR [#5766](https://github.com/tobymao/sqlglot/pull/5766) by [@treysp](https://github.com/treysp))*\n- [`005564a`](https://github.com/tobymao/sqlglot/commit/005564ab28cb14be469f09e89b01275d6e25874e) - **snowflake**: refactor logic related to ALTER SESSION *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`164fec1`](https://github.com/tobymao/sqlglot/commit/164fec1b36e3c7df41e2e5a5ad6b226fc5f76305) - **optimizer**: test type annotation for snowflake CHARINDEX function *(PR [#5805](https://github.com/tobymao/sqlglot/pull/5805) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`b244f30`](https://github.com/tobymao/sqlglot/commit/b244f30524846bd08d03a73410ae9b4674254ecd) - move `exp.Contains` to `BOOLEAN` entry in `TYPE_TO_EXPRESSIONS` *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`e8974e7`](https://github.com/tobymao/sqlglot/commit/e8974e70d9956ce7a5cb119ba465660f5f172a17) - **optimizer**: Add tests for snowflake likeall, likeany and ilikeany functions *(PR [#5908](https://github.com/tobymao/sqlglot/pull/5908) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`837890c`](https://github.com/tobymao/sqlglot/commit/837890c7e8bcc3695541bbe32fd8088eee70fea3) - handle badly formed binary expressions gracefully in type inference *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c18aaf8`](https://github.com/tobymao/sqlglot/commit/c18aaf80fd7375e89dfc8863da619d84f3257353) - cleanup *(commit by [@tobymao](https://github.com/tobymao))*\n- [`1514bc6`](https://github.com/tobymao/sqlglot/commit/1514bc640ec129a96aedd9e89bfd5d61e832d6b1) - **optimizer**: add type inference tests for Snowflake RPAD function *(PR [#5967](https://github.com/tobymao/sqlglot/pull/5967) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`050b89d`](https://github.com/tobymao/sqlglot/commit/050b89deb9be842f2ddd07c78ea201ec4eae4779) - **optimizer**: Annotate type for snowflake regexp function *(PR [#5970](https://github.com/tobymao/sqlglot/pull/5970) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`345c6a1`](https://github.com/tobymao/sqlglot/commit/345c6a153481a22d6df1b12ef1863e2133688fdf) - add uv support to Makefile *(PR [#5973](https://github.com/tobymao/sqlglot/pull/5973) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`1b1c6f8`](https://github.com/tobymao/sqlglot/commit/1b1c6f8d418371d49f0d3511baf3c5e35dd3ef42) - coerce type for EXTRACT canonicalization *(PR [#5998](https://github.com/tobymao/sqlglot/pull/5998) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5996](https://github.com/tobymao/sqlglot/issues/5996) opened by [@snovik75](https://github.com/snovik75)*\n- [`f00ae73`](https://github.com/tobymao/sqlglot/commit/f00ae735c8f185b4c6c132373c9fa9bbe58e37b7) - **optimizer**: Annotate type for sqrt function *(PR [#6003](https://github.com/tobymao/sqlglot/pull/6003) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ac97f14`](https://github.com/tobymao/sqlglot/commit/ac97f14ee1a576a276018f6c9ae1237ecf9ceda7) - simplify `SEARCH` Snowflake instantiation *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`5dd2ed3`](https://github.com/tobymao/sqlglot/commit/5dd2ed3c69cf9e8c3e327297e0cc932f0954e108) - bump sqlglotrs to 0.7.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7ac01c2`](https://github.com/tobymao/sqlglot/commit/7ac01c2ae9bc4375efb63c60e3221e85088fdd1f) - bump sqlglotrs to 0.7.1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`9ab3a96`](https://github.com/tobymao/sqlglot/commit/9ab3a96a853639224c80a9daff4674187a1a84ef) - bump sqlglotrs to 0.7.2 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`15030a3`](https://github.com/tobymao/sqlglot/commit/15030a3996d005d79f27408a68d17f94c98aec68) - **optimizer**: Add tests for snowflake LN and LOG functions *(PR [#6048](https://github.com/tobymao/sqlglot/pull/6048) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`2ae8dbd`](https://github.com/tobymao/sqlglot/commit/2ae8dbd4d1b43bb27647144c32b2a781ff3edbeb) - push docs to `api-docs` branch instead of main *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`75b8d16`](https://github.com/tobymao/sqlglot/commit/75b8d16e41b677ea7e150c89d713795073aae6e3) - remove docs from main branch *(PR [#6057](https://github.com/tobymao/sqlglot/pull/6057) by [@georgesittas](https://github.com/georgesittas))*\n- [`cfa2493`](https://github.com/tobymao/sqlglot/commit/cfa249328eef31ab0e0688dcc03521da3343ce47) - **optimizer**: Annotate type for snowflake SQUARE function *(PR [#6059](https://github.com/tobymao/sqlglot/pull/6059) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`e26c394`](https://github.com/tobymao/sqlglot/commit/e26c3949beb7f73020fcd099237dbe31a4db8d84) - **optimizer**: Annotate type for snowflake POW function *(PR [#6058](https://github.com/tobymao/sqlglot/pull/6058) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`7d303ad`](https://github.com/tobymao/sqlglot/commit/7d303adc5efe9d51eb62aeab80bfa4f844e1911d) - include Python 3.14 in the testing matrix *(PR [#6074](https://github.com/tobymao/sqlglot/pull/6074) by [@georgesittas](https://github.com/georgesittas))*\n- [`dab2a3f`](https://github.com/tobymao/sqlglot/commit/dab2a3fbdb8a523f05319eb34a1fd34534272206) - bump sqlglotrs version to 0.7.3 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d36ba87`](https://github.com/tobymao/sqlglot/commit/d36ba8774a2a4b53c122e3b78086ce0f09e77244) - **optimizer**: add tests for Snowflake DATE_FROM_PARTS function *(PR [#6077](https://github.com/tobymao/sqlglot/pull/6077) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`2bc05cf`](https://github.com/tobymao/sqlglot/commit/2bc05cf3bd53b874a1505c747e38f8a6a1dbf8c7) - **optimizer**: add tests for Snowflake DATEDIFF function *(PR [#6090](https://github.com/tobymao/sqlglot/pull/6090) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`a4d07a0`](https://github.com/tobymao/sqlglot/commit/a4d07a07eefbdaf88d30df2310a9533afdc75a82) - **optimizer**: Annotate type for snowflake EXTRACT function *(PR [#6099](https://github.com/tobymao/sqlglot/pull/6099) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ab1da2e`](https://github.com/tobymao/sqlglot/commit/ab1da2e54a83e29d708047d4b3f8abcc1094229d) - **optimizer**: add type annotation tests for snowflake LAST_DAY function *(PR [#6105](https://github.com/tobymao/sqlglot/pull/6105) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`4e24c0a`](https://github.com/tobymao/sqlglot/commit/4e24c0ad92e7071a1f1537886173e29999b46f72) - **optimizer**: add type annotation tests for snowflake TIMESTAMPDIFF function *(PR [#6138](https://github.com/tobymao/sqlglot/pull/6138) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`ae8571f`](https://github.com/tobymao/sqlglot/commit/ae8571fdec71587188e45fe087e1967f5ba641bc) - **optimizer**: add type annotation tests for snowflake TIMEDIFF *(PR [#6140](https://github.com/tobymao/sqlglot/pull/6140) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`3059320`](https://github.com/tobymao/sqlglot/commit/30593202b30001933f05747937975013754b75fa) - copy by default in `lineage` *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`99949cc`](https://github.com/tobymao/sqlglot/commit/99949ccd3ff81b524edeae437d874b86250dbb5b) - avoid needlessly copying in lineage *(PR [#6150](https://github.com/tobymao/sqlglot/pull/6150) by [@georgesittas](https://github.com/georgesittas))*\n- [`e7756d8`](https://github.com/tobymao/sqlglot/commit/e7756d8e9f347bfba3f861463890bf57e532cc54) - **optimizer**: add annotation tests for snowflake's BOOLXOR *(PR [#6154](https://github.com/tobymao/sqlglot/pull/6154) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`72e43e3`](https://github.com/tobymao/sqlglot/commit/72e43e3ea08f9dce5a32654060a56f2ee31bea8f) - **optimizer**: add type annotation tests for snowflake's TIMESTAMPADD function *(PR [#6146](https://github.com/tobymao/sqlglot/pull/6146) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`aca106c`](https://github.com/tobymao/sqlglot/commit/aca106c660b8aaf229065ec5c5a4a80d10e8daf6) - **optimizer**: add type annotation tests for snowflake GREATEST *(PR [#6157](https://github.com/tobymao/sqlglot/pull/6157) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`f763604`](https://github.com/tobymao/sqlglot/commit/f7636041d7b796545ed923ffd4803521f05fa7ea) - add `IS [NOT]` tests *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1ab5854`](https://github.com/tobymao/sqlglot/commit/1ab5854216da591e6036ac103239ac0280e09c3d) - **optimizer**: add snowflake test for [NOT] IN *(PR [#6180](https://github.com/tobymao/sqlglot/pull/6180) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`64939ce`](https://github.com/tobymao/sqlglot/commit/64939ce9926f4740387a151311e918e807bfa681) - **optimizer**: add annotation tests for ZEROIFNULL *(PR [#6187](https://github.com/tobymao/sqlglot/pull/6187) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`4b6bcdd`](https://github.com/tobymao/sqlglot/commit/4b6bcdd4dc297bd42ad000ffda98d14110565dc9) - **optimizer**: Add tests for snowflake's `NULLIFZERO` *(PR [#6197](https://github.com/tobymao/sqlglot/pull/6197) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`ef68075`](https://github.com/tobymao/sqlglot/commit/ef680756c33da180ed2f21fb6113a0123db341c9) - **optimizer**: add annotation tests for NVL2 *(PR [#6208](https://github.com/tobymao/sqlglot/pull/6208) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7f550f2`](https://github.com/tobymao/sqlglot/commit/7f550f22da40d8c1cfc8afb183d6e4dbd50241ea) - **optimizer**: add annotation tests for NVL *(PR [#6207](https://github.com/tobymao/sqlglot/pull/6207) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`d7be4a5`](https://github.com/tobymao/sqlglot/commit/d7be4a5da3dca6bcc44230b2a176c8b17b81c46e) - **optimizer**: add annotation test for COALESCE *(PR [#6210](https://github.com/tobymao/sqlglot/pull/6210) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`8aa7356`](https://github.com/tobymao/sqlglot/commit/8aa7356ab8adee26193086754ca1a1805957d944) - **optimizer**: add annotation tests for IFF *(PR [#6215](https://github.com/tobymao/sqlglot/pull/6215) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`160a1b9`](https://github.com/tobymao/sqlglot/commit/160a1b90f4ce39a2fce6f7f0e9e854d974fed053) - **optimizer**: mixed type annotation test for sf IFNULL *(commit by [@geooo109](https://github.com/geooo109))*\n- [`893ad2a`](https://github.com/tobymao/sqlglot/commit/893ad2a5b1a28339ccc65c85ac813506e6ad56f1) - **optimizer**: add annotation tests for NULLIF *(PR [#6221](https://github.com/tobymao/sqlglot/pull/6221) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`78d7733`](https://github.com/tobymao/sqlglot/commit/78d77335819d1796fa3989ef072d3f8fd4b83559) - remove redundant or term for unknown in annotate_types *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b202f3a`](https://github.com/tobymao/sqlglot/commit/b202f3ad64e88a47e52c45e32c9e4faae6c8ac45) - **optimizer**: add test for BITXOR *(PR [#6223](https://github.com/tobymao/sqlglot/pull/6223) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`b20f2e8`](https://github.com/tobymao/sqlglot/commit/b20f2e88d86038f1a98f4b97b5a2ae0b86652e33) - **optimizer**: add test for BITSHIFTLEFT *(PR [#6227](https://github.com/tobymao/sqlglot/pull/6227) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7f93e85`](https://github.com/tobymao/sqlglot/commit/7f93e8551b00cc32014236a07c8794bd7a3a2b91) - **optimizer**: add annotation tests for BITSHIFTRIGHT *(PR [#6228](https://github.com/tobymao/sqlglot/pull/6228) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`fcf017c`](https://github.com/tobymao/sqlglot/commit/fcf017cfb95923fea8ae5669340713a326f4f306) - rename `EXPRESSION_SPEC` to `EXPRESSION_METADATA` *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`55bc9e4`](https://github.com/tobymao/sqlglot/commit/55bc9e4019f8ef8d7e571256d7b0e07b30d9240c) - remove predicate/connector/not from typing metadata *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`349ab29`](https://github.com/tobymao/sqlglot/commit/349ab29aa84fb087388b6a1494fea70273a4a560) - **optimizer**: add annotation test for BOOLAND_OR *(PR [#6260](https://github.com/tobymao/sqlglot/pull/6260) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`75ec424`](https://github.com/tobymao/sqlglot/commit/75ec424667b95462bb1750a251a5096da0d5161b) - **optimizer**: add annotation test for BOOLAND_AGG *(PR [#6257](https://github.com/tobymao/sqlglot/pull/6257) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`bb574aa`](https://github.com/tobymao/sqlglot/commit/bb574aa0cf0a8c0b92f9af7ef3dfddb7de725a8b) - **optimizer**: add annotation test for ARRAY_AGG *(PR [#6264](https://github.com/tobymao/sqlglot/pull/6264) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`a95c5cc`](https://github.com/tobymao/sqlglot/commit/a95c5ccf411dc4d28ef9c19fb03bd8a3615d7c4b) - **optimizer**: add nonnull clickhouse column test case *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6d6c689`](https://github.com/tobymao/sqlglot/commit/6d6c68915ca699da7cb707675aece963df97f80b) - **optimizer**: add annotation tests for ANY_VALUE *(PR [#6275](https://github.com/tobymao/sqlglot/pull/6275) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`2459f88`](https://github.com/tobymao/sqlglot/commit/2459f8832ae398aa1381025724a4286f7f5e3e9d) - Follow up of 6280 *(PR [#6281](https://github.com/tobymao/sqlglot/pull/6281) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`a7d33d0`](https://github.com/tobymao/sqlglot/commit/a7d33d0e190fc5c9f23a1ab43082ac017d20fd18) - **optimizer**: add annotation tests for APPROX_PERCENTILE *(PR [#6283](https://github.com/tobymao/sqlglot/pull/6283) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`1b2d139`](https://github.com/tobymao/sqlglot/commit/1b2d139d3338c7053dee333914323236a2d15d97) - **optimizer**: add type annotation tests with window for sf APPROX_PERCENTILE *(commit by [@geooo109](https://github.com/geooo109))*\n- [`d059648`](https://github.com/tobymao/sqlglot/commit/d05964851c99553ba06e318bbbda39f9851120db) - **optimizer**: add annotation tests for APPROX_COUNT_DISTINCT *(PR [#6282](https://github.com/tobymao/sqlglot/pull/6282) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`6bd59ac`](https://github.com/tobymao/sqlglot/commit/6bd59acf2288da5bfe6151c5adf6f2a63792dc1e) - Follow up of PR 6288 *(PR [#6293](https://github.com/tobymao/sqlglot/pull/6293) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`546fd2a`](https://github.com/tobymao/sqlglot/commit/546fd2a2588f7b385bdbb9e39490bd6a422283ca) - Remove dead line in qualify_columns *(PR [#6304](https://github.com/tobymao/sqlglot/pull/6304) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ac7ac19`](https://github.com/tobymao/sqlglot/commit/ac7ac198a3b915e63ba8a055e9a0193c3dd3e26a) - **exasol**: Implement ODBC date time literals in Exasol Sqlglot *(PR [#6311](https://github.com/tobymao/sqlglot/pull/6311) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`8d1d25c`](https://github.com/tobymao/sqlglot/commit/8d1d25c6de7ad03c50e3efe892d16d16329d8ee9) - **exasol**: Implement local qualifier for-aliases, in GROUP BY, WHERE AND HAVING clause in exasol dialect *(PR [#6277](https://github.com/tobymao/sqlglot/pull/6277) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`509b0aa`](https://github.com/tobymao/sqlglot/commit/509b0aaada0e27542864771ba14777d398b6cee0) - **exasol**: Implement day_of_week function *(PR [#6319](https://github.com/tobymao/sqlglot/pull/6319) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`487d218`](https://github.com/tobymao/sqlglot/commit/487d218a6fcad4e28c65c6df55435ba218826186) - iterative annotate types *(PR [#6342](https://github.com/tobymao/sqlglot/pull/6342) by [@geooo109](https://github.com/geooo109))*\n- [`8201062`](https://github.com/tobymao/sqlglot/commit/8201062ac41b85e5a89aa8e1c5973852f105c66e) - clean up derived table traversal in table qualification *(PR [#6363](https://github.com/tobymao/sqlglot/pull/6363) by [@georgesittas](https://github.com/georgesittas))*\n- [`6b7084d`](https://github.com/tobymao/sqlglot/commit/6b7084d0c9f4735432afc12509c77c286cc50513) - **optimizer**: refactor costly scope walking loop in qualify tables *(PR [#6364](https://github.com/tobymao/sqlglot/pull/6364) by [@georgesittas](https://github.com/georgesittas))*\n- [`0319241`](https://github.com/tobymao/sqlglot/commit/0319241162bbe6d278a626100eac73999b250968) - **mysql,postgres**: tests for unsupported IGNORE/RESPECT NULLS *(PR [#6386](https://github.com/tobymao/sqlglot/pull/6386) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#6376](https://github.com/tobymao/sqlglot/issues/6376) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`11354cc`](https://github.com/tobymao/sqlglot/commit/11354cc85d116cd24c28114a437111965ba828a9) - Make integration test workflow more robust *(PR [#6403](https://github.com/tobymao/sqlglot/pull/6403) by [@erindru](https://github.com/erindru))*\n- [`f758cea`](https://github.com/tobymao/sqlglot/commit/f758cea0e9fca5850895a730c554c17b488d29ca) - **exasol**: transformed rank function, ignoring parameters *(PR [#6408](https://github.com/tobymao/sqlglot/pull/6408) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`07d9958`](https://github.com/tobymao/sqlglot/commit/07d99583b4aebdc682bb7604ccdf45bddb89f9c3) - **optimizer**: replace direct comparison with dialect properties *(PR [#6398](https://github.com/tobymao/sqlglot/pull/6398) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`137549e`](https://github.com/tobymao/sqlglot/commit/137549e5e803416d46e13e9a8123cef9b53d349a) - **exasol**: transform substring_index using substr and instr *(PR [#6406](https://github.com/tobymao/sqlglot/pull/6406) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`78f1824`](https://github.com/tobymao/sqlglot/commit/78f1824c790f523845cbda488ecf4c43a92ac0f0) - **exasol**: transform substring_index using substr and instr *(PR [#6406](https://github.com/tobymao/sqlglot/pull/6406) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`39cc555`](https://github.com/tobymao/sqlglot/commit/39cc55586ed76a4a583e6db22a9ee51e09bff92e) - **snowflake**: annotate type for COUNT *(PR [#6437](https://github.com/tobymao/sqlglot/pull/6437) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`61f39ba`](https://github.com/tobymao/sqlglot/commit/61f39bab9a0668c338e8c1b5e0fa953f22c0a886) - **optimizer**: improve error message for ambiguous columns *(PR [#6423](https://github.com/tobymao/sqlglot/pull/6423) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`313afe5`](https://github.com/tobymao/sqlglot/commit/313afe540aa2cdc4cc179c4852c6ef37362bcb3e) - **optimizer**: annotate type for snowflake func ARRAY_UNION_AGG *(PR [#6446](https://github.com/tobymao/sqlglot/pull/6446) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`b321ca6`](https://github.com/tobymao/sqlglot/commit/b321ca6191fefc88da1a6de83a465886b5754b7a) - bump sqlglotrs to 0.8.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`4a061e2`](https://github.com/tobymao/sqlglot/commit/4a061e26b638c9acb0c8a77d9347914b35082bb3) - **optimizer**: Include BIGDECIMAL in numeric precedence *(PR [#6456](https://github.com/tobymao/sqlglot/pull/6456) by [@vchan](https://github.com/vchan))*\n- [`f305305`](https://github.com/tobymao/sqlglot/commit/f305305e5cf3ef45afba822542aebeb944c00e0b) - **optimizer**: Annotate types for BigQuery's AVG function *(PR [#6459](https://github.com/tobymao/sqlglot/pull/6459) by [@vchan](https://github.com/vchan))*\n- [`910349f`](https://github.com/tobymao/sqlglot/commit/910349f3c30af59ce1820e48cae0cbb77539877d) - **optimizer**: Annotate types for BigQuery's SAFE_DIVIDE function *(PR [#6464](https://github.com/tobymao/sqlglot/pull/6464) by [@vchan](https://github.com/vchan))*\n- [`5e75621`](https://github.com/tobymao/sqlglot/commit/5e75621e90defd50076383485f6a4689a8c551ac) - **optimizer**: annotate type for snowflake func ARRAY_UNIQUE_AGG *(PR [#6465](https://github.com/tobymao/sqlglot/pull/6465) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`4d77500`](https://github.com/tobymao/sqlglot/commit/4d775007d2ceb997ff33721def768493c95f98a5) - **optimizer**: add tests for snowflake CAST function *(PR [#6471](https://github.com/tobymao/sqlglot/pull/6471) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`88dfd26`](https://github.com/tobymao/sqlglot/commit/88dfd26b832d13e517fe7c18d2c086885bf4954d) - **optimizer**: annotate type for snowflake func TO_BINARY *(PR [#6474](https://github.com/tobymao/sqlglot/pull/6474) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`483318b`](https://github.com/tobymao/sqlglot/commit/483318bc25e4ee4fa2731be1a0aea02858872ab5) - clean up TO_BINARY tests *(commit by [@geooo109](https://github.com/geooo109))*\n- [`bff7084`](https://github.com/tobymao/sqlglot/commit/bff70841d0bfbede6ea0fae2e7b37d68735a53d8) - remove duckdb TO_BINARY 2 arg test *(commit by [@geooo109](https://github.com/geooo109))*\n- [`80591f9`](https://github.com/tobymao/sqlglot/commit/80591f9513dff9160884e4bbbd48d9c26cf8f253) - starrocks TO_BINARY tests *(commit by [@geooo109](https://github.com/geooo109))*\n- [`01e5a05`](https://github.com/tobymao/sqlglot/commit/01e5a050c76f728ef542f0127209e2cd1c5f5558) - **exasol**: implementing the last day function in exasol sql dialect *(PR [#6483](https://github.com/tobymao/sqlglot/pull/6483) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`aacc981`](https://github.com/tobymao/sqlglot/commit/aacc98105fb381c17a80ee011f107157279312d7) - **duckdb**: tests for MAX_BY and MIN_BY *(PR [#6489](https://github.com/tobymao/sqlglot/pull/6489) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`06c7ffb`](https://github.com/tobymao/sqlglot/commit/06c7ffbe14985a4da35a97d47322021e79525adf) - cleanup bitwise operator fixes *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`edb8964`](https://github.com/tobymao/sqlglot/commit/edb8964ed064a687e52323143d52281eaa391c9a) - bump sqlglotrs to 0.9.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`0211328`](https://github.com/tobymao/sqlglot/commit/021132821fb33620643295533ce1517a172e7dc6) - add test-fast and test-fast-rs Makefile targets *(PR [#6546](https://github.com/tobymao/sqlglot/pull/6546) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`60b00bc`](https://github.com/tobymao/sqlglot/commit/60b00bc4d462c5ac03410a804305ad57ed6fbfbb) - Refactor PR 6555 *(PR [#6569](https://github.com/tobymao/sqlglot/pull/6569) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v28.3.0] - 2025-12-11\n### :boom: BREAKING CHANGES\n- due to [`62b348c`](https://github.com/tobymao/sqlglot/commit/62b348ce46d014895bd17d89ccb0b3e186e46d15) - add support for noop string escapes *(PR [#6526](https://github.com/tobymao/sqlglot/pull/6526) by [@nian0114](https://github.com/nian0114))*:\n\n  add support for noop string escapes (#6526)\n\n- due to [`1876c5a`](https://github.com/tobymao/sqlglot/commit/1876c5a86c3b737b7360c4fef25c44dc010b66db) - consolidate can_quote logic and fix an issue with identify=False *(PR [#6534](https://github.com/tobymao/sqlglot/pull/6534) by [@tobymao](https://github.com/tobymao))*:\n\n  consolidate can_quote logic and fix an issue with identify=False (#6534)\n\n- due to [`edb8964`](https://github.com/tobymao/sqlglot/commit/edb8964ed064a687e52323143d52281eaa391c9a) - bump sqlglotrs to 0.9.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.9.0\n\n\n### :bug: Bug Fixes\n- [`62b348c`](https://github.com/tobymao/sqlglot/commit/62b348ce46d014895bd17d89ccb0b3e186e46d15) - **tokenizer**: add support for noop string escapes *(PR [#6526](https://github.com/tobymao/sqlglot/pull/6526) by [@nian0114](https://github.com/nian0114))*\n\n### :recycle: Refactors\n- [`1876c5a`](https://github.com/tobymao/sqlglot/commit/1876c5a86c3b737b7360c4fef25c44dc010b66db) - consolidate can_quote logic and fix an issue with identify=False *(PR [#6534](https://github.com/tobymao/sqlglot/pull/6534) by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`edb8964`](https://github.com/tobymao/sqlglot/commit/edb8964ed064a687e52323143d52281eaa391c9a) - bump sqlglotrs to 0.9.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v28.2.0] - 2025-12-11\n### :boom: BREAKING CHANGES\n- due to [`ebe718a`](https://github.com/tobymao/sqlglot/commit/ebe718a72d5b5871a8d6e67754ff50e873d55b41) - Add support for format elements used in date/time functions like FORMAT_DATETIME *(PR [#6428](https://github.com/tobymao/sqlglot/pull/6428) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add support for format elements used in date/time functions like FORMAT_DATETIME (#6428)\n\n- due to [`c111f64`](https://github.com/tobymao/sqlglot/commit/c111f643d61064280024b4cc5c0fc250581fbe55) - annotation support for APPROX_PERCENTILE_ACCUMULATE *(PR [#6455](https://github.com/tobymao/sqlglot/pull/6455) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for APPROX_PERCENTILE_ACCUMULATE (#6455)\n\n- due to [`f305305`](https://github.com/tobymao/sqlglot/commit/f305305e5cf3ef45afba822542aebeb944c00e0b) - Annotate types for BigQuery's AVG function *(PR [#6459](https://github.com/tobymao/sqlglot/pull/6459) by [@vchan](https://github.com/vchan))*:\n\n  Annotate types for BigQuery's AVG function (#6459)\n\n- due to [`910349f`](https://github.com/tobymao/sqlglot/commit/910349f3c30af59ce1820e48cae0cbb77539877d) - Annotate types for BigQuery's SAFE_DIVIDE function *(PR [#6464](https://github.com/tobymao/sqlglot/pull/6464) by [@vchan](https://github.com/vchan))*:\n\n  Annotate types for BigQuery's SAFE_DIVIDE function (#6464)\n\n- due to [`5e75621`](https://github.com/tobymao/sqlglot/commit/5e75621e90defd50076383485f6a4689a8c551ac) - annotate type for snowflake func ARRAY_UNIQUE_AGG *(PR [#6465](https://github.com/tobymao/sqlglot/pull/6465) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake func ARRAY_UNIQUE_AGG (#6465)\n\n- due to [`94d46b8`](https://github.com/tobymao/sqlglot/commit/94d46b8eafd5abe252407d2bbe306ca579a29b20) - annotation support for APPROX_PERCENTILE_ESTIMATE. Return type DOUBLE *(PR [#6461](https://github.com/tobymao/sqlglot/pull/6461) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for APPROX_PERCENTILE_ESTIMATE. Return type DOUBLE (#6461)\n\n- due to [`2ac30b0`](https://github.com/tobymao/sqlglot/commit/2ac30b08bd663bbaf00ae075c4db0c3d27ab6640) - annotation support for APPROX_PERCENTILE_COMBINE *(PR [#6460](https://github.com/tobymao/sqlglot/pull/6460) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for APPROX_PERCENTILE_COMBINE (#6460)\n\n- due to [`d44bda3`](https://github.com/tobymao/sqlglot/commit/d44bda376c06956947a09a9f279cce886a63b981) - Annotate type for ZIPF *(PR [#6453](https://github.com/tobymao/sqlglot/pull/6453) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for ZIPF (#6453)\n\n- due to [`34dbd47`](https://github.com/tobymao/sqlglot/commit/34dbd478957c1796998d0b263f63c8ce1db7a320) - Annotate type for XMLGET *(PR [#6457](https://github.com/tobymao/sqlglot/pull/6457) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for XMLGET (#6457)\n\n- due to [`0d211f2`](https://github.com/tobymao/sqlglot/commit/0d211f2b36167cfb7856b8ec25f597f70317a9c7) - annotate type for MODE function snowflake *(PR [#6447](https://github.com/tobymao/sqlglot/pull/6447) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate type for MODE function snowflake (#6447)\n\n- due to [`cc4c8ab`](https://github.com/tobymao/sqlglot/commit/cc4c8ab43ab71790bc2bb9f8f3c06e34f89f999f) - annotate type for PERCENTILE_CONT in Snowflake *(PR [#6470](https://github.com/tobymao/sqlglot/pull/6470) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate type for PERCENTILE_CONT in Snowflake (#6470)\n\n- due to [`7dbc242`](https://github.com/tobymao/sqlglot/commit/7dbc242a637a8890511cc14f22bce4d425f1f55d) - annotation support for CURRENT REGION. Return type VARCHAR *(PR [#6473](https://github.com/tobymao/sqlglot/pull/6473) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT REGION. Return type VARCHAR (#6473)\n\n- due to [`43a6a5c`](https://github.com/tobymao/sqlglot/commit/43a6a5c601421e15a7f94dd489cb4fbcf9d2c8c3) - annotation support for CURRENT_ORGANIZATION_NAME. Return type VARCHAR *(PR [#6475](https://github.com/tobymao/sqlglot/pull/6475) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ORGANIZATION_NAME. Return type VARCHAR (#6475)\n\n- due to [`f1f7c6a`](https://github.com/tobymao/sqlglot/commit/f1f7c6ae6b6aa3f6f2251d0f81ee667440ca53d1) - annotation support for CURRENT_ORGANIZATION_USER. *(PR [#6476](https://github.com/tobymao/sqlglot/pull/6476) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ORGANIZATION_USER. (#6476)\n\n- due to [`88dfd26`](https://github.com/tobymao/sqlglot/commit/88dfd26b832d13e517fe7c18d2c086885bf4954d) - annotate type for snowflake func TO_BINARY *(PR [#6474](https://github.com/tobymao/sqlglot/pull/6474) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake func TO_BINARY (#6474)\n\n- due to [`d268203`](https://github.com/tobymao/sqlglot/commit/d268203e1dbae4e3aff863108f6d09a6f8274db5) - annotation support for CURRENT_ROLE_TYPE *(PR [#6479](https://github.com/tobymao/sqlglot/pull/6479) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ROLE_TYPE (#6479)\n\n- due to [`fd4431b`](https://github.com/tobymao/sqlglot/commit/fd4431bf9550c03aa761c642a68a21a146fd8548) - annotate type for VECTOR_L1_DISTANCE, VECTOR_L2_DISTANCE, VECTOR_COSINE_SIMILARITY functions *(PR [#6468](https://github.com/tobymao/sqlglot/pull/6468) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  annotate type for VECTOR_L1_DISTANCE, VECTOR_L2_DISTANCE, VECTOR_COSINE_SIMILARITY functions (#6468)\n\n- due to [`e6adba7`](https://github.com/tobymao/sqlglot/commit/e6adba76cc2f27633a9d38bfaea3356e71d00a4c) - Add support for coercing STRING literals to temporal types *(PR [#6482](https://github.com/tobymao/sqlglot/pull/6482) by [@vchan](https://github.com/vchan))*:\n\n  Add support for coercing STRING literals to temporal types (#6482)\n\n- due to [`68a5e61`](https://github.com/tobymao/sqlglot/commit/68a5e615b24e518cb90c9b80cf25355fcabdb468) - annotate type for REGR_* functions *(PR [#6452](https://github.com/tobymao/sqlglot/pull/6452) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  annotate type for REGR_* functions (#6452)\n\n- due to [`f7458a4`](https://github.com/tobymao/sqlglot/commit/f7458a40d3b09a2e212f6705ac4a77c99714508e) - annotate type for snowflake func TO_BOOLEAN *(PR [#6481](https://github.com/tobymao/sqlglot/pull/6481) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake func TO_BOOLEAN (#6481)\n\n- due to [`1531a67`](https://github.com/tobymao/sqlglot/commit/1531a67ac7806f3b4582f6cf1ea02342a517de74) - annotate type for VECTOR_INNER_PRODUCT *(PR [#6486](https://github.com/tobymao/sqlglot/pull/6486) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  annotate type for VECTOR_INNER_PRODUCT (#6486)\n\n- due to [`df4c1d3`](https://github.com/tobymao/sqlglot/commit/df4c1d37ff77151a74b5de3d119c7e03f5db85f4) - REGEXP_EXTRACT position arg overflow *(PR [#6458](https://github.com/tobymao/sqlglot/pull/6458) by [@treysp](https://github.com/treysp))*:\n\n  REGEXP_EXTRACT position arg overflow (#6458)\n\n- due to [`f6b2b3b`](https://github.com/tobymao/sqlglot/commit/f6b2b3bc6e1c95340149be65d80ef7e177b28d82) - support padside argument for BIT[OR|AND|XOR] *(PR [#6487](https://github.com/tobymao/sqlglot/pull/6487) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support padside argument for BIT[OR|AND|XOR] (#6487)\n\n- due to [`5a49c3f`](https://github.com/tobymao/sqlglot/commit/5a49c3f7a7619ad9e711ff2cd9e85b8606969b36) - support ORDER / LIMIT expressions for BigQuery ARRAY_AGG / STRING_AGG functions *(PR [#6463](https://github.com/tobymao/sqlglot/pull/6463) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support ORDER / LIMIT expressions for BigQuery ARRAY_AGG / STRING_AGG functions (#6463)\n\n- due to [`ef130f1`](https://github.com/tobymao/sqlglot/commit/ef130f1b944b4be835d4a6831fec9a333a825a34) - Annotated type for ARRAY_CONSTRUCT_COMPACT [#6496](https://github.com/tobymao/sqlglot/pull/6496) *(commit by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotated type for ARRAY_CONSTRUCT_COMPACT #6496\n\n- due to [`1b6076b`](https://github.com/tobymao/sqlglot/commit/1b6076bd5a64b044f52f5366244ba0746aca75e1) - wrap connectives generated due to transpiling LIKE ANY closes [#6493](https://github.com/tobymao/sqlglot/pull/6493) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  wrap connectives generated due to transpiling LIKE ANY closes #6493\n\n- due to [`36ad534`](https://github.com/tobymao/sqlglot/commit/36ad534b14eabe9ee197017f5087e8e5190f8526) - qualified select list with \"LOCAL\" *(PR [#6450](https://github.com/tobymao/sqlglot/pull/6450) by [@nnamdi16](https://github.com/nnamdi16))*:\n\n  qualified select list with \"LOCAL\" (#6450)\n\n- due to [`36cf0bf`](https://github.com/tobymao/sqlglot/commit/36cf0bf6671f622344afee52d7aafe30f19ecf9a) - annotation support for CURRENT_ROLE. *(PR [#6478](https://github.com/tobymao/sqlglot/pull/6478) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ROLE. (#6478)\n\n- due to [`cbba04c`](https://github.com/tobymao/sqlglot/commit/cbba04cb292fe8b3fd38c87d9ccb624cdcb52843) - support comma-separated syntax for OVERLAY function *(PR [#6497](https://github.com/tobymao/sqlglot/pull/6497) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  support comma-separated syntax for OVERLAY function (#6497)\n\n- due to [`dc8f26a`](https://github.com/tobymao/sqlglot/commit/dc8f26a3a5e023a0e54caa345b129fb1b4fe805f) - bq annotate type for NULL *(PR [#6491](https://github.com/tobymao/sqlglot/pull/6491) by [@geooo109](https://github.com/geooo109))*:\n\n  bq annotate type for NULL (#6491)\n\n- due to [`52aceaa`](https://github.com/tobymao/sqlglot/commit/52aceaaa887dddb35f8ede5c2d9577fdeee35c48) - annotate `HavingMax` by `this` *(PR [#6499](https://github.com/tobymao/sqlglot/pull/6499) by [@georgesittas](https://github.com/georgesittas))*:\n\n  annotate `HavingMax` by `this` (#6499)\n\n- due to [`c97a81d`](https://github.com/tobymao/sqlglot/commit/c97a81d68a1584fad48475725665a7678fcad9d1) - annotate TO_HEX(MD5(...)) in BigQuery *(PR [#6500](https://github.com/tobymao/sqlglot/pull/6500) by [@georgesittas](https://github.com/georgesittas))*:\n\n  annotate TO_HEX(MD5(...)) in BigQuery (#6500)\n\n- due to [`a5797a1`](https://github.com/tobymao/sqlglot/commit/a5797a1c867c4ade71ae4ddf93232576993cf5bc) - handle named arguments and non-integer scale input for ROUND *(PR [#6495](https://github.com/tobymao/sqlglot/pull/6495) by [@toriwei](https://github.com/toriwei))*:\n\n  handle named arguments and non-integer scale input for ROUND (#6495)\n\n- due to [`3224235`](https://github.com/tobymao/sqlglot/commit/3224235c1b7a80511af11f7dbffe608a747a3df0) - make CTE builder produce AST consistent with parser closes [#6503](https://github.com/tobymao/sqlglot/pull/6503) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  make CTE builder produce AST consistent with parser closes #6503\n\n- due to [`8b5298a`](https://github.com/tobymao/sqlglot/commit/8b5298a6578af80fd9676eb222422862d5468859) - Transpile BQ's WEEK based `DATE_DIFF` *(PR [#6507](https://github.com/tobymao/sqlglot/pull/6507) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Transpile BQ's WEEK based `DATE_DIFF` (#6507)\n\n- due to [`9454a18`](https://github.com/tobymao/sqlglot/commit/9454a18cca41a510e61522f6b785d646980e2100) - uppercase join method, side, kind for consistency fixes [#6510](https://github.com/tobymao/sqlglot/pull/6510) *(PR [#6511](https://github.com/tobymao/sqlglot/pull/6511) by [@georgesittas](https://github.com/georgesittas))*:\n\n  uppercase join method, side, kind for consistency fixes #6510 (#6511)\n\n- due to [`41b776b`](https://github.com/tobymao/sqlglot/commit/41b776bdc6936f18accd9f7308b55acd383bb596) - added support for current_catalog *(PR [#6492](https://github.com/tobymao/sqlglot/pull/6492) by [@AbhishekASLK](https://github.com/AbhishekASLK))*:\n\n  added support for current_catalog (#6492)\n\n- due to [`dd19bea`](https://github.com/tobymao/sqlglot/commit/dd19beae95f077cfd8b6e315eca7ff212817b250) - annotation support for CURRENT_ACCOUNT *(PR [#6512](https://github.com/tobymao/sqlglot/pull/6512) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ACCOUNT (#6512)\n\n- due to [`2e8105e`](https://github.com/tobymao/sqlglot/commit/2e8105eebaec25fc8f94f1e68951198660f404e1) - Annotate type for VAR_POP, VAR_SAMP, DuckDB consistency fix for VAR_SAMP *(PR [#6488](https://github.com/tobymao/sqlglot/pull/6488) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for VAR_POP, VAR_SAMP, DuckDB consistency fix for VAR_SAMP (#6488)\n\n- due to [`cfb02c1`](https://github.com/tobymao/sqlglot/commit/cfb02c1aa676e801b2d13a84467b4904cd834ffe) - annotation support for CURRENT_ACCOUNT_NAME *(PR [#6513](https://github.com/tobymao/sqlglot/pull/6513) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_ACCOUNT_NAME (#6513)\n\n- due to [`1004e31`](https://github.com/tobymao/sqlglot/commit/1004e31cce62cce2e2afb7eab85ed8bdecaede3b) - annotation support for CURRENT_AVAILABLE_ROLES *(PR [#6514](https://github.com/tobymao/sqlglot/pull/6514) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_AVAILABLE_ROLES (#6514)\n\n- due to [`ff201fe`](https://github.com/tobymao/sqlglot/commit/ff201febd27937a97674dd091928456dde733254) - annotation support for CURRENT_CLIENT *(PR [#6515](https://github.com/tobymao/sqlglot/pull/6515) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_CLIENT (#6515)\n\n- due to [`d777a9c`](https://github.com/tobymao/sqlglot/commit/d777a9c0feef15ac036f7b413112de4d7cc8bea4) - annotation support for CURRENT_IP_ADDRESS *(PR [#6518](https://github.com/tobymao/sqlglot/pull/6518) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_IP_ADDRESS (#6518)\n\n- due to [`c296061`](https://github.com/tobymao/sqlglot/commit/c2960615a3bd279b7c5f775d5b93ae12aa27a3b8) - Transpilation of TO_BINARY from snowflake to duckdb *(PR [#6504](https://github.com/tobymao/sqlglot/pull/6504) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  Transpilation of TO_BINARY from snowflake to duckdb (#6504)\n\n- due to [`7a70164`](https://github.com/tobymao/sqlglot/commit/7a70164d8cf361cf4c0a7d5789bb51676f772959) - transpile Snowflake's `RANDSTR` function *(PR [#6502](https://github.com/tobymao/sqlglot/pull/6502) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  transpile Snowflake's `RANDSTR` function (#6502)\n\n- due to [`a26d419`](https://github.com/tobymao/sqlglot/commit/a26d4191e5468e39eafdf7a981e7b890d438b2c9) - annotation support for CURRENT_DATABASE *(PR [#6516](https://github.com/tobymao/sqlglot/pull/6516) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_DATABASE (#6516)\n\n- due to [`0acdf7f`](https://github.com/tobymao/sqlglot/commit/0acdf7fc783f2722536ec24dcf8600957febf7ca) - annotation support for CURRENT_SCHEMAS *(PR [#6519](https://github.com/tobymao/sqlglot/pull/6519) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_SCHEMAS (#6519)\n\n- due to [`43cce89`](https://github.com/tobymao/sqlglot/commit/43cce895da80d21abc89d40de5d7fddd68871bf0) - annotation support for CURRENT_SECONDARY_ROLES *(PR [#6520](https://github.com/tobymao/sqlglot/pull/6520) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_SECONDARY_ROLES (#6520)\n\n- due to [`c21b4b1`](https://github.com/tobymao/sqlglot/commit/c21b4b1134b368ee5144339b59e70ddcc54f3dbc) - annotation support for CURRENT_SESSION *(PR [#6521](https://github.com/tobymao/sqlglot/pull/6521) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_SESSION (#6521)\n\n- due to [`57a83c0`](https://github.com/tobymao/sqlglot/commit/57a83c018dace690f7bb363c25ee6bde33c3d60f) - annotation support for CURRENT_STATEMENT *(PR [#6522](https://github.com/tobymao/sqlglot/pull/6522) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_STATEMENT (#6522)\n\n- due to [`4b240e4`](https://github.com/tobymao/sqlglot/commit/4b240e40a8809a6eea2a279370a884f4a7b03dfa) - annotation support for CURRENT_VERSION *(PR [#6524](https://github.com/tobymao/sqlglot/pull/6524) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_VERSION (#6524)\n\n- due to [`c1a831f`](https://github.com/tobymao/sqlglot/commit/c1a831f5bf662ab8d8e07dc2bb949f2adcbe7d7c) - annotation support for CURRENT_TRANSACTION *(PR [#6523](https://github.com/tobymao/sqlglot/pull/6523) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_TRANSACTION (#6523)\n\n- due to [`2e162b0`](https://github.com/tobymao/sqlglot/commit/2e162b0d34066e7aa7edac3156739bcd31a634fc) - annotation support for CURRENT_WAREHOUSE *(PR [#6525](https://github.com/tobymao/sqlglot/pull/6525) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for CURRENT_WAREHOUSE (#6525)\n\n- due to [`9d06859`](https://github.com/tobymao/sqlglot/commit/9d0685923209c04747fa6fa2b35ee2e516453abc) - annotate bigquery ARRAY when arg contains set operations *(PR [#6517](https://github.com/tobymao/sqlglot/pull/6517) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate bigquery ARRAY when arg contains set operations (#6517)\n\n- due to [`2fd14ed`](https://github.com/tobymao/sqlglot/commit/2fd14ed32b3793444405005fb98342222b4d7956) - query schema directly when type annotation fails for processing UNNEST source *(PR [#6451](https://github.com/tobymao/sqlglot/pull/6451) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  query schema directly when type annotation fails for processing UNNEST source (#6451)\n\n- due to [`41a9e88`](https://github.com/tobymao/sqlglot/commit/41a9e88bb9800205df0b3e10a1976699dc4fe4f9) - Add support to transpile binary args for bitwise operators *(PR [#6508](https://github.com/tobymao/sqlglot/pull/6508) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add support to transpile binary args for bitwise operators (#6508)\n\n- due to [`06c7ffb`](https://github.com/tobymao/sqlglot/commit/06c7ffbe14985a4da35a97d47322021e79525adf) - cleanup bitwise operator fixes *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  cleanup bitwise operator fixes\n\n\n### :sparkles: New Features\n- [`ebe718a`](https://github.com/tobymao/sqlglot/commit/ebe718a72d5b5871a8d6e67754ff50e873d55b41) - **duckdb**: Add support for format elements used in date/time functions like FORMAT_DATETIME *(PR [#6428](https://github.com/tobymao/sqlglot/pull/6428) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c111f64`](https://github.com/tobymao/sqlglot/commit/c111f643d61064280024b4cc5c0fc250581fbe55) - **snowflake**: annotation support for APPROX_PERCENTILE_ACCUMULATE *(PR [#6455](https://github.com/tobymao/sqlglot/pull/6455) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`a7d211e`](https://github.com/tobymao/sqlglot/commit/a7d211e6fdce968c64b050c77e026cc23fdc07e5) - **duckdb**: transpile DECFLOAT type to DECIMAL(38, 5) *(PR [#6462](https://github.com/tobymao/sqlglot/pull/6462) by [@toriwei](https://github.com/toriwei))*\n- [`94d46b8`](https://github.com/tobymao/sqlglot/commit/94d46b8eafd5abe252407d2bbe306ca579a29b20) - **snowflake**: annotation support for APPROX_PERCENTILE_ESTIMATE. Return type DOUBLE *(PR [#6461](https://github.com/tobymao/sqlglot/pull/6461) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`2ac30b0`](https://github.com/tobymao/sqlglot/commit/2ac30b08bd663bbaf00ae075c4db0c3d27ab6640) - **snowflake**: annotation support for APPROX_PERCENTILE_COMBINE *(PR [#6460](https://github.com/tobymao/sqlglot/pull/6460) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d44bda3`](https://github.com/tobymao/sqlglot/commit/d44bda376c06956947a09a9f279cce886a63b981) - **optimizer**: Annotate type for ZIPF *(PR [#6453](https://github.com/tobymao/sqlglot/pull/6453) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`34dbd47`](https://github.com/tobymao/sqlglot/commit/34dbd478957c1796998d0b263f63c8ce1db7a320) - **optimizer**: Annotate type for XMLGET *(PR [#6457](https://github.com/tobymao/sqlglot/pull/6457) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ff3f0f9`](https://github.com/tobymao/sqlglot/commit/ff3f0f998674f5b2741c3f6cadbe24fa8fb607ad) - **databricks**: add support for ?:: operator *(PR [#6469](https://github.com/tobymao/sqlglot/pull/6469) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`0d211f2`](https://github.com/tobymao/sqlglot/commit/0d211f2b36167cfb7856b8ec25f597f70317a9c7) - **snowflake**: annotate type for MODE function snowflake *(PR [#6447](https://github.com/tobymao/sqlglot/pull/6447) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`cc4c8ab`](https://github.com/tobymao/sqlglot/commit/cc4c8ab43ab71790bc2bb9f8f3c06e34f89f999f) - **snowflake**: annotate type for PERCENTILE_CONT in Snowflake *(PR [#6470](https://github.com/tobymao/sqlglot/pull/6470) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7dbc242`](https://github.com/tobymao/sqlglot/commit/7dbc242a637a8890511cc14f22bce4d425f1f55d) - **snowflake**: annotation support for CURRENT REGION. Return type VARCHAR *(PR [#6473](https://github.com/tobymao/sqlglot/pull/6473) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`43a6a5c`](https://github.com/tobymao/sqlglot/commit/43a6a5c601421e15a7f94dd489cb4fbcf9d2c8c3) - **snowflake**: annotation support for CURRENT_ORGANIZATION_NAME. Return type VARCHAR *(PR [#6475](https://github.com/tobymao/sqlglot/pull/6475) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`f1f7c6a`](https://github.com/tobymao/sqlglot/commit/f1f7c6ae6b6aa3f6f2251d0f81ee667440ca53d1) - **snowflake**: annotation support for CURRENT_ORGANIZATION_USER. *(PR [#6476](https://github.com/tobymao/sqlglot/pull/6476) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d268203`](https://github.com/tobymao/sqlglot/commit/d268203e1dbae4e3aff863108f6d09a6f8274db5) - **snowflake**: annotation support for CURRENT_ROLE_TYPE *(PR [#6479](https://github.com/tobymao/sqlglot/pull/6479) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`fd4431b`](https://github.com/tobymao/sqlglot/commit/fd4431bf9550c03aa761c642a68a21a146fd8548) - **snowflake**: annotate type for VECTOR_L1_DISTANCE, VECTOR_L2_DISTANCE, VECTOR_COSINE_SIMILARITY functions *(PR [#6468](https://github.com/tobymao/sqlglot/pull/6468) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`e6adba7`](https://github.com/tobymao/sqlglot/commit/e6adba76cc2f27633a9d38bfaea3356e71d00a4c) - **BigQuery**: Add support for coercing STRING literals to temporal types *(PR [#6482](https://github.com/tobymao/sqlglot/pull/6482) by [@vchan](https://github.com/vchan))*\n- [`68a5e61`](https://github.com/tobymao/sqlglot/commit/68a5e615b24e518cb90c9b80cf25355fcabdb468) - **snowflake**: annotate type for REGR_* functions *(PR [#6452](https://github.com/tobymao/sqlglot/pull/6452) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`f7458a4`](https://github.com/tobymao/sqlglot/commit/f7458a40d3b09a2e212f6705ac4a77c99714508e) - **optimizer**: annotate type for snowflake func TO_BOOLEAN *(PR [#6481](https://github.com/tobymao/sqlglot/pull/6481) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`1531a67`](https://github.com/tobymao/sqlglot/commit/1531a67ac7806f3b4582f6cf1ea02342a517de74) - **snowflake**: annotate type for VECTOR_INNER_PRODUCT *(PR [#6486](https://github.com/tobymao/sqlglot/pull/6486) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`f6b2b3b`](https://github.com/tobymao/sqlglot/commit/f6b2b3bc6e1c95340149be65d80ef7e177b28d82) - **snowflake**: support padside argument for BIT[OR|AND|XOR] *(PR [#6487](https://github.com/tobymao/sqlglot/pull/6487) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`e891397`](https://github.com/tobymao/sqlglot/commit/e89139714aefd8a6481a90d9753c81910c9f88e9) - **BigQuery**: Add support for the NET.HOST function *(PR [#6480](https://github.com/tobymao/sqlglot/pull/6480) by [@vchan](https://github.com/vchan))*\n- [`2cc67cd`](https://github.com/tobymao/sqlglot/commit/2cc67cd7386914043a9cb4eb322fb1fa9af15c8b) - **singlestore**: support dcolonqmark *(PR [#6485](https://github.com/tobymao/sqlglot/pull/6485) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`7d485c7`](https://github.com/tobymao/sqlglot/commit/7d485c7cffe7b6d0113cfcfcf0736de0383bd380) - **duckdb**: Add transpilation support for the negative integer args for BITNOT *(PR [#6490](https://github.com/tobymao/sqlglot/pull/6490) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ef130f1`](https://github.com/tobymao/sqlglot/commit/ef130f1b944b4be835d4a6831fec9a333a825a34) - **snowflake**: Annotated type for ARRAY_CONSTRUCT_COMPACT [#6496](https://github.com/tobymao/sqlglot/pull/6496) *(commit by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`36cf0bf`](https://github.com/tobymao/sqlglot/commit/36cf0bf6671f622344afee52d7aafe30f19ecf9a) - **snowflake**: annotation support for CURRENT_ROLE. *(PR [#6478](https://github.com/tobymao/sqlglot/pull/6478) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`cbba04c`](https://github.com/tobymao/sqlglot/commit/cbba04cb292fe8b3fd38c87d9ccb624cdcb52843) - **databricks**: support comma-separated syntax for OVERLAY function *(PR [#6497](https://github.com/tobymao/sqlglot/pull/6497) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`dc8f26a`](https://github.com/tobymao/sqlglot/commit/dc8f26a3a5e023a0e54caa345b129fb1b4fe805f) - **optimizer**: bq annotate type for NULL *(PR [#6491](https://github.com/tobymao/sqlglot/pull/6491) by [@geooo109](https://github.com/geooo109))*\n- [`c97a81d`](https://github.com/tobymao/sqlglot/commit/c97a81d68a1584fad48475725665a7678fcad9d1) - **optimizer**: annotate TO_HEX(MD5(...)) in BigQuery *(PR [#6500](https://github.com/tobymao/sqlglot/pull/6500) by [@georgesittas](https://github.com/georgesittas))*\n- [`a5797a1`](https://github.com/tobymao/sqlglot/commit/a5797a1c867c4ade71ae4ddf93232576993cf5bc) - **duckdb**: handle named arguments and non-integer scale input for ROUND *(PR [#6495](https://github.com/tobymao/sqlglot/pull/6495) by [@toriwei](https://github.com/toriwei))*\n- [`8b5298a`](https://github.com/tobymao/sqlglot/commit/8b5298a6578af80fd9676eb222422862d5468859) - **duckdb**: Transpile BQ's WEEK based `DATE_DIFF` *(PR [#6507](https://github.com/tobymao/sqlglot/pull/6507) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2c013a5`](https://github.com/tobymao/sqlglot/commit/2c013a5cc8e37cde8a8f9443e0397191ce82f0f5) - **exasol**: qualify bare stars to facilitate transpilation *(PR [#6431](https://github.com/tobymao/sqlglot/pull/6431) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`41b776b`](https://github.com/tobymao/sqlglot/commit/41b776bdc6936f18accd9f7308b55acd383bb596) - **postgres,trino,duckdb**: added support for current_catalog *(PR [#6492](https://github.com/tobymao/sqlglot/pull/6492) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`dd19bea`](https://github.com/tobymao/sqlglot/commit/dd19beae95f077cfd8b6e315eca7ff212817b250) - **snowflake**: annotation support for CURRENT_ACCOUNT *(PR [#6512](https://github.com/tobymao/sqlglot/pull/6512) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`2e8105e`](https://github.com/tobymao/sqlglot/commit/2e8105eebaec25fc8f94f1e68951198660f404e1) - **snowflake**: Annotate type for VAR_POP, VAR_SAMP, DuckDB consistency fix for VAR_SAMP *(PR [#6488](https://github.com/tobymao/sqlglot/pull/6488) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`cfb02c1`](https://github.com/tobymao/sqlglot/commit/cfb02c1aa676e801b2d13a84467b4904cd834ffe) - **snowflake**: annotation support for CURRENT_ACCOUNT_NAME *(PR [#6513](https://github.com/tobymao/sqlglot/pull/6513) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`1004e31`](https://github.com/tobymao/sqlglot/commit/1004e31cce62cce2e2afb7eab85ed8bdecaede3b) - **snowflake**: annotation support for CURRENT_AVAILABLE_ROLES *(PR [#6514](https://github.com/tobymao/sqlglot/pull/6514) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`ff201fe`](https://github.com/tobymao/sqlglot/commit/ff201febd27937a97674dd091928456dde733254) - **snowflake**: annotation support for CURRENT_CLIENT *(PR [#6515](https://github.com/tobymao/sqlglot/pull/6515) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d777a9c`](https://github.com/tobymao/sqlglot/commit/d777a9c0feef15ac036f7b413112de4d7cc8bea4) - **snowflake**: annotation support for CURRENT_IP_ADDRESS *(PR [#6518](https://github.com/tobymao/sqlglot/pull/6518) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`c296061`](https://github.com/tobymao/sqlglot/commit/c2960615a3bd279b7c5f775d5b93ae12aa27a3b8) - **snowflake**: Transpilation of TO_BINARY from snowflake to duckdb *(PR [#6504](https://github.com/tobymao/sqlglot/pull/6504) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7a70164`](https://github.com/tobymao/sqlglot/commit/7a70164d8cf361cf4c0a7d5789bb51676f772959) - **duckdb**: transpile Snowflake's `RANDSTR` function *(PR [#6502](https://github.com/tobymao/sqlglot/pull/6502) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`a26d419`](https://github.com/tobymao/sqlglot/commit/a26d4191e5468e39eafdf7a981e7b890d438b2c9) - **snowflake**: annotation support for CURRENT_DATABASE *(PR [#6516](https://github.com/tobymao/sqlglot/pull/6516) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`0acdf7f`](https://github.com/tobymao/sqlglot/commit/0acdf7fc783f2722536ec24dcf8600957febf7ca) - **snowflake**: annotation support for CURRENT_SCHEMAS *(PR [#6519](https://github.com/tobymao/sqlglot/pull/6519) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`43cce89`](https://github.com/tobymao/sqlglot/commit/43cce895da80d21abc89d40de5d7fddd68871bf0) - **snowflake**: annotation support for CURRENT_SECONDARY_ROLES *(PR [#6520](https://github.com/tobymao/sqlglot/pull/6520) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`c21b4b1`](https://github.com/tobymao/sqlglot/commit/c21b4b1134b368ee5144339b59e70ddcc54f3dbc) - **snowflake**: annotation support for CURRENT_SESSION *(PR [#6521](https://github.com/tobymao/sqlglot/pull/6521) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`57a83c0`](https://github.com/tobymao/sqlglot/commit/57a83c018dace690f7bb363c25ee6bde33c3d60f) - **snowflake**: annotation support for CURRENT_STATEMENT *(PR [#6522](https://github.com/tobymao/sqlglot/pull/6522) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4b240e4`](https://github.com/tobymao/sqlglot/commit/4b240e40a8809a6eea2a279370a884f4a7b03dfa) - **snowflake**: annotation support for CURRENT_VERSION *(PR [#6524](https://github.com/tobymao/sqlglot/pull/6524) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`c1a831f`](https://github.com/tobymao/sqlglot/commit/c1a831f5bf662ab8d8e07dc2bb949f2adcbe7d7c) - **snowflake**: annotation support for CURRENT_TRANSACTION *(PR [#6523](https://github.com/tobymao/sqlglot/pull/6523) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`2e162b0`](https://github.com/tobymao/sqlglot/commit/2e162b0d34066e7aa7edac3156739bcd31a634fc) - **snowflake**: annotation support for CURRENT_WAREHOUSE *(PR [#6525](https://github.com/tobymao/sqlglot/pull/6525) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`18e9814`](https://github.com/tobymao/sqlglot/commit/18e98145906eaa5b769af49cf46b58a1d9448aee) - **snowflake**: support DAYOFWEEK_ISO date part *(PR [#6531](https://github.com/tobymao/sqlglot/pull/6531) by [@toriwei](https://github.com/toriwei))*\n- [`ee5e7b9`](https://github.com/tobymao/sqlglot/commit/ee5e7b931ca745a000dc8a720b56aee7b44186b2) - Automatically trigger integration tests scoped to modified dialects *(PR [#6505](https://github.com/tobymao/sqlglot/pull/6505) by [@erindru](https://github.com/erindru))*\n- [`e60634f`](https://github.com/tobymao/sqlglot/commit/e60634f0e1c396b54ad357132606286bd21d3e36) - **clickhouse**: Add support for quantilesExactExclusive agg func *(PR [#6535](https://github.com/tobymao/sqlglot/pull/6535) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#6533](https://github.com/tobymao/sqlglot/issues/6533) opened by [@vargasj-ms](https://github.com/vargasj-ms)*\n- [`41a9e88`](https://github.com/tobymao/sqlglot/commit/41a9e88bb9800205df0b3e10a1976699dc4fe4f9) - **duckdb**: Add support to transpile binary args for bitwise operators *(PR [#6508](https://github.com/tobymao/sqlglot/pull/6508) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n### :bug: Bug Fixes\n- [`7021d54`](https://github.com/tobymao/sqlglot/commit/7021d54ecf0ceab3c3606642cbfca8e080cc8613) - **tsql**: CEILING generation *(PR [#6477](https://github.com/tobymao/sqlglot/pull/6477) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6472](https://github.com/tobymao/sqlglot/issues/6472) opened by [@ricky-ho](https://github.com/ricky-ho)*\n- [`df4c1d3`](https://github.com/tobymao/sqlglot/commit/df4c1d37ff77151a74b5de3d119c7e03f5db85f4) - REGEXP_EXTRACT position arg overflow *(PR [#6458](https://github.com/tobymao/sqlglot/pull/6458) by [@treysp](https://github.com/treysp))*\n  - :arrow_lower_right: *fixes issue [#6442](https://github.com/tobymao/sqlglot/issues/6442) opened by [@erindru](https://github.com/erindru)*\n- [`5a49c3f`](https://github.com/tobymao/sqlglot/commit/5a49c3f7a7619ad9e711ff2cd9e85b8606969b36) - **optimizer**: support ORDER / LIMIT expressions for BigQuery ARRAY_AGG / STRING_AGG functions *(PR [#6463](https://github.com/tobymao/sqlglot/pull/6463) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`1b6076b`](https://github.com/tobymao/sqlglot/commit/1b6076bd5a64b044f52f5366244ba0746aca75e1) - wrap connectives generated due to transpiling LIKE ANY closes [#6493](https://github.com/tobymao/sqlglot/pull/6493) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`36ad534`](https://github.com/tobymao/sqlglot/commit/36ad534b14eabe9ee197017f5087e8e5190f8526) - **exasol**: qualified select list with \"LOCAL\" *(PR [#6450](https://github.com/tobymao/sqlglot/pull/6450) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`52aceaa`](https://github.com/tobymao/sqlglot/commit/52aceaaa887dddb35f8ede5c2d9577fdeee35c48) - **optimizer**: annotate `HavingMax` by `this` *(PR [#6499](https://github.com/tobymao/sqlglot/pull/6499) by [@georgesittas](https://github.com/georgesittas))*\n- [`ce5487e`](https://github.com/tobymao/sqlglot/commit/ce5487ef2ec0a3de8fa79b9febf41236c05c04cc) - sources doesn't store columns, clean up this old code *(commit by [@tobymao](https://github.com/tobymao))*\n- [`3224235`](https://github.com/tobymao/sqlglot/commit/3224235c1b7a80511af11f7dbffe608a747a3df0) - make CTE builder produce AST consistent with parser closes [#6503](https://github.com/tobymao/sqlglot/pull/6503) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`9454a18`](https://github.com/tobymao/sqlglot/commit/9454a18cca41a510e61522f6b785d646980e2100) - uppercase join method, side, kind for consistency fixes [#6510](https://github.com/tobymao/sqlglot/pull/6510) *(PR [#6511](https://github.com/tobymao/sqlglot/pull/6511) by [@georgesittas](https://github.com/georgesittas))*\n- [`a6ec4b6`](https://github.com/tobymao/sqlglot/commit/a6ec4b688891691b26ab874a3401e370c0b8d574) - reorder join mark check in eliminate_join_marks *(PR [#6528](https://github.com/tobymao/sqlglot/pull/6528) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#6527](https://github.com/tobymao/sqlglot/issues/6527) opened by [@snovik75](https://github.com/snovik75)*\n- [`9d06859`](https://github.com/tobymao/sqlglot/commit/9d0685923209c04747fa6fa2b35ee2e516453abc) - **optimizer**: annotate bigquery ARRAY when arg contains set operations *(PR [#6517](https://github.com/tobymao/sqlglot/pull/6517) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`241073d`](https://github.com/tobymao/sqlglot/commit/241073d886e0b4ad7b2252a8c8c394e717ef700a) - on_qualify type *(commit by [@tobymao](https://github.com/tobymao))*\n- [`2fd14ed`](https://github.com/tobymao/sqlglot/commit/2fd14ed32b3793444405005fb98342222b4d7956) - **optimizer**: query schema directly when type annotation fails for processing UNNEST source *(PR [#6451](https://github.com/tobymao/sqlglot/pull/6451) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n\n### :recycle: Refactors\n- [`2d380e7`](https://github.com/tobymao/sqlglot/commit/2d380e72c9e3b842a8fe57c191f494c8872c00ee) - add test to make sure callback doesn't trigger ctes *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`4a061e2`](https://github.com/tobymao/sqlglot/commit/4a061e26b638c9acb0c8a77d9347914b35082bb3) - **optimizer**: Include BIGDECIMAL in numeric precedence *(PR [#6456](https://github.com/tobymao/sqlglot/pull/6456) by [@vchan](https://github.com/vchan))*\n- [`f305305`](https://github.com/tobymao/sqlglot/commit/f305305e5cf3ef45afba822542aebeb944c00e0b) - **optimizer**: Annotate types for BigQuery's AVG function *(PR [#6459](https://github.com/tobymao/sqlglot/pull/6459) by [@vchan](https://github.com/vchan))*\n- [`910349f`](https://github.com/tobymao/sqlglot/commit/910349f3c30af59ce1820e48cae0cbb77539877d) - **optimizer**: Annotate types for BigQuery's SAFE_DIVIDE function *(PR [#6464](https://github.com/tobymao/sqlglot/pull/6464) by [@vchan](https://github.com/vchan))*\n- [`5e75621`](https://github.com/tobymao/sqlglot/commit/5e75621e90defd50076383485f6a4689a8c551ac) - **optimizer**: annotate type for snowflake func ARRAY_UNIQUE_AGG *(PR [#6465](https://github.com/tobymao/sqlglot/pull/6465) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`4d77500`](https://github.com/tobymao/sqlglot/commit/4d775007d2ceb997ff33721def768493c95f98a5) - **optimizer**: add tests for snowflake CAST function *(PR [#6471](https://github.com/tobymao/sqlglot/pull/6471) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`88dfd26`](https://github.com/tobymao/sqlglot/commit/88dfd26b832d13e517fe7c18d2c086885bf4954d) - **optimizer**: annotate type for snowflake func TO_BINARY *(PR [#6474](https://github.com/tobymao/sqlglot/pull/6474) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`483318b`](https://github.com/tobymao/sqlglot/commit/483318bc25e4ee4fa2731be1a0aea02858872ab5) - clean up TO_BINARY tests *(commit by [@geooo109](https://github.com/geooo109))*\n- [`bff7084`](https://github.com/tobymao/sqlglot/commit/bff70841d0bfbede6ea0fae2e7b37d68735a53d8) - remove duckdb TO_BINARY 2 arg test *(commit by [@geooo109](https://github.com/geooo109))*\n- [`80591f9`](https://github.com/tobymao/sqlglot/commit/80591f9513dff9160884e4bbbd48d9c26cf8f253) - starrocks TO_BINARY tests *(commit by [@geooo109](https://github.com/geooo109))*\n- [`01e5a05`](https://github.com/tobymao/sqlglot/commit/01e5a050c76f728ef542f0127209e2cd1c5f5558) - **exasol**: implementing the last day function in exasol sql dialect *(PR [#6483](https://github.com/tobymao/sqlglot/pull/6483) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`aacc981`](https://github.com/tobymao/sqlglot/commit/aacc98105fb381c17a80ee011f107157279312d7) - **duckdb**: tests for MAX_BY and MIN_BY *(PR [#6489](https://github.com/tobymao/sqlglot/pull/6489) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`06c7ffb`](https://github.com/tobymao/sqlglot/commit/06c7ffbe14985a4da35a97d47322021e79525adf) - cleanup bitwise operator fixes *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v28.1.0] - 2025-12-02\n### :boom: BREAKING CHANGES\n- due to [`e4d1a4f`](https://github.com/tobymao/sqlglot/commit/e4d1a4fcd6741d679c5444bf023077d2aaa8f980) - map date/timestamp `TRUNC` to `DATE_TRUNC` *(PR [#6328](https://github.com/tobymao/sqlglot/pull/6328) by [@nnamdi16](https://github.com/nnamdi16))*:\n\n  map date/timestamp `TRUNC` to `DATE_TRUNC` (#6328)\n\n- due to [`e1b6558`](https://github.com/tobymao/sqlglot/commit/e1b6558cb1a860bbd695f25b66e52064b57c0a84) - handle all datepart alternatives *(PR [#6324](https://github.com/tobymao/sqlglot/pull/6324) by [@lBilali](https://github.com/lBilali))*:\n\n  handle all datepart alternatives (#6324)\n\n- due to [`06daa47`](https://github.com/tobymao/sqlglot/commit/06daa47dedebac672548e1db230b89f5c9eae84e) - update annotated type of ARRAY_AGG to untyped array *(PR [#6347](https://github.com/tobymao/sqlglot/pull/6347) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  update annotated type of ARRAY_AGG to untyped array (#6347)\n\n- due to [`7484c06`](https://github.com/tobymao/sqlglot/commit/7484c06be4534cd22dee14da542d5e29ff2c13a2) - Support rounding mode argument for ROUND function *(PR [#6350](https://github.com/tobymao/sqlglot/pull/6350) by [@vchan](https://github.com/vchan))*:\n\n  Support rounding mode argument for ROUND function (#6350)\n\n- due to [`c495a40`](https://github.com/tobymao/sqlglot/commit/c495a40ee4c1a69b14892e8455ae1bd2ceb5ea4f) - annotate type for MINHASH *(PR [#6355](https://github.com/tobymao/sqlglot/pull/6355) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for MINHASH (#6355)\n\n- due to [`b1f9a97`](https://github.com/tobymao/sqlglot/commit/b1f9a976be3c0bcd895bef5bcdb95a013eeb28b7) - annotate type for APPROXIMATE_SIMILARITY *(PR [#6360](https://github.com/tobymao/sqlglot/pull/6360) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for APPROXIMATE_SIMILARITY (#6360)\n\n- due to [`3aafca7`](https://github.com/tobymao/sqlglot/commit/3aafca74546b932cea93ed830c021f347ae03ded) - annotate type for MINHASH_COMBINE *(PR [#6362](https://github.com/tobymao/sqlglot/pull/6362) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for MINHASH_COMBINE (#6362)\n\n- due to [`df13a65`](https://github.com/tobymao/sqlglot/commit/df13a655646bd2ef5d8b4613670bb5fe48845b73) - unnest deep stuff *(PR [#6366](https://github.com/tobymao/sqlglot/pull/6366) by [@tobymao](https://github.com/tobymao))*:\n\n  unnest deep stuff (#6366)\n\n- due to [`d4c2256`](https://github.com/tobymao/sqlglot/commit/d4c2256fb493ed2f16c29694ae5c31517123d419) - at time zone precedence *(PR [#6383](https://github.com/tobymao/sqlglot/pull/6383) by [@geooo109](https://github.com/geooo109))*:\n\n  at time zone precedence (#6383)\n\n- due to [`4fb4d08`](https://github.com/tobymao/sqlglot/commit/4fb4d08ef8896bda434d4f89c21c669c6146fd02) - properly support table alias in the `INSERT` DML *(PR [#6374](https://github.com/tobymao/sqlglot/pull/6374) by [@snovik75](https://github.com/snovik75))*:\n\n  properly support table alias in the `INSERT` DML (#6374)\n\n- due to [`bf07abd`](https://github.com/tobymao/sqlglot/commit/bf07abd4ee9eb0f5510cb7d1f232bdcaea88941e) - annotation support for  APPROX_TOP_K_COMBINE  *(PR [#6378](https://github.com/tobymao/sqlglot/pull/6378) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for  APPROX_TOP_K_COMBINE  (#6378)\n\n- due to [`50348ac`](https://github.com/tobymao/sqlglot/commit/50348ac31f784aa97bd09d5d6c6613fbd68402ee) - support order by clause for mysql delete statement *(PR [#6381](https://github.com/tobymao/sqlglot/pull/6381) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support order by clause for mysql delete statement (#6381)\n\n- due to [`21d3859`](https://github.com/tobymao/sqlglot/commit/21d38590fec6cb55a1a03aeb2621bd9fca677496) - Disable STRING_AGG sep canonicalization *(PR [#6395](https://github.com/tobymao/sqlglot/pull/6395) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Disable STRING_AGG sep canonicalization (#6395)\n\n- due to [`95727f6`](https://github.com/tobymao/sqlglot/commit/95727f60d601796b34c850dee9366d79f6e4a24b) - canonicalize table aliases *(PR [#6369](https://github.com/tobymao/sqlglot/pull/6369) by [@georgesittas](https://github.com/georgesittas))*:\n\n  canonicalize table aliases (#6369)\n\n- due to [`c7cb098`](https://github.com/tobymao/sqlglot/commit/c7cb0983a0fa463c43d2c4ee925816e9a1628c79) - Fix underscore separator with scientific notation *(PR [#6401](https://github.com/tobymao/sqlglot/pull/6401) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix underscore separator with scientific notation (#6401)\n\n- due to [`bb4eda1`](https://github.com/tobymao/sqlglot/commit/bb4eda1beb68b92de9ab014a63c67797a07df2fa) - support transpiling SHA1 from BigQuery to DuckDB *(PR [#6404](https://github.com/tobymao/sqlglot/pull/6404) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpiling SHA1 from BigQuery to DuckDB (#6404)\n\n- due to [`d038ad7`](https://github.com/tobymao/sqlglot/commit/d038ad7f036a140f3eae4bdde15824437d4e44ee) - support named primary keys for mysql *(PR [#6389](https://github.com/tobymao/sqlglot/pull/6389) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support named primary keys for mysql (#6389)\n\n- due to [`05e83b5`](https://github.com/tobymao/sqlglot/commit/05e83b56f1bf9323cfa819a7f1beb542524c1219) - support transpilation of LEAST from BigQuery to DuckDB *(PR [#6415](https://github.com/tobymao/sqlglot/pull/6415) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of LEAST from BigQuery to DuckDB (#6415)\n\n- due to [`4f3bb0d`](https://github.com/tobymao/sqlglot/commit/4f3bb0d6714bf89ff72e13e1398d8f01cefafb00) - Correct transpilation of BigQuery's JSON_EXTRACT_SCALAR… *(PR [#6414](https://github.com/tobymao/sqlglot/pull/6414) by [@vchan](https://github.com/vchan))*:\n\n  Correct transpilation of BigQuery's JSON_EXTRACT_SCALAR… (#6414)\n\n- due to [`8c314a8`](https://github.com/tobymao/sqlglot/commit/8c314a8b457a5c3ed470ac8fcff022fec881c248) - support cte pivot for duckdb *(PR [#6413](https://github.com/tobymao/sqlglot/pull/6413) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  support cte pivot for duckdb (#6413)\n\n- due to [`c6b0a63`](https://github.com/tobymao/sqlglot/commit/c6b0a6342a21d79635a26d40001c916d05d47cf7) - change version to be a tuple so that it can be pickled, also simpler *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  change version to be a tuple so that it can be pickled, also simpler\n\n- due to [`07d9958`](https://github.com/tobymao/sqlglot/commit/07d99583b4aebdc682bb7604ccdf45bddb89f9c3) - replace direct comparison with dialect properties *(PR [#6398](https://github.com/tobymao/sqlglot/pull/6398) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  replace direct comparison with dialect properties (#6398)\n\n- due to [`38472ce`](https://github.com/tobymao/sqlglot/commit/38472ce14bce731ba4c309d515223ae99e2575ac) - transpile bigquery's %x format literal *(PR [#6375](https://github.com/tobymao/sqlglot/pull/6375) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  transpile bigquery's %x format literal (#6375)\n\n- due to [`92ee124`](https://github.com/tobymao/sqlglot/commit/92ee1241ea3088d4e63c094404252339c54ad0c1) - postgres qualify GENERATE_SERIES and table projection *(PR [#6373](https://github.com/tobymao/sqlglot/pull/6373) by [@geooo109](https://github.com/geooo109))*:\n\n  postgres qualify GENERATE_SERIES and table projection (#6373)\n\n- due to [`0b9d8ac`](https://github.com/tobymao/sqlglot/commit/0b9d8acbe75457424436e8c0acc047ab66e9fdc0) - Annotate type for snowflake MAX function *(PR [#6422](https://github.com/tobymao/sqlglot/pull/6422) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake MAX function (#6422)\n\n- due to [`68e9414`](https://github.com/tobymao/sqlglot/commit/68e9414725a60b2842d870fa222d8466057a94f6) - Annotate type for snowflake MIN function *(PR [#6427](https://github.com/tobymao/sqlglot/pull/6427) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  Annotate type for snowflake MIN function (#6427)\n\n- due to [`1318de7`](https://github.com/tobymao/sqlglot/commit/1318de77a8aa514ec7eb9f9b8c03228e3f8eb008) - Annotate type for snowflake NORMAL *(PR [#6434](https://github.com/tobymao/sqlglot/pull/6434) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake NORMAL (#6434)\n\n- due to [`ffbb5c7`](https://github.com/tobymao/sqlglot/commit/ffbb5c7e40aa064ffcd4827e96ea66cfd045118e) - annotate type for HASH_AGG in Snowflake *(PR [#6438](https://github.com/tobymao/sqlglot/pull/6438) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate type for HASH_AGG in Snowflake (#6438)\n\n- due to [`161255f`](https://github.com/tobymao/sqlglot/commit/161255f6c90b9c3ed2074e734f6d074db1d7a6dd) - Add support for `LOCALTIME` function *(PR [#6443](https://github.com/tobymao/sqlglot/pull/6443) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for `LOCALTIME` function (#6443)\n\n- due to [`ca329f0`](https://github.com/tobymao/sqlglot/commit/ca329f037a230c315437d830638b514190764c5a) - support transpilation of SHA256 from bigquery to duckdb *(PR [#6421](https://github.com/tobymao/sqlglot/pull/6421) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  support transpilation of SHA256 from bigquery to duckdb (#6421)\n\n- due to [`e18ae24`](https://github.com/tobymao/sqlglot/commit/e18ae248423dbbca78a24a60ea0193da2ee7f68c) - Annotate type for snowflake REGR_SLOPE function *(PR [#6425](https://github.com/tobymao/sqlglot/pull/6425) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake REGR_SLOPE function (#6425)\n\n- due to [`1d847f0`](https://github.com/tobymao/sqlglot/commit/1d847f0a1f88fce5df340ab646a72c8abbc12a86) - parse & annotate `CHECK_JSON`, `CHECK_XML` *(PR [#6439](https://github.com/tobymao/sqlglot/pull/6439) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  parse & annotate `CHECK_JSON`, `CHECK_XML` (#6439)\n\n- due to [`cb3080d`](https://github.com/tobymao/sqlglot/commit/cb3080d4bed18b1bfbbd08380ed60deeefd15530) - annotation support for APPROX_TOP_K_ESTIMATE . Return type ARRAY *(PR [#6445](https://github.com/tobymao/sqlglot/pull/6445) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotation support for APPROX_TOP_K_ESTIMATE . Return type ARRAY (#6445)\n\n- due to [`313afe5`](https://github.com/tobymao/sqlglot/commit/313afe540aa2cdc4cc179c4852c6ef37362bcb3e) - annotate type for snowflake func ARRAY_UNION_AGG *(PR [#6446](https://github.com/tobymao/sqlglot/pull/6446) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for snowflake func ARRAY_UNION_AGG (#6446)\n\n- due to [`cd9f037`](https://github.com/tobymao/sqlglot/commit/cd9f037882eef253e86fdb1d51521e0acd7db3f9) - store pk name if provided *(PR [#6424](https://github.com/tobymao/sqlglot/pull/6424) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  store pk name if provided (#6424)\n\n- due to [`65194e4`](https://github.com/tobymao/sqlglot/commit/65194e465489151aa51859a6e3f5672f7d4c5f3b) - Annotate type for snowflake RANDSTR function *(PR [#6436](https://github.com/tobymao/sqlglot/pull/6436) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake RANDSTR function (#6436)\n\n- due to [`a56262e`](https://github.com/tobymao/sqlglot/commit/a56262e6b4276baae144855478807c173db77ab9) - Annotate type for snowflake MEDIAN *(PR [#6426](https://github.com/tobymao/sqlglot/pull/6426) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  Annotate type for snowflake MEDIAN (#6426)\n\n- due to [`2c56567`](https://github.com/tobymao/sqlglot/commit/2c56567755c8a6571d8b7d410c9de943e54df58b) - Annotate type for snowflake SEARCH_IP  *(PR [#6440](https://github.com/tobymao/sqlglot/pull/6440) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Annotate type for snowflake SEARCH_IP  (#6440)\n\n- due to [`ac86568`](https://github.com/tobymao/sqlglot/commit/ac86568a939f692b99813da100297b61fb54e044) - Added decfloat type *(PR [#6444](https://github.com/tobymao/sqlglot/pull/6444) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*:\n\n  Added decfloat type (#6444)\n\n- due to [`b321ca6`](https://github.com/tobymao/sqlglot/commit/b321ca6191fefc88da1a6de83a465886b5754b7a) - bump sqlglotrs to 0.8.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.8.0\n\n\n### :sparkles: New Features\n- [`ca81217`](https://github.com/tobymao/sqlglot/commit/ca812171ab800e3faa73ea1874dd6814c8d6f701) - **duckdb**: Transpile INITCAP with custom delimiters *(PR [#6302](https://github.com/tobymao/sqlglot/pull/6302) by [@treysp](https://github.com/treysp))*\n- [`7484c06`](https://github.com/tobymao/sqlglot/commit/7484c06be4534cd22dee14da542d5e29ff2c13a2) - **DuckDB**: Support rounding mode argument for ROUND function *(PR [#6350](https://github.com/tobymao/sqlglot/pull/6350) by [@vchan](https://github.com/vchan))*\n- [`79e314d`](https://github.com/tobymao/sqlglot/commit/79e314df76161319ba8495b95f54603cfef0c08a) - **duckdb**: handle casting BLOB input for TRIM() *(PR [#6353](https://github.com/tobymao/sqlglot/pull/6353) by [@toriwei](https://github.com/toriwei))*\n- [`c495a40`](https://github.com/tobymao/sqlglot/commit/c495a40ee4c1a69b14892e8455ae1bd2ceb5ea4f) - **optimizer**: annotate type for MINHASH *(PR [#6355](https://github.com/tobymao/sqlglot/pull/6355) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`f16f8a0`](https://github.com/tobymao/sqlglot/commit/f16f8a08072556fd617b5125300262d9bb8c1e48) - improve validate qualify column message closes [#6348](https://github.com/tobymao/sqlglot/pull/6348) *(PR [#6356](https://github.com/tobymao/sqlglot/pull/6356) by [@tobymao](https://github.com/tobymao))*\n- [`17abe23`](https://github.com/tobymao/sqlglot/commit/17abe231bc4d59912952f266ad4df86ece22c8d2) - make simplify more efficient in number of iterations *(PR [#6351](https://github.com/tobymao/sqlglot/pull/6351) by [@tobymao](https://github.com/tobymao))*\n- [`b1f9a97`](https://github.com/tobymao/sqlglot/commit/b1f9a976be3c0bcd895bef5bcdb95a013eeb28b7) - **optimizer**: annotate type for APPROXIMATE_SIMILARITY *(PR [#6360](https://github.com/tobymao/sqlglot/pull/6360) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`3aafca7`](https://github.com/tobymao/sqlglot/commit/3aafca74546b932cea93ed830c021f347ae03ded) - **optimizer**: annotate type for MINHASH_COMBINE *(PR [#6362](https://github.com/tobymao/sqlglot/pull/6362) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`09a4bd8`](https://github.com/tobymao/sqlglot/commit/09a4bd8870a075e641c6e3e4cee74d73a39e760a) - Trigger integration tests *(PR [#6339](https://github.com/tobymao/sqlglot/pull/6339) by [@erindru](https://github.com/erindru))*\n- [`7769129`](https://github.com/tobymao/sqlglot/commit/7769129eba7ae5f3594e0061bdb1079fedc5aafd) - bignum and time_ns to duckdb closes [#6379](https://github.com/tobymao/sqlglot/pull/6379) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`90a3fa9`](https://github.com/tobymao/sqlglot/commit/90a3fa9f6ddf0aa32b41118c59d4facd9fdb3398) - mark IgnoreNulls and RespectNulls as unsupported on postgres and mysql *(PR [#6377](https://github.com/tobymao/sqlglot/pull/6377) by [@NickCrews](https://github.com/NickCrews))*\n  - :arrow_lower_right: *addresses issue [#6376](https://github.com/tobymao/sqlglot/issues/6376) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`5bb1170`](https://github.com/tobymao/sqlglot/commit/5bb117082caeee719442d783ce6742d027b1492e) - transpile bigquery `greatest` null handling to duckdb *(PR [#6361](https://github.com/tobymao/sqlglot/pull/6361) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`bf07abd`](https://github.com/tobymao/sqlglot/commit/bf07abd4ee9eb0f5510cb7d1f232bdcaea88941e) - **snowflake**: annotation support for  APPROX_TOP_K_COMBINE  *(PR [#6378](https://github.com/tobymao/sqlglot/pull/6378) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`01890eb`](https://github.com/tobymao/sqlglot/commit/01890eb16d6624de4f26b7d8eadf850df6f2a042) - **trino**: support refresh materialized view statement closes [#6387](https://github.com/tobymao/sqlglot/pull/6387) *(PR [#6388](https://github.com/tobymao/sqlglot/pull/6388) by [@georgesittas](https://github.com/georgesittas))*\n- [`e4ea6cc`](https://github.com/tobymao/sqlglot/commit/e4ea6ccf08c0ff4063424bf538bc3b22f4b4cfaf) - transpile BQ APPROX_QUANTILES to DuckDB *(PR [#6349](https://github.com/tobymao/sqlglot/pull/6349) by [@treysp](https://github.com/treysp))*\n- [`95727f6`](https://github.com/tobymao/sqlglot/commit/95727f60d601796b34c850dee9366d79f6e4a24b) - **optimizer**: canonicalize table aliases *(PR [#6369](https://github.com/tobymao/sqlglot/pull/6369) by [@georgesittas](https://github.com/georgesittas))*\n- [`3b6855b`](https://github.com/tobymao/sqlglot/commit/3b6855b9787111f27225108241fbe4f389443e29) - **mysql**: support ZEROFILL column attribute *(PR [#6400](https://github.com/tobymao/sqlglot/pull/6400) by [@nian0114](https://github.com/nian0114))*\n  - :arrow_lower_right: *addresses issue [#6399](https://github.com/tobymao/sqlglot/issues/6399) opened by [@nian0114](https://github.com/nian0114)*\n- [`bb4eda1`](https://github.com/tobymao/sqlglot/commit/bb4eda1beb68b92de9ab014a63c67797a07df2fa) - **duckdb**: support transpiling SHA1 from BigQuery to DuckDB *(PR [#6404](https://github.com/tobymao/sqlglot/pull/6404) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`05e83b5`](https://github.com/tobymao/sqlglot/commit/05e83b56f1bf9323cfa819a7f1beb542524c1219) - **duckdb**: support transpilation of LEAST from BigQuery to DuckDB *(PR [#6415](https://github.com/tobymao/sqlglot/pull/6415) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`38472ce`](https://github.com/tobymao/sqlglot/commit/38472ce14bce731ba4c309d515223ae99e2575ac) - **duckdb**: transpile bigquery's %x format literal *(PR [#6375](https://github.com/tobymao/sqlglot/pull/6375) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`a6e1581`](https://github.com/tobymao/sqlglot/commit/a6e15811cf5643bcc18e1e227fea20922b05c54a) - **DuckDB**: Cast BIGNUMERIC and BIGDECIMAL types to DECIMAL(38, 5) *(PR [#6419](https://github.com/tobymao/sqlglot/pull/6419) by [@vchan](https://github.com/vchan))*\n- [`0b9d8ac`](https://github.com/tobymao/sqlglot/commit/0b9d8acbe75457424436e8c0acc047ab66e9fdc0) - **snowflake**: Annotate type for snowflake MAX function *(PR [#6422](https://github.com/tobymao/sqlglot/pull/6422) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`68e9414`](https://github.com/tobymao/sqlglot/commit/68e9414725a60b2842d870fa222d8466057a94f6) - **snowflake**: Annotate type for snowflake MIN function *(PR [#6427](https://github.com/tobymao/sqlglot/pull/6427) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`1318de7`](https://github.com/tobymao/sqlglot/commit/1318de77a8aa514ec7eb9f9b8c03228e3f8eb008) - **snowflake**: Annotate type for snowflake NORMAL *(PR [#6434](https://github.com/tobymao/sqlglot/pull/6434) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ffbb5c7`](https://github.com/tobymao/sqlglot/commit/ffbb5c7e40aa064ffcd4827e96ea66cfd045118e) - **snowflake**: annotate type for HASH_AGG in Snowflake *(PR [#6438](https://github.com/tobymao/sqlglot/pull/6438) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`161255f`](https://github.com/tobymao/sqlglot/commit/161255f6c90b9c3ed2074e734f6d074db1d7a6dd) - Add support for `LOCALTIME` function *(PR [#6443](https://github.com/tobymao/sqlglot/pull/6443) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ca329f0`](https://github.com/tobymao/sqlglot/commit/ca329f037a230c315437d830638b514190764c5a) - **duckdb**: support transpilation of SHA256 from bigquery to duckdb *(PR [#6421](https://github.com/tobymao/sqlglot/pull/6421) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`e18ae24`](https://github.com/tobymao/sqlglot/commit/e18ae248423dbbca78a24a60ea0193da2ee7f68c) - **snowflake**: Annotate type for snowflake REGR_SLOPE function *(PR [#6425](https://github.com/tobymao/sqlglot/pull/6425) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`1d847f0`](https://github.com/tobymao/sqlglot/commit/1d847f0a1f88fce5df340ab646a72c8abbc12a86) - **snowflake**: parse & annotate `CHECK_JSON`, `CHECK_XML` *(PR [#6439](https://github.com/tobymao/sqlglot/pull/6439) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`6843812`](https://github.com/tobymao/sqlglot/commit/68438129ceeea70f801e0ae728c51c19291fc7d8) - add correlation id to remote workflow trigger *(PR [#6441](https://github.com/tobymao/sqlglot/pull/6441) by [@erindru](https://github.com/erindru))*\n- [`cb3080d`](https://github.com/tobymao/sqlglot/commit/cb3080d4bed18b1bfbbd08380ed60deeefd15530) - **snowflake**: annotation support for APPROX_TOP_K_ESTIMATE . Return type ARRAY *(PR [#6445](https://github.com/tobymao/sqlglot/pull/6445) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`cd9f037`](https://github.com/tobymao/sqlglot/commit/cd9f037882eef253e86fdb1d51521e0acd7db3f9) - **optimizer**: store pk name if provided *(PR [#6424](https://github.com/tobymao/sqlglot/pull/6424) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`65194e4`](https://github.com/tobymao/sqlglot/commit/65194e465489151aa51859a6e3f5672f7d4c5f3b) - **snowflake**: Annotate type for snowflake RANDSTR function *(PR [#6436](https://github.com/tobymao/sqlglot/pull/6436) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`351d783`](https://github.com/tobymao/sqlglot/commit/351d7834915e02a9f4949f9925437e2731f3a8b4) - add support for LOCALTIMESTAMP *(PR [#6448](https://github.com/tobymao/sqlglot/pull/6448) by [@AbhishekASLK](https://github.com/AbhishekASLK))*\n- [`a56262e`](https://github.com/tobymao/sqlglot/commit/a56262e6b4276baae144855478807c173db77ab9) - **snowflake**: Annotate type for snowflake MEDIAN *(PR [#6426](https://github.com/tobymao/sqlglot/pull/6426) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`2c56567`](https://github.com/tobymao/sqlglot/commit/2c56567755c8a6571d8b7d410c9de943e54df58b) - **snowflake**: Annotate type for snowflake SEARCH_IP  *(PR [#6440](https://github.com/tobymao/sqlglot/pull/6440) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n- [`ac86568`](https://github.com/tobymao/sqlglot/commit/ac86568a939f692b99813da100297b61fb54e044) - **snowflake**: Added decfloat type *(PR [#6444](https://github.com/tobymao/sqlglot/pull/6444) by [@fivetran-kwoodbeck](https://github.com/fivetran-kwoodbeck))*\n\n### :bug: Bug Fixes\n- [`0f79f2a`](https://github.com/tobymao/sqlglot/commit/0f79f2a55c4ba14d4a5fcfd01a0a727271992b8c) - **snowflake**: MAX_BY and MIN_BY with count should return plain `ARRAY` *(PR [#6343](https://github.com/tobymao/sqlglot/pull/6343) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`e1b6558`](https://github.com/tobymao/sqlglot/commit/e1b6558cb1a860bbd695f25b66e52064b57c0a84) - **tsql**: handle all datepart alternatives *(PR [#6324](https://github.com/tobymao/sqlglot/pull/6324) by [@lBilali](https://github.com/lBilali))*\n- [`06daa47`](https://github.com/tobymao/sqlglot/commit/06daa47dedebac672548e1db230b89f5c9eae84e) - **optimizer**: update annotated type of ARRAY_AGG to untyped array *(PR [#6347](https://github.com/tobymao/sqlglot/pull/6347) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`826db4d`](https://github.com/tobymao/sqlglot/commit/826db4d3c413941e3b0b31e1f907fabd017bd461) - **redshift**: properly parse default IAM_ROLE and AVRO/JSON formats in COPY *(PR [#6346](https://github.com/tobymao/sqlglot/pull/6346) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6345](https://github.com/tobymao/sqlglot/issues/6345) opened by [@zachary-povey](https://github.com/zachary-povey)*\n- [`c367bac`](https://github.com/tobymao/sqlglot/commit/c367bac878a3c17773009b54b9836e7b9a5b84fe) - **duckdb**: Support update without set in DuckDB merge when matched *(PR [#6357](https://github.com/tobymao/sqlglot/pull/6357) by [@themisvaltinos](https://github.com/themisvaltinos))*\n- [`df13a65`](https://github.com/tobymao/sqlglot/commit/df13a655646bd2ef5d8b4613670bb5fe48845b73) - unnest deep stuff *(PR [#6366](https://github.com/tobymao/sqlglot/pull/6366) by [@tobymao](https://github.com/tobymao))*\n- [`20e33fd`](https://github.com/tobymao/sqlglot/commit/20e33fd0d1bc1899727d023411e604f1ea9347b8) - **duckdb**: regexp_extract_all closes [#6380](https://github.com/tobymao/sqlglot/pull/6380) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d4c2256`](https://github.com/tobymao/sqlglot/commit/d4c2256fb493ed2f16c29694ae5c31517123d419) - **parser**: at time zone precedence *(PR [#6383](https://github.com/tobymao/sqlglot/pull/6383) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6359](https://github.com/tobymao/sqlglot/issues/6359) opened by [@parth-wisdom](https://github.com/parth-wisdom)*\n- [`4fb4d08`](https://github.com/tobymao/sqlglot/commit/4fb4d08ef8896bda434d4f89c21c669c6146fd02) - **oracle**: properly support table alias in the `INSERT` DML *(PR [#6374](https://github.com/tobymao/sqlglot/pull/6374) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#6371](https://github.com/tobymao/sqlglot/issues/6371) opened by [@snovik75](https://github.com/snovik75)*\n- [`2169f5b`](https://github.com/tobymao/sqlglot/commit/2169f5b8f30b6c8be1635bb5648a1abf636e49a6) - **parser**: support SET with := *(PR [#6385](https://github.com/tobymao/sqlglot/pull/6385) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6384](https://github.com/tobymao/sqlglot/issues/6384) opened by [@AndyVW77](https://github.com/AndyVW77)*\n- [`50348ac`](https://github.com/tobymao/sqlglot/commit/50348ac31f784aa97bd09d5d6c6613fbd68402ee) - **mysql**: support order by clause for mysql delete statement *(PR [#6381](https://github.com/tobymao/sqlglot/pull/6381) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n  - :arrow_lower_right: *fixes issue [#6372](https://github.com/tobymao/sqlglot/issues/6372) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`21d3859`](https://github.com/tobymao/sqlglot/commit/21d38590fec6cb55a1a03aeb2621bd9fca677496) - **bigquery**: Disable STRING_AGG sep canonicalization *(PR [#6395](https://github.com/tobymao/sqlglot/pull/6395) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6392](https://github.com/tobymao/sqlglot/issues/6392) opened by [@erindru](https://github.com/erindru)*\n- [`67f499d`](https://github.com/tobymao/sqlglot/commit/67f499dd497efdf4f3fc49dd75e49a77e036ee63) - **duckdb**: Make exp.DateFromParts more lenient *(PR [#6397](https://github.com/tobymao/sqlglot/pull/6397) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6394](https://github.com/tobymao/sqlglot/issues/6394) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`39f8c37`](https://github.com/tobymao/sqlglot/commit/39f8c37aca755d97e1e41f232042d1c649e58908) - **parser**: support FROM-syntax with joins *(PR [#6402](https://github.com/tobymao/sqlglot/pull/6402) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6396](https://github.com/tobymao/sqlglot/issues/6396) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`9ddae4d`](https://github.com/tobymao/sqlglot/commit/9ddae4d56d1e3a15fc3b4b76ce3b3040683c220f) - **duckdb**: support IN with no paren *(PR [#6409](https://github.com/tobymao/sqlglot/pull/6409) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6407](https://github.com/tobymao/sqlglot/issues/6407) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`c7cb098`](https://github.com/tobymao/sqlglot/commit/c7cb0983a0fa463c43d2c4ee925816e9a1628c79) - **tokenizer**: Fix underscore separator with scientific notation *(PR [#6401](https://github.com/tobymao/sqlglot/pull/6401) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6393](https://github.com/tobymao/sqlglot/issues/6393) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`f5635d2`](https://github.com/tobymao/sqlglot/commit/f5635d2cc2a5612d6403bbf508b545f2a4e8f773) - **duckdb**: splice with col named after type closes [#6411](https://github.com/tobymao/sqlglot/pull/6411) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`097d865`](https://github.com/tobymao/sqlglot/commit/097d865554d9ba2e226962fa71778ae0a6c596cb) - **duckdb**: pivot using cast closes [#6410](https://github.com/tobymao/sqlglot/pull/6410) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d038ad7`](https://github.com/tobymao/sqlglot/commit/d038ad7f036a140f3eae4bdde15824437d4e44ee) - **mysql**: support named primary keys for mysql *(PR [#6389](https://github.com/tobymao/sqlglot/pull/6389) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n  - :arrow_lower_right: *fixes issue [#6382](https://github.com/tobymao/sqlglot/issues/6382) opened by [@AndyVW77](https://github.com/AndyVW77)*\n- [`4f3bb0d`](https://github.com/tobymao/sqlglot/commit/4f3bb0d6714bf89ff72e13e1398d8f01cefafb00) - **DuckDB**: Correct transpilation of BigQuery's JSON_EXTRACT_SCALAR… *(PR [#6414](https://github.com/tobymao/sqlglot/pull/6414) by [@vchan](https://github.com/vchan))*\n- [`e2f306f`](https://github.com/tobymao/sqlglot/commit/e2f306f1893a3f565cbbf7857ffd9795850aba7b) - interval column ops closes [#6416](https://github.com/tobymao/sqlglot/pull/6416) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`8c314a8`](https://github.com/tobymao/sqlglot/commit/8c314a8b457a5c3ed470ac8fcff022fec881c248) - **duckdb**: support cte pivot for duckdb *(PR [#6413](https://github.com/tobymao/sqlglot/pull/6413) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n  - :arrow_lower_right: *fixes issue [#6405](https://github.com/tobymao/sqlglot/issues/6405) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`92ee124`](https://github.com/tobymao/sqlglot/commit/92ee1241ea3088d4e63c094404252339c54ad0c1) - **optimizer**: postgres qualify GENERATE_SERIES and table projection *(PR [#6373](https://github.com/tobymao/sqlglot/pull/6373) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6358](https://github.com/tobymao/sqlglot/issues/6358) opened by [@metahexane](https://github.com/metahexane)*\n\n### :recycle: Refactors\n- [`e4d1a4f`](https://github.com/tobymao/sqlglot/commit/e4d1a4fcd6741d679c5444bf023077d2aaa8f980) - **exasol**: map date/timestamp `TRUNC` to `DATE_TRUNC` *(PR [#6328](https://github.com/tobymao/sqlglot/pull/6328) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c6b0a63`](https://github.com/tobymao/sqlglot/commit/c6b0a6342a21d79635a26d40001c916d05d47cf7) - change version to be a tuple so that it can be pickled, also simpler *(commit by [@tobymao](https://github.com/tobymao))*\n- [`625654a`](https://github.com/tobymao/sqlglot/commit/625654a9623cc5407bfde922c29f32a8ee905a3b) - move resolver to own file *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`487d218`](https://github.com/tobymao/sqlglot/commit/487d218a6fcad4e28c65c6df55435ba218826186) - iterative annotate types *(PR [#6342](https://github.com/tobymao/sqlglot/pull/6342) by [@geooo109](https://github.com/geooo109))*\n- [`8201062`](https://github.com/tobymao/sqlglot/commit/8201062ac41b85e5a89aa8e1c5973852f105c66e) - clean up derived table traversal in table qualification *(PR [#6363](https://github.com/tobymao/sqlglot/pull/6363) by [@georgesittas](https://github.com/georgesittas))*\n- [`6b7084d`](https://github.com/tobymao/sqlglot/commit/6b7084d0c9f4735432afc12509c77c286cc50513) - **optimizer**: refactor costly scope walking loop in qualify tables *(PR [#6364](https://github.com/tobymao/sqlglot/pull/6364) by [@georgesittas](https://github.com/georgesittas))*\n- [`0319241`](https://github.com/tobymao/sqlglot/commit/0319241162bbe6d278a626100eac73999b250968) - **mysql,postgres**: tests for unsupported IGNORE/RESPECT NULLS *(PR [#6386](https://github.com/tobymao/sqlglot/pull/6386) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#6376](https://github.com/tobymao/sqlglot/issues/6376) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`11354cc`](https://github.com/tobymao/sqlglot/commit/11354cc85d116cd24c28114a437111965ba828a9) - Make integration test workflow more robust *(PR [#6403](https://github.com/tobymao/sqlglot/pull/6403) by [@erindru](https://github.com/erindru))*\n- [`f758cea`](https://github.com/tobymao/sqlglot/commit/f758cea0e9fca5850895a730c554c17b488d29ca) - **exasol**: transformed rank function, ignoring parameters *(PR [#6408](https://github.com/tobymao/sqlglot/pull/6408) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`07d9958`](https://github.com/tobymao/sqlglot/commit/07d99583b4aebdc682bb7604ccdf45bddb89f9c3) - **optimizer**: replace direct comparison with dialect properties *(PR [#6398](https://github.com/tobymao/sqlglot/pull/6398) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`137549e`](https://github.com/tobymao/sqlglot/commit/137549e5e803416d46e13e9a8123cef9b53d349a) - **exasol**: transform substring_index using substr and instr *(PR [#6406](https://github.com/tobymao/sqlglot/pull/6406) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`78f1824`](https://github.com/tobymao/sqlglot/commit/78f1824c790f523845cbda488ecf4c43a92ac0f0) - **exasol**: transform substring_index using substr and instr *(PR [#6406](https://github.com/tobymao/sqlglot/pull/6406) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`39cc555`](https://github.com/tobymao/sqlglot/commit/39cc55586ed76a4a583e6db22a9ee51e09bff92e) - **snowflake**: annotate type for COUNT *(PR [#6437](https://github.com/tobymao/sqlglot/pull/6437) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`61f39ba`](https://github.com/tobymao/sqlglot/commit/61f39bab9a0668c338e8c1b5e0fa953f22c0a886) - **optimizer**: improve error message for ambiguous columns *(PR [#6423](https://github.com/tobymao/sqlglot/pull/6423) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`313afe5`](https://github.com/tobymao/sqlglot/commit/313afe540aa2cdc4cc179c4852c6ef37362bcb3e) - **optimizer**: annotate type for snowflake func ARRAY_UNION_AGG *(PR [#6446](https://github.com/tobymao/sqlglot/pull/6446) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`b321ca6`](https://github.com/tobymao/sqlglot/commit/b321ca6191fefc88da1a6de83a465886b5754b7a) - bump sqlglotrs to 0.8.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v28.0.0] - 2025-11-17\n### :boom: BREAKING CHANGES\n- due to [`39d8e19`](https://github.com/tobymao/sqlglot/commit/39d8e19419c2adbb80465be414d1cc3bbc6d007b) - include VARIABLE kind in SET transpilation to DuckDB *(PR [#6201](https://github.com/tobymao/sqlglot/pull/6201) by [@toriwei](https://github.com/toriwei))*:\n\n  include VARIABLE kind in SET transpilation to DuckDB (#6201)\n\n- due to [`e7ddad1`](https://github.com/tobymao/sqlglot/commit/e7ddad10b5edf9b801d2151e3e5fca448754df0d) - ensure `NULL` coerces into any type *(PR [#6211](https://github.com/tobymao/sqlglot/pull/6211) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  ensure `NULL` coerces into any type (#6211)\n\n- due to [`0037266`](https://github.com/tobymao/sqlglot/commit/00372664bf6acf2b0fff9ad4b206b597ef5378f7) - annotate types for GETBIT *(PR [#6219](https://github.com/tobymao/sqlglot/pull/6219) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for GETBIT (#6219)\n\n- due to [`a5458ce`](https://github.com/tobymao/sqlglot/commit/a5458ceca3bc239fb611791e38020632dd0824c8) - add type annotation for DECODE function support *(PR [#6199](https://github.com/tobymao/sqlglot/pull/6199) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for DECODE function support (#6199)\n\n- due to [`417f1e8`](https://github.com/tobymao/sqlglot/commit/417f1e8ee50fb8f4377fad261660ffbd7444a429) - annotate types for BITNOT *(PR [#6234](https://github.com/tobymao/sqlglot/pull/6234) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BITNOT (#6234)\n\n- due to [`fe8ab40`](https://github.com/tobymao/sqlglot/commit/fe8ab40e8e0559201e0b1896a6f1a8fb6b5b932d) - 1st-class parsing support for BITAND, BIT_AND, BIT_NOT *(PR [#6243](https://github.com/tobymao/sqlglot/pull/6243) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  1st-class parsing support for BITAND, BIT_AND, BIT_NOT (#6243)\n\n- due to [`5ae3c47`](https://github.com/tobymao/sqlglot/commit/5ae3c47b1c6993b87341472c08714f4a0f738168) - add type annotation for GROUPING() function *(PR [#6244](https://github.com/tobymao/sqlglot/pull/6244) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for GROUPING() function (#6244)\n\n- due to [`4133265`](https://github.com/tobymao/sqlglot/commit/413326514507ef06537dcc3d4b80a3fcbcd26f66) - parse `has` function into an `ArrayContains` expression *(PR [#6245](https://github.com/tobymao/sqlglot/pull/6245) by [@joeyutong](https://github.com/joeyutong))*:\n\n  parse `has` function into an `ArrayContains` expression (#6245)\n\n- due to [`cdd45b9`](https://github.com/tobymao/sqlglot/commit/cdd45b949fd1eefb147053424279b56b8effcbcf) - annotate types for GROUPING_ID function. *(PR [#6249](https://github.com/tobymao/sqlglot/pull/6249) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotate types for GROUPING_ID function. (#6249)\n\n- due to [`080ff3b`](https://github.com/tobymao/sqlglot/commit/080ff3bd93b36291d5bb0092d722f8307f0ae082) - annotate types for BITAND_AGG *(PR [#6248](https://github.com/tobymao/sqlglot/pull/6248) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BITAND_AGG (#6248)\n\n- due to [`87a818a`](https://github.com/tobymao/sqlglot/commit/87a818a899f61a675c22c697f468b3f6f7e2787f) - annotate types for BITOR_AGG  *(PR [#6251](https://github.com/tobymao/sqlglot/pull/6251) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BITOR_AGG  (#6251)\n\n- due to [`4c4189b`](https://github.com/tobymao/sqlglot/commit/4c4189b4083d272a6e678d83b5c567a2e9c0d672) - Transpile CONCAT function to double pipe operators when source … *(PR [#6241](https://github.com/tobymao/sqlglot/pull/6241) by [@vchan](https://github.com/vchan))*:\n\n  Transpile CONCAT function to double pipe operators when source … (#6241)\n\n- due to [`a1b884d`](https://github.com/tobymao/sqlglot/commit/a1b884dc9ddfd2185de48cc9451a39f152879d39) - annotate types for BITXOR_AGG *(PR [#6253](https://github.com/tobymao/sqlglot/pull/6253) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BITXOR_AGG (#6253)\n\n- due to [`fc78d20`](https://github.com/tobymao/sqlglot/commit/fc78d2016d8f7d20c094df791f746de323cd3639) - Unwrap subqueries without modifiers *(PR [#6247](https://github.com/tobymao/sqlglot/pull/6247) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Unwrap subqueries without modifiers (#6247)\n\n- due to [`ad2ad23`](https://github.com/tobymao/sqlglot/commit/ad2ad234b5a508040dce4f3920439be052742573) - add missing return type mapping for MAX_BY and MAX_BY function *(PR [#6250](https://github.com/tobymao/sqlglot/pull/6250) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add missing return type mapping for MAX_BY and MAX_BY function (#6250)\n\n- due to [`39c1d81`](https://github.com/tobymao/sqlglot/commit/39c1d81174f2390b6b0c9dd14c0e550ad452a1df) - annotate types for BOOLXOR_AGG *(PR [#6261](https://github.com/tobymao/sqlglot/pull/6261) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BOOLXOR_AGG (#6261)\n\n- due to [`71590d2`](https://github.com/tobymao/sqlglot/commit/71590d22cdb05594e2173a1500f763dc1a32a81d) - add type annotation for SKEW function. *(PR [#6262](https://github.com/tobymao/sqlglot/pull/6262) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for SKEW function. (#6262)\n\n- due to [`5fd366d`](https://github.com/tobymao/sqlglot/commit/5fd366d9e6f7b3f1eb7a9cf41975cf13ce890ffe) - annotate types for OBJECT_AGG *(PR [#6265](https://github.com/tobymao/sqlglot/pull/6265) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for OBJECT_AGG (#6265)\n\n- due to [`00abc39`](https://github.com/tobymao/sqlglot/commit/00abc393c9042e839457c5a6582e95cdb74356f3) - handle casting for bytestrings  *(PR [#6252](https://github.com/tobymao/sqlglot/pull/6252) by [@toriwei](https://github.com/toriwei))*:\n\n  handle casting for bytestrings  (#6252)\n\n- due to [`3dae0fb`](https://github.com/tobymao/sqlglot/commit/3dae0fbb528762e5d5fd446350d42e9c841e2959) - Support position and occurrence args for REGEXP_EXTRACT *(PR [#6266](https://github.com/tobymao/sqlglot/pull/6266) by [@vchan](https://github.com/vchan))*:\n\n  Support position and occurrence args for REGEXP_EXTRACT (#6266)\n\n- due to [`ddea61d`](https://github.com/tobymao/sqlglot/commit/ddea61d83f6699c97cc7b25aabe01a138138bdb1) - simplify connector complements only for non-null operands *(PR [#6214](https://github.com/tobymao/sqlglot/pull/6214) by [@geooo109](https://github.com/geooo109))*:\n\n  simplify connector complements only for non-null operands (#6214)\n\n- due to [`771732d`](https://github.com/tobymao/sqlglot/commit/771732d81459cc576f11eccc49794f33e62d14af) - annotate types for REGR_AVGY *(PR [#6271](https://github.com/tobymao/sqlglot/pull/6271) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for REGR_AVGY (#6271)\n\n- due to [`8470be0`](https://github.com/tobymao/sqlglot/commit/8470be00731a4d79518a533a5f7ba884fa2f047e) - add type annotation for BITMAP_COUNT function. *(PR [#6274](https://github.com/tobymao/sqlglot/pull/6274) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for BITMAP_COUNT function. (#6274)\n\n- due to [`98f25f9`](https://github.com/tobymao/sqlglot/commit/98f25f92cc1175ac7b2118a5a342db82adade13a) - support splitBy function *(PR [#6278](https://github.com/tobymao/sqlglot/pull/6278) by [@joeyutong](https://github.com/joeyutong))*:\n\n  support splitBy function (#6278)\n\n- due to [`fabbf05`](https://github.com/tobymao/sqlglot/commit/fabbf057aba88f30205767d8c339727de45991c8) - Add support for shorthand struct array literals in duckDB. *(PR [#6233](https://github.com/tobymao/sqlglot/pull/6233) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add support for shorthand struct array literals in duckDB. (#6233)\n\n- due to [`c02b64c`](https://github.com/tobymao/sqlglot/commit/c02b64c3524dd074c2108baaca668ab2607ac843) - Handle pseudocolumns differently than columns *(PR [#6273](https://github.com/tobymao/sqlglot/pull/6273) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Handle pseudocolumns differently than columns (#6273)\n\n- due to [`05c5181`](https://github.com/tobymao/sqlglot/commit/05c5181b36a7ada32b96fc91bdfbf73b38a1a408) - refactor `Connector` simplification to factor in types *(PR [#6152](https://github.com/tobymao/sqlglot/pull/6152) by [@geooo109](https://github.com/geooo109))*:\n\n  refactor `Connector` simplification to factor in types (#6152)\n\n- due to [`9c1a222`](https://github.com/tobymao/sqlglot/commit/9c1a2221b0327ba6848542c7b906e92f25a05bea) - add type annotation for BITMAP_CONSTRUCT_AGG function. *(PR [#6285](https://github.com/tobymao/sqlglot/pull/6285) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add type annotation for BITMAP_CONSTRUCT_AGG function. (#6285)\n\n- due to [`cb0bcff`](https://github.com/tobymao/sqlglot/commit/cb0bcff310e9acdf806fc98e99cb9938b747c771) - cast UUID() output to varchar when source dialect UUID() returns string *(PR [#6284](https://github.com/tobymao/sqlglot/pull/6284) by [@toriwei](https://github.com/toriwei))*:\n\n  cast UUID() output to varchar when source dialect UUID() returns string (#6284)\n\n- due to [`358105d`](https://github.com/tobymao/sqlglot/commit/358105d1296c7425e071ccf3189a31a02c00c923) - type annotation for BITMAP_BIT_POSITION function *(PR [#6301](https://github.com/tobymao/sqlglot/pull/6301) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  type annotation for BITMAP_BIT_POSITION function (#6301)\n\n- due to [`4ee7a50`](https://github.com/tobymao/sqlglot/commit/4ee7a500cc460b6f6a1ed103a12dca72e6d01c18) - type inference for BITMAP_OR_AGG *(PR [#6297](https://github.com/tobymao/sqlglot/pull/6297) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  type inference for BITMAP_OR_AGG (#6297)\n\n- due to [`fcd537d`](https://github.com/tobymao/sqlglot/commit/fcd537de2c993ad0bd18acd84dbae354165f7d3f) - conflict resolution. type annotation for BITMAP_BUCKET_NUMBER function. Tests added all dialects that support BITMAP_BUCKET_NUMBER *(PR [#6299](https://github.com/tobymao/sqlglot/pull/6299) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  conflict resolution. type annotation for BITMAP_BUCKET_NUMBER function. Tests added all dialects that support BITMAP_BUCKET_NUMBER (#6299)\n\n- due to [`3dffd59`](https://github.com/tobymao/sqlglot/commit/3dffd598496a9f2d94caec9d7f3dcb9791c94019) - annotate types for PERCENTILE_DISC and WithinGroup *(PR [#6300](https://github.com/tobymao/sqlglot/pull/6300) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for PERCENTILE_DISC and WithinGroup (#6300)\n\n- due to [`f9287f7`](https://github.com/tobymao/sqlglot/commit/f9287f7d596a6d8a1e1cd2c48978a4dec77a96cb) - robust deduplication of connectors *(PR [#6296](https://github.com/tobymao/sqlglot/pull/6296) by [@geooo109](https://github.com/geooo109))*:\n\n  robust deduplication of connectors (#6296)\n\n- due to [`ea0ea79`](https://github.com/tobymao/sqlglot/commit/ea0ea79c1c611b62c79f82f744fe0c98803598a3) - Parse `LIKE` functions *(PR [#6314](https://github.com/tobymao/sqlglot/pull/6314) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Parse `LIKE` functions (#6314)\n\n- due to [`e903883`](https://github.com/tobymao/sqlglot/commit/e90388328fcf5b8061c99e325b87d5beb0046ffc) - type annotation for APPROX_TOP_K_ACCUMULATE functio… *(PR [#6309](https://github.com/tobymao/sqlglot/pull/6309) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  type annotation for APPROX_TOP_K_ACCUMULATE functio… (#6309)\n\n- due to [`d3fefad`](https://github.com/tobymao/sqlglot/commit/d3fefad80d25ff5a6dd02426667ff0ea8478a1b2) - support `DATEDIFF_BIG` *(PR [#6323](https://github.com/tobymao/sqlglot/pull/6323) by [@lBilali](https://github.com/lBilali))*:\n\n  support `DATEDIFF_BIG` (#6323)\n\n- due to [`21d1468`](https://github.com/tobymao/sqlglot/commit/21d1468377b9c8ad48c6cca1ae3b3744a807c29e) - annotate type for APPROX_TOP_K *(PR [#6286](https://github.com/tobymao/sqlglot/pull/6286) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for APPROX_TOP_K (#6286)\n\n- due to [`85ddcc5`](https://github.com/tobymao/sqlglot/commit/85ddcc5eca22ac726582de454f2f12b9d4877634) - Do not normalize JSON fields in dot notation *(PR [#6320](https://github.com/tobymao/sqlglot/pull/6320) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Do not normalize JSON fields in dot notation (#6320)\n\n- due to [`933e981`](https://github.com/tobymao/sqlglot/commit/933e98102fb39d24ae0350da13337d981287130a) - more robust NULL reduction *(PR [#6327](https://github.com/tobymao/sqlglot/pull/6327) by [@geooo109](https://github.com/geooo109))*:\n\n  more robust NULL reduction (#6327)\n\n\n### :sparkles: New Features\n- [`39d8e19`](https://github.com/tobymao/sqlglot/commit/39d8e19419c2adbb80465be414d1cc3bbc6d007b) - **snowflake**: include VARIABLE kind in SET transpilation to DuckDB *(PR [#6201](https://github.com/tobymao/sqlglot/pull/6201) by [@toriwei](https://github.com/toriwei))*\n  - :arrow_lower_right: *addresses issue [#6177](https://github.com/tobymao/sqlglot/issues/6177) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`0037266`](https://github.com/tobymao/sqlglot/commit/00372664bf6acf2b0fff9ad4b206b597ef5378f7) - **snowflake**: annotate types for GETBIT *(PR [#6219](https://github.com/tobymao/sqlglot/pull/6219) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`a5458ce`](https://github.com/tobymao/sqlglot/commit/a5458ceca3bc239fb611791e38020632dd0824c8) - **snowflake**: add type annotation for DECODE function support *(PR [#6199](https://github.com/tobymao/sqlglot/pull/6199) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`a9d0f63`](https://github.com/tobymao/sqlglot/commit/a9d0f6333c38ffb0b5afc3c213ac7bf008d98ad6) - **DuckDB**: Transpile unix_millis to epoch_ms *(PR [#6224](https://github.com/tobymao/sqlglot/pull/6224) by [@vchan](https://github.com/vchan))*\n- [`238f705`](https://github.com/tobymao/sqlglot/commit/238f705940751f09464ee0f8260186f3f8124374) - **DuckDB**: Transpile unix_seconds to epoch *(PR [#6225](https://github.com/tobymao/sqlglot/pull/6225) by [@vchan](https://github.com/vchan))*\n- [`c8b0129`](https://github.com/tobymao/sqlglot/commit/c8b0129380df389be6ff22cafb4251181e919d23) - **exasol**: support bracket-delimited identifiers *(PR [#6231](https://github.com/tobymao/sqlglot/pull/6231) by [@JoepvandenHoven-Bluemine](https://github.com/JoepvandenHoven-Bluemine))*\n- [`417f1e8`](https://github.com/tobymao/sqlglot/commit/417f1e8ee50fb8f4377fad261660ffbd7444a429) - **snowflake**: annotate types for BITNOT *(PR [#6234](https://github.com/tobymao/sqlglot/pull/6234) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`fe8ab40`](https://github.com/tobymao/sqlglot/commit/fe8ab40e8e0559201e0b1896a6f1a8fb6b5b932d) - **snowflake**: 1st-class parsing support for BITAND, BIT_AND, BIT_NOT *(PR [#6243](https://github.com/tobymao/sqlglot/pull/6243) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`5ae3c47`](https://github.com/tobymao/sqlglot/commit/5ae3c47b1c6993b87341472c08714f4a0f738168) - **snowflake**: add type annotation for GROUPING() function *(PR [#6244](https://github.com/tobymao/sqlglot/pull/6244) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4133265`](https://github.com/tobymao/sqlglot/commit/413326514507ef06537dcc3d4b80a3fcbcd26f66) - **clickhouse**: parse `has` function into an `ArrayContains` expression *(PR [#6245](https://github.com/tobymao/sqlglot/pull/6245) by [@joeyutong](https://github.com/joeyutong))*\n- [`b722aa2`](https://github.com/tobymao/sqlglot/commit/b722aa2d4b65c698921066426838f080a31bdc35) - **duckdb**: cast LOWER() result to BLOB if input is bytes *(PR [#6218](https://github.com/tobymao/sqlglot/pull/6218) by [@toriwei](https://github.com/toriwei))*\n- [`cdd45b9`](https://github.com/tobymao/sqlglot/commit/cdd45b949fd1eefb147053424279b56b8effcbcf) - **optimizer**: annotate types for GROUPING_ID function. *(PR [#6249](https://github.com/tobymao/sqlglot/pull/6249) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`080ff3b`](https://github.com/tobymao/sqlglot/commit/080ff3bd93b36291d5bb0092d722f8307f0ae082) - **snowflake**: annotate types for BITAND_AGG *(PR [#6248](https://github.com/tobymao/sqlglot/pull/6248) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`87a818a`](https://github.com/tobymao/sqlglot/commit/87a818a899f61a675c22c697f468b3f6f7e2787f) - **snowflake**: annotate types for BITOR_AGG  *(PR [#6251](https://github.com/tobymao/sqlglot/pull/6251) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`a1b884d`](https://github.com/tobymao/sqlglot/commit/a1b884dc9ddfd2185de48cc9451a39f152879d39) - **snowflake**: annotate types for BITXOR_AGG *(PR [#6253](https://github.com/tobymao/sqlglot/pull/6253) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`71d93b1`](https://github.com/tobymao/sqlglot/commit/71d93b181d2aa3a77a022820446d6fec0133291f) - **duckdb**: implement casting to blob for UPPER() and move to helper method *(PR [#6254](https://github.com/tobymao/sqlglot/pull/6254) by [@toriwei](https://github.com/toriwei))*\n- [`ad2ad23`](https://github.com/tobymao/sqlglot/commit/ad2ad234b5a508040dce4f3920439be052742573) - **snowflake**: add missing return type mapping for MAX_BY and MAX_BY function *(PR [#6250](https://github.com/tobymao/sqlglot/pull/6250) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`39c1d81`](https://github.com/tobymao/sqlglot/commit/39c1d81174f2390b6b0c9dd14c0e550ad452a1df) - **snowflake**: annotate types for BOOLXOR_AGG *(PR [#6261](https://github.com/tobymao/sqlglot/pull/6261) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`71590d2`](https://github.com/tobymao/sqlglot/commit/71590d22cdb05594e2173a1500f763dc1a32a81d) - **snowflake**: add type annotation for SKEW function. *(PR [#6262](https://github.com/tobymao/sqlglot/pull/6262) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`5fd366d`](https://github.com/tobymao/sqlglot/commit/5fd366d9e6f7b3f1eb7a9cf41975cf13ce890ffe) - **snowflake**: annotate types for OBJECT_AGG *(PR [#6265](https://github.com/tobymao/sqlglot/pull/6265) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`3dae0fb`](https://github.com/tobymao/sqlglot/commit/3dae0fbb528762e5d5fd446350d42e9c841e2959) - **duckdb**: Support position and occurrence args for REGEXP_EXTRACT *(PR [#6266](https://github.com/tobymao/sqlglot/pull/6266) by [@vchan](https://github.com/vchan))*\n- [`dba0414`](https://github.com/tobymao/sqlglot/commit/dba04145c4bcda8c55890b4d7173dd6c0a64c37e) - **clickhouse**: Parse toStartOfxxx into exp.TimestampTrunc *(PR [#6268](https://github.com/tobymao/sqlglot/pull/6268) by [@joeyutong](https://github.com/joeyutong))*\n- [`d959ad0`](https://github.com/tobymao/sqlglot/commit/d959ad02140d692483a63b67d69d2a5d49954ea3) - transpile DuckDB exclusive end RANGE to SEQUENCE *(PR [#6270](https://github.com/tobymao/sqlglot/pull/6270) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6267](https://github.com/tobymao/sqlglot/issues/6267) opened by [@joeyutong](https://github.com/joeyutong)*\n- [`771732d`](https://github.com/tobymao/sqlglot/commit/771732d81459cc576f11eccc49794f33e62d14af) - **snowflake**: annotate types for REGR_AVGY *(PR [#6271](https://github.com/tobymao/sqlglot/pull/6271) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`8470be0`](https://github.com/tobymao/sqlglot/commit/8470be00731a4d79518a533a5f7ba884fa2f047e) - **snowflake**: add type annotation for BITMAP_COUNT function. *(PR [#6274](https://github.com/tobymao/sqlglot/pull/6274) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`98f25f9`](https://github.com/tobymao/sqlglot/commit/98f25f92cc1175ac7b2118a5a342db82adade13a) - **clickhouse**: support splitBy function *(PR [#6278](https://github.com/tobymao/sqlglot/pull/6278) by [@joeyutong](https://github.com/joeyutong))*\n- [`fabbf05`](https://github.com/tobymao/sqlglot/commit/fabbf057aba88f30205767d8c339727de45991c8) - **duckDB**: Add support for shorthand struct array literals in duckDB. *(PR [#6233](https://github.com/tobymao/sqlglot/pull/6233) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`a909fde`](https://github.com/tobymao/sqlglot/commit/a909fde068919823dc4cccc2655af48e4290137a) - **duckdb**: Add support for CREATE MACRO *(PR [#6292](https://github.com/tobymao/sqlglot/pull/6292) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#6290](https://github.com/tobymao/sqlglot/issues/6290) opened by [@francescomucio](https://github.com/francescomucio)*\n- [`11989be`](https://github.com/tobymao/sqlglot/commit/11989be34153ccdedeab3ab18ccf735f86e8b822) - add more expressions with positional meta *(PR [#6289](https://github.com/tobymao/sqlglot/pull/6289) by [@tobymao](https://github.com/tobymao))*\n- [`87651a6`](https://github.com/tobymao/sqlglot/commit/87651a671db2fe6162f06e2dcdef0b98e229bea5) - semantic facts closes [#6287](https://github.com/tobymao/sqlglot/pull/6287) *(PR [#6288](https://github.com/tobymao/sqlglot/pull/6288) by [@tobymao](https://github.com/tobymao))*\n- [`9c1a222`](https://github.com/tobymao/sqlglot/commit/9c1a2221b0327ba6848542c7b906e92f25a05bea) - **snowflake**: add type annotation for BITMAP_CONSTRUCT_AGG function. *(PR [#6285](https://github.com/tobymao/sqlglot/pull/6285) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`358105d`](https://github.com/tobymao/sqlglot/commit/358105d1296c7425e071ccf3189a31a02c00c923) - **snowflake**: type annotation for BITMAP_BIT_POSITION function *(PR [#6301](https://github.com/tobymao/sqlglot/pull/6301) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4ee7a50`](https://github.com/tobymao/sqlglot/commit/4ee7a500cc460b6f6a1ed103a12dca72e6d01c18) - **snowflake**: type inference for BITMAP_OR_AGG *(PR [#6297](https://github.com/tobymao/sqlglot/pull/6297) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`fcd537d`](https://github.com/tobymao/sqlglot/commit/fcd537de2c993ad0bd18acd84dbae354165f7d3f) - **snowflake**: conflict resolution. type annotation for BITMAP_BUCKET_NUMBER function. Tests added all dialects that support BITMAP_BUCKET_NUMBER *(PR [#6299](https://github.com/tobymao/sqlglot/pull/6299) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`3dffd59`](https://github.com/tobymao/sqlglot/commit/3dffd598496a9f2d94caec9d7f3dcb9791c94019) - **snowflake**: annotate types for PERCENTILE_DISC and WithinGroup *(PR [#6300](https://github.com/tobymao/sqlglot/pull/6300) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`6ce3cd7`](https://github.com/tobymao/sqlglot/commit/6ce3cd7de958d9f3773579ab22ae6cbbcb56ceb0) - **sqlite**: support binary `MATCH` operator closes [#6305](https://github.com/tobymao/sqlglot/pull/6305) *(PR [#6306](https://github.com/tobymao/sqlglot/pull/6306) by [@georgesittas](https://github.com/georgesittas))*\n- [`e903883`](https://github.com/tobymao/sqlglot/commit/e90388328fcf5b8061c99e325b87d5beb0046ffc) - **snowflake**: type annotation for APPROX_TOP_K_ACCUMULATE functio… *(PR [#6309](https://github.com/tobymao/sqlglot/pull/6309) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`afc0242`](https://github.com/tobymao/sqlglot/commit/afc0242c564f8de53e11865c2fba43fb36df0694) - **duckDB**: Cast inputs (BLOB → VARCHAR) for duckDB STARTS_WITH *(PR [#6240](https://github.com/tobymao/sqlglot/pull/6240) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`d170bbd`](https://github.com/tobymao/sqlglot/commit/d170bbde800a0308aaf8c81e59152c65be312155) - **duckdb**: transpile bigquery's `BYTES` variant of `REPLACE` *(PR [#6312](https://github.com/tobymao/sqlglot/pull/6312) by [@toriwei](https://github.com/toriwei))*\n- [`d3fefad`](https://github.com/tobymao/sqlglot/commit/d3fefad80d25ff5a6dd02426667ff0ea8478a1b2) - **tsql**: support `DATEDIFF_BIG` *(PR [#6323](https://github.com/tobymao/sqlglot/pull/6323) by [@lBilali](https://github.com/lBilali))*\n- [`21d1468`](https://github.com/tobymao/sqlglot/commit/21d1468377b9c8ad48c6cca1ae3b3744a807c29e) - **optimizer**: annotate type for APPROX_TOP_K *(PR [#6286](https://github.com/tobymao/sqlglot/pull/6286) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`93b4039`](https://github.com/tobymao/sqlglot/commit/93b4039f957f3eefbaaed2cb147bfa8c8c2a304e) - **duckdb**: preserve time zone and timestamp in DATE_TRUNC() *(PR [#6318](https://github.com/tobymao/sqlglot/pull/6318) by [@toriwei](https://github.com/toriwei))*\n- [`b71990f`](https://github.com/tobymao/sqlglot/commit/b71990f528d55c845f5771bfc4c5f6098eb97ad7) - **duckdb**: Add transpilation support for ANY_VALUE function with HAVING MAX and MIN clauses *(PR [#6325](https://github.com/tobymao/sqlglot/pull/6325) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`64c0d55`](https://github.com/tobymao/sqlglot/commit/64c0d554207ad40bcd6a93c20d15020752a5929d) - **sqlite**: support indexed table clause closes [#6331](https://github.com/tobymao/sqlglot/pull/6331) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6725217`](https://github.com/tobymao/sqlglot/commit/6725217d4058b5202006576bdf6ff4ec7230a9b9) - **sqlite**: support `NOT NULL` operator closes [#6334](https://github.com/tobymao/sqlglot/pull/6334) closes [#6335](https://github.com/tobymao/sqlglot/pull/6335) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`e7ddad1`](https://github.com/tobymao/sqlglot/commit/e7ddad10b5edf9b801d2151e3e5fca448754df0d) - **optimizer**: ensure `NULL` coerces into any type *(PR [#6211](https://github.com/tobymao/sqlglot/pull/6211) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`4c4189b`](https://github.com/tobymao/sqlglot/commit/4c4189b4083d272a6e678d83b5c567a2e9c0d672) - Transpile CONCAT function to double pipe operators when source … *(PR [#6241](https://github.com/tobymao/sqlglot/pull/6241) by [@vchan](https://github.com/vchan))*\n- [`fc78d20`](https://github.com/tobymao/sqlglot/commit/fc78d2016d8f7d20c094df791f746de323cd3639) - **parser**: Unwrap subqueries without modifiers *(PR [#6247](https://github.com/tobymao/sqlglot/pull/6247) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6237](https://github.com/tobymao/sqlglot/issues/6237) opened by [@preet-sheth](https://github.com/preet-sheth)*\n- [`7ad4c17`](https://github.com/tobymao/sqlglot/commit/7ad4c177fbf8dda78aa8de1ca112f606b2fd5456) - **databricks**: Support table names in FROM STREAM *(PR [#6259](https://github.com/tobymao/sqlglot/pull/6259) by [@roveo](https://github.com/roveo))*\n- [`00abc39`](https://github.com/tobymao/sqlglot/commit/00abc393c9042e839457c5a6582e95cdb74356f3) - **generator**: handle casting for bytestrings  *(PR [#6252](https://github.com/tobymao/sqlglot/pull/6252) by [@toriwei](https://github.com/toriwei))*\n- [`bcf2eac`](https://github.com/tobymao/sqlglot/commit/bcf2eace0baf1d85047841f36cb5c0082c61b29c) - **duckdb**: map int8 to bigint instead of tinyint fixes [#6269](https://github.com/tobymao/sqlglot/pull/6269) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ddea61d`](https://github.com/tobymao/sqlglot/commit/ddea61d83f6699c97cc7b25aabe01a138138bdb1) - **optimizer**: simplify connector complements only for non-null operands *(PR [#6214](https://github.com/tobymao/sqlglot/pull/6214) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6213](https://github.com/tobymao/sqlglot/issues/6213) opened by [@geooo109](https://github.com/geooo109)*\n- [`e17320e`](https://github.com/tobymao/sqlglot/commit/e17320ee3bdd0ef541d616c447b4973d12780dae) - Handle edge cases in  for DuckDB RANGE to Spark SEQUENCE transpilation *(PR [#6276](https://github.com/tobymao/sqlglot/pull/6276) by [@joeyutong](https://github.com/joeyutong))*\n- [`33b6218`](https://github.com/tobymao/sqlglot/commit/33b62183a15cdedf0b1ebd96fcb856afbe8879a0) - sqlsecurityproperty parseerror *(PR [#6280](https://github.com/tobymao/sqlglot/pull/6280) by [@ds-cbo](https://github.com/ds-cbo))*\n  - :arrow_lower_right: *fixes issue [#6279](https://github.com/tobymao/sqlglot/issues/6279) opened by [@ds-cbo](https://github.com/ds-cbo)*\n- [`c02b64c`](https://github.com/tobymao/sqlglot/commit/c02b64c3524dd074c2108baaca668ab2607ac843) - **optimizer**: Handle pseudocolumns differently than columns *(PR [#6273](https://github.com/tobymao/sqlglot/pull/6273) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6256](https://github.com/tobymao/sqlglot/issues/6256) opened by [@azilya](https://github.com/azilya)*\n- [`05c5181`](https://github.com/tobymao/sqlglot/commit/05c5181b36a7ada32b96fc91bdfbf73b38a1a408) - **optimizer**: refactor `Connector` simplification to factor in types *(PR [#6152](https://github.com/tobymao/sqlglot/pull/6152) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6137](https://github.com/tobymao/sqlglot/issues/6137) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`cb0bcff`](https://github.com/tobymao/sqlglot/commit/cb0bcff310e9acdf806fc98e99cb9938b747c771) - **duckdb**: cast UUID() output to varchar when source dialect UUID() returns string *(PR [#6284](https://github.com/tobymao/sqlglot/pull/6284) by [@toriwei](https://github.com/toriwei))*\n- [`f9287f7`](https://github.com/tobymao/sqlglot/commit/f9287f7d596a6d8a1e1cd2c48978a4dec77a96cb) - **optimizer**: robust deduplication of connectors *(PR [#6296](https://github.com/tobymao/sqlglot/pull/6296) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6182](https://github.com/tobymao/sqlglot/issues/6182) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`ea0ea79`](https://github.com/tobymao/sqlglot/commit/ea0ea79c1c611b62c79f82f744fe0c98803598a3) - **clickhouse**: Parse `LIKE` functions *(PR [#6314](https://github.com/tobymao/sqlglot/pull/6314) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6313](https://github.com/tobymao/sqlglot/issues/6313) opened by [@CainYang](https://github.com/CainYang)*\n- [`bbd4c90`](https://github.com/tobymao/sqlglot/commit/bbd4c901a9550beb363758e6be1e1877d4e56f2c) - **sqlite**: support IS with identifier as RHS *(PR [#6316](https://github.com/tobymao/sqlglot/pull/6316) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6315](https://github.com/tobymao/sqlglot/issues/6315) opened by [@VLDB2026](https://github.com/VLDB2026)*\n- [`65d213a`](https://github.com/tobymao/sqlglot/commit/65d213a7662962d4226368590508fbf61675c055) - **dialect**: fix typo from millenium to millennium [#6321](https://github.com/tobymao/sqlglot/pull/6321) *(commit by [@lBilali](https://github.com/lBilali))*\n- [`c9d1615`](https://github.com/tobymao/sqlglot/commit/c9d16150a408a41daf704d2d0b0ebfce57425b81) - **tsql**: map iso_week with the correct python directive from strftime *(PR [#6322](https://github.com/tobymao/sqlglot/pull/6322) by [@lBilali](https://github.com/lBilali))*\n- [`85ddcc5`](https://github.com/tobymao/sqlglot/commit/85ddcc5eca22ac726582de454f2f12b9d4877634) - **bigquery**: Do not normalize JSON fields in dot notation *(PR [#6320](https://github.com/tobymao/sqlglot/pull/6320) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`933e981`](https://github.com/tobymao/sqlglot/commit/933e98102fb39d24ae0350da13337d981287130a) - **optimizer**: more robust NULL reduction *(PR [#6327](https://github.com/tobymao/sqlglot/pull/6327) by [@geooo109](https://github.com/geooo109))*\n- [`e1c6d57`](https://github.com/tobymao/sqlglot/commit/e1c6d5716f80eb24b6d0a9c93e187a8c9f05e555) - **parser**: improve between .. preceding .. following parser fixes [#6332](https://github.com/tobymao/sqlglot/pull/6332) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`65706e8`](https://github.com/tobymao/sqlglot/commit/65706e8c7edeb7de674d427718eac181df206dc9) - avoid full traversal for pushdown_cte_alias_columns *(commit by [@tobymao](https://github.com/tobymao))*\n- [`c81258e`](https://github.com/tobymao/sqlglot/commit/c81258e9c26f637f6f8520051c159685c8b1cb7e) - **parser**: allow using OVER token as unquoted identifier *(PR [#6338](https://github.com/tobymao/sqlglot/pull/6338) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6337](https://github.com/tobymao/sqlglot/issues/6337) opened by [@VLDB2026](https://github.com/VLDB2026)*\n- [`73abfac`](https://github.com/tobymao/sqlglot/commit/73abfac4cec27350754c942be71175fa7bdfd1d0) - **redshift**: do not inherit postgres `ROUND` generator closes [#6340](https://github.com/tobymao/sqlglot/pull/6340) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`9c98fc2`](https://github.com/tobymao/sqlglot/commit/9c98fc2b39fef2bd052b60ba4e15a4b93fd66c00) - **optimizer**: avoid extra copy in simplify *(commit by [@geooo109](https://github.com/geooo109))*\n- [`43985fb`](https://github.com/tobymao/sqlglot/commit/43985fbcb9edea088119951c5c245a9606cf92ae) - **snowflake**: remove redundant tests for ANY_VALUE *(commit by [@geooo109](https://github.com/geooo109))*\n- [`bf7b032`](https://github.com/tobymao/sqlglot/commit/bf7b032baae0c0fd112054a7bed6fa2f56f32890) - clean up struct name inheritance *(PR [#6295](https://github.com/tobymao/sqlglot/pull/6295) by [@georgesittas](https://github.com/georgesittas))*\n- [`49e0f43`](https://github.com/tobymao/sqlglot/commit/49e0f43ba19739575987f2e9c52c2061a6f59717) - extra test for spark approx_top_k_accumulate *(commit by [@geooo109](https://github.com/geooo109))*\n\n### :wrench: Chores\n- [`d7be4a5`](https://github.com/tobymao/sqlglot/commit/d7be4a5da3dca6bcc44230b2a176c8b17b81c46e) - **optimizer**: add annotation test for COALESCE *(PR [#6210](https://github.com/tobymao/sqlglot/pull/6210) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`8aa7356`](https://github.com/tobymao/sqlglot/commit/8aa7356ab8adee26193086754ca1a1805957d944) - **optimizer**: add annotation tests for IFF *(PR [#6215](https://github.com/tobymao/sqlglot/pull/6215) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`160a1b9`](https://github.com/tobymao/sqlglot/commit/160a1b90f4ce39a2fce6f7f0e9e854d974fed053) - **optimizer**: mixed type annotation test for sf IFNULL *(commit by [@geooo109](https://github.com/geooo109))*\n- [`893ad2a`](https://github.com/tobymao/sqlglot/commit/893ad2a5b1a28339ccc65c85ac813506e6ad56f1) - **optimizer**: add annotation tests for NULLIF *(PR [#6221](https://github.com/tobymao/sqlglot/pull/6221) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`78d7733`](https://github.com/tobymao/sqlglot/commit/78d77335819d1796fa3989ef072d3f8fd4b83559) - remove redundant or term for unknown in annotate_types *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b202f3a`](https://github.com/tobymao/sqlglot/commit/b202f3ad64e88a47e52c45e32c9e4faae6c8ac45) - **optimizer**: add test for BITXOR *(PR [#6223](https://github.com/tobymao/sqlglot/pull/6223) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`b20f2e8`](https://github.com/tobymao/sqlglot/commit/b20f2e88d86038f1a98f4b97b5a2ae0b86652e33) - **optimizer**: add test for BITSHIFTLEFT *(PR [#6227](https://github.com/tobymao/sqlglot/pull/6227) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7f93e85`](https://github.com/tobymao/sqlglot/commit/7f93e8551b00cc32014236a07c8794bd7a3a2b91) - **optimizer**: add annotation tests for BITSHIFTRIGHT *(PR [#6228](https://github.com/tobymao/sqlglot/pull/6228) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`fcf017c`](https://github.com/tobymao/sqlglot/commit/fcf017cfb95923fea8ae5669340713a326f4f306) - rename `EXPRESSION_SPEC` to `EXPRESSION_METADATA` *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`55bc9e4`](https://github.com/tobymao/sqlglot/commit/55bc9e4019f8ef8d7e571256d7b0e07b30d9240c) - remove predicate/connector/not from typing metadata *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`349ab29`](https://github.com/tobymao/sqlglot/commit/349ab29aa84fb087388b6a1494fea70273a4a560) - **optimizer**: add annotation test for BOOLAND_OR *(PR [#6260](https://github.com/tobymao/sqlglot/pull/6260) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`75ec424`](https://github.com/tobymao/sqlglot/commit/75ec424667b95462bb1750a251a5096da0d5161b) - **optimizer**: add annotation test for BOOLAND_AGG *(PR [#6257](https://github.com/tobymao/sqlglot/pull/6257) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`bb574aa`](https://github.com/tobymao/sqlglot/commit/bb574aa0cf0a8c0b92f9af7ef3dfddb7de725a8b) - **optimizer**: add annotation test for ARRAY_AGG *(PR [#6264](https://github.com/tobymao/sqlglot/pull/6264) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`a95c5cc`](https://github.com/tobymao/sqlglot/commit/a95c5ccf411dc4d28ef9c19fb03bd8a3615d7c4b) - **optimizer**: add nonnull clickhouse column test case *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6d6c689`](https://github.com/tobymao/sqlglot/commit/6d6c68915ca699da7cb707675aece963df97f80b) - **optimizer**: add annotation tests for ANY_VALUE *(PR [#6275](https://github.com/tobymao/sqlglot/pull/6275) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`2459f88`](https://github.com/tobymao/sqlglot/commit/2459f8832ae398aa1381025724a4286f7f5e3e9d) - Follow up of 6280 *(PR [#6281](https://github.com/tobymao/sqlglot/pull/6281) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`a7d33d0`](https://github.com/tobymao/sqlglot/commit/a7d33d0e190fc5c9f23a1ab43082ac017d20fd18) - **optimizer**: add annotation tests for APPROX_PERCENTILE *(PR [#6283](https://github.com/tobymao/sqlglot/pull/6283) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`1b2d139`](https://github.com/tobymao/sqlglot/commit/1b2d139d3338c7053dee333914323236a2d15d97) - **optimizer**: add type annotation tests with window for sf APPROX_PERCENTILE *(commit by [@geooo109](https://github.com/geooo109))*\n- [`d059648`](https://github.com/tobymao/sqlglot/commit/d05964851c99553ba06e318bbbda39f9851120db) - **optimizer**: add annotation tests for APPROX_COUNT_DISTINCT *(PR [#6282](https://github.com/tobymao/sqlglot/pull/6282) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`6bd59ac`](https://github.com/tobymao/sqlglot/commit/6bd59acf2288da5bfe6151c5adf6f2a63792dc1e) - Follow up of PR 6288 *(PR [#6293](https://github.com/tobymao/sqlglot/pull/6293) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`546fd2a`](https://github.com/tobymao/sqlglot/commit/546fd2a2588f7b385bdbb9e39490bd6a422283ca) - Remove dead line in qualify_columns *(PR [#6304](https://github.com/tobymao/sqlglot/pull/6304) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ac7ac19`](https://github.com/tobymao/sqlglot/commit/ac7ac198a3b915e63ba8a055e9a0193c3dd3e26a) - **exasol**: Implement ODBC date time literals in Exasol Sqlglot *(PR [#6311](https://github.com/tobymao/sqlglot/pull/6311) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`8d1d25c`](https://github.com/tobymao/sqlglot/commit/8d1d25c6de7ad03c50e3efe892d16d16329d8ee9) - **exasol**: Implement local qualifier for-aliases, in GROUP BY, WHERE AND HAVING clause in exasol dialect *(PR [#6277](https://github.com/tobymao/sqlglot/pull/6277) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`509b0aa`](https://github.com/tobymao/sqlglot/commit/509b0aaada0e27542864771ba14777d398b6cee0) - **exasol**: Implement day_of_week function *(PR [#6319](https://github.com/tobymao/sqlglot/pull/6319) by [@nnamdi16](https://github.com/nnamdi16))*\n\n\n## [v27.29.0] - 2025-10-29\n### :boom: BREAKING CHANGES\n- due to [`5242cdd`](https://github.com/tobymao/sqlglot/commit/5242cddf487e367e7f543ca19d9bccae858f36ac) - annotate type for bq LENGTH *(commit by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq LENGTH\n\n- due to [`0fc6dbf`](https://github.com/tobymao/sqlglot/commit/0fc6dbf2e7b611fa0977e3c3e61be1cc84bcf4a9) - add GREATEST_IGNORE_NULLS function support *(PR [#6161](https://github.com/tobymao/sqlglot/pull/6161) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  add GREATEST_IGNORE_NULLS function support (#6161)\n\n- due to [`d382a31`](https://github.com/tobymao/sqlglot/commit/d382a3106d5ce2e9b75527aacd4a37d1f8e16d18) - simplify double negation only if the inner expr is BOOLEAN *(PR [#6151](https://github.com/tobymao/sqlglot/pull/6151) by [@geooo109](https://github.com/geooo109))*:\n\n  simplify double negation only if the inner expr is BOOLEAN (#6151)\n\n- due to [`bcf6c89`](https://github.com/tobymao/sqlglot/commit/bcf6c89a47abd3c2c4383d1c908f892b6619b6fa) - add type annotation tests for snowflake BOOLAND *(PR [#6153](https://github.com/tobymao/sqlglot/pull/6153) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  add type annotation tests for snowflake BOOLAND (#6153)\n\n- due to [`52d1eec`](https://github.com/tobymao/sqlglot/commit/52d1eecaad505703e8b22dcfe8954652f57985b6) - Annotate type for snowflake TIMESTAMP_FROM_PARTS function *(PR [#6139](https://github.com/tobymao/sqlglot/pull/6139) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TIMESTAMP_FROM_PARTS function (#6139)\n\n- due to [`8651fe6`](https://github.com/tobymao/sqlglot/commit/8651fe6526dea865c0d54d6d53086359a7835d32) - annotate types for BOOLOR *(PR [#6159](https://github.com/tobymao/sqlglot/pull/6159) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for BOOLOR (#6159)\n\n- due to [`812ba9a`](https://github.com/tobymao/sqlglot/commit/812ba9abad8247df81c8f8b514336c8766292112) - Annotate type for snowflake date parts functions *(PR [#6158](https://github.com/tobymao/sqlglot/pull/6158) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  Annotate type for snowflake date parts functions (#6158)\n\n- due to [`9f8c123`](https://github.com/tobymao/sqlglot/commit/9f8c123ae44249e274334d0aa551ac33814f2b32) - make qualify table callback more generic *(PR [#6171](https://github.com/tobymao/sqlglot/pull/6171) by [@tobymao](https://github.com/tobymao))*:\n\n  make qualify table callback more generic (#6171)\n\n- due to [`74b4e7c`](https://github.com/tobymao/sqlglot/commit/74b4e7c311e9d4ff39ce2e4d91940eced96aa32f) - fix type annotation for Snowflake BOOLOR and BOOLAND *(PR [#6169](https://github.com/tobymao/sqlglot/pull/6169) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  fix type annotation for Snowflake BOOLOR and BOOLAND (#6169)\n\n- due to [`ef87520`](https://github.com/tobymao/sqlglot/commit/ef875204596b8529f3358025c7a61d757a999bdc) - Transpile `REGEXP_REPLACE` with 'g' option *(PR [#6174](https://github.com/tobymao/sqlglot/pull/6174) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Transpile `REGEXP_REPLACE` with 'g' option (#6174)\n\n- due to [`93071e2`](https://github.com/tobymao/sqlglot/commit/93071e255406f62ea83dd89a3be4871b7edfb3fe) - Fix simplify_parens from removing negated *(PR [#6194](https://github.com/tobymao/sqlglot/pull/6194) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix simplify_parens from removing negated (#6194)\n\n- due to [`e90168a`](https://github.com/tobymao/sqlglot/commit/e90168a6829b85534edcecec7d0df2a8b1b56fc4) - annotate type for Snowflake's `IS_NULL_VALUE` function *(PR [#6186](https://github.com/tobymao/sqlglot/pull/6186) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotate type for Snowflake's `IS_NULL_VALUE` function (#6186)\n\n- due to [`c93b535`](https://github.com/tobymao/sqlglot/commit/c93b5354827282c806899c36b11e7a7598e96e38) - annotate type for LEAST_IGNORE_NULLS *(PR [#6196](https://github.com/tobymao/sqlglot/pull/6196) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  annotate type for LEAST_IGNORE_NULLS (#6196)\n\n- due to [`f60c71f`](https://github.com/tobymao/sqlglot/commit/f60c71fb03db91bfe90430d032ac16f4945d5dff) - annotate types for REGR_VALX *(PR [#6198](https://github.com/tobymao/sqlglot/pull/6198) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for REGR_VALX (#6198)\n\n- due to [`b82c571`](https://github.com/tobymao/sqlglot/commit/b82c57131707297abe174539023b9cb62b7cd6c7) - annotate types for REGR_VALY *(PR [#6206](https://github.com/tobymao/sqlglot/pull/6206) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate types for REGR_VALY (#6206)\n\n\n### :sparkles: New Features\n- [`5242cdd`](https://github.com/tobymao/sqlglot/commit/5242cddf487e367e7f543ca19d9bccae858f36ac) - **optimizer**: annotate type for bq LENGTH *(commit by [@geooo109](https://github.com/geooo109))*\n- [`0fc6dbf`](https://github.com/tobymao/sqlglot/commit/0fc6dbf2e7b611fa0977e3c3e61be1cc84bcf4a9) - **snowflake**: add GREATEST_IGNORE_NULLS function support *(PR [#6161](https://github.com/tobymao/sqlglot/pull/6161) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`54ecadc`](https://github.com/tobymao/sqlglot/commit/54ecadc57b8f1e87fd2a2ba35a5366d75231ea85) - **duckdb**: support `KV_METADATA` in `COPY` statement closes [#6165](https://github.com/tobymao/sqlglot/pull/6165) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`e241964`](https://github.com/tobymao/sqlglot/commit/e2419642a4966a4da194147aa488793eae152af4) - **duckdb**: support `USING` condition for `MERGE` closes [#6162](https://github.com/tobymao/sqlglot/pull/6162) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bcf6c89`](https://github.com/tobymao/sqlglot/commit/bcf6c89a47abd3c2c4383d1c908f892b6619b6fa) - **optimizer**: add type annotation tests for snowflake BOOLAND *(PR [#6153](https://github.com/tobymao/sqlglot/pull/6153) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`52d1eec`](https://github.com/tobymao/sqlglot/commit/52d1eecaad505703e8b22dcfe8954652f57985b6) - **optimizer**: Annotate type for snowflake TIMESTAMP_FROM_PARTS function *(PR [#6139](https://github.com/tobymao/sqlglot/pull/6139) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`8651fe6`](https://github.com/tobymao/sqlglot/commit/8651fe6526dea865c0d54d6d53086359a7835d32) - **optimizer**: annotate types for BOOLOR *(PR [#6159](https://github.com/tobymao/sqlglot/pull/6159) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`812ba9a`](https://github.com/tobymao/sqlglot/commit/812ba9abad8247df81c8f8b514336c8766292112) - **optimizer**: Annotate type for snowflake date parts functions *(PR [#6158](https://github.com/tobymao/sqlglot/pull/6158) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`9f8c123`](https://github.com/tobymao/sqlglot/commit/9f8c123ae44249e274334d0aa551ac33814f2b32) - make qualify table callback more generic *(PR [#6171](https://github.com/tobymao/sqlglot/pull/6171) by [@tobymao](https://github.com/tobymao))*\n- [`74b4e7c`](https://github.com/tobymao/sqlglot/commit/74b4e7c311e9d4ff39ce2e4d91940eced96aa32f) - **optimizer**: fix type annotation for Snowflake BOOLOR and BOOLAND *(PR [#6169](https://github.com/tobymao/sqlglot/pull/6169) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`e90168a`](https://github.com/tobymao/sqlglot/commit/e90168a6829b85534edcecec7d0df2a8b1b56fc4) - **optimizer**: annotate type for Snowflake's `IS_NULL_VALUE` function *(PR [#6186](https://github.com/tobymao/sqlglot/pull/6186) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`cea2595`](https://github.com/tobymao/sqlglot/commit/cea25952c98e70f2a4c35e675fe7ee4df0af02cd) - **duckdb**: Transpile DATE function from BQ->DuckDB *(PR [#6178](https://github.com/tobymao/sqlglot/pull/6178) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`00aaa47`](https://github.com/tobymao/sqlglot/commit/00aaa47feff1cf9e69320074c35d9adfc8538026) - **duckDB**: Transpile BigQuery's CURRENT_DATE (Conversion) function to DuckDB *(PR [#6189](https://github.com/tobymao/sqlglot/pull/6189) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c93b535`](https://github.com/tobymao/sqlglot/commit/c93b5354827282c806899c36b11e7a7598e96e38) - **snowflake**: annotate type for LEAST_IGNORE_NULLS *(PR [#6196](https://github.com/tobymao/sqlglot/pull/6196) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`d2162fb`](https://github.com/tobymao/sqlglot/commit/d2162fbece0747b8ee42fa1f78e26baa0c944d41) - check same ref on Expression.__eq__ *(PR [#6200](https://github.com/tobymao/sqlglot/pull/6200) by [@georgesittas](https://github.com/georgesittas))*\n- [`f60c71f`](https://github.com/tobymao/sqlglot/commit/f60c71fb03db91bfe90430d032ac16f4945d5dff) - **optimizer**: annotate types for REGR_VALX *(PR [#6198](https://github.com/tobymao/sqlglot/pull/6198) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`42e0ae4`](https://github.com/tobymao/sqlglot/commit/42e0ae43b3531bf6c593bcac2ece2ab1d969e5e1) - **duckdb**: transpile BigQuery function TIMESTAMP_SUB to DuckDB *(PR [#6202](https://github.com/tobymao/sqlglot/pull/6202) by [@toriwei](https://github.com/toriwei))*\n- [`b82c571`](https://github.com/tobymao/sqlglot/commit/b82c57131707297abe174539023b9cb62b7cd6c7) - **snowflake**: annotate types for REGR_VALY *(PR [#6206](https://github.com/tobymao/sqlglot/pull/6206) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n\n### :bug: Bug Fixes\n- [`3acf796`](https://github.com/tobymao/sqlglot/commit/3acf7965105a098fea6336df0c304d94acbd05ec) - **duckdb**: Allow ESCAPE NULL *(PR [#6164](https://github.com/tobymao/sqlglot/pull/6164) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6160](https://github.com/tobymao/sqlglot/issues/6160) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`f7f1fca`](https://github.com/tobymao/sqlglot/commit/f7f1fca39a75df16ebb93f038e6277a25b8be6b9) - **duckdb**: Support positional index in list comprehension *(PR [#6163](https://github.com/tobymao/sqlglot/pull/6163) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6156](https://github.com/tobymao/sqlglot/issues/6156) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`d382a31`](https://github.com/tobymao/sqlglot/commit/d382a3106d5ce2e9b75527aacd4a37d1f8e16d18) - **optimizer**: simplify double negation only if the inner expr is BOOLEAN *(PR [#6151](https://github.com/tobymao/sqlglot/pull/6151) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6129](https://github.com/tobymao/sqlglot/issues/6129) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`dfe6b3c`](https://github.com/tobymao/sqlglot/commit/dfe6b3c8e6db40e22e626e2d56e9a7008dd75c32) - **optimizer**: Disambiguate JOIN ON columns during qualify *(PR [#6155](https://github.com/tobymao/sqlglot/pull/6155) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6132](https://github.com/tobymao/sqlglot/issues/6132) opened by [@Fosly](https://github.com/Fosly)*\n- [`f267ece`](https://github.com/tobymao/sqlglot/commit/f267ecea92b0751f6b35a4ad0c70fe6754e49038) - normalize before qualifying tables *(PR [#6176](https://github.com/tobymao/sqlglot/pull/6176) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6167](https://github.com/tobymao/sqlglot/issues/6167) opened by [@schelip](https://github.com/schelip)*\n- [`ef87520`](https://github.com/tobymao/sqlglot/commit/ef875204596b8529f3358025c7a61d757a999bdc) - **postgres, duckdb**: Transpile `REGEXP_REPLACE` with 'g' option *(PR [#6174](https://github.com/tobymao/sqlglot/pull/6174) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6170](https://github.com/tobymao/sqlglot/issues/6170) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`51a8d70`](https://github.com/tobymao/sqlglot/commit/51a8d700a9602278d1e98425af0fa87d02c739fe) - **parser**: allow LIMIT % OFFSET *(PR [#6184](https://github.com/tobymao/sqlglot/pull/6184) by [@toriwei](https://github.com/toriwei))*\n  - :arrow_lower_right: *fixes issue [#6166](https://github.com/tobymao/sqlglot/issues/6166) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`8bf0a9f`](https://github.com/tobymao/sqlglot/commit/8bf0a9fe8e167984dc2e7b43d52d3850e063da3f) - **duckdb**: Cast literal arg to timestamp for epoch_us function *(PR [#6190](https://github.com/tobymao/sqlglot/pull/6190) by [@vchan](https://github.com/vchan))*\n- [`93071e2`](https://github.com/tobymao/sqlglot/commit/93071e255406f62ea83dd89a3be4871b7edfb3fe) - **optimizer**: Fix simplify_parens from removing negated *(PR [#6194](https://github.com/tobymao/sqlglot/pull/6194) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6179](https://github.com/tobymao/sqlglot/issues/6179) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`2ac3a03`](https://github.com/tobymao/sqlglot/commit/2ac3a03409d9239d0cf7fb265843d7837a0a3fcd) - **lineage**: correct star detection and add join star tests *(PR [#6185](https://github.com/tobymao/sqlglot/pull/6185) by [@lancewl](https://github.com/lancewl))*\n- [`c9ae2eb`](https://github.com/tobymao/sqlglot/commit/c9ae2ebdb86abdb767f2fcb00da0b6277b4aea45) - **duckdb**: transpile BigQuery TIMESTAMP_ADD to duckdb *(PR [#6188](https://github.com/tobymao/sqlglot/pull/6188) by [@toriwei](https://github.com/toriwei))*\n- [`ba0e17a`](https://github.com/tobymao/sqlglot/commit/ba0e17a25af417e24162bfab49c3074454a5c1a8) - **snowflake**: Transpile `ARRAY_CONCAT_AGG` to `ARRAY_FLATTEN(ARRAY_AGG(...))` *(PR [#6192](https://github.com/tobymao/sqlglot/pull/6192) by [@ozadari](https://github.com/ozadari))*\n- [`730e4cc`](https://github.com/tobymao/sqlglot/commit/730e4cc5b77bff9135667193cc0a65c24cdfb6b5) - **trino**: Allow 2nd arg for FIRST/LAST functions *(PR [#6205](https://github.com/tobymao/sqlglot/pull/6205) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6204](https://github.com/tobymao/sqlglot/issues/6204) opened by [@Harmuth94](https://github.com/Harmuth94)*\n\n### :recycle: Refactors\n- [`6d775fd`](https://github.com/tobymao/sqlglot/commit/6d775fdb6091cb866c27c0f1141514b23d689284) - snowflake GREATEST type checks *(commit by [@geooo109](https://github.com/geooo109))*\n- [`e797fb1`](https://github.com/tobymao/sqlglot/commit/e797fb105f7fa4e7bd42698eda71037cae9fd155) - update `LIKE` operator when using functional syntax with spark dialect *(PR [#6173](https://github.com/tobymao/sqlglot/pull/6173) by [@themattmorris](https://github.com/themattmorris))*\n  - :arrow_lower_right: *addresses issue [#6172](https://github.com/tobymao/sqlglot/issues/6172) opened by [@themattmorris](https://github.com/themattmorris)*\n\n### :wrench: Chores\n- [`aca106c`](https://github.com/tobymao/sqlglot/commit/aca106c660b8aaf229065ec5c5a4a80d10e8daf6) - **optimizer**: add type annotation tests for snowflake GREATEST *(PR [#6157](https://github.com/tobymao/sqlglot/pull/6157) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`f763604`](https://github.com/tobymao/sqlglot/commit/f7636041d7b796545ed923ffd4803521f05fa7ea) - add `IS [NOT]` tests *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1ab5854`](https://github.com/tobymao/sqlglot/commit/1ab5854216da591e6036ac103239ac0280e09c3d) - **optimizer**: add snowflake test for [NOT] IN *(PR [#6180](https://github.com/tobymao/sqlglot/pull/6180) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`64939ce`](https://github.com/tobymao/sqlglot/commit/64939ce9926f4740387a151311e918e807bfa681) - **optimizer**: add annotation tests for ZEROIFNULL *(PR [#6187](https://github.com/tobymao/sqlglot/pull/6187) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`4b6bcdd`](https://github.com/tobymao/sqlglot/commit/4b6bcdd4dc297bd42ad000ffda98d14110565dc9) - **optimizer**: Add tests for snowflake's `NULLIFZERO` *(PR [#6197](https://github.com/tobymao/sqlglot/pull/6197) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`ef68075`](https://github.com/tobymao/sqlglot/commit/ef680756c33da180ed2f21fb6113a0123db341c9) - **optimizer**: add annotation tests for NVL2 *(PR [#6208](https://github.com/tobymao/sqlglot/pull/6208) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`7f550f2`](https://github.com/tobymao/sqlglot/commit/7f550f22da40d8c1cfc8afb183d6e4dbd50241ea) - **optimizer**: add annotation tests for NVL *(PR [#6207](https://github.com/tobymao/sqlglot/pull/6207) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n\n\n## [v27.28.0] - 2025-10-21\n### :boom: BREAKING CHANGES\n- due to [`2238ac2`](https://github.com/tobymao/sqlglot/commit/2238ac27478bd272ba39928bbec1075c4191ee1b) - transpile timestamp literals in datediff fixes [#6083](https://github.com/tobymao/sqlglot/pull/6083) *(PR [#6086](https://github.com/tobymao/sqlglot/pull/6086) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile timestamp literals in datediff fixes #6083 (#6086)\n\n- due to [`c49ba0e`](https://github.com/tobymao/sqlglot/commit/c49ba0eee21f7776703d2a26c6641b4a32a1cff7) - Annotate type for snowflake WIDTH_BUCKET function *(PR [#6078](https://github.com/tobymao/sqlglot/pull/6078) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake WIDTH_BUCKET function (#6078)\n\n- due to [`fbc1f13`](https://github.com/tobymao/sqlglot/commit/fbc1f1335eecaaaab4fc93ddbb74611a4df0aea7) - annotate type for Snowflake CONVERT_TIMEZONE function *(PR [#6076](https://github.com/tobymao/sqlglot/pull/6076) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake CONVERT_TIMEZONE function (#6076)\n\n- due to [`70e977c`](https://github.com/tobymao/sqlglot/commit/70e977c5edfb495529d38a9096cb40762a9b5d7b) - annotate type for Snowflake DATE_TRUNC function *(PR [#6080](https://github.com/tobymao/sqlglot/pull/6080) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake DATE_TRUNC function (#6080)\n\n- due to [`e9cf146`](https://github.com/tobymao/sqlglot/commit/e9cf146a4a6cd78f6a59c195e7ec12240b836e5e) - annotate type for Snowflake DATE_PART function *(PR [#6079](https://github.com/tobymao/sqlglot/pull/6079) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake DATE_PART function (#6079)\n\n- due to [`5109890`](https://github.com/tobymao/sqlglot/commit/510989043d18baa17502a971262462814a2eb5be) - VALUES with ORDER BY/LIMIT/OFFSET *(PR [#6094](https://github.com/tobymao/sqlglot/pull/6094) by [@geooo109](https://github.com/geooo109))*:\n\n  VALUES with ORDER BY/LIMIT/OFFSET (#6094)\n\n- due to [`6fe5824`](https://github.com/tobymao/sqlglot/commit/6fe58247888c326093618657fb027e482d82d107) - Annotate type for hour, minute, second functions *(PR [#6100](https://github.com/tobymao/sqlglot/pull/6100) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for hour, minute, second functions (#6100)\n\n- due to [`a4d07a0`](https://github.com/tobymao/sqlglot/commit/a4d07a07eefbdaf88d30df2310a9533afdc75a82) - Annotate type for snowflake EXTRACT function *(PR [#6099](https://github.com/tobymao/sqlglot/pull/6099) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake EXTRACT function (#6099)\n\n- due to [`483770b`](https://github.com/tobymao/sqlglot/commit/483770b816fab14b7eb7222974ed2c99045302a7) - Annotate type for snowflake TIME_SLICE function *(PR [#6098](https://github.com/tobymao/sqlglot/pull/6098) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TIME_SLICE function (#6098)\n\n- due to [`06f40f9`](https://github.com/tobymao/sqlglot/commit/06f40f900ce693ba4203514e422cba8cda0dbb07) - don't simplify x XOR x due to NULL semantics *(PR [#6115](https://github.com/tobymao/sqlglot/pull/6115) by [@geooo109](https://github.com/geooo109))*:\n\n  don't simplify x XOR x due to NULL semantics (#6115)\n\n- due to [`c286cee`](https://github.com/tobymao/sqlglot/commit/c286cee54ab93e1fd0b3be658f7e767e3e00afe9) - Annotate type for snowflake MONTHNAME function *(PR [#6116](https://github.com/tobymao/sqlglot/pull/6116) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake MONTHNAME function (#6116)\n\n- due to [`1a34788`](https://github.com/tobymao/sqlglot/commit/1a34788025bdd8a018c4bb9214f72152e68bdd14) - Annotate type for snowflake PREVIOUS_DAY function *(PR [#6117](https://github.com/tobymao/sqlglot/pull/6117) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake PREVIOUS_DAY function (#6117)\n\n- due to [`533faf8`](https://github.com/tobymao/sqlglot/commit/533faf87b6df351070b565dd1fe9ce4e13b6c46e) - transpile duckdb `READ_PARQUET` to `parquet.<path>` closes [#6122](https://github.com/tobymao/sqlglot/pull/6122) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile duckdb `READ_PARQUET` to `parquet.<path>` closes #6122\n\n- due to [`cd4e557`](https://github.com/tobymao/sqlglot/commit/cd4e557658b1384f36c9a1ef9da5a09b893229b1) - Annotate type for snowflake RANDOM function *(PR [#6124](https://github.com/tobymao/sqlglot/pull/6124) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  Annotate type for snowflake RANDOM function (#6124)\n\n- due to [`fe63d84`](https://github.com/tobymao/sqlglot/commit/fe63d84f1bd365b22221f348d79c0546aa3118b0) - annotate type for Snowflake MONTHS_BETWEEN function *(PR [#6120](https://github.com/tobymao/sqlglot/pull/6120) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*:\n\n  annotate type for Snowflake MONTHS_BETWEEN function (#6120)\n\n- due to [`598d09b`](https://github.com/tobymao/sqlglot/commit/598d09b036d938c90a44955d67175ea868090ba2) - annotate type for Snowflake DATEADD function *(PR [#6089](https://github.com/tobymao/sqlglot/pull/6089) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake DATEADD function (#6089)\n\n- due to [`b98bcee`](https://github.com/tobymao/sqlglot/commit/b98bcee148ba426816e166dbfa9ba8e0979aae21) - Annotate type for snowflake next_day function *(PR [#6125](https://github.com/tobymao/sqlglot/pull/6125) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*:\n\n  Annotate type for snowflake next_day function (#6125)\n\n- due to [`e2129c6`](https://github.com/tobymao/sqlglot/commit/e2129c6766ca1f10ff6663bec98be984abb33c91) - Do not consider BIT_COUNT an aggregate function *(PR [#6135](https://github.com/tobymao/sqlglot/pull/6135) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Do not consider BIT_COUNT an aggregate function (#6135)\n\n- due to [`d136414`](https://github.com/tobymao/sqlglot/commit/d136414e520270ac9ab2fd8e9df4691d269b3af0) - avoid simplifying AND with NULL *(PR [#6148](https://github.com/tobymao/sqlglot/pull/6148) by [@geooo109](https://github.com/geooo109))*:\n\n  avoid simplifying AND with NULL (#6148)\n\n- due to [`3a334f3`](https://github.com/tobymao/sqlglot/commit/3a334f376b9766b6b99fdf195ae763bb44976ec4) - annotate type for boolnot snowflake function *(PR [#6141](https://github.com/tobymao/sqlglot/pull/6141) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*:\n\n  annotate type for boolnot snowflake function (#6141)\n\n- due to [`99949cc`](https://github.com/tobymao/sqlglot/commit/99949ccd3ff81b524edeae437d874b86250dbb5b) - avoid needlessly copying in lineage *(PR [#6150](https://github.com/tobymao/sqlglot/pull/6150) by [@georgesittas](https://github.com/georgesittas))*:\n\n  avoid needlessly copying in lineage (#6150)\n\n- due to [`4e36f9d`](https://github.com/tobymao/sqlglot/commit/4e36f9dd6a854b378c9bbf6b2e9811045affc63d) - Annotate type for snowflake TIMEADD function *(PR [#6134](https://github.com/tobymao/sqlglot/pull/6134) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TIMEADD function (#6134)\n\n\n### :sparkles: New Features\n- [`c49ba0e`](https://github.com/tobymao/sqlglot/commit/c49ba0eee21f7776703d2a26c6641b4a32a1cff7) - **optimizer**: Annotate type for snowflake WIDTH_BUCKET function *(PR [#6078](https://github.com/tobymao/sqlglot/pull/6078) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fbc1f13`](https://github.com/tobymao/sqlglot/commit/fbc1f1335eecaaaab4fc93ddbb74611a4df0aea7) - **optimizer**: annotate type for Snowflake CONVERT_TIMEZONE function *(PR [#6076](https://github.com/tobymao/sqlglot/pull/6076) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`70e977c`](https://github.com/tobymao/sqlglot/commit/70e977c5edfb495529d38a9096cb40762a9b5d7b) - **optimizer**: annotate type for Snowflake DATE_TRUNC function *(PR [#6080](https://github.com/tobymao/sqlglot/pull/6080) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`e9cf146`](https://github.com/tobymao/sqlglot/commit/e9cf146a4a6cd78f6a59c195e7ec12240b836e5e) - **optimizer**: annotate type for Snowflake DATE_PART function *(PR [#6079](https://github.com/tobymao/sqlglot/pull/6079) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`cdf3b1b`](https://github.com/tobymao/sqlglot/commit/cdf3b1b34dc044064d0a5ba7ff22723b8ae33e5d) - **optimizer**: Annotate type for snowflake add_months function *(PR [#6097](https://github.com/tobymao/sqlglot/pull/6097) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`6fe5824`](https://github.com/tobymao/sqlglot/commit/6fe58247888c326093618657fb027e482d82d107) - **optimizer**: Annotate type for hour, minute, second functions *(PR [#6100](https://github.com/tobymao/sqlglot/pull/6100) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`483770b`](https://github.com/tobymao/sqlglot/commit/483770b816fab14b7eb7222974ed2c99045302a7) - **optimizer**: Annotate type for snowflake TIME_SLICE function *(PR [#6098](https://github.com/tobymao/sqlglot/pull/6098) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`071a995`](https://github.com/tobymao/sqlglot/commit/071a9954aad220c1e13ba7a6714a083058a8e03f) - **tsql**: add support for iso_week on DATEPART *(PR [#6111](https://github.com/tobymao/sqlglot/pull/6111) by [@lBilali](https://github.com/lBilali))*\n  - :arrow_lower_right: *addresses issue [#6110](https://github.com/tobymao/sqlglot/issues/6110) opened by [@lBilali](https://github.com/lBilali)*\n- [`c286cee`](https://github.com/tobymao/sqlglot/commit/c286cee54ab93e1fd0b3be658f7e767e3e00afe9) - **optimizer**: Annotate type for snowflake MONTHNAME function *(PR [#6116](https://github.com/tobymao/sqlglot/pull/6116) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`1a34788`](https://github.com/tobymao/sqlglot/commit/1a34788025bdd8a018c4bb9214f72152e68bdd14) - **optimizer**: Annotate type for snowflake PREVIOUS_DAY function *(PR [#6117](https://github.com/tobymao/sqlglot/pull/6117) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`533faf8`](https://github.com/tobymao/sqlglot/commit/533faf87b6df351070b565dd1fe9ce4e13b6c46e) - **spark**: transpile duckdb `READ_PARQUET` to `parquet.<path>` closes [#6122](https://github.com/tobymao/sqlglot/pull/6122) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`cd4e557`](https://github.com/tobymao/sqlglot/commit/cd4e557658b1384f36c9a1ef9da5a09b893229b1) - **optimizer**: Annotate type for snowflake RANDOM function *(PR [#6124](https://github.com/tobymao/sqlglot/pull/6124) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`fe63d84`](https://github.com/tobymao/sqlglot/commit/fe63d84f1bd365b22221f348d79c0546aa3118b0) - **optimizer**: annotate type for Snowflake MONTHS_BETWEEN function *(PR [#6120](https://github.com/tobymao/sqlglot/pull/6120) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`7cb7598`](https://github.com/tobymao/sqlglot/commit/7cb7598e13260aa45c851dc620b4994ddfa089fe) - **optimizer**: Annotate type for snowflake TIME_FROM_PARTS function *(PR [#6119](https://github.com/tobymao/sqlglot/pull/6119) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`598d09b`](https://github.com/tobymao/sqlglot/commit/598d09b036d938c90a44955d67175ea868090ba2) - **optimizer**: annotate type for Snowflake DATEADD function *(PR [#6089](https://github.com/tobymao/sqlglot/pull/6089) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`b98bcee`](https://github.com/tobymao/sqlglot/commit/b98bcee148ba426816e166dbfa9ba8e0979aae21) - **optimizer**: Annotate type for snowflake next_day function *(PR [#6125](https://github.com/tobymao/sqlglot/pull/6125) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n- [`fe1927f`](https://github.com/tobymao/sqlglot/commit/fe1927f28600e2d8863a4e7f06e6a21bf6ff7f9c) - **duckdb**: Transpile unix_micros to epoch_us *(PR [#6127](https://github.com/tobymao/sqlglot/pull/6127) by [@vchan](https://github.com/vchan))*\n- [`a531f10`](https://github.com/tobymao/sqlglot/commit/a531f107235c29ac6a7e627a323f00b8ecf7023d) - **duckdb**: transpile TimeSub *(PR [#6142](https://github.com/tobymao/sqlglot/pull/6142) by [@toriwei](https://github.com/toriwei))*\n- [`b1a9dff`](https://github.com/tobymao/sqlglot/commit/b1a9dfff52a0ffbb0b7c8bfedb0a90e245b97851) - make qualify faster by owly resetting scope when needed *(PR [#6081](https://github.com/tobymao/sqlglot/pull/6081) by [@tobymao](https://github.com/tobymao))*\n- [`3a334f3`](https://github.com/tobymao/sqlglot/commit/3a334f376b9766b6b99fdf195ae763bb44976ec4) - **optimizer**: annotate type for boolnot snowflake function *(PR [#6141](https://github.com/tobymao/sqlglot/pull/6141) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`4e36f9d`](https://github.com/tobymao/sqlglot/commit/4e36f9dd6a854b378c9bbf6b2e9811045affc63d) - **optimizer**: Annotate type for snowflake TIMEADD function *(PR [#6134](https://github.com/tobymao/sqlglot/pull/6134) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n### :bug: Bug Fixes\n- [`2238ac2`](https://github.com/tobymao/sqlglot/commit/2238ac27478bd272ba39928bbec1075c4191ee1b) - **duckdb**: transpile timestamp literals in datediff fixes [#6083](https://github.com/tobymao/sqlglot/pull/6083) *(PR [#6086](https://github.com/tobymao/sqlglot/pull/6086) by [@georgesittas](https://github.com/georgesittas))*\n- [`bef541c`](https://github.com/tobymao/sqlglot/commit/bef541cec36f8c4295f815c3f5cd22491738901b) - **parser**: query mods and set ops in FROM-first syntax *(PR [#6092](https://github.com/tobymao/sqlglot/pull/6092) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6088](https://github.com/tobymao/sqlglot/issues/6088) opened by [@denis-komarov](https://github.com/denis-komarov)*\n  - :arrow_lower_right: *fixes issue [#6091](https://github.com/tobymao/sqlglot/issues/6091) opened by [@denis-komarov](https://github.com/denis-komarov)*\n  - :arrow_lower_right: *fixes issue [#6093](https://github.com/tobymao/sqlglot/issues/6093) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`5109890`](https://github.com/tobymao/sqlglot/commit/510989043d18baa17502a971262462814a2eb5be) - **parser**: VALUES with ORDER BY/LIMIT/OFFSET *(PR [#6094](https://github.com/tobymao/sqlglot/pull/6094) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6087](https://github.com/tobymao/sqlglot/issues/6087) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`4b062c8`](https://github.com/tobymao/sqlglot/commit/4b062c850bd9867be0d622f3f526762fa2b72302) - consume more syntax for cubes/rollups fixes [#6101](https://github.com/tobymao/sqlglot/pull/6101) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`f00866a`](https://github.com/tobymao/sqlglot/commit/f00866aeb8b7f51e27173c688225fe16d777eb1a) - **duckdb**: 1 arg FORMAT func *(PR [#6109](https://github.com/tobymao/sqlglot/pull/6109) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6108](https://github.com/tobymao/sqlglot/issues/6108) opened by [@erindru](https://github.com/erindru)*\n- [`77dfd5a`](https://github.com/tobymao/sqlglot/commit/77dfd5a41bb9ce5450e0f6b7a78c953c8ade14d5) - lineage does not modify sql input if expression *(PR [#6113](https://github.com/tobymao/sqlglot/pull/6113) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#6112](https://github.com/tobymao/sqlglot/issues/6112) opened by [@snovik75](https://github.com/snovik75)*\n- [`06f40f9`](https://github.com/tobymao/sqlglot/commit/06f40f900ce693ba4203514e422cba8cda0dbb07) - **optimizer**: don't simplify x XOR x due to NULL semantics *(PR [#6115](https://github.com/tobymao/sqlglot/pull/6115) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6104](https://github.com/tobymao/sqlglot/issues/6104) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`03e2dff`](https://github.com/tobymao/sqlglot/commit/03e2dff9b074dc228cf3854ff1f4357e091aa9b3) - allow parsing `analyze` as an identifier fixes [#6123](https://github.com/tobymao/sqlglot/pull/6123) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`8744431`](https://github.com/tobymao/sqlglot/commit/874443148c8ec2a773dfaca5da10d3587a49de3e) - transpile bigquery DATETIME_DIFF to duckdb *(PR [#6126](https://github.com/tobymao/sqlglot/pull/6126) by [@toriwei](https://github.com/toriwei))*\n  - :arrow_lower_right: *fixes issue [#6107](https://github.com/tobymao/sqlglot/issues/6107) opened by [@izeigerman](https://github.com/izeigerman)*\n- [`b94e81b`](https://github.com/tobymao/sqlglot/commit/b94e81b42b89c75625b2da779c0f53777d9b6b48) - **optimizer**: avoid removing string literals from WHERE clause *(PR [#6131](https://github.com/tobymao/sqlglot/pull/6131) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6128](https://github.com/tobymao/sqlglot/issues/6128) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`e2129c6`](https://github.com/tobymao/sqlglot/commit/e2129c6766ca1f10ff6663bec98be984abb33c91) - **optimizer**: Do not consider BIT_COUNT an aggregate function *(PR [#6135](https://github.com/tobymao/sqlglot/pull/6135) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6130](https://github.com/tobymao/sqlglot/issues/6130) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`03bfeed`](https://github.com/tobymao/sqlglot/commit/03bfeed56c5c2f143ce2e1be38d519f902d19961) - **starrocks**: disable IS TRUE/FALSE syntax support *(PR [#6145](https://github.com/tobymao/sqlglot/pull/6145) by [@petrikoro](https://github.com/petrikoro))*\n  - :arrow_lower_right: *fixes issue [#6144](https://github.com/tobymao/sqlglot/issues/6144) opened by [@petrikoro](https://github.com/petrikoro)*\n- [`d136414`](https://github.com/tobymao/sqlglot/commit/d136414e520270ac9ab2fd8e9df4691d269b3af0) - **optimizer**: avoid simplifying AND with NULL *(PR [#6148](https://github.com/tobymao/sqlglot/pull/6148) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6136](https://github.com/tobymao/sqlglot/issues/6136) opened by [@dllggyx](https://github.com/dllggyx)*\n- [`1fd9991`](https://github.com/tobymao/sqlglot/commit/1fd99911a60f0543fbc79221a8c6a6f232ed0a2a) - **clickhouse**: NOT + IN precedence *(PR [#6149](https://github.com/tobymao/sqlglot/pull/6149) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6143](https://github.com/tobymao/sqlglot/issues/6143) opened by [@mlipiev](https://github.com/mlipiev)*\n\n### :recycle: Refactors\n- [`58dbce3`](https://github.com/tobymao/sqlglot/commit/58dbce30da5ab94af82247ab8a7eb85200d9b8af) - bq static type annotators *(PR [#6103](https://github.com/tobymao/sqlglot/pull/6103) by [@geooo109](https://github.com/geooo109))*\n- [`c970235`](https://github.com/tobymao/sqlglot/commit/c97023549623fe5974d6bff57e64339eff74187e) - clean up MONTHNAME test *(commit by [@geooo109](https://github.com/geooo109))*\n\n### :wrench: Chores\n- [`d36ba87`](https://github.com/tobymao/sqlglot/commit/d36ba8774a2a4b53c122e3b78086ce0f09e77244) - **optimizer**: add tests for Snowflake DATE_FROM_PARTS function *(PR [#6077](https://github.com/tobymao/sqlglot/pull/6077) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`2bc05cf`](https://github.com/tobymao/sqlglot/commit/2bc05cf3bd53b874a1505c747e38f8a6a1dbf8c7) - **optimizer**: add tests for Snowflake DATEDIFF function *(PR [#6090](https://github.com/tobymao/sqlglot/pull/6090) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`a4d07a0`](https://github.com/tobymao/sqlglot/commit/a4d07a07eefbdaf88d30df2310a9533afdc75a82) - **optimizer**: Annotate type for snowflake EXTRACT function *(PR [#6099](https://github.com/tobymao/sqlglot/pull/6099) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ab1da2e`](https://github.com/tobymao/sqlglot/commit/ab1da2e54a83e29d708047d4b3f8abcc1094229d) - **optimizer**: add type annotation tests for snowflake LAST_DAY function *(PR [#6105](https://github.com/tobymao/sqlglot/pull/6105) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`4e24c0a`](https://github.com/tobymao/sqlglot/commit/4e24c0ad92e7071a1f1537886173e29999b46f72) - **optimizer**: add type annotation tests for snowflake TIMESTAMPDIFF function *(PR [#6138](https://github.com/tobymao/sqlglot/pull/6138) by [@fivetran-MichaelLee](https://github.com/fivetran-MichaelLee))*\n- [`ae8571f`](https://github.com/tobymao/sqlglot/commit/ae8571fdec71587188e45fe087e1967f5ba641bc) - **optimizer**: add type annotation tests for snowflake TIMEDIFF *(PR [#6140](https://github.com/tobymao/sqlglot/pull/6140) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`3059320`](https://github.com/tobymao/sqlglot/commit/30593202b30001933f05747937975013754b75fa) - copy by default in `lineage` *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`99949cc`](https://github.com/tobymao/sqlglot/commit/99949ccd3ff81b524edeae437d874b86250dbb5b) - avoid needlessly copying in lineage *(PR [#6150](https://github.com/tobymao/sqlglot/pull/6150) by [@georgesittas](https://github.com/georgesittas))*\n- [`e7756d8`](https://github.com/tobymao/sqlglot/commit/e7756d8e9f347bfba3f861463890bf57e532cc54) - **optimizer**: add annotation tests for snowflake's BOOLXOR *(PR [#6154](https://github.com/tobymao/sqlglot/pull/6154) by [@fivetran-felixhuang](https://github.com/fivetran-felixhuang))*\n- [`72e43e3`](https://github.com/tobymao/sqlglot/commit/72e43e3ea08f9dce5a32654060a56f2ee31bea8f) - **optimizer**: add type annotation tests for snowflake's TIMESTAMPADD function *(PR [#6146](https://github.com/tobymao/sqlglot/pull/6146) by [@fivetran-ashashankar](https://github.com/fivetran-ashashankar))*\n\n\n## [v27.27.0] - 2025-10-13\n### :boom: BREAKING CHANGES\n- due to [`c67276d`](https://github.com/tobymao/sqlglot/commit/c67276d5be970252e14d1817d8498fc9985222d9) - Annotate type for snowflake RADIANS function. *(PR [#6064](https://github.com/tobymao/sqlglot/pull/6064) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake RADIANS function. (#6064)\n\n\n### :sparkles: New Features\n- [`c67276d`](https://github.com/tobymao/sqlglot/commit/c67276d5be970252e14d1817d8498fc9985222d9) - **optimizer**: Annotate type for snowflake RADIANS function. *(PR [#6064](https://github.com/tobymao/sqlglot/pull/6064) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n### :wrench: Chores\n- [`dab2a3f`](https://github.com/tobymao/sqlglot/commit/dab2a3fbdb8a523f05319eb34a1fd34534272206) - bump sqlglotrs version to 0.7.3 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.26.0] - 2025-10-10\n### :boom: BREAKING CHANGES\n- due to [`9060f60`](https://github.com/tobymao/sqlglot/commit/9060f603818db863b7570a2c3c50c3eb88155e76) - Annotate type for snowflake ATAN2 function. *(PR [#6060](https://github.com/tobymao/sqlglot/pull/6060) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake ATAN2 function. (#6060)\n\n- due to [`b3eb2e4`](https://github.com/tobymao/sqlglot/commit/b3eb2e4ca6177ee61b27675e8ec8b4815587df31) - annotate type for Snowflake SINH function *(PR [#6052](https://github.com/tobymao/sqlglot/pull/6052) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake SINH function (#6052)\n\n- due to [`157d2fa`](https://github.com/tobymao/sqlglot/commit/157d2fa06ab110ebc760aa7567d7fda801a5ced9) - annotate type for Snowflake CEIL function *(PR [#6051](https://github.com/tobymao/sqlglot/pull/6051) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake CEIL function (#6051)\n\n- due to [`e7833de`](https://github.com/tobymao/sqlglot/commit/e7833de9744a4aa69d244285e7f6f7281af178ba) - support DELETE with USING and multiple VALUES *(PR [#6072](https://github.com/tobymao/sqlglot/pull/6072) by [@geooo109](https://github.com/geooo109))*:\n\n  support DELETE with USING and multiple VALUES (#6072)\n\n- due to [`354140d`](https://github.com/tobymao/sqlglot/commit/354140d0a279f317439bdb247e1ab9578f9a035d) - Annotate type for snowflake TANH and ATAN functions *(PR [#6069](https://github.com/tobymao/sqlglot/pull/6069) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TANH and ATAN functions (#6069)\n\n\n### :sparkles: New Features\n- [`9060f60`](https://github.com/tobymao/sqlglot/commit/9060f603818db863b7570a2c3c50c3eb88155e76) - **optimizer**: Annotate type for snowflake ATAN2 function. *(PR [#6060](https://github.com/tobymao/sqlglot/pull/6060) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`b3eb2e4`](https://github.com/tobymao/sqlglot/commit/b3eb2e4ca6177ee61b27675e8ec8b4815587df31) - **optimizer**: annotate type for Snowflake SINH function *(PR [#6052](https://github.com/tobymao/sqlglot/pull/6052) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`440b960`](https://github.com/tobymao/sqlglot/commit/440b960529801674fa23708212485fda95749699) - **duckdb**: support `USING KEY (...)` in recursive DuckDB CTEs *(PR [#6068](https://github.com/tobymao/sqlglot/pull/6068) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6066](https://github.com/tobymao/sqlglot/issues/6066) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`157d2fa`](https://github.com/tobymao/sqlglot/commit/157d2fa06ab110ebc760aa7567d7fda801a5ced9) - **optimizer**: annotate type for Snowflake CEIL function *(PR [#6051](https://github.com/tobymao/sqlglot/pull/6051) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`eb6d6e7`](https://github.com/tobymao/sqlglot/commit/eb6d6e7ccde37456ab56ad976e7d95cea23c14e3) - **duckdb**: support `DEFAULT VALUES` clause in `INSERT` DML *(PR [#6067](https://github.com/tobymao/sqlglot/pull/6067) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6065](https://github.com/tobymao/sqlglot/issues/6065) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`354140d`](https://github.com/tobymao/sqlglot/commit/354140d0a279f317439bdb247e1ab9578f9a035d) - **optimizer**: Annotate type for snowflake TANH and ATAN functions *(PR [#6069](https://github.com/tobymao/sqlglot/pull/6069) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c94e3e0`](https://github.com/tobymao/sqlglot/commit/c94e3e0e4e20bd76d4cf630123d2c05a0e3044c3) - add ColumnDef expression parser *(PR [#6075](https://github.com/tobymao/sqlglot/pull/6075) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`2c7cc29`](https://github.com/tobymao/sqlglot/commit/2c7cc29a329dcbaaa90a6f857d2383d2967ea6cc) - **duckdb**: Transform exp.HexString to BLOB in hex notation *(PR [#6045](https://github.com/tobymao/sqlglot/pull/6045) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6035](https://github.com/tobymao/sqlglot/issues/6035) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`e7833de`](https://github.com/tobymao/sqlglot/commit/e7833de9744a4aa69d244285e7f6f7281af178ba) - **parser**: support DELETE with USING and multiple VALUES *(PR [#6072](https://github.com/tobymao/sqlglot/pull/6072) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6070](https://github.com/tobymao/sqlglot/issues/6070) opened by [@denis-komarov](https://github.com/denis-komarov)*\n\n### :recycle: Refactors\n- [`2c9d15c`](https://github.com/tobymao/sqlglot/commit/2c9d15c92da25c8456b2463c69aa56c8ec47c453) - replace direct arg manipulation *(PR [#6073](https://github.com/tobymao/sqlglot/pull/6073) by [@geooo109](https://github.com/geooo109))*\n\n### :wrench: Chores\n- [`75b8d16`](https://github.com/tobymao/sqlglot/commit/75b8d16e41b677ea7e150c89d713795073aae6e3) - remove docs from main branch *(PR [#6057](https://github.com/tobymao/sqlglot/pull/6057) by [@georgesittas](https://github.com/georgesittas))*\n- [`cfa2493`](https://github.com/tobymao/sqlglot/commit/cfa249328eef31ab0e0688dcc03521da3343ce47) - **optimizer**: Annotate type for snowflake SQUARE function *(PR [#6059](https://github.com/tobymao/sqlglot/pull/6059) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`e26c394`](https://github.com/tobymao/sqlglot/commit/e26c3949beb7f73020fcd099237dbe31a4db8d84) - **optimizer**: Annotate type for snowflake POW function *(PR [#6058](https://github.com/tobymao/sqlglot/pull/6058) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`7d303ad`](https://github.com/tobymao/sqlglot/commit/7d303adc5efe9d51eb62aeab80bfa4f844e1911d) - include Python 3.14 in the testing matrix *(PR [#6074](https://github.com/tobymao/sqlglot/pull/6074) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.25.0] - 2025-10-09\n### :boom: BREAKING CHANGES\n- due to [`6f31b86`](https://github.com/tobymao/sqlglot/commit/6f31b86599258afe156aa3d9ccc42389cac37021) - Annotate type for snowflake FLOOR function *(PR [#6030](https://github.com/tobymao/sqlglot/pull/6030) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake FLOOR function (#6030)\n\n- due to [`cecab2f`](https://github.com/tobymao/sqlglot/commit/cecab2fd66d578ddc765b5fd0e7b155971280a0c) - annotate type for Snowflake ATANH function *(PR [#6054](https://github.com/tobymao/sqlglot/pull/6054) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake ATANH function (#6054)\n\n- due to [`08339a9`](https://github.com/tobymao/sqlglot/commit/08339a902138211f67cfb009d2576b22ea8d8e42) - annotate type for Snowflake FACTORIAL function *(PR [#6053](https://github.com/tobymao/sqlglot/pull/6053) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake FACTORIAL function (#6053)\n\n\n### :sparkles: New Features\n- [`6f31b86`](https://github.com/tobymao/sqlglot/commit/6f31b86599258afe156aa3d9ccc42389cac37021) - **optimizer**: Annotate type for snowflake FLOOR function *(PR [#6030](https://github.com/tobymao/sqlglot/pull/6030) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`b7463d5`](https://github.com/tobymao/sqlglot/commit/b7463d5b0a1e286498d7ccfd9a07ef7edfa80bb2) - **optimizer**: Annotate type for snowflake ASIN function. *(PR [#6049](https://github.com/tobymao/sqlglot/pull/6049) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fe959a5`](https://github.com/tobymao/sqlglot/commit/fe959a5598508526ed5910a4c62372116b5d3c30) - **optimizer**: Annotate type for snowflake CBRT function *(PR [#6050](https://github.com/tobymao/sqlglot/pull/6050) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`cecab2f`](https://github.com/tobymao/sqlglot/commit/cecab2fd66d578ddc765b5fd0e7b155971280a0c) - **optimizer**: annotate type for Snowflake ATANH function *(PR [#6054](https://github.com/tobymao/sqlglot/pull/6054) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`08339a9`](https://github.com/tobymao/sqlglot/commit/08339a902138211f67cfb009d2576b22ea8d8e42) - **optimizer**: annotate type for Snowflake FACTORIAL function *(PR [#6053](https://github.com/tobymao/sqlglot/pull/6053) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n\n### :bug: Bug Fixes\n- [`3bb6bb3`](https://github.com/tobymao/sqlglot/commit/3bb6bb3e5193ed53c803c3786a1791f15cd2f89a) - **parser**: support :: cast operator after IS NULL/IS NOT NULL *(PR [#6056](https://github.com/tobymao/sqlglot/pull/6056) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6055](https://github.com/tobymao/sqlglot/issues/6055) opened by [@vchan](https://github.com/vchan)*\n\n### :wrench: Chores\n- [`15030a3`](https://github.com/tobymao/sqlglot/commit/15030a3996d005d79f27408a68d17f94c98aec68) - **optimizer**: Add tests for snowflake LN and LOG functions *(PR [#6048](https://github.com/tobymao/sqlglot/pull/6048) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`2ae8dbd`](https://github.com/tobymao/sqlglot/commit/2ae8dbd4d1b43bb27647144c32b2a781ff3edbeb) - push docs to `api-docs` branch instead of main *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.22.2] - 2025-10-08\n### :wrench: Chores\n- [`9ab3a96`](https://github.com/tobymao/sqlglot/commit/9ab3a96a853639224c80a9daff4674187a1a84ef) - bump sqlglotrs to 0.7.2 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.22.1] - 2025-10-08\n### :boom: BREAKING CHANGES\n- due to [`7ac01c2`](https://github.com/tobymao/sqlglot/commit/7ac01c2ae9bc4375efb63c60e3221e85088fdd1f) - bump sqlglotrs to 0.7.1 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.7.1\n\n\n### :wrench: Chores\n- [`7ac01c2`](https://github.com/tobymao/sqlglot/commit/7ac01c2ae9bc4375efb63c60e3221e85088fdd1f) - bump sqlglotrs to 0.7.1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.22.0] - 2025-10-08\n### :boom: BREAKING CHANGES\n- due to [`6beb917`](https://github.com/tobymao/sqlglot/commit/6beb9172dffd0aaea46b75477485060737e774b9) - Annotate type for snowflake ROUND function *(PR [#6032](https://github.com/tobymao/sqlglot/pull/6032) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake ROUND function (#6032)\n\n- due to [`0939d69`](https://github.com/tobymao/sqlglot/commit/0939d69223a860581b1c30cc2f762294946b93f3) - move odbc date literal handling in t-sql closes [#6037](https://github.com/tobymao/sqlglot/pull/6037) *(PR [#6044](https://github.com/tobymao/sqlglot/pull/6044) by [@georgesittas](https://github.com/georgesittas))*:\n\n  move odbc date literal handling in t-sql closes #6037 (#6044)\n\n- due to [`56c8b3b`](https://github.com/tobymao/sqlglot/commit/56c8b3bbff7451b9049e1a168716bb41222a86ed) - Support CHANGE COLUMN statements in Hive and CHANGE/ALTER COLUMN statements in Spark *(PR [#6004](https://github.com/tobymao/sqlglot/pull/6004) by [@tsamaras](https://github.com/tsamaras))*:\n\n  Support CHANGE COLUMN statements in Hive and CHANGE/ALTER COLUMN statements in Spark (#6004)\n\n\n### :sparkles: New Features\n- [`6beb917`](https://github.com/tobymao/sqlglot/commit/6beb9172dffd0aaea46b75477485060737e774b9) - **optimizer**: Annotate type for snowflake ROUND function *(PR [#6032](https://github.com/tobymao/sqlglot/pull/6032) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`8e03ad9`](https://github.com/tobymao/sqlglot/commit/8e03ad9dd087ebc72bf58cb6383607c0ce2e8f8f) - **optimizer**: Annotate type for snowflake MOD function *(PR [#6031](https://github.com/tobymao/sqlglot/pull/6031) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`15b3fac`](https://github.com/tobymao/sqlglot/commit/15b3fac3dd5efd4c347ac40055f07a9be5906802) - **mysql**: support `FOR ORDINALITY` clause in `COLUMN` expression *(PR [#6046](https://github.com/tobymao/sqlglot/pull/6046) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#6039](https://github.com/tobymao/sqlglot/issues/6039) opened by [@jdddog](https://github.com/jdddog)*\n- [`56c8b3b`](https://github.com/tobymao/sqlglot/commit/56c8b3bbff7451b9049e1a168716bb41222a86ed) - **hive,spark**: Support CHANGE COLUMN statements in Hive and CHANGE/ALTER COLUMN statements in Spark *(PR [#6004](https://github.com/tobymao/sqlglot/pull/6004) by [@tsamaras](https://github.com/tsamaras))*\n\n### :bug: Bug Fixes\n- [`6a6ca92`](https://github.com/tobymao/sqlglot/commit/6a6ca927c4e6e06f5cb38ad1153a8b556999ef90) - **parser**: Allow nested GROUPING SETS *(PR [#6041](https://github.com/tobymao/sqlglot/pull/6041) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6038](https://github.com/tobymao/sqlglot/issues/6038) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`41baeaa`](https://github.com/tobymao/sqlglot/commit/41baeaa1530c5419c945409133e3b7caa5250ec7) - **optimizer**: more robust CROSS JOIN substitution and JOIN reordering *(PR [#6021](https://github.com/tobymao/sqlglot/pull/6021) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6009](https://github.com/tobymao/sqlglot/issues/6009) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`e2f299f`](https://github.com/tobymao/sqlglot/commit/e2f299f5ad18d75a394e55bd1ee59ed243d77e54) - allow subqueries to have modifiers closes [#6014](https://github.com/tobymao/sqlglot/pull/6014) *(PR [#6034](https://github.com/tobymao/sqlglot/pull/6034) by [@tobymao](https://github.com/tobymao))*\n- [`0d65266`](https://github.com/tobymao/sqlglot/commit/0d6526693f8e7dda9b7c180d31c364bde91afc72) - parse lambda for arg_min/max arguments closes [#6036](https://github.com/tobymao/sqlglot/pull/6036) *(PR [#6042](https://github.com/tobymao/sqlglot/pull/6042) by [@georgesittas](https://github.com/georgesittas))*\n- [`0939d69`](https://github.com/tobymao/sqlglot/commit/0939d69223a860581b1c30cc2f762294946b93f3) - move odbc date literal handling in t-sql closes [#6037](https://github.com/tobymao/sqlglot/pull/6037) *(PR [#6044](https://github.com/tobymao/sqlglot/pull/6044) by [@georgesittas](https://github.com/georgesittas))*\n- [`65848e5`](https://github.com/tobymao/sqlglot/commit/65848e5a3e4c1cb26e6ca4deb7819a282838c3c2) - **tsql**: UPDATE with OPTIONS *(PR [#6043](https://github.com/tobymao/sqlglot/pull/6043) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6033](https://github.com/tobymao/sqlglot/issues/6033) opened by [@ligfx](https://github.com/ligfx)*\n\n### :recycle: Refactors\n- [`8f00c80`](https://github.com/tobymao/sqlglot/commit/8f00c804a67209a5eca1fcb28aeb95941c58e583) - _parse_in expr len check *(commit by [@geooo109](https://github.com/geooo109))*\n\n\n## [v27.21.0] - 2025-10-07\n### :boom: BREAKING CHANGES\n- due to [`3c7b5c0`](https://github.com/tobymao/sqlglot/commit/3c7b5c0e2dc071b7b9f6da308ba58a3a43da93dc) - Annotate type for snowflake SOUNDEX_P123 function *(PR [#5987](https://github.com/tobymao/sqlglot/pull/5987) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake SOUNDEX_P123 function (#5987)\n\n- due to [`f25e42e`](https://github.com/tobymao/sqlglot/commit/f25e42e3f5b3b7b671bd724ba7b09a9b07d13995) - annotate type for Snowflake REGEXP_INSTR function *(PR [#5978](https://github.com/tobymao/sqlglot/pull/5978) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake REGEXP_INSTR function (#5978)\n\n- due to [`13cb26e`](https://github.com/tobymao/sqlglot/commit/13cb26e2f29373538d60a8124ddebf95fd22a8d8) - annotate type for Snowflake REGEXP_SUBSTR_ALL function *(PR [#5979](https://github.com/tobymao/sqlglot/pull/5979) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake REGEXP_SUBSTR_ALL function (#5979)\n\n- due to [`4ce683e`](https://github.com/tobymao/sqlglot/commit/4ce683eb8ac5716a334cbd7625438b9f89623c7a) - Annotate type for snowflake UNICODE function *(PR [#5993](https://github.com/tobymao/sqlglot/pull/5993) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake UNICODE function (#5993)\n\n- due to [`c7657fb`](https://github.com/tobymao/sqlglot/commit/c7657fbd27a4350c424ef65947471ab9ec086831) - remove `unalias_group_by` transformation since it is unsafe *(PR [#5997](https://github.com/tobymao/sqlglot/pull/5997) by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove `unalias_group_by` transformation since it is unsafe (#5997)\n\n- due to [`587196c`](https://github.com/tobymao/sqlglot/commit/587196c9c2d122f73f9deb7e87c2831f27f6ed02) - Annotate type for snowflake STRTOK_TO_ARRAY function *(PR [#5994](https://github.com/tobymao/sqlglot/pull/5994) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake STRTOK_TO_ARRAY function (#5994)\n\n- due to [`bced710`](https://github.com/tobymao/sqlglot/commit/bced71084ffb3a8f7a11db843777f05b68f367da) - Annotate type for snowflake STRTOK function. *(PR [#5991](https://github.com/tobymao/sqlglot/pull/5991) by [@georgesittas](https://github.com/georgesittas))*:\n\n  Annotate type for snowflake STRTOK function. (#5991)\n\n- due to [`be1cdc8`](https://github.com/tobymao/sqlglot/commit/be1cdc81b511d462b710b50941d5c2770d901e91) - Fix roundtrip of ~ operator *(PR [#6017](https://github.com/tobymao/sqlglot/pull/6017) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix roundtrip of ~ operator (#6017)\n\n- due to [`74a13f2`](https://github.com/tobymao/sqlglot/commit/74a13f2a548b9cd41061e835cb3cd9dd2a5a9fb3) - Annotate type for snowflake DIV0 and DIVNULL functions *(PR [#6008](https://github.com/tobymao/sqlglot/pull/6008) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake DIV0 and DIVNULL functions (#6008)\n\n- due to [`fec2b31`](https://github.com/tobymao/sqlglot/commit/fec2b31956f2debdad7c53744a577894cd8d747c) - Annotate type for snowflake SEARCH function *(PR [#5985](https://github.com/tobymao/sqlglot/pull/5985) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake SEARCH function (#5985)\n\n- due to [`27a76cd`](https://github.com/tobymao/sqlglot/commit/27a76cdfe4212f16f945521eb3997580eacf1d61) - Annotate type for snowflake COT, SIN and TAN functions *(PR [#6022](https://github.com/tobymao/sqlglot/pull/6022) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake COT, SIN and TAN functions (#6022)\n\n- due to [`0911276`](https://github.com/tobymao/sqlglot/commit/091127663ab4cb94b02be5aa40c6a46dd7f89243) - annotate type for Snowflake EXP function *(PR [#6007](https://github.com/tobymao/sqlglot/pull/6007) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake EXP function (#6007)\n\n- due to [`a96d50e`](https://github.com/tobymao/sqlglot/commit/a96d50e14bed5e87ff2dce9c545e0c48897b64d6) - annotate type for Snowflake COSH function *(PR [#6006](https://github.com/tobymao/sqlglot/pull/6006) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake COSH function (#6006)\n\n- due to [`4df58e0`](https://github.com/tobymao/sqlglot/commit/4df58e0f0b8985590fb29a8ab6ba0ced987ac5b9) - annotate type for Snowflake DEGREES function *(PR [#6027](https://github.com/tobymao/sqlglot/pull/6027) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake DEGREES function (#6027)\n\n- due to [`db71a20`](https://github.com/tobymao/sqlglot/commit/db71a2023aaeca2ffda782ae7b91fdee356c402e) - annotate type for Snowflake COS function *(PR [#6028](https://github.com/tobymao/sqlglot/pull/6028) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake COS function (#6028)\n\n- due to [`5dd2ed3`](https://github.com/tobymao/sqlglot/commit/5dd2ed3c69cf9e8c3e327297e0cc932f0954e108) - bump sqlglotrs to 0.7.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.7.0\n\n\n### :sparkles: New Features\n- [`3c7b5c0`](https://github.com/tobymao/sqlglot/commit/3c7b5c0e2dc071b7b9f6da308ba58a3a43da93dc) - **optimizer**: Annotate type for snowflake SOUNDEX_P123 function *(PR [#5987](https://github.com/tobymao/sqlglot/pull/5987) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`475c09b`](https://github.com/tobymao/sqlglot/commit/475c09bd27179db4d186638645698dd4ad6553cd) - **optimizer**: Annotate type for snowflake TRANSLATE function *(PR [#5992](https://github.com/tobymao/sqlglot/pull/5992) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`f25e42e`](https://github.com/tobymao/sqlglot/commit/f25e42e3f5b3b7b671bd724ba7b09a9b07d13995) - **optimizer**: annotate type for Snowflake REGEXP_INSTR function *(PR [#5978](https://github.com/tobymao/sqlglot/pull/5978) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`13cb26e`](https://github.com/tobymao/sqlglot/commit/13cb26e2f29373538d60a8124ddebf95fd22a8d8) - **optimizer**: annotate type for Snowflake REGEXP_SUBSTR_ALL function *(PR [#5979](https://github.com/tobymao/sqlglot/pull/5979) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`4ce683e`](https://github.com/tobymao/sqlglot/commit/4ce683eb8ac5716a334cbd7625438b9f89623c7a) - **optimizer**: Annotate type for snowflake UNICODE function *(PR [#5993](https://github.com/tobymao/sqlglot/pull/5993) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`587196c`](https://github.com/tobymao/sqlglot/commit/587196c9c2d122f73f9deb7e87c2831f27f6ed02) - **optimizer**: Annotate type for snowflake STRTOK_TO_ARRAY function *(PR [#5994](https://github.com/tobymao/sqlglot/pull/5994) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`bced710`](https://github.com/tobymao/sqlglot/commit/bced71084ffb3a8f7a11db843777f05b68f367da) - **optimizer**: Annotate type for snowflake STRTOK function. *(PR [#5991](https://github.com/tobymao/sqlglot/pull/5991) by [@georgesittas](https://github.com/georgesittas))*\n- [`74a13f2`](https://github.com/tobymao/sqlglot/commit/74a13f2a548b9cd41061e835cb3cd9dd2a5a9fb3) - **optimizer**: Annotate type for snowflake DIV0 and DIVNULL functions *(PR [#6008](https://github.com/tobymao/sqlglot/pull/6008) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fec2b31`](https://github.com/tobymao/sqlglot/commit/fec2b31956f2debdad7c53744a577894cd8d747c) - **optimizer**: Annotate type for snowflake SEARCH function *(PR [#5985](https://github.com/tobymao/sqlglot/pull/5985) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`27a76cd`](https://github.com/tobymao/sqlglot/commit/27a76cdfe4212f16f945521eb3997580eacf1d61) - **optimizer**: Annotate type for snowflake COT, SIN and TAN functions *(PR [#6022](https://github.com/tobymao/sqlglot/pull/6022) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`8b48f7b`](https://github.com/tobymao/sqlglot/commit/8b48f7b985342cfcc45bc2b94540a1a2bf5995c4) - **optimizer**: Annotate type for snowflake SIGN and ABS functions *(PR [#6025](https://github.com/tobymao/sqlglot/pull/6025) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`0911276`](https://github.com/tobymao/sqlglot/commit/091127663ab4cb94b02be5aa40c6a46dd7f89243) - **optimizer**: annotate type for Snowflake EXP function *(PR [#6007](https://github.com/tobymao/sqlglot/pull/6007) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`a96d50e`](https://github.com/tobymao/sqlglot/commit/a96d50e14bed5e87ff2dce9c545e0c48897b64d6) - **optimizer**: annotate type for Snowflake COSH function *(PR [#6006](https://github.com/tobymao/sqlglot/pull/6006) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`4df58e0`](https://github.com/tobymao/sqlglot/commit/4df58e0f0b8985590fb29a8ab6ba0ced987ac5b9) - **optimizer**: annotate type for Snowflake DEGREES function *(PR [#6027](https://github.com/tobymao/sqlglot/pull/6027) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`db71a20`](https://github.com/tobymao/sqlglot/commit/db71a2023aaeca2ffda782ae7b91fdee356c402e) - **optimizer**: annotate type for Snowflake COS function *(PR [#6028](https://github.com/tobymao/sqlglot/pull/6028) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n\n### :bug: Bug Fixes\n- [`51b1bb1`](https://github.com/tobymao/sqlglot/commit/51b1bb178fa952edc13b2cbc6f624d30b0bde798) - move `WATERMARK` logic to risingwave fixes [#5989](https://github.com/tobymao/sqlglot/pull/5989) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`033ddf0`](https://github.com/tobymao/sqlglot/commit/033ddf04da895f1f5d38aff5361b2ae0793fefea) - **optimizer**: convert INNER JOINs to LEFT JOINs when merging LEFT JOIN subqueries *(PR [#5980](https://github.com/tobymao/sqlglot/pull/5980) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5969](https://github.com/tobymao/sqlglot/issues/5969) opened by [@karta0807913](https://github.com/karta0807913)*\n- [`c7657fb`](https://github.com/tobymao/sqlglot/commit/c7657fbd27a4350c424ef65947471ab9ec086831) - remove `unalias_group_by` transformation since it is unsafe *(PR [#5997](https://github.com/tobymao/sqlglot/pull/5997) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5995](https://github.com/tobymao/sqlglot/issues/5995) opened by [@capricornsky0119](https://github.com/capricornsky0119)*\n- [`b6f9694`](https://github.com/tobymao/sqlglot/commit/b6f9694c535cdd1403a63036cc246fda4e6d4d22) - **optimizer**: avoid merging subquery with JOIN when outer query uses JOIN *(PR [#5999](https://github.com/tobymao/sqlglot/pull/5999) by [@geooo109](https://github.com/geooo109))*\n- [`23fd7b9`](https://github.com/tobymao/sqlglot/commit/23fd7b9116541b96e5d89389e862c6004e92d109) - respect multi-part Column units instead of converting to Var *(PR [#6005](https://github.com/tobymao/sqlglot/pull/6005) by [@georgesittas](https://github.com/georgesittas))*\n- [`be1cdc8`](https://github.com/tobymao/sqlglot/commit/be1cdc81b511d462b710b50941d5c2770d901e91) - **duckdb**: Fix roundtrip of ~ operator *(PR [#6017](https://github.com/tobymao/sqlglot/pull/6017) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6016](https://github.com/tobymao/sqlglot/issues/6016) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`27c278f`](https://github.com/tobymao/sqlglot/commit/27c278f562f5ce98a1a4d31f8e66f148a1f42236) - **parser**: Allow LIMIT with % percentage *(PR [#6019](https://github.com/tobymao/sqlglot/pull/6019) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`39bf3f8`](https://github.com/tobymao/sqlglot/commit/39bf3f893389663796cdd799ef0f1e684f315a01) - **parser**: Allow CUBE & ROLLUP inside GROUPING SETS *(PR [#6018](https://github.com/tobymao/sqlglot/pull/6018) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6015](https://github.com/tobymao/sqlglot/issues/6015) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`ba7ad34`](https://github.com/tobymao/sqlglot/commit/ba7ad341d5ee1298b8fe54be11ca6252c1a44c99) - **duckdb**: Parse ROW type as STRUCT *(PR [#6020](https://github.com/tobymao/sqlglot/pull/6020) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#6012](https://github.com/tobymao/sqlglot/issues/6012) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`718d6bb`](https://github.com/tobymao/sqlglot/commit/718d6bbf7f40e5b3e99563e2f1ac9eadeff57c3d) - handle unicode heredoc tags & Rust grapheme clusters properly *(PR [#6024](https://github.com/tobymao/sqlglot/pull/6024) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#6010](https://github.com/tobymao/sqlglot/issues/6010) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`c8cfb9d`](https://github.com/tobymao/sqlglot/commit/c8cfb9db2e789be2dc7f8a154082a9210b736502) - **snowflake**: transpile ARRAY_CONTAINS with VARIANT CAST *(PR [#6029](https://github.com/tobymao/sqlglot/pull/6029) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#6026](https://github.com/tobymao/sqlglot/issues/6026) opened by [@Birkman](https://github.com/Birkman)*\n\n### :wrench: Chores\n- [`1b1c6f8`](https://github.com/tobymao/sqlglot/commit/1b1c6f8d418371d49f0d3511baf3c5e35dd3ef42) - coerce type for EXTRACT canonicalization *(PR [#5998](https://github.com/tobymao/sqlglot/pull/5998) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5996](https://github.com/tobymao/sqlglot/issues/5996) opened by [@snovik75](https://github.com/snovik75)*\n- [`f00ae73`](https://github.com/tobymao/sqlglot/commit/f00ae735c8f185b4c6c132373c9fa9bbe58e37b7) - **optimizer**: Annotate type for sqrt function *(PR [#6003](https://github.com/tobymao/sqlglot/pull/6003) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ac97f14`](https://github.com/tobymao/sqlglot/commit/ac97f14ee1a576a276018f6c9ae1237ecf9ceda7) - simplify `SEARCH` Snowflake instantiation *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`5dd2ed3`](https://github.com/tobymao/sqlglot/commit/5dd2ed3c69cf9e8c3e327297e0cc932f0954e108) - bump sqlglotrs to 0.7.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.20.0] - 2025-09-30\n### :boom: BREAKING CHANGES\n- due to [`13a30df`](https://github.com/tobymao/sqlglot/commit/13a30dfa37096df5bfc2c31538325c40a49f7917) - Annotate type for snowflake TRY_BASE64_DECODE_BINARY function *(PR [#5972](https://github.com/tobymao/sqlglot/pull/5972) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TRY_BASE64_DECODE_BINARY function (#5972)\n\n- due to [`1f5fdd7`](https://github.com/tobymao/sqlglot/commit/1f5fdd799c047de167a4572f7ac26b7ad92167f2) - Annotate type for snowflake TRY_BASE64_DECODE_STRING function *(PR [#5974](https://github.com/tobymao/sqlglot/pull/5974) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TRY_BASE64_DECODE_STRING function (#5974)\n\n- due to [`324e82f`](https://github.com/tobymao/sqlglot/commit/324e82fe1fb11722f91341010602a743b151e055) - Annotate type for snowflake TRY_HEX_DECODE_BINARY function *(PR [#5975](https://github.com/tobymao/sqlglot/pull/5975) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TRY_HEX_DECODE_BINARY function (#5975)\n\n- due to [`6caf99d`](https://github.com/tobymao/sqlglot/commit/6caf99d556a3357ffaa6c294a9babcd30dd5fac5) - Annotate type for snowflake TRY_HEX_DECODE_STRING function *(PR [#5976](https://github.com/tobymao/sqlglot/pull/5976) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake TRY_HEX_DECODE_STRING function (#5976)\n\n- due to [`73186a8`](https://github.com/tobymao/sqlglot/commit/73186a812ce422c108ee81b3de11da6ee9a9e902) - annotate type for Snowflake REGEXP_COUNT function *(PR [#5963](https://github.com/tobymao/sqlglot/pull/5963) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake REGEXP_COUNT function (#5963)\n\n- due to [`c3bdb3c`](https://github.com/tobymao/sqlglot/commit/c3bdb3cd1af1809ed82be0ae40744d9fffc8ce18) - array start index is 1, support array_flatten, fixes [#5983](https://github.com/tobymao/sqlglot/pull/5983) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  array start index is 1, support array_flatten, fixes #5983\n\n- due to [`244fb48`](https://github.com/tobymao/sqlglot/commit/244fb48fc9c4776f427c08b825d139b1c172fd26) - annotate type for Snowflake SPLIT_PART function *(PR [#5988](https://github.com/tobymao/sqlglot/pull/5988) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake SPLIT_PART function (#5988)\n\n- due to [`0d772e0`](https://github.com/tobymao/sqlglot/commit/0d772e0b9d687b24d49203c05d7a90cc1dce02d5) - add ast node for `DIRECTORY` source *(PR [#5990](https://github.com/tobymao/sqlglot/pull/5990) by [@georgesittas](https://github.com/georgesittas))*:\n\n  add ast node for `DIRECTORY` source (#5990)\n\n\n### :sparkles: New Features\n- [`13a30df`](https://github.com/tobymao/sqlglot/commit/13a30dfa37096df5bfc2c31538325c40a49f7917) - **optimizer**: Annotate type for snowflake TRY_BASE64_DECODE_BINARY function *(PR [#5972](https://github.com/tobymao/sqlglot/pull/5972) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`1f5fdd7`](https://github.com/tobymao/sqlglot/commit/1f5fdd799c047de167a4572f7ac26b7ad92167f2) - **optimizer**: Annotate type for snowflake TRY_BASE64_DECODE_STRING function *(PR [#5974](https://github.com/tobymao/sqlglot/pull/5974) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`324e82f`](https://github.com/tobymao/sqlglot/commit/324e82fe1fb11722f91341010602a743b151e055) - **optimizer**: Annotate type for snowflake TRY_HEX_DECODE_BINARY function *(PR [#5975](https://github.com/tobymao/sqlglot/pull/5975) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`6caf99d`](https://github.com/tobymao/sqlglot/commit/6caf99d556a3357ffaa6c294a9babcd30dd5fac5) - **optimizer**: Annotate type for snowflake TRY_HEX_DECODE_STRING function *(PR [#5976](https://github.com/tobymao/sqlglot/pull/5976) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`73186a8`](https://github.com/tobymao/sqlglot/commit/73186a812ce422c108ee81b3de11da6ee9a9e902) - **optimizer**: annotate type for Snowflake REGEXP_COUNT function *(PR [#5963](https://github.com/tobymao/sqlglot/pull/5963) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`6124de7`](https://github.com/tobymao/sqlglot/commit/6124de76fa6d6725e844cd37e09ebfe99469b0ec) - **optimizer**: Annotate type for snowflake SOUNDEX function *(PR [#5986](https://github.com/tobymao/sqlglot/pull/5986) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`244fb48`](https://github.com/tobymao/sqlglot/commit/244fb48fc9c4776f427c08b825d139b1c172fd26) - **optimizer**: annotate type for Snowflake SPLIT_PART function *(PR [#5988](https://github.com/tobymao/sqlglot/pull/5988) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`0d772e0`](https://github.com/tobymao/sqlglot/commit/0d772e0b9d687b24d49203c05d7a90cc1dce02d5) - **snowflake**: add ast node for `DIRECTORY` source *(PR [#5990](https://github.com/tobymao/sqlglot/pull/5990) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`7a3744f`](https://github.com/tobymao/sqlglot/commit/7a3744f203b93211e5dd97e6730b6bf59d6d96e0) - **sqlite**: support `RANGE CURRENT ROW` in window spec *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c3bdb3c`](https://github.com/tobymao/sqlglot/commit/c3bdb3cd1af1809ed82be0ae40744d9fffc8ce18) - **starrocks**: array start index is 1, support array_flatten, fixes [#5983](https://github.com/tobymao/sqlglot/pull/5983) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`d425ba2`](https://github.com/tobymao/sqlglot/commit/d425ba26b96b368801f8f486fa375cd75105993d) - make hash and eq non recursive *(PR [#5966](https://github.com/tobymao/sqlglot/pull/5966) by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`345c6a1`](https://github.com/tobymao/sqlglot/commit/345c6a153481a22d6df1b12ef1863e2133688fdf) - add uv support to Makefile *(PR [#5973](https://github.com/tobymao/sqlglot/pull/5973) by [@eakmanrq](https://github.com/eakmanrq))*\n\n\n## [v27.19.0] - 2025-09-26\n### :boom: BREAKING CHANGES\n- due to [`68473ac`](https://github.com/tobymao/sqlglot/commit/68473ac3ec8dc76512dc76819892a1b0324c7ddc) - Annotate type for snowflake PARSE_URL function *(PR [#5962](https://github.com/tobymao/sqlglot/pull/5962) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake PARSE_URL function (#5962)\n\n- due to [`b015a9d`](https://github.com/tobymao/sqlglot/commit/b015a9d944d0a87069a7750ad74953c399d7da34) - annotate type for Snowflake REGEXP_INSTR function *(commit by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake REGEXP_INSTR function\n\n- due to [`1f29ba7`](https://github.com/tobymao/sqlglot/commit/1f29ba710f4213beb1a2f993244d7d824f3536ce) - annotate type for Snowflake PARSE_IP function *(PR [#5961](https://github.com/tobymao/sqlglot/pull/5961) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake PARSE_IP function (#5961)\n\n- due to [`bf45d5d`](https://github.com/tobymao/sqlglot/commit/bf45d5d3cb0c0f380824019eb32ec29049268a61) - annotate types for Snowflake RTRIMMED_LENGTH function *(PR [#5968](https://github.com/tobymao/sqlglot/pull/5968) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake RTRIMMED_LENGTH function (#5968)\n\n- due to [`13caa69`](https://github.com/tobymao/sqlglot/commit/13caa6991f003ad7abb590073451e591b6fd888c) - Annotate type for snowflake POSITION function *(PR [#5964](https://github.com/tobymao/sqlglot/pull/5964) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake POSITION function (#5964)\n\n\n### :sparkles: New Features\n- [`88e4e4c`](https://github.com/tobymao/sqlglot/commit/88e4e4c55f3a113127eb3c82c0be46c29bcf15ab) - **optimizer**: Annotate type for OCTET_LENGTH function *(PR [#5960](https://github.com/tobymao/sqlglot/pull/5960) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`68473ac`](https://github.com/tobymao/sqlglot/commit/68473ac3ec8dc76512dc76819892a1b0324c7ddc) - **optimizer**: Annotate type for snowflake PARSE_URL function *(PR [#5962](https://github.com/tobymao/sqlglot/pull/5962) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`b015a9d`](https://github.com/tobymao/sqlglot/commit/b015a9d944d0a87069a7750ad74953c399d7da34) - **optimizer**: annotate type for Snowflake REGEXP_INSTR function *(commit by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`1f29ba7`](https://github.com/tobymao/sqlglot/commit/1f29ba710f4213beb1a2f993244d7d824f3536ce) - **optimizer**: annotate type for Snowflake PARSE_IP function *(PR [#5961](https://github.com/tobymao/sqlglot/pull/5961) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`bf45d5d`](https://github.com/tobymao/sqlglot/commit/bf45d5d3cb0c0f380824019eb32ec29049268a61) - **optimizer**: annotate types for Snowflake RTRIMMED_LENGTH function *(PR [#5968](https://github.com/tobymao/sqlglot/pull/5968) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`13caa69`](https://github.com/tobymao/sqlglot/commit/13caa6991f003ad7abb590073451e591b6fd888c) - **optimizer**: Annotate type for snowflake POSITION function *(PR [#5964](https://github.com/tobymao/sqlglot/pull/5964) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`1471306`](https://github.com/tobymao/sqlglot/commit/1471306ed317830c294e3654075f55424d14bf5a) - support parse into grant principal and privilege *(PR [#5971](https://github.com/tobymao/sqlglot/pull/5971) by [@eakmanrq](https://github.com/eakmanrq))*\n\n### :bug: Bug Fixes\n- [`5432976`](https://github.com/tobymao/sqlglot/commit/543297680755344185e0f306843bc4909f4f75ed) - **bigquery**: allow GRANT as an id var *(PR [#5965](https://github.com/tobymao/sqlglot/pull/5965) by [@treysp](https://github.com/treysp))*\n\n### :wrench: Chores\n- [`1514bc6`](https://github.com/tobymao/sqlglot/commit/1514bc640ec129a96aedd9e89bfd5d61e832d6b1) - **optimizer**: add type inference tests for Snowflake RPAD function *(PR [#5967](https://github.com/tobymao/sqlglot/pull/5967) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`050b89d`](https://github.com/tobymao/sqlglot/commit/050b89deb9be842f2ddd07c78ea201ec4eae4779) - **optimizer**: Annotate type for snowflake regexp function *(PR [#5970](https://github.com/tobymao/sqlglot/pull/5970) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n\n## [v27.18.0] - 2025-09-25\n### :boom: BREAKING CHANGES\n- due to [`7f13eaf`](https://github.com/tobymao/sqlglot/commit/7f13eaf7769a3381a56c9209af590835be2f95cd) - Annotate type for snowflake DECOMPRESS_BINARY function *(PR [#5945](https://github.com/tobymao/sqlglot/pull/5945) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake DECOMPRESS_BINARY function (#5945)\n\n- due to [`be12b29`](https://github.com/tobymao/sqlglot/commit/be12b29b5a7bd6d6e09dbd8c17086bd77c19abc0) - Annotate type for snowflake DECOMPRESS_STRING function *(PR [#5947](https://github.com/tobymao/sqlglot/pull/5947) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake DECOMPRESS_STRING function (#5947)\n\n- due to [`1573fef`](https://github.com/tobymao/sqlglot/commit/1573fefac27b5b1215e3d458f8ccf1b9dadbb772) - annotate types for Snowflake JAROWINKLER_SIMILARITY function *(PR [#5950](https://github.com/tobymao/sqlglot/pull/5950) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake JAROWINKLER_SIMILARITY function (#5950)\n\n- due to [`883c6ab`](https://github.com/tobymao/sqlglot/commit/883c6abe589865f478d95604e8d670e57afd04af) - annotate type for Snowflake COLLATION function *(PR [#5939](https://github.com/tobymao/sqlglot/pull/5939) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake COLLATION function (#5939)\n\n\n### :sparkles: New Features\n- [`7f13eaf`](https://github.com/tobymao/sqlglot/commit/7f13eaf7769a3381a56c9209af590835be2f95cd) - **optimizer**: Annotate type for snowflake DECOMPRESS_BINARY function *(PR [#5945](https://github.com/tobymao/sqlglot/pull/5945) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`be12b29`](https://github.com/tobymao/sqlglot/commit/be12b29b5a7bd6d6e09dbd8c17086bd77c19abc0) - **optimizer**: Annotate type for snowflake DECOMPRESS_STRING function *(PR [#5947](https://github.com/tobymao/sqlglot/pull/5947) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`a55fce5`](https://github.com/tobymao/sqlglot/commit/a55fce5310a50af132c5d06bb299fe3f025442c4) - **optimizer**: Annotate type for snowflake LPAD function *(PR [#5948](https://github.com/tobymao/sqlglot/pull/5948) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`05e07aa`](https://github.com/tobymao/sqlglot/commit/05e07aa740d7977a6b42ec15ae4fa9c2168a15f5) - **optimizer**: annotate type for Snowflake INSERT function *(PR [#5942](https://github.com/tobymao/sqlglot/pull/5942) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`6268e10`](https://github.com/tobymao/sqlglot/commit/6268e107a947badaa00508544f5389412806ecd0) - **solr**: initial dialect implementation *(PR [#5946](https://github.com/tobymao/sqlglot/pull/5946) by [@aadel](https://github.com/aadel))*\n- [`1573fef`](https://github.com/tobymao/sqlglot/commit/1573fefac27b5b1215e3d458f8ccf1b9dadbb772) - **optimizer**: annotate types for Snowflake JAROWINKLER_SIMILARITY function *(PR [#5950](https://github.com/tobymao/sqlglot/pull/5950) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`883c6ab`](https://github.com/tobymao/sqlglot/commit/883c6abe589865f478d95604e8d670e57afd04af) - **optimizer**: annotate type for Snowflake COLLATION function *(PR [#5939](https://github.com/tobymao/sqlglot/pull/5939) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`627c18d`](https://github.com/tobymao/sqlglot/commit/627c18d7da6bf644bc14c0f17963dea0be20604a) - **mysql**: add valid INTERVAL units *(PR [#5951](https://github.com/tobymao/sqlglot/pull/5951) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`3846d4d`](https://github.com/tobymao/sqlglot/commit/3846d4dcdf8cbf8e90b2661083a567ab0547ad3c) - **solr**: properly support OR alternative operator *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`df428d5`](https://github.com/tobymao/sqlglot/commit/df428d516113a47ae50d04cd50a250830589c072) - **parser**: interval identifier followed by END *(PR [#5944](https://github.com/tobymao/sqlglot/pull/5944) by [@geooo109](https://github.com/geooo109))*\n- [`e178d16`](https://github.com/tobymao/sqlglot/commit/e178d1674a71e6f35a6acfa8f4a317f0fe2e4516) - **duckdb**: UNNEST as table *(PR [#5953](https://github.com/tobymao/sqlglot/pull/5953) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5952](https://github.com/tobymao/sqlglot/issues/5952) opened by [@denis-komarov](https://github.com/denis-komarov)*\n- [`24feb8e`](https://github.com/tobymao/sqlglot/commit/24feb8ee0bc43f3f14fd768c9a0d986355becea2) - **parser**: parse `UPDATE` clauses in any order *(PR [#5958](https://github.com/tobymao/sqlglot/pull/5958) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5956](https://github.com/tobymao/sqlglot/issues/5956) opened by [@sfc-gh-clathrope](https://github.com/sfc-gh-clathrope)*\n- [`980f99a`](https://github.com/tobymao/sqlglot/commit/980f99a4cc0613012a189ee5636af37ec736040c) - **snowflake**: properly generate inferred `STRUCT` data types *(PR [#5954](https://github.com/tobymao/sqlglot/pull/5954) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`c18aaf8`](https://github.com/tobymao/sqlglot/commit/c18aaf80fd7375e89dfc8863da619d84f3257353) - cleanup *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v27.17.0] - 2025-09-23\n### :boom: BREAKING CHANGES\n- due to [`f4ad258`](https://github.com/tobymao/sqlglot/commit/f4ad25882951de4e4442dfd5189a56d5a1c5e630) - Annotate types for Snowflake BASE64_DECODE_BINARY function *(PR [#5917](https://github.com/tobymao/sqlglot/pull/5917) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate types for Snowflake BASE64_DECODE_BINARY function (#5917)\n\n- due to [`6d0e3f8`](https://github.com/tobymao/sqlglot/commit/6d0e3f8dcae7ed1a7659ece69b1f94cec5e7300e) - Add parser support to ilike like function versions. *(PR [#5915](https://github.com/tobymao/sqlglot/pull/5915) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add parser support to ilike like function versions. (#5915)\n\n- due to [`22c7ed7`](https://github.com/tobymao/sqlglot/commit/22c7ed7734b41ca544bb67bcc1ca4151f6d5f05f) - parse tuple *(PR [#5920](https://github.com/tobymao/sqlglot/pull/5920) by [@geooo109](https://github.com/geooo109))*:\n\n  parse tuple (#5920)\n\n- due to [`fc5624e`](https://github.com/tobymao/sqlglot/commit/fc5624eca43d2855ac350c92d85b184a6893d5ca) - annotate types for Snowflake ASCII function *(PR [#5926](https://github.com/tobymao/sqlglot/pull/5926) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake ASCII function (#5926)\n\n- due to [`4e81690`](https://github.com/tobymao/sqlglot/commit/4e8169045edcaa28ae43abeb07370df63846fbfd) - annotate type for Snowflake COLLATE function *(PR [#5931](https://github.com/tobymao/sqlglot/pull/5931) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake COLLATE function (#5931)\n\n- due to [`f07d35d`](https://github.com/tobymao/sqlglot/commit/f07d35d29104c6203efaab738118d1903614b83c) - annotate type for Snowflake CHR function *(PR [#5929](https://github.com/tobymao/sqlglot/pull/5929) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake CHR function (#5929)\n\n- due to [`f8c0ee4`](https://github.com/tobymao/sqlglot/commit/f8c0ee4d3c1a4d4a92b897d1cc85f9904c8e566b) - Add function and annotate snowflake hex decode string and binary functions *(PR [#5928](https://github.com/tobymao/sqlglot/pull/5928) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Add function and annotate snowflake hex decode string and binary functions (#5928)\n\n- due to [`66f9501`](https://github.com/tobymao/sqlglot/commit/66f9501d76d087798bad93e578273ab2a45e2575) - annotate types for Snowflake BIT_LENGTH function *(PR [#5927](https://github.com/tobymao/sqlglot/pull/5927) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake BIT_LENGTH function (#5927)\n\n- due to [`7878437`](https://github.com/tobymao/sqlglot/commit/78784370712df65a2e1e79a1c2b441131ed7222a) - annotate snowflake's `BASE64_DECODE_STRING`, `BASE64_ENCODE` *(PR [#5922](https://github.com/tobymao/sqlglot/pull/5922) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  annotate snowflake's `BASE64_DECODE_STRING`, `BASE64_ENCODE` (#5922)\n\n- due to [`9bcad04`](https://github.com/tobymao/sqlglot/commit/9bcad040bd51dd03821c68eea1a73534fc7a81b7) - Annotate type for HEX ENCODE function. *(PR [#5936](https://github.com/tobymao/sqlglot/pull/5936) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for HEX ENCODE function. (#5936)\n\n- due to [`590928f`](https://github.com/tobymao/sqlglot/commit/590928f4637306e8cf3f1302d5dd5d5dbc76e7e0) - annotate type for Snowflake INITCAP function *(PR [#5941](https://github.com/tobymao/sqlglot/pull/5941) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake INITCAP function (#5941)\n\n- due to [`ac04de1`](https://github.com/tobymao/sqlglot/commit/ac04de1944c7a976406581b489b3cf9b11dafb77) - annotate type for Snowflake EDITDISTANCE function *(PR [#5940](https://github.com/tobymao/sqlglot/pull/5940) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for Snowflake EDITDISTANCE function (#5940)\n\n- due to [`9e28af8`](https://github.com/tobymao/sqlglot/commit/9e28af8a52ced951ecf7f4e85a6305e20a13de1f) - Annotate type for snowflake COMPRESS function *(PR [#5938](https://github.com/tobymao/sqlglot/pull/5938) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate type for snowflake COMPRESS function (#5938)\n\n\n### :sparkles: New Features\n- [`f4ad258`](https://github.com/tobymao/sqlglot/commit/f4ad25882951de4e4442dfd5189a56d5a1c5e630) - **optimizer**: Annotate types for Snowflake BASE64_DECODE_BINARY function *(PR [#5917](https://github.com/tobymao/sqlglot/pull/5917) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`6d0e3f8`](https://github.com/tobymao/sqlglot/commit/6d0e3f8dcae7ed1a7659ece69b1f94cec5e7300e) - **optimizer**: Add parser support to ilike like function versions. *(PR [#5915](https://github.com/tobymao/sqlglot/pull/5915) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fc5624e`](https://github.com/tobymao/sqlglot/commit/fc5624eca43d2855ac350c92d85b184a6893d5ca) - **optimizer**: annotate types for Snowflake ASCII function *(PR [#5926](https://github.com/tobymao/sqlglot/pull/5926) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`4e81690`](https://github.com/tobymao/sqlglot/commit/4e8169045edcaa28ae43abeb07370df63846fbfd) - **optimizer**: annotate type for Snowflake COLLATE function *(PR [#5931](https://github.com/tobymao/sqlglot/pull/5931) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f07d35d`](https://github.com/tobymao/sqlglot/commit/f07d35d29104c6203efaab738118d1903614b83c) - **optimizer**: annotate type for Snowflake CHR function *(PR [#5929](https://github.com/tobymao/sqlglot/pull/5929) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f8c0ee4`](https://github.com/tobymao/sqlglot/commit/f8c0ee4d3c1a4d4a92b897d1cc85f9904c8e566b) - **optimizer**: Add function and annotate snowflake hex decode string and binary functions *(PR [#5928](https://github.com/tobymao/sqlglot/pull/5928) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`66f9501`](https://github.com/tobymao/sqlglot/commit/66f9501d76d087798bad93e578273ab2a45e2575) - **optimizer**: annotate types for Snowflake BIT_LENGTH function *(PR [#5927](https://github.com/tobymao/sqlglot/pull/5927) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f4c810e`](https://github.com/tobymao/sqlglot/commit/f4c810e043d9379e94efb185e368e27ad9c15715) - transpile Trino `FORMAT` to DuckDB and Snowflake, closes [#5933](https://github.com/tobymao/sqlglot/pull/5933) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7878437`](https://github.com/tobymao/sqlglot/commit/78784370712df65a2e1e79a1c2b441131ed7222a) - **optimizer**: annotate snowflake's `BASE64_DECODE_STRING`, `BASE64_ENCODE` *(PR [#5922](https://github.com/tobymao/sqlglot/pull/5922) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`9bcad04`](https://github.com/tobymao/sqlglot/commit/9bcad040bd51dd03821c68eea1a73534fc7a81b7) - **optimizer**: Annotate type for HEX ENCODE function. *(PR [#5936](https://github.com/tobymao/sqlglot/pull/5936) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`590928f`](https://github.com/tobymao/sqlglot/commit/590928f4637306e8cf3f1302d5dd5d5dbc76e7e0) - **optimizer**: annotate type for Snowflake INITCAP function *(PR [#5941](https://github.com/tobymao/sqlglot/pull/5941) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`ac04de1`](https://github.com/tobymao/sqlglot/commit/ac04de1944c7a976406581b489b3cf9b11dafb77) - **optimizer**: annotate type for Snowflake EDITDISTANCE function *(PR [#5940](https://github.com/tobymao/sqlglot/pull/5940) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`a385990`](https://github.com/tobymao/sqlglot/commit/a38599080932a8b54a169df8b7a69650cb47b6bc) - **parser**: support wrapped aggregate functions *(PR [#5943](https://github.com/tobymao/sqlglot/pull/5943) by [@geooo109](https://github.com/geooo109))*\n- [`9e28af8`](https://github.com/tobymao/sqlglot/commit/9e28af8a52ced951ecf7f4e85a6305e20a13de1f) - **optimizer**: Annotate type for snowflake COMPRESS function *(PR [#5938](https://github.com/tobymao/sqlglot/pull/5938) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n### :bug: Bug Fixes\n- [`6807a32`](https://github.com/tobymao/sqlglot/commit/6807a32cccf984dc13a30b815750b2c41374b845) - escape byte string delimiters *(PR [#5916](https://github.com/tobymao/sqlglot/pull/5916) by [@georgesittas](https://github.com/georgesittas))*\n- [`22c7ed7`](https://github.com/tobymao/sqlglot/commit/22c7ed7734b41ca544bb67bcc1ca4151f6d5f05f) - **clickhouse**: parse tuple *(PR [#5920](https://github.com/tobymao/sqlglot/pull/5920) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5913](https://github.com/tobymao/sqlglot/issues/5913) opened by [@tiagoskaneta](https://github.com/tiagoskaneta)*\n- [`223160b`](https://github.com/tobymao/sqlglot/commit/223160bd7914d51e9ec1abb8d0f1053e13a65c98) - **parser**: NULLABLE as an identifier *(PR [#5921](https://github.com/tobymao/sqlglot/pull/5921) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5919](https://github.com/tobymao/sqlglot/issues/5919) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`42cfc79`](https://github.com/tobymao/sqlglot/commit/42cfc79ce120dee83084e2bb6b8bbd19f45bf06f) - **snowflake**: parse DAYOFWEEKISO *(PR [#5925](https://github.com/tobymao/sqlglot/pull/5925) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5924](https://github.com/tobymao/sqlglot/issues/5924) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`0be2cb4`](https://github.com/tobymao/sqlglot/commit/0be2cb448ee1a5ac020ac47e9944875c30e42632) - **postgres**: support `DISTINCT` qualifier in `JSON_AGG` fixes [#5935](https://github.com/tobymao/sqlglot/pull/5935) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`e34b2e1`](https://github.com/tobymao/sqlglot/commit/e34b2e14d1f87d095955765173a5e17fc9985220) - allow grouping set parser to consume more syntax fixes [#5937](https://github.com/tobymao/sqlglot/pull/5937) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.16.3] - 2025-09-18\n### :bug: Bug Fixes\n- [`d127051`](https://github.com/tobymao/sqlglot/commit/d1270517c3e124ca59caf29e4506eb3848f7452e) - precedence issue with column operator parsing *(PR [#5914](https://github.com/tobymao/sqlglot/pull/5914) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.16.2] - 2025-09-18\n### :wrench: Chores\n- [`837890c`](https://github.com/tobymao/sqlglot/commit/837890c7e8bcc3695541bbe32fd8088eee70fea3) - handle badly formed binary expressions gracefully in type inference *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.16.1] - 2025-09-18\n### :bug: Bug Fixes\n- [`0e256b3`](https://github.com/tobymao/sqlglot/commit/0e256b3f864bc2d026817bd08e89ee89f44ad256) - edge case with parsing `interval` as identifier *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.16.0] - 2025-09-18\n### :boom: BREAKING CHANGES\n- due to [`5a973e9`](https://github.com/tobymao/sqlglot/commit/5a973e9a88fa7f522a9bf91dc60fb0f6effef53d) - annotate types for Snowflake AI_CLASSIFY function *(PR [#5909](https://github.com/tobymao/sqlglot/pull/5909) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake AI_CLASSIFY function (#5909)\n\n- due to [`2d0d908`](https://github.com/tobymao/sqlglot/commit/2d0d908b5bbc32ff3bc92eb1ae9fc6e5ac3409bc) - produce TableAlias instead of Alias for USING in merge builder *(PR [#5911](https://github.com/tobymao/sqlglot/pull/5911) by [@georgesittas](https://github.com/georgesittas))*:\n\n  produce TableAlias instead of Alias for USING in merge builder (#5911)\n\n\n### :sparkles: New Features\n- [`5a973e9`](https://github.com/tobymao/sqlglot/commit/5a973e9a88fa7f522a9bf91dc60fb0f6effef53d) - **optimizer**: annotate types for Snowflake AI_CLASSIFY function *(PR [#5909](https://github.com/tobymao/sqlglot/pull/5909) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n\n### :bug: Bug Fixes\n- [`2d0d908`](https://github.com/tobymao/sqlglot/commit/2d0d908b5bbc32ff3bc92eb1ae9fc6e5ac3409bc) - produce TableAlias instead of Alias for USING in merge builder *(PR [#5911](https://github.com/tobymao/sqlglot/pull/5911) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5910](https://github.com/tobymao/sqlglot/issues/5910) opened by [@deepyaman](https://github.com/deepyaman)*\n\n### :wrench: Chores\n- [`e8974e7`](https://github.com/tobymao/sqlglot/commit/e8974e70d9956ce7a5cb119ba465660f5f172a17) - **optimizer**: Add tests for snowflake likeall, likeany and ilikeany functions *(PR [#5908](https://github.com/tobymao/sqlglot/pull/5908) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n\n## [v27.15.3] - 2025-09-17\n### :bug: Bug Fixes\n- [`bd3e965`](https://github.com/tobymao/sqlglot/commit/bd3e9655aa72ffef8a9e0221205fa2c3915ef58b) - allow `lock` to be used as an identifier *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.15.2] - 2025-09-17\n### :sparkles: New Features\n- [`d5cf114`](https://github.com/tobymao/sqlglot/commit/d5cf1149932850a91cb5f1ebecda2652616729ef) - **duckdb**: support INSTALL *(PR [#5904](https://github.com/tobymao/sqlglot/pull/5904) by [@geooo109](https://github.com/geooo109))*\n- [`73e05bb`](https://github.com/tobymao/sqlglot/commit/73e05bb15bb86e4a07cc09bf02028a6cf7fa1e6f) - **snowflake**: properly generate `BITNOT` *(PR [#5906](https://github.com/tobymao/sqlglot/pull/5906) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`16f317c`](https://github.com/tobymao/sqlglot/commit/16f317c04f7c0a398c38b461e05f4d4c30baf98b) - **snowflake**: add support for `<model>!<attribute>` syntax *(PR [#5907](https://github.com/tobymao/sqlglot/pull/5907) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`095b2ac`](https://github.com/tobymao/sqlglot/commit/095b2ac3af230eff86d9bc1b0fd3a0a2095f151c) - clean up duckdb INSTALL tests *(commit by [@geooo109](https://github.com/geooo109))*\n\n\n## [v27.15.1] - 2025-09-17\n### :sparkles: New Features\n- [`1ee026d`](https://github.com/tobymao/sqlglot/commit/1ee026d22d4f6c3613c1809a6738cdea846c48a9) - **postgres**: support `SUBSTRING(value FOR length FROM start)` variant *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.15.0] - 2025-09-17\n### :boom: BREAKING CHANGES\n- due to [`96ae7a3`](https://github.com/tobymao/sqlglot/commit/96ae7a3bcbf9de1932150baa0bd704d4ce05c9f7) - Annotate and add tests for snowflake REPEAT and SPLIT functions *(PR [#5875](https://github.com/tobymao/sqlglot/pull/5875) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate and add tests for snowflake REPEAT and SPLIT functions (#5875)\n\n- due to [`f2d3bf7`](https://github.com/tobymao/sqlglot/commit/f2d3bf74e804e5a5e2ac6ca94210ba04df07e7f3) - annotate types for Snowflake UUID_STRING function *(PR [#5881](https://github.com/tobymao/sqlglot/pull/5881) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake UUID_STRING function (#5881)\n\n- due to [`ec80ff3`](https://github.com/tobymao/sqlglot/commit/ec80ff34957c3e3f80c44175383b06cf72988a68) - make dump a list instead of a nested dict to avoid all recursion errors *(PR [#5885](https://github.com/tobymao/sqlglot/pull/5885) by [@tobymao](https://github.com/tobymao))*:\n\n  make dump a list instead of a nested dict to avoid all recursion errors (#5885)\n\n- due to [`2fdaccd`](https://github.com/tobymao/sqlglot/commit/2fdaccd1a9045bda3d529025a4706c397b8a836f) - annotate types for Snowflake SHA1, SHA2 functions *(PR [#5884](https://github.com/tobymao/sqlglot/pull/5884) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake SHA1, SHA2 functions (#5884)\n\n- due to [`faba309`](https://github.com/tobymao/sqlglot/commit/faba30905390e5efaf0ba9a05aab9ac2724b1b85) - annotate types for Snowflake AI_AGG function *(PR [#5894](https://github.com/tobymao/sqlglot/pull/5894) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake AI_AGG function (#5894)\n\n- due to [`304bec5`](https://github.com/tobymao/sqlglot/commit/304bec5f7342501ad28ea4cd0a4b9aa092f2192f) - Annotate snowflake MD5 functions *(PR [#5883](https://github.com/tobymao/sqlglot/pull/5883) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate snowflake MD5 functions (#5883)\n\n- due to [`c0180ec`](https://github.com/tobymao/sqlglot/commit/c0180ec163a43836fed754efcb6f26ad37cdae50) - annotate types for Snowflake AI_SUMMARIZE_AGG function *(PR [#5902](https://github.com/tobymao/sqlglot/pull/5902) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake AI_SUMMARIZE_AGG function (#5902)\n\n- due to [`f5409df`](https://github.com/tobymao/sqlglot/commit/f5409df64ed6069880669878db687e4b98c3e280) - use column name in struct type annotation *(PR [#5903](https://github.com/tobymao/sqlglot/pull/5903) by [@georgesittas](https://github.com/georgesittas))*:\n\n  use column name in struct type annotation (#5903)\n\n\n### :sparkles: New Features\n- [`cd818ba`](https://github.com/tobymao/sqlglot/commit/cd818bad51e93ec349b97675e4c1f5bd7c4c1522) - **singlestore**: Fixed generation/parsing of computed collumns *(PR [#5878](https://github.com/tobymao/sqlglot/pull/5878) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`5d1f241`](https://github.com/tobymao/sqlglot/commit/5d1f241209197419111e9eda37fb6f2a5ec2bc4b) - **tsql**: support JSON_ARRAYAGG *(PR [#5879](https://github.com/tobymao/sqlglot/pull/5879) by [@geooo109](https://github.com/geooo109))*\n- [`96ae7a3`](https://github.com/tobymao/sqlglot/commit/96ae7a3bcbf9de1932150baa0bd704d4ce05c9f7) - **optimizer**: Annotate and add tests for snowflake REPEAT and SPLIT functions *(PR [#5875](https://github.com/tobymao/sqlglot/pull/5875) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`0fe6a25`](https://github.com/tobymao/sqlglot/commit/0fe6a25e366dcbc5a4a0878b285d147a6aa00412) - **postgres**: support JSON_AGG *(PR [#5880](https://github.com/tobymao/sqlglot/pull/5880) by [@geooo109](https://github.com/geooo109))*\n- [`854eeeb`](https://github.com/tobymao/sqlglot/commit/854eeeb5b25954cc26b91135d58eb8370271f1de) - **optimizer**: annotate types for Snowflake REGEXP_LIKE, REGEXP_REPLACE, REGEXP_SUBSTR functions *(PR [#5876](https://github.com/tobymao/sqlglot/pull/5876) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`f2d3bf7`](https://github.com/tobymao/sqlglot/commit/f2d3bf74e804e5a5e2ac6ca94210ba04df07e7f3) - **optimizer**: annotate types for Snowflake UUID_STRING function *(PR [#5881](https://github.com/tobymao/sqlglot/pull/5881) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`5b9463a`](https://github.com/tobymao/sqlglot/commit/5b9463ad11a49c821585985c35394ebb30e827dd) - **mysql**: add support for binary `MOD` operator fixes [#5887](https://github.com/tobymao/sqlglot/pull/5887) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d24eabc`](https://github.com/tobymao/sqlglot/commit/d24eabcbe30dc0f7c2dbae346e429efef58b5680) - **bigquery**: Add support for ML.GENERATE_TEXT_EMBEDDING(...) *(PR [#5891](https://github.com/tobymao/sqlglot/pull/5891) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`950a3fa`](https://github.com/tobymao/sqlglot/commit/950a3fa6d6307f7713f40117655da2f9710ebfa9) - **mysql**: SOUNDS LIKE, SUBSTR *(PR [#5886](https://github.com/tobymao/sqlglot/pull/5886) by [@vuvova](https://github.com/vuvova))*\n- [`688afc5`](https://github.com/tobymao/sqlglot/commit/688afc55ab08588636eba92893c603ca68e43e6e) - **singlestore**: Fixed generation of exp.National *(PR [#5890](https://github.com/tobymao/sqlglot/pull/5890) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`c77147e`](https://github.com/tobymao/sqlglot/commit/c77147ebaafa6942f80af75dd6c2d7a62a7e6fe2) - **parser**: Extend support for `IS UNKOWN` across all dialects *(PR [#5888](https://github.com/tobymao/sqlglot/pull/5888) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ec80ff3`](https://github.com/tobymao/sqlglot/commit/ec80ff34957c3e3f80c44175383b06cf72988a68) - make dump a list instead of a nested dict to avoid all recursion errors *(PR [#5885](https://github.com/tobymao/sqlglot/pull/5885) by [@tobymao](https://github.com/tobymao))*\n- [`2fdaccd`](https://github.com/tobymao/sqlglot/commit/2fdaccd1a9045bda3d529025a4706c397b8a836f) - **optimizer**: annotate types for Snowflake SHA1, SHA2 functions *(PR [#5884](https://github.com/tobymao/sqlglot/pull/5884) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`faba309`](https://github.com/tobymao/sqlglot/commit/faba30905390e5efaf0ba9a05aab9ac2724b1b85) - **optimizer**: annotate types for Snowflake AI_AGG function *(PR [#5894](https://github.com/tobymao/sqlglot/pull/5894) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`dd27844`](https://github.com/tobymao/sqlglot/commit/dd2784435c7bdd2ceaaaaa359fcd112ad1f8190c) - **snowflake**: transpile `BYTE_LENGTH` *(PR [#5899](https://github.com/tobymao/sqlglot/pull/5899) by [@ozadari](https://github.com/ozadari))*\n- [`304bec5`](https://github.com/tobymao/sqlglot/commit/304bec5f7342501ad28ea4cd0a4b9aa092f2192f) - **optimizer**: Annotate snowflake MD5 functions *(PR [#5883](https://github.com/tobymao/sqlglot/pull/5883) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ec3006d`](https://github.com/tobymao/sqlglot/commit/ec3006d815951fdc1a80d6722ce6f1176417d595) - **optimizer**: Add tests for snowflake NOT ILIKE and NOT LIKE *(PR [#5901](https://github.com/tobymao/sqlglot/pull/5901) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c0180ec`](https://github.com/tobymao/sqlglot/commit/c0180ec163a43836fed754efcb6f26ad37cdae50) - **optimizer**: annotate types for Snowflake AI_SUMMARIZE_AGG function *(PR [#5902](https://github.com/tobymao/sqlglot/pull/5902) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n\n### :bug: Bug Fixes\n- [`1d9e357`](https://github.com/tobymao/sqlglot/commit/1d9e357fb7549635ca25c6c42299880d7864e074) - **optimizer**: expand columns on the LHS of recursive CTEs *(PR [#5872](https://github.com/tobymao/sqlglot/pull/5872) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5814](https://github.com/tobymao/sqlglot/issues/5814) opened by [@suresh-summation](https://github.com/suresh-summation)*\n- [`7fcc52a`](https://github.com/tobymao/sqlglot/commit/7fcc52a22241c480c22b3e6f843e7a210c75a0ec) - **parser**: Require an explicit alias in EXCLUDE/RENAME/REPLACE star ops *(PR [#5892](https://github.com/tobymao/sqlglot/pull/5892) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5fdcc65`](https://github.com/tobymao/sqlglot/commit/5fdcc651277ba4e86e11d0c5952a56e40299a998) - **snowflake**: parse OCTET_LENGTH *(PR [#5900](https://github.com/tobymao/sqlglot/pull/5900) by [@geooo109](https://github.com/geooo109))*\n- [`f5409df`](https://github.com/tobymao/sqlglot/commit/f5409df64ed6069880669878db687e4b98c3e280) - **optimizer**: use column name in struct type annotation *(PR [#5903](https://github.com/tobymao/sqlglot/pull/5903) by [@georgesittas](https://github.com/georgesittas))*\n- [`74886d8`](https://github.com/tobymao/sqlglot/commit/74886d82f70c9317af51c77b322e67a6aa260a5e) - **snowflake**: transpile BQ UNNEST with alias *(PR [#5897](https://github.com/tobymao/sqlglot/pull/5897) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5895](https://github.com/tobymao/sqlglot/issues/5895) opened by [@YuvalOmerRep](https://github.com/YuvalOmerRep)*\n\n\n## [v27.14.0] - 2025-09-11\n### :boom: BREAKING CHANGES\n- due to [`9c8a600`](https://github.com/tobymao/sqlglot/commit/9c8a6001f41816035f391d046eb9692d6f13cefc) - correct parsing of TO_VARCHAR *(PR [#5840](https://github.com/tobymao/sqlglot/pull/5840) by [@geooo109](https://github.com/geooo109))*:\n\n  correct parsing of TO_VARCHAR (#5840)\n\n- due to [`1e9aef1`](https://github.com/tobymao/sqlglot/commit/1e9aef1bb20f4dc5e9c03d59cb3165c235c11ce1) - convert NULL annotations to UNKNOWN *(PR [#5842](https://github.com/tobymao/sqlglot/pull/5842) by [@georgesittas](https://github.com/georgesittas))*:\n\n  convert NULL annotations to UNKNOWN (#5842)\n\n- due to [`44c9e70`](https://github.com/tobymao/sqlglot/commit/44c9e70bd8c9421035eb0e87e4286061ec5d2fa8) - add tests for snowflake STARTSWITH function *(PR [#5847](https://github.com/tobymao/sqlglot/pull/5847) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  add tests for snowflake STARTSWITH function (#5847)\n\n- due to [`0779c2d`](https://github.com/tobymao/sqlglot/commit/0779c2d4e8ce0228592de6882763940783fa5e87) - support BIT_X aggregates again for duckdb, postgres *(PR [#5851](https://github.com/tobymao/sqlglot/pull/5851) by [@georgesittas](https://github.com/georgesittas))*:\n\n  support BIT_X aggregates again for duckdb, postgres (#5851)\n\n- due to [`c50d6e3`](https://github.com/tobymao/sqlglot/commit/c50d6e3c7b96f00d27c34a02c8e0dced21e6c373) - annotate type for snowflake LEFT, RIGHT and SUBSTRING functions *(PR [#5849](https://github.com/tobymao/sqlglot/pull/5849) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  annotate type for snowflake LEFT, RIGHT and SUBSTRING functions (#5849)\n\n- due to [`e441e16`](https://github.com/tobymao/sqlglot/commit/e441e16991626c2da2d38bc9c3a2b408e3f773bd) - make dump/pickling non-recursive to avoid hitting stack limits *(PR [#5850](https://github.com/tobymao/sqlglot/pull/5850) by [@tobymao](https://github.com/tobymao))*:\n\n  make dump/pickling non-recursive to avoid hitting stack limits (#5850)\n\n- due to [`b128339`](https://github.com/tobymao/sqlglot/commit/b12833977e2a395712481cf11e293fdbd70fd4ce) - annotate and add tests for snowflake LENGTH and LOWER functions *(PR [#5856](https://github.com/tobymao/sqlglot/pull/5856) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  annotate and add tests for snowflake LENGTH and LOWER functions (#5856)\n\n- due to [`134957a`](https://github.com/tobymao/sqlglot/commit/134957af11c55a4ab16f58d0725d6bb8ab23eb28) - annotate types for Snowflake TRIM function *(PR [#5811](https://github.com/tobymao/sqlglot/pull/5811) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate types for Snowflake TRIM function (#5811)\n\n- due to [`d3cd6bf`](https://github.com/tobymao/sqlglot/commit/d3cd6bf6e5fbaa490868ee3cd2cc99dd5e40a396) - Annotate and add tests for snowflake REPLACE and SPACE functions *(PR [#5871](https://github.com/tobymao/sqlglot/pull/5871) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  Annotate and add tests for snowflake REPLACE and SPACE functions (#5871)\n\n\n### :sparkles: New Features\n- [`a398fb4`](https://github.com/tobymao/sqlglot/commit/a398fb4df28c868f4cfc34530044b9d7b78e2e90) - **singlestore**: Splitted truncation of multiple tables into several queries *(PR [#5839](https://github.com/tobymao/sqlglot/pull/5839) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`cd27c96`](https://github.com/tobymao/sqlglot/commit/cd27c96fe85aba5f54116f38649edd8db064a5e6) - **snowflake**: transpile `TO_HEX` from bigquery *(PR [#5838](https://github.com/tobymao/sqlglot/pull/5838) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`d2e4ab7`](https://github.com/tobymao/sqlglot/commit/d2e4ab7df41ae3601e9b66e1338db3d851729339) - **snowflake**: add tests for endswith function *(PR [#5846](https://github.com/tobymao/sqlglot/pull/5846) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`c50d6e3`](https://github.com/tobymao/sqlglot/commit/c50d6e3c7b96f00d27c34a02c8e0dced21e6c373) - **optimizer**: annotate type for snowflake LEFT, RIGHT and SUBSTRING functions *(PR [#5849](https://github.com/tobymao/sqlglot/pull/5849) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ca6c8f7`](https://github.com/tobymao/sqlglot/commit/ca6c8f753ba8458544439e20671f0981c98d168d) - **singlestore**: Improved parsting/generation of exp.Show *(PR [#5853](https://github.com/tobymao/sqlglot/pull/5853) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`722eceb`](https://github.com/tobymao/sqlglot/commit/722ecebfa43aa5948031edd1828b6482a241d9ef) - **snowflake**: MD5Digest transpiling to MD5_BINARY *(PR [#5855](https://github.com/tobymao/sqlglot/pull/5855) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`b128339`](https://github.com/tobymao/sqlglot/commit/b12833977e2a395712481cf11e293fdbd70fd4ce) - **optimizer**: annotate and add tests for snowflake LENGTH and LOWER functions *(PR [#5856](https://github.com/tobymao/sqlglot/pull/5856) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`134957a`](https://github.com/tobymao/sqlglot/commit/134957af11c55a4ab16f58d0725d6bb8ab23eb28) - **optimizer**: annotate types for Snowflake TRIM function *(PR [#5811](https://github.com/tobymao/sqlglot/pull/5811) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`0475dae`](https://github.com/tobymao/sqlglot/commit/0475dae21231b85407bf778fd9f1abaecdeb68de) - **singlestore**: Marked several exp.Describe args as unsupported *(PR [#5861](https://github.com/tobymao/sqlglot/pull/5861) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`7a07b41`](https://github.com/tobymao/sqlglot/commit/7a07b41b2357149adc6afb50bb98e37e6a3175f1) - **optimizer**: Add tests for snowflake LTRIM and RTRIM functions *(PR [#5857](https://github.com/tobymao/sqlglot/pull/5857) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`fb90666`](https://github.com/tobymao/sqlglot/commit/fb90666ff3e710d70815a68defde3dc85aeef7b3) - **singlestore**: Added collate handling to exp.AlterColumn *(PR [#5864](https://github.com/tobymao/sqlglot/pull/5864) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`2f27692`](https://github.com/tobymao/sqlglot/commit/2f276929d6b6f788eb5b3ee0b1a8a8c108833474) - **snowflake**: JSONFormat transpiling to TO_JSON  *(PR [#5860](https://github.com/tobymao/sqlglot/pull/5860) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`487c811`](https://github.com/tobymao/sqlglot/commit/487c8119cbfaf2783f5f17ec90c8e69e4432a4fa) - **singlestore**: Fixed parsing/generation of exp.RenameColumn *(PR [#5865](https://github.com/tobymao/sqlglot/pull/5865) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`76cf4d8`](https://github.com/tobymao/sqlglot/commit/76cf4d892a6d011a2e0020fb1ea82518d4f49e71) - **bigquery**: add support for ML.TRANSLATE func *(PR [#5859](https://github.com/tobymao/sqlglot/pull/5859) by [@geooo109](https://github.com/geooo109))*\n- [`a899eb1`](https://github.com/tobymao/sqlglot/commit/a899eb188d5e354d3ed56d1e7c32861eecf3e906) - **singlestore**: Fixed parsing and generation of VECTOR type *(PR [#5854](https://github.com/tobymao/sqlglot/pull/5854) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`0acf076`](https://github.com/tobymao/sqlglot/commit/0acf0769773061fca3ec03125a5d43a4aa9c8e4b) - **postgres**: Support `?|` JSONB operator *(PR [#5866](https://github.com/tobymao/sqlglot/pull/5866) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`bd4b278`](https://github.com/tobymao/sqlglot/commit/bd4b2780c32ee52d25b6539d7b4479b6a7f80d18) - **optimizer**: annotate types for Snowflake UPPER function *(PR [#5812](https://github.com/tobymao/sqlglot/pull/5812) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`edab189`](https://github.com/tobymao/sqlglot/commit/edab1890e2c790b737be4995a31667448eff148e) - **postgres**: Support ?& JSONB operator *(PR [#5867](https://github.com/tobymao/sqlglot/pull/5867) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`960ec06`](https://github.com/tobymao/sqlglot/commit/960ec069eb275b7b8cc6705dbbb1143159f06237) - **postgres**: Support #- JSONB operator *(PR [#5868](https://github.com/tobymao/sqlglot/pull/5868) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`d3cd6bf`](https://github.com/tobymao/sqlglot/commit/d3cd6bf6e5fbaa490868ee3cd2cc99dd5e40a396) - **optimizer**: Annotate and add tests for snowflake REPLACE and SPACE functions *(PR [#5871](https://github.com/tobymao/sqlglot/pull/5871) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`ba22531`](https://github.com/tobymao/sqlglot/commit/ba2253113ea5a7c76c8df7ec9b6faf37da698fa4) - **bigquery**: Add support for ML.FORECAST(...) *(PR [#5873](https://github.com/tobymao/sqlglot/pull/5873) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`9c8a600`](https://github.com/tobymao/sqlglot/commit/9c8a6001f41816035f391d046eb9692d6f13cefc) - **snowflake**: correct parsing of TO_VARCHAR *(PR [#5840](https://github.com/tobymao/sqlglot/pull/5840) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5837](https://github.com/tobymao/sqlglot/issues/5837) opened by [@ultrabear](https://github.com/ultrabear)*\n- [`f3d07fd`](https://github.com/tobymao/sqlglot/commit/f3d07fd8a106b034f64bb100291671c0fe39a106) - **snowflake**: Enable parsing of COPY INTO without files list *(PR [#5841](https://github.com/tobymao/sqlglot/pull/5841) by [@whummer](https://github.com/whummer))*\n- [`0ffb1fa`](https://github.com/tobymao/sqlglot/commit/0ffb1faac3b32aad845306eed0e000ff0d055554) - **duckdb**: transpile joins without ON/USING to CROSS JOIN *(PR [#5804](https://github.com/tobymao/sqlglot/pull/5804) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5795](https://github.com/tobymao/sqlglot/issues/5795) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`1e9aef1`](https://github.com/tobymao/sqlglot/commit/1e9aef1bb20f4dc5e9c03d59cb3165c235c11ce1) - **optimizer**: convert NULL annotations to UNKNOWN *(PR [#5842](https://github.com/tobymao/sqlglot/pull/5842) by [@georgesittas](https://github.com/georgesittas))*\n- [`bbcf0d4`](https://github.com/tobymao/sqlglot/commit/bbcf0d4404ea014f08319c44313719b4377adcdb) - **duckdb**: support trailing commas before `FOR` in pivot, fixes [#5843](https://github.com/tobymao/sqlglot/pull/5843) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ad8a408`](https://github.com/tobymao/sqlglot/commit/ad8a408a4e3e26e32472fc55c67b44687992ae47) - **parser**: more robust nested pipe syntax *(PR [#5845](https://github.com/tobymao/sqlglot/pull/5845) by [@geooo109](https://github.com/geooo109))*\n- [`44c9e70`](https://github.com/tobymao/sqlglot/commit/44c9e70bd8c9421035eb0e87e4286061ec5d2fa8) - **optimizer**: add tests for snowflake STARTSWITH function *(PR [#5847](https://github.com/tobymao/sqlglot/pull/5847) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`0779c2d`](https://github.com/tobymao/sqlglot/commit/0779c2d4e8ce0228592de6882763940783fa5e87) - support BIT_X aggregates again for duckdb, postgres *(PR [#5851](https://github.com/tobymao/sqlglot/pull/5851) by [@georgesittas](https://github.com/georgesittas))*\n- [`d131aab`](https://github.com/tobymao/sqlglot/commit/d131aab6815bf77d444a763d9bb4028d8f0e742d) - **redshift**: convert FETCH clauses to LIMIT for Redshift dialect *(PR [#5848](https://github.com/tobymao/sqlglot/pull/5848) by [@tomasmontielp](https://github.com/tomasmontielp))*\n- [`b22c4ec`](https://github.com/tobymao/sqlglot/commit/b22c4ecf4c032d89ca737f01d614102aa9c2b1ed) - **fabric**: UUID to UNIQUEIDENTIFIER *(PR [#5863](https://github.com/tobymao/sqlglot/pull/5863) by [@fresioAS](https://github.com/fresioAS))*\n- [`03d4f49`](https://github.com/tobymao/sqlglot/commit/03d4f49d92cd034d37074359b8c2cf96c5c3f5cf) - **clickhouse**: arrays are 1-indexed *(PR [#5862](https://github.com/tobymao/sqlglot/pull/5862) by [@joeyutong](https://github.com/joeyutong))*\n\n### :recycle: Refactors\n- [`e441e16`](https://github.com/tobymao/sqlglot/commit/e441e16991626c2da2d38bc9c3a2b408e3f773bd) - make dump/pickling non-recursive to avoid hitting stack limits *(PR [#5850](https://github.com/tobymao/sqlglot/pull/5850) by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`b244f30`](https://github.com/tobymao/sqlglot/commit/b244f30524846bd08d03a73410ae9b4674254ecd) - move `exp.Contains` to `BOOLEAN` entry in `TYPE_TO_EXPRESSIONS` *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.13.2] - 2025-09-08\n### :bug: Bug Fixes\n- [`5e7979f`](https://github.com/tobymao/sqlglot/commit/5e7979f3cf5f7996e198ddd81069d49a4a3b9391) - select session *(PR [#5836](https://github.com/tobymao/sqlglot/pull/5836) by [@tobymao](https://github.com/tobymao))*\n\n\n## [v27.13.1] - 2025-09-08\n### :bug: Bug Fixes\n- [`f3d55c0`](https://github.com/tobymao/sqlglot/commit/f3d55c05c8411c9871f8ca4d23f726f976c9236b) - remove always token *(PR [#5832](https://github.com/tobymao/sqlglot/pull/5832) by [@tobymao](https://github.com/tobymao))*\n- [`1724775`](https://github.com/tobymao/sqlglot/commit/1724775429f66c2768864c8f96ace861eaa435fd) - suppert types() with no args *(PR [#5833](https://github.com/tobymao/sqlglot/pull/5833) by [@tobymao](https://github.com/tobymao))*\n- [`31c82c6`](https://github.com/tobymao/sqlglot/commit/31c82c6d6cd402e59cb59a94daafd22410eae0f6) - support `case.*` *(PR [#5835](https://github.com/tobymao/sqlglot/pull/5835) by [@georgesittas](https://github.com/georgesittas))*\n- [`c00f73b`](https://github.com/tobymao/sqlglot/commit/c00f73bac2530a62c25093c60bf02d0a4231bb0b) - window spec no and only exclude *(PR [#5834](https://github.com/tobymao/sqlglot/pull/5834) by [@tobymao](https://github.com/tobymao))*\n\n\n## [v27.13.0] - 2025-09-08\n### :boom: BREAKING CHANGES\n- due to [`3726b33`](https://github.com/tobymao/sqlglot/commit/3726b33bb6b4ab286617f510e96e1fbd27c429f3) - support nulls_first arg for array_sort *(PR [#5802](https://github.com/tobymao/sqlglot/pull/5802) by [@treysp](https://github.com/treysp))*:\n\n  support nulls_first arg for array_sort (#5802)\n\n- due to [`cf1d1e3`](https://github.com/tobymao/sqlglot/commit/cf1d1e3e0ef9e6cd1b1c6128c63ddf06c30f1339) - annotate type for snowflake's REVERSE function *(PR [#5803](https://github.com/tobymao/sqlglot/pull/5803) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*:\n\n  annotate type for snowflake's REVERSE function (#5803)\n\n- due to [`ad0b407`](https://github.com/tobymao/sqlglot/commit/ad0b407098e1611d4fc0e1f0916511337b9aefdb) - Mark 'BEGIN' as TokenType.BEGIN for transactions *(PR [#5826](https://github.com/tobymao/sqlglot/pull/5826) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Mark 'BEGIN' as TokenType.BEGIN for transactions (#5826)\n\n- due to [`0198282`](https://github.com/tobymao/sqlglot/commit/0198282a82bbf3e81476e164718d63fd1210acdc) - : Update tests for concat string function *(PR [#5809](https://github.com/tobymao/sqlglot/pull/5809) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  : Update tests for concat string function (#5809)\n\n- due to [`db2c430`](https://github.com/tobymao/sqlglot/commit/db2c4303237a1244070c359245c398a724df6de2) - annoate the \"contains\" function *(PR [#5829](https://github.com/tobymao/sqlglot/pull/5829) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*:\n\n  annoate the \"contains\" function (#5829)\n\n\n### :sparkles: New Features\n- [`cf1d1e3`](https://github.com/tobymao/sqlglot/commit/cf1d1e3e0ef9e6cd1b1c6128c63ddf06c30f1339) - **optimizer**: annotate type for snowflake's REVERSE function *(PR [#5803](https://github.com/tobymao/sqlglot/pull/5803) by [@fivetran-BradfordPaskewitz](https://github.com/fivetran-BradfordPaskewitz))*\n- [`1d07c52`](https://github.com/tobymao/sqlglot/commit/1d07c52badb2e392e6895cbb275d2224789366c9) - **SingleStore**: Implemented generation of CURRENT_DATETIME *(PR [#5816](https://github.com/tobymao/sqlglot/pull/5816) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`cad4fd0`](https://github.com/tobymao/sqlglot/commit/cad4fd0c5b0ec90e693fa6883af0ab287b921019) - **singlestore**: Added handling of exp.JSONObject *(PR [#5817](https://github.com/tobymao/sqlglot/pull/5817) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e3cb076`](https://github.com/tobymao/sqlglot/commit/e3cb0766bd5c3ccb31ea52cfc76201f548798dc1) - **singlestore**: Implemented generation of exp.StandardHash *(PR [#5823](https://github.com/tobymao/sqlglot/pull/5823) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`0198282`](https://github.com/tobymao/sqlglot/commit/0198282a82bbf3e81476e164718d63fd1210acdc) - **optimizer**: : Update tests for concat string function *(PR [#5809](https://github.com/tobymao/sqlglot/pull/5809) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n- [`4e8a436`](https://github.com/tobymao/sqlglot/commit/4e8a436c16f487a72bd1ac2432bcb1c46599d901) - **singlestore**: Added generation of exp.JSONExists *(PR [#5820](https://github.com/tobymao/sqlglot/pull/5820) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`82bea49`](https://github.com/tobymao/sqlglot/commit/82bea49978ae459492b5127a2a52049826e2fd06) - **singlestore**: Refactored parsing of JSON_BUILD_OBJECT *(PR [#5828](https://github.com/tobymao/sqlglot/pull/5828) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`f7d38c3`](https://github.com/tobymao/sqlglot/commit/f7d38c3a10c505346f04e39a2712d60b4c96370f) - **singlestore**: Implemented generation of exp.Stuff *(PR [#5825](https://github.com/tobymao/sqlglot/pull/5825) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`030a5b5`](https://github.com/tobymao/sqlglot/commit/030a5b5ea03ecee869b07cfd27f4ea044732822e) - **singlestore**: Added generation of exp.JSONBExists *(PR [#5821](https://github.com/tobymao/sqlglot/pull/5821) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e58fef1`](https://github.com/tobymao/sqlglot/commit/e58fef1d6dc654a3b36461bcbea21c99cdc96477) - **singlestore**: Implemented parsing and generation of exp.MatchAgainst *(PR [#5822](https://github.com/tobymao/sqlglot/pull/5822) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e94f530`](https://github.com/tobymao/sqlglot/commit/e94f530af0e0cdad995b4c8dc5ed86953490d37f) - **singlestore**: Added handling of exp.JSONArray *(PR [#5818](https://github.com/tobymao/sqlglot/pull/5818) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`1c42ef4`](https://github.com/tobymao/sqlglot/commit/1c42ef4374aeab8a1ee9848892d7f8c4511c7f04) - **singlestore**: Fixed parsing/generation of exp.JSONArrayAgg *(PR [#5819](https://github.com/tobymao/sqlglot/pull/5819) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`67219f0`](https://github.com/tobymao/sqlglot/commit/67219f0606231514f430e146e2fdb99e796f718b) - **singlestore**: Added support of UTC_TIMESTAMP and CURRENT_TIMESTAMP *(PR [#5808](https://github.com/tobymao/sqlglot/pull/5808) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`db2c430`](https://github.com/tobymao/sqlglot/commit/db2c4303237a1244070c359245c398a724df6de2) - **optimizer**: annoate the \"contains\" function *(PR [#5829](https://github.com/tobymao/sqlglot/pull/5829) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n### :bug: Bug Fixes\n- [`3726b33`](https://github.com/tobymao/sqlglot/commit/3726b33bb6b4ab286617f510e96e1fbd27c429f3) - **snowflake**: support nulls_first arg for array_sort *(PR [#5802](https://github.com/tobymao/sqlglot/pull/5802) by [@treysp](https://github.com/treysp))*\n- [`3408de0`](https://github.com/tobymao/sqlglot/commit/3408de09e50d2510c1a6f511dc2dec357059044f) - parsing quoted built-in data types *(PR [#5810](https://github.com/tobymao/sqlglot/pull/5810) by [@treysp](https://github.com/treysp))*\n- [`ad0b407`](https://github.com/tobymao/sqlglot/commit/ad0b407098e1611d4fc0e1f0916511337b9aefdb) - **postgres**: Mark 'BEGIN' as TokenType.BEGIN for transactions *(PR [#5826](https://github.com/tobymao/sqlglot/pull/5826) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5815](https://github.com/tobymao/sqlglot/issues/5815) opened by [@karakanb](https://github.com/karakanb)*\n- [`e1a1b5b`](https://github.com/tobymao/sqlglot/commit/e1a1b5befefb0ca30ac1310cecb82a44f6089034) - **snowflake**: transpile BigQuery's `&` to `BITAND` *(PR [#5827](https://github.com/tobymao/sqlglot/pull/5827) by [@YuvalOmerRep](https://github.com/YuvalOmerRep))*\n- [`32d0278`](https://github.com/tobymao/sqlglot/commit/32d027827eaa7aa0cd9faf2ac1f84739f914050f) - parse and generation of BITWISE AGG funcs across dialects *(PR [#5831](https://github.com/tobymao/sqlglot/pull/5831) by [@geooo109](https://github.com/geooo109))*\n- [`5f39a83`](https://github.com/tobymao/sqlglot/commit/5f39a83f1ff957aca57eb4745f83c296436acaac) - **bigquery**: properly generate `LIMIT` for `STRING_AGG` *(PR [#5830](https://github.com/tobymao/sqlglot/pull/5830) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`164fec1`](https://github.com/tobymao/sqlglot/commit/164fec1b36e3c7df41e2e5a5ad6b226fc5f76305) - **optimizer**: test type annotation for snowflake CHARINDEX function *(PR [#5805](https://github.com/tobymao/sqlglot/pull/5805) by [@fivetran-amrutabhimsenayachit](https://github.com/fivetran-amrutabhimsenayachit))*\n\n\n## [v27.12.0] - 2025-09-04\n### :boom: BREAKING CHANGES\n- due to [`1c551d5`](https://github.com/tobymao/sqlglot/commit/1c551d5ed3315e314013c1f063deabd9d8613e5d) - parse and annotate type for bq TO_JSON *(PR [#5768](https://github.com/tobymao/sqlglot/pull/5768) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq TO_JSON (#5768)\n\n- due to [`1707f2d`](https://github.com/tobymao/sqlglot/commit/1707f2d7f9d3b58e8c216db638f8e572f9fe6f13) - annotate type for ABS *(PR [#5770](https://github.com/tobymao/sqlglot/pull/5770) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for ABS (#5770)\n\n- due to [`69acc51`](https://github.com/tobymao/sqlglot/commit/69acc5142b2d4f0b30832c350aa49f16d1adabef) - annotate type for bq IS_INF, IS_NAN *(PR [#5771](https://github.com/tobymao/sqlglot/pull/5771) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq IS_INF, IS_NAN (#5771)\n\n- due to [`0da2076`](https://github.com/tobymao/sqlglot/commit/0da207652331920416b29e2cc67bdc3c3f964466) - annotate type for bq CBRT *(PR [#5772](https://github.com/tobymao/sqlglot/pull/5772) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq CBRT (#5772)\n\n- due to [`a4968cb`](https://github.com/tobymao/sqlglot/commit/a4968cb5693670c1a2e9cd2c86404dd90fd76160) - annotate type for bq RAND *(PR [#5774](https://github.com/tobymao/sqlglot/pull/5774) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq RAND (#5774)\n\n- due to [`3e63350`](https://github.com/tobymao/sqlglot/commit/3e63350bd1d58b510cecd1a573d27be3fd2565ce) - parse and annotate type for bq ACOS *(PR [#5776](https://github.com/tobymao/sqlglot/pull/5776) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq ACOS (#5776)\n\n- due to [`2be9d01`](https://github.com/tobymao/sqlglot/commit/2be9d01830c778186dc274c94c6db0dd6c4116d1) - parse and annotate type for bq ACOSH *(PR [#5779](https://github.com/tobymao/sqlglot/pull/5779) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq ACOSH (#5779)\n\n- due to [`b77d3da`](https://github.com/tobymao/sqlglot/commit/b77d3da8f2548858d2b9d8590fcde83e1ec62b8a) - remove `\"EXCLUDE\" -> TokenType.EXCEPT` in DuckDB, Snowflake *(PR [#5766](https://github.com/tobymao/sqlglot/pull/5766) by [@treysp](https://github.com/treysp))*:\n\n  remove `\"EXCLUDE\" -> TokenType.EXCEPT` in DuckDB, Snowflake (#5766)\n\n- due to [`7da2f31`](https://github.com/tobymao/sqlglot/commit/7da2f31d6613f16585e98c3fa1f592c617ae40c9) - parse and annotate type for bq ASIN/H *(PR [#5783](https://github.com/tobymao/sqlglot/pull/5783) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq ASIN/H (#5783)\n\n- due to [`341ea83`](https://github.com/tobymao/sqlglot/commit/341ea83a07c707fdbf565b8d9ef4b9b6341ed1d5) - parse and annotate type for bq ATAN/H/2 *(PR [#5784](https://github.com/tobymao/sqlglot/pull/5784) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq ATAN/H/2 (#5784)\n\n- due to [`aa360cb`](https://github.com/tobymao/sqlglot/commit/aa360cb0e204aa056557ff8b15aa2d4f678430e6) - use regexp_like as it exists *(PR [#5781](https://github.com/tobymao/sqlglot/pull/5781) by [@jasonthomassql](https://github.com/jasonthomassql))*:\n\n  use regexp_like as it exists (#5781)\n\n- due to [`c2a1ad4`](https://github.com/tobymao/sqlglot/commit/c2a1ad4050771401a5b26bcadd90060e4527fbff) - parse and annotate type for bq COT/H *(PR [#5786](https://github.com/tobymao/sqlglot/pull/5786) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq COT/H (#5786)\n\n- due to [`316ae91`](https://github.com/tobymao/sqlglot/commit/316ae913d8b1a63f3071ebb1b826328108d74cef) - Added handling of UTC_DATE and exp.CurrentDate *(PR [#5785](https://github.com/tobymao/sqlglot/pull/5785) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*:\n\n  Added handling of UTC_DATE and exp.CurrentDate (#5785)\n\n- due to [`2c6d237`](https://github.com/tobymao/sqlglot/commit/2c6d23742ea9fcc2b9c784315d3d5364e360fea5) - parse and annotate type for bq CSC/H *(PR [#5787](https://github.com/tobymao/sqlglot/pull/5787) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq CSC/H (#5787)\n\n- due to [`8a35076`](https://github.com/tobymao/sqlglot/commit/8a350763c2337f6910a5f0e19af387ba488fcb70) - parse and annotate type for bq SEC/H *(PR [#5788](https://github.com/tobymao/sqlglot/pull/5788) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq SEC/H (#5788)\n\n- due to [`79901cb`](https://github.com/tobymao/sqlglot/commit/79901cb506737ae1932fa44a705858d2597ee587) - parse and annotate type for bq SIN\\H *(PR [#5790](https://github.com/tobymao/sqlglot/pull/5790) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq SIN\\H (#5790)\n\n- due to [`74fb547`](https://github.com/tobymao/sqlglot/commit/74fb5476def1b389da425885db56bd6592fd7f78) - parse and annotate type for bq RANGE_BUCKET *(PR [#5793](https://github.com/tobymao/sqlglot/pull/5793) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq RANGE_BUCKET (#5793)\n\n- due to [`eca65e8`](https://github.com/tobymao/sqlglot/commit/eca65e8b79f65850b014a4cb7913ba4a5861dbe9) - parse and annotate type for bq COSINE/EUCLIDEAN_DISTANCE *(PR [#5792](https://github.com/tobymao/sqlglot/pull/5792) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq COSINE/EUCLIDEAN_DISTANCE (#5792)\n\n- due to [`a180d3f`](https://github.com/tobymao/sqlglot/commit/a180d3f2f9f3938611027269028c03274aa1889c) - parse and annotate type for bq SAFE math funcs *(PR [#5797](https://github.com/tobymao/sqlglot/pull/5797) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq SAFE math funcs (#5797)\n\n- due to [`fc7ad7a`](https://github.com/tobymao/sqlglot/commit/fc7ad7a4d953424b56542eacfe1835f5789921c7) - parse ALTER SESSION  *(PR [#5734](https://github.com/tobymao/sqlglot/pull/5734) by [@tekumara](https://github.com/tekumara))*:\n\n  parse ALTER SESSION  (#5734)\n\n- due to [`8ec1a6c`](https://github.com/tobymao/sqlglot/commit/8ec1a6cf5a8edc2d834c713ce0fd8d87237f11ed) - annotate type for bq STRING_AGG *(PR [#5798](https://github.com/tobymao/sqlglot/pull/5798) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq STRING_AGG (#5798)\n\n- due to [`dd97bfa`](https://github.com/tobymao/sqlglot/commit/dd97bfa1dc2f86b727c55b06b3c54b18c02e360d) - annotate type for bq DATETIME_TRUNC *(PR [#5799](https://github.com/tobymao/sqlglot/pull/5799) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq DATETIME_TRUNC (#5799)\n\n- due to [`d3e9dda`](https://github.com/tobymao/sqlglot/commit/d3e9dda183695dd1e4a9832a6671bccc6db561a0) - annotate type for bq GENERATE_UUID *(commit by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq GENERATE_UUID\n\n\n### :sparkles: New Features\n- [`1c551d5`](https://github.com/tobymao/sqlglot/commit/1c551d5ed3315e314013c1f063deabd9d8613e5d) - **optimizer**: parse and annotate type for bq TO_JSON *(PR [#5768](https://github.com/tobymao/sqlglot/pull/5768) by [@geooo109](https://github.com/geooo109))*\n- [`a024d48`](https://github.com/tobymao/sqlglot/commit/a024d48fedd049796329050a1f51822dd1388695) - **singlestore**: Added generation of exp.TsOrDsDiff *(PR [#5769](https://github.com/tobymao/sqlglot/pull/5769) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`1707f2d`](https://github.com/tobymao/sqlglot/commit/1707f2d7f9d3b58e8c216db638f8e572f9fe6f13) - **optimizer**: annotate type for ABS *(PR [#5770](https://github.com/tobymao/sqlglot/pull/5770) by [@geooo109](https://github.com/geooo109))*\n- [`69acc51`](https://github.com/tobymao/sqlglot/commit/69acc5142b2d4f0b30832c350aa49f16d1adabef) - **optimizer**: annotate type for bq IS_INF, IS_NAN *(PR [#5771](https://github.com/tobymao/sqlglot/pull/5771) by [@geooo109](https://github.com/geooo109))*\n- [`0da2076`](https://github.com/tobymao/sqlglot/commit/0da207652331920416b29e2cc67bdc3c3f964466) - **optimizer**: annotate type for bq CBRT *(PR [#5772](https://github.com/tobymao/sqlglot/pull/5772) by [@geooo109](https://github.com/geooo109))*\n- [`a4968cb`](https://github.com/tobymao/sqlglot/commit/a4968cb5693670c1a2e9cd2c86404dd90fd76160) - **optimizer**: annotate type for bq RAND *(PR [#5774](https://github.com/tobymao/sqlglot/pull/5774) by [@geooo109](https://github.com/geooo109))*\n- [`dd7781a`](https://github.com/tobymao/sqlglot/commit/dd7781a15b842a5826714958ed7af9024903cd1e) - **singlestore**: Fixed generation of exp.Collate *(PR [#5775](https://github.com/tobymao/sqlglot/pull/5775) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`fb684cb`](https://github.com/tobymao/sqlglot/commit/fb684cbdb6178ddc441f598cc1a6e914291cd00e) - **singelstore**: Fixed generation of exp.RegexpILike *(PR [#5777](https://github.com/tobymao/sqlglot/pull/5777) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`3e63350`](https://github.com/tobymao/sqlglot/commit/3e63350bd1d58b510cecd1a573d27be3fd2565ce) - **optimizer**: parse and annotate type for bq ACOS *(PR [#5776](https://github.com/tobymao/sqlglot/pull/5776) by [@geooo109](https://github.com/geooo109))*\n- [`8705a78`](https://github.com/tobymao/sqlglot/commit/8705a787df034b4cecb4ba95e9599772c5561ba9) - **singlestore**: Fixed generation of exp.CastToStrType *(PR [#5778](https://github.com/tobymao/sqlglot/pull/5778) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e3c35ad`](https://github.com/tobymao/sqlglot/commit/e3c35ade797f46549cc803e1acd8816041713a10) - **singlestore**: Fixed generation of exp.UnicodeString *(PR [#5773](https://github.com/tobymao/sqlglot/pull/5773) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`2be9d01`](https://github.com/tobymao/sqlglot/commit/2be9d01830c778186dc274c94c6db0dd6c4116d1) - **optimizer**: parse and annotate type for bq ACOSH *(PR [#5779](https://github.com/tobymao/sqlglot/pull/5779) by [@geooo109](https://github.com/geooo109))*\n- [`7da2f31`](https://github.com/tobymao/sqlglot/commit/7da2f31d6613f16585e98c3fa1f592c617ae40c9) - **optimizer**: parse and annotate type for bq ASIN/H *(PR [#5783](https://github.com/tobymao/sqlglot/pull/5783) by [@geooo109](https://github.com/geooo109))*\n- [`341ea83`](https://github.com/tobymao/sqlglot/commit/341ea83a07c707fdbf565b8d9ef4b9b6341ed1d5) - **optimizer**: parse and annotate type for bq ATAN/H/2 *(PR [#5784](https://github.com/tobymao/sqlglot/pull/5784) by [@geooo109](https://github.com/geooo109))*\n- [`be54a45`](https://github.com/tobymao/sqlglot/commit/be54a458413ce3be6c321e5f4feb3e5df5ee6d08) - **singlestore**: Implemented generation of exp.Cbrt *(PR [#5782](https://github.com/tobymao/sqlglot/pull/5782) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`aa360cb`](https://github.com/tobymao/sqlglot/commit/aa360cb0e204aa056557ff8b15aa2d4f678430e6) - **databricks**: use regexp_like as it exists *(PR [#5781](https://github.com/tobymao/sqlglot/pull/5781) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`c2a1ad4`](https://github.com/tobymao/sqlglot/commit/c2a1ad4050771401a5b26bcadd90060e4527fbff) - **optimizer**: parse and annotate type for bq COT/H *(PR [#5786](https://github.com/tobymao/sqlglot/pull/5786) by [@geooo109](https://github.com/geooo109))*\n- [`316ae91`](https://github.com/tobymao/sqlglot/commit/316ae913d8b1a63f3071ebb1b826328108d74cef) - **singlestore**: Added handling of UTC_DATE and exp.CurrentDate *(PR [#5785](https://github.com/tobymao/sqlglot/pull/5785) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`2c6d237`](https://github.com/tobymao/sqlglot/commit/2c6d23742ea9fcc2b9c784315d3d5364e360fea5) - **optimizer**: parse and annotate type for bq CSC/H *(PR [#5787](https://github.com/tobymao/sqlglot/pull/5787) by [@geooo109](https://github.com/geooo109))*\n- [`8a35076`](https://github.com/tobymao/sqlglot/commit/8a350763c2337f6910a5f0e19af387ba488fcb70) - **optimizer**: parse and annotate type for bq SEC/H *(PR [#5788](https://github.com/tobymao/sqlglot/pull/5788) by [@geooo109](https://github.com/geooo109))*\n- [`566bfb2`](https://github.com/tobymao/sqlglot/commit/566bfb2a64a64b74da63b3a89d68caf702ab6522) - **singlestore**: Added support of UTC_TIME and CURRENT_TIME *(PR [#5789](https://github.com/tobymao/sqlglot/pull/5789) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`79901cb`](https://github.com/tobymao/sqlglot/commit/79901cb506737ae1932fa44a705858d2597ee587) - **optimizer**: parse and annotate type for bq SIN\\H *(PR [#5790](https://github.com/tobymao/sqlglot/pull/5790) by [@geooo109](https://github.com/geooo109))*\n- [`74fb547`](https://github.com/tobymao/sqlglot/commit/74fb5476def1b389da425885db56bd6592fd7f78) - **optimizer**: parse and annotate type for bq RANGE_BUCKET *(PR [#5793](https://github.com/tobymao/sqlglot/pull/5793) by [@geooo109](https://github.com/geooo109))*\n- [`eca65e8`](https://github.com/tobymao/sqlglot/commit/eca65e8b79f65850b014a4cb7913ba4a5861dbe9) - **optimizer**: parse and annotate type for bq COSINE/EUCLIDEAN_DISTANCE *(PR [#5792](https://github.com/tobymao/sqlglot/pull/5792) by [@geooo109](https://github.com/geooo109))*\n- [`a180d3f`](https://github.com/tobymao/sqlglot/commit/a180d3f2f9f3938611027269028c03274aa1889c) - **optimizer**: parse and annotate type for bq SAFE math funcs *(PR [#5797](https://github.com/tobymao/sqlglot/pull/5797) by [@geooo109](https://github.com/geooo109))*\n- [`fc7ad7a`](https://github.com/tobymao/sqlglot/commit/fc7ad7a4d953424b56542eacfe1835f5789921c7) - **snowflake**: parse ALTER SESSION  *(PR [#5734](https://github.com/tobymao/sqlglot/pull/5734) by [@tekumara](https://github.com/tekumara))*\n- [`8ec1a6c`](https://github.com/tobymao/sqlglot/commit/8ec1a6cf5a8edc2d834c713ce0fd8d87237f11ed) - **optimizer**: annotate type for bq STRING_AGG *(PR [#5798](https://github.com/tobymao/sqlglot/pull/5798) by [@geooo109](https://github.com/geooo109))*\n- [`dd97bfa`](https://github.com/tobymao/sqlglot/commit/dd97bfa1dc2f86b727c55b06b3c54b18c02e360d) - **optimizer**: annotate type for bq DATETIME_TRUNC *(PR [#5799](https://github.com/tobymao/sqlglot/pull/5799) by [@geooo109](https://github.com/geooo109))*\n- [`d3e9dda`](https://github.com/tobymao/sqlglot/commit/d3e9dda183695dd1e4a9832a6671bccc6db561a0) - **optimizer**: annotate type for bq GENERATE_UUID *(commit by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`d8f6a37`](https://github.com/tobymao/sqlglot/commit/d8f6a376ba1fcca48e4a65923dd7a319ce6cfb91) - **optimizer**: allow aliased negative integer literal as group by column *(PR [#5791](https://github.com/tobymao/sqlglot/pull/5791) by [@treysp](https://github.com/treysp))*\n- [`1259576`](https://github.com/tobymao/sqlglot/commit/1259576283f1d45abb70ec40c60e500214a27b6f) - **hive**: DATE_SUB to DATE_ADD use parens if needed *(PR [#5796](https://github.com/tobymao/sqlglot/pull/5796) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5794](https://github.com/tobymao/sqlglot/issues/5794) opened by [@mingelchan](https://github.com/mingelchan)*\n- [`b0516b4`](https://github.com/tobymao/sqlglot/commit/b0516b4bc9cf2bba2cb57e6bb79ff09b5e2244e3) - **optimizer**: Do not qualify columns if a projection coflicts with a source *(PR [#5780](https://github.com/tobymao/sqlglot/pull/5780) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5262](https://github.com/TobikoData/sqlmesh/issues/5262) opened by [@mChlopek](https://github.com/mChlopek)*\n- [`8af0d40`](https://github.com/tobymao/sqlglot/commit/8af0d40055450f71b7e36e576f4a9a1104bc02b2) - **parser**: address edge case where `values` is used as an identifier *(PR [#5801](https://github.com/tobymao/sqlglot/pull/5801) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`b77d3da`](https://github.com/tobymao/sqlglot/commit/b77d3da8f2548858d2b9d8590fcde83e1ec62b8a) - remove `\"EXCLUDE\" -> TokenType.EXCEPT` in DuckDB, Snowflake *(PR [#5766](https://github.com/tobymao/sqlglot/pull/5766) by [@treysp](https://github.com/treysp))*\n- [`005564a`](https://github.com/tobymao/sqlglot/commit/005564ab28cb14be469f09e89b01275d6e25874e) - **snowflake**: refactor logic related to ALTER SESSION *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.11.0] - 2025-09-03\n### :boom: BREAKING CHANGES\n- due to [`baffd2c`](https://github.com/tobymao/sqlglot/commit/baffd2c0be9657683781f3f8831c47e32dbf68bb) - parse and annotate type for bq REGEXP_INSTR *(PR [#5710](https://github.com/tobymao/sqlglot/pull/5710) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq REGEXP_INSTR (#5710)\n\n- due to [`b79eb19`](https://github.com/tobymao/sqlglot/commit/b79eb198cc21203efa82128b357d435338e9133d) - annotate type for bq ROW_NUMBER *(PR [#5716](https://github.com/tobymao/sqlglot/pull/5716) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq ROW_NUMBER (#5716)\n\n- due to [`f709bef`](https://github.com/tobymao/sqlglot/commit/f709bef3af7cd0daa25fe3d58b1753c3e65720ef) - annotate type for bq FIRST_VALUE *(PR [#5718](https://github.com/tobymao/sqlglot/pull/5718) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq FIRST_VALUE (#5718)\n\n- due to [`15a9061`](https://github.com/tobymao/sqlglot/commit/15a906170e5d5cdaa207ec7607edfdd7d4a8b774) - annotate type for bq PERCENTILE_DISC *(PR [#5722](https://github.com/tobymao/sqlglot/pull/5722) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq PERCENTILE_DISC (#5722)\n\n- due to [`7d49609`](https://github.com/tobymao/sqlglot/commit/7d4960963f0ef70b96f5b969bb008d2742e833ea) - annotate type for bq NTH_VALUE *(PR [#5720](https://github.com/tobymao/sqlglot/pull/5720) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq NTH_VALUE (#5720)\n\n- due to [`d41acf1`](https://github.com/tobymao/sqlglot/commit/d41acf11221bee30a5ae089cbac9b158ed3dd515) - annotate type for bq LEAD *(PR [#5719](https://github.com/tobymao/sqlglot/pull/5719) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq LEAD (#5719)\n\n- due to [`ff12130`](https://github.com/tobymao/sqlglot/commit/ff12130c23a215917f20fda7d50322f1cb7de599) - annotate type for bq PERNCENTILE_CONT *(PR [#5729](https://github.com/tobymao/sqlglot/pull/5729) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq PERNCENTILE_CONT (#5729)\n\n- due to [`fdb8a0a`](https://github.com/tobymao/sqlglot/commit/fdb8a0a6d0d74194255f313bd934db7fc1ce0d3f) - parse and annotate type for bq FORMAT *(PR [#5715](https://github.com/tobymao/sqlglot/pull/5715) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq FORMAT (#5715)\n\n- due to [`012bdd3`](https://github.com/tobymao/sqlglot/commit/012bdd3c8aeff180f85354ffd403fc1aa5815dcf) - parse and annotate type for bq CUME_DIST *(PR [#5735](https://github.com/tobymao/sqlglot/pull/5735) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq CUME_DIST (#5735)\n\n- due to [`b99eaeb`](https://github.com/tobymao/sqlglot/commit/b99eaeb0c6eb3dc613e76d205e02632bd6af353b) - parse and annotate type for bq DENSE_RANK *(PR [#5736](https://github.com/tobymao/sqlglot/pull/5736) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq DENSE_RANK (#5736)\n\n- due to [`bb95c73`](https://github.com/tobymao/sqlglot/commit/bb95c7312c942ef987955f01e060604d60e32e83) - parse and annotate type for bq RANK *(PR [#5738](https://github.com/tobymao/sqlglot/pull/5738) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq RANK (#5738)\n\n- due to [`8713c08`](https://github.com/tobymao/sqlglot/commit/8713c082b0aa8454a5773fc2a85e08a132dc6ce3) - parse and annotate type for bq PERCENT_RANK *(PR [#5739](https://github.com/tobymao/sqlglot/pull/5739) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq PERCENT_RANK (#5739)\n\n- due to [`9ce4e31`](https://github.com/tobymao/sqlglot/commit/9ce4e31aecbde6ea1f227a7166c0f3dc9e302a66) - annotate type for bq JSON_OBJECT *(PR [#5740](https://github.com/tobymao/sqlglot/pull/5740) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq JSON_OBJECT (#5740)\n\n- due to [`d35ec6e`](https://github.com/tobymao/sqlglot/commit/d35ec6e37e21cf3cec848ed55bd73128c4633cd2) - annotate type for bq JSON_QUERY/JSON_QUERY_ARRAY *(PR [#5741](https://github.com/tobymao/sqlglot/pull/5741) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq JSON_QUERY/JSON_QUERY_ARRAY (#5741)\n\n- due to [`4753642`](https://github.com/tobymao/sqlglot/commit/4753642cfcfb1f192ec4d21a492737b27affef09) - annotate type for bq JSON_EXTRACT_SCALAR *(commit by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq JSON_EXTRACT_SCALAR\n\n- due to [`113a530`](https://github.com/tobymao/sqlglot/commit/113a5308d050fd5ceacab4c6188e5eea5dd740b1) - parse and annotate type for bq JSON_ARRAY_APPEND *(PR [#5747](https://github.com/tobymao/sqlglot/pull/5747) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_ARRAY_APPEND (#5747)\n\n- due to [`268e2c6`](https://github.com/tobymao/sqlglot/commit/268e2c694d1eb99f1fe64477bc38ed4946bf1c32) - parse and annotate type for bq JSON_ARRAY_INSERT *(PR [#5748](https://github.com/tobymao/sqlglot/pull/5748) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_ARRAY_INSERT (#5748)\n\n- due to [`455ec1f`](https://github.com/tobymao/sqlglot/commit/455ec1f4f8aecb5435fa4cb2912bfc21db8dd44d) - parse and annotate type for bq JSON_KEYS *(PR [#5749](https://github.com/tobymao/sqlglot/pull/5749) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_KEYS (#5749)\n\n- due to [`59895fa`](https://github.com/tobymao/sqlglot/commit/59895faa23ebe1b27938c37a7b39df87de609844) - parse and annotate type for bq JSON_REMOVE *(PR [#5750](https://github.com/tobymao/sqlglot/pull/5750) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_REMOVE (#5750)\n\n- due to [`06d7df7`](https://github.com/tobymao/sqlglot/commit/06d7df7a05f2824cabf48e8d1e8a4ebca8fda496) - parse and annotate type for bq JSON_SET *(PR [#5751](https://github.com/tobymao/sqlglot/pull/5751) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_SET (#5751)\n\n- due to [`e72b341`](https://github.com/tobymao/sqlglot/commit/e72b3419c8a367caa0e5e80030979cd94e87a40d) - parse and annotate type for bq JSON_STRIP_NULLS *(PR [#5753](https://github.com/tobymao/sqlglot/pull/5753) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_STRIP_NULLS (#5753)\n\n- due to [`5de61a7`](https://github.com/tobymao/sqlglot/commit/5de61a7ab850d4e68fde4d76ee396d30d7bdef33) - parse and annotate type for bq JSON_EXTRACT_STRING_ARRAY *(PR [#5758](https://github.com/tobymao/sqlglot/pull/5758) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON_EXTRACT_STRING_ARRAY (#5758)\n\n- due to [`36c9393`](https://github.com/tobymao/sqlglot/commit/36c93939575a19bd611269719c39d3d216be8cde) - parse and annotate type for bq JSON LAX funcs *(PR [#5760](https://github.com/tobymao/sqlglot/pull/5760) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq JSON LAX funcs (#5760)\n\n- due to [`88862b5`](https://github.com/tobymao/sqlglot/commit/88862b56bc29c8a600b4d0e4693d5846d3a577ff) - annotate type for bq TO_JSON_STRING *(PR [#5762](https://github.com/tobymao/sqlglot/pull/5762) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq TO_JSON_STRING (#5762)\n\n\n### :sparkles: New Features\n- [`baffd2c`](https://github.com/tobymao/sqlglot/commit/baffd2c0be9657683781f3f8831c47e32dbf68bb) - **optimizer**: parse and annotate type for bq REGEXP_INSTR *(PR [#5710](https://github.com/tobymao/sqlglot/pull/5710) by [@geooo109](https://github.com/geooo109))*\n- [`b79eb19`](https://github.com/tobymao/sqlglot/commit/b79eb198cc21203efa82128b357d435338e9133d) - **optimizer**: annotate type for bq ROW_NUMBER *(PR [#5716](https://github.com/tobymao/sqlglot/pull/5716) by [@geooo109](https://github.com/geooo109))*\n- [`f709bef`](https://github.com/tobymao/sqlglot/commit/f709bef3af7cd0daa25fe3d58b1753c3e65720ef) - **optimizer**: annotate type for bq FIRST_VALUE *(PR [#5718](https://github.com/tobymao/sqlglot/pull/5718) by [@geooo109](https://github.com/geooo109))*\n- [`b9ae9e5`](https://github.com/tobymao/sqlglot/commit/b9ae9e534dee1e32fccbf22cab9bc17fbd920629) - **singlestore**: Implemeted generation of exp.TsOrDiToDi *(PR [#5724](https://github.com/tobymao/sqlglot/pull/5724) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`9b14fff`](https://github.com/tobymao/sqlglot/commit/9b14fffd2c9404f76a3faced2ec9d6eaac8feb01) - **singlestore**: Implemented generation of exp.DateToDi *(PR [#5717](https://github.com/tobymao/sqlglot/pull/5717) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`07d8c23`](https://github.com/tobymao/sqlglot/commit/07d8c2347baba6523310c4d31cddfb0e5c0eddc1) - **singlestore**: Implemented generation of exp.DiToDate *(PR [#5721](https://github.com/tobymao/sqlglot/pull/5721) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`ad34a85`](https://github.com/tobymao/sqlglot/commit/ad34a855a433bc0f51a707cbcb66f8dce667a562) - **singlestore**: Implemented generation of exp.FromTimeZone *(PR [#5723](https://github.com/tobymao/sqlglot/pull/5723) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`29d5e4f`](https://github.com/tobymao/sqlglot/commit/29d5e4f62a799f35c0904a23cedacc6efa95a63b) - **singlestore**: Implemented generation of exp.DatetimeAdd *(PR [#5728](https://github.com/tobymao/sqlglot/pull/5728) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`15a9061`](https://github.com/tobymao/sqlglot/commit/15a906170e5d5cdaa207ec7607edfdd7d4a8b774) - **optimizer**: annotate type for bq PERCENTILE_DISC *(PR [#5722](https://github.com/tobymao/sqlglot/pull/5722) by [@geooo109](https://github.com/geooo109))*\n- [`7d49609`](https://github.com/tobymao/sqlglot/commit/7d4960963f0ef70b96f5b969bb008d2742e833ea) - **optimizer**: annotate type for bq NTH_VALUE *(PR [#5720](https://github.com/tobymao/sqlglot/pull/5720) by [@geooo109](https://github.com/geooo109))*\n- [`d41acf1`](https://github.com/tobymao/sqlglot/commit/d41acf11221bee30a5ae089cbac9b158ed3dd515) - **optimizer**: annotate type for bq LEAD *(PR [#5719](https://github.com/tobymao/sqlglot/pull/5719) by [@geooo109](https://github.com/geooo109))*\n- [`113809a`](https://github.com/tobymao/sqlglot/commit/113809a07efee0f12758bd2571c8515885568466) - **singlestore**: Implemented exp.TimeStrToDate generation *(PR [#5725](https://github.com/tobymao/sqlglot/pull/5725) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`cf63d0d`](https://github.com/tobymao/sqlglot/commit/cf63d0df4c2f58b2cf0c87e2a3a6f63f836a50a1) - **dremio**: add regexp_like and alias regexp_matches *(PR [#5731](https://github.com/tobymao/sqlglot/pull/5731) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`e42160f`](https://github.com/tobymao/sqlglot/commit/e42160f27fa68828898969073f2f4a0014f5e3e9) - **dremio**: support alias repeatstr *(PR [#5730](https://github.com/tobymao/sqlglot/pull/5730) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`ff12130`](https://github.com/tobymao/sqlglot/commit/ff12130c23a215917f20fda7d50322f1cb7de599) - **optimizer**: annotate type for bq PERNCENTILE_CONT *(PR [#5729](https://github.com/tobymao/sqlglot/pull/5729) by [@geooo109](https://github.com/geooo109))*\n- [`fdb8a0a`](https://github.com/tobymao/sqlglot/commit/fdb8a0a6d0d74194255f313bd934db7fc1ce0d3f) - **optimizer**: parse and annotate type for bq FORMAT *(PR [#5715](https://github.com/tobymao/sqlglot/pull/5715) by [@geooo109](https://github.com/geooo109))*\n- [`e272292`](https://github.com/tobymao/sqlglot/commit/e272292197f2bb81ccfad1de06a95f321f0b565f) - **singlestore**: Implemented generation of exp.Time *(PR [#5727](https://github.com/tobymao/sqlglot/pull/5727) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`012bdd3`](https://github.com/tobymao/sqlglot/commit/012bdd3c8aeff180f85354ffd403fc1aa5815dcf) - **optimizer**: parse and annotate type for bq CUME_DIST *(PR [#5735](https://github.com/tobymao/sqlglot/pull/5735) by [@geooo109](https://github.com/geooo109))*\n- [`b99eaeb`](https://github.com/tobymao/sqlglot/commit/b99eaeb0c6eb3dc613e76d205e02632bd6af353b) - **optimizer**: parse and annotate type for bq DENSE_RANK *(PR [#5736](https://github.com/tobymao/sqlglot/pull/5736) by [@geooo109](https://github.com/geooo109))*\n- [`8cf6ef9`](https://github.com/tobymao/sqlglot/commit/8cf6ef92a0f43943efb0fe380f41dc09f43aca85) - **optimizer**: parse and annotate_type for bq NTILE *(PR [#5737](https://github.com/tobymao/sqlglot/pull/5737) by [@geooo109](https://github.com/geooo109))*\n- [`bb95c73`](https://github.com/tobymao/sqlglot/commit/bb95c7312c942ef987955f01e060604d60e32e83) - **optimizer**: parse and annotate type for bq RANK *(PR [#5738](https://github.com/tobymao/sqlglot/pull/5738) by [@geooo109](https://github.com/geooo109))*\n- [`8713c08`](https://github.com/tobymao/sqlglot/commit/8713c082b0aa8454a5773fc2a85e08a132dc6ce3) - **optimizer**: parse and annotate type for bq PERCENT_RANK *(PR [#5739](https://github.com/tobymao/sqlglot/pull/5739) by [@geooo109](https://github.com/geooo109))*\n- [`9ce4e31`](https://github.com/tobymao/sqlglot/commit/9ce4e31aecbde6ea1f227a7166c0f3dc9e302a66) - **optimizer**: annotate type for bq JSON_OBJECT *(PR [#5740](https://github.com/tobymao/sqlglot/pull/5740) by [@geooo109](https://github.com/geooo109))*\n- [`d35ec6e`](https://github.com/tobymao/sqlglot/commit/d35ec6e37e21cf3cec848ed55bd73128c4633cd2) - **optimizer**: annotate type for bq JSON_QUERY/JSON_QUERY_ARRAY *(PR [#5741](https://github.com/tobymao/sqlglot/pull/5741) by [@geooo109](https://github.com/geooo109))*\n- [`4753642`](https://github.com/tobymao/sqlglot/commit/4753642cfcfb1f192ec4d21a492737b27affef09) - **optimizer**: annotate type for bq JSON_EXTRACT_SCALAR *(commit by [@geooo109](https://github.com/geooo109))*\n- [`6249dbe`](https://github.com/tobymao/sqlglot/commit/6249dbe4173ad5278adf84452dcf7253a2395b91) - **singlestore**: Added generation of exp.DatetimeDiff *(PR [#5743](https://github.com/tobymao/sqlglot/pull/5743) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`113a530`](https://github.com/tobymao/sqlglot/commit/113a5308d050fd5ceacab4c6188e5eea5dd740b1) - **optimizer**: parse and annotate type for bq JSON_ARRAY_APPEND *(PR [#5747](https://github.com/tobymao/sqlglot/pull/5747) by [@geooo109](https://github.com/geooo109))*\n- [`8603705`](https://github.com/tobymao/sqlglot/commit/8603705a8e5513699adc2499389c67412eee70cb) - **singlestore**: feat(singlestore): Implemented generation of exp.DatetimeSub *(PR [#5744](https://github.com/tobymao/sqlglot/pull/5744) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`7d71c0b`](https://github.com/tobymao/sqlglot/commit/7d71c0bb576f9de3447b4780ab64a3f4d92c6432) - **singlestore**: Fixed generation of exp.DatetimeTrunc and exp.DateTrunc *(PR [#5745](https://github.com/tobymao/sqlglot/pull/5745) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`268e2c6`](https://github.com/tobymao/sqlglot/commit/268e2c694d1eb99f1fe64477bc38ed4946bf1c32) - **optimizer**: parse and annotate type for bq JSON_ARRAY_INSERT *(PR [#5748](https://github.com/tobymao/sqlglot/pull/5748) by [@geooo109](https://github.com/geooo109))*\n- [`455ec1f`](https://github.com/tobymao/sqlglot/commit/455ec1f4f8aecb5435fa4cb2912bfc21db8dd44d) - **optimizer**: parse and annotate type for bq JSON_KEYS *(PR [#5749](https://github.com/tobymao/sqlglot/pull/5749) by [@geooo109](https://github.com/geooo109))*\n- [`59895fa`](https://github.com/tobymao/sqlglot/commit/59895faa23ebe1b27938c37a7b39df87de609844) - **optimizer**: parse and annotate type for bq JSON_REMOVE *(PR [#5750](https://github.com/tobymao/sqlglot/pull/5750) by [@geooo109](https://github.com/geooo109))*\n- [`06d7df7`](https://github.com/tobymao/sqlglot/commit/06d7df7a05f2824cabf48e8d1e8a4ebca8fda496) - **optimizer**: parse and annotate type for bq JSON_SET *(PR [#5751](https://github.com/tobymao/sqlglot/pull/5751) by [@geooo109](https://github.com/geooo109))*\n- [`7f5079a`](https://github.com/tobymao/sqlglot/commit/7f5079a1b71c4dd28e98b77b5b749e074fce862c) - **singlestore**: Improved geneation of exp.DataType *(PR [#5746](https://github.com/tobymao/sqlglot/pull/5746) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`ad9405c`](https://github.com/tobymao/sqlglot/commit/ad9405cd43108ff80d16711f8b33ff57430ed686) - **singlestore**: fixed generation of exp.TimestampTrunc *(PR [#5754](https://github.com/tobymao/sqlglot/pull/5754) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`a1852f9`](https://github.com/tobymao/sqlglot/commit/a1852f93fdfe926072c12954c95796d038e15140) - **dremio**: parse date_part *(PR [#5756](https://github.com/tobymao/sqlglot/pull/5756) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`0db1df6`](https://github.com/tobymao/sqlglot/commit/0db1df617ec4f05b1ee6cf1d606272f6e799a9b9) - **singlestore**: Fixed generation of exp.DateDiff *(PR [#5752](https://github.com/tobymao/sqlglot/pull/5752) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e72b341`](https://github.com/tobymao/sqlglot/commit/e72b3419c8a367caa0e5e80030979cd94e87a40d) - **optimizer**: parse and annotate type for bq JSON_STRIP_NULLS *(PR [#5753](https://github.com/tobymao/sqlglot/pull/5753) by [@geooo109](https://github.com/geooo109))*\n- [`5de61a7`](https://github.com/tobymao/sqlglot/commit/5de61a7ab850d4e68fde4d76ee396d30d7bdef33) - **optimizer**: parse and annotate type for bq JSON_EXTRACT_STRING_ARRAY *(PR [#5758](https://github.com/tobymao/sqlglot/pull/5758) by [@geooo109](https://github.com/geooo109))*\n- [`36c9393`](https://github.com/tobymao/sqlglot/commit/36c93939575a19bd611269719c39d3d216be8cde) - **optimizer**: parse and annotate type for bq JSON LAX funcs *(PR [#5760](https://github.com/tobymao/sqlglot/pull/5760) by [@geooo109](https://github.com/geooo109))*\n- [`c443d5c`](https://github.com/tobymao/sqlglot/commit/c443d5caf2d9695856103eebfff21cb215777112) - **dremio**: parse datetype *(PR [#5759](https://github.com/tobymao/sqlglot/pull/5759) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`5172a99`](https://github.com/tobymao/sqlglot/commit/5172a99fc4d5e21a1dbe4509d6d7ab1ccfe8bff7) - **singlestore**: Fixed parsing of columns with table name *(PR [#5767](https://github.com/tobymao/sqlglot/pull/5767) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`88862b5`](https://github.com/tobymao/sqlglot/commit/88862b56bc29c8a600b4d0e4693d5846d3a577ff) - **optimizer**: annotate type for bq TO_JSON_STRING *(PR [#5762](https://github.com/tobymao/sqlglot/pull/5762) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`ec93497`](https://github.com/tobymao/sqlglot/commit/ec93497bac82090b88c6e749ec2adc99bbc23a61) - **bigquery**: support commands inside for loops *(PR [#5732](https://github.com/tobymao/sqlglot/pull/5732) by [@treysp](https://github.com/treysp))*\n- [`85845bb`](https://github.com/tobymao/sqlglot/commit/85845bb941ac9a4ee090a89cd3d3dab4ab5835a7) - **snowflake**: allow exclude as id var *(PR [#5764](https://github.com/tobymao/sqlglot/pull/5764) by [@treysp](https://github.com/treysp))*\n- [`db2d9cc`](https://github.com/tobymao/sqlglot/commit/db2d9cca9718fb196066dbf60840124917d1f8ac) - **tokenizer**: handle empty hex strings *(PR [#5763](https://github.com/tobymao/sqlglot/pull/5763) by [@paulolieuthier](https://github.com/paulolieuthier))*\n  - :arrow_lower_right: *fixes issue [#5761](https://github.com/tobymao/sqlglot/issues/5761) opened by [@paulolieuthier](https://github.com/paulolieuthier)*\n- [`982257b`](https://github.com/tobymao/sqlglot/commit/982257b40973cdfc20a8d6dd9a1674cda7eb75c4) - **bigquery**: Crash when ARRAY_CONCAT is called with no expressions *(PR [#5755](https://github.com/tobymao/sqlglot/pull/5755) by [@ozadari](https://github.com/ozadari))*\n- [`24ca504`](https://github.com/tobymao/sqlglot/commit/24ca504360779c8a20a58accf506eb9600ac9bf8) - **bigquery**: Crash when ARRAY_CONCAT is called with no expressions *(PR [#5755](https://github.com/tobymao/sqlglot/pull/5755) by [@ozadari](https://github.com/ozadari))*\n\n### :wrench: Chores\n- [`41521e3`](https://github.com/tobymao/sqlglot/commit/41521e31b465acd51ab02b1ac4e5512b98175b7e) - bump sqlglotrs to 0.6.2 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.10.0] - 2025-08-28\n### :boom: BREAKING CHANGES\n- due to [`de2fe15`](https://github.com/tobymao/sqlglot/commit/de2fe1503b5bb003431d1f0c7b9ae87932a6cc1c) - annotate type for bq CONTAINS_SUBSTR *(PR [#5705](https://github.com/tobymao/sqlglot/pull/5705) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq CONTAINS_SUBSTR (#5705)\n\n- due to [`770888f`](https://github.com/tobymao/sqlglot/commit/770888f4e9a9061329e3c416f968f7dd9639fb81) - annotate type for bq NORMALIZE *(PR [#5711](https://github.com/tobymao/sqlglot/pull/5711) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bq NORMALIZE (#5711)\n\n- due to [`506033f`](https://github.com/tobymao/sqlglot/commit/506033f299f7a4c28f6efd8bf715be5dcf73e929) - parse and annotate type for bq NORMALIZE_AND_CASEFOLD *(PR [#5712](https://github.com/tobymao/sqlglot/pull/5712) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq NORMALIZE_AND_CASEFOLD (#5712)\n\n- due to [`848aea1`](https://github.com/tobymao/sqlglot/commit/848aea1dbaaeb580b633796dcca06c28314b9c3e) - parse and annotate type for bq OCTET_LENGTH *(PR [#5713](https://github.com/tobymao/sqlglot/pull/5713) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq OCTET_LENGTH (#5713)\n\n- due to [`727bf83`](https://github.com/tobymao/sqlglot/commit/727bf8378f232188d35834d980b035552999ea3b) - add support for REVOKE DDL *(PR [#5703](https://github.com/tobymao/sqlglot/pull/5703) by [@newtonapple](https://github.com/newtonapple))*:\n\n  add support for REVOKE DDL (#5703)\n\n\n### :sparkles: New Features\n- [`f6f8f56`](https://github.com/tobymao/sqlglot/commit/f6f8f56a59d550dfc7dfcab0c3b9a6885c7e758a) - **singlestore**: Fixed parsing/generation of exp.JSONFormat *(PR [#5706](https://github.com/tobymao/sqlglot/pull/5706) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`de2fe15`](https://github.com/tobymao/sqlglot/commit/de2fe1503b5bb003431d1f0c7b9ae87932a6cc1c) - **optimizer**: annotate type for bq CONTAINS_SUBSTR *(PR [#5705](https://github.com/tobymao/sqlglot/pull/5705) by [@geooo109](https://github.com/geooo109))*\n- [`a78146e`](https://github.com/tobymao/sqlglot/commit/a78146e37bfc972050b4467c39769407061e9bc3) - **singlestore**: Fixed parsing/generation of exp.DateBin *(PR [#5709](https://github.com/tobymao/sqlglot/pull/5709) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`ab0c985`](https://github.com/tobymao/sqlglot/commit/ab0c985424ae9d9340eafd15ecdc9b31bdd8837c) - **singlestore**: Marked exp.Reduce finish argument as unsupported *(PR [#5707](https://github.com/tobymao/sqlglot/pull/5707) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`770888f`](https://github.com/tobymao/sqlglot/commit/770888f4e9a9061329e3c416f968f7dd9639fb81) - **optimizer**: annotate type for bq NORMALIZE *(PR [#5711](https://github.com/tobymao/sqlglot/pull/5711) by [@geooo109](https://github.com/geooo109))*\n- [`506033f`](https://github.com/tobymao/sqlglot/commit/506033f299f7a4c28f6efd8bf715be5dcf73e929) - **optimizer**: parse and annotate type for bq NORMALIZE_AND_CASEFOLD *(PR [#5712](https://github.com/tobymao/sqlglot/pull/5712) by [@geooo109](https://github.com/geooo109))*\n- [`848aea1`](https://github.com/tobymao/sqlglot/commit/848aea1dbaaeb580b633796dcca06c28314b9c3e) - **optimizer**: parse and annotate type for bq OCTET_LENGTH *(PR [#5713](https://github.com/tobymao/sqlglot/pull/5713) by [@geooo109](https://github.com/geooo109))*\n- [`727bf83`](https://github.com/tobymao/sqlglot/commit/727bf8378f232188d35834d980b035552999ea3b) - add support for REVOKE DDL *(PR [#5703](https://github.com/tobymao/sqlglot/pull/5703) by [@newtonapple](https://github.com/newtonapple))*\n\n### :bug: Bug Fixes\n- [`0427c7b`](https://github.com/tobymao/sqlglot/commit/0427c7b7aa9f8161324085a98c5f531fa35c8b0c) - **optimizer**: qualify columns for AggFunc with DISTINCT *(PR [#5708](https://github.com/tobymao/sqlglot/pull/5708) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5698](https://github.com/tobymao/sqlglot/issues/5698) opened by [@georgesittas](https://github.com/georgesittas)*\n\n\n## [v27.9.0] - 2025-08-27\n### :boom: BREAKING CHANGES\n- due to [`7b180bd`](https://github.com/tobymao/sqlglot/commit/7b180bdc3da9e39946c22970bd2523f7d8beaf29) - raise if query modifier is specified multiple times *(PR [#5608](https://github.com/tobymao/sqlglot/pull/5608) by [@georgesittas](https://github.com/georgesittas))*:\n\n  raise if query modifier is specified multiple times (#5608)\n\n- due to [`36602a2`](https://github.com/tobymao/sqlglot/commit/36602a2ecc9ffca98e89044d23e40f33c6ed71e4) - parse LIST_FILTER into ArrayFilter closes [#5633](https://github.com/tobymao/sqlglot/pull/5633) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse LIST_FILTER into ArrayFilter closes #5633\n\n- due to [`0188d21`](https://github.com/tobymao/sqlglot/commit/0188d21d443c991a528eb9d220459890b7dca477) - parse LIST_TRANSFORM into Transform closes [#5634](https://github.com/tobymao/sqlglot/pull/5634) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse LIST_TRANSFORM into Transform closes #5634\n\n- due to [`3ab1d44`](https://github.com/tobymao/sqlglot/commit/3ab1d4487279cab3be2d3764e51516c6db21629d) - Wrap CONCAT items with COALESCE less aggressively *(PR [#5641](https://github.com/tobymao/sqlglot/pull/5641) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Wrap CONCAT items with COALESCE less aggressively (#5641)\n\n- due to [`af0b299`](https://github.com/tobymao/sqlglot/commit/af0b299561914953b30ab36004e53dcb92d39e1c) - Qualify columns generated by exp.Aliases *(PR [#5647](https://github.com/tobymao/sqlglot/pull/5647) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Qualify columns generated by exp.Aliases (#5647)\n\n- due to [`53aa8fe`](https://github.com/tobymao/sqlglot/commit/53aa8fe7f188012f765066f32c4179035fff036d) - support alter table with check closes [#5649](https://github.com/tobymao/sqlglot/pull/5649) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  support alter table with check closes #5649\n\n- due to [`1a60a5a`](https://github.com/tobymao/sqlglot/commit/1a60a5a845c7431d7d3d7ccb71119699316f4b41) - Added parsing/generation of JSON_ARRAY_CONTAINS function *(PR [#5661](https://github.com/tobymao/sqlglot/pull/5661) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*:\n\n  Added parsing/generation of JSON_ARRAY_CONTAINS function (#5661)\n\n- due to [`e0db0a9`](https://github.com/tobymao/sqlglot/commit/e0db0a95d3cb7614242dbd1b439d408e7e7bd475) - add parse and annotate type for bigquery FARM_FINGERPRINT *(PR [#5667](https://github.com/tobymao/sqlglot/pull/5667) by [@geooo109](https://github.com/geooo109))*:\n\n  add parse and annotate type for bigquery FARM_FINGERPRINT (#5667)\n\n- due to [`56588c7`](https://github.com/tobymao/sqlglot/commit/56588c7e22b4db4f0e44696a460483ca1e549163) - Add support for vector_search function. Move predict to BigQuery dialect. *(PR [#5660](https://github.com/tobymao/sqlglot/pull/5660) by [@rloredo](https://github.com/rloredo))*:\n\n  Add support for vector_search function. Move predict to BigQuery dialect. (#5660)\n\n- due to [`a688a0f`](https://github.com/tobymao/sqlglot/commit/a688a0f0d70f87139e531d1419b338b695bec384) - parse and annotate type for bigquery APPROX_TOP_COUNT *(PR [#5670](https://github.com/tobymao/sqlglot/pull/5670) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery APPROX_TOP_COUNT (#5670)\n\n- due to [`3c93fcc`](https://github.com/tobymao/sqlglot/commit/3c93fcce96ec82e78753f6c9dd5fb0e730a82058) - parse and annotate type for bigquery APPROX_TOP_SUM *(PR [#5675](https://github.com/tobymao/sqlglot/pull/5675) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery APPROX_TOP_SUM (#5675)\n\n- due to [`741d45a`](https://github.com/tobymao/sqlglot/commit/741d45a0ca7c1bad67da4393cd10cc9cfa49ea68) - parse and annotate type for bigquery FROM/TO_BASE32 *(PR [#5676](https://github.com/tobymao/sqlglot/pull/5676) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery FROM/TO_BASE32 (#5676)\n\n- due to [`9ae045c`](https://github.com/tobymao/sqlglot/commit/9ae045c0405e43b148e3b9261825288ebf09100c) - parse and annotate type for bigquery FROM_HEX *(PR [#5679](https://github.com/tobymao/sqlglot/pull/5679) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery FROM_HEX (#5679)\n\n- due to [`5a22a25`](https://github.com/tobymao/sqlglot/commit/5a22a254143978989027f6e7f6163019a34f112a) - annotate type for bigquery TO_HEX *(PR [#5680](https://github.com/tobymao/sqlglot/pull/5680) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery TO_HEX (#5680)\n\n- due to [`5c1eb2d`](https://github.com/tobymao/sqlglot/commit/5c1eb2df5dd3dcc6ed2c8204cec56b5c3d276f87) - parse and annotate type for bq PARSE_BIG/NUMERIC *(PR [#5690](https://github.com/tobymao/sqlglot/pull/5690) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq PARSE_BIG/NUMERIC (#5690)\n\n- due to [`311373d`](https://github.com/tobymao/sqlglot/commit/311373d22134de906d1c1cef019541e85e2f7c9f) - parse and annotate type for bq CODE_POINTS_TO_BYTES *(PR [#5686](https://github.com/tobymao/sqlglot/pull/5686) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq CODE_POINTS_TO_BYTES (#5686)\n\n- due to [`79d9de1`](https://github.com/tobymao/sqlglot/commit/79d9de1745598f8f3ae2c82c1389dd455c946a09) - parse and annotate type for bq TO_CODE_POINTS *(PR [#5685](https://github.com/tobymao/sqlglot/pull/5685) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq TO_CODE_POINTS (#5685)\n\n- due to [`5df3ea9`](https://github.com/tobymao/sqlglot/commit/5df3ea92f59125955124ea1883b777b489db3042) - parse and annotate type for bq SAFE_CONVERT_BYTES_TO_STRING *(PR [#5681](https://github.com/tobymao/sqlglot/pull/5681) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq SAFE_CONVERT_BYTES_TO_STRING (#5681)\n\n- due to [`c832746`](https://github.com/tobymao/sqlglot/commit/c832746018fbc2c531d5b2a7c7f8cd5d78e511ff) - parse and annotate type for bigquery APPROX_QUANTILES *(PR [#5678](https://github.com/tobymao/sqlglot/pull/5678) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery APPROX_QUANTILES (#5678)\n\n- due to [`99e169e`](https://github.com/tobymao/sqlglot/commit/99e169ea13d5be3712a47f6b55b98a4764a3c24d) - parse and annotate type for bq BOOL *(PR [#5697](https://github.com/tobymao/sqlglot/pull/5697) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq BOOL (#5697)\n\n- due to [`3f31770`](https://github.com/tobymao/sqlglot/commit/3f31770c793f464fcac1ce2b8dfa03d4b7f0231c) - parse and annotate type for bq FLOAT64 *(PR [#5700](https://github.com/tobymao/sqlglot/pull/5700) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bq FLOAT64 (#5700)\n\n\n### :sparkles: New Features\n- [`02e60e7`](https://github.com/tobymao/sqlglot/commit/02e60e73fc0c2dae815aa225be247a17ccdf4b82) - **singlestore**: desugarize DAYNAME into DATE_FORMAT *(PR [#5610](https://github.com/tobymao/sqlglot/pull/5610) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`7b180bd`](https://github.com/tobymao/sqlglot/commit/7b180bdc3da9e39946c22970bd2523f7d8beaf29) - **parser**: raise if query modifier is specified multiple times *(PR [#5608](https://github.com/tobymao/sqlglot/pull/5608) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5604](https://github.com/tobymao/sqlglot/issues/5604) opened by [@bricct](https://github.com/bricct)*\n- [`442eafc`](https://github.com/tobymao/sqlglot/commit/442eafcb00a2650930bd6023aa9a5febfebbe796) - **singlestore**: Added parsing of HOUR function *(PR [#5612](https://github.com/tobymao/sqlglot/pull/5612) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`5320359`](https://github.com/tobymao/sqlglot/commit/532035978605efd1d43de75aafca750e2894c0b9) - **singlestore**: Added parsing of MICROSECOND function *(PR [#5619](https://github.com/tobymao/sqlglot/pull/5619) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`db1db97`](https://github.com/tobymao/sqlglot/commit/db1db9732352187629df853ad937ebaf4abfe487) - **doris**: update exp.UniqueKeyProperty SQL generation logic *(PR [#5613](https://github.com/tobymao/sqlglot/pull/5613) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`54623a6`](https://github.com/tobymao/sqlglot/commit/54623a6b85432272703f12a197b05ced78529f90) - **singlestore**: Added parsing of MINUTE function *(PR [#5620](https://github.com/tobymao/sqlglot/pull/5620) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`565c9f8`](https://github.com/tobymao/sqlglot/commit/565c9f8c55cfbef5d3a9e1470551f1dc4416825e) - **singlestore**: Added generation of DAYOFWEEK_ISO function *(PR [#5627](https://github.com/tobymao/sqlglot/pull/5627) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`8db916e`](https://github.com/tobymao/sqlglot/commit/8db916e2f2ce241bdff130d626f98df182b48f3e) - **singlestore**: Added parsing of WEEKDAY function *(PR [#5624](https://github.com/tobymao/sqlglot/pull/5624) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`aa6274a`](https://github.com/tobymao/sqlglot/commit/aa6274a0ea647df1251563945635260a6ddd4972) - **singlestore**: Fixed generation of DAY_OF_MONTH function *(PR [#5629](https://github.com/tobymao/sqlglot/pull/5629) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`dee44b8`](https://github.com/tobymao/sqlglot/commit/dee44b8c1d70ca6079867896fb68cad256909dad) - **singlestore**: Added parsing of MONTHNAME function *(PR [#5623](https://github.com/tobymao/sqlglot/pull/5623) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`deebf0c`](https://github.com/tobymao/sqlglot/commit/deebf0c3cc379e28c4ab66b6bb7a9c84c14e88c6) - **singlestore**: Added parsing of SECOND function *(PR [#5621](https://github.com/tobymao/sqlglot/pull/5621) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`12a60b9`](https://github.com/tobymao/sqlglot/commit/12a60b99b6b2b0673b57218c691794deb67aa3a5) - **singlestore**: Removed redundant deletions from TRANSFORMS *(PR [#5632](https://github.com/tobymao/sqlglot/pull/5632) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`36602a2`](https://github.com/tobymao/sqlglot/commit/36602a2ecc9ffca98e89044d23e40f33c6ed71e4) - **duckdb**: parse LIST_FILTER into ArrayFilter closes [#5633](https://github.com/tobymao/sqlglot/pull/5633) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`0188d21`](https://github.com/tobymao/sqlglot/commit/0188d21d443c991a528eb9d220459890b7dca477) - **duckdb**: parse LIST_TRANSFORM into Transform closes [#5634](https://github.com/tobymao/sqlglot/pull/5634) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b117d59`](https://github.com/tobymao/sqlglot/commit/b117d59f3c43f6f44cd0ccdf22717f7bcd990889) - **dremio**: add dremio date_add and date_sub parsing *(PR [#5617](https://github.com/tobymao/sqlglot/pull/5617) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`999b9e7`](https://github.com/tobymao/sqlglot/commit/999b9e793c0819a4d2af6400fc924946d26b3e6f) - **singlestore**: Changed generation of exp.TsOrDsToDate to handle case when format is not provided *(PR [#5639](https://github.com/tobymao/sqlglot/pull/5639) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`b556e97`](https://github.com/tobymao/sqlglot/commit/b556e97f8cfbde21c0a921ac1c01c9e4f2ec2535) - **singlestore**: Marked exp.All as unsupported *(PR [#5640](https://github.com/tobymao/sqlglot/pull/5640) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`c076694`](https://github.com/tobymao/sqlglot/commit/c0766946e6799fb61c38e855fd18812d08a5c251) - **clickhouse**: support custom partition key expressions *(PR [#5645](https://github.com/tobymao/sqlglot/pull/5645) by [@GaliFFun](https://github.com/GaliFFun))*\n- [`cab62b0`](https://github.com/tobymao/sqlglot/commit/cab62b06ce926e3116a6a45a9c57e4901cd8a281) - **doris**: add support for BUILD and REFRESH properties in materialized view *(PR [#5614](https://github.com/tobymao/sqlglot/pull/5614) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`af0b299`](https://github.com/tobymao/sqlglot/commit/af0b299561914953b30ab36004e53dcb92d39e1c) - **optimizer**: Qualify columns generated by exp.Aliases *(PR [#5647](https://github.com/tobymao/sqlglot/pull/5647) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5638](https://github.com/tobymao/sqlglot/issues/5638) opened by [@catlynkong](https://github.com/catlynkong)*\n- [`981e0e7`](https://github.com/tobymao/sqlglot/commit/981e0e70a304665e746158c859bcc81f99384685) - **doris**: add support for PARTITION BY LIST *(PR [#5615](https://github.com/tobymao/sqlglot/pull/5615) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`53aa8fe`](https://github.com/tobymao/sqlglot/commit/53aa8fe7f188012f765066f32c4179035fff036d) - **tsql**: support alter table with check closes [#5649](https://github.com/tobymao/sqlglot/pull/5649) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`23cac6c`](https://github.com/tobymao/sqlglot/commit/23cac6c58099a9ac818ac5d3970a427ca3579cca) - **exasol**: Add support for GROUP_CONCAT and LISTAGG functions *(PR [#5646](https://github.com/tobymao/sqlglot/pull/5646) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`d087ac8`](https://github.com/tobymao/sqlglot/commit/d087ac89376df5ab16de99c8b67f99060f0a6170) - **bigquery**: Add support for ml.generate_embedding function *(PR [#5652](https://github.com/tobymao/sqlglot/pull/5652) by [@rloredo](https://github.com/rloredo))*\n- [`e71bcb5`](https://github.com/tobymao/sqlglot/commit/e71bcb51181de63c8ad13004216506529fcf9644) - **dremio**: support array_generate_range *(PR [#5653](https://github.com/tobymao/sqlglot/pull/5653) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`edbd04b`](https://github.com/tobymao/sqlglot/commit/edbd04b6a91b1a6f76e4fa938098ba5ed581ba72) - **singlestore**: Fixed generation of exp.RegexpLike *(PR [#5663](https://github.com/tobymao/sqlglot/pull/5663) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`4992edb`](https://github.com/tobymao/sqlglot/commit/4992edbb79f4922917cc5ce5aa687e6f7da7798c) - **singlestore**: Fixed exp.Xor generation *(PR [#5662](https://github.com/tobymao/sqlglot/pull/5662) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`20de3d3`](https://github.com/tobymao/sqlglot/commit/20de3d37cdae0705c67f80fbacbe024a62f34657) - **singlestore**: Fixed parsing/generation of exp.Hll *(PR [#5664](https://github.com/tobymao/sqlglot/pull/5664) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`1a60a5a`](https://github.com/tobymao/sqlglot/commit/1a60a5a845c7431d7d3d7ccb71119699316f4b41) - **singlestore**: Added parsing/generation of JSON_ARRAY_CONTAINS function *(PR [#5661](https://github.com/tobymao/sqlglot/pull/5661) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`f662dc0`](https://github.com/tobymao/sqlglot/commit/f662dc0b47fd14d00899c14a899756a5ba1fe9da) - **singlestore**: Fixed generation of exp.ApproxDistinct *(PR [#5666](https://github.com/tobymao/sqlglot/pull/5666) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e0db0a9`](https://github.com/tobymao/sqlglot/commit/e0db0a95d3cb7614242dbd1b439d408e7e7bd475) - **optimizer**: add parse and annotate type for bigquery FARM_FINGERPRINT *(PR [#5667](https://github.com/tobymao/sqlglot/pull/5667) by [@geooo109](https://github.com/geooo109))*\n- [`dcd4ef7`](https://github.com/tobymao/sqlglot/commit/dcd4ef769727ed1227911f2d9a85244d61173003) - **singlestore**: Fixed exp.CountIf generation *(PR [#5668](https://github.com/tobymao/sqlglot/pull/5668) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`e431e85`](https://github.com/tobymao/sqlglot/commit/e431e851c2c5d20f049adbc38e370a64d39c346f) - **singlestore**: Fixed generation of exp.LogicalOr *(PR [#5669](https://github.com/tobymao/sqlglot/pull/5669) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`56588c7`](https://github.com/tobymao/sqlglot/commit/56588c7e22b4db4f0e44696a460483ca1e549163) - **bigquery**: Add support for vector_search function. Move predict to BigQuery dialect. *(PR [#5660](https://github.com/tobymao/sqlglot/pull/5660) by [@rloredo](https://github.com/rloredo))*\n- [`f0d2cc2`](https://github.com/tobymao/sqlglot/commit/f0d2cc2b0f72340172ecd154f632aa6a24c15512) - **singlestore**: Fixed generation of exp.LogicalAnd *(PR [#5671](https://github.com/tobymao/sqlglot/pull/5671) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`a688a0f`](https://github.com/tobymao/sqlglot/commit/a688a0f0d70f87139e531d1419b338b695bec384) - **optimizer**: parse and annotate type for bigquery APPROX_TOP_COUNT *(PR [#5670](https://github.com/tobymao/sqlglot/pull/5670) by [@geooo109](https://github.com/geooo109))*\n- [`fa8d571`](https://github.com/tobymao/sqlglot/commit/fa8d57132b1d21d92eb5de3ba88b41f880e14889) - **singlestore**: Fixed generation/parsing of exp.ApproxQuantile *(PR [#5672](https://github.com/tobymao/sqlglot/pull/5672) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`9955ebe`](https://github.com/tobymao/sqlglot/commit/9955ebe90d3421815738ecb643806add755c5df3) - **singlestore**: Fixed parsing/generation of exp.Variance *(PR [#5673](https://github.com/tobymao/sqlglot/pull/5673) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`3c93fcc`](https://github.com/tobymao/sqlglot/commit/3c93fcce96ec82e78753f6c9dd5fb0e730a82058) - **optimizer**: parse and annotate type for bigquery APPROX_TOP_SUM *(PR [#5675](https://github.com/tobymao/sqlglot/pull/5675) by [@geooo109](https://github.com/geooo109))*\n- [`60cbb9d`](https://github.com/tobymao/sqlglot/commit/60cbb9d0e3c9b5a36c1368c9b5bb05def8ce8658) - **dremio**: add CURRENT_DATE_UTC *(PR [#5674](https://github.com/tobymao/sqlglot/pull/5674) by [@jasonthomassql](https://github.com/jasonthomassql))*\n  - :arrow_lower_right: *addresses issue [#5655](https://github.com/tobymao/sqlglot/issues/5655) opened by [@jasonthomassql](https://github.com/jasonthomassql)*\n- [`741d45a`](https://github.com/tobymao/sqlglot/commit/741d45a0ca7c1bad67da4393cd10cc9cfa49ea68) - **optimizer**: parse and annotate type for bigquery FROM/TO_BASE32 *(PR [#5676](https://github.com/tobymao/sqlglot/pull/5676) by [@geooo109](https://github.com/geooo109))*\n- [`9ae045c`](https://github.com/tobymao/sqlglot/commit/9ae045c0405e43b148e3b9261825288ebf09100c) - **optimizer**: parse and annotate type for bigquery FROM_HEX *(PR [#5679](https://github.com/tobymao/sqlglot/pull/5679) by [@geooo109](https://github.com/geooo109))*\n- [`5a22a25`](https://github.com/tobymao/sqlglot/commit/5a22a254143978989027f6e7f6163019a34f112a) - **optimizer**: annotate type for bigquery TO_HEX *(PR [#5680](https://github.com/tobymao/sqlglot/pull/5680) by [@geooo109](https://github.com/geooo109))*\n- [`d920ac3`](https://github.com/tobymao/sqlglot/commit/d920ac3886ce006d76616bc31884ee2f5c4162bc) - **singlestore**: Fixed parsing/generation of exp.RegexpExtractAll *(PR [#5692](https://github.com/tobymao/sqlglot/pull/5692) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`260c72b`](https://github.com/tobymao/sqlglot/commit/260c72befc0510ebe1d007284c0eef9343de20d7) - **singlestore**: Fixed parsing/generation of exp.Contains *(PR [#5684](https://github.com/tobymao/sqlglot/pull/5684) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`081dc67`](https://github.com/tobymao/sqlglot/commit/081dc673b89d3d8d0709b29e359142297ff64536) - **singlestore**: Fixed generaion/parsing of exp.VariancePop *(PR [#5682](https://github.com/tobymao/sqlglot/pull/5682) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`eb538bf`](https://github.com/tobymao/sqlglot/commit/eb538bf225645d0a54d614733e447c13cf91a37a) - **singlestore**: Fixed generation of exp.Chr *(PR [#5683](https://github.com/tobymao/sqlglot/pull/5683) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`32d9dd1`](https://github.com/tobymao/sqlglot/commit/32d9dd1309ce0876114f57993596c4456aa1d50f) - **singlestore**: Fixed exp.MD5Digest generation *(PR [#5688](https://github.com/tobymao/sqlglot/pull/5688) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`5c1eb2d`](https://github.com/tobymao/sqlglot/commit/5c1eb2df5dd3dcc6ed2c8204cec56b5c3d276f87) - **optimizer**: parse and annotate type for bq PARSE_BIG/NUMERIC *(PR [#5690](https://github.com/tobymao/sqlglot/pull/5690) by [@geooo109](https://github.com/geooo109))*\n- [`6f88500`](https://github.com/tobymao/sqlglot/commit/6f885007a075339cf20034459571a6ae821c61c0) - **singlestore**: Fixed exp.IsAscii generation *(PR [#5687](https://github.com/tobymao/sqlglot/pull/5687) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`311373d`](https://github.com/tobymao/sqlglot/commit/311373d22134de906d1c1cef019541e85e2f7c9f) - **optimizer**: parse and annotate type for bq CODE_POINTS_TO_BYTES *(PR [#5686](https://github.com/tobymao/sqlglot/pull/5686) by [@geooo109](https://github.com/geooo109))*\n- [`79d9de1`](https://github.com/tobymao/sqlglot/commit/79d9de1745598f8f3ae2c82c1389dd455c946a09) - **optimizer**: parse and annotate type for bq TO_CODE_POINTS *(PR [#5685](https://github.com/tobymao/sqlglot/pull/5685) by [@geooo109](https://github.com/geooo109))*\n- [`5df3ea9`](https://github.com/tobymao/sqlglot/commit/5df3ea92f59125955124ea1883b777b489db3042) - **optimizer**: parse and annotate type for bq SAFE_CONVERT_BYTES_TO_STRING *(PR [#5681](https://github.com/tobymao/sqlglot/pull/5681) by [@geooo109](https://github.com/geooo109))*\n- [`c832746`](https://github.com/tobymao/sqlglot/commit/c832746018fbc2c531d5b2a7c7f8cd5d78e511ff) - **optimizer**: parse and annotate type for bigquery APPROX_QUANTILES *(PR [#5678](https://github.com/tobymao/sqlglot/pull/5678) by [@geooo109](https://github.com/geooo109))*\n- [`8fa5ae8`](https://github.com/tobymao/sqlglot/commit/8fa5ae8a61c698abaea265b4950390ea3ddfa7e9) - **singlestore**: Fixed generation/parsing of exp.RegexpExtract *(PR [#5691](https://github.com/tobymao/sqlglot/pull/5691) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`d6d409a`](https://github.com/tobymao/sqlglot/commit/d6d409a548042063f80d02dfaf5b61a0096d1d50) - **singlestore**: Fixed generaion of exp.Repeat *(PR [#5693](https://github.com/tobymao/sqlglot/pull/5693) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`b7db08b`](https://github.com/tobymao/sqlglot/commit/b7db08b96c7d7d02ec54f26b8749b3d57f021d8b) - **singlestore**: Fixed generation of exp.StartsWith *(PR [#5694](https://github.com/tobymao/sqlglot/pull/5694) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`87b04ef`](https://github.com/tobymao/sqlglot/commit/87b04ef0fc2df5064be9e6b75b264cff0639face) - **singlestore**: Fixed generation of exp.FromBase *(PR [#5695](https://github.com/tobymao/sqlglot/pull/5695) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`9c1d0fd`](https://github.com/tobymao/sqlglot/commit/9c1d0fdac9acd3fb3109ca3d3cae9c9ffaed1a7d) - **duckdb**: transpile array unique aggregation closes [#5689](https://github.com/tobymao/sqlglot/pull/5689) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`99e169e`](https://github.com/tobymao/sqlglot/commit/99e169ea13d5be3712a47f6b55b98a4764a3c24d) - **optimizer**: parse and annotate type for bq BOOL *(PR [#5697](https://github.com/tobymao/sqlglot/pull/5697) by [@geooo109](https://github.com/geooo109))*\n- [`3f31770`](https://github.com/tobymao/sqlglot/commit/3f31770c793f464fcac1ce2b8dfa03d4b7f0231c) - **optimizer**: parse and annotate type for bq FLOAT64 *(PR [#5700](https://github.com/tobymao/sqlglot/pull/5700) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`f1269f5`](https://github.com/tobymao/sqlglot/commit/f1269f5ecfccfee4cdeeda5bfd10eb1c47994fad) - **tsql**: do not attach limit modifier to set operation *(PR [#5609](https://github.com/tobymao/sqlglot/pull/5609) by [@georgesittas](https://github.com/georgesittas))*\n- [`a6edf8e`](https://github.com/tobymao/sqlglot/commit/a6edf8ee3273a7736ed801ef8dea302613b119da) - **tsql**: Remove ORDER from set op modifiers too *(PR [#5626](https://github.com/tobymao/sqlglot/pull/5626) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5618](https://github.com/tobymao/sqlglot/issues/5618) opened by [@MQMMMQM](https://github.com/MQMMMQM)*\n- [`ce5840e`](https://github.com/tobymao/sqlglot/commit/ce5840ed615e162a93cd911ab6207160878fcc64) - **exasol**: update several dialect properties to correctly reflect semantics *(PR [#5642](https://github.com/tobymao/sqlglot/pull/5642) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`3ab1d44`](https://github.com/tobymao/sqlglot/commit/3ab1d4487279cab3be2d3764e51516c6db21629d) - **generator**: Wrap CONCAT items with COALESCE less aggressively *(PR [#5641](https://github.com/tobymao/sqlglot/pull/5641) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`045d2f0`](https://github.com/tobymao/sqlglot/commit/045d2f02649b0e6dc178c079e4e0db201ed9bf08) - **duckdb**: Transpile Spark's FIRST(col, TRUE) *(PR [#5644](https://github.com/tobymao/sqlglot/pull/5644) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5643](https://github.com/tobymao/sqlglot/issues/5643) opened by [@michal-clutch](https://github.com/michal-clutch)*\n\n### :wrench: Chores\n- [`4c04c0c`](https://github.com/tobymao/sqlglot/commit/4c04c0ce859ab8314ed36fb8779f14c0fc2f1094) - use a valid SPDX identifier as license classifier *(PR [#5606](https://github.com/tobymao/sqlglot/pull/5606) by [@ecederstrand](https://github.com/ecederstrand))*\n- [`249f638`](https://github.com/tobymao/sqlglot/commit/249f638877ddd2a1732d1e6bc859793f3bc0622d) - add table to document dialect support level *(PR [#5628](https://github.com/tobymao/sqlglot/pull/5628) by [@georgesittas](https://github.com/georgesittas))*\n- [`3357125`](https://github.com/tobymao/sqlglot/commit/33571250d172d64a3e0450738b3ad330e5c0a795) - **doris**: refactor unique key prop generation *(PR [#5625](https://github.com/tobymao/sqlglot/pull/5625) by [@georgesittas](https://github.com/georgesittas))*\n- [`545f1ac`](https://github.com/tobymao/sqlglot/commit/545f1acd76bdc4e537209266984137f6c69ce622) - Clean up of PR5614 *(PR [#5648](https://github.com/tobymao/sqlglot/pull/5648) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v27.8.0] - 2025-08-19\n### :boom: BREAKING CHANGES\n- due to [`2a33339`](https://github.com/tobymao/sqlglot/commit/2a333395cde71936df911488afcff92cae735e11) - annotate type for bigquery REPLACE *(PR [#5572](https://github.com/tobymao/sqlglot/pull/5572) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery REPLACE (#5572)\n\n- due to [`1e6f813`](https://github.com/tobymao/sqlglot/commit/1e6f81343de641e588f1a05ce7dc01bed72bd849) - annotate type for bigquery REGEXP_EXTRACT_ALL *(PR [#5573](https://github.com/tobymao/sqlglot/pull/5573) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery REGEXP_EXTRACT_ALL (#5573)\n\n- due to [`d0d62ed`](https://github.com/tobymao/sqlglot/commit/d0d62ede6320b3fd0eee04b7073f5708676dc58c) - support `TO_CHAR` with numeric inputs *(PR [#5570](https://github.com/tobymao/sqlglot/pull/5570) by [@jasonthomassql](https://github.com/jasonthomassql))*:\n\n  support `TO_CHAR` with numeric inputs (#5570)\n\n- due to [`7928985`](https://github.com/tobymao/sqlglot/commit/7928985a655c3d0244bc9175a37f502b19a5c5f0) - allow dashes in JSONPath keys *(PR [#5574](https://github.com/tobymao/sqlglot/pull/5574) by [@georgesittas](https://github.com/georgesittas))*:\n\n  allow dashes in JSONPath keys (#5574)\n\n- due to [`eb09e6e`](https://github.com/tobymao/sqlglot/commit/eb09e6e32491a05846488de7b72b1dca0e0a2669) - parse and annotate type for bigquery TRANSLATE *(PR [#5575](https://github.com/tobymao/sqlglot/pull/5575) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery TRANSLATE (#5575)\n\n- due to [`f9a522b`](https://github.com/tobymao/sqlglot/commit/f9a522b26cd5d643b8b18fa64d70f2a3f0ff2d2c) - parse and annotate type for bigquery SOUNDEX *(PR [#5576](https://github.com/tobymao/sqlglot/pull/5576) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery SOUNDEX (#5576)\n\n- due to [`51da41b`](https://github.com/tobymao/sqlglot/commit/51da41b90ce421b154e45add28353ac044640a1c) - annotate type for bigquery MD5 *(PR [#5577](https://github.com/tobymao/sqlglot/pull/5577) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery MD5 (#5577)\n\n- due to [`bcf302f`](https://github.com/tobymao/sqlglot/commit/bcf302ff6ad2d0adfc29f708a8b53b5c0e547619) - annotate type for bigquery MIN/MAX BY *(PR [#5579](https://github.com/tobymao/sqlglot/pull/5579) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery MIN/MAX BY (#5579)\n\n- due to [`c501d9e`](https://github.com/tobymao/sqlglot/commit/c501d9e6f58e4880e4d23f21f53f72dcb5fdaa8c) - parse and annotate type for bigquery GROUPING *(PR [#5581](https://github.com/tobymao/sqlglot/pull/5581) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery GROUPING (#5581)\n\n\n### :sparkles: New Features\n- [`2a33339`](https://github.com/tobymao/sqlglot/commit/2a333395cde71936df911488afcff92cae735e11) - **optimizer**: annotate type for bigquery REPLACE *(PR [#5572](https://github.com/tobymao/sqlglot/pull/5572) by [@geooo109](https://github.com/geooo109))*\n- [`1e6f813`](https://github.com/tobymao/sqlglot/commit/1e6f81343de641e588f1a05ce7dc01bed72bd849) - **optimizer**: annotate type for bigquery REGEXP_EXTRACT_ALL *(PR [#5573](https://github.com/tobymao/sqlglot/pull/5573) by [@geooo109](https://github.com/geooo109))*\n- [`eb09e6e`](https://github.com/tobymao/sqlglot/commit/eb09e6e32491a05846488de7b72b1dca0e0a2669) - **optimizer**: parse and annotate type for bigquery TRANSLATE *(PR [#5575](https://github.com/tobymao/sqlglot/pull/5575) by [@geooo109](https://github.com/geooo109))*\n- [`f9a522b`](https://github.com/tobymao/sqlglot/commit/f9a522b26cd5d643b8b18fa64d70f2a3f0ff2d2c) - **optimizer**: parse and annotate type for bigquery SOUNDEX *(PR [#5576](https://github.com/tobymao/sqlglot/pull/5576) by [@geooo109](https://github.com/geooo109))*\n- [`51da41b`](https://github.com/tobymao/sqlglot/commit/51da41b90ce421b154e45add28353ac044640a1c) - **optimizer**: annotate type for bigquery MD5 *(PR [#5577](https://github.com/tobymao/sqlglot/pull/5577) by [@geooo109](https://github.com/geooo109))*\n- [`bcf302f`](https://github.com/tobymao/sqlglot/commit/bcf302ff6ad2d0adfc29f708a8b53b5c0e547619) - **optimizer**: annotate type for bigquery MIN/MAX BY *(PR [#5579](https://github.com/tobymao/sqlglot/pull/5579) by [@geooo109](https://github.com/geooo109))*\n- [`c501d9e`](https://github.com/tobymao/sqlglot/commit/c501d9e6f58e4880e4d23f21f53f72dcb5fdaa8c) - **optimizer**: parse and annotate type for bigquery GROUPING *(PR [#5581](https://github.com/tobymao/sqlglot/pull/5581) by [@geooo109](https://github.com/geooo109))*\n- [`8612825`](https://github.com/tobymao/sqlglot/commit/86128253f911b733d45b073356e3b8ddf261c22b) - **spark**: generate date/time ops as interval binary ops *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`8fda774`](https://github.com/tobymao/sqlglot/commit/8fda774b7a9b0c66948349dfe030d3c122ff6eee) - **singlestore**: Added parsing and generation of JSON_EXTRACT *(PR [#5555](https://github.com/tobymao/sqlglot/pull/5555) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`82cc954`](https://github.com/tobymao/sqlglot/commit/82cc9549a875211a400e5c4e818b05ca48a0a9f4) - **exasol**: map div function to IntDiv in exasol dialect *(PR [#5593](https://github.com/tobymao/sqlglot/pull/5593) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`eb0fe68`](https://github.com/tobymao/sqlglot/commit/eb0fe68d6b5977053c871badf2f5c1895b3e1c66) - **trino**: add JSON_VALUE function support with RETURNING clause *(PR [#5590](https://github.com/tobymao/sqlglot/pull/5590) by [@rev-rwasilewski](https://github.com/rev-rwasilewski))*\n- [`9e95c11`](https://github.com/tobymao/sqlglot/commit/9e95c115ea0304d9ccb4cb0be8389f5ff5f2a952) - **exasol**: mapped weekofyear to week in Exasol dialect *(PR [#5594](https://github.com/tobymao/sqlglot/pull/5594) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`8f013c3`](https://github.com/tobymao/sqlglot/commit/8f013c37a412ca5978889c1e47b0c6f7add0715d) - **singlestore**: Fixed parsing of DATE function *(PR [#5601](https://github.com/tobymao/sqlglot/pull/5601) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`a4a299a`](https://github.com/tobymao/sqlglot/commit/a4a299acbaf4461f0c2b470bc4e9e9590515eda7) - transpile `TO_CHAR` from Dremio to Databricks *(PR [#5598](https://github.com/tobymao/sqlglot/pull/5598) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`093f35c`](https://github.com/tobymao/sqlglot/commit/093f35c201c3c22c3a14c6f8de26c06246bdf19c) - **dremio**: handle `DATE_FORMAT`, `TO_DATE`, and `TO_TIMESTAMP` *(PR [#5597](https://github.com/tobymao/sqlglot/pull/5597) by [@jasonthomassql](https://github.com/jasonthomassql))*\n\n### :bug: Bug Fixes\n- [`d0d62ed`](https://github.com/tobymao/sqlglot/commit/d0d62ede6320b3fd0eee04b7073f5708676dc58c) - **dremio**: support `TO_CHAR` with numeric inputs *(PR [#5570](https://github.com/tobymao/sqlglot/pull/5570) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`7928985`](https://github.com/tobymao/sqlglot/commit/7928985a655c3d0244bc9175a37f502b19a5c5f0) - **bigquery**: allow dashes in JSONPath keys *(PR [#5574](https://github.com/tobymao/sqlglot/pull/5574) by [@georgesittas](https://github.com/georgesittas))*\n- [`866042d`](https://github.com/tobymao/sqlglot/commit/866042d0268da0cebce042c0868878c0fb39c3d1) - Remove TokenType.APPLY from table alias tokens *(PR [#5592](https://github.com/tobymao/sqlglot/pull/5592) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5591](https://github.com/tobymao/sqlglot/issues/5591) opened by [@saadbelgi](https://github.com/saadbelgi)*\n- [`b485f66`](https://github.com/tobymao/sqlglot/commit/b485f6666fa8625b7da45ef832b5d666fbb707ea) - **dremio**: improve `TO_CHAR` transpilability *(PR [#5580](https://github.com/tobymao/sqlglot/pull/5580) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`81874e9`](https://github.com/tobymao/sqlglot/commit/81874e9c3aafcc2cf8fb443f65146c5b3598b9b3) - handle unknown types in `unit_to_str` *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`173e442`](https://github.com/tobymao/sqlglot/commit/173e4425b692728abffa8542324690823f984303) - refactor JSON_VALUE handling for MySQL and Trino *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.7.0] - 2025-08-13\n### :boom: BREAKING CHANGES\n- due to [`938f4b6`](https://github.com/tobymao/sqlglot/commit/938f4b6ebc1c0d26bd3c1400883978c79a435189) - annotate type for LAST_DAY *(PR [#5528](https://github.com/tobymao/sqlglot/pull/5528) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for LAST_DAY (#5528)\n\n- due to [`7d12dac`](https://github.com/tobymao/sqlglot/commit/7d12dac613ba5119334408f2c52cb270067156d9) - annotate type for bigquery GENERATE_TIMESTAMP_ARRAY *(PR [#5529](https://github.com/tobymao/sqlglot/pull/5529) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery GENERATE_TIMESTAMP_ARRAY (#5529)\n\n- due to [`d50ebe2`](https://github.com/tobymao/sqlglot/commit/d50ebe286dd8e2836b9eb2a3406f15976db3aa05) - annotate type for bigquery TIME_TRUNC *(PR [#5530](https://github.com/tobymao/sqlglot/pull/5530) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery TIME_TRUNC (#5530)\n\n- due to [`29748be`](https://github.com/tobymao/sqlglot/commit/29748be7dfc10edc9f29665c98327883dd25c13d) - annotate type for bigquery TIME *(PR [#5531](https://github.com/tobymao/sqlglot/pull/5531) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery TIME (#5531)\n\n- due to [`7003b3f`](https://github.com/tobymao/sqlglot/commit/7003b3fa39cd455e3643066364696708d1ac4f38) - parse and annotate type for bigquery DATE_FROM_UNIX_DATE *(PR [#5532](https://github.com/tobymao/sqlglot/pull/5532) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery DATE_FROM_UNIX_DATE (#5532)\n\n- due to [`a276ca6`](https://github.com/tobymao/sqlglot/commit/a276ca6fd5f9d47fa8c90fcfa19f9864e7a28f8f) - parse and annotate type for bigquery JUSTIFY funcs *(PR [#5534](https://github.com/tobymao/sqlglot/pull/5534) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery JUSTIFY funcs (#5534)\n\n- due to [`374178e`](https://github.com/tobymao/sqlglot/commit/374178e22fe8d2d2275b65fe08e27ef66c611220) - parse and annotate type for bigquery UNIX_MICROS and UNIX_MILLIS *(PR [#5535](https://github.com/tobymao/sqlglot/pull/5535) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery UNIX_MICROS and UNIX_MILLIS (#5535)\n\n- due to [`1d8d1ab`](https://github.com/tobymao/sqlglot/commit/1d8d1abe459053a135a46525d0a13bb861220927) - annotate type for bigquery DATE_TRUNC *(PR [#5540](https://github.com/tobymao/sqlglot/pull/5540) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery DATE_TRUNC (#5540)\n\n- due to [`306ba65`](https://github.com/tobymao/sqlglot/commit/306ba6531839ea2823f5165de7bde01d17560845) - annotate type for bigquery TIMESTAMP_TRUNC *(PR [#5541](https://github.com/tobymao/sqlglot/pull/5541) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery TIMESTAMP_TRUNC (#5541)\n\n- due to [`d799c5a`](https://github.com/tobymao/sqlglot/commit/d799c5af23010a67c29edb6d45a40fb24903e1a3) - preserve projection names when merging subqueries *(commit by [@snovik75](https://github.com/snovik75))*:\n\n  preserve projection names when merging subqueries\n\n- due to [`8130bd4`](https://github.com/tobymao/sqlglot/commit/8130bd40815803a6781ee8f20fccd30987516192) - WEEKDAY of WEEK as VAR *(PR [#5552](https://github.com/tobymao/sqlglot/pull/5552) by [@geooo109](https://github.com/geooo109))*:\n\n  WEEKDAY of WEEK as VAR (#5552)\n\n- due to [`f3ffe19`](https://github.com/tobymao/sqlglot/commit/f3ffe19ec01533c5f27b9d3a7b6704b83c005118) - annotate type for bigquery format_time *(PR [#5559](https://github.com/tobymao/sqlglot/pull/5559) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery format_time (#5559)\n\n- due to [`6872b43`](https://github.com/tobymao/sqlglot/commit/6872b43ba17a39137172fd2fa9f0d059ce595ef9) - use dialect in DataType.build fixes [#5560](https://github.com/tobymao/sqlglot/pull/5560) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  use dialect in DataType.build fixes #5560\n\n- due to [`3ab3690`](https://github.com/tobymao/sqlglot/commit/3ab369096313b418699b7942b1c513c0c66a5331) - parse and annotate type for bigquery PARSE_DATETIME *(PR [#5558](https://github.com/tobymao/sqlglot/pull/5558) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery PARSE_DATETIME (#5558)\n\n- due to [`e5da951`](https://github.com/tobymao/sqlglot/commit/e5da951542eb55691bc43fbbfbec4a30100de038) - parse and annotate type for bigquery PARSE_TIME *(PR [#5561](https://github.com/tobymao/sqlglot/pull/5561) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery PARSE_TIME (#5561)\n\n- due to [`798e213`](https://github.com/tobymao/sqlglot/commit/798e213fd10c3b61afbd8cef621546de65fa6f26) - improve transpilability of ANY_VALUE closes [#5563](https://github.com/tobymao/sqlglot/pull/5563) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve transpilability of ANY_VALUE closes #5563\n\n- due to [`8c0cb76`](https://github.com/tobymao/sqlglot/commit/8c0cb764fd825062fb7334032b8eeffbc39627d5) - more robust CREATE SEQUENCE *(PR [#5566](https://github.com/tobymao/sqlglot/pull/5566) by [@geooo109](https://github.com/geooo109))*:\n\n  more robust CREATE SEQUENCE (#5566)\n\n- due to [`c7041c7`](https://github.com/tobymao/sqlglot/commit/c7041c71250b17192c2f25fb8f33407324d332c2) - parse and annotate type for bigquery BYTE_LENGHT *(PR [#5568](https://github.com/tobymao/sqlglot/pull/5568) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery BYTE_LENGHT (#5568)\n\n- due to [`a6c61c3`](https://github.com/tobymao/sqlglot/commit/a6c61c34f1e168c97dd5c2b8ec071372ba593992) - parse and annotate type for bigquery CODE_POINTS_TO_STRING *(PR [#5569](https://github.com/tobymao/sqlglot/pull/5569) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery CODE_POINTS_TO_STRING (#5569)\n\n- due to [`51e0335`](https://github.com/tobymao/sqlglot/commit/51e0335377fe2bc2e2a94a623475791e9dd19fb9) - parse and annotate type for bigquery REVERSE *(PR [#5571](https://github.com/tobymao/sqlglot/pull/5571) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for bigquery REVERSE (#5571)\n\n\n### :sparkles: New Features\n- [`1fb90db`](https://github.com/tobymao/sqlglot/commit/1fb90db52b59e6e3a40597c6f611d0476b72025b) - **teradata**: Add support for Teradata set query band expression *(PR [#5519](https://github.com/tobymao/sqlglot/pull/5519) by [@treff7es](https://github.com/treff7es))*\n- [`a49baaf`](https://github.com/tobymao/sqlglot/commit/a49baaf717cb41abb25ca51ae5adddc8473baa8b) - **doris**: Override table_sql to avoid AS keyword in UPDATE and DELETE statements *(PR [#5517](https://github.com/tobymao/sqlglot/pull/5517) by [@peterylh](https://github.com/peterylh))*\n- [`75fd6d2`](https://github.com/tobymao/sqlglot/commit/75fd6d21fb7bc8399432e73d10b4837ae62d2ab5) - **exasol**: Add support for date difference functions in Exasol dialect *(PR [#5510](https://github.com/tobymao/sqlglot/pull/5510) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`2a91bb4`](https://github.com/tobymao/sqlglot/commit/2a91bb4f17c7569a5b409cc07e970e5d68235149) - **teradata**: Add support for Teradata locking select *(PR [#5524](https://github.com/tobymao/sqlglot/pull/5524) by [@treff7es](https://github.com/treff7es))*\n- [`938f4b6`](https://github.com/tobymao/sqlglot/commit/938f4b6ebc1c0d26bd3c1400883978c79a435189) - **optimizer**: annotate type for LAST_DAY *(PR [#5528](https://github.com/tobymao/sqlglot/pull/5528) by [@geooo109](https://github.com/geooo109))*\n- [`7d12dac`](https://github.com/tobymao/sqlglot/commit/7d12dac613ba5119334408f2c52cb270067156d9) - **optimizer**: annotate type for bigquery GENERATE_TIMESTAMP_ARRAY *(PR [#5529](https://github.com/tobymao/sqlglot/pull/5529) by [@geooo109](https://github.com/geooo109))*\n- [`d50ebe2`](https://github.com/tobymao/sqlglot/commit/d50ebe286dd8e2836b9eb2a3406f15976db3aa05) - **optimizer**: annotate type for bigquery TIME_TRUNC *(PR [#5530](https://github.com/tobymao/sqlglot/pull/5530) by [@geooo109](https://github.com/geooo109))*\n- [`29748be`](https://github.com/tobymao/sqlglot/commit/29748be7dfc10edc9f29665c98327883dd25c13d) - **optimizer**: annotate type for bigquery TIME *(PR [#5531](https://github.com/tobymao/sqlglot/pull/5531) by [@geooo109](https://github.com/geooo109))*\n- [`7003b3f`](https://github.com/tobymao/sqlglot/commit/7003b3fa39cd455e3643066364696708d1ac4f38) - **optimizer**: parse and annotate type for bigquery DATE_FROM_UNIX_DATE *(PR [#5532](https://github.com/tobymao/sqlglot/pull/5532) by [@geooo109](https://github.com/geooo109))*\n- [`a276ca6`](https://github.com/tobymao/sqlglot/commit/a276ca6fd5f9d47fa8c90fcfa19f9864e7a28f8f) - **optimizer**: parse and annotate type for bigquery JUSTIFY funcs *(PR [#5534](https://github.com/tobymao/sqlglot/pull/5534) by [@geooo109](https://github.com/geooo109))*\n- [`374178e`](https://github.com/tobymao/sqlglot/commit/374178e22fe8d2d2275b65fe08e27ef66c611220) - **optimizer**: parse and annotate type for bigquery UNIX_MICROS and UNIX_MILLIS *(PR [#5535](https://github.com/tobymao/sqlglot/pull/5535) by [@geooo109](https://github.com/geooo109))*\n- [`1d8d1ab`](https://github.com/tobymao/sqlglot/commit/1d8d1abe459053a135a46525d0a13bb861220927) - **optimizer**: annotate type for bigquery DATE_TRUNC *(PR [#5540](https://github.com/tobymao/sqlglot/pull/5540) by [@geooo109](https://github.com/geooo109))*\n- [`306ba65`](https://github.com/tobymao/sqlglot/commit/306ba6531839ea2823f5165de7bde01d17560845) - **optimizer**: annotate type for bigquery TIMESTAMP_TRUNC *(PR [#5541](https://github.com/tobymao/sqlglot/pull/5541) by [@geooo109](https://github.com/geooo109))*\n- [`6a68cca`](https://github.com/tobymao/sqlglot/commit/6a68cca97ad4bdd75c544ada0a5af0fa92ec4664) - **dremio**: support lowercase `TIME_MAPPING` formats *(PR [#5556](https://github.com/tobymao/sqlglot/pull/5556) by [@jasonthomassql](https://github.com/jasonthomassql))*\n- [`f3ffe19`](https://github.com/tobymao/sqlglot/commit/f3ffe19ec01533c5f27b9d3a7b6704b83c005118) - **optimizer**: annotate type for bigquery format_time *(PR [#5559](https://github.com/tobymao/sqlglot/pull/5559) by [@geooo109](https://github.com/geooo109))*\n- [`3ab3690`](https://github.com/tobymao/sqlglot/commit/3ab369096313b418699b7942b1c513c0c66a5331) - **optimizer**: parse and annotate type for bigquery PARSE_DATETIME *(PR [#5558](https://github.com/tobymao/sqlglot/pull/5558) by [@geooo109](https://github.com/geooo109))*\n- [`e5da951`](https://github.com/tobymao/sqlglot/commit/e5da951542eb55691bc43fbbfbec4a30100de038) - **optimizer**: parse and annotate type for bigquery PARSE_TIME *(PR [#5561](https://github.com/tobymao/sqlglot/pull/5561) by [@geooo109](https://github.com/geooo109))*\n- [`902a0cd`](https://github.com/tobymao/sqlglot/commit/902a0cdfe46f693aa55612d45a2de2def21f0b8c) - **singlestore**: Added parsing/generation of UNIXTIME functions *(PR [#5562](https://github.com/tobymao/sqlglot/pull/5562) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`798e213`](https://github.com/tobymao/sqlglot/commit/798e213fd10c3b61afbd8cef621546de65fa6f26) - **duckdb**: improve transpilability of ANY_VALUE closes [#5563](https://github.com/tobymao/sqlglot/pull/5563) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c7041c7`](https://github.com/tobymao/sqlglot/commit/c7041c71250b17192c2f25fb8f33407324d332c2) - **optimizer**: parse and annotate type for bigquery BYTE_LENGHT *(PR [#5568](https://github.com/tobymao/sqlglot/pull/5568) by [@geooo109](https://github.com/geooo109))*\n- [`a6c61c3`](https://github.com/tobymao/sqlglot/commit/a6c61c34f1e168c97dd5c2b8ec071372ba593992) - **optimizer**: parse and annotate type for bigquery CODE_POINTS_TO_STRING *(PR [#5569](https://github.com/tobymao/sqlglot/pull/5569) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`9020684`](https://github.com/tobymao/sqlglot/commit/9020684a7e984a10fa4775339596ac5a0d6a6d93) - nested natural join performance closes [#5514](https://github.com/tobymao/sqlglot/pull/5514) *(PR [#5515](https://github.com/tobymao/sqlglot/pull/5515) by [@tobymao](https://github.com/tobymao))*\n- [`394870a`](https://github.com/tobymao/sqlglot/commit/394870a7ee9bb3bc814b7c3847193687f06b432b) - **duckdb**: transpile ADD_MONTHS *(PR [#5523](https://github.com/tobymao/sqlglot/pull/5523) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5505](https://github.com/tobymao/sqlglot/issues/5505) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`249692c`](https://github.com/tobymao/sqlglot/commit/249692c67450a1fe3775e1f35b6f62fdb0a62e1a) - **duckdb**: put guard in AddMonths generator before annotating it *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d799c5a`](https://github.com/tobymao/sqlglot/commit/d799c5af23010a67c29edb6d45a40fb24903e1a3) - **optimizer**: preserve projection names when merging subqueries *(commit by [@snovik75](https://github.com/snovik75))*\n- [`8130bd4`](https://github.com/tobymao/sqlglot/commit/8130bd40815803a6781ee8f20fccd30987516192) - **parser**: WEEKDAY of WEEK as VAR *(PR [#5552](https://github.com/tobymao/sqlglot/pull/5552) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5547](https://github.com/tobymao/sqlglot/issues/5547) opened by [@rloredo](https://github.com/rloredo)*\n- [`4e1373f`](https://github.com/tobymao/sqlglot/commit/4e1373f301cbea3cb5762fc1430b65deae3f9d04) - **doris**: Rename Table *(PR [#5549](https://github.com/tobymao/sqlglot/pull/5549) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`16f544d`](https://github.com/tobymao/sqlglot/commit/16f544dc25d5d61277d32f02e4be18c10d16cf9f) - **doris**: fix DATE_TRUNC and partition by *(PR [#5553](https://github.com/tobymao/sqlglot/pull/5553) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`6295414`](https://github.com/tobymao/sqlglot/commit/6295414fb41401f92993e661b880a0727e74c087) - convert unit to Var instead of choosing default in `unit_to_var` *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6872b43`](https://github.com/tobymao/sqlglot/commit/6872b43ba17a39137172fd2fa9f0d059ce595ef9) - **parser**: use dialect in DataType.build fixes [#5560](https://github.com/tobymao/sqlglot/pull/5560) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6f354d9`](https://github.com/tobymao/sqlglot/commit/6f354d958fb9ca9242b7fc1d2da86af74d57fedc) - **clickhouse**: add ROWS keyword in OFFSET followed by FETCH fixes [#5564](https://github.com/tobymao/sqlglot/pull/5564) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`8c0cb76`](https://github.com/tobymao/sqlglot/commit/8c0cb764fd825062fb7334032b8eeffbc39627d5) - **parser**: more robust CREATE SEQUENCE *(PR [#5566](https://github.com/tobymao/sqlglot/pull/5566) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5537](https://github.com/tobymao/sqlglot/issues/5537) opened by [@tekumara](https://github.com/tekumara)*\n- [`7e9df88`](https://github.com/tobymao/sqlglot/commit/7e9df880bc118d0dbb2dbd6344f805f79af2fe5e) - **doris**: CURRENT_DATE *(PR [#5567](https://github.com/tobymao/sqlglot/pull/5567) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`51e0335`](https://github.com/tobymao/sqlglot/commit/51e0335377fe2bc2e2a94a623475791e9dd19fb9) - **optimizer**: parse and annotate type for bigquery REVERSE *(PR [#5571](https://github.com/tobymao/sqlglot/pull/5571) by [@geooo109](https://github.com/geooo109))*\n\n### :wrench: Chores\n- [`720f634`](https://github.com/tobymao/sqlglot/commit/720f6343f6144e8986ec6b7e50419c3d7a331f0a) - Fix style on main, refactor exasol tests *(PR [#5527](https://github.com/tobymao/sqlglot/pull/5527) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5653501`](https://github.com/tobymao/sqlglot/commit/5653501606f041282b6315c3efa33b9a3baf8d98) - Refactor PR 5517 *(PR [#5526](https://github.com/tobymao/sqlglot/pull/5526) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`d15dfe3`](https://github.com/tobymao/sqlglot/commit/d15dfe3f0f4444e4999ad65051b2474e62f422b3) - build type using dialect for bigquery *(PR [#5539](https://github.com/tobymao/sqlglot/pull/5539) by [@geooo109](https://github.com/geooo109))*\n\n\n## [v27.6.0] - 2025-08-01\n### :boom: BREAKING CHANGES\n- due to [`6b691b3`](https://github.com/tobymao/sqlglot/commit/6b691b33c3528c0377bd8822a3df90de869c6cb1) - Parse and transpile GET(...) extract function *(PR [#5500](https://github.com/tobymao/sqlglot/pull/5500) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Parse and transpile GET(...) extract function (#5500)\n\n- due to [`964a275`](https://github.com/tobymao/sqlglot/commit/964a275b42314380de3b301ada9f9756602729f7) - Make `UNION` column qualification recursive *(PR [#5508](https://github.com/tobymao/sqlglot/pull/5508) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Make `UNION` column qualification recursive (#5508)\n\n\n### :sparkles: New Features\n- [`6b691b3`](https://github.com/tobymao/sqlglot/commit/6b691b33c3528c0377bd8822a3df90de869c6cb1) - **snowflake**: Parse and transpile GET(...) extract function *(PR [#5500](https://github.com/tobymao/sqlglot/pull/5500) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5495](https://github.com/tobymao/sqlglot/issues/5495) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`a2a2f0f`](https://github.com/tobymao/sqlglot/commit/a2a2f0fe910228651c5c39beebcc02172a0b7e94) - **exasol**: Add support for IF, NULLIFZERO, and ZEROIFNULL functions *(PR [#5502](https://github.com/tobymao/sqlglot/pull/5502) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`2d8ce58`](https://github.com/tobymao/sqlglot/commit/2d8ce587c75f21b188ec4c201936eedac3b051e8) - **singlestore**: Added cast operator *(PR [#5504](https://github.com/tobymao/sqlglot/pull/5504) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`6256348`](https://github.com/tobymao/sqlglot/commit/6256348a28b72ae9052d4244736846af209410b0) - **exasol**: add support for ADD_DAYS function in exasol dialect *(PR [#5507](https://github.com/tobymao/sqlglot/pull/5507) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`2f40fc5`](https://github.com/tobymao/sqlglot/commit/2f40fc578a840c9276a4c3b91351fb8d95c837fc) - add more pseudocols to bq which are not expanded by star *(PR [#5509](https://github.com/tobymao/sqlglot/pull/5509) by [@z3z1ma](https://github.com/z3z1ma))*\n\n### :bug: Bug Fixes\n- [`3b52061`](https://github.com/tobymao/sqlglot/commit/3b520611c5a894ddea935d13aadd27c791a8a755) - **exasol**: fix TokenType.TEXT mapping in exasol dialect *(PR [#5506](https://github.com/tobymao/sqlglot/pull/5506) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`964a275`](https://github.com/tobymao/sqlglot/commit/964a275b42314380de3b301ada9f9756602729f7) - Make `UNION` column qualification recursive *(PR [#5508](https://github.com/tobymao/sqlglot/pull/5508) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v27.5.1] - 2025-07-30\n### :bug: Bug Fixes\n- [`caf71d6`](https://github.com/tobymao/sqlglot/commit/caf71d687c0048d2346fddaee58b519e4f2e7945) - `between` builder should not set `symmetric` by default *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.5.0] - 2025-07-30\n### :boom: BREAKING CHANGES\n- due to [`002286e`](https://github.com/tobymao/sqlglot/commit/002286ee05a608e303a2238a9a74ab963709b5da) - remove AM/PM entries from postgres, oracle `TIME_MAPPING` *(PR [#5491](https://github.com/tobymao/sqlglot/pull/5491) by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove AM/PM entries from postgres, oracle `TIME_MAPPING` (#5491)\n\n- due to [`ad78db6`](https://github.com/tobymao/sqlglot/commit/ad78db6c9002a5bf9188d66f0080dfefd070f77b) - Refactor `LIKE ANY` and support  `ALL | SOME` quantifiers *(PR [#5493](https://github.com/tobymao/sqlglot/pull/5493) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Refactor `LIKE ANY` and support  `ALL | SOME` quantifiers (#5493)\n\n\n### :sparkles: New Features\n- [`8cdd9e8`](https://github.com/tobymao/sqlglot/commit/8cdd9e8715b4cf67c200c723940743ed69bbfd80) - **mysql**: Parse UNIQUE INDEX constraint similar to UNIQUE KEY *(PR [#5489](https://github.com/tobymao/sqlglot/pull/5489) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5479](https://github.com/tobymao/sqlglot/issues/5479) opened by [@nathanchapman](https://github.com/nathanchapman)*\n- [`787d167`](https://github.com/tobymao/sqlglot/commit/787d167d694b557d6e43ed391f59847a888fa572) - **exasol**: add support for REGEXP_SUBSTR  in exasol dialect *(PR [#5487](https://github.com/tobymao/sqlglot/pull/5487) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`0963f60`](https://github.com/tobymao/sqlglot/commit/0963f60987c267c64f2fcfbde469b8b28911a14b) - **singlestore**: Fixed time formatting *(PR [#5476](https://github.com/tobymao/sqlglot/pull/5476) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`488d2e4`](https://github.com/tobymao/sqlglot/commit/488d2e4bf9d4eb148356d1fd6c2360bbf77f283c) - **singlestore**: Added RESERVED_KEYWORDS *(PR [#5497](https://github.com/tobymao/sqlglot/pull/5497) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n- [`fad9992`](https://github.com/tobymao/sqlglot/commit/fad9992a00478a964552f72802b95ca3918c4377) - **exasol**: Add support for TRUNC, TRUNCATE and DATE_TRUNC function… *(PR [#5490](https://github.com/tobymao/sqlglot/pull/5490) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`ad78db6`](https://github.com/tobymao/sqlglot/commit/ad78db6c9002a5bf9188d66f0080dfefd070f77b) - Refactor `LIKE ANY` and support  `ALL | SOME` quantifiers *(PR [#5493](https://github.com/tobymao/sqlglot/pull/5493) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5484](https://github.com/tobymao/sqlglot/issues/5484) opened by [@mazum21](https://github.com/mazum21)*\n- [`a7a6f16`](https://github.com/tobymao/sqlglot/commit/a7a6f167d30ac19383ad15931c26751c66a61976) - **singlestore**: Added Tokenizer *(PR [#5492](https://github.com/tobymao/sqlglot/pull/5492) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n\n### :bug: Bug Fixes\n- [`3982653`](https://github.com/tobymao/sqlglot/commit/3982653e62a42ca1be2bdd8722119e27bd1ba680) - Do not consume BUCKET/TRUNCATE as partitioning keywords *(PR [#5488](https://github.com/tobymao/sqlglot/pull/5488) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5485](https://github.com/tobymao/sqlglot/issues/5485) opened by [@chenkovsky](https://github.com/chenkovsky)*\n- [`002286e`](https://github.com/tobymao/sqlglot/commit/002286ee05a608e303a2238a9a74ab963709b5da) - remove AM/PM entries from postgres, oracle `TIME_MAPPING` *(PR [#5491](https://github.com/tobymao/sqlglot/pull/5491) by [@georgesittas](https://github.com/georgesittas))*\n- [`74f278a`](https://github.com/tobymao/sqlglot/commit/74f278a226058e196270042e2a9664b9acded28a) - **optimizer**: Fix SEMI/ANTI join handling in optimizer rules *(PR [#5498](https://github.com/tobymao/sqlglot/pull/5498) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5481](https://github.com/tobymao/sqlglot/issues/5481) opened by [@themattmorris](https://github.com/themattmorris)*\n- [`42633fb`](https://github.com/tobymao/sqlglot/commit/42633fb49b3c04eeea42e061e33ee08e61960cb4) - dont print (A)SYMMETRIC keyword in BETWEEN for postgres subclasses *(PR [#5503](https://github.com/tobymao/sqlglot/pull/5503) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`18b7d0f`](https://github.com/tobymao/sqlglot/commit/18b7d0fe19708d88b224770d844a8f6a74fe2aa7) - fix deprecated 'license' specification format *(PR [#5494](https://github.com/tobymao/sqlglot/pull/5494) by [@loonies](https://github.com/loonies))*\n\n\n## [v27.4.1] - 2025-07-27\n### :bug: Bug Fixes\n- [`ba2b3e2`](https://github.com/tobymao/sqlglot/commit/ba2b3e21ca5454402808b68697ea4eb62963d341) - **bigquery**: make exp.Array type inference more robust *(PR [#5483](https://github.com/tobymao/sqlglot/pull/5483) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.4.0] - 2025-07-25\n### :boom: BREAKING CHANGES\n- due to [`4f348bd`](https://github.com/tobymao/sqlglot/commit/4f348bddda21b18841fd2d728fe486e95cdaa549) - store Query schemas in meta dict instead of type attr *(PR [#5480](https://github.com/tobymao/sqlglot/pull/5480) by [@georgesittas](https://github.com/georgesittas))*:\n\n  store Query schemas in meta dict instead of type attr (#5480)\n\n\n### :sparkles: New Features\n- [`7961ece`](https://github.com/tobymao/sqlglot/commit/7961ece058f3771364aad5beedba9484e3a2e27c) - **exasol**: Add support for HASH_SHA1 function *(PR [#5468](https://github.com/tobymao/sqlglot/pull/5468) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`406815d`](https://github.com/tobymao/sqlglot/commit/406815de21f0fdc9874ff46155d4ee0274aa6337) - **exasol**: support HASH_SHA1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`e6f4fc9`](https://github.com/tobymao/sqlglot/commit/e6f4fc9c6d59d96777b2a2ec5dcc360e53639f8d) - **sqlite**: support ATTACH/DETACH DATABASE *(PR [#5469](https://github.com/tobymao/sqlglot/pull/5469) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5459](https://github.com/tobymao/sqlglot/issues/5459) opened by [@mariofox](https://github.com/mariofox)*\n- [`8aa3498`](https://github.com/tobymao/sqlglot/commit/8aa349890673dccdd4daa0aea6ca5fcb9fdaf46f) - **hive, spark**: Add support for LOCATION in ADD PARTITION *(PR [#5472](https://github.com/tobymao/sqlglot/pull/5472) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5457](https://github.com/tobymao/sqlglot/issues/5457) opened by [@tsamaras](https://github.com/tsamaras)*\n- [`44adfc0`](https://github.com/tobymao/sqlglot/commit/44adfc0e74da9d1b05a5a8a67b81fb7c67634c70) - **exasol**: add HASH_MD5 functionality to exasol dialect *(PR [#5473](https://github.com/tobymao/sqlglot/pull/5473) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`05e1c4d`](https://github.com/tobymao/sqlglot/commit/05e1c4dbf795915448173a894a89a33b289a3b5b) - **snowflake**: Transpile BQ's `STRUCT` dot access *(PR [#5471](https://github.com/tobymao/sqlglot/pull/5471) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`3c5ecdf`](https://github.com/tobymao/sqlglot/commit/3c5ecdf7f27629c01f0f3402e64a9dedf0583851) - **exasol**: Add HASHTYPE_MD5 functions to Exasol dialect *(PR [#5474](https://github.com/tobymao/sqlglot/pull/5474) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`1d640d2`](https://github.com/tobymao/sqlglot/commit/1d640d2278288b9a39a65b2532a13bc17e06c4e8) - **exasol**: add support for HASH_SHA256 and HASH_SHA512 hashing *(PR [#5475](https://github.com/tobymao/sqlglot/pull/5475) by [@nnamdi16](https://github.com/nnamdi16))*\n\n### :bug: Bug Fixes\n- [`e1819d6`](https://github.com/tobymao/sqlglot/commit/e1819d6451fec0eb3a1f77c90fd8d5c5b0d89889) - only strip kind from joins when it is inner|outer *(PR [#5477](https://github.com/tobymao/sqlglot/pull/5477) by [@themattmorris](https://github.com/themattmorris))*\n  - :arrow_lower_right: *fixes issue [#5470](https://github.com/tobymao/sqlglot/issues/5470) opened by [@themattmorris](https://github.com/themattmorris)*\n- [`4f348bd`](https://github.com/tobymao/sqlglot/commit/4f348bddda21b18841fd2d728fe486e95cdaa549) - **bigquery**: store Query schemas in meta dict instead of type attr *(PR [#5480](https://github.com/tobymao/sqlglot/pull/5480) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.3.1] - 2025-07-24\n### :boom: BREAKING CHANGES\n- due to [`48703c4`](https://github.com/tobymao/sqlglot/commit/48703c4fadd9f24de151a63d1bfa74f4b8e71133) - temporarily move VARCHAR length inference logic to Fabric *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  temporarily move VARCHAR length inference logic to Fabric\n\n\n### :sparkles: New Features\n- [`4cc321c`](https://github.com/tobymao/sqlglot/commit/4cc321cc1995d538ab0c48a7a0a473c31e76ddff) - **singlestore**: Added initial implementation of SingleStore dialect *(PR [#5447](https://github.com/tobymao/sqlglot/pull/5447) by [@AdalbertMemSQL](https://github.com/AdalbertMemSQL))*\n\n### :wrench: Chores\n- [`48703c4`](https://github.com/tobymao/sqlglot/commit/48703c4fadd9f24de151a63d1bfa74f4b8e71133) - **tsql**: temporarily move VARCHAR length inference logic to Fabric *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.3.0] - 2025-07-24\n### :boom: BREAKING CHANGES\n- due to [`d7ccb48`](https://github.com/tobymao/sqlglot/commit/d7ccb48e542c49258e31cc4df45f49beebc2e238) - week/quarter support *(PR [#5374](https://github.com/tobymao/sqlglot/pull/5374) by [@eakmanrq](https://github.com/eakmanrq))*:\n\n  week/quarter support (#5374)\n\n- due to [`b368fba`](https://github.com/tobymao/sqlglot/commit/b368fba59b606e038d445b2ca2d8436e115af3d6) - parse and annotate type for ASCII *(PR [#5377](https://github.com/tobymao/sqlglot/pull/5377) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for ASCII (#5377)\n\n- due to [`7f19b31`](https://github.com/tobymao/sqlglot/commit/7f19b31ebd7981e53a8f8ba343b4f3222fe160c7) - annotate type for UNICODE *(PR [#5381](https://github.com/tobymao/sqlglot/pull/5381) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for UNICODE (#5381)\n\n- due to [`9e8d3ab`](https://github.com/tobymao/sqlglot/commit/9e8d3abedcffb1c267ed0e6a8332af3b52105d41) - Preserve struct-column parentheses for RisingWave dialect *(PR [#5376](https://github.com/tobymao/sqlglot/pull/5376) by [@MisterWheatley](https://github.com/MisterWheatley))*:\n\n  Added dialect as argument to `simplify_parens` function  \n  * style: Ran formatter and tests. Fixed type annotation for simplify_parens  \n  * Fix: Make dialect in `simplify_parens` optional.  \n  Co-authored-by: Jo <46752250+georgesittas@users.noreply.github.com>  \n  * Fix(optimizer): Tweaks to make simple non-nested star expand pass unit test for RW  \n  * Fix(optimizer): Added test for deep nested unpacking for BigQuery and RisingWave  \n  * style: Ran formatting check  \n  * fix: Remove unuses function from RisingWave dialect test  \n  * docs: updated docstring of new _expand_struct_stars_risingwave internal function  \n  * fix: apply suggestions from code review 2  \n  Co-authored-by: Jo <46752250+georgesittas@users.noreply.github.com>  \n  * fix(optimizer,risingwave): Ensure that struct star-expansion to the correct level for RisingWave  \n  Updated logic for expanding (struct_col).* expressions in RisingWave to correctly handle the level of nesting.  \n  Moved struct expansion tests to tests/fixtures/qualify_columns.sql on behest of maintainers.  \n  ---------\n\n- due to [`3223e63`](https://github.com/tobymao/sqlglot/commit/3223e6394fdd3f8e48c68bbb940b661ff8e76fd8) - cast datetimeoffset to datetime2 *(PR [#5385](https://github.com/tobymao/sqlglot/pull/5385) by [@mattiasthalen](https://github.com/mattiasthalen))*:\n\n  cast datetimeoffset to datetime2 (#5385)\n\n- due to [`06cea31`](https://github.com/tobymao/sqlglot/commit/06cea310bd9fd3a9a9fa0ba008596e878a430df8) - support KEY related locks *(PR [#5397](https://github.com/tobymao/sqlglot/pull/5397) by [@geooo109](https://github.com/geooo109))*:\n\n  support KEY related locks (#5397)\n\n- due to [`1014a67`](https://github.com/tobymao/sqlglot/commit/1014a6759b0917ef1bf5af0dbbdcca72214a8dea) - remove redundant todate in dayofweek closes [#5398](https://github.com/tobymao/sqlglot/pull/5398) *(PR [#5399](https://github.com/tobymao/sqlglot/pull/5399) by [@tobymao](https://github.com/tobymao))*:\n\n  remove redundant todate in dayofweek closes #5398 (#5399)\n\n- due to [`b2631ae`](https://github.com/tobymao/sqlglot/commit/b2631aec8d1bdb08decb201b6bd2ba5d927bb121) - annotate type for bigquery BIT_AND, BIT_OR, BIT_XOR, BIT_COUNT *(PR [#5405](https://github.com/tobymao/sqlglot/pull/5405) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery BIT_AND, BIT_OR, BIT_XOR, BIT_COUNT (#5405)\n\n- due to [`5835b8d`](https://github.com/tobymao/sqlglot/commit/5835b8d6c7fe77d9645691bb88021af137ed0bac) - make bracket parsing aware of duckdb MAP func *(PR [#5423](https://github.com/tobymao/sqlglot/pull/5423) by [@geooo109](https://github.com/geooo109))*:\n\n  make bracket parsing aware of duckdb MAP func (#5423)\n\n- due to [`489dc5c`](https://github.com/tobymao/sqlglot/commit/489dc5c2f7506e0fe4de549384dd0f816e9fd12f) - parse and annotate type support for JSON_ARRAY *(PR [#5424](https://github.com/tobymao/sqlglot/pull/5424) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type support for JSON_ARRAY (#5424)\n\n- due to [`0ed518c`](https://github.com/tobymao/sqlglot/commit/0ed518c67042002ee0af91bee0b9e7093c85f926) - annotate type for bigquery JSON_VALUE *(PR [#5427](https://github.com/tobymao/sqlglot/pull/5427) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery JSON_VALUE (#5427)\n\n- due to [`6091617`](https://github.com/tobymao/sqlglot/commit/6091617067c263e3e834e579b37aa1c601b1ddc7) - annotate type for bigquery JSON_VALUE_ARRAY *(PR [#5428](https://github.com/tobymao/sqlglot/pull/5428) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery JSON_VALUE_ARRAY (#5428)\n\n- due to [`631c851`](https://github.com/tobymao/sqlglot/commit/631c851cbbfbf55cb66a79c2549aeeb443fcab83) - parse and annotate type support for bigquery JSON_TYPE *(PR [#5430](https://github.com/tobymao/sqlglot/pull/5430) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type support for bigquery JSON_TYPE (#5430)\n\n- due to [`6268f6f`](https://github.com/tobymao/sqlglot/commit/6268f6f39dda1ca5cf2ad0403e219b49c5c9593a) - add default precision to CHAR/VARCHAR create expressions *(PR [#5434](https://github.com/tobymao/sqlglot/pull/5434) by [@mattiasthalen](https://github.com/mattiasthalen))*:\n\n  add default precision to CHAR/VARCHAR create expressions (#5434)\n\n- due to [`8467bad`](https://github.com/tobymao/sqlglot/commit/8467bad405e27c842c989e71588adc39cf2383fc) - add parsing/generating for BigQuery `DECLARE` *(PR [#5442](https://github.com/tobymao/sqlglot/pull/5442) by [@plaflamme](https://github.com/plaflamme))*:\n\n  add parsing/generating for BigQuery `DECLARE` (#5442)\n\n- due to [`79c5c30`](https://github.com/tobymao/sqlglot/commit/79c5c30f3802c6959376b3b0f3c4d055a30b6b43) - transpile STRING_AGG *(PR [#5449](https://github.com/tobymao/sqlglot/pull/5449) by [@geooo109](https://github.com/geooo109))*:\n\n  transpile STRING_AGG (#5449)\n\n- due to [`190f8ab`](https://github.com/tobymao/sqlglot/commit/190f8abe3d3bbda09e2f945287398d2aa9d6a863) - improve BigQuery `UNNEST` transpilation *(PR [#5451](https://github.com/tobymao/sqlglot/pull/5451) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve BigQuery `UNNEST` transpilation (#5451)\n\n- due to [`3590e75`](https://github.com/tobymao/sqlglot/commit/3590e75c1df2d572e2fea664893dba5565a17e05) - support ? placeholder *(PR [#5455](https://github.com/tobymao/sqlglot/pull/5455) by [@geooo109](https://github.com/geooo109))*:\n\n  support ? placeholder (#5455)\n\n- due to [`cdbf595`](https://github.com/tobymao/sqlglot/commit/cdbf5953171c8d4c8e4a24262f278c6f7d74e057) - Wrap GET_PATH value with PARSE_JSON preemptively *(PR [#5458](https://github.com/tobymao/sqlglot/pull/5458) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Wrap GET_PATH value with PARSE_JSON preemptively (#5458)\n\n- due to [`bee82f3`](https://github.com/tobymao/sqlglot/commit/bee82f37ac537780495ff408738d88871208517a) - Remove `UNKNOWN` type from `TRY_CAST` *(PR [#5466](https://github.com/tobymao/sqlglot/pull/5466) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Remove `UNKNOWN` type from `TRY_CAST` (#5466)\n\n\n### :sparkles: New Features\n- [`b368fba`](https://github.com/tobymao/sqlglot/commit/b368fba59b606e038d445b2ca2d8436e115af3d6) - **optimizer**: parse and annotate type for ASCII *(PR [#5377](https://github.com/tobymao/sqlglot/pull/5377) by [@geooo109](https://github.com/geooo109))*\n- [`7f19b31`](https://github.com/tobymao/sqlglot/commit/7f19b31ebd7981e53a8f8ba343b4f3222fe160c7) - **optimizer**: annotate type for UNICODE *(PR [#5381](https://github.com/tobymao/sqlglot/pull/5381) by [@geooo109](https://github.com/geooo109))*\n- [`f035bf0`](https://github.com/tobymao/sqlglot/commit/f035bf0eb582aa07d4ad79e0ed1958ce0d091ad9) - **dremio**: Add TIME_MAPPING for Dremio dialect *(PR [#5378](https://github.com/tobymao/sqlglot/pull/5378) by [@mateuszpoleski](https://github.com/mateuszpoleski))*\n- [`31cfd0f`](https://github.com/tobymao/sqlglot/commit/31cfd0fc3309bc1080b7a2ba8d40b2aba5c098a3) - **exasol**: add to_date and refactored to_char functions with respect to time mapping *(PR [#5379](https://github.com/tobymao/sqlglot/pull/5379) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`bd3776e`](https://github.com/tobymao/sqlglot/commit/bd3776eaa26d40b44c4cebc2f3838b4055653548) - **doris**: add PROPERTIES_LOCATION mapping for Doris dialect *(PR [#5391](https://github.com/tobymao/sqlglot/pull/5391) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`7eaa67a`](https://github.com/tobymao/sqlglot/commit/7eaa67acb216501046c739f56839418b84f244c0) - **doris**: properly supported PROPERTIES and UNIQUE KEY table prop *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1e78163`](https://github.com/tobymao/sqlglot/commit/1e78163b829e910e7960c79e7ab118c07d1ecdc3) - **duckdb**: support column access via index *(PR [#5395](https://github.com/tobymao/sqlglot/pull/5395) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5392](https://github.com/tobymao/sqlglot/issues/5392) opened by [@tekumara](https://github.com/tekumara)*\n- [`1014a67`](https://github.com/tobymao/sqlglot/commit/1014a6759b0917ef1bf5af0dbbdcca72214a8dea) - remove redundant todate in dayofweek closes [#5398](https://github.com/tobymao/sqlglot/pull/5398) *(PR [#5399](https://github.com/tobymao/sqlglot/pull/5399) by [@tobymao](https://github.com/tobymao))*\n- [`be52f78`](https://github.com/tobymao/sqlglot/commit/be52f7866b03e436d103d9201d1a44c6632c643a) - **exasol**: add support for CONVERT_TZ function *(PR [#5401](https://github.com/tobymao/sqlglot/pull/5401) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`d637161`](https://github.com/tobymao/sqlglot/commit/d637161406faf623418f112162268bedb422213b) - **exasol**: add mapping to TIME_TO_STR in exasol dialect *(PR [#5403](https://github.com/tobymao/sqlglot/pull/5403) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`b2631ae`](https://github.com/tobymao/sqlglot/commit/b2631aec8d1bdb08decb201b6bd2ba5d927bb121) - **optimizer**: annotate type for bigquery BIT_AND, BIT_OR, BIT_XOR, BIT_COUNT *(PR [#5405](https://github.com/tobymao/sqlglot/pull/5405) by [@geooo109](https://github.com/geooo109))*\n- [`b81ae62`](https://github.com/tobymao/sqlglot/commit/b81ae629bfb27760ddd832402a86dabe4e65072f) - **exasol**: map STR_TO_TIME to TO_DATE and *(PR [#5407](https://github.com/tobymao/sqlglot/pull/5407) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c2fb9ab`](https://github.com/tobymao/sqlglot/commit/c2fb9abeb2f077f00278e46efd9573a3806cd218) - add `DateStrToTime` *(PR [#5409](https://github.com/tobymao/sqlglot/pull/5409) by [@betodealmeida](https://github.com/betodealmeida))*\n- [`a95993a`](https://github.com/tobymao/sqlglot/commit/a95993ae4e8aa99969db059a534819a4f0b62b96) - **snowflake**: improve transpilation of queries with UNNEST sources *(PR [#5408](https://github.com/tobymao/sqlglot/pull/5408) by [@georgesittas](https://github.com/georgesittas))*\n- [`7b69f54`](https://github.com/tobymao/sqlglot/commit/7b69f545bbcfeb1e1f2f3b7e0b9757cfd675e4a5) - **snowflake**: Support SEMANTIC_VIEW *(PR [#5414](https://github.com/tobymao/sqlglot/pull/5414) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5406](https://github.com/tobymao/sqlglot/issues/5406) opened by [@jkillian](https://github.com/jkillian)*\n- [`7dba6f6`](https://github.com/tobymao/sqlglot/commit/7dba6f64d9a7945bbdef1b6e014d802014567a1e) - **exasol**: map AT TIME ZONE to CONVERT_TZ *(PR [#5416](https://github.com/tobymao/sqlglot/pull/5416) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`25f2c1b`](https://github.com/tobymao/sqlglot/commit/25f2c1bb18f9d073b128150566cb27c0c2da0865) - **postgres**: query placeholders *(PR [#5415](https://github.com/tobymao/sqlglot/pull/5415) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5412](https://github.com/tobymao/sqlglot/issues/5412) opened by [@aersam](https://github.com/aersam)*\n- [`c309c87`](https://github.com/tobymao/sqlglot/commit/c309c8763a90bf0bce02e21f4088b38d85556cce) - **doris**: support range partitioning *(PR [#5402](https://github.com/tobymao/sqlglot/pull/5402) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`394d3a8`](https://github.com/tobymao/sqlglot/commit/394d3a81ef41d3052c0b0d6e48180c344b7db143) - **dremio**: Add support for DATE_ADD and DATE_SUB *(PR [#5411](https://github.com/tobymao/sqlglot/pull/5411) by [@mateuszpoleski](https://github.com/mateuszpoleski))*\n- [`9cfac4f`](https://github.com/tobymao/sqlglot/commit/9cfac4fb04ce1fd038c3e8cbdb755cc24c052497) - **doris**: enhance partitioning support *(PR [#5421](https://github.com/tobymao/sqlglot/pull/5421) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`a018bea`](https://github.com/tobymao/sqlglot/commit/a018bea159261a3ad4ac082f29e30fe1153995b3) - **exasol**: mapped exp.CurrentUser to exasol CURRENT_USER *(PR [#5422](https://github.com/tobymao/sqlglot/pull/5422) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`489dc5c`](https://github.com/tobymao/sqlglot/commit/489dc5c2f7506e0fe4de549384dd0f816e9fd12f) - **optimizer**: parse and annotate type support for JSON_ARRAY *(PR [#5424](https://github.com/tobymao/sqlglot/pull/5424) by [@geooo109](https://github.com/geooo109))*\n- [`0ed518c`](https://github.com/tobymao/sqlglot/commit/0ed518c67042002ee0af91bee0b9e7093c85f926) - **optimizer**: annotate type for bigquery JSON_VALUE *(PR [#5427](https://github.com/tobymao/sqlglot/pull/5427) by [@geooo109](https://github.com/geooo109))*\n- [`6091617`](https://github.com/tobymao/sqlglot/commit/6091617067c263e3e834e579b37aa1c601b1ddc7) - **optimizer**: annotate type for bigquery JSON_VALUE_ARRAY *(PR [#5428](https://github.com/tobymao/sqlglot/pull/5428) by [@geooo109](https://github.com/geooo109))*\n- [`631c851`](https://github.com/tobymao/sqlglot/commit/631c851cbbfbf55cb66a79c2549aeeb443fcab83) - **optimizer**: parse and annotate type support for bigquery JSON_TYPE *(PR [#5430](https://github.com/tobymao/sqlglot/pull/5430) by [@geooo109](https://github.com/geooo109))*\n- [`732548f`](https://github.com/tobymao/sqlglot/commit/732548ff7a6792cfa38dba8b3b8a73a302532ae7) - **postgresql**: add support for table creation DDL that contains a primary key alongside the INCLUDE keyword *(PR [#5425](https://github.com/tobymao/sqlglot/pull/5425) by [@amosbiras](https://github.com/amosbiras))*\n- [`9f887f1`](https://github.com/tobymao/sqlglot/commit/9f887f14d20cd493b4a0a4489649fc5b9f2ae7fd) - Add support for BETWEEN flags *(PR [#5435](https://github.com/tobymao/sqlglot/pull/5435) by [@mateuszpoleski](https://github.com/mateuszpoleski))*\n- [`edef00a`](https://github.com/tobymao/sqlglot/commit/edef00af9b703ace76871b989d9b94d9c30dcafd) - **duckdb**: Add reset command for duckdb *(PR [#5448](https://github.com/tobymao/sqlglot/pull/5448) by [@themisvaltinos](https://github.com/themisvaltinos))*\n- [`6268f6f`](https://github.com/tobymao/sqlglot/commit/6268f6f39dda1ca5cf2ad0403e219b49c5c9593a) - **tsql**: add default precision to CHAR/VARCHAR create expressions *(PR [#5434](https://github.com/tobymao/sqlglot/pull/5434) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`bbf26e9`](https://github.com/tobymao/sqlglot/commit/bbf26e9610bee341d4e6df12a031b05ff6b57861) - **mysql**: Add support for SELECT DISTINCTROW *(PR [#5446](https://github.com/tobymao/sqlglot/pull/5446) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5445](https://github.com/tobymao/sqlglot/issues/5445) opened by [@chenweida123](https://github.com/chenweida123)*\n- [`8467bad`](https://github.com/tobymao/sqlglot/commit/8467bad405e27c842c989e71588adc39cf2383fc) - add parsing/generating for BigQuery `DECLARE` *(PR [#5442](https://github.com/tobymao/sqlglot/pull/5442) by [@plaflamme](https://github.com/plaflamme))*\n- [`190f8ab`](https://github.com/tobymao/sqlglot/commit/190f8abe3d3bbda09e2f945287398d2aa9d6a863) - improve BigQuery `UNNEST` transpilation *(PR [#5451](https://github.com/tobymao/sqlglot/pull/5451) by [@georgesittas](https://github.com/georgesittas))*\n- [`dbef44d`](https://github.com/tobymao/sqlglot/commit/dbef44db64d8c80e5000c55c981e0de89054e6eb) - **exasol**: mapped STRPOS to INSTR in exasol dialect *(PR [#5454](https://github.com/tobymao/sqlglot/pull/5454) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`010c34c`](https://github.com/tobymao/sqlglot/commit/010c34c1803df0223cf65263f2fb03b404e5141c) - support `DESC SEMANTIC VIEW` *(PR [#5452](https://github.com/tobymao/sqlglot/pull/5452) by [@betodealmeida](https://github.com/betodealmeida))*\n- [`9795021`](https://github.com/tobymao/sqlglot/commit/9795021ff35bae17ff5a9ba7c5cdb46a75aab63b) - **exasol**: transformed column comments *(PR [#5464](https://github.com/tobymao/sqlglot/pull/5464) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`4c5b687`](https://github.com/tobymao/sqlglot/commit/4c5b68746dcede62ca9d1217bd428f50a1731e2c) - **snowflake**: transpile IS <boolean> (IS can only be used with NULL) *(PR [#5467](https://github.com/tobymao/sqlglot/pull/5467) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`d7ccb48`](https://github.com/tobymao/sqlglot/commit/d7ccb48e542c49258e31cc4df45f49beebc2e238) - **duckdb**: week/quarter support *(PR [#5374](https://github.com/tobymao/sqlglot/pull/5374) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`252469d`](https://github.com/tobymao/sqlglot/commit/252469d2d0ed221dbb2fde86043506ad15dbe7e5) - **snowflake**: transpile bigquery CURRENT_DATE with timezone *(PR [#5387](https://github.com/tobymao/sqlglot/pull/5387) by [@geooo109](https://github.com/geooo109))*\n- [`7511853`](https://github.com/tobymao/sqlglot/commit/751185325caf838107ecb4e8f35ad77bf3cc9bf2) - **postgres**: add XML type *(PR [#5396](https://github.com/tobymao/sqlglot/pull/5396) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5393](https://github.com/tobymao/sqlglot/issues/5393) opened by [@aersam](https://github.com/aersam)*\n- [`9e8d3ab`](https://github.com/tobymao/sqlglot/commit/9e8d3abedcffb1c267ed0e6a8332af3b52105d41) - **optimizer**: Preserve struct-column parentheses for RisingWave dialect *(PR [#5376](https://github.com/tobymao/sqlglot/pull/5376) by [@MisterWheatley](https://github.com/MisterWheatley))*\n- [`3223e63`](https://github.com/tobymao/sqlglot/commit/3223e6394fdd3f8e48c68bbb940b661ff8e76fd8) - **fabric**: cast datetimeoffset to datetime2 *(PR [#5385](https://github.com/tobymao/sqlglot/pull/5385) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`12b49dd`](https://github.com/tobymao/sqlglot/commit/12b49dd800951a48ea8bc0f01d7c35340236f559) - remove equal sign from CREATE TABLE comment (doris, starrocks) *(PR [#5390](https://github.com/tobymao/sqlglot/pull/5390) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`06cea31`](https://github.com/tobymao/sqlglot/commit/06cea310bd9fd3a9a9fa0ba008596e878a430df8) - **postgres**: support KEY related locks *(PR [#5397](https://github.com/tobymao/sqlglot/pull/5397) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5394](https://github.com/tobymao/sqlglot/issues/5394) opened by [@aurimasandriusaitis](https://github.com/aurimasandriusaitis)*\n- [`92d93a6`](https://github.com/tobymao/sqlglot/commit/92d93a624b41df8bb4628c1f2d0cbb8c7844c927) - **parser**: do not consume modifier prefixes in group parser, fixes [#5400](https://github.com/tobymao/sqlglot/pull/5400) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ba0c801`](https://github.com/tobymao/sqlglot/commit/ba0c801e3dab8e08d4b5f7f73247ec6cfdc667e5) - **tsql**: change READ_ONLY to READONLY *(PR [#5410](https://github.com/tobymao/sqlglot/pull/5410) by [@CrispinStichartFNSB](https://github.com/CrispinStichartFNSB))*\n- [`63da895`](https://github.com/tobymao/sqlglot/commit/63da89563fddc13ee7aec06ee36d8a0f74227ee1) - **risingwave**: Fix RisingWave dialect SQL for MAP datatype declaration *(PR [#5418](https://github.com/tobymao/sqlglot/pull/5418) by [@MisterWheatley](https://github.com/MisterWheatley))*\n- [`edacae1`](https://github.com/tobymao/sqlglot/commit/edacae183fe26ea25bffe1bccd335bf57ed34ecb) - **snowflake**: transpile bigquery GENERATE_DATE_ARRAY with column access *(PR [#5388](https://github.com/tobymao/sqlglot/pull/5388) by [@geooo109](https://github.com/geooo109))*\n- [`5835b8d`](https://github.com/tobymao/sqlglot/commit/5835b8d6c7fe77d9645691bb88021af137ed0bac) - **duckdb**: make bracket parsing aware of duckdb MAP func *(PR [#5423](https://github.com/tobymao/sqlglot/pull/5423) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5417](https://github.com/tobymao/sqlglot/issues/5417) opened by [@MisterWheatley](https://github.com/MisterWheatley)*\n- [`5c59816`](https://github.com/tobymao/sqlglot/commit/5c59816f5572f8adb1de9c97f0007d19091910ec) - **snowflake**: ALTER TABLE ADD with multiple columns *(PR [#5431](https://github.com/tobymao/sqlglot/pull/5431) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5426](https://github.com/tobymao/sqlglot/issues/5426) opened by [@ca0904](https://github.com/ca0904)*\n- [`9f860a0`](https://github.com/tobymao/sqlglot/commit/9f860a0ce47f74930efa1afcd86fe7668a40c239) - **snowflake**: ALTER TABLE ADD with IF NOT EXISTS *(PR [#5438](https://github.com/tobymao/sqlglot/pull/5438) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5432](https://github.com/tobymao/sqlglot/issues/5432) opened by [@ca0904](https://github.com/ca0904)*\n- [`d7b3a26`](https://github.com/tobymao/sqlglot/commit/d7b3a261647e4ce675c84bbf72a33d320099fc01) - **postgres**: transpile duckdb LIST_HAS_ANY and LIST_CONTAINS *(PR [#5440](https://github.com/tobymao/sqlglot/pull/5440) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5437](https://github.com/tobymao/sqlglot/issues/5437) opened by [@aersam](https://github.com/aersam)*\n- [`79c5c30`](https://github.com/tobymao/sqlglot/commit/79c5c30f3802c6959376b3b0f3c4d055a30b6b43) - **spark**: transpile STRING_AGG *(PR [#5449](https://github.com/tobymao/sqlglot/pull/5449) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5441](https://github.com/tobymao/sqlglot/issues/5441) opened by [@dxaen](https://github.com/dxaen)*\n- [`3590e75`](https://github.com/tobymao/sqlglot/commit/3590e75c1df2d572e2fea664893dba5565a17e05) - **postgres**: support ? placeholder *(PR [#5455](https://github.com/tobymao/sqlglot/pull/5455) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5453](https://github.com/tobymao/sqlglot/issues/5453) opened by [@jkillian](https://github.com/jkillian)*\n- [`cdbf595`](https://github.com/tobymao/sqlglot/commit/cdbf5953171c8d4c8e4a24262f278c6f7d74e057) - **snowflake**: Wrap GET_PATH value with PARSE_JSON preemptively *(PR [#5458](https://github.com/tobymao/sqlglot/pull/5458) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`8f16f52`](https://github.com/tobymao/sqlglot/commit/8f16f52859b66e3f8b30fff82f0c1679c7e37a25) - restore default `sql_names` for `DecodeCase` *(PR [#5465](https://github.com/tobymao/sqlglot/pull/5465) by [@georgesittas](https://github.com/georgesittas))*\n- [`bee82f3`](https://github.com/tobymao/sqlglot/commit/bee82f37ac537780495ff408738d88871208517a) - **snowflake**: Remove `UNKNOWN` type from `TRY_CAST` *(PR [#5466](https://github.com/tobymao/sqlglot/pull/5466) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :wrench: Chores\n- [`71b1349`](https://github.com/tobymao/sqlglot/commit/71b1349a26d2b9839899900ef8fdfb1ebc3d68fd) - **postgres, hive**: use ASCII node instead of UNICODE node *(PR [#5380](https://github.com/tobymao/sqlglot/pull/5380) by [@geooo109](https://github.com/geooo109))*\n- [`a5c2245`](https://github.com/tobymao/sqlglot/commit/a5c2245c3e30f5bc3f410edacf3a077ce99f4a80) - improve error msg for PIVOT with missing aggregation *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`59fd875`](https://github.com/tobymao/sqlglot/commit/59fd875cd4ee1c44f9ca20f701215ae64d669d60) - Refactor PRIMARY KEY ... INCLUDE handling *(PR [#5433](https://github.com/tobymao/sqlglot/pull/5433) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`e9bb3e8`](https://github.com/tobymao/sqlglot/commit/e9bb3e8ccb52c76ed77fc5e7d04cf75230b737fa) - Refactor DECLARE statement *(PR [#5450](https://github.com/tobymao/sqlglot/pull/5450) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v27.2.0] - 2025-07-22\n### :boom: BREAKING CHANGES\n- due to [`6268f6f`](https://github.com/tobymao/sqlglot/commit/6268f6f39dda1ca5cf2ad0403e219b49c5c9593a) - add default precision to CHAR/VARCHAR create expressions *(PR [#5434](https://github.com/tobymao/sqlglot/pull/5434) by [@mattiasthalen](https://github.com/mattiasthalen))*:\n\n  add default precision to CHAR/VARCHAR create expressions (#5434)\n\n- due to [`8467bad`](https://github.com/tobymao/sqlglot/commit/8467bad405e27c842c989e71588adc39cf2383fc) - add parsing/generating for BigQuery `DECLARE` *(PR [#5442](https://github.com/tobymao/sqlglot/pull/5442) by [@plaflamme](https://github.com/plaflamme))*:\n\n  add parsing/generating for BigQuery `DECLARE` (#5442)\n\n- due to [`79c5c30`](https://github.com/tobymao/sqlglot/commit/79c5c30f3802c6959376b3b0f3c4d055a30b6b43) - transpile STRING_AGG *(PR [#5449](https://github.com/tobymao/sqlglot/pull/5449) by [@geooo109](https://github.com/geooo109))*:\n\n  transpile STRING_AGG (#5449)\n\n- due to [`190f8ab`](https://github.com/tobymao/sqlglot/commit/190f8abe3d3bbda09e2f945287398d2aa9d6a863) - improve BigQuery `UNNEST` transpilation *(PR [#5451](https://github.com/tobymao/sqlglot/pull/5451) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve BigQuery `UNNEST` transpilation (#5451)\n\n\n### :sparkles: New Features\n- [`732548f`](https://github.com/tobymao/sqlglot/commit/732548ff7a6792cfa38dba8b3b8a73a302532ae7) - **postgresql**: add support for table creation DDL that contains a primary key alongside the INCLUDE keyword *(PR [#5425](https://github.com/tobymao/sqlglot/pull/5425) by [@amosbiras](https://github.com/amosbiras))*\n- [`9f887f1`](https://github.com/tobymao/sqlglot/commit/9f887f14d20cd493b4a0a4489649fc5b9f2ae7fd) - Add support for BETWEEN flags *(PR [#5435](https://github.com/tobymao/sqlglot/pull/5435) by [@mateuszpoleski](https://github.com/mateuszpoleski))*\n- [`edef00a`](https://github.com/tobymao/sqlglot/commit/edef00af9b703ace76871b989d9b94d9c30dcafd) - **duckdb**: Add reset command for duckdb *(PR [#5448](https://github.com/tobymao/sqlglot/pull/5448) by [@themisvaltinos](https://github.com/themisvaltinos))*\n- [`6268f6f`](https://github.com/tobymao/sqlglot/commit/6268f6f39dda1ca5cf2ad0403e219b49c5c9593a) - **tsql**: add default precision to CHAR/VARCHAR create expressions *(PR [#5434](https://github.com/tobymao/sqlglot/pull/5434) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`bbf26e9`](https://github.com/tobymao/sqlglot/commit/bbf26e9610bee341d4e6df12a031b05ff6b57861) - **mysql**: Add support for SELECT DISTINCTROW *(PR [#5446](https://github.com/tobymao/sqlglot/pull/5446) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5445](https://github.com/tobymao/sqlglot/issues/5445) opened by [@chenweida123](https://github.com/chenweida123)*\n- [`8467bad`](https://github.com/tobymao/sqlglot/commit/8467bad405e27c842c989e71588adc39cf2383fc) - add parsing/generating for BigQuery `DECLARE` *(PR [#5442](https://github.com/tobymao/sqlglot/pull/5442) by [@plaflamme](https://github.com/plaflamme))*\n- [`190f8ab`](https://github.com/tobymao/sqlglot/commit/190f8abe3d3bbda09e2f945287398d2aa9d6a863) - improve BigQuery `UNNEST` transpilation *(PR [#5451](https://github.com/tobymao/sqlglot/pull/5451) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`9f860a0`](https://github.com/tobymao/sqlglot/commit/9f860a0ce47f74930efa1afcd86fe7668a40c239) - **snowflake**: ALTER TABLE ADD with IF NOT EXISTS *(PR [#5438](https://github.com/tobymao/sqlglot/pull/5438) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5432](https://github.com/tobymao/sqlglot/issues/5432) opened by [@ca0904](https://github.com/ca0904)*\n- [`d7b3a26`](https://github.com/tobymao/sqlglot/commit/d7b3a261647e4ce675c84bbf72a33d320099fc01) - **postgres**: transpile duckdb LIST_HAS_ANY and LIST_CONTAINS *(PR [#5440](https://github.com/tobymao/sqlglot/pull/5440) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5437](https://github.com/tobymao/sqlglot/issues/5437) opened by [@aersam](https://github.com/aersam)*\n- [`79c5c30`](https://github.com/tobymao/sqlglot/commit/79c5c30f3802c6959376b3b0f3c4d055a30b6b43) - **spark**: transpile STRING_AGG *(PR [#5449](https://github.com/tobymao/sqlglot/pull/5449) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5441](https://github.com/tobymao/sqlglot/issues/5441) opened by [@dxaen](https://github.com/dxaen)*\n\n### :wrench: Chores\n- [`59fd875`](https://github.com/tobymao/sqlglot/commit/59fd875cd4ee1c44f9ca20f701215ae64d669d60) - Refactor PRIMARY KEY ... INCLUDE handling *(PR [#5433](https://github.com/tobymao/sqlglot/pull/5433) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`e9bb3e8`](https://github.com/tobymao/sqlglot/commit/e9bb3e8ccb52c76ed77fc5e7d04cf75230b737fa) - Refactor DECLARE statement *(PR [#5450](https://github.com/tobymao/sqlglot/pull/5450) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v27.1.0] - 2025-07-18\n### :boom: BREAKING CHANGES\n- due to [`5724538`](https://github.com/tobymao/sqlglot/commit/5724538f278b2178114b88850251afd7c3db0dda) - ARRAY_CONCAT type annotation *(PR [#5293](https://github.com/tobymao/sqlglot/pull/5293) by [@geooo109](https://github.com/geooo109))*:\n\n  ARRAY_CONCAT type annotation (#5293)\n\n- due to [`c103b23`](https://github.com/tobymao/sqlglot/commit/c103b2304dca552ac8cf6733156db8b59d3614f3) - add support for `SUBSTRING_INDEX` *(PR [#5296](https://github.com/tobymao/sqlglot/pull/5296) by [@ankur334](https://github.com/ankur334))*:\n\n  add support for `SUBSTRING_INDEX` (#5296)\n\n- due to [`a7bd823`](https://github.com/tobymao/sqlglot/commit/a7bd8234e0dd02abfe6fa56287e7bda14a549e5a) - annotate type of ARRAY_TO_STRING *(PR [#5301](https://github.com/tobymao/sqlglot/pull/5301) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type of ARRAY_TO_STRING (#5301)\n\n- due to [`6b42353`](https://github.com/tobymao/sqlglot/commit/6b4235340a2e432015c27b2aeadbdcb930bfa6b0) - annotate type of ARRAY_FIRST, ARRAY_LAST *(PR [#5303](https://github.com/tobymao/sqlglot/pull/5303) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type of ARRAY_FIRST, ARRAY_LAST (#5303)\n\n- due to [`db9b61e`](https://github.com/tobymao/sqlglot/commit/db9b61e4ecaa0600418eb90f637fb8b06b08c399) - parse, annotate type for ARRAY_REVERSE *(PR [#5306](https://github.com/tobymao/sqlglot/pull/5306) by [@geooo109](https://github.com/geooo109))*:\n\n  parse, annotate type for ARRAY_REVERSE (#5306)\n\n- due to [`5612a6d`](https://github.com/tobymao/sqlglot/commit/5612a6da6dee3545f3600db1e5b87c9450952eba) - add support for SPACE *(PR [#5308](https://github.com/tobymao/sqlglot/pull/5308) by [@ankur334](https://github.com/ankur334))*:\n\n  add support for SPACE (#5308)\n\n- due to [`8a2f65d`](https://github.com/tobymao/sqlglot/commit/8a2f65d6b2b68ad5ba45a5aed5e56c4dc0fea6fc) - parse and annotate type for ARRAY_SLICE *(PR [#5312](https://github.com/tobymao/sqlglot/pull/5312) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for ARRAY_SLICE (#5312)\n\n- due to [`8d118ea`](https://github.com/tobymao/sqlglot/commit/8d118ead9c15e7b2b4b51b7cf93cab94e61c2625) - route statements to hive/trino depending on their type *(PR [#5314](https://github.com/tobymao/sqlglot/pull/5314) by [@georgesittas](https://github.com/georgesittas))*:\n\n  route statements to hive/trino depending on their type (#5314)\n\n- due to [`d2f7c41`](https://github.com/tobymao/sqlglot/commit/d2f7c41f9f30f4cf0c74782be9be0cc6e75565f3) - add TypeOf / toTypeName support *(PR [#5315](https://github.com/tobymao/sqlglot/pull/5315) by [@ankur334](https://github.com/ankur334))*:\n\n  add TypeOf / toTypeName support (#5315)\n\n- due to [`5a0f589`](https://github.com/tobymao/sqlglot/commit/5a0f589a0fdb6743c3be2f98b74a34780f51332b) - distinguish STORED AS from USING *(PR [#5320](https://github.com/tobymao/sqlglot/pull/5320) by [@geooo109](https://github.com/geooo109))*:\n\n  distinguish STORED AS from USING (#5320)\n\n- due to [`c4ca182`](https://github.com/tobymao/sqlglot/commit/c4ca182ad637b7a22b55d0ecf320c5a09ec5d56c) - annotate type for FROM_BASE64 *(PR [#5329](https://github.com/tobymao/sqlglot/pull/5329) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for FROM_BASE64 (#5329)\n\n- due to [`7b72bbe`](https://github.com/tobymao/sqlglot/commit/7b72bbed3a0930e11ce4a0fdd9082de715326ac9) - annotate type for ANY_VALUE *(PR [#5331](https://github.com/tobymao/sqlglot/pull/5331) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for ANY_VALUE (#5331)\n\n- due to [`c0d57e7`](https://github.com/tobymao/sqlglot/commit/c0d57e747bf5d2bed7ba2007ac2092d5797ee038) - annotate type for CHR *(PR [#5332](https://github.com/tobymao/sqlglot/pull/5332) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for CHR (#5332)\n\n- due to [`d65b5c2`](https://github.com/tobymao/sqlglot/commit/d65b5c22c29416007cca0154fd35f1d4b5efc929) - annotate type for COUNTIF *(PR [#5334](https://github.com/tobymao/sqlglot/pull/5334) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for COUNTIF (#5334)\n\n- due to [`521b705`](https://github.com/tobymao/sqlglot/commit/521b7053213df8577f609409af2552c2ff4fd8c9) - annotate type for GENERATE_ARRAY *(PR [#5335](https://github.com/tobymao/sqlglot/pull/5335) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for GENERATE_ARRAY (#5335)\n\n- due to [`5fb26c5`](https://github.com/tobymao/sqlglot/commit/5fb26c58026018360f36a732394b612a3baac38b) - annotate type for INT64 *(PR [#5339](https://github.com/tobymao/sqlglot/pull/5339) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for INT64 (#5339)\n\n- due to [`cff9b55`](https://github.com/tobymao/sqlglot/commit/cff9b55d70a3b85057e6385c93c0814eaa50f40b) - annotate type for LOGICAL_AND and LOGICAL_OR *(PR [#5340](https://github.com/tobymao/sqlglot/pull/5340) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for LOGICAL_AND and LOGICAL_OR (#5340)\n\n- due to [`b94a6f9`](https://github.com/tobymao/sqlglot/commit/b94a6f9228aa730296c3152179bfbf3503521063) - annotate type for MAKE_INTERVAL *(PR [#5341](https://github.com/tobymao/sqlglot/pull/5341) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for MAKE_INTERVAL (#5341)\n\n- due to [`2c9a7c6`](https://github.com/tobymao/sqlglot/commit/2c9a7c6f0b097a9e8514fc5e2af21c52f145920c) - annotate type for LAST_VALUE *(PR [#5336](https://github.com/tobymao/sqlglot/pull/5336) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for LAST_VALUE (#5336)\n\n- due to [`d862a28`](https://github.com/tobymao/sqlglot/commit/d862a28b0a30f0c5774351f38a61f195120ad904) - annoate type for TO_BASE64 *(PR [#5342](https://github.com/tobymao/sqlglot/pull/5342) by [@geooo109](https://github.com/geooo109))*:\n\n  annoate type for TO_BASE64 (#5342)\n\n- due to [`85888c1`](https://github.com/tobymao/sqlglot/commit/85888c1b7cbbd0eee179d902a54fbd2a899cc16b) - annotate type for UNIX_DATE *(PR [#5343](https://github.com/tobymao/sqlglot/pull/5343) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for UNIX_DATE (#5343)\n\n- due to [`8a214e0`](https://github.com/tobymao/sqlglot/commit/8a214e0859dfb715fcef0dd6b2d6392012b1f3fb) - annotate type for UNIX_SECONDS *(PR [#5344](https://github.com/tobymao/sqlglot/pull/5344) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for UNIX_SECONDS (#5344)\n\n- due to [`625cb74`](https://github.com/tobymao/sqlglot/commit/625cb74b69e99ea1a707549366ea960d759848c9) - annotate type for STARTS_WITH *(PR [#5345](https://github.com/tobymao/sqlglot/pull/5345) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for STARTS_WITH (#5345)\n\n- due to [`0337c4d`](https://github.com/tobymao/sqlglot/commit/0337c4d46e9e85d951fc9565a47e338106543711) - annotate type for SHA and SHA2 *(PR [#5346](https://github.com/tobymao/sqlglot/pull/5346) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for SHA and SHA2 (#5346)\n\n- due to [`cc389fa`](https://github.com/tobymao/sqlglot/commit/cc389facb33f94a0d1f696f2ef9e92f298711894) - annotate type SHA1, SHA256, SHA512 for BigQuery *(PR [#5347](https://github.com/tobymao/sqlglot/pull/5347) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type SHA1, SHA256, SHA512 for BigQuery (#5347)\n\n- due to [`509b741`](https://github.com/tobymao/sqlglot/commit/509b74173f678842e7550c75c4d8d906c879fb12) - preserve multi-arg DECODE function instead of converting to CASE *(PR [#5352](https://github.com/tobymao/sqlglot/pull/5352) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve multi-arg DECODE function instead of converting to CASE (#5352)\n\n- due to [`c1d3d61`](https://github.com/tobymao/sqlglot/commit/c1d3d61d00f00d2030107689d8704f7a488a80a7) - annotate type for CORR *(PR [#5364](https://github.com/tobymao/sqlglot/pull/5364) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for CORR (#5364)\n\n- due to [`c1e8677`](https://github.com/tobymao/sqlglot/commit/c1e867767a006e774a2c200c10eb85b3fbd8a372) - annotate type for COVAR_POP *(PR [#5365](https://github.com/tobymao/sqlglot/pull/5365) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for COVAR_POP (#5365)\n\n- due to [`e110ef4`](https://github.com/tobymao/sqlglot/commit/e110ef4f774e6ab8de6d4c86e5d306ab53fe895b) - annotate type for COVAR_SAMP *(PR [#5367](https://github.com/tobymao/sqlglot/pull/5367) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for COVAR_SAMP (#5367)\n\n- due to [`5b59c16`](https://github.com/tobymao/sqlglot/commit/5b59c16528fb1904c64bef0ca6307bb6a95e5a2c) - annotate type for DATETIME *(PR [#5369](https://github.com/tobymao/sqlglot/pull/5369) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for DATETIME (#5369)\n\n- due to [`47176ce`](https://github.com/tobymao/sqlglot/commit/47176ce6b9a4c1722f285034b08a6ae782129894) - annotate type for ENDS_WITH *(PR [#5370](https://github.com/tobymao/sqlglot/pull/5370) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for ENDS_WITH (#5370)\n\n- due to [`2cce53d`](https://github.com/tobymao/sqlglot/commit/2cce53d59968f0a4bb3e9599ade93b0e6a140c68) - annotate type for LAG *(PR [#5371](https://github.com/tobymao/sqlglot/pull/5371) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for LAG (#5371)\n\n- due to [`a3227de`](https://github.com/tobymao/sqlglot/commit/a3227de3fc57d559eb899dec08af01f85b470ce4) - improve transpilation of `ROUND(x, y)` to Postgres *(PR [#5368](https://github.com/tobymao/sqlglot/pull/5368) by [@blecourt-private](https://github.com/blecourt-private))*:\n\n  improve transpilation of `ROUND(x, y)` to Postgres (#5368)\n\n- due to [`d7ccb48`](https://github.com/tobymao/sqlglot/commit/d7ccb48e542c49258e31cc4df45f49beebc2e238) - week/quarter support *(PR [#5374](https://github.com/tobymao/sqlglot/pull/5374) by [@eakmanrq](https://github.com/eakmanrq))*:\n\n  week/quarter support (#5374)\n\n- due to [`b368fba`](https://github.com/tobymao/sqlglot/commit/b368fba59b606e038d445b2ca2d8436e115af3d6) - parse and annotate type for ASCII *(PR [#5377](https://github.com/tobymao/sqlglot/pull/5377) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for ASCII (#5377)\n\n- due to [`7f19b31`](https://github.com/tobymao/sqlglot/commit/7f19b31ebd7981e53a8f8ba343b4f3222fe160c7) - annotate type for UNICODE *(PR [#5381](https://github.com/tobymao/sqlglot/pull/5381) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for UNICODE (#5381)\n\n- due to [`9e8d3ab`](https://github.com/tobymao/sqlglot/commit/9e8d3abedcffb1c267ed0e6a8332af3b52105d41) - Preserve struct-column parentheses for RisingWave dialect *(PR [#5376](https://github.com/tobymao/sqlglot/pull/5376) by [@MisterWheatley](https://github.com/MisterWheatley))*:\n\n  Added dialect as argument to `simplify_parens` function  \n  * style: Ran formatter and tests. Fixed type annotation for simplify_parens  \n  * Fix: Make dialect in `simplify_parens` optional.  \n  Co-authored-by: Jo <46752250+georgesittas@users.noreply.github.com>  \n  * Fix(optimizer): Tweaks to make simple non-nested star expand pass unit test for RW  \n  * Fix(optimizer): Added test for deep nested unpacking for BigQuery and RisingWave  \n  * style: Ran formatting check  \n  * fix: Remove unuses function from RisingWave dialect test  \n  * docs: updated docstring of new _expand_struct_stars_risingwave internal function  \n  * fix: apply suggestions from code review 2  \n  Co-authored-by: Jo <46752250+georgesittas@users.noreply.github.com>  \n  * fix(optimizer,risingwave): Ensure that struct star-expansion to the correct level for RisingWave  \n  Updated logic for expanding (struct_col).* expressions in RisingWave to correctly handle the level of nesting.  \n  Moved struct expansion tests to tests/fixtures/qualify_columns.sql on behest of maintainers.  \n  ---------\n\n- due to [`3223e63`](https://github.com/tobymao/sqlglot/commit/3223e6394fdd3f8e48c68bbb940b661ff8e76fd8) - cast datetimeoffset to datetime2 *(PR [#5385](https://github.com/tobymao/sqlglot/pull/5385) by [@mattiasthalen](https://github.com/mattiasthalen))*:\n\n  cast datetimeoffset to datetime2 (#5385)\n\n- due to [`06cea31`](https://github.com/tobymao/sqlglot/commit/06cea310bd9fd3a9a9fa0ba008596e878a430df8) - support KEY related locks *(PR [#5397](https://github.com/tobymao/sqlglot/pull/5397) by [@geooo109](https://github.com/geooo109))*:\n\n  support KEY related locks (#5397)\n\n- due to [`1014a67`](https://github.com/tobymao/sqlglot/commit/1014a6759b0917ef1bf5af0dbbdcca72214a8dea) - remove redundant todate in dayofweek closes [#5398](https://github.com/tobymao/sqlglot/pull/5398) *(PR [#5399](https://github.com/tobymao/sqlglot/pull/5399) by [@tobymao](https://github.com/tobymao))*:\n\n  remove redundant todate in dayofweek closes #5398 (#5399)\n\n- due to [`b2631ae`](https://github.com/tobymao/sqlglot/commit/b2631aec8d1bdb08decb201b6bd2ba5d927bb121) - annotate type for bigquery BIT_AND, BIT_OR, BIT_XOR, BIT_COUNT *(PR [#5405](https://github.com/tobymao/sqlglot/pull/5405) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery BIT_AND, BIT_OR, BIT_XOR, BIT_COUNT (#5405)\n\n- due to [`5835b8d`](https://github.com/tobymao/sqlglot/commit/5835b8d6c7fe77d9645691bb88021af137ed0bac) - make bracket parsing aware of duckdb MAP func *(PR [#5423](https://github.com/tobymao/sqlglot/pull/5423) by [@geooo109](https://github.com/geooo109))*:\n\n  make bracket parsing aware of duckdb MAP func (#5423)\n\n- due to [`489dc5c`](https://github.com/tobymao/sqlglot/commit/489dc5c2f7506e0fe4de549384dd0f816e9fd12f) - parse and annotate type support for JSON_ARRAY *(PR [#5424](https://github.com/tobymao/sqlglot/pull/5424) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type support for JSON_ARRAY (#5424)\n\n- due to [`0ed518c`](https://github.com/tobymao/sqlglot/commit/0ed518c67042002ee0af91bee0b9e7093c85f926) - annotate type for bigquery JSON_VALUE *(PR [#5427](https://github.com/tobymao/sqlglot/pull/5427) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery JSON_VALUE (#5427)\n\n- due to [`6091617`](https://github.com/tobymao/sqlglot/commit/6091617067c263e3e834e579b37aa1c601b1ddc7) - annotate type for bigquery JSON_VALUE_ARRAY *(PR [#5428](https://github.com/tobymao/sqlglot/pull/5428) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for bigquery JSON_VALUE_ARRAY (#5428)\n\n- due to [`631c851`](https://github.com/tobymao/sqlglot/commit/631c851cbbfbf55cb66a79c2549aeeb443fcab83) - parse and annotate type support for bigquery JSON_TYPE *(PR [#5430](https://github.com/tobymao/sqlglot/pull/5430) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type support for bigquery JSON_TYPE (#5430)\n\n\n### :sparkles: New Features\n- [`ba7bf39`](https://github.com/tobymao/sqlglot/commit/ba7bf39966b519e11cde02a3c1f720598469e616) - **exasol**: implemented BIT_AND function with test *(PR [#5294](https://github.com/tobymao/sqlglot/pull/5294) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`fb4122e`](https://github.com/tobymao/sqlglot/commit/fb4122e80d1995bb87401e9ebe3749078c026a06) - **exasol**: add bitwiseOr function to exasol dialect *(PR [#5297](https://github.com/tobymao/sqlglot/pull/5297) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c103b23`](https://github.com/tobymao/sqlglot/commit/c103b2304dca552ac8cf6733156db8b59d3614f3) - add support for `SUBSTRING_INDEX` *(PR [#5296](https://github.com/tobymao/sqlglot/pull/5296) by [@ankur334](https://github.com/ankur334))*\n- [`4752f3a`](https://github.com/tobymao/sqlglot/commit/4752f3a6b715d8b6968c8f1f05f6ccdfb7351071) - **exasol**: added bit_xor built in exasol function to exasol dialect in sqlglot *(PR [#5298](https://github.com/tobymao/sqlglot/pull/5298) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`09bd610`](https://github.com/tobymao/sqlglot/commit/09bd6101de21ed86c9fd6df0f63e8bca2666dd81) - **parser**: annotate type of ARRAY_CONCAT_AGG *(PR [#5299](https://github.com/tobymao/sqlglot/pull/5299) by [@geooo109](https://github.com/geooo109))*\n- [`ad0311a`](https://github.com/tobymao/sqlglot/commit/ad0311a7f8b0b3c5746c29d816b58578a892dd33) - **exasol**: added bit_not exasol built in function. *(PR [#5300](https://github.com/tobymao/sqlglot/pull/5300) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`a7bd823`](https://github.com/tobymao/sqlglot/commit/a7bd8234e0dd02abfe6fa56287e7bda14a549e5a) - **parser**: annotate type of ARRAY_TO_STRING *(PR [#5301](https://github.com/tobymao/sqlglot/pull/5301) by [@geooo109](https://github.com/geooo109))*\n- [`2aa2182`](https://github.com/tobymao/sqlglot/commit/2aa21820f7d3a26cc4f47c1c757a9b7c97dd0382) - **exasol**: added BIT_LSHIFT built in function to exasol dialect *(PR [#5302](https://github.com/tobymao/sqlglot/pull/5302) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c3d9ef2`](https://github.com/tobymao/sqlglot/commit/c3d9ef2cb2d004b57c64af4f3f1bac41f1890737) - **exasol**: added the bit_rshift built in exasol function *(PR [#5304](https://github.com/tobymao/sqlglot/pull/5304) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`6b42353`](https://github.com/tobymao/sqlglot/commit/6b4235340a2e432015c27b2aeadbdcb930bfa6b0) - **parser**: annotate type of ARRAY_FIRST, ARRAY_LAST *(PR [#5303](https://github.com/tobymao/sqlglot/pull/5303) by [@geooo109](https://github.com/geooo109))*\n- [`f5b7cc6`](https://github.com/tobymao/sqlglot/commit/f5b7cc6d2f8d73bff4e42e242d3ad3db41d899cc) - **exasol**: added `EVERY` built in function *(PR [#5305](https://github.com/tobymao/sqlglot/pull/5305) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`d3f04d6`](https://github.com/tobymao/sqlglot/commit/d3f04d6766281ecb7ced9a5e812ab765d7b699be) - add Dremio dialect *(PR [#5277](https://github.com/tobymao/sqlglot/pull/5277) by [@mateuszpoleski](https://github.com/mateuszpoleski))*\n- [`3d8e478`](https://github.com/tobymao/sqlglot/commit/3d8e478eac3df6a94c87cd610f96c5f19697a9bf) - **exasol**: added edit_distance built in function to exasol dialect *(PR [#5310](https://github.com/tobymao/sqlglot/pull/5310) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`db9b61e`](https://github.com/tobymao/sqlglot/commit/db9b61e4ecaa0600418eb90f637fb8b06b08c399) - **parser**: parse, annotate type for ARRAY_REVERSE *(PR [#5306](https://github.com/tobymao/sqlglot/pull/5306) by [@geooo109](https://github.com/geooo109))*\n- [`5612a6d`](https://github.com/tobymao/sqlglot/commit/5612a6da6dee3545f3600db1e5b87c9450952eba) - add support for SPACE *(PR [#5308](https://github.com/tobymao/sqlglot/pull/5308) by [@ankur334](https://github.com/ankur334))*\n- [`f148c9e`](https://github.com/tobymao/sqlglot/commit/f148c9e64ae0d4df96323271729fa6a6ca68a671) - **duckdb**: Transpile Spark's `exp.PosExplode` *(PR [#5311](https://github.com/tobymao/sqlglot/pull/5311) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5309](https://github.com/tobymao/sqlglot/issues/5309) opened by [@nimrodolev](https://github.com/nimrodolev)*\n- [`179a278`](https://github.com/tobymao/sqlglot/commit/179a278c7fdbc29105e37f132e6f03e18627f769) - **exasol**: added the regexp_replace function *(PR [#5313](https://github.com/tobymao/sqlglot/pull/5313) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`8a2f65d`](https://github.com/tobymao/sqlglot/commit/8a2f65d6b2b68ad5ba45a5aed5e56c4dc0fea6fc) - **parser**: parse and annotate type for ARRAY_SLICE *(PR [#5312](https://github.com/tobymao/sqlglot/pull/5312) by [@geooo109](https://github.com/geooo109))*\n- [`d2f7c41`](https://github.com/tobymao/sqlglot/commit/d2f7c41f9f30f4cf0c74782be9be0cc6e75565f3) - add TypeOf / toTypeName support *(PR [#5315](https://github.com/tobymao/sqlglot/pull/5315) by [@ankur334](https://github.com/ankur334))*\n- [`950c15d`](https://github.com/tobymao/sqlglot/commit/950c15db5ff64b6f11036f8003db3e5b1fb3afc3) - **exasol**: add var_pop built in function to exasol dialect *(PR [#5328](https://github.com/tobymao/sqlglot/pull/5328) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c4ca182`](https://github.com/tobymao/sqlglot/commit/c4ca182ad637b7a22b55d0ecf320c5a09ec5d56c) - **optimizer**: annotate type for FROM_BASE64 *(PR [#5329](https://github.com/tobymao/sqlglot/pull/5329) by [@geooo109](https://github.com/geooo109))*\n- [`0992e99`](https://github.com/tobymao/sqlglot/commit/0992e99f99aeb4ecc97e6918a23b8fd524311ed9) - **exasol**: Add support  APPROXIMATE_COUNT_DISTINCT functions in exasol dialect *(PR [#5330](https://github.com/tobymao/sqlglot/pull/5330) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`7b72bbe`](https://github.com/tobymao/sqlglot/commit/7b72bbed3a0930e11ce4a0fdd9082de715326ac9) - **optimizer**: annotate type for ANY_VALUE *(PR [#5331](https://github.com/tobymao/sqlglot/pull/5331) by [@geooo109](https://github.com/geooo109))*\n- [`c0d57e7`](https://github.com/tobymao/sqlglot/commit/c0d57e747bf5d2bed7ba2007ac2092d5797ee038) - **optimizer**: annotate type for CHR *(PR [#5332](https://github.com/tobymao/sqlglot/pull/5332) by [@geooo109](https://github.com/geooo109))*\n- [`d65b5c2`](https://github.com/tobymao/sqlglot/commit/d65b5c22c29416007cca0154fd35f1d4b5efc929) - **optimizer**: annotate type for COUNTIF *(PR [#5334](https://github.com/tobymao/sqlglot/pull/5334) by [@geooo109](https://github.com/geooo109))*\n- [`521b705`](https://github.com/tobymao/sqlglot/commit/521b7053213df8577f609409af2552c2ff4fd8c9) - **optimizer**: annotate type for GENERATE_ARRAY *(PR [#5335](https://github.com/tobymao/sqlglot/pull/5335) by [@geooo109](https://github.com/geooo109))*\n- [`5fb26c5`](https://github.com/tobymao/sqlglot/commit/5fb26c58026018360f36a732394b612a3baac38b) - **optimizer**: annotate type for INT64 *(PR [#5339](https://github.com/tobymao/sqlglot/pull/5339) by [@geooo109](https://github.com/geooo109))*\n- [`cff9b55`](https://github.com/tobymao/sqlglot/commit/cff9b55d70a3b85057e6385c93c0814eaa50f40b) - **optimizer**: annotate type for LOGICAL_AND and LOGICAL_OR *(PR [#5340](https://github.com/tobymao/sqlglot/pull/5340) by [@geooo109](https://github.com/geooo109))*\n- [`b94a6f9`](https://github.com/tobymao/sqlglot/commit/b94a6f9228aa730296c3152179bfbf3503521063) - **optimizer**: annotate type for MAKE_INTERVAL *(PR [#5341](https://github.com/tobymao/sqlglot/pull/5341) by [@geooo109](https://github.com/geooo109))*\n- [`2c9a7c6`](https://github.com/tobymao/sqlglot/commit/2c9a7c6f0b097a9e8514fc5e2af21c52f145920c) - **optimizer**: annotate type for LAST_VALUE *(PR [#5336](https://github.com/tobymao/sqlglot/pull/5336) by [@geooo109](https://github.com/geooo109))*\n- [`d862a28`](https://github.com/tobymao/sqlglot/commit/d862a28b0a30f0c5774351f38a61f195120ad904) - **optimizer**: annoate type for TO_BASE64 *(PR [#5342](https://github.com/tobymao/sqlglot/pull/5342) by [@geooo109](https://github.com/geooo109))*\n- [`85888c1`](https://github.com/tobymao/sqlglot/commit/85888c1b7cbbd0eee179d902a54fbd2a899cc16b) - **optimizer**: annotate type for UNIX_DATE *(PR [#5343](https://github.com/tobymao/sqlglot/pull/5343) by [@geooo109](https://github.com/geooo109))*\n- [`8a214e0`](https://github.com/tobymao/sqlglot/commit/8a214e0859dfb715fcef0dd6b2d6392012b1f3fb) - **optimizer**: annotate type for UNIX_SECONDS *(PR [#5344](https://github.com/tobymao/sqlglot/pull/5344) by [@geooo109](https://github.com/geooo109))*\n- [`625cb74`](https://github.com/tobymao/sqlglot/commit/625cb74b69e99ea1a707549366ea960d759848c9) - **optimizer**: annotate type for STARTS_WITH *(PR [#5345](https://github.com/tobymao/sqlglot/pull/5345) by [@geooo109](https://github.com/geooo109))*\n- [`0337c4d`](https://github.com/tobymao/sqlglot/commit/0337c4d46e9e85d951fc9565a47e338106543711) - **optimizer**: annotate type for SHA and SHA2 *(PR [#5346](https://github.com/tobymao/sqlglot/pull/5346) by [@geooo109](https://github.com/geooo109))*\n- [`835d9e6`](https://github.com/tobymao/sqlglot/commit/835d9e6c9ffc05de642113b566a1a4eb9cc38470) - add case-insensitive uppercase normalization strategy *(PR [#5349](https://github.com/tobymao/sqlglot/pull/5349) by [@georgesittas](https://github.com/georgesittas))*\n- [`f80493e`](https://github.com/tobymao/sqlglot/commit/f80493efb168f600dc92da439d84e820f303e5aa) - **exasol**: Add TO_CHAR function support in exasol dialect *(PR [#5350](https://github.com/tobymao/sqlglot/pull/5350) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`cea6a24`](https://github.com/tobymao/sqlglot/commit/cea6a240292d6e31bc73179d433835483e65747a) - **teradata**: add FORMAT phrase parsing *(PR [#5348](https://github.com/tobymao/sqlglot/pull/5348) by [@readjfb](https://github.com/readjfb))*\n- [`eae64e1`](https://github.com/tobymao/sqlglot/commit/eae64e1629a276bf3885749991869b6c6dea8a8b) - **duckdb**: support new lambda syntax *(PR [#5359](https://github.com/tobymao/sqlglot/pull/5359) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5357](https://github.com/tobymao/sqlglot/issues/5357) opened by [@aersam](https://github.com/aersam)*\n- [`e77991d`](https://github.com/tobymao/sqlglot/commit/e77991d92fad56014ba2778c71e5e446d4dd090e) - **duckdb**: Add support for SET VARIABLE *(PR [#5360](https://github.com/tobymao/sqlglot/pull/5360) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5356](https://github.com/tobymao/sqlglot/issues/5356) opened by [@aersam](https://github.com/aersam)*\n- [`c1d3d61`](https://github.com/tobymao/sqlglot/commit/c1d3d61d00f00d2030107689d8704f7a488a80a7) - **optimizer**: annotate type for CORR *(PR [#5364](https://github.com/tobymao/sqlglot/pull/5364) by [@geooo109](https://github.com/geooo109))*\n- [`c1e8677`](https://github.com/tobymao/sqlglot/commit/c1e867767a006e774a2c200c10eb85b3fbd8a372) - **optimizer**: annotate type for COVAR_POP *(PR [#5365](https://github.com/tobymao/sqlglot/pull/5365) by [@geooo109](https://github.com/geooo109))*\n- [`e110ef4`](https://github.com/tobymao/sqlglot/commit/e110ef4f774e6ab8de6d4c86e5d306ab53fe895b) - **optimizer**: annotate type for COVAR_SAMP *(PR [#5367](https://github.com/tobymao/sqlglot/pull/5367) by [@geooo109](https://github.com/geooo109))*\n- [`5b59c16`](https://github.com/tobymao/sqlglot/commit/5b59c16528fb1904c64bef0ca6307bb6a95e5a2c) - **optimizer**: annotate type for DATETIME *(PR [#5369](https://github.com/tobymao/sqlglot/pull/5369) by [@geooo109](https://github.com/geooo109))*\n- [`47176ce`](https://github.com/tobymao/sqlglot/commit/47176ce6b9a4c1722f285034b08a6ae782129894) - **optimizer**: annotate type for ENDS_WITH *(PR [#5370](https://github.com/tobymao/sqlglot/pull/5370) by [@geooo109](https://github.com/geooo109))*\n- [`1fd757e`](https://github.com/tobymao/sqlglot/commit/1fd757e6279315f00e719974613313a6e43dfe55) - **fabric**: Ensure TIMESTAMPTZ is used with AT TIME ZONE *(PR [#5362](https://github.com/tobymao/sqlglot/pull/5362) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`2cce53d`](https://github.com/tobymao/sqlglot/commit/2cce53d59968f0a4bb3e9599ade93b0e6a140c68) - **optimizer**: annotate type for LAG *(PR [#5371](https://github.com/tobymao/sqlglot/pull/5371) by [@geooo109](https://github.com/geooo109))*\n- [`a3227de`](https://github.com/tobymao/sqlglot/commit/a3227de3fc57d559eb899dec08af01f85b470ce4) - improve transpilation of `ROUND(x, y)` to Postgres *(PR [#5368](https://github.com/tobymao/sqlglot/pull/5368) by [@blecourt-private](https://github.com/blecourt-private))*\n  - :arrow_lower_right: *addresses issue [#5366](https://github.com/tobymao/sqlglot/issues/5366) opened by [@blecourt-private](https://github.com/blecourt-private)*\n- [`b368fba`](https://github.com/tobymao/sqlglot/commit/b368fba59b606e038d445b2ca2d8436e115af3d6) - **optimizer**: parse and annotate type for ASCII *(PR [#5377](https://github.com/tobymao/sqlglot/pull/5377) by [@geooo109](https://github.com/geooo109))*\n- [`7f19b31`](https://github.com/tobymao/sqlglot/commit/7f19b31ebd7981e53a8f8ba343b4f3222fe160c7) - **optimizer**: annotate type for UNICODE *(PR [#5381](https://github.com/tobymao/sqlglot/pull/5381) by [@geooo109](https://github.com/geooo109))*\n- [`f035bf0`](https://github.com/tobymao/sqlglot/commit/f035bf0eb582aa07d4ad79e0ed1958ce0d091ad9) - **dremio**: Add TIME_MAPPING for Dremio dialect *(PR [#5378](https://github.com/tobymao/sqlglot/pull/5378) by [@mateuszpoleski](https://github.com/mateuszpoleski))*\n- [`31cfd0f`](https://github.com/tobymao/sqlglot/commit/31cfd0fc3309bc1080b7a2ba8d40b2aba5c098a3) - **exasol**: add to_date and refactored to_char functions with respect to time mapping *(PR [#5379](https://github.com/tobymao/sqlglot/pull/5379) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`bd3776e`](https://github.com/tobymao/sqlglot/commit/bd3776eaa26d40b44c4cebc2f3838b4055653548) - **doris**: add PROPERTIES_LOCATION mapping for Doris dialect *(PR [#5391](https://github.com/tobymao/sqlglot/pull/5391) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`7eaa67a`](https://github.com/tobymao/sqlglot/commit/7eaa67acb216501046c739f56839418b84f244c0) - **doris**: properly supported PROPERTIES and UNIQUE KEY table prop *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1e78163`](https://github.com/tobymao/sqlglot/commit/1e78163b829e910e7960c79e7ab118c07d1ecdc3) - **duckdb**: support column access via index *(PR [#5395](https://github.com/tobymao/sqlglot/pull/5395) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5392](https://github.com/tobymao/sqlglot/issues/5392) opened by [@tekumara](https://github.com/tekumara)*\n- [`1014a67`](https://github.com/tobymao/sqlglot/commit/1014a6759b0917ef1bf5af0dbbdcca72214a8dea) - remove redundant todate in dayofweek closes [#5398](https://github.com/tobymao/sqlglot/pull/5398) *(PR [#5399](https://github.com/tobymao/sqlglot/pull/5399) by [@tobymao](https://github.com/tobymao))*\n- [`be52f78`](https://github.com/tobymao/sqlglot/commit/be52f7866b03e436d103d9201d1a44c6632c643a) - **exasol**: add support for CONVERT_TZ function *(PR [#5401](https://github.com/tobymao/sqlglot/pull/5401) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`d637161`](https://github.com/tobymao/sqlglot/commit/d637161406faf623418f112162268bedb422213b) - **exasol**: add mapping to TIME_TO_STR in exasol dialect *(PR [#5403](https://github.com/tobymao/sqlglot/pull/5403) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`b2631ae`](https://github.com/tobymao/sqlglot/commit/b2631aec8d1bdb08decb201b6bd2ba5d927bb121) - **optimizer**: annotate type for bigquery BIT_AND, BIT_OR, BIT_XOR, BIT_COUNT *(PR [#5405](https://github.com/tobymao/sqlglot/pull/5405) by [@geooo109](https://github.com/geooo109))*\n- [`b81ae62`](https://github.com/tobymao/sqlglot/commit/b81ae629bfb27760ddd832402a86dabe4e65072f) - **exasol**: map STR_TO_TIME to TO_DATE and *(PR [#5407](https://github.com/tobymao/sqlglot/pull/5407) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c2fb9ab`](https://github.com/tobymao/sqlglot/commit/c2fb9abeb2f077f00278e46efd9573a3806cd218) - add `DateStrToTime` *(PR [#5409](https://github.com/tobymao/sqlglot/pull/5409) by [@betodealmeida](https://github.com/betodealmeida))*\n- [`a95993a`](https://github.com/tobymao/sqlglot/commit/a95993ae4e8aa99969db059a534819a4f0b62b96) - **snowflake**: improve transpilation of queries with UNNEST sources *(PR [#5408](https://github.com/tobymao/sqlglot/pull/5408) by [@georgesittas](https://github.com/georgesittas))*\n- [`7b69f54`](https://github.com/tobymao/sqlglot/commit/7b69f545bbcfeb1e1f2f3b7e0b9757cfd675e4a5) - **snowflake**: Support SEMANTIC_VIEW *(PR [#5414](https://github.com/tobymao/sqlglot/pull/5414) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5406](https://github.com/tobymao/sqlglot/issues/5406) opened by [@jkillian](https://github.com/jkillian)*\n- [`7dba6f6`](https://github.com/tobymao/sqlglot/commit/7dba6f64d9a7945bbdef1b6e014d802014567a1e) - **exasol**: map AT TIME ZONE to CONVERT_TZ *(PR [#5416](https://github.com/tobymao/sqlglot/pull/5416) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`25f2c1b`](https://github.com/tobymao/sqlglot/commit/25f2c1bb18f9d073b128150566cb27c0c2da0865) - **postgres**: query placeholders *(PR [#5415](https://github.com/tobymao/sqlglot/pull/5415) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5412](https://github.com/tobymao/sqlglot/issues/5412) opened by [@aersam](https://github.com/aersam)*\n- [`c309c87`](https://github.com/tobymao/sqlglot/commit/c309c8763a90bf0bce02e21f4088b38d85556cce) - **doris**: support range partitioning *(PR [#5402](https://github.com/tobymao/sqlglot/pull/5402) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`394d3a8`](https://github.com/tobymao/sqlglot/commit/394d3a81ef41d3052c0b0d6e48180c344b7db143) - **dremio**: Add support for DATE_ADD and DATE_SUB *(PR [#5411](https://github.com/tobymao/sqlglot/pull/5411) by [@mateuszpoleski](https://github.com/mateuszpoleski))*\n- [`9cfac4f`](https://github.com/tobymao/sqlglot/commit/9cfac4fb04ce1fd038c3e8cbdb755cc24c052497) - **doris**: enhance partitioning support *(PR [#5421](https://github.com/tobymao/sqlglot/pull/5421) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`a018bea`](https://github.com/tobymao/sqlglot/commit/a018bea159261a3ad4ac082f29e30fe1153995b3) - **exasol**: mapped exp.CurrentUser to exasol CURRENT_USER *(PR [#5422](https://github.com/tobymao/sqlglot/pull/5422) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`489dc5c`](https://github.com/tobymao/sqlglot/commit/489dc5c2f7506e0fe4de549384dd0f816e9fd12f) - **optimizer**: parse and annotate type support for JSON_ARRAY *(PR [#5424](https://github.com/tobymao/sqlglot/pull/5424) by [@geooo109](https://github.com/geooo109))*\n- [`0ed518c`](https://github.com/tobymao/sqlglot/commit/0ed518c67042002ee0af91bee0b9e7093c85f926) - **optimizer**: annotate type for bigquery JSON_VALUE *(PR [#5427](https://github.com/tobymao/sqlglot/pull/5427) by [@geooo109](https://github.com/geooo109))*\n- [`6091617`](https://github.com/tobymao/sqlglot/commit/6091617067c263e3e834e579b37aa1c601b1ddc7) - **optimizer**: annotate type for bigquery JSON_VALUE_ARRAY *(PR [#5428](https://github.com/tobymao/sqlglot/pull/5428) by [@geooo109](https://github.com/geooo109))*\n- [`631c851`](https://github.com/tobymao/sqlglot/commit/631c851cbbfbf55cb66a79c2549aeeb443fcab83) - **optimizer**: parse and annotate type support for bigquery JSON_TYPE *(PR [#5430](https://github.com/tobymao/sqlglot/pull/5430) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`5724538`](https://github.com/tobymao/sqlglot/commit/5724538f278b2178114b88850251afd7c3db0dda) - **bigquery**: ARRAY_CONCAT type annotation *(PR [#5293](https://github.com/tobymao/sqlglot/pull/5293) by [@geooo109](https://github.com/geooo109))*\n- [`0a6afcd`](https://github.com/tobymao/sqlglot/commit/0a6afcd90c663aaef9b385fc12ccd19dbf6388cc) - use re-entrant lock in dialects/__init__ to avoid deadlocks *(PR [#5322](https://github.com/tobymao/sqlglot/pull/5322) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5321](https://github.com/tobymao/sqlglot/issues/5321) opened by [@jc-5s](https://github.com/jc-5s)*\n- [`599ca81`](https://github.com/tobymao/sqlglot/commit/599ca8101f48805098cbdf808ac2923a8246066b) - **parser**: avoid CTE values ALIAS gen, when ALIAS exists *(PR [#5323](https://github.com/tobymao/sqlglot/pull/5323) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5318](https://github.com/tobymao/sqlglot/issues/5318) opened by [@ankur334](https://github.com/ankur334)*\n- [`5a0f589`](https://github.com/tobymao/sqlglot/commit/5a0f589a0fdb6743c3be2f98b74a34780f51332b) - **spark**: distinguish STORED AS from USING *(PR [#5320](https://github.com/tobymao/sqlglot/pull/5320) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5317](https://github.com/tobymao/sqlglot/issues/5317) opened by [@cosinequanon](https://github.com/cosinequanon)*\n- [`cbc79c2`](https://github.com/tobymao/sqlglot/commit/cbc79c2a47c46370de0378b8bae61f4f3c17ca82) - preserve ORDER BY comments fixes [#5326](https://github.com/tobymao/sqlglot/pull/5326) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`fa69583`](https://github.com/tobymao/sqlglot/commit/fa69583d8b4f5801d05c21a92b43dea272a3ef49) - **optimizer**: avoid qualifying CTE *(PR [#5327](https://github.com/tobymao/sqlglot/pull/5327) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5319](https://github.com/tobymao/sqlglot/issues/5319) opened by [@naamamaoz](https://github.com/naamamaoz)*\n- [`29cce43`](https://github.com/tobymao/sqlglot/commit/29cce43e72451feeb8788ac2660658075bf59093) - comment lost before GROUP, JOIN and HAVING *(PR [#5338](https://github.com/tobymao/sqlglot/pull/5338) by [@chiiips](https://github.com/chiiips))*\n- [`509b741`](https://github.com/tobymao/sqlglot/commit/509b74173f678842e7550c75c4d8d906c879fb12) - preserve multi-arg DECODE function instead of converting to CASE *(PR [#5352](https://github.com/tobymao/sqlglot/pull/5352) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5351](https://github.com/tobymao/sqlglot/issues/5351) opened by [@kentmaxwell](https://github.com/kentmaxwell)*\n- [`188d446`](https://github.com/tobymao/sqlglot/commit/188d446ca65125c63bbfff96d15d91078deb6b4a) - **optimizer**: downstream column for PIVOT *(PR [#5363](https://github.com/tobymao/sqlglot/pull/5363) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5354](https://github.com/tobymao/sqlglot/issues/5354) opened by [@suresh-summation](https://github.com/suresh-summation)*\n- [`d7ccb48`](https://github.com/tobymao/sqlglot/commit/d7ccb48e542c49258e31cc4df45f49beebc2e238) - **duckdb**: week/quarter support *(PR [#5374](https://github.com/tobymao/sqlglot/pull/5374) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`252469d`](https://github.com/tobymao/sqlglot/commit/252469d2d0ed221dbb2fde86043506ad15dbe7e5) - **snowflake**: transpile bigquery CURRENT_DATE with timezone *(PR [#5387](https://github.com/tobymao/sqlglot/pull/5387) by [@geooo109](https://github.com/geooo109))*\n- [`7511853`](https://github.com/tobymao/sqlglot/commit/751185325caf838107ecb4e8f35ad77bf3cc9bf2) - **postgres**: add XML type *(PR [#5396](https://github.com/tobymao/sqlglot/pull/5396) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5393](https://github.com/tobymao/sqlglot/issues/5393) opened by [@aersam](https://github.com/aersam)*\n- [`9e8d3ab`](https://github.com/tobymao/sqlglot/commit/9e8d3abedcffb1c267ed0e6a8332af3b52105d41) - **optimizer**: Preserve struct-column parentheses for RisingWave dialect *(PR [#5376](https://github.com/tobymao/sqlglot/pull/5376) by [@MisterWheatley](https://github.com/MisterWheatley))*\n- [`3223e63`](https://github.com/tobymao/sqlglot/commit/3223e6394fdd3f8e48c68bbb940b661ff8e76fd8) - **fabric**: cast datetimeoffset to datetime2 *(PR [#5385](https://github.com/tobymao/sqlglot/pull/5385) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`12b49dd`](https://github.com/tobymao/sqlglot/commit/12b49dd800951a48ea8bc0f01d7c35340236f559) - remove equal sign from CREATE TABLE comment (doris, starrocks) *(PR [#5390](https://github.com/tobymao/sqlglot/pull/5390) by [@xinge-ji](https://github.com/xinge-ji))*\n- [`06cea31`](https://github.com/tobymao/sqlglot/commit/06cea310bd9fd3a9a9fa0ba008596e878a430df8) - **postgres**: support KEY related locks *(PR [#5397](https://github.com/tobymao/sqlglot/pull/5397) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5394](https://github.com/tobymao/sqlglot/issues/5394) opened by [@aurimasandriusaitis](https://github.com/aurimasandriusaitis)*\n- [`92d93a6`](https://github.com/tobymao/sqlglot/commit/92d93a624b41df8bb4628c1f2d0cbb8c7844c927) - **parser**: do not consume modifier prefixes in group parser, fixes [#5400](https://github.com/tobymao/sqlglot/pull/5400) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ba0c801`](https://github.com/tobymao/sqlglot/commit/ba0c801e3dab8e08d4b5f7f73247ec6cfdc667e5) - **tsql**: change READ_ONLY to READONLY *(PR [#5410](https://github.com/tobymao/sqlglot/pull/5410) by [@CrispinStichartFNSB](https://github.com/CrispinStichartFNSB))*\n- [`63da895`](https://github.com/tobymao/sqlglot/commit/63da89563fddc13ee7aec06ee36d8a0f74227ee1) - **risingwave**: Fix RisingWave dialect SQL for MAP datatype declaration *(PR [#5418](https://github.com/tobymao/sqlglot/pull/5418) by [@MisterWheatley](https://github.com/MisterWheatley))*\n- [`edacae1`](https://github.com/tobymao/sqlglot/commit/edacae183fe26ea25bffe1bccd335bf57ed34ecb) - **snowflake**: transpile bigquery GENERATE_DATE_ARRAY with column access *(PR [#5388](https://github.com/tobymao/sqlglot/pull/5388) by [@geooo109](https://github.com/geooo109))*\n- [`5835b8d`](https://github.com/tobymao/sqlglot/commit/5835b8d6c7fe77d9645691bb88021af137ed0bac) - **duckdb**: make bracket parsing aware of duckdb MAP func *(PR [#5423](https://github.com/tobymao/sqlglot/pull/5423) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5417](https://github.com/tobymao/sqlglot/issues/5417) opened by [@MisterWheatley](https://github.com/MisterWheatley)*\n- [`5c59816`](https://github.com/tobymao/sqlglot/commit/5c59816f5572f8adb1de9c97f0007d19091910ec) - **snowflake**: ALTER TABLE ADD with multiple columns *(PR [#5431](https://github.com/tobymao/sqlglot/pull/5431) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5426](https://github.com/tobymao/sqlglot/issues/5426) opened by [@ca0904](https://github.com/ca0904)*\n\n### :recycle: Refactors\n- [`8d118ea`](https://github.com/tobymao/sqlglot/commit/8d118ead9c15e7b2b4b51b7cf93cab94e61c2625) - **athena**: route statements to hive/trino depending on their type *(PR [#5314](https://github.com/tobymao/sqlglot/pull/5314) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5267](https://github.com/tobymao/sqlglot/issues/5267) opened by [@cpcloud](https://github.com/cpcloud)*\n\n### :wrench: Chores\n- [`cc389fa`](https://github.com/tobymao/sqlglot/commit/cc389facb33f94a0d1f696f2ef9e92f298711894) - **optimizer**: annotate type SHA1, SHA256, SHA512 for BigQuery *(PR [#5347](https://github.com/tobymao/sqlglot/pull/5347) by [@geooo109](https://github.com/geooo109))*\n- [`194850a`](https://github.com/tobymao/sqlglot/commit/194850a52497300a8f1d47f2306b67cdd11ffab6) - **exasol**: clean up TO_CHAR *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1abd461`](https://github.com/tobymao/sqlglot/commit/1abd461295830807c52f24d25ac6938095f54831) - bump min. supported version to python 3.9 *(PR [#5353](https://github.com/tobymao/sqlglot/pull/5353) by [@georgesittas](https://github.com/georgesittas))*\n- [`71b1349`](https://github.com/tobymao/sqlglot/commit/71b1349a26d2b9839899900ef8fdfb1ebc3d68fd) - **postgres, hive**: use ASCII node instead of UNICODE node *(PR [#5380](https://github.com/tobymao/sqlglot/pull/5380) by [@geooo109](https://github.com/geooo109))*\n- [`a5c2245`](https://github.com/tobymao/sqlglot/commit/a5c2245c3e30f5bc3f410edacf3a077ce99f4a80) - improve error msg for PIVOT with missing aggregation *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v27.0.0] - 2025-07-07\n### :boom: BREAKING CHANGES\n- due to [`f2bf000`](https://github.com/tobymao/sqlglot/commit/f2bf000a410fb18531bb90ef1d767baf0e8bce7a) - avoid creating new alias for qualifying unpivot *(PR [#5121](https://github.com/tobymao/sqlglot/pull/5121) by [@geooo109](https://github.com/geooo109))*:\n\n  avoid creating new alias for qualifying unpivot (#5121)\n\n- due to [`a126ce8`](https://github.com/tobymao/sqlglot/commit/a126ce8a25287cf3531d815035fa3d567dc772fb) - make coalesce simplification optional, skip by default *(PR [#5123](https://github.com/tobymao/sqlglot/pull/5123) by [@barakalon](https://github.com/barakalon))*:\n\n  make coalesce simplification optional, skip by default (#5123)\n\n- due to [`6910744`](https://github.com/tobymao/sqlglot/commit/6910744e6260793b3f9190782cf60fbbd9adcd38) - update py03 version *(PR [#5136](https://github.com/tobymao/sqlglot/pull/5136) by [@benfdking](https://github.com/benfdking))*:\n\n  update py03 version (#5136)\n\n- due to [`a56deab`](https://github.com/tobymao/sqlglot/commit/a56deabc2b9543209fb5e41f19c3bef89177a577) - bump sqlglotrs to 0.5.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.5.0\n\n- due to [`c484ca3`](https://github.com/tobymao/sqlglot/commit/c484ca39bad750a96b62e2edae85612cac66ba30) - recognize ARRAY_CONCAT_AGG as an aggregate function *(PR [#5141](https://github.com/tobymao/sqlglot/pull/5141) by [@georgesittas](https://github.com/georgesittas))*:\n\n  recognize ARRAY_CONCAT_AGG as an aggregate function (#5141)\n\n- due to [`72ce404`](https://github.com/tobymao/sqlglot/commit/72ce40405625239a0d6763d502e5af8b12abfe9b) - Refactor ALTER TABLE ADD parsing *(PR [#5144](https://github.com/tobymao/sqlglot/pull/5144) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Refactor ALTER TABLE ADD parsing (#5144)\n\n- due to [`e73ddb7`](https://github.com/tobymao/sqlglot/commit/e73ddb733b7f120ae74054e6d4dc7d458f59ac50) - preserve TIMESTAMP on roundtrip *(PR [#5145](https://github.com/tobymao/sqlglot/pull/5145) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve TIMESTAMP on roundtrip (#5145)\n\n- due to [`f6124c6`](https://github.com/tobymao/sqlglot/commit/f6124c6343f67563fc19f617891ecfc145a642db) - return token vector in `tokenize` even on failure *(PR [#5155](https://github.com/tobymao/sqlglot/pull/5155) by [@georgesittas](https://github.com/georgesittas))*:\n\n  return token vector in `tokenize` even on failure (#5155)\n\n- due to [`64c37f1`](https://github.com/tobymao/sqlglot/commit/64c37f147366fe87ae187996ecb3c9a5afa7c264) - bump sqlglotrs to 0.6.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.6.0\n\n- due to [`434c45b`](https://github.com/tobymao/sqlglot/commit/434c45b547c3a5ea155dc8d7da2baab326eb6d4f) - improve support for ENDSWITH closes [#5170](https://github.com/tobymao/sqlglot/pull/5170) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve support for ENDSWITH closes #5170\n\n- due to [`bc001ce`](https://github.com/tobymao/sqlglot/commit/bc001cef4c907d8fa421d3190b4fa91865d9ff6c) - Add support for ANY_VALUE for versions 16+ *(PR [#5179](https://github.com/tobymao/sqlglot/pull/5179) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for ANY_VALUE for versions 16+ (#5179)\n\n- due to [`6a2cb39`](https://github.com/tobymao/sqlglot/commit/6a2cb39d0ceec091dc4fc228f26d4f457729a3cf) - virtual column with AS(expr) as ComputedColumnConstraint *(PR [#5180](https://github.com/tobymao/sqlglot/pull/5180) by [@geooo109](https://github.com/geooo109))*:\n\n  virtual column with AS(expr) as ComputedColumnConstraint (#5180)\n\n- due to [`29e2f1d`](https://github.com/tobymao/sqlglot/commit/29e2f1d89c095c9fab0944a6962c99bd745c2c91) - Array_intersection transpilation support *(PR [#5186](https://github.com/tobymao/sqlglot/pull/5186) by [@HarishRavi96](https://github.com/HarishRavi96))*:\n\n  Array_intersection transpilation support (#5186)\n\n- due to [`ac6555b`](https://github.com/tobymao/sqlglot/commit/ac6555b4d6c162ef7b14b63307d01fd560138ea0) - preserve DIV binary operator, fixes [#5198](https://github.com/tobymao/sqlglot/pull/5198) *(PR [#5199](https://github.com/tobymao/sqlglot/pull/5199) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve DIV binary operator, fixes #5198 (#5199)\n\n- due to [`dfdd84b`](https://github.com/tobymao/sqlglot/commit/dfdd84bbc50da70f40a17b39935f8171d961f7d2) - CTEs instead of subqueries for pipe syntax *(PR [#5205](https://github.com/tobymao/sqlglot/pull/5205) by [@geooo109](https://github.com/geooo109))*:\n\n  CTEs instead of subqueries for pipe syntax (#5205)\n\n- due to [`5f95299`](https://github.com/tobymao/sqlglot/commit/5f9529940d83e89704f7d25eda63cd73fdb503ae) - support multi-part (>3) dotted functions *(PR [#5211](https://github.com/tobymao/sqlglot/pull/5211) by [@georgesittas](https://github.com/georgesittas))*:\n\n  support multi-part (>3) dotted functions (#5211)\n\n- due to [`02afa2a`](https://github.com/tobymao/sqlglot/commit/02afa2a1941fc67086d50dffac2857262f1c3c4f) - Preserve quoting for UDT *(PR [#5216](https://github.com/tobymao/sqlglot/pull/5216) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Preserve quoting for UDT (#5216)\n\n- due to [`44297f1`](https://github.com/tobymao/sqlglot/commit/44297f1c5c8c2cb16fe77c318312f417b4281708) - JOIN pipe syntax, Set Operators as CTEs *(PR [#5215](https://github.com/tobymao/sqlglot/pull/5215) by [@geooo109](https://github.com/geooo109))*:\n\n  JOIN pipe syntax, Set Operators as CTEs (#5215)\n\n- due to [`4f42d95`](https://github.com/tobymao/sqlglot/commit/4f42d951363f8c43a4c414dc21d0505d9c8e48bf) - Normalize date parts in `exp.Extract` generation *(PR [#5229](https://github.com/tobymao/sqlglot/pull/5229) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Normalize date parts in `exp.Extract` generation (#5229)\n\n- due to [`e7e38fe`](https://github.com/tobymao/sqlglot/commit/e7e38fe0e09f9affbff4ffa7023d0161e3a1ee49) - resolve table \"columns\" in bigquery that produce structs *(PR [#5230](https://github.com/tobymao/sqlglot/pull/5230) by [@georgesittas](https://github.com/georgesittas))*:\n\n  resolve table \"columns\" in bigquery that produce structs (#5230)\n\n- due to [`d3dc761`](https://github.com/tobymao/sqlglot/commit/d3dc761393146357a5d20c4d7992fd2a1ae5e6e2) - change comma to cross join when precedence is the same for all join types *(PR [#5240](https://github.com/tobymao/sqlglot/pull/5240) by [@georgesittas](https://github.com/georgesittas))*:\n\n  change comma to cross join when precedence is the same for all join types (#5240)\n\n- due to [`e7c217e`](https://github.com/tobymao/sqlglot/commit/e7c217ef08e5811e7dad2b3d26dbaa9f02114e38) - transpile from/to dbms_random.value *(PR [#5242](https://github.com/tobymao/sqlglot/pull/5242) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile from/to dbms_random.value (#5242)\n\n- due to [`31814cd`](https://github.com/tobymao/sqlglot/commit/31814cddb0cf65caf29fbc45a31a9c865b7991c7) - cast constructed timestamp literal to zone-aware type if needed *(PR [#5253](https://github.com/tobymao/sqlglot/pull/5253) by [@georgesittas](https://github.com/georgesittas))*:\n\n  cast constructed timestamp literal to zone-aware type if needed (#5253)\n\n- due to [`db4e0ec`](https://github.com/tobymao/sqlglot/commit/db4e0ece950a6a1f543d8ecad48a7d4b1d6872be) - convert information schema keywords to uppercase for consistency *(PR [#5263](https://github.com/tobymao/sqlglot/pull/5263) by [@mattiasthalen](https://github.com/mattiasthalen))*:\n\n  convert information schema keywords to uppercase for consistency (#5263)\n\n- due to [`eea1570`](https://github.com/tobymao/sqlglot/commit/eea1570ba530517a95699092ccd9ce6a856f5e84) - add support for SYSDATETIMEOFFSET closes [#5272](https://github.com/tobymao/sqlglot/pull/5272) *(PR [#5273](https://github.com/tobymao/sqlglot/pull/5273) by [@georgesittas](https://github.com/georgesittas))*:\n\n  add support for SYSDATETIMEOFFSET closes #5272 (#5273)\n\n- due to [`3d3ccc5`](https://github.com/tobymao/sqlglot/commit/3d3ccc52a40536b9ac4e974f1592dffe5a7568f9) - Transpile exp.PosExplode pos column alias *(PR [#5274](https://github.com/tobymao/sqlglot/pull/5274) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Transpile exp.PosExplode pos column alias (#5274)\n\n- due to [`9a95af1`](https://github.com/tobymao/sqlglot/commit/9a95af1c725cd70ffa8206f1d88452a7faab93b2) - only cast strings to timestamp for TO_CHAR (TimeToStr) *(PR [#5283](https://github.com/tobymao/sqlglot/pull/5283) by [@georgesittas](https://github.com/georgesittas))*:\n\n  only cast strings to timestamp for TO_CHAR (TimeToStr) (#5283)\n\n- due to [`8af4790`](https://github.com/tobymao/sqlglot/commit/8af479017ccde16049c897ae5d322d4a69843b65) - Fix parsing of ADD CONSTRAINT *(PR [#5288](https://github.com/tobymao/sqlglot/pull/5288) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix parsing of ADD CONSTRAINT (#5288)\n\n- due to [`18aea08`](https://github.com/tobymao/sqlglot/commit/18aea08f7dcaa887bcf29886cd3b3bc2850a3679) - include bigquery unnest aliases in selected sources *(PR [#5285](https://github.com/tobymao/sqlglot/pull/5285) by [@georgesittas](https://github.com/georgesittas))*:\n\n  include bigquery unnest aliases in selected sources (#5285)\n\n- due to [`0ff95c5`](https://github.com/tobymao/sqlglot/commit/0ff95c5903907c9ab30b7850bb3b962bc6da2bab) - add parsing/transpilation support for the REPLACE function *(PR [#5289](https://github.com/tobymao/sqlglot/pull/5289) by [@rahulj51](https://github.com/rahulj51))*:\n\n  add parsing/transpilation support for the REPLACE function (#5289)\n\n- due to [`dc03649`](https://github.com/tobymao/sqlglot/commit/dc03649bca0b7a090254976182a03c21dd2269ba) - only coerce time var -like units into strings for DATE_TRUNC *(PR [#5291](https://github.com/tobymao/sqlglot/pull/5291) by [@georgesittas](https://github.com/georgesittas))*:\n\n  only coerce time var -like units into strings for DATE_TRUNC (#5291)\n\n- due to [`5724538`](https://github.com/tobymao/sqlglot/commit/5724538f278b2178114b88850251afd7c3db0dda) - ARRAY_CONCAT type annotation *(PR [#5293](https://github.com/tobymao/sqlglot/pull/5293) by [@geooo109](https://github.com/geooo109))*:\n\n  ARRAY_CONCAT type annotation (#5293)\n\n- due to [`c103b23`](https://github.com/tobymao/sqlglot/commit/c103b2304dca552ac8cf6733156db8b59d3614f3) - add support for `SUBSTRING_INDEX` *(PR [#5296](https://github.com/tobymao/sqlglot/pull/5296) by [@ankur334](https://github.com/ankur334))*:\n\n  add support for `SUBSTRING_INDEX` (#5296)\n\n- due to [`a7bd823`](https://github.com/tobymao/sqlglot/commit/a7bd8234e0dd02abfe6fa56287e7bda14a549e5a) - annotate type of ARRAY_TO_STRING *(PR [#5301](https://github.com/tobymao/sqlglot/pull/5301) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type of ARRAY_TO_STRING (#5301)\n\n- due to [`6b42353`](https://github.com/tobymao/sqlglot/commit/6b4235340a2e432015c27b2aeadbdcb930bfa6b0) - annotate type of ARRAY_FIRST, ARRAY_LAST *(PR [#5303](https://github.com/tobymao/sqlglot/pull/5303) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type of ARRAY_FIRST, ARRAY_LAST (#5303)\n\n- due to [`db9b61e`](https://github.com/tobymao/sqlglot/commit/db9b61e4ecaa0600418eb90f637fb8b06b08c399) - parse, annotate type for ARRAY_REVERSE *(PR [#5306](https://github.com/tobymao/sqlglot/pull/5306) by [@geooo109](https://github.com/geooo109))*:\n\n  parse, annotate type for ARRAY_REVERSE (#5306)\n\n- due to [`5612a6d`](https://github.com/tobymao/sqlglot/commit/5612a6da6dee3545f3600db1e5b87c9450952eba) - add support for SPACE *(PR [#5308](https://github.com/tobymao/sqlglot/pull/5308) by [@ankur334](https://github.com/ankur334))*:\n\n  add support for SPACE (#5308)\n\n- due to [`8a2f65d`](https://github.com/tobymao/sqlglot/commit/8a2f65d6b2b68ad5ba45a5aed5e56c4dc0fea6fc) - parse and annotate type for ARRAY_SLICE *(PR [#5312](https://github.com/tobymao/sqlglot/pull/5312) by [@geooo109](https://github.com/geooo109))*:\n\n  parse and annotate type for ARRAY_SLICE (#5312)\n\n- due to [`8d118ea`](https://github.com/tobymao/sqlglot/commit/8d118ead9c15e7b2b4b51b7cf93cab94e61c2625) - route statements to hive/trino depending on their type *(PR [#5314](https://github.com/tobymao/sqlglot/pull/5314) by [@georgesittas](https://github.com/georgesittas))*:\n\n  route statements to hive/trino depending on their type (#5314)\n\n- due to [`d2f7c41`](https://github.com/tobymao/sqlglot/commit/d2f7c41f9f30f4cf0c74782be9be0cc6e75565f3) - add TypeOf / toTypeName support *(PR [#5315](https://github.com/tobymao/sqlglot/pull/5315) by [@ankur334](https://github.com/ankur334))*:\n\n  add TypeOf / toTypeName support (#5315)\n\n- due to [`5a0f589`](https://github.com/tobymao/sqlglot/commit/5a0f589a0fdb6743c3be2f98b74a34780f51332b) - distinguish STORED AS from USING *(PR [#5320](https://github.com/tobymao/sqlglot/pull/5320) by [@geooo109](https://github.com/geooo109))*:\n\n  distinguish STORED AS from USING (#5320)\n\n- due to [`c4ca182`](https://github.com/tobymao/sqlglot/commit/c4ca182ad637b7a22b55d0ecf320c5a09ec5d56c) - annotate type for FROM_BASE64 *(PR [#5329](https://github.com/tobymao/sqlglot/pull/5329) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for FROM_BASE64 (#5329)\n\n- due to [`7b72bbe`](https://github.com/tobymao/sqlglot/commit/7b72bbed3a0930e11ce4a0fdd9082de715326ac9) - annotate type for ANY_VALUE *(PR [#5331](https://github.com/tobymao/sqlglot/pull/5331) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for ANY_VALUE (#5331)\n\n- due to [`c0d57e7`](https://github.com/tobymao/sqlglot/commit/c0d57e747bf5d2bed7ba2007ac2092d5797ee038) - annotate type for CHR *(PR [#5332](https://github.com/tobymao/sqlglot/pull/5332) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for CHR (#5332)\n\n- due to [`d65b5c2`](https://github.com/tobymao/sqlglot/commit/d65b5c22c29416007cca0154fd35f1d4b5efc929) - annotate type for COUNTIF *(PR [#5334](https://github.com/tobymao/sqlglot/pull/5334) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for COUNTIF (#5334)\n\n- due to [`521b705`](https://github.com/tobymao/sqlglot/commit/521b7053213df8577f609409af2552c2ff4fd8c9) - annotate type for GENERATE_ARRAY *(PR [#5335](https://github.com/tobymao/sqlglot/pull/5335) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for GENERATE_ARRAY (#5335)\n\n- due to [`5fb26c5`](https://github.com/tobymao/sqlglot/commit/5fb26c58026018360f36a732394b612a3baac38b) - annotate type for INT64 *(PR [#5339](https://github.com/tobymao/sqlglot/pull/5339) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for INT64 (#5339)\n\n- due to [`cff9b55`](https://github.com/tobymao/sqlglot/commit/cff9b55d70a3b85057e6385c93c0814eaa50f40b) - annotate type for LOGICAL_AND and LOGICAL_OR *(PR [#5340](https://github.com/tobymao/sqlglot/pull/5340) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for LOGICAL_AND and LOGICAL_OR (#5340)\n\n- due to [`b94a6f9`](https://github.com/tobymao/sqlglot/commit/b94a6f9228aa730296c3152179bfbf3503521063) - annotate type for MAKE_INTERVAL *(PR [#5341](https://github.com/tobymao/sqlglot/pull/5341) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for MAKE_INTERVAL (#5341)\n\n- due to [`2c9a7c6`](https://github.com/tobymao/sqlglot/commit/2c9a7c6f0b097a9e8514fc5e2af21c52f145920c) - annotate type for LAST_VALUE *(PR [#5336](https://github.com/tobymao/sqlglot/pull/5336) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for LAST_VALUE (#5336)\n\n- due to [`d862a28`](https://github.com/tobymao/sqlglot/commit/d862a28b0a30f0c5774351f38a61f195120ad904) - annoate type for TO_BASE64 *(PR [#5342](https://github.com/tobymao/sqlglot/pull/5342) by [@geooo109](https://github.com/geooo109))*:\n\n  annoate type for TO_BASE64 (#5342)\n\n- due to [`85888c1`](https://github.com/tobymao/sqlglot/commit/85888c1b7cbbd0eee179d902a54fbd2a899cc16b) - annotate type for UNIX_DATE *(PR [#5343](https://github.com/tobymao/sqlglot/pull/5343) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for UNIX_DATE (#5343)\n\n- due to [`8a214e0`](https://github.com/tobymao/sqlglot/commit/8a214e0859dfb715fcef0dd6b2d6392012b1f3fb) - annotate type for UNIX_SECONDS *(PR [#5344](https://github.com/tobymao/sqlglot/pull/5344) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for UNIX_SECONDS (#5344)\n\n- due to [`625cb74`](https://github.com/tobymao/sqlglot/commit/625cb74b69e99ea1a707549366ea960d759848c9) - annotate type for STARTS_WITH *(PR [#5345](https://github.com/tobymao/sqlglot/pull/5345) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for STARTS_WITH (#5345)\n\n- due to [`0337c4d`](https://github.com/tobymao/sqlglot/commit/0337c4d46e9e85d951fc9565a47e338106543711) - annotate type for SHA and SHA2 *(PR [#5346](https://github.com/tobymao/sqlglot/pull/5346) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for SHA and SHA2 (#5346)\n\n- due to [`cc389fa`](https://github.com/tobymao/sqlglot/commit/cc389facb33f94a0d1f696f2ef9e92f298711894) - annotate type SHA1, SHA256, SHA512 for BigQuery *(PR [#5347](https://github.com/tobymao/sqlglot/pull/5347) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type SHA1, SHA256, SHA512 for BigQuery (#5347)\n\n- due to [`509b741`](https://github.com/tobymao/sqlglot/commit/509b74173f678842e7550c75c4d8d906c879fb12) - preserve multi-arg DECODE function instead of converting to CASE *(PR [#5352](https://github.com/tobymao/sqlglot/pull/5352) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve multi-arg DECODE function instead of converting to CASE (#5352)\n\n- due to [`c1d3d61`](https://github.com/tobymao/sqlglot/commit/c1d3d61d00f00d2030107689d8704f7a488a80a7) - annotate type for CORR *(PR [#5364](https://github.com/tobymao/sqlglot/pull/5364) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for CORR (#5364)\n\n- due to [`c1e8677`](https://github.com/tobymao/sqlglot/commit/c1e867767a006e774a2c200c10eb85b3fbd8a372) - annotate type for COVAR_POP *(PR [#5365](https://github.com/tobymao/sqlglot/pull/5365) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for COVAR_POP (#5365)\n\n- due to [`e110ef4`](https://github.com/tobymao/sqlglot/commit/e110ef4f774e6ab8de6d4c86e5d306ab53fe895b) - annotate type for COVAR_SAMP *(PR [#5367](https://github.com/tobymao/sqlglot/pull/5367) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for COVAR_SAMP (#5367)\n\n- due to [`5b59c16`](https://github.com/tobymao/sqlglot/commit/5b59c16528fb1904c64bef0ca6307bb6a95e5a2c) - annotate type for DATETIME *(PR [#5369](https://github.com/tobymao/sqlglot/pull/5369) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for DATETIME (#5369)\n\n- due to [`47176ce`](https://github.com/tobymao/sqlglot/commit/47176ce6b9a4c1722f285034b08a6ae782129894) - annotate type for ENDS_WITH *(PR [#5370](https://github.com/tobymao/sqlglot/pull/5370) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for ENDS_WITH (#5370)\n\n- due to [`2cce53d`](https://github.com/tobymao/sqlglot/commit/2cce53d59968f0a4bb3e9599ade93b0e6a140c68) - annotate type for LAG *(PR [#5371](https://github.com/tobymao/sqlglot/pull/5371) by [@geooo109](https://github.com/geooo109))*:\n\n  annotate type for LAG (#5371)\n\n- due to [`a3227de`](https://github.com/tobymao/sqlglot/commit/a3227de3fc57d559eb899dec08af01f85b470ce4) - improve transpilation of `ROUND(x, y)` to Postgres *(PR [#5368](https://github.com/tobymao/sqlglot/pull/5368) by [@blecourt-private](https://github.com/blecourt-private))*:\n\n  improve transpilation of `ROUND(x, y)` to Postgres (#5368)\n\n\n### :sparkles: New Features\n- [`82c50ce`](https://github.com/tobymao/sqlglot/commit/82c50ce68d9a1ad25095086ae3645f5c4996c18b) - **duckdb**: extend time travel parsing to take VERSION into account *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bb4f428`](https://github.com/tobymao/sqlglot/commit/bb4f4283b53bc060a8c7e0f12c1e7ef5b521c4e6) - bubble up comments nested under a Bracket, fixes [#5131](https://github.com/tobymao/sqlglot/pull/5131) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`9f318eb`](https://github.com/tobymao/sqlglot/commit/9f318ebe4502bb484a34873252cf4a40c7e440e4) - **snowflake**: Transpile BQ's `ARRAY(SELECT AS STRUCT ...)` *(PR [#5140](https://github.com/tobymao/sqlglot/pull/5140) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`93b402a`](https://github.com/tobymao/sqlglot/commit/93b402abc74e642ed312db585b33315674a450cd) - **parser**: support SELECT, FROM, WHERE with pipe syntax *(PR [#5128](https://github.com/tobymao/sqlglot/pull/5128) by [@geooo109](https://github.com/geooo109))*\n- [`1a8e78b`](https://github.com/tobymao/sqlglot/commit/1a8e78bd84e006023d5d3ea561504587dfbb55a9) - **parser**: ORDER BY with pipe syntax *(PR [#5153](https://github.com/tobymao/sqlglot/pull/5153) by [@geooo109](https://github.com/geooo109))*\n- [`966ad95`](https://github.com/tobymao/sqlglot/commit/966ad95432d5f8e29ade36d8271a5c489c207324) - **tsql**: add convert style 126 *(PR [#5157](https://github.com/tobymao/sqlglot/pull/5157) by [@pa1ch](https://github.com/pa1ch))*\n- [`b7ac6ff`](https://github.com/tobymao/sqlglot/commit/b7ac6ff4680ff619be4b0ddb01f61f916ed09d58) - **parser**: LIMIT/OFFSET pipe syntax *(PR [#5159](https://github.com/tobymao/sqlglot/pull/5159) by [@geooo109](https://github.com/geooo109))*\n- [`cfc158d`](https://github.com/tobymao/sqlglot/commit/cfc158d753d4f43d12c3b502633d29e43dcc5569) - **snowflake**: transpile STRTOK_TO_ARRAY to duckdb *(PR [#5165](https://github.com/tobymao/sqlglot/pull/5165) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5160](https://github.com/tobymao/sqlglot/issues/5160) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`ff0f30b`](https://github.com/tobymao/sqlglot/commit/ff0f30bcf7d0d74b26a703eaa632e1be15b3c001) - support ARRAY_REMOVE *(PR [#5163](https://github.com/tobymao/sqlglot/pull/5163) by [@geooo109](https://github.com/geooo109))*\n- [`9cac01f`](https://github.com/tobymao/sqlglot/commit/9cac01f6b4a5c93b55f5b68f21cb104932880a0e) - **tsql**: support FOR XML syntax *(PR [#5167](https://github.com/tobymao/sqlglot/pull/5167) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5161](https://github.com/tobymao/sqlglot/issues/5161) opened by [@codykonior](https://github.com/codykonior)*\n- [`8b5129f`](https://github.com/tobymao/sqlglot/commit/8b5129f288880032f0bf9d649984d82314039af1) - **postgres**: improve pretty-formatting of ARRAY[...] *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`964b4a1`](https://github.com/tobymao/sqlglot/commit/964b4a1e367e00e243b80edf677cd48d453ed31e) - add line/col position for Star *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`434c45b`](https://github.com/tobymao/sqlglot/commit/434c45b547c3a5ea155dc8d7da2baab326eb6d4f) - improve support for ENDSWITH closes [#5170](https://github.com/tobymao/sqlglot/pull/5170) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`63f9cb4`](https://github.com/tobymao/sqlglot/commit/63f9cb4b158b88574136b32241ee60254352c9e6) - **sqlglotrs**: match the Python implementation of __repr__ for tokens *(PR [#5172](https://github.com/tobymao/sqlglot/pull/5172) by [@georgesittas](https://github.com/georgesittas))*\n- [`c007afa`](https://github.com/tobymao/sqlglot/commit/c007afa23831e9bd86f401d85260e15edf00328f) - support Star instance as first arg of exp.column helper *(PR [#5177](https://github.com/tobymao/sqlglot/pull/5177) by [@georgesittas](https://github.com/georgesittas))*\n- [`bc001ce`](https://github.com/tobymao/sqlglot/commit/bc001cef4c907d8fa421d3190b4fa91865d9ff6c) - **postgres**: Add support for ANY_VALUE for versions 16+ *(PR [#5179](https://github.com/tobymao/sqlglot/pull/5179) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4674](https://github.com/TobikoData/sqlmesh/issues/4674) opened by [@petrikoro](https://github.com/petrikoro)*\n- [`ba05ff6`](https://github.com/tobymao/sqlglot/commit/ba05ff67127e056d567fc2c1d3bcc8e3dcce7b7e) - **parser**: AGGREGATE with GROUP AND ORDER BY pipe syntax *(PR [#5171](https://github.com/tobymao/sqlglot/pull/5171) by [@geooo109](https://github.com/geooo109))*\n- [`26077a4`](https://github.com/tobymao/sqlglot/commit/26077a47d9db750f44ab1baf9a434596b5bb613b) - make to_table more lenient *(PR [#5183](https://github.com/tobymao/sqlglot/pull/5183) by [@georgesittas](https://github.com/georgesittas))*\n- [`29e2f1d`](https://github.com/tobymao/sqlglot/commit/29e2f1d89c095c9fab0944a6962c99bd745c2c91) - Array_intersection transpilation support *(PR [#5186](https://github.com/tobymao/sqlglot/pull/5186) by [@HarishRavi96](https://github.com/HarishRavi96))*\n- [`d86a114`](https://github.com/tobymao/sqlglot/commit/d86a1147aeb866ed0ab2c342914ecf8cbfadac8a) - **sqlite**: implement RESPECT/IGNORE NULLS in first_value() *(PR [#5185](https://github.com/tobymao/sqlglot/pull/5185) by [@NickCrews](https://github.com/NickCrews))*\n- [`1d50fca`](https://github.com/tobymao/sqlglot/commit/1d50fca8ffc34e4acbc1b791c4cdf5f184a748db) - improve transpilation of st_point and st_distance *(PR [#5194](https://github.com/tobymao/sqlglot/pull/5194) by [@georgesittas](https://github.com/georgesittas))*\n- [`756ec3b`](https://github.com/tobymao/sqlglot/commit/756ec3b65db1eb2572d017a3ac12ece6bb44c726) - **parser**: SET OPERATORS with pipe syntax *(PR [#5184](https://github.com/tobymao/sqlglot/pull/5184) by [@geooo109](https://github.com/geooo109))*\n- [`c20f85e`](https://github.com/tobymao/sqlglot/commit/c20f85e3e171e502fc51f74894d3313f0ad61535) - **spark**: support ALTER ADD PARTITION *(PR [#5208](https://github.com/tobymao/sqlglot/pull/5208) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5204](https://github.com/tobymao/sqlglot/issues/5204) opened by [@cosinequanon](https://github.com/cosinequanon)*\n- [`44297f1`](https://github.com/tobymao/sqlglot/commit/44297f1c5c8c2cb16fe77c318312f417b4281708) - **parser**: JOIN pipe syntax, Set Operators as CTEs *(PR [#5215](https://github.com/tobymao/sqlglot/pull/5215) by [@geooo109](https://github.com/geooo109))*\n- [`21cd3eb`](https://github.com/tobymao/sqlglot/commit/21cd3ebf5d0b57f5b102c5aadc3b24a598ebe918) - **parser**: PIVOT/UNPIVOT pipe syntax *(PR [#5222](https://github.com/tobymao/sqlglot/pull/5222) by [@geooo109](https://github.com/geooo109))*\n- [`97f5822`](https://github.com/tobymao/sqlglot/commit/97f58226fc8815b23787b7b8699ea71f58268560) - **parser**: AS pipe syntax *(PR [#5224](https://github.com/tobymao/sqlglot/pull/5224) by [@geooo109](https://github.com/geooo109))*\n- [`a7e7fee`](https://github.com/tobymao/sqlglot/commit/a7e7feef02a77fe8606f3f482bad91230fa637f4) - **parser**: EXTEND pipe syntax *(PR [#5225](https://github.com/tobymao/sqlglot/pull/5225) by [@geooo109](https://github.com/geooo109))*\n- [`c1cb9f8`](https://github.com/tobymao/sqlglot/commit/c1cb9f8f682080f7a06c387219d79c6d068b6dbe) - **snowflake**: add autoincrement order clause support *(PR [#5223](https://github.com/tobymao/sqlglot/pull/5223) by [@dmaresma](https://github.com/dmaresma))*\n- [`91afe4c`](https://github.com/tobymao/sqlglot/commit/91afe4cfd7b3f427e4c0b298075e867b8a1bbe55) - **parser**: TABLESAMPLE pipe syntax *(PR [#5231](https://github.com/tobymao/sqlglot/pull/5231) by [@geooo109](https://github.com/geooo109))*\n- [`62da84a`](https://github.com/tobymao/sqlglot/commit/62da84acce7f44802dca26a9357a16115e21fabf) - **snowflake**: improve transpilation of unnested object lookup *(PR [#5234](https://github.com/tobymao/sqlglot/pull/5234) by [@georgesittas](https://github.com/georgesittas))*\n- [`2c60453`](https://github.com/tobymao/sqlglot/commit/2c604537ba83dee74e9ced7e216673ecc70fe487) - **parser**: DROP pipe syntax *(PR [#5226](https://github.com/tobymao/sqlglot/pull/5226) by [@geooo109](https://github.com/geooo109))*\n- [`9885729`](https://github.com/tobymao/sqlglot/commit/988572954135c68dc021b992c815024ce3debaff) - **parser**: SET pipe syntax *(PR [#5236](https://github.com/tobymao/sqlglot/pull/5236) by [@geooo109](https://github.com/geooo109))*\n- [`e7c217e`](https://github.com/tobymao/sqlglot/commit/e7c217ef08e5811e7dad2b3d26dbaa9f02114e38) - **oracle**: transpile from/to dbms_random.value *(PR [#5242](https://github.com/tobymao/sqlglot/pull/5242) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5241](https://github.com/tobymao/sqlglot/issues/5241) opened by [@Akshat-2512](https://github.com/Akshat-2512)*\n- [`0d19544`](https://github.com/tobymao/sqlglot/commit/0d19544317c1056b17fb089d4be9b5bddfe6feb3) - add Microsoft Fabric dialect, a case sensitive version of TSQL *(PR [#5247](https://github.com/tobymao/sqlglot/pull/5247) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`249dbc9`](https://github.com/tobymao/sqlglot/commit/249dbc906adc6b20932dc8efe83f6f4d23ef8c1e) - **parser**: start with SELECT and nested pipe syntax *(PR [#5248](https://github.com/tobymao/sqlglot/pull/5248) by [@geooo109](https://github.com/geooo109))*\n- [`f5b5b93`](https://github.com/tobymao/sqlglot/commit/f5b5b9338eb92b7aa2c9b4c92c6138c2c05e1c40) - **fabric**: implement type mappings for unsupported Fabric types *(PR [#5249](https://github.com/tobymao/sqlglot/pull/5249) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`78fcea1`](https://github.com/tobymao/sqlglot/commit/78fcea13b5eb1734a15a254875bc80ad8063b0b0) - **spark, databricks**: parse brackets as placeholder *(PR [#5256](https://github.com/tobymao/sqlglot/pull/5256) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5251](https://github.com/tobymao/sqlglot/issues/5251) opened by [@aersam](https://github.com/aersam)*\n- [`7d71387`](https://github.com/tobymao/sqlglot/commit/7d7138780db82e7a75949d29282b944e739ad99d) - **fabric**: Add precision cap to temporal data types *(PR [#5250](https://github.com/tobymao/sqlglot/pull/5250) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`e8cf793`](https://github.com/tobymao/sqlglot/commit/e8cf79305d398f25640ef3c07dd8b32997cb0167) - **duckdb**: Transpile Snowflake's TO_CHAR if format is in Snowflake.TIME_MAPPING *(PR [#5257](https://github.com/tobymao/sqlglot/pull/5257) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5255](https://github.com/tobymao/sqlglot/issues/5255) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`0cdfe64`](https://github.com/tobymao/sqlglot/commit/0cdfe642e3cb996c5ac48cc055af2862340dcf56) - add Exasol dialect (pass 1: string type mapping) *(PR [#5264](https://github.com/tobymao/sqlglot/pull/5264) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`eea1570`](https://github.com/tobymao/sqlglot/commit/eea1570ba530517a95699092ccd9ce6a856f5e84) - **tsql**: add support for SYSDATETIMEOFFSET closes [#5272](https://github.com/tobymao/sqlglot/pull/5272) *(PR [#5273](https://github.com/tobymao/sqlglot/pull/5273) by [@georgesittas](https://github.com/georgesittas))*\n- [`3d3ccc5`](https://github.com/tobymao/sqlglot/commit/3d3ccc52a40536b9ac4e974f1592dffe5a7568f9) - **hive**: Transpile exp.PosExplode pos column alias *(PR [#5274](https://github.com/tobymao/sqlglot/pull/5274) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5271](https://github.com/tobymao/sqlglot/issues/5271) opened by [@charlie-liner](https://github.com/charlie-liner)*\n- [`1c48c09`](https://github.com/tobymao/sqlglot/commit/1c48c09fd836db40bba6c46d0e9969937ce96587) - **exasol**: added datatype mappings and test for exasol dialect. *(PR [#5270](https://github.com/tobymao/sqlglot/pull/5270) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`883fcb1`](https://github.com/tobymao/sqlglot/commit/883fcb137583f6d36f3a70a1343780bb40bf6f81) - **databricks**: GROUP_CONCAT to LISTAGG *(PR [#5284](https://github.com/tobymao/sqlglot/pull/5284) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5281](https://github.com/tobymao/sqlglot/issues/5281) opened by [@wKollendorf](https://github.com/wKollendorf)*\n- [`21ef897`](https://github.com/tobymao/sqlglot/commit/21ef8974426d9f3562ade0bd2c8448bb440bee27) - **fabric**: implement UnixToTime transformation to DATEADD syntax *(PR [#5269](https://github.com/tobymao/sqlglot/pull/5269) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`0ff95c5`](https://github.com/tobymao/sqlglot/commit/0ff95c5903907c9ab30b7850bb3b962bc6da2bab) - add parsing/transpilation support for the REPLACE function *(PR [#5289](https://github.com/tobymao/sqlglot/pull/5289) by [@rahulj51](https://github.com/rahulj51))*\n- [`1b0631c`](https://github.com/tobymao/sqlglot/commit/1b0631c2b4516a9ceb81af6173790dd09269b635) - **exasol**: implemented the Mod function *(PR [#5292](https://github.com/tobymao/sqlglot/pull/5292) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`ba7bf39`](https://github.com/tobymao/sqlglot/commit/ba7bf39966b519e11cde02a3c1f720598469e616) - **exasol**: implemented BIT_AND function with test *(PR [#5294](https://github.com/tobymao/sqlglot/pull/5294) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`fb4122e`](https://github.com/tobymao/sqlglot/commit/fb4122e80d1995bb87401e9ebe3749078c026a06) - **exasol**: add bitwiseOr function to exasol dialect *(PR [#5297](https://github.com/tobymao/sqlglot/pull/5297) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c103b23`](https://github.com/tobymao/sqlglot/commit/c103b2304dca552ac8cf6733156db8b59d3614f3) - add support for `SUBSTRING_INDEX` *(PR [#5296](https://github.com/tobymao/sqlglot/pull/5296) by [@ankur334](https://github.com/ankur334))*\n- [`4752f3a`](https://github.com/tobymao/sqlglot/commit/4752f3a6b715d8b6968c8f1f05f6ccdfb7351071) - **exasol**: added bit_xor built in exasol function to exasol dialect in sqlglot *(PR [#5298](https://github.com/tobymao/sqlglot/pull/5298) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`09bd610`](https://github.com/tobymao/sqlglot/commit/09bd6101de21ed86c9fd6df0f63e8bca2666dd81) - **parser**: annotate type of ARRAY_CONCAT_AGG *(PR [#5299](https://github.com/tobymao/sqlglot/pull/5299) by [@geooo109](https://github.com/geooo109))*\n- [`ad0311a`](https://github.com/tobymao/sqlglot/commit/ad0311a7f8b0b3c5746c29d816b58578a892dd33) - **exasol**: added bit_not exasol built in function. *(PR [#5300](https://github.com/tobymao/sqlglot/pull/5300) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`a7bd823`](https://github.com/tobymao/sqlglot/commit/a7bd8234e0dd02abfe6fa56287e7bda14a549e5a) - **parser**: annotate type of ARRAY_TO_STRING *(PR [#5301](https://github.com/tobymao/sqlglot/pull/5301) by [@geooo109](https://github.com/geooo109))*\n- [`2aa2182`](https://github.com/tobymao/sqlglot/commit/2aa21820f7d3a26cc4f47c1c757a9b7c97dd0382) - **exasol**: added BIT_LSHIFT built in function to exasol dialect *(PR [#5302](https://github.com/tobymao/sqlglot/pull/5302) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c3d9ef2`](https://github.com/tobymao/sqlglot/commit/c3d9ef2cb2d004b57c64af4f3f1bac41f1890737) - **exasol**: added the bit_rshift built in exasol function *(PR [#5304](https://github.com/tobymao/sqlglot/pull/5304) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`6b42353`](https://github.com/tobymao/sqlglot/commit/6b4235340a2e432015c27b2aeadbdcb930bfa6b0) - **parser**: annotate type of ARRAY_FIRST, ARRAY_LAST *(PR [#5303](https://github.com/tobymao/sqlglot/pull/5303) by [@geooo109](https://github.com/geooo109))*\n- [`f5b7cc6`](https://github.com/tobymao/sqlglot/commit/f5b7cc6d2f8d73bff4e42e242d3ad3db41d899cc) - **exasol**: added `EVERY` built in function *(PR [#5305](https://github.com/tobymao/sqlglot/pull/5305) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`d3f04d6`](https://github.com/tobymao/sqlglot/commit/d3f04d6766281ecb7ced9a5e812ab765d7b699be) - add Dremio dialect *(PR [#5277](https://github.com/tobymao/sqlglot/pull/5277) by [@mateuszpoleski](https://github.com/mateuszpoleski))*\n- [`3d8e478`](https://github.com/tobymao/sqlglot/commit/3d8e478eac3df6a94c87cd610f96c5f19697a9bf) - **exasol**: added edit_distance built in function to exasol dialect *(PR [#5310](https://github.com/tobymao/sqlglot/pull/5310) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`db9b61e`](https://github.com/tobymao/sqlglot/commit/db9b61e4ecaa0600418eb90f637fb8b06b08c399) - **parser**: parse, annotate type for ARRAY_REVERSE *(PR [#5306](https://github.com/tobymao/sqlglot/pull/5306) by [@geooo109](https://github.com/geooo109))*\n- [`5612a6d`](https://github.com/tobymao/sqlglot/commit/5612a6da6dee3545f3600db1e5b87c9450952eba) - add support for SPACE *(PR [#5308](https://github.com/tobymao/sqlglot/pull/5308) by [@ankur334](https://github.com/ankur334))*\n- [`f148c9e`](https://github.com/tobymao/sqlglot/commit/f148c9e64ae0d4df96323271729fa6a6ca68a671) - **duckdb**: Transpile Spark's `exp.PosExplode` *(PR [#5311](https://github.com/tobymao/sqlglot/pull/5311) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5309](https://github.com/tobymao/sqlglot/issues/5309) opened by [@nimrodolev](https://github.com/nimrodolev)*\n- [`179a278`](https://github.com/tobymao/sqlglot/commit/179a278c7fdbc29105e37f132e6f03e18627f769) - **exasol**: added the regexp_replace function *(PR [#5313](https://github.com/tobymao/sqlglot/pull/5313) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`8a2f65d`](https://github.com/tobymao/sqlglot/commit/8a2f65d6b2b68ad5ba45a5aed5e56c4dc0fea6fc) - **parser**: parse and annotate type for ARRAY_SLICE *(PR [#5312](https://github.com/tobymao/sqlglot/pull/5312) by [@geooo109](https://github.com/geooo109))*\n- [`d2f7c41`](https://github.com/tobymao/sqlglot/commit/d2f7c41f9f30f4cf0c74782be9be0cc6e75565f3) - add TypeOf / toTypeName support *(PR [#5315](https://github.com/tobymao/sqlglot/pull/5315) by [@ankur334](https://github.com/ankur334))*\n- [`950c15d`](https://github.com/tobymao/sqlglot/commit/950c15db5ff64b6f11036f8003db3e5b1fb3afc3) - **exasol**: add var_pop built in function to exasol dialect *(PR [#5328](https://github.com/tobymao/sqlglot/pull/5328) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`c4ca182`](https://github.com/tobymao/sqlglot/commit/c4ca182ad637b7a22b55d0ecf320c5a09ec5d56c) - **optimizer**: annotate type for FROM_BASE64 *(PR [#5329](https://github.com/tobymao/sqlglot/pull/5329) by [@geooo109](https://github.com/geooo109))*\n- [`0992e99`](https://github.com/tobymao/sqlglot/commit/0992e99f99aeb4ecc97e6918a23b8fd524311ed9) - **exasol**: Add support  APPROXIMATE_COUNT_DISTINCT functions in exasol dialect *(PR [#5330](https://github.com/tobymao/sqlglot/pull/5330) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`7b72bbe`](https://github.com/tobymao/sqlglot/commit/7b72bbed3a0930e11ce4a0fdd9082de715326ac9) - **optimizer**: annotate type for ANY_VALUE *(PR [#5331](https://github.com/tobymao/sqlglot/pull/5331) by [@geooo109](https://github.com/geooo109))*\n- [`c0d57e7`](https://github.com/tobymao/sqlglot/commit/c0d57e747bf5d2bed7ba2007ac2092d5797ee038) - **optimizer**: annotate type for CHR *(PR [#5332](https://github.com/tobymao/sqlglot/pull/5332) by [@geooo109](https://github.com/geooo109))*\n- [`d65b5c2`](https://github.com/tobymao/sqlglot/commit/d65b5c22c29416007cca0154fd35f1d4b5efc929) - **optimizer**: annotate type for COUNTIF *(PR [#5334](https://github.com/tobymao/sqlglot/pull/5334) by [@geooo109](https://github.com/geooo109))*\n- [`521b705`](https://github.com/tobymao/sqlglot/commit/521b7053213df8577f609409af2552c2ff4fd8c9) - **optimizer**: annotate type for GENERATE_ARRAY *(PR [#5335](https://github.com/tobymao/sqlglot/pull/5335) by [@geooo109](https://github.com/geooo109))*\n- [`5fb26c5`](https://github.com/tobymao/sqlglot/commit/5fb26c58026018360f36a732394b612a3baac38b) - **optimizer**: annotate type for INT64 *(PR [#5339](https://github.com/tobymao/sqlglot/pull/5339) by [@geooo109](https://github.com/geooo109))*\n- [`cff9b55`](https://github.com/tobymao/sqlglot/commit/cff9b55d70a3b85057e6385c93c0814eaa50f40b) - **optimizer**: annotate type for LOGICAL_AND and LOGICAL_OR *(PR [#5340](https://github.com/tobymao/sqlglot/pull/5340) by [@geooo109](https://github.com/geooo109))*\n- [`b94a6f9`](https://github.com/tobymao/sqlglot/commit/b94a6f9228aa730296c3152179bfbf3503521063) - **optimizer**: annotate type for MAKE_INTERVAL *(PR [#5341](https://github.com/tobymao/sqlglot/pull/5341) by [@geooo109](https://github.com/geooo109))*\n- [`2c9a7c6`](https://github.com/tobymao/sqlglot/commit/2c9a7c6f0b097a9e8514fc5e2af21c52f145920c) - **optimizer**: annotate type for LAST_VALUE *(PR [#5336](https://github.com/tobymao/sqlglot/pull/5336) by [@geooo109](https://github.com/geooo109))*\n- [`d862a28`](https://github.com/tobymao/sqlglot/commit/d862a28b0a30f0c5774351f38a61f195120ad904) - **optimizer**: annoate type for TO_BASE64 *(PR [#5342](https://github.com/tobymao/sqlglot/pull/5342) by [@geooo109](https://github.com/geooo109))*\n- [`85888c1`](https://github.com/tobymao/sqlglot/commit/85888c1b7cbbd0eee179d902a54fbd2a899cc16b) - **optimizer**: annotate type for UNIX_DATE *(PR [#5343](https://github.com/tobymao/sqlglot/pull/5343) by [@geooo109](https://github.com/geooo109))*\n- [`8a214e0`](https://github.com/tobymao/sqlglot/commit/8a214e0859dfb715fcef0dd6b2d6392012b1f3fb) - **optimizer**: annotate type for UNIX_SECONDS *(PR [#5344](https://github.com/tobymao/sqlglot/pull/5344) by [@geooo109](https://github.com/geooo109))*\n- [`625cb74`](https://github.com/tobymao/sqlglot/commit/625cb74b69e99ea1a707549366ea960d759848c9) - **optimizer**: annotate type for STARTS_WITH *(PR [#5345](https://github.com/tobymao/sqlglot/pull/5345) by [@geooo109](https://github.com/geooo109))*\n- [`0337c4d`](https://github.com/tobymao/sqlglot/commit/0337c4d46e9e85d951fc9565a47e338106543711) - **optimizer**: annotate type for SHA and SHA2 *(PR [#5346](https://github.com/tobymao/sqlglot/pull/5346) by [@geooo109](https://github.com/geooo109))*\n- [`835d9e6`](https://github.com/tobymao/sqlglot/commit/835d9e6c9ffc05de642113b566a1a4eb9cc38470) - add case-insensitive uppercase normalization strategy *(PR [#5349](https://github.com/tobymao/sqlglot/pull/5349) by [@georgesittas](https://github.com/georgesittas))*\n- [`f80493e`](https://github.com/tobymao/sqlglot/commit/f80493efb168f600dc92da439d84e820f303e5aa) - **exasol**: Add TO_CHAR function support in exasol dialect *(PR [#5350](https://github.com/tobymao/sqlglot/pull/5350) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`cea6a24`](https://github.com/tobymao/sqlglot/commit/cea6a240292d6e31bc73179d433835483e65747a) - **teradata**: add FORMAT phrase parsing *(PR [#5348](https://github.com/tobymao/sqlglot/pull/5348) by [@readjfb](https://github.com/readjfb))*\n- [`eae64e1`](https://github.com/tobymao/sqlglot/commit/eae64e1629a276bf3885749991869b6c6dea8a8b) - **duckdb**: support new lambda syntax *(PR [#5359](https://github.com/tobymao/sqlglot/pull/5359) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5357](https://github.com/tobymao/sqlglot/issues/5357) opened by [@aersam](https://github.com/aersam)*\n- [`e77991d`](https://github.com/tobymao/sqlglot/commit/e77991d92fad56014ba2778c71e5e446d4dd090e) - **duckdb**: Add support for SET VARIABLE *(PR [#5360](https://github.com/tobymao/sqlglot/pull/5360) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5356](https://github.com/tobymao/sqlglot/issues/5356) opened by [@aersam](https://github.com/aersam)*\n- [`c1d3d61`](https://github.com/tobymao/sqlglot/commit/c1d3d61d00f00d2030107689d8704f7a488a80a7) - **optimizer**: annotate type for CORR *(PR [#5364](https://github.com/tobymao/sqlglot/pull/5364) by [@geooo109](https://github.com/geooo109))*\n- [`c1e8677`](https://github.com/tobymao/sqlglot/commit/c1e867767a006e774a2c200c10eb85b3fbd8a372) - **optimizer**: annotate type for COVAR_POP *(PR [#5365](https://github.com/tobymao/sqlglot/pull/5365) by [@geooo109](https://github.com/geooo109))*\n- [`e110ef4`](https://github.com/tobymao/sqlglot/commit/e110ef4f774e6ab8de6d4c86e5d306ab53fe895b) - **optimizer**: annotate type for COVAR_SAMP *(PR [#5367](https://github.com/tobymao/sqlglot/pull/5367) by [@geooo109](https://github.com/geooo109))*\n- [`5b59c16`](https://github.com/tobymao/sqlglot/commit/5b59c16528fb1904c64bef0ca6307bb6a95e5a2c) - **optimizer**: annotate type for DATETIME *(PR [#5369](https://github.com/tobymao/sqlglot/pull/5369) by [@geooo109](https://github.com/geooo109))*\n- [`47176ce`](https://github.com/tobymao/sqlglot/commit/47176ce6b9a4c1722f285034b08a6ae782129894) - **optimizer**: annotate type for ENDS_WITH *(PR [#5370](https://github.com/tobymao/sqlglot/pull/5370) by [@geooo109](https://github.com/geooo109))*\n- [`1fd757e`](https://github.com/tobymao/sqlglot/commit/1fd757e6279315f00e719974613313a6e43dfe55) - **fabric**: Ensure TIMESTAMPTZ is used with AT TIME ZONE *(PR [#5362](https://github.com/tobymao/sqlglot/pull/5362) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`2cce53d`](https://github.com/tobymao/sqlglot/commit/2cce53d59968f0a4bb3e9599ade93b0e6a140c68) - **optimizer**: annotate type for LAG *(PR [#5371](https://github.com/tobymao/sqlglot/pull/5371) by [@geooo109](https://github.com/geooo109))*\n- [`a3227de`](https://github.com/tobymao/sqlglot/commit/a3227de3fc57d559eb899dec08af01f85b470ce4) - improve transpilation of `ROUND(x, y)` to Postgres *(PR [#5368](https://github.com/tobymao/sqlglot/pull/5368) by [@blecourt-private](https://github.com/blecourt-private))*\n  - :arrow_lower_right: *addresses issue [#5366](https://github.com/tobymao/sqlglot/issues/5366) opened by [@blecourt-private](https://github.com/blecourt-private)*\n\n### :bug: Bug Fixes\n- [`f2bf000`](https://github.com/tobymao/sqlglot/commit/f2bf000a410fb18531bb90ef1d767baf0e8bce7a) - **optimizer**: avoid creating new alias for qualifying unpivot *(PR [#5121](https://github.com/tobymao/sqlglot/pull/5121) by [@geooo109](https://github.com/geooo109))*\n- [`a126ce8`](https://github.com/tobymao/sqlglot/commit/a126ce8a25287cf3531d815035fa3d567dc772fb) - **optimizer**: make coalesce simplification optional, skip by default *(PR [#5123](https://github.com/tobymao/sqlglot/pull/5123) by [@barakalon](https://github.com/barakalon))*\n- [`f7401fd`](https://github.com/tobymao/sqlglot/commit/f7401fdc29a35738eb23f424ceba03463a4d8af9) - **bigquery**: avoid getting stuck in infinite loop when parsing tables *(PR [#5130](https://github.com/tobymao/sqlglot/pull/5130) by [@georgesittas](https://github.com/georgesittas))*\n- [`e9b3156`](https://github.com/tobymao/sqlglot/commit/e9b3156aa1ed95fdee4c6b419134d8ca746964b6) - **athena**: Handle transpilation of FileFormatProperty from dialects that treat it as a variable and not a string literal *(PR [#5133](https://github.com/tobymao/sqlglot/pull/5133) by [@erindru](https://github.com/erindru))*\n- [`a3fccd9`](https://github.com/tobymao/sqlglot/commit/a3fccd9be294499b53477da931f8b097cdbe09fc) - **snowflake**: generate SELECT for UNNEST without JOIN or FROM *(PR [#5138](https://github.com/tobymao/sqlglot/pull/5138) by [@geooo109](https://github.com/geooo109))*\n- [`993919d`](https://github.com/tobymao/sqlglot/commit/993919d05d5d3c814471607b56831bb65d349eb4) - **snowflake**: Properly transpile ARRAY_AGG, IGNORE/RESPECT NULLS *(PR [#5137](https://github.com/tobymao/sqlglot/pull/5137) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`6e57619`](https://github.com/tobymao/sqlglot/commit/6e57619f85375e789bb39a6478aa01cd7c7758f0) - **snowflake**: Transpile ISOWEEK to WEEKISO *(PR [#5139](https://github.com/tobymao/sqlglot/pull/5139) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`c484ca3`](https://github.com/tobymao/sqlglot/commit/c484ca39bad750a96b62e2edae85612cac66ba30) - **bigquery**: recognize ARRAY_CONCAT_AGG as an aggregate function *(PR [#5141](https://github.com/tobymao/sqlglot/pull/5141) by [@georgesittas](https://github.com/georgesittas))*\n- [`f3aeb37`](https://github.com/tobymao/sqlglot/commit/f3aeb374351a0b1b3c75945718d8ea42f8926b62) - **tsql**: properly parse and generate ALTER SET *(PR [#5143](https://github.com/tobymao/sqlglot/pull/5143) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5135](https://github.com/tobymao/sqlglot/issues/5135) opened by [@codykonior](https://github.com/codykonior)*\n- [`72ce404`](https://github.com/tobymao/sqlglot/commit/72ce40405625239a0d6763d502e5af8b12abfe9b) - Refactor ALTER TABLE ADD parsing *(PR [#5144](https://github.com/tobymao/sqlglot/pull/5144) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5129](https://github.com/tobymao/sqlglot/issues/5129) opened by [@Mevrael](https://github.com/Mevrael)*\n- [`e73ddb7`](https://github.com/tobymao/sqlglot/commit/e73ddb733b7f120ae74054e6d4dc7d458f59ac50) - **mysql**: preserve TIMESTAMP on roundtrip *(PR [#5145](https://github.com/tobymao/sqlglot/pull/5145) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5127](https://github.com/tobymao/sqlglot/issues/5127) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`4f8c73d`](https://github.com/tobymao/sqlglot/commit/4f8c73d60eecebc601c60ee8c7819458435e34b8) - **hive**: STRUCT column names and data type should be separated by ':' in hive *(PR [#5147](https://github.com/tobymao/sqlglot/pull/5147) by [@tsamaras](https://github.com/tsamaras))*\n- [`e2a488f`](https://github.com/tobymao/sqlglot/commit/e2a488f48f3e036566462463bbc58cc6a1c7492e) - Error on columns mismatch in pushdown_projections ignores dialect *(PR [#5151](https://github.com/tobymao/sqlglot/pull/5151) by [@snovik75](https://github.com/snovik75))*\n- [`1a35365`](https://github.com/tobymao/sqlglot/commit/1a35365a3bb1ef56e8da0023271cbe3108e0ccb1) - avoid generating nested comments when not supported *(PR [#5158](https://github.com/tobymao/sqlglot/pull/5158) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5132](https://github.com/tobymao/sqlglot/issues/5132) opened by [@patricksurry](https://github.com/patricksurry)*\n- [`f6124c6`](https://github.com/tobymao/sqlglot/commit/f6124c6343f67563fc19f617891ecfc145a642db) - **rust-tokenizer**: return token vector in `tokenize` even on failure *(PR [#5155](https://github.com/tobymao/sqlglot/pull/5155) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5148](https://github.com/tobymao/sqlglot/issues/5148) opened by [@kamoser](https://github.com/kamoser)*\n- [`760a606`](https://github.com/tobymao/sqlglot/commit/760a6062d5f259488e471af9c1d33e200066e9dc) - **postgres**: support decimal values in INTERVAL expressions fixes [#5168](https://github.com/tobymao/sqlglot/pull/5168) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6a2cb39`](https://github.com/tobymao/sqlglot/commit/6a2cb39d0ceec091dc4fc228f26d4f457729a3cf) - **parser**: virtual column with AS(expr) as ComputedColumnConstraint *(PR [#5180](https://github.com/tobymao/sqlglot/pull/5180) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5173](https://github.com/tobymao/sqlglot/issues/5173) opened by [@suyah](https://github.com/suyah)*\n- [`c87ae02`](https://github.com/tobymao/sqlglot/commit/c87ae02aa263be8463ca7283ebd090385a4bfd59) - **sqlite**: Add REPLACE to command tokens *(PR [#5192](https://github.com/tobymao/sqlglot/pull/5192) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5187](https://github.com/tobymao/sqlglot/issues/5187) opened by [@stefanmalanik](https://github.com/stefanmalanik)*\n- [`4b89afd`](https://github.com/tobymao/sqlglot/commit/4b89afdcc0063e70cbc64165c7f1f5102afaa87c) - **starrocks**: array_agg_transpilation_fix *(PR [#5190](https://github.com/tobymao/sqlglot/pull/5190) by [@Swathiraj23](https://github.com/Swathiraj23))*\n- [`461b054`](https://github.com/tobymao/sqlglot/commit/461b0548832ab8d916c3a6638f27a49f681109fe) - **postgres**: support use_spheroid argument in ST_DISTANCE *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`99bbae3`](https://github.com/tobymao/sqlglot/commit/99bbae370329c5f5cd132b711c714359cf96ba58) - **sqlite**: allow ALTER RENAME without COLUMN keyword fixes [#5195](https://github.com/tobymao/sqlglot/pull/5195) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ac6555b`](https://github.com/tobymao/sqlglot/commit/ac6555b4d6c162ef7b14b63307d01fd560138ea0) - **hive**: preserve DIV binary operator, fixes [#5198](https://github.com/tobymao/sqlglot/pull/5198) *(PR [#5199](https://github.com/tobymao/sqlglot/pull/5199) by [@georgesittas](https://github.com/georgesittas))*\n- [`d0eeb26`](https://github.com/tobymao/sqlglot/commit/d0eeb2639e771e8f8b6feabd41c65f16ed5a9829) - eliminate_join_marks has multiple issues fixes [#5188](https://github.com/tobymao/sqlglot/pull/5188) *(PR [#5189](https://github.com/tobymao/sqlglot/pull/5189) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#5188](https://github.com/tobymao/sqlglot/issues/5188) opened by [@snovik75](https://github.com/snovik75)*\n- [`dfdd84b`](https://github.com/tobymao/sqlglot/commit/dfdd84bbc50da70f40a17b39935f8171d961f7d2) - **parser**: CTEs instead of subqueries for pipe syntax *(PR [#5205](https://github.com/tobymao/sqlglot/pull/5205) by [@geooo109](https://github.com/geooo109))*\n- [`77e9d9a`](https://github.com/tobymao/sqlglot/commit/77e9d9a0269e2013379967cf2f46fbd79c036277) - **mysql**: properly parse STORED/VIRTUAL computed columns *(PR [#5210](https://github.com/tobymao/sqlglot/pull/5210) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5203](https://github.com/tobymao/sqlglot/issues/5203) opened by [@mdebski](https://github.com/mdebski)*\n- [`5f95299`](https://github.com/tobymao/sqlglot/commit/5f9529940d83e89704f7d25eda63cd73fdb503ae) - **parser**: support multi-part (>3) dotted functions *(PR [#5211](https://github.com/tobymao/sqlglot/pull/5211) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5200](https://github.com/tobymao/sqlglot/issues/5200) opened by [@mateuszpoleski](https://github.com/mateuszpoleski)*\n- [`02afa2a`](https://github.com/tobymao/sqlglot/commit/02afa2a1941fc67086d50dffac2857262f1c3c4f) - **postgres**: Preserve quoting for UDT *(PR [#5216](https://github.com/tobymao/sqlglot/pull/5216) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5212](https://github.com/tobymao/sqlglot/issues/5212) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`f37c0b1`](https://github.com/tobymao/sqlglot/commit/f37c0b1197321dd610648ce652a171ab063deeeb) - **snowflake**: ensure a standalone GET() expression can be parsed *(PR [#5219](https://github.com/tobymao/sqlglot/pull/5219) by [@georgesittas](https://github.com/georgesittas))*\n- [`28fed58`](https://github.com/tobymao/sqlglot/commit/28fed586a39df83aade4792217743a1a859fd039) - **optimizer**: UnboundLocalError in scope module *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`809e05a`](https://github.com/tobymao/sqlglot/commit/809e05a743d5a2904a1d6f6813f24ca7549ac7ef) - **snowflake**: preserve STRTOK_TO_ARRAY roundtrip *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`df73a79`](https://github.com/tobymao/sqlglot/commit/df73a79a2ca3ba859b8aba5e3d0f6ed269874a63) - **tsql**: Retain limit clause in subquery expression. *(PR [#5227](https://github.com/tobymao/sqlglot/pull/5227) by [@MarcusRisanger](https://github.com/MarcusRisanger))*\n- [`4f42d95`](https://github.com/tobymao/sqlglot/commit/4f42d951363f8c43a4c414dc21d0505d9c8e48bf) - **duckdb**: Normalize date parts in `exp.Extract` generation *(PR [#5229](https://github.com/tobymao/sqlglot/pull/5229) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5228](https://github.com/tobymao/sqlglot/issues/5228) opened by [@greybeam-bot](https://github.com/greybeam-bot)*\n- [`1b4c083`](https://github.com/tobymao/sqlglot/commit/1b4c083fff8d7c44bf1dbba28c1225fa1e28c4d2) - **athena**: include Hive string escapes in the tokenizer *(PR [#5233](https://github.com/tobymao/sqlglot/pull/5233) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5232](https://github.com/tobymao/sqlglot/issues/5232) opened by [@ligfx](https://github.com/ligfx)*\n- [`e7e38fe`](https://github.com/tobymao/sqlglot/commit/e7e38fe0e09f9affbff4ffa7023d0161e3a1ee49) - **optimizer**: resolve table \"columns\" in bigquery that produce structs *(PR [#5230](https://github.com/tobymao/sqlglot/pull/5230) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5207](https://github.com/tobymao/sqlglot/issues/5207) opened by [@Bladieblah](https://github.com/Bladieblah)*\n- [`781539d`](https://github.com/tobymao/sqlglot/commit/781539d5cbe58142ed6688f1522fc4ed31da0a56) - **duckdb**: Generate correct DETACH syntax if IF EXISTS is set *(PR [#5235](https://github.com/tobymao/sqlglot/pull/5235) by [@erindru](https://github.com/erindru))*\n- [`d3dc761`](https://github.com/tobymao/sqlglot/commit/d3dc761393146357a5d20c4d7992fd2a1ae5e6e2) - change comma to cross join when precedence is the same for all join types *(PR [#5240](https://github.com/tobymao/sqlglot/pull/5240) by [@georgesittas](https://github.com/georgesittas))*\n- [`31814cd`](https://github.com/tobymao/sqlglot/commit/31814cddb0cf65caf29fbc45a31a9c865b7991c7) - **presto**: cast constructed timestamp literal to zone-aware type if needed *(PR [#5253](https://github.com/tobymao/sqlglot/pull/5253) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5252](https://github.com/tobymao/sqlglot/issues/5252) opened by [@agni-sairent](https://github.com/agni-sairent)*\n- [`847248d`](https://github.com/tobymao/sqlglot/commit/847248dd1b66e3a8f60c23a4488be85dfdef4113) - format ADD CONSTRAINT clause properly fixes [#5260](https://github.com/tobymao/sqlglot/pull/5260) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`db4e0ec`](https://github.com/tobymao/sqlglot/commit/db4e0ece950a6a1f543d8ecad48a7d4b1d6872be) - **tsql**: convert information schema keywords to uppercase for consistency *(PR [#5263](https://github.com/tobymao/sqlglot/pull/5263) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`8de87e3`](https://github.com/tobymao/sqlglot/commit/8de87e3f755a40b600aa94ee2c30cf697ef7c43c) - **redshift**: handle scale parameter in to_timestamp *(PR [#5266](https://github.com/tobymao/sqlglot/pull/5266) by [@MatiasCasaliSplit](https://github.com/MatiasCasaliSplit))*\n- [`e32f709`](https://github.com/tobymao/sqlglot/commit/e32f70992b5058efb93f5d2b6106fb00b810f576) - **hive**: Fix exp.PosExplode alias order *(PR [#5279](https://github.com/tobymao/sqlglot/pull/5279) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`3dd9f8e`](https://github.com/tobymao/sqlglot/commit/3dd9f8e78ecbfde0dd7fc6fefcc09c8cb99bcd7b) - **fabric**: Type mismatches and precision error *(PR [#5280](https://github.com/tobymao/sqlglot/pull/5280) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`9a95af1`](https://github.com/tobymao/sqlglot/commit/9a95af1c725cd70ffa8206f1d88452a7faab93b2) - **snowflake**: only cast strings to timestamp for TO_CHAR (TimeToStr) *(PR [#5283](https://github.com/tobymao/sqlglot/pull/5283) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5282](https://github.com/tobymao/sqlglot/issues/5282) opened by [@wedotech-ashley](https://github.com/wedotech-ashley)*\n- [`8af4790`](https://github.com/tobymao/sqlglot/commit/8af479017ccde16049c897ae5d322d4a69843b65) - **tsql**: Fix parsing of ADD CONSTRAINT *(PR [#5288](https://github.com/tobymao/sqlglot/pull/5288) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4813](https://github.com/TobikoData/sqlmesh/issues/4813) opened by [@bnstewrt](https://github.com/bnstewrt)*\n- [`18aea08`](https://github.com/tobymao/sqlglot/commit/18aea08f7dcaa887bcf29886cd3b3bc2850a3679) - **scope**: include bigquery unnest aliases in selected sources *(PR [#5285](https://github.com/tobymao/sqlglot/pull/5285) by [@georgesittas](https://github.com/georgesittas))*\n- [`ba4a234`](https://github.com/tobymao/sqlglot/commit/ba4a234bfabdd8161b96a29436a50e0eb04c2dc2) - **fabric**: ignore Date cap *(PR [#5290](https://github.com/tobymao/sqlglot/pull/5290) by [@fresioAS](https://github.com/fresioAS))*\n- [`dc03649`](https://github.com/tobymao/sqlglot/commit/dc03649bca0b7a090254976182a03c21dd2269ba) - **bigquery**: only coerce time var -like units into strings for DATE_TRUNC *(PR [#5291](https://github.com/tobymao/sqlglot/pull/5291) by [@georgesittas](https://github.com/georgesittas))*\n- [`5724538`](https://github.com/tobymao/sqlglot/commit/5724538f278b2178114b88850251afd7c3db0dda) - **bigquery**: ARRAY_CONCAT type annotation *(PR [#5293](https://github.com/tobymao/sqlglot/pull/5293) by [@geooo109](https://github.com/geooo109))*\n- [`0a6afcd`](https://github.com/tobymao/sqlglot/commit/0a6afcd90c663aaef9b385fc12ccd19dbf6388cc) - use re-entrant lock in dialects/__init__ to avoid deadlocks *(PR [#5322](https://github.com/tobymao/sqlglot/pull/5322) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5321](https://github.com/tobymao/sqlglot/issues/5321) opened by [@jc-5s](https://github.com/jc-5s)*\n- [`599ca81`](https://github.com/tobymao/sqlglot/commit/599ca8101f48805098cbdf808ac2923a8246066b) - **parser**: avoid CTE values ALIAS gen, when ALIAS exists *(PR [#5323](https://github.com/tobymao/sqlglot/pull/5323) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5318](https://github.com/tobymao/sqlglot/issues/5318) opened by [@ankur334](https://github.com/ankur334)*\n- [`5a0f589`](https://github.com/tobymao/sqlglot/commit/5a0f589a0fdb6743c3be2f98b74a34780f51332b) - **spark**: distinguish STORED AS from USING *(PR [#5320](https://github.com/tobymao/sqlglot/pull/5320) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5317](https://github.com/tobymao/sqlglot/issues/5317) opened by [@cosinequanon](https://github.com/cosinequanon)*\n- [`cbc79c2`](https://github.com/tobymao/sqlglot/commit/cbc79c2a47c46370de0378b8bae61f4f3c17ca82) - preserve ORDER BY comments fixes [#5326](https://github.com/tobymao/sqlglot/pull/5326) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`fa69583`](https://github.com/tobymao/sqlglot/commit/fa69583d8b4f5801d05c21a92b43dea272a3ef49) - **optimizer**: avoid qualifying CTE *(PR [#5327](https://github.com/tobymao/sqlglot/pull/5327) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5319](https://github.com/tobymao/sqlglot/issues/5319) opened by [@naamamaoz](https://github.com/naamamaoz)*\n- [`29cce43`](https://github.com/tobymao/sqlglot/commit/29cce43e72451feeb8788ac2660658075bf59093) - comment lost before GROUP, JOIN and HAVING *(PR [#5338](https://github.com/tobymao/sqlglot/pull/5338) by [@chiiips](https://github.com/chiiips))*\n- [`509b741`](https://github.com/tobymao/sqlglot/commit/509b74173f678842e7550c75c4d8d906c879fb12) - preserve multi-arg DECODE function instead of converting to CASE *(PR [#5352](https://github.com/tobymao/sqlglot/pull/5352) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5351](https://github.com/tobymao/sqlglot/issues/5351) opened by [@kentmaxwell](https://github.com/kentmaxwell)*\n- [`188d446`](https://github.com/tobymao/sqlglot/commit/188d446ca65125c63bbfff96d15d91078deb6b4a) - **optimizer**: downstream column for PIVOT *(PR [#5363](https://github.com/tobymao/sqlglot/pull/5363) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5354](https://github.com/tobymao/sqlglot/issues/5354) opened by [@suresh-summation](https://github.com/suresh-summation)*\n\n### :recycle: Refactors\n- [`86c6b90`](https://github.com/tobymao/sqlglot/commit/86c6b90d21b204b4376639affa142e8cee509065) - **tsql**: XML_OPTIONS *(commit by [@geooo109](https://github.com/geooo109))*\n- [`aac70aa`](https://github.com/tobymao/sqlglot/commit/aac70aaaa8d840c267129e2307ccb65058cef0c9) - **parser**: simpler _parse_pipe_syntax_select *(commit by [@geooo109](https://github.com/geooo109))*\n- [`8d118ea`](https://github.com/tobymao/sqlglot/commit/8d118ead9c15e7b2b4b51b7cf93cab94e61c2625) - **athena**: route statements to hive/trino depending on their type *(PR [#5314](https://github.com/tobymao/sqlglot/pull/5314) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5267](https://github.com/tobymao/sqlglot/issues/5267) opened by [@cpcloud](https://github.com/cpcloud)*\n\n### :wrench: Chores\n- [`6910744`](https://github.com/tobymao/sqlglot/commit/6910744e6260793b3f9190782cf60fbbd9adcd38) - update py03 version *(PR [#5136](https://github.com/tobymao/sqlglot/pull/5136) by [@benfdking](https://github.com/benfdking))*\n  - :arrow_lower_right: *addresses issue [#5134](https://github.com/tobymao/sqlglot/issues/5134) opened by [@mgorny](https://github.com/mgorny)*\n- [`a56deab`](https://github.com/tobymao/sqlglot/commit/a56deabc2b9543209fb5e41f19c3bef89177a577) - bump sqlglotrs to 0.5.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`5752a87`](https://github.com/tobymao/sqlglot/commit/5752a87406b736317e4dc5cce9ae05cbc5c19547) - udpate benchmarking framework *(PR [#5146](https://github.com/tobymao/sqlglot/pull/5146) by [@benfdking](https://github.com/benfdking))*\n- [`0ae297a`](https://github.com/tobymao/sqlglot/commit/0ae297a01262cf323e225fe578bdeab2230c6fd5) - compare performance on main vs pr branch *(PR [#5149](https://github.com/tobymao/sqlglot/pull/5149) by [@georgesittas](https://github.com/georgesittas))*\n- [`180963b`](https://github.com/tobymao/sqlglot/commit/180963b8cf25d9ff83d2347859b7f46398af5000) - handle pipe syntax unsupported operators more gracefully *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6c8d61a`](https://github.com/tobymao/sqlglot/commit/6c8d61ae1ef5b645835ccd683063845dd801e8d2) - include optimization benchmarks *(PR [#5152](https://github.com/tobymao/sqlglot/pull/5152) by [@georgesittas](https://github.com/georgesittas))*\n- [`bc5c66c`](https://github.com/tobymao/sqlglot/commit/bc5c66c9210a472147d98a94c34b4bb582ade8b1) - Run benchmark job if /benchmark comment *(PR [#5164](https://github.com/tobymao/sqlglot/pull/5164) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`742b2b7`](https://github.com/tobymao/sqlglot/commit/742b2b770b88a2e901d2f84af00db821da441e4c) - Fix benchmark CI to include issue number *(PR [#5166](https://github.com/tobymao/sqlglot/pull/5166) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`64c37f1`](https://github.com/tobymao/sqlglot/commit/64c37f147366fe87ae187996ecb3c9a5afa7c264) - bump sqlglotrs to 0.6.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`440590b`](https://github.com/tobymao/sqlglot/commit/440590bf92ab1281f50b96a1400cbca695d40f0c) - bump sqlglotrs to 0.6.1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`83de4e1`](https://github.com/tobymao/sqlglot/commit/83de4e11bc1547aa22b275b20c0326dfbe43b2b8) - improve benchmark result displaying *(PR [#5176](https://github.com/tobymao/sqlglot/pull/5176) by [@georgesittas](https://github.com/georgesittas))*\n- [`5d5dc2f`](https://github.com/tobymao/sqlglot/commit/5d5dc2fa471bd53730e03ac8039804221949f843) - Clean up exp.ArrayIntersect PR *(PR [#5193](https://github.com/tobymao/sqlglot/pull/5193) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ad8a4e7`](https://github.com/tobymao/sqlglot/commit/ad8a4e73e1a9e4234f0b711163fb49630acf736c) - refactor join mark elimination to use is_correlated_subquery *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7dfb578`](https://github.com/tobymao/sqlglot/commit/7dfb5780fb242c82744dc1538077776ac624081e) - Refactor DETACH generation *(PR [#5237](https://github.com/tobymao/sqlglot/pull/5237) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`cc389fa`](https://github.com/tobymao/sqlglot/commit/cc389facb33f94a0d1f696f2ef9e92f298711894) - **optimizer**: annotate type SHA1, SHA256, SHA512 for BigQuery *(PR [#5347](https://github.com/tobymao/sqlglot/pull/5347) by [@geooo109](https://github.com/geooo109))*\n- [`194850a`](https://github.com/tobymao/sqlglot/commit/194850a52497300a8f1d47f2306b67cdd11ffab6) - **exasol**: clean up TO_CHAR *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1abd461`](https://github.com/tobymao/sqlglot/commit/1abd461295830807c52f24d25ac6938095f54831) - bump min. supported version to python 3.9 *(PR [#5353](https://github.com/tobymao/sqlglot/pull/5353) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.33.0] - 2025-07-01\n### :boom: BREAKING CHANGES\n- due to [`d2f7c41`](https://github.com/tobymao/sqlglot/commit/d2f7c41f9f30f4cf0c74782be9be0cc6e75565f3) - add TypeOf / toTypeName support *(PR [#5315](https://github.com/tobymao/sqlglot/pull/5315) by [@ankur334](https://github.com/ankur334))*:\n\n  add TypeOf / toTypeName support (#5315)\n\n\n### :sparkles: New Features\n- [`d2f7c41`](https://github.com/tobymao/sqlglot/commit/d2f7c41f9f30f4cf0c74782be9be0cc6e75565f3) - add TypeOf / toTypeName support *(PR [#5315](https://github.com/tobymao/sqlglot/pull/5315) by [@ankur334](https://github.com/ankur334))*\n\n### :bug: Bug Fixes\n- [`0a6afcd`](https://github.com/tobymao/sqlglot/commit/0a6afcd90c663aaef9b385fc12ccd19dbf6388cc) - use re-entrant lock in dialects/__init__ to avoid deadlocks *(PR [#5322](https://github.com/tobymao/sqlglot/pull/5322) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5321](https://github.com/tobymao/sqlglot/issues/5321) opened by [@jc-5s](https://github.com/jc-5s)*\n- [`599ca81`](https://github.com/tobymao/sqlglot/commit/599ca8101f48805098cbdf808ac2923a8246066b) - **parser**: avoid CTE values ALIAS gen, when ALIAS exists *(PR [#5323](https://github.com/tobymao/sqlglot/pull/5323) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5318](https://github.com/tobymao/sqlglot/issues/5318) opened by [@ankur334](https://github.com/ankur334)*\n\n\n## [v26.31.0] - 2025-06-26\n### :boom: BREAKING CHANGES\n- due to [`f2bf000`](https://github.com/tobymao/sqlglot/commit/f2bf000a410fb18531bb90ef1d767baf0e8bce7a) - avoid creating new alias for qualifying unpivot *(PR [#5121](https://github.com/tobymao/sqlglot/pull/5121) by [@geooo109](https://github.com/geooo109))*:\n\n  avoid creating new alias for qualifying unpivot (#5121)\n\n- due to [`a126ce8`](https://github.com/tobymao/sqlglot/commit/a126ce8a25287cf3531d815035fa3d567dc772fb) - make coalesce simplification optional, skip by default *(PR [#5123](https://github.com/tobymao/sqlglot/pull/5123) by [@barakalon](https://github.com/barakalon))*:\n\n  make coalesce simplification optional, skip by default (#5123)\n\n- due to [`6910744`](https://github.com/tobymao/sqlglot/commit/6910744e6260793b3f9190782cf60fbbd9adcd38) - update py03 version *(PR [#5136](https://github.com/tobymao/sqlglot/pull/5136) by [@benfdking](https://github.com/benfdking))*:\n\n  update py03 version (#5136)\n\n- due to [`a56deab`](https://github.com/tobymao/sqlglot/commit/a56deabc2b9543209fb5e41f19c3bef89177a577) - bump sqlglotrs to 0.5.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.5.0\n\n- due to [`c484ca3`](https://github.com/tobymao/sqlglot/commit/c484ca39bad750a96b62e2edae85612cac66ba30) - recognize ARRAY_CONCAT_AGG as an aggregate function *(PR [#5141](https://github.com/tobymao/sqlglot/pull/5141) by [@georgesittas](https://github.com/georgesittas))*:\n\n  recognize ARRAY_CONCAT_AGG as an aggregate function (#5141)\n\n- due to [`72ce404`](https://github.com/tobymao/sqlglot/commit/72ce40405625239a0d6763d502e5af8b12abfe9b) - Refactor ALTER TABLE ADD parsing *(PR [#5144](https://github.com/tobymao/sqlglot/pull/5144) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Refactor ALTER TABLE ADD parsing (#5144)\n\n- due to [`e73ddb7`](https://github.com/tobymao/sqlglot/commit/e73ddb733b7f120ae74054e6d4dc7d458f59ac50) - preserve TIMESTAMP on roundtrip *(PR [#5145](https://github.com/tobymao/sqlglot/pull/5145) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve TIMESTAMP on roundtrip (#5145)\n\n- due to [`f6124c6`](https://github.com/tobymao/sqlglot/commit/f6124c6343f67563fc19f617891ecfc145a642db) - return token vector in `tokenize` even on failure *(PR [#5155](https://github.com/tobymao/sqlglot/pull/5155) by [@georgesittas](https://github.com/georgesittas))*:\n\n  return token vector in `tokenize` even on failure (#5155)\n\n- due to [`64c37f1`](https://github.com/tobymao/sqlglot/commit/64c37f147366fe87ae187996ecb3c9a5afa7c264) - bump sqlglotrs to 0.6.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.6.0\n\n- due to [`434c45b`](https://github.com/tobymao/sqlglot/commit/434c45b547c3a5ea155dc8d7da2baab326eb6d4f) - improve support for ENDSWITH closes [#5170](https://github.com/tobymao/sqlglot/pull/5170) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve support for ENDSWITH closes #5170\n\n- due to [`bc001ce`](https://github.com/tobymao/sqlglot/commit/bc001cef4c907d8fa421d3190b4fa91865d9ff6c) - Add support for ANY_VALUE for versions 16+ *(PR [#5179](https://github.com/tobymao/sqlglot/pull/5179) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for ANY_VALUE for versions 16+ (#5179)\n\n- due to [`6a2cb39`](https://github.com/tobymao/sqlglot/commit/6a2cb39d0ceec091dc4fc228f26d4f457729a3cf) - virtual column with AS(expr) as ComputedColumnConstraint *(PR [#5180](https://github.com/tobymao/sqlglot/pull/5180) by [@geooo109](https://github.com/geooo109))*:\n\n  virtual column with AS(expr) as ComputedColumnConstraint (#5180)\n\n- due to [`29e2f1d`](https://github.com/tobymao/sqlglot/commit/29e2f1d89c095c9fab0944a6962c99bd745c2c91) - Array_intersection transpilation support *(PR [#5186](https://github.com/tobymao/sqlglot/pull/5186) by [@HarishRavi96](https://github.com/HarishRavi96))*:\n\n  Array_intersection transpilation support (#5186)\n\n- due to [`ac6555b`](https://github.com/tobymao/sqlglot/commit/ac6555b4d6c162ef7b14b63307d01fd560138ea0) - preserve DIV binary operator, fixes [#5198](https://github.com/tobymao/sqlglot/pull/5198) *(PR [#5199](https://github.com/tobymao/sqlglot/pull/5199) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve DIV binary operator, fixes #5198 (#5199)\n\n- due to [`dfdd84b`](https://github.com/tobymao/sqlglot/commit/dfdd84bbc50da70f40a17b39935f8171d961f7d2) - CTEs instead of subqueries for pipe syntax *(PR [#5205](https://github.com/tobymao/sqlglot/pull/5205) by [@geooo109](https://github.com/geooo109))*:\n\n  CTEs instead of subqueries for pipe syntax (#5205)\n\n- due to [`5f95299`](https://github.com/tobymao/sqlglot/commit/5f9529940d83e89704f7d25eda63cd73fdb503ae) - support multi-part (>3) dotted functions *(PR [#5211](https://github.com/tobymao/sqlglot/pull/5211) by [@georgesittas](https://github.com/georgesittas))*:\n\n  support multi-part (>3) dotted functions (#5211)\n\n- due to [`02afa2a`](https://github.com/tobymao/sqlglot/commit/02afa2a1941fc67086d50dffac2857262f1c3c4f) - Preserve quoting for UDT *(PR [#5216](https://github.com/tobymao/sqlglot/pull/5216) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Preserve quoting for UDT (#5216)\n\n- due to [`44297f1`](https://github.com/tobymao/sqlglot/commit/44297f1c5c8c2cb16fe77c318312f417b4281708) - JOIN pipe syntax, Set Operators as CTEs *(PR [#5215](https://github.com/tobymao/sqlglot/pull/5215) by [@geooo109](https://github.com/geooo109))*:\n\n  JOIN pipe syntax, Set Operators as CTEs (#5215)\n\n- due to [`4f42d95`](https://github.com/tobymao/sqlglot/commit/4f42d951363f8c43a4c414dc21d0505d9c8e48bf) - Normalize date parts in `exp.Extract` generation *(PR [#5229](https://github.com/tobymao/sqlglot/pull/5229) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Normalize date parts in `exp.Extract` generation (#5229)\n\n- due to [`e7e38fe`](https://github.com/tobymao/sqlglot/commit/e7e38fe0e09f9affbff4ffa7023d0161e3a1ee49) - resolve table \"columns\" in bigquery that produce structs *(PR [#5230](https://github.com/tobymao/sqlglot/pull/5230) by [@georgesittas](https://github.com/georgesittas))*:\n\n  resolve table \"columns\" in bigquery that produce structs (#5230)\n\n- due to [`d3dc761`](https://github.com/tobymao/sqlglot/commit/d3dc761393146357a5d20c4d7992fd2a1ae5e6e2) - change comma to cross join when precedence is the same for all join types *(PR [#5240](https://github.com/tobymao/sqlglot/pull/5240) by [@georgesittas](https://github.com/georgesittas))*:\n\n  change comma to cross join when precedence is the same for all join types (#5240)\n\n- due to [`e7c217e`](https://github.com/tobymao/sqlglot/commit/e7c217ef08e5811e7dad2b3d26dbaa9f02114e38) - transpile from/to dbms_random.value *(PR [#5242](https://github.com/tobymao/sqlglot/pull/5242) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile from/to dbms_random.value (#5242)\n\n- due to [`31814cd`](https://github.com/tobymao/sqlglot/commit/31814cddb0cf65caf29fbc45a31a9c865b7991c7) - cast constructed timestamp literal to zone-aware type if needed *(PR [#5253](https://github.com/tobymao/sqlglot/pull/5253) by [@georgesittas](https://github.com/georgesittas))*:\n\n  cast constructed timestamp literal to zone-aware type if needed (#5253)\n\n- due to [`db4e0ec`](https://github.com/tobymao/sqlglot/commit/db4e0ece950a6a1f543d8ecad48a7d4b1d6872be) - convert information schema keywords to uppercase for consistency *(PR [#5263](https://github.com/tobymao/sqlglot/pull/5263) by [@mattiasthalen](https://github.com/mattiasthalen))*:\n\n  convert information schema keywords to uppercase for consistency (#5263)\n\n- due to [`eea1570`](https://github.com/tobymao/sqlglot/commit/eea1570ba530517a95699092ccd9ce6a856f5e84) - add support for SYSDATETIMEOFFSET closes [#5272](https://github.com/tobymao/sqlglot/pull/5272) *(PR [#5273](https://github.com/tobymao/sqlglot/pull/5273) by [@georgesittas](https://github.com/georgesittas))*:\n\n  add support for SYSDATETIMEOFFSET closes #5272 (#5273)\n\n- due to [`3d3ccc5`](https://github.com/tobymao/sqlglot/commit/3d3ccc52a40536b9ac4e974f1592dffe5a7568f9) - Transpile exp.PosExplode pos column alias *(PR [#5274](https://github.com/tobymao/sqlglot/pull/5274) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Transpile exp.PosExplode pos column alias (#5274)\n\n- due to [`9a95af1`](https://github.com/tobymao/sqlglot/commit/9a95af1c725cd70ffa8206f1d88452a7faab93b2) - only cast strings to timestamp for TO_CHAR (TimeToStr) *(PR [#5283](https://github.com/tobymao/sqlglot/pull/5283) by [@georgesittas](https://github.com/georgesittas))*:\n\n  only cast strings to timestamp for TO_CHAR (TimeToStr) (#5283)\n\n- due to [`8af4790`](https://github.com/tobymao/sqlglot/commit/8af479017ccde16049c897ae5d322d4a69843b65) - Fix parsing of ADD CONSTRAINT *(PR [#5288](https://github.com/tobymao/sqlglot/pull/5288) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix parsing of ADD CONSTRAINT (#5288)\n\n- due to [`18aea08`](https://github.com/tobymao/sqlglot/commit/18aea08f7dcaa887bcf29886cd3b3bc2850a3679) - include bigquery unnest aliases in selected sources *(PR [#5285](https://github.com/tobymao/sqlglot/pull/5285) by [@georgesittas](https://github.com/georgesittas))*:\n\n  include bigquery unnest aliases in selected sources (#5285)\n\n- due to [`0ff95c5`](https://github.com/tobymao/sqlglot/commit/0ff95c5903907c9ab30b7850bb3b962bc6da2bab) - add parsing/transpilation support for the REPLACE function *(PR [#5289](https://github.com/tobymao/sqlglot/pull/5289) by [@rahulj51](https://github.com/rahulj51))*:\n\n  add parsing/transpilation support for the REPLACE function (#5289)\n\n- due to [`dc03649`](https://github.com/tobymao/sqlglot/commit/dc03649bca0b7a090254976182a03c21dd2269ba) - only coerce time var -like units into strings for DATE_TRUNC *(PR [#5291](https://github.com/tobymao/sqlglot/pull/5291) by [@georgesittas](https://github.com/georgesittas))*:\n\n  only coerce time var -like units into strings for DATE_TRUNC (#5291)\n\n\n### :sparkles: New Features\n- [`82c50ce`](https://github.com/tobymao/sqlglot/commit/82c50ce68d9a1ad25095086ae3645f5c4996c18b) - **duckdb**: extend time travel parsing to take VERSION into account *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bb4f428`](https://github.com/tobymao/sqlglot/commit/bb4f4283b53bc060a8c7e0f12c1e7ef5b521c4e6) - bubble up comments nested under a Bracket, fixes [#5131](https://github.com/tobymao/sqlglot/pull/5131) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`9f318eb`](https://github.com/tobymao/sqlglot/commit/9f318ebe4502bb484a34873252cf4a40c7e440e4) - **snowflake**: Transpile BQ's `ARRAY(SELECT AS STRUCT ...)` *(PR [#5140](https://github.com/tobymao/sqlglot/pull/5140) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`93b402a`](https://github.com/tobymao/sqlglot/commit/93b402abc74e642ed312db585b33315674a450cd) - **parser**: support SELECT, FROM, WHERE with pipe syntax *(PR [#5128](https://github.com/tobymao/sqlglot/pull/5128) by [@geooo109](https://github.com/geooo109))*\n- [`1a8e78b`](https://github.com/tobymao/sqlglot/commit/1a8e78bd84e006023d5d3ea561504587dfbb55a9) - **parser**: ORDER BY with pipe syntax *(PR [#5153](https://github.com/tobymao/sqlglot/pull/5153) by [@geooo109](https://github.com/geooo109))*\n- [`966ad95`](https://github.com/tobymao/sqlglot/commit/966ad95432d5f8e29ade36d8271a5c489c207324) - **tsql**: add convert style 126 *(PR [#5157](https://github.com/tobymao/sqlglot/pull/5157) by [@pa1ch](https://github.com/pa1ch))*\n- [`b7ac6ff`](https://github.com/tobymao/sqlglot/commit/b7ac6ff4680ff619be4b0ddb01f61f916ed09d58) - **parser**: LIMIT/OFFSET pipe syntax *(PR [#5159](https://github.com/tobymao/sqlglot/pull/5159) by [@geooo109](https://github.com/geooo109))*\n- [`cfc158d`](https://github.com/tobymao/sqlglot/commit/cfc158d753d4f43d12c3b502633d29e43dcc5569) - **snowflake**: transpile STRTOK_TO_ARRAY to duckdb *(PR [#5165](https://github.com/tobymao/sqlglot/pull/5165) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5160](https://github.com/tobymao/sqlglot/issues/5160) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`ff0f30b`](https://github.com/tobymao/sqlglot/commit/ff0f30bcf7d0d74b26a703eaa632e1be15b3c001) - support ARRAY_REMOVE *(PR [#5163](https://github.com/tobymao/sqlglot/pull/5163) by [@geooo109](https://github.com/geooo109))*\n- [`9cac01f`](https://github.com/tobymao/sqlglot/commit/9cac01f6b4a5c93b55f5b68f21cb104932880a0e) - **tsql**: support FOR XML syntax *(PR [#5167](https://github.com/tobymao/sqlglot/pull/5167) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5161](https://github.com/tobymao/sqlglot/issues/5161) opened by [@codykonior](https://github.com/codykonior)*\n- [`8b5129f`](https://github.com/tobymao/sqlglot/commit/8b5129f288880032f0bf9d649984d82314039af1) - **postgres**: improve pretty-formatting of ARRAY[...] *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`964b4a1`](https://github.com/tobymao/sqlglot/commit/964b4a1e367e00e243b80edf677cd48d453ed31e) - add line/col position for Star *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`434c45b`](https://github.com/tobymao/sqlglot/commit/434c45b547c3a5ea155dc8d7da2baab326eb6d4f) - improve support for ENDSWITH closes [#5170](https://github.com/tobymao/sqlglot/pull/5170) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`63f9cb4`](https://github.com/tobymao/sqlglot/commit/63f9cb4b158b88574136b32241ee60254352c9e6) - **sqlglotrs**: match the Python implementation of __repr__ for tokens *(PR [#5172](https://github.com/tobymao/sqlglot/pull/5172) by [@georgesittas](https://github.com/georgesittas))*\n- [`c007afa`](https://github.com/tobymao/sqlglot/commit/c007afa23831e9bd86f401d85260e15edf00328f) - support Star instance as first arg of exp.column helper *(PR [#5177](https://github.com/tobymao/sqlglot/pull/5177) by [@georgesittas](https://github.com/georgesittas))*\n- [`bc001ce`](https://github.com/tobymao/sqlglot/commit/bc001cef4c907d8fa421d3190b4fa91865d9ff6c) - **postgres**: Add support for ANY_VALUE for versions 16+ *(PR [#5179](https://github.com/tobymao/sqlglot/pull/5179) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4674](https://github.com/TobikoData/sqlmesh/issues/4674) opened by [@petrikoro](https://github.com/petrikoro)*\n- [`ba05ff6`](https://github.com/tobymao/sqlglot/commit/ba05ff67127e056d567fc2c1d3bcc8e3dcce7b7e) - **parser**: AGGREGATE with GROUP AND ORDER BY pipe syntax *(PR [#5171](https://github.com/tobymao/sqlglot/pull/5171) by [@geooo109](https://github.com/geooo109))*\n- [`26077a4`](https://github.com/tobymao/sqlglot/commit/26077a47d9db750f44ab1baf9a434596b5bb613b) - make to_table more lenient *(PR [#5183](https://github.com/tobymao/sqlglot/pull/5183) by [@georgesittas](https://github.com/georgesittas))*\n- [`29e2f1d`](https://github.com/tobymao/sqlglot/commit/29e2f1d89c095c9fab0944a6962c99bd745c2c91) - Array_intersection transpilation support *(PR [#5186](https://github.com/tobymao/sqlglot/pull/5186) by [@HarishRavi96](https://github.com/HarishRavi96))*\n- [`d86a114`](https://github.com/tobymao/sqlglot/commit/d86a1147aeb866ed0ab2c342914ecf8cbfadac8a) - **sqlite**: implement RESPECT/IGNORE NULLS in first_value() *(PR [#5185](https://github.com/tobymao/sqlglot/pull/5185) by [@NickCrews](https://github.com/NickCrews))*\n- [`1d50fca`](https://github.com/tobymao/sqlglot/commit/1d50fca8ffc34e4acbc1b791c4cdf5f184a748db) - improve transpilation of st_point and st_distance *(PR [#5194](https://github.com/tobymao/sqlglot/pull/5194) by [@georgesittas](https://github.com/georgesittas))*\n- [`756ec3b`](https://github.com/tobymao/sqlglot/commit/756ec3b65db1eb2572d017a3ac12ece6bb44c726) - **parser**: SET OPERATORS with pipe syntax *(PR [#5184](https://github.com/tobymao/sqlglot/pull/5184) by [@geooo109](https://github.com/geooo109))*\n- [`c20f85e`](https://github.com/tobymao/sqlglot/commit/c20f85e3e171e502fc51f74894d3313f0ad61535) - **spark**: support ALTER ADD PARTITION *(PR [#5208](https://github.com/tobymao/sqlglot/pull/5208) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5204](https://github.com/tobymao/sqlglot/issues/5204) opened by [@cosinequanon](https://github.com/cosinequanon)*\n- [`44297f1`](https://github.com/tobymao/sqlglot/commit/44297f1c5c8c2cb16fe77c318312f417b4281708) - **parser**: JOIN pipe syntax, Set Operators as CTEs *(PR [#5215](https://github.com/tobymao/sqlglot/pull/5215) by [@geooo109](https://github.com/geooo109))*\n- [`21cd3eb`](https://github.com/tobymao/sqlglot/commit/21cd3ebf5d0b57f5b102c5aadc3b24a598ebe918) - **parser**: PIVOT/UNPIVOT pipe syntax *(PR [#5222](https://github.com/tobymao/sqlglot/pull/5222) by [@geooo109](https://github.com/geooo109))*\n- [`97f5822`](https://github.com/tobymao/sqlglot/commit/97f58226fc8815b23787b7b8699ea71f58268560) - **parser**: AS pipe syntax *(PR [#5224](https://github.com/tobymao/sqlglot/pull/5224) by [@geooo109](https://github.com/geooo109))*\n- [`a7e7fee`](https://github.com/tobymao/sqlglot/commit/a7e7feef02a77fe8606f3f482bad91230fa637f4) - **parser**: EXTEND pipe syntax *(PR [#5225](https://github.com/tobymao/sqlglot/pull/5225) by [@geooo109](https://github.com/geooo109))*\n- [`c1cb9f8`](https://github.com/tobymao/sqlglot/commit/c1cb9f8f682080f7a06c387219d79c6d068b6dbe) - **snowflake**: add autoincrement order clause support *(PR [#5223](https://github.com/tobymao/sqlglot/pull/5223) by [@dmaresma](https://github.com/dmaresma))*\n- [`91afe4c`](https://github.com/tobymao/sqlglot/commit/91afe4cfd7b3f427e4c0b298075e867b8a1bbe55) - **parser**: TABLESAMPLE pipe syntax *(PR [#5231](https://github.com/tobymao/sqlglot/pull/5231) by [@geooo109](https://github.com/geooo109))*\n- [`62da84a`](https://github.com/tobymao/sqlglot/commit/62da84acce7f44802dca26a9357a16115e21fabf) - **snowflake**: improve transpilation of unnested object lookup *(PR [#5234](https://github.com/tobymao/sqlglot/pull/5234) by [@georgesittas](https://github.com/georgesittas))*\n- [`2c60453`](https://github.com/tobymao/sqlglot/commit/2c604537ba83dee74e9ced7e216673ecc70fe487) - **parser**: DROP pipe syntax *(PR [#5226](https://github.com/tobymao/sqlglot/pull/5226) by [@geooo109](https://github.com/geooo109))*\n- [`9885729`](https://github.com/tobymao/sqlglot/commit/988572954135c68dc021b992c815024ce3debaff) - **parser**: SET pipe syntax *(PR [#5236](https://github.com/tobymao/sqlglot/pull/5236) by [@geooo109](https://github.com/geooo109))*\n- [`e7c217e`](https://github.com/tobymao/sqlglot/commit/e7c217ef08e5811e7dad2b3d26dbaa9f02114e38) - **oracle**: transpile from/to dbms_random.value *(PR [#5242](https://github.com/tobymao/sqlglot/pull/5242) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5241](https://github.com/tobymao/sqlglot/issues/5241) opened by [@Akshat-2512](https://github.com/Akshat-2512)*\n- [`0d19544`](https://github.com/tobymao/sqlglot/commit/0d19544317c1056b17fb089d4be9b5bddfe6feb3) - add Microsoft Fabric dialect, a case sensitive version of TSQL *(PR [#5247](https://github.com/tobymao/sqlglot/pull/5247) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`249dbc9`](https://github.com/tobymao/sqlglot/commit/249dbc906adc6b20932dc8efe83f6f4d23ef8c1e) - **parser**: start with SELECT and nested pipe syntax *(PR [#5248](https://github.com/tobymao/sqlglot/pull/5248) by [@geooo109](https://github.com/geooo109))*\n- [`f5b5b93`](https://github.com/tobymao/sqlglot/commit/f5b5b9338eb92b7aa2c9b4c92c6138c2c05e1c40) - **fabric**: implement type mappings for unsupported Fabric types *(PR [#5249](https://github.com/tobymao/sqlglot/pull/5249) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`78fcea1`](https://github.com/tobymao/sqlglot/commit/78fcea13b5eb1734a15a254875bc80ad8063b0b0) - **spark, databricks**: parse brackets as placeholder *(PR [#5256](https://github.com/tobymao/sqlglot/pull/5256) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5251](https://github.com/tobymao/sqlglot/issues/5251) opened by [@aersam](https://github.com/aersam)*\n- [`7d71387`](https://github.com/tobymao/sqlglot/commit/7d7138780db82e7a75949d29282b944e739ad99d) - **fabric**: Add precision cap to temporal data types *(PR [#5250](https://github.com/tobymao/sqlglot/pull/5250) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`e8cf793`](https://github.com/tobymao/sqlglot/commit/e8cf79305d398f25640ef3c07dd8b32997cb0167) - **duckdb**: Transpile Snowflake's TO_CHAR if format is in Snowflake.TIME_MAPPING *(PR [#5257](https://github.com/tobymao/sqlglot/pull/5257) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5255](https://github.com/tobymao/sqlglot/issues/5255) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`0cdfe64`](https://github.com/tobymao/sqlglot/commit/0cdfe642e3cb996c5ac48cc055af2862340dcf56) - add Exasol dialect (pass 1: string type mapping) *(PR [#5264](https://github.com/tobymao/sqlglot/pull/5264) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`eea1570`](https://github.com/tobymao/sqlglot/commit/eea1570ba530517a95699092ccd9ce6a856f5e84) - **tsql**: add support for SYSDATETIMEOFFSET closes [#5272](https://github.com/tobymao/sqlglot/pull/5272) *(PR [#5273](https://github.com/tobymao/sqlglot/pull/5273) by [@georgesittas](https://github.com/georgesittas))*\n- [`3d3ccc5`](https://github.com/tobymao/sqlglot/commit/3d3ccc52a40536b9ac4e974f1592dffe5a7568f9) - **hive**: Transpile exp.PosExplode pos column alias *(PR [#5274](https://github.com/tobymao/sqlglot/pull/5274) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5271](https://github.com/tobymao/sqlglot/issues/5271) opened by [@charlie-liner](https://github.com/charlie-liner)*\n- [`1c48c09`](https://github.com/tobymao/sqlglot/commit/1c48c09fd836db40bba6c46d0e9969937ce96587) - **exasol**: added datatype mappings and test for exasol dialect. *(PR [#5270](https://github.com/tobymao/sqlglot/pull/5270) by [@nnamdi16](https://github.com/nnamdi16))*\n- [`883fcb1`](https://github.com/tobymao/sqlglot/commit/883fcb137583f6d36f3a70a1343780bb40bf6f81) - **databricks**: GROUP_CONCAT to LISTAGG *(PR [#5284](https://github.com/tobymao/sqlglot/pull/5284) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5281](https://github.com/tobymao/sqlglot/issues/5281) opened by [@wKollendorf](https://github.com/wKollendorf)*\n- [`21ef897`](https://github.com/tobymao/sqlglot/commit/21ef8974426d9f3562ade0bd2c8448bb440bee27) - **fabric**: implement UnixToTime transformation to DATEADD syntax *(PR [#5269](https://github.com/tobymao/sqlglot/pull/5269) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`0ff95c5`](https://github.com/tobymao/sqlglot/commit/0ff95c5903907c9ab30b7850bb3b962bc6da2bab) - add parsing/transpilation support for the REPLACE function *(PR [#5289](https://github.com/tobymao/sqlglot/pull/5289) by [@rahulj51](https://github.com/rahulj51))*\n- [`1b0631c`](https://github.com/tobymao/sqlglot/commit/1b0631c2b4516a9ceb81af6173790dd09269b635) - **exasol**: implemented the Mod function *(PR [#5292](https://github.com/tobymao/sqlglot/pull/5292) by [@nnamdi16](https://github.com/nnamdi16))*\n\n### :bug: Bug Fixes\n- [`f2bf000`](https://github.com/tobymao/sqlglot/commit/f2bf000a410fb18531bb90ef1d767baf0e8bce7a) - **optimizer**: avoid creating new alias for qualifying unpivot *(PR [#5121](https://github.com/tobymao/sqlglot/pull/5121) by [@geooo109](https://github.com/geooo109))*\n- [`a126ce8`](https://github.com/tobymao/sqlglot/commit/a126ce8a25287cf3531d815035fa3d567dc772fb) - **optimizer**: make coalesce simplification optional, skip by default *(PR [#5123](https://github.com/tobymao/sqlglot/pull/5123) by [@barakalon](https://github.com/barakalon))*\n- [`f7401fd`](https://github.com/tobymao/sqlglot/commit/f7401fdc29a35738eb23f424ceba03463a4d8af9) - **bigquery**: avoid getting stuck in infinite loop when parsing tables *(PR [#5130](https://github.com/tobymao/sqlglot/pull/5130) by [@georgesittas](https://github.com/georgesittas))*\n- [`e9b3156`](https://github.com/tobymao/sqlglot/commit/e9b3156aa1ed95fdee4c6b419134d8ca746964b6) - **athena**: Handle transpilation of FileFormatProperty from dialects that treat it as a variable and not a string literal *(PR [#5133](https://github.com/tobymao/sqlglot/pull/5133) by [@erindru](https://github.com/erindru))*\n- [`a3fccd9`](https://github.com/tobymao/sqlglot/commit/a3fccd9be294499b53477da931f8b097cdbe09fc) - **snowflake**: generate SELECT for UNNEST without JOIN or FROM *(PR [#5138](https://github.com/tobymao/sqlglot/pull/5138) by [@geooo109](https://github.com/geooo109))*\n- [`993919d`](https://github.com/tobymao/sqlglot/commit/993919d05d5d3c814471607b56831bb65d349eb4) - **snowflake**: Properly transpile ARRAY_AGG, IGNORE/RESPECT NULLS *(PR [#5137](https://github.com/tobymao/sqlglot/pull/5137) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`6e57619`](https://github.com/tobymao/sqlglot/commit/6e57619f85375e789bb39a6478aa01cd7c7758f0) - **snowflake**: Transpile ISOWEEK to WEEKISO *(PR [#5139](https://github.com/tobymao/sqlglot/pull/5139) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`c484ca3`](https://github.com/tobymao/sqlglot/commit/c484ca39bad750a96b62e2edae85612cac66ba30) - **bigquery**: recognize ARRAY_CONCAT_AGG as an aggregate function *(PR [#5141](https://github.com/tobymao/sqlglot/pull/5141) by [@georgesittas](https://github.com/georgesittas))*\n- [`f3aeb37`](https://github.com/tobymao/sqlglot/commit/f3aeb374351a0b1b3c75945718d8ea42f8926b62) - **tsql**: properly parse and generate ALTER SET *(PR [#5143](https://github.com/tobymao/sqlglot/pull/5143) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5135](https://github.com/tobymao/sqlglot/issues/5135) opened by [@codykonior](https://github.com/codykonior)*\n- [`72ce404`](https://github.com/tobymao/sqlglot/commit/72ce40405625239a0d6763d502e5af8b12abfe9b) - Refactor ALTER TABLE ADD parsing *(PR [#5144](https://github.com/tobymao/sqlglot/pull/5144) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5129](https://github.com/tobymao/sqlglot/issues/5129) opened by [@Mevrael](https://github.com/Mevrael)*\n- [`e73ddb7`](https://github.com/tobymao/sqlglot/commit/e73ddb733b7f120ae74054e6d4dc7d458f59ac50) - **mysql**: preserve TIMESTAMP on roundtrip *(PR [#5145](https://github.com/tobymao/sqlglot/pull/5145) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5127](https://github.com/tobymao/sqlglot/issues/5127) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`4f8c73d`](https://github.com/tobymao/sqlglot/commit/4f8c73d60eecebc601c60ee8c7819458435e34b8) - **hive**: STRUCT column names and data type should be separated by ':' in hive *(PR [#5147](https://github.com/tobymao/sqlglot/pull/5147) by [@tsamaras](https://github.com/tsamaras))*\n- [`e2a488f`](https://github.com/tobymao/sqlglot/commit/e2a488f48f3e036566462463bbc58cc6a1c7492e) - Error on columns mismatch in pushdown_projections ignores dialect *(PR [#5151](https://github.com/tobymao/sqlglot/pull/5151) by [@snovik75](https://github.com/snovik75))*\n- [`1a35365`](https://github.com/tobymao/sqlglot/commit/1a35365a3bb1ef56e8da0023271cbe3108e0ccb1) - avoid generating nested comments when not supported *(PR [#5158](https://github.com/tobymao/sqlglot/pull/5158) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5132](https://github.com/tobymao/sqlglot/issues/5132) opened by [@patricksurry](https://github.com/patricksurry)*\n- [`f6124c6`](https://github.com/tobymao/sqlglot/commit/f6124c6343f67563fc19f617891ecfc145a642db) - **rust-tokenizer**: return token vector in `tokenize` even on failure *(PR [#5155](https://github.com/tobymao/sqlglot/pull/5155) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5148](https://github.com/tobymao/sqlglot/issues/5148) opened by [@kamoser](https://github.com/kamoser)*\n- [`760a606`](https://github.com/tobymao/sqlglot/commit/760a6062d5f259488e471af9c1d33e200066e9dc) - **postgres**: support decimal values in INTERVAL expressions fixes [#5168](https://github.com/tobymao/sqlglot/pull/5168) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6a2cb39`](https://github.com/tobymao/sqlglot/commit/6a2cb39d0ceec091dc4fc228f26d4f457729a3cf) - **parser**: virtual column with AS(expr) as ComputedColumnConstraint *(PR [#5180](https://github.com/tobymao/sqlglot/pull/5180) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5173](https://github.com/tobymao/sqlglot/issues/5173) opened by [@suyah](https://github.com/suyah)*\n- [`c87ae02`](https://github.com/tobymao/sqlglot/commit/c87ae02aa263be8463ca7283ebd090385a4bfd59) - **sqlite**: Add REPLACE to command tokens *(PR [#5192](https://github.com/tobymao/sqlglot/pull/5192) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5187](https://github.com/tobymao/sqlglot/issues/5187) opened by [@stefanmalanik](https://github.com/stefanmalanik)*\n- [`4b89afd`](https://github.com/tobymao/sqlglot/commit/4b89afdcc0063e70cbc64165c7f1f5102afaa87c) - **starrocks**: array_agg_transpilation_fix *(PR [#5190](https://github.com/tobymao/sqlglot/pull/5190) by [@Swathiraj23](https://github.com/Swathiraj23))*\n- [`461b054`](https://github.com/tobymao/sqlglot/commit/461b0548832ab8d916c3a6638f27a49f681109fe) - **postgres**: support use_spheroid argument in ST_DISTANCE *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`99bbae3`](https://github.com/tobymao/sqlglot/commit/99bbae370329c5f5cd132b711c714359cf96ba58) - **sqlite**: allow ALTER RENAME without COLUMN keyword fixes [#5195](https://github.com/tobymao/sqlglot/pull/5195) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ac6555b`](https://github.com/tobymao/sqlglot/commit/ac6555b4d6c162ef7b14b63307d01fd560138ea0) - **hive**: preserve DIV binary operator, fixes [#5198](https://github.com/tobymao/sqlglot/pull/5198) *(PR [#5199](https://github.com/tobymao/sqlglot/pull/5199) by [@georgesittas](https://github.com/georgesittas))*\n- [`d0eeb26`](https://github.com/tobymao/sqlglot/commit/d0eeb2639e771e8f8b6feabd41c65f16ed5a9829) - eliminate_join_marks has multiple issues fixes [#5188](https://github.com/tobymao/sqlglot/pull/5188) *(PR [#5189](https://github.com/tobymao/sqlglot/pull/5189) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#5188](https://github.com/tobymao/sqlglot/issues/5188) opened by [@snovik75](https://github.com/snovik75)*\n- [`dfdd84b`](https://github.com/tobymao/sqlglot/commit/dfdd84bbc50da70f40a17b39935f8171d961f7d2) - **parser**: CTEs instead of subqueries for pipe syntax *(PR [#5205](https://github.com/tobymao/sqlglot/pull/5205) by [@geooo109](https://github.com/geooo109))*\n- [`77e9d9a`](https://github.com/tobymao/sqlglot/commit/77e9d9a0269e2013379967cf2f46fbd79c036277) - **mysql**: properly parse STORED/VIRTUAL computed columns *(PR [#5210](https://github.com/tobymao/sqlglot/pull/5210) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5203](https://github.com/tobymao/sqlglot/issues/5203) opened by [@mdebski](https://github.com/mdebski)*\n- [`5f95299`](https://github.com/tobymao/sqlglot/commit/5f9529940d83e89704f7d25eda63cd73fdb503ae) - **parser**: support multi-part (>3) dotted functions *(PR [#5211](https://github.com/tobymao/sqlglot/pull/5211) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5200](https://github.com/tobymao/sqlglot/issues/5200) opened by [@mateuszpoleski](https://github.com/mateuszpoleski)*\n- [`02afa2a`](https://github.com/tobymao/sqlglot/commit/02afa2a1941fc67086d50dffac2857262f1c3c4f) - **postgres**: Preserve quoting for UDT *(PR [#5216](https://github.com/tobymao/sqlglot/pull/5216) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5212](https://github.com/tobymao/sqlglot/issues/5212) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`f37c0b1`](https://github.com/tobymao/sqlglot/commit/f37c0b1197321dd610648ce652a171ab063deeeb) - **snowflake**: ensure a standalone GET() expression can be parsed *(PR [#5219](https://github.com/tobymao/sqlglot/pull/5219) by [@georgesittas](https://github.com/georgesittas))*\n- [`28fed58`](https://github.com/tobymao/sqlglot/commit/28fed586a39df83aade4792217743a1a859fd039) - **optimizer**: UnboundLocalError in scope module *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`809e05a`](https://github.com/tobymao/sqlglot/commit/809e05a743d5a2904a1d6f6813f24ca7549ac7ef) - **snowflake**: preserve STRTOK_TO_ARRAY roundtrip *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`df73a79`](https://github.com/tobymao/sqlglot/commit/df73a79a2ca3ba859b8aba5e3d0f6ed269874a63) - **tsql**: Retain limit clause in subquery expression. *(PR [#5227](https://github.com/tobymao/sqlglot/pull/5227) by [@MarcusRisanger](https://github.com/MarcusRisanger))*\n- [`4f42d95`](https://github.com/tobymao/sqlglot/commit/4f42d951363f8c43a4c414dc21d0505d9c8e48bf) - **duckdb**: Normalize date parts in `exp.Extract` generation *(PR [#5229](https://github.com/tobymao/sqlglot/pull/5229) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5228](https://github.com/tobymao/sqlglot/issues/5228) opened by [@greybeam-bot](https://github.com/greybeam-bot)*\n- [`1b4c083`](https://github.com/tobymao/sqlglot/commit/1b4c083fff8d7c44bf1dbba28c1225fa1e28c4d2) - **athena**: include Hive string escapes in the tokenizer *(PR [#5233](https://github.com/tobymao/sqlglot/pull/5233) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5232](https://github.com/tobymao/sqlglot/issues/5232) opened by [@ligfx](https://github.com/ligfx)*\n- [`e7e38fe`](https://github.com/tobymao/sqlglot/commit/e7e38fe0e09f9affbff4ffa7023d0161e3a1ee49) - **optimizer**: resolve table \"columns\" in bigquery that produce structs *(PR [#5230](https://github.com/tobymao/sqlglot/pull/5230) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5207](https://github.com/tobymao/sqlglot/issues/5207) opened by [@Bladieblah](https://github.com/Bladieblah)*\n- [`781539d`](https://github.com/tobymao/sqlglot/commit/781539d5cbe58142ed6688f1522fc4ed31da0a56) - **duckdb**: Generate correct DETACH syntax if IF EXISTS is set *(PR [#5235](https://github.com/tobymao/sqlglot/pull/5235) by [@erindru](https://github.com/erindru))*\n- [`d3dc761`](https://github.com/tobymao/sqlglot/commit/d3dc761393146357a5d20c4d7992fd2a1ae5e6e2) - change comma to cross join when precedence is the same for all join types *(PR [#5240](https://github.com/tobymao/sqlglot/pull/5240) by [@georgesittas](https://github.com/georgesittas))*\n- [`31814cd`](https://github.com/tobymao/sqlglot/commit/31814cddb0cf65caf29fbc45a31a9c865b7991c7) - **presto**: cast constructed timestamp literal to zone-aware type if needed *(PR [#5253](https://github.com/tobymao/sqlglot/pull/5253) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5252](https://github.com/tobymao/sqlglot/issues/5252) opened by [@agni-sairent](https://github.com/agni-sairent)*\n- [`847248d`](https://github.com/tobymao/sqlglot/commit/847248dd1b66e3a8f60c23a4488be85dfdef4113) - format ADD CONSTRAINT clause properly fixes [#5260](https://github.com/tobymao/sqlglot/pull/5260) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`db4e0ec`](https://github.com/tobymao/sqlglot/commit/db4e0ece950a6a1f543d8ecad48a7d4b1d6872be) - **tsql**: convert information schema keywords to uppercase for consistency *(PR [#5263](https://github.com/tobymao/sqlglot/pull/5263) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`8de87e3`](https://github.com/tobymao/sqlglot/commit/8de87e3f755a40b600aa94ee2c30cf697ef7c43c) - **redshift**: handle scale parameter in to_timestamp *(PR [#5266](https://github.com/tobymao/sqlglot/pull/5266) by [@MatiasCasaliSplit](https://github.com/MatiasCasaliSplit))*\n- [`e32f709`](https://github.com/tobymao/sqlglot/commit/e32f70992b5058efb93f5d2b6106fb00b810f576) - **hive**: Fix exp.PosExplode alias order *(PR [#5279](https://github.com/tobymao/sqlglot/pull/5279) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`3dd9f8e`](https://github.com/tobymao/sqlglot/commit/3dd9f8e78ecbfde0dd7fc6fefcc09c8cb99bcd7b) - **fabric**: Type mismatches and precision error *(PR [#5280](https://github.com/tobymao/sqlglot/pull/5280) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`9a95af1`](https://github.com/tobymao/sqlglot/commit/9a95af1c725cd70ffa8206f1d88452a7faab93b2) - **snowflake**: only cast strings to timestamp for TO_CHAR (TimeToStr) *(PR [#5283](https://github.com/tobymao/sqlglot/pull/5283) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5282](https://github.com/tobymao/sqlglot/issues/5282) opened by [@wedotech-ashley](https://github.com/wedotech-ashley)*\n- [`8af4790`](https://github.com/tobymao/sqlglot/commit/8af479017ccde16049c897ae5d322d4a69843b65) - **tsql**: Fix parsing of ADD CONSTRAINT *(PR [#5288](https://github.com/tobymao/sqlglot/pull/5288) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4813](https://github.com/TobikoData/sqlmesh/issues/4813) opened by [@bnstewrt](https://github.com/bnstewrt)*\n- [`18aea08`](https://github.com/tobymao/sqlglot/commit/18aea08f7dcaa887bcf29886cd3b3bc2850a3679) - **scope**: include bigquery unnest aliases in selected sources *(PR [#5285](https://github.com/tobymao/sqlglot/pull/5285) by [@georgesittas](https://github.com/georgesittas))*\n- [`ba4a234`](https://github.com/tobymao/sqlglot/commit/ba4a234bfabdd8161b96a29436a50e0eb04c2dc2) - **fabric**: ignore Date cap *(PR [#5290](https://github.com/tobymao/sqlglot/pull/5290) by [@fresioAS](https://github.com/fresioAS))*\n- [`dc03649`](https://github.com/tobymao/sqlglot/commit/dc03649bca0b7a090254976182a03c21dd2269ba) - **bigquery**: only coerce time var -like units into strings for DATE_TRUNC *(PR [#5291](https://github.com/tobymao/sqlglot/pull/5291) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`86c6b90`](https://github.com/tobymao/sqlglot/commit/86c6b90d21b204b4376639affa142e8cee509065) - **tsql**: XML_OPTIONS *(commit by [@geooo109](https://github.com/geooo109))*\n- [`aac70aa`](https://github.com/tobymao/sqlglot/commit/aac70aaaa8d840c267129e2307ccb65058cef0c9) - **parser**: simpler _parse_pipe_syntax_select *(commit by [@geooo109](https://github.com/geooo109))*\n\n### :wrench: Chores\n- [`6910744`](https://github.com/tobymao/sqlglot/commit/6910744e6260793b3f9190782cf60fbbd9adcd38) - update py03 version *(PR [#5136](https://github.com/tobymao/sqlglot/pull/5136) by [@benfdking](https://github.com/benfdking))*\n  - :arrow_lower_right: *addresses issue [#5134](https://github.com/tobymao/sqlglot/issues/5134) opened by [@mgorny](https://github.com/mgorny)*\n- [`a56deab`](https://github.com/tobymao/sqlglot/commit/a56deabc2b9543209fb5e41f19c3bef89177a577) - bump sqlglotrs to 0.5.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`5752a87`](https://github.com/tobymao/sqlglot/commit/5752a87406b736317e4dc5cce9ae05cbc5c19547) - udpate benchmarking framework *(PR [#5146](https://github.com/tobymao/sqlglot/pull/5146) by [@benfdking](https://github.com/benfdking))*\n- [`0ae297a`](https://github.com/tobymao/sqlglot/commit/0ae297a01262cf323e225fe578bdeab2230c6fd5) - compare performance on main vs pr branch *(PR [#5149](https://github.com/tobymao/sqlglot/pull/5149) by [@georgesittas](https://github.com/georgesittas))*\n- [`180963b`](https://github.com/tobymao/sqlglot/commit/180963b8cf25d9ff83d2347859b7f46398af5000) - handle pipe syntax unsupported operators more gracefully *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6c8d61a`](https://github.com/tobymao/sqlglot/commit/6c8d61ae1ef5b645835ccd683063845dd801e8d2) - include optimization benchmarks *(PR [#5152](https://github.com/tobymao/sqlglot/pull/5152) by [@georgesittas](https://github.com/georgesittas))*\n- [`bc5c66c`](https://github.com/tobymao/sqlglot/commit/bc5c66c9210a472147d98a94c34b4bb582ade8b1) - Run benchmark job if /benchmark comment *(PR [#5164](https://github.com/tobymao/sqlglot/pull/5164) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`742b2b7`](https://github.com/tobymao/sqlglot/commit/742b2b770b88a2e901d2f84af00db821da441e4c) - Fix benchmark CI to include issue number *(PR [#5166](https://github.com/tobymao/sqlglot/pull/5166) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`64c37f1`](https://github.com/tobymao/sqlglot/commit/64c37f147366fe87ae187996ecb3c9a5afa7c264) - bump sqlglotrs to 0.6.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`440590b`](https://github.com/tobymao/sqlglot/commit/440590bf92ab1281f50b96a1400cbca695d40f0c) - bump sqlglotrs to 0.6.1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`83de4e1`](https://github.com/tobymao/sqlglot/commit/83de4e11bc1547aa22b275b20c0326dfbe43b2b8) - improve benchmark result displaying *(PR [#5176](https://github.com/tobymao/sqlglot/pull/5176) by [@georgesittas](https://github.com/georgesittas))*\n- [`5d5dc2f`](https://github.com/tobymao/sqlglot/commit/5d5dc2fa471bd53730e03ac8039804221949f843) - Clean up exp.ArrayIntersect PR *(PR [#5193](https://github.com/tobymao/sqlglot/pull/5193) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ad8a4e7`](https://github.com/tobymao/sqlglot/commit/ad8a4e73e1a9e4234f0b711163fb49630acf736c) - refactor join mark elimination to use is_correlated_subquery *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7dfb578`](https://github.com/tobymao/sqlglot/commit/7dfb5780fb242c82744dc1538077776ac624081e) - Refactor DETACH generation *(PR [#5237](https://github.com/tobymao/sqlglot/pull/5237) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v26.30.0] - 2025-06-21\n### :boom: BREAKING CHANGES\n- due to [`d3dc761`](https://github.com/tobymao/sqlglot/commit/d3dc761393146357a5d20c4d7992fd2a1ae5e6e2) - change comma to cross join when precedence is the same for all join types *(PR [#5240](https://github.com/tobymao/sqlglot/pull/5240) by [@georgesittas](https://github.com/georgesittas))*:\n\n  change comma to cross join when precedence is the same for all join types (#5240)\n\n- due to [`e7c217e`](https://github.com/tobymao/sqlglot/commit/e7c217ef08e5811e7dad2b3d26dbaa9f02114e38) - transpile from/to dbms_random.value *(PR [#5242](https://github.com/tobymao/sqlglot/pull/5242) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile from/to dbms_random.value (#5242)\n\n- due to [`31814cd`](https://github.com/tobymao/sqlglot/commit/31814cddb0cf65caf29fbc45a31a9c865b7991c7) - cast constructed timestamp literal to zone-aware type if needed *(PR [#5253](https://github.com/tobymao/sqlglot/pull/5253) by [@georgesittas](https://github.com/georgesittas))*:\n\n  cast constructed timestamp literal to zone-aware type if needed (#5253)\n\n\n### :sparkles: New Features\n- [`e7c217e`](https://github.com/tobymao/sqlglot/commit/e7c217ef08e5811e7dad2b3d26dbaa9f02114e38) - **oracle**: transpile from/to dbms_random.value *(PR [#5242](https://github.com/tobymao/sqlglot/pull/5242) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5241](https://github.com/tobymao/sqlglot/issues/5241) opened by [@Akshat-2512](https://github.com/Akshat-2512)*\n- [`0d19544`](https://github.com/tobymao/sqlglot/commit/0d19544317c1056b17fb089d4be9b5bddfe6feb3) - add Microsoft Fabric dialect, a case sensitive version of TSQL *(PR [#5247](https://github.com/tobymao/sqlglot/pull/5247) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`249dbc9`](https://github.com/tobymao/sqlglot/commit/249dbc906adc6b20932dc8efe83f6f4d23ef8c1e) - **parser**: start with SELECT and nested pipe syntax *(PR [#5248](https://github.com/tobymao/sqlglot/pull/5248) by [@geooo109](https://github.com/geooo109))*\n- [`f5b5b93`](https://github.com/tobymao/sqlglot/commit/f5b5b9338eb92b7aa2c9b4c92c6138c2c05e1c40) - **fabric**: implement type mappings for unsupported Fabric types *(PR [#5249](https://github.com/tobymao/sqlglot/pull/5249) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`78fcea1`](https://github.com/tobymao/sqlglot/commit/78fcea13b5eb1734a15a254875bc80ad8063b0b0) - **spark, databricks**: parse brackets as placeholder *(PR [#5256](https://github.com/tobymao/sqlglot/pull/5256) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5251](https://github.com/tobymao/sqlglot/issues/5251) opened by [@aersam](https://github.com/aersam)*\n- [`7d71387`](https://github.com/tobymao/sqlglot/commit/7d7138780db82e7a75949d29282b944e739ad99d) - **fabric**: Add precision cap to temporal data types *(PR [#5250](https://github.com/tobymao/sqlglot/pull/5250) by [@mattiasthalen](https://github.com/mattiasthalen))*\n- [`e8cf793`](https://github.com/tobymao/sqlglot/commit/e8cf79305d398f25640ef3c07dd8b32997cb0167) - **duckdb**: Transpile Snowflake's TO_CHAR if format is in Snowflake.TIME_MAPPING *(PR [#5257](https://github.com/tobymao/sqlglot/pull/5257) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#5255](https://github.com/tobymao/sqlglot/issues/5255) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n\n### :bug: Bug Fixes\n- [`d3dc761`](https://github.com/tobymao/sqlglot/commit/d3dc761393146357a5d20c4d7992fd2a1ae5e6e2) - change comma to cross join when precedence is the same for all join types *(PR [#5240](https://github.com/tobymao/sqlglot/pull/5240) by [@georgesittas](https://github.com/georgesittas))*\n- [`31814cd`](https://github.com/tobymao/sqlglot/commit/31814cddb0cf65caf29fbc45a31a9c865b7991c7) - **presto**: cast constructed timestamp literal to zone-aware type if needed *(PR [#5253](https://github.com/tobymao/sqlglot/pull/5253) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5252](https://github.com/tobymao/sqlglot/issues/5252) opened by [@agni-sairent](https://github.com/agni-sairent)*\n\n\n## [v26.29.0] - 2025-06-17\n### :boom: BREAKING CHANGES\n- due to [`4f42d95`](https://github.com/tobymao/sqlglot/commit/4f42d951363f8c43a4c414dc21d0505d9c8e48bf) - Normalize date parts in `exp.Extract` generation *(PR [#5229](https://github.com/tobymao/sqlglot/pull/5229) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Normalize date parts in `exp.Extract` generation (#5229)\n\n- due to [`e7e38fe`](https://github.com/tobymao/sqlglot/commit/e7e38fe0e09f9affbff4ffa7023d0161e3a1ee49) - resolve table \"columns\" in bigquery that produce structs *(PR [#5230](https://github.com/tobymao/sqlglot/pull/5230) by [@georgesittas](https://github.com/georgesittas))*:\n\n  resolve table \"columns\" in bigquery that produce structs (#5230)\n\n\n### :sparkles: New Features\n- [`97f5822`](https://github.com/tobymao/sqlglot/commit/97f58226fc8815b23787b7b8699ea71f58268560) - **parser**: AS pipe syntax *(PR [#5224](https://github.com/tobymao/sqlglot/pull/5224) by [@geooo109](https://github.com/geooo109))*\n- [`a7e7fee`](https://github.com/tobymao/sqlglot/commit/a7e7feef02a77fe8606f3f482bad91230fa637f4) - **parser**: EXTEND pipe syntax *(PR [#5225](https://github.com/tobymao/sqlglot/pull/5225) by [@geooo109](https://github.com/geooo109))*\n- [`c1cb9f8`](https://github.com/tobymao/sqlglot/commit/c1cb9f8f682080f7a06c387219d79c6d068b6dbe) - **snowflake**: add autoincrement order clause support *(PR [#5223](https://github.com/tobymao/sqlglot/pull/5223) by [@dmaresma](https://github.com/dmaresma))*\n- [`91afe4c`](https://github.com/tobymao/sqlglot/commit/91afe4cfd7b3f427e4c0b298075e867b8a1bbe55) - **parser**: TABLESAMPLE pipe syntax *(PR [#5231](https://github.com/tobymao/sqlglot/pull/5231) by [@geooo109](https://github.com/geooo109))*\n- [`62da84a`](https://github.com/tobymao/sqlglot/commit/62da84acce7f44802dca26a9357a16115e21fabf) - **snowflake**: improve transpilation of unnested object lookup *(PR [#5234](https://github.com/tobymao/sqlglot/pull/5234) by [@georgesittas](https://github.com/georgesittas))*\n- [`2c60453`](https://github.com/tobymao/sqlglot/commit/2c604537ba83dee74e9ced7e216673ecc70fe487) - **parser**: DROP pipe syntax *(PR [#5226](https://github.com/tobymao/sqlglot/pull/5226) by [@geooo109](https://github.com/geooo109))*\n- [`9885729`](https://github.com/tobymao/sqlglot/commit/988572954135c68dc021b992c815024ce3debaff) - **parser**: SET pipe syntax *(PR [#5236](https://github.com/tobymao/sqlglot/pull/5236) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`df73a79`](https://github.com/tobymao/sqlglot/commit/df73a79a2ca3ba859b8aba5e3d0f6ed269874a63) - **tsql**: Retain limit clause in subquery expression. *(PR [#5227](https://github.com/tobymao/sqlglot/pull/5227) by [@MarcusRisanger](https://github.com/MarcusRisanger))*\n- [`4f42d95`](https://github.com/tobymao/sqlglot/commit/4f42d951363f8c43a4c414dc21d0505d9c8e48bf) - **duckdb**: Normalize date parts in `exp.Extract` generation *(PR [#5229](https://github.com/tobymao/sqlglot/pull/5229) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5228](https://github.com/tobymao/sqlglot/issues/5228) opened by [@greybeam-bot](https://github.com/greybeam-bot)*\n- [`1b4c083`](https://github.com/tobymao/sqlglot/commit/1b4c083fff8d7c44bf1dbba28c1225fa1e28c4d2) - **athena**: include Hive string escapes in the tokenizer *(PR [#5233](https://github.com/tobymao/sqlglot/pull/5233) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5232](https://github.com/tobymao/sqlglot/issues/5232) opened by [@ligfx](https://github.com/ligfx)*\n- [`e7e38fe`](https://github.com/tobymao/sqlglot/commit/e7e38fe0e09f9affbff4ffa7023d0161e3a1ee49) - **optimizer**: resolve table \"columns\" in bigquery that produce structs *(PR [#5230](https://github.com/tobymao/sqlglot/pull/5230) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5207](https://github.com/tobymao/sqlglot/issues/5207) opened by [@Bladieblah](https://github.com/Bladieblah)*\n- [`781539d`](https://github.com/tobymao/sqlglot/commit/781539d5cbe58142ed6688f1522fc4ed31da0a56) - **duckdb**: Generate correct DETACH syntax if IF EXISTS is set *(PR [#5235](https://github.com/tobymao/sqlglot/pull/5235) by [@erindru](https://github.com/erindru))*\n\n### :wrench: Chores\n- [`7dfb578`](https://github.com/tobymao/sqlglot/commit/7dfb5780fb242c82744dc1538077776ac624081e) - Refactor DETACH generation *(PR [#5237](https://github.com/tobymao/sqlglot/pull/5237) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v26.28.1] - 2025-06-13\n### :boom: BREAKING CHANGES\n- due to [`44297f1`](https://github.com/tobymao/sqlglot/commit/44297f1c5c8c2cb16fe77c318312f417b4281708) - JOIN pipe syntax, Set Operators as CTEs *(PR [#5215](https://github.com/tobymao/sqlglot/pull/5215) by [@geooo109](https://github.com/geooo109))*:\n\n  JOIN pipe syntax, Set Operators as CTEs (#5215)\n\n\n### :sparkles: New Features\n- [`44297f1`](https://github.com/tobymao/sqlglot/commit/44297f1c5c8c2cb16fe77c318312f417b4281708) - **parser**: JOIN pipe syntax, Set Operators as CTEs *(PR [#5215](https://github.com/tobymao/sqlglot/pull/5215) by [@geooo109](https://github.com/geooo109))*\n- [`21cd3eb`](https://github.com/tobymao/sqlglot/commit/21cd3ebf5d0b57f5b102c5aadc3b24a598ebe918) - **parser**: PIVOT/UNPIVOT pipe syntax *(PR [#5222](https://github.com/tobymao/sqlglot/pull/5222) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`28fed58`](https://github.com/tobymao/sqlglot/commit/28fed586a39df83aade4792217743a1a859fd039) - **optimizer**: UnboundLocalError in scope module *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`809e05a`](https://github.com/tobymao/sqlglot/commit/809e05a743d5a2904a1d6f6813f24ca7549ac7ef) - **snowflake**: preserve STRTOK_TO_ARRAY roundtrip *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`aac70aa`](https://github.com/tobymao/sqlglot/commit/aac70aaaa8d840c267129e2307ccb65058cef0c9) - **parser**: simpler _parse_pipe_syntax_select *(commit by [@geooo109](https://github.com/geooo109))*\n\n\n## [v26.27.0] - 2025-06-12\n### :boom: BREAKING CHANGES\n- due to [`ac6555b`](https://github.com/tobymao/sqlglot/commit/ac6555b4d6c162ef7b14b63307d01fd560138ea0) - preserve DIV binary operator, fixes [#5198](https://github.com/tobymao/sqlglot/pull/5198) *(PR [#5199](https://github.com/tobymao/sqlglot/pull/5199) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve DIV binary operator, fixes #5198 (#5199)\n\n- due to [`dfdd84b`](https://github.com/tobymao/sqlglot/commit/dfdd84bbc50da70f40a17b39935f8171d961f7d2) - CTEs instead of subqueries for pipe syntax *(PR [#5205](https://github.com/tobymao/sqlglot/pull/5205) by [@geooo109](https://github.com/geooo109))*:\n\n  CTEs instead of subqueries for pipe syntax (#5205)\n\n- due to [`5f95299`](https://github.com/tobymao/sqlglot/commit/5f9529940d83e89704f7d25eda63cd73fdb503ae) - support multi-part (>3) dotted functions *(PR [#5211](https://github.com/tobymao/sqlglot/pull/5211) by [@georgesittas](https://github.com/georgesittas))*:\n\n  support multi-part (>3) dotted functions (#5211)\n\n- due to [`02afa2a`](https://github.com/tobymao/sqlglot/commit/02afa2a1941fc67086d50dffac2857262f1c3c4f) - Preserve quoting for UDT *(PR [#5216](https://github.com/tobymao/sqlglot/pull/5216) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Preserve quoting for UDT (#5216)\n\n\n### :sparkles: New Features\n- [`c20f85e`](https://github.com/tobymao/sqlglot/commit/c20f85e3e171e502fc51f74894d3313f0ad61535) - **spark**: support ALTER ADD PARTITION *(PR [#5208](https://github.com/tobymao/sqlglot/pull/5208) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5204](https://github.com/tobymao/sqlglot/issues/5204) opened by [@cosinequanon](https://github.com/cosinequanon)*\n\n### :bug: Bug Fixes\n- [`99bbae3`](https://github.com/tobymao/sqlglot/commit/99bbae370329c5f5cd132b711c714359cf96ba58) - **sqlite**: allow ALTER RENAME without COLUMN keyword fixes [#5195](https://github.com/tobymao/sqlglot/pull/5195) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ac6555b`](https://github.com/tobymao/sqlglot/commit/ac6555b4d6c162ef7b14b63307d01fd560138ea0) - **hive**: preserve DIV binary operator, fixes [#5198](https://github.com/tobymao/sqlglot/pull/5198) *(PR [#5199](https://github.com/tobymao/sqlglot/pull/5199) by [@georgesittas](https://github.com/georgesittas))*\n- [`d0eeb26`](https://github.com/tobymao/sqlglot/commit/d0eeb2639e771e8f8b6feabd41c65f16ed5a9829) - eliminate_join_marks has multiple issues fixes [#5188](https://github.com/tobymao/sqlglot/pull/5188) *(PR [#5189](https://github.com/tobymao/sqlglot/pull/5189) by [@snovik75](https://github.com/snovik75))*\n  - :arrow_lower_right: *fixes issue [#5188](https://github.com/tobymao/sqlglot/issues/5188) opened by [@snovik75](https://github.com/snovik75)*\n- [`dfdd84b`](https://github.com/tobymao/sqlglot/commit/dfdd84bbc50da70f40a17b39935f8171d961f7d2) - **parser**: CTEs instead of subqueries for pipe syntax *(PR [#5205](https://github.com/tobymao/sqlglot/pull/5205) by [@geooo109](https://github.com/geooo109))*\n- [`77e9d9a`](https://github.com/tobymao/sqlglot/commit/77e9d9a0269e2013379967cf2f46fbd79c036277) - **mysql**: properly parse STORED/VIRTUAL computed columns *(PR [#5210](https://github.com/tobymao/sqlglot/pull/5210) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5203](https://github.com/tobymao/sqlglot/issues/5203) opened by [@mdebski](https://github.com/mdebski)*\n- [`5f95299`](https://github.com/tobymao/sqlglot/commit/5f9529940d83e89704f7d25eda63cd73fdb503ae) - **parser**: support multi-part (>3) dotted functions *(PR [#5211](https://github.com/tobymao/sqlglot/pull/5211) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5200](https://github.com/tobymao/sqlglot/issues/5200) opened by [@mateuszpoleski](https://github.com/mateuszpoleski)*\n- [`02afa2a`](https://github.com/tobymao/sqlglot/commit/02afa2a1941fc67086d50dffac2857262f1c3c4f) - **postgres**: Preserve quoting for UDT *(PR [#5216](https://github.com/tobymao/sqlglot/pull/5216) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5212](https://github.com/tobymao/sqlglot/issues/5212) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`f37c0b1`](https://github.com/tobymao/sqlglot/commit/f37c0b1197321dd610648ce652a171ab063deeeb) - **snowflake**: ensure a standalone GET() expression can be parsed *(PR [#5219](https://github.com/tobymao/sqlglot/pull/5219) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`ad8a4e7`](https://github.com/tobymao/sqlglot/commit/ad8a4e73e1a9e4234f0b711163fb49630acf736c) - refactor join mark elimination to use is_correlated_subquery *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.26.0] - 2025-06-09\n### :boom: BREAKING CHANGES\n- due to [`434c45b`](https://github.com/tobymao/sqlglot/commit/434c45b547c3a5ea155dc8d7da2baab326eb6d4f) - improve support for ENDSWITH closes [#5170](https://github.com/tobymao/sqlglot/pull/5170) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve support for ENDSWITH closes #5170\n\n- due to [`bc001ce`](https://github.com/tobymao/sqlglot/commit/bc001cef4c907d8fa421d3190b4fa91865d9ff6c) - Add support for ANY_VALUE for versions 16+ *(PR [#5179](https://github.com/tobymao/sqlglot/pull/5179) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for ANY_VALUE for versions 16+ (#5179)\n\n- due to [`6a2cb39`](https://github.com/tobymao/sqlglot/commit/6a2cb39d0ceec091dc4fc228f26d4f457729a3cf) - virtual column with AS(expr) as ComputedColumnConstraint *(PR [#5180](https://github.com/tobymao/sqlglot/pull/5180) by [@geooo109](https://github.com/geooo109))*:\n\n  virtual column with AS(expr) as ComputedColumnConstraint (#5180)\n\n- due to [`29e2f1d`](https://github.com/tobymao/sqlglot/commit/29e2f1d89c095c9fab0944a6962c99bd745c2c91) - Array_intersection transpilation support *(PR [#5186](https://github.com/tobymao/sqlglot/pull/5186) by [@HarishRavi96](https://github.com/HarishRavi96))*:\n\n  Array_intersection transpilation support (#5186)\n\n\n### :sparkles: New Features\n- [`434c45b`](https://github.com/tobymao/sqlglot/commit/434c45b547c3a5ea155dc8d7da2baab326eb6d4f) - improve support for ENDSWITH closes [#5170](https://github.com/tobymao/sqlglot/pull/5170) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`63f9cb4`](https://github.com/tobymao/sqlglot/commit/63f9cb4b158b88574136b32241ee60254352c9e6) - **sqlglotrs**: match the Python implementation of __repr__ for tokens *(PR [#5172](https://github.com/tobymao/sqlglot/pull/5172) by [@georgesittas](https://github.com/georgesittas))*\n- [`c007afa`](https://github.com/tobymao/sqlglot/commit/c007afa23831e9bd86f401d85260e15edf00328f) - support Star instance as first arg of exp.column helper *(PR [#5177](https://github.com/tobymao/sqlglot/pull/5177) by [@georgesittas](https://github.com/georgesittas))*\n- [`bc001ce`](https://github.com/tobymao/sqlglot/commit/bc001cef4c907d8fa421d3190b4fa91865d9ff6c) - **postgres**: Add support for ANY_VALUE for versions 16+ *(PR [#5179](https://github.com/tobymao/sqlglot/pull/5179) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4674](https://github.com/TobikoData/sqlmesh/issues/4674) opened by [@petrikoro](https://github.com/petrikoro)*\n- [`ba05ff6`](https://github.com/tobymao/sqlglot/commit/ba05ff67127e056d567fc2c1d3bcc8e3dcce7b7e) - **parser**: AGGREGATE with GROUP AND ORDER BY pipe syntax *(PR [#5171](https://github.com/tobymao/sqlglot/pull/5171) by [@geooo109](https://github.com/geooo109))*\n- [`26077a4`](https://github.com/tobymao/sqlglot/commit/26077a47d9db750f44ab1baf9a434596b5bb613b) - make to_table more lenient *(PR [#5183](https://github.com/tobymao/sqlglot/pull/5183) by [@georgesittas](https://github.com/georgesittas))*\n- [`29e2f1d`](https://github.com/tobymao/sqlglot/commit/29e2f1d89c095c9fab0944a6962c99bd745c2c91) - Array_intersection transpilation support *(PR [#5186](https://github.com/tobymao/sqlglot/pull/5186) by [@HarishRavi96](https://github.com/HarishRavi96))*\n- [`d86a114`](https://github.com/tobymao/sqlglot/commit/d86a1147aeb866ed0ab2c342914ecf8cbfadac8a) - **sqlite**: implement RESPECT/IGNORE NULLS in first_value() *(PR [#5185](https://github.com/tobymao/sqlglot/pull/5185) by [@NickCrews](https://github.com/NickCrews))*\n- [`1d50fca`](https://github.com/tobymao/sqlglot/commit/1d50fca8ffc34e4acbc1b791c4cdf5f184a748db) - improve transpilation of st_point and st_distance *(PR [#5194](https://github.com/tobymao/sqlglot/pull/5194) by [@georgesittas](https://github.com/georgesittas))*\n- [`756ec3b`](https://github.com/tobymao/sqlglot/commit/756ec3b65db1eb2572d017a3ac12ece6bb44c726) - **parser**: SET OPERATORS with pipe syntax *(PR [#5184](https://github.com/tobymao/sqlglot/pull/5184) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`6a2cb39`](https://github.com/tobymao/sqlglot/commit/6a2cb39d0ceec091dc4fc228f26d4f457729a3cf) - **parser**: virtual column with AS(expr) as ComputedColumnConstraint *(PR [#5180](https://github.com/tobymao/sqlglot/pull/5180) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5173](https://github.com/tobymao/sqlglot/issues/5173) opened by [@suyah](https://github.com/suyah)*\n- [`c87ae02`](https://github.com/tobymao/sqlglot/commit/c87ae02aa263be8463ca7283ebd090385a4bfd59) - **sqlite**: Add REPLACE to command tokens *(PR [#5192](https://github.com/tobymao/sqlglot/pull/5192) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5187](https://github.com/tobymao/sqlglot/issues/5187) opened by [@stefanmalanik](https://github.com/stefanmalanik)*\n- [`4b89afd`](https://github.com/tobymao/sqlglot/commit/4b89afdcc0063e70cbc64165c7f1f5102afaa87c) - **starrocks**: array_agg_transpilation_fix *(PR [#5190](https://github.com/tobymao/sqlglot/pull/5190) by [@Swathiraj23](https://github.com/Swathiraj23))*\n- [`461b054`](https://github.com/tobymao/sqlglot/commit/461b0548832ab8d916c3a6638f27a49f681109fe) - **postgres**: support use_spheroid argument in ST_DISTANCE *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`83de4e1`](https://github.com/tobymao/sqlglot/commit/83de4e11bc1547aa22b275b20c0326dfbe43b2b8) - improve benchmark result displaying *(PR [#5176](https://github.com/tobymao/sqlglot/pull/5176) by [@georgesittas](https://github.com/georgesittas))*\n- [`5d5dc2f`](https://github.com/tobymao/sqlglot/commit/5d5dc2fa471bd53730e03ac8039804221949f843) - Clean up exp.ArrayIntersect PR *(PR [#5193](https://github.com/tobymao/sqlglot/pull/5193) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v26.25.3] - 2025-06-04\n### :sparkles: New Features\n- [`964b4a1`](https://github.com/tobymao/sqlglot/commit/964b4a1e367e00e243b80edf677cd48d453ed31e) - add line/col position for Star *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.25.2] - 2025-06-04\n### :sparkles: New Features\n- [`8b5129f`](https://github.com/tobymao/sqlglot/commit/8b5129f288880032f0bf9d649984d82314039af1) - **postgres**: improve pretty-formatting of ARRAY[...] *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.25.1] - 2025-06-04\n### :wrench: Chores\n- [`440590b`](https://github.com/tobymao/sqlglot/commit/440590bf92ab1281f50b96a1400cbca695d40f0c) - bump sqlglotrs to 0.6.1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.25.0] - 2025-06-03\n### :boom: BREAKING CHANGES\n- due to [`72ce404`](https://github.com/tobymao/sqlglot/commit/72ce40405625239a0d6763d502e5af8b12abfe9b) - Refactor ALTER TABLE ADD parsing *(PR [#5144](https://github.com/tobymao/sqlglot/pull/5144) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Refactor ALTER TABLE ADD parsing (#5144)\n\n- due to [`e73ddb7`](https://github.com/tobymao/sqlglot/commit/e73ddb733b7f120ae74054e6d4dc7d458f59ac50) - preserve TIMESTAMP on roundtrip *(PR [#5145](https://github.com/tobymao/sqlglot/pull/5145) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve TIMESTAMP on roundtrip (#5145)\n\n- due to [`f6124c6`](https://github.com/tobymao/sqlglot/commit/f6124c6343f67563fc19f617891ecfc145a642db) - return token vector in `tokenize` even on failure *(PR [#5155](https://github.com/tobymao/sqlglot/pull/5155) by [@georgesittas](https://github.com/georgesittas))*:\n\n  return token vector in `tokenize` even on failure (#5155)\n\n- due to [`64c37f1`](https://github.com/tobymao/sqlglot/commit/64c37f147366fe87ae187996ecb3c9a5afa7c264) - bump sqlglotrs to 0.6.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.6.0\n\n\n### :sparkles: New Features\n- [`93b402a`](https://github.com/tobymao/sqlglot/commit/93b402abc74e642ed312db585b33315674a450cd) - **parser**: support SELECT, FROM, WHERE with pipe syntax *(PR [#5128](https://github.com/tobymao/sqlglot/pull/5128) by [@geooo109](https://github.com/geooo109))*\n- [`1a8e78b`](https://github.com/tobymao/sqlglot/commit/1a8e78bd84e006023d5d3ea561504587dfbb55a9) - **parser**: ORDER BY with pipe syntax *(PR [#5153](https://github.com/tobymao/sqlglot/pull/5153) by [@geooo109](https://github.com/geooo109))*\n- [`966ad95`](https://github.com/tobymao/sqlglot/commit/966ad95432d5f8e29ade36d8271a5c489c207324) - **tsql**: add convert style 126 *(PR [#5157](https://github.com/tobymao/sqlglot/pull/5157) by [@pa1ch](https://github.com/pa1ch))*\n- [`b7ac6ff`](https://github.com/tobymao/sqlglot/commit/b7ac6ff4680ff619be4b0ddb01f61f916ed09d58) - **parser**: LIMIT/OFFSET pipe syntax *(PR [#5159](https://github.com/tobymao/sqlglot/pull/5159) by [@geooo109](https://github.com/geooo109))*\n- [`cfc158d`](https://github.com/tobymao/sqlglot/commit/cfc158d753d4f43d12c3b502633d29e43dcc5569) - **snowflake**: transpile STRTOK_TO_ARRAY to duckdb *(PR [#5165](https://github.com/tobymao/sqlglot/pull/5165) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5160](https://github.com/tobymao/sqlglot/issues/5160) opened by [@kyle-cheung](https://github.com/kyle-cheung)*\n- [`ff0f30b`](https://github.com/tobymao/sqlglot/commit/ff0f30bcf7d0d74b26a703eaa632e1be15b3c001) - support ARRAY_REMOVE *(PR [#5163](https://github.com/tobymao/sqlglot/pull/5163) by [@geooo109](https://github.com/geooo109))*\n- [`9cac01f`](https://github.com/tobymao/sqlglot/commit/9cac01f6b4a5c93b55f5b68f21cb104932880a0e) - **tsql**: support FOR XML syntax *(PR [#5167](https://github.com/tobymao/sqlglot/pull/5167) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5161](https://github.com/tobymao/sqlglot/issues/5161) opened by [@codykonior](https://github.com/codykonior)*\n\n### :bug: Bug Fixes\n- [`f3aeb37`](https://github.com/tobymao/sqlglot/commit/f3aeb374351a0b1b3c75945718d8ea42f8926b62) - **tsql**: properly parse and generate ALTER SET *(PR [#5143](https://github.com/tobymao/sqlglot/pull/5143) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5135](https://github.com/tobymao/sqlglot/issues/5135) opened by [@codykonior](https://github.com/codykonior)*\n- [`72ce404`](https://github.com/tobymao/sqlglot/commit/72ce40405625239a0d6763d502e5af8b12abfe9b) - Refactor ALTER TABLE ADD parsing *(PR [#5144](https://github.com/tobymao/sqlglot/pull/5144) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5129](https://github.com/tobymao/sqlglot/issues/5129) opened by [@Mevrael](https://github.com/Mevrael)*\n- [`e73ddb7`](https://github.com/tobymao/sqlglot/commit/e73ddb733b7f120ae74054e6d4dc7d458f59ac50) - **mysql**: preserve TIMESTAMP on roundtrip *(PR [#5145](https://github.com/tobymao/sqlglot/pull/5145) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5127](https://github.com/tobymao/sqlglot/issues/5127) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`4f8c73d`](https://github.com/tobymao/sqlglot/commit/4f8c73d60eecebc601c60ee8c7819458435e34b8) - **hive**: STRUCT column names and data type should be separated by ':' in hive *(PR [#5147](https://github.com/tobymao/sqlglot/pull/5147) by [@tsamaras](https://github.com/tsamaras))*\n- [`e2a488f`](https://github.com/tobymao/sqlglot/commit/e2a488f48f3e036566462463bbc58cc6a1c7492e) - Error on columns mismatch in pushdown_projections ignores dialect *(PR [#5151](https://github.com/tobymao/sqlglot/pull/5151) by [@snovik75](https://github.com/snovik75))*\n- [`1a35365`](https://github.com/tobymao/sqlglot/commit/1a35365a3bb1ef56e8da0023271cbe3108e0ccb1) - avoid generating nested comments when not supported *(PR [#5158](https://github.com/tobymao/sqlglot/pull/5158) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5132](https://github.com/tobymao/sqlglot/issues/5132) opened by [@patricksurry](https://github.com/patricksurry)*\n- [`f6124c6`](https://github.com/tobymao/sqlglot/commit/f6124c6343f67563fc19f617891ecfc145a642db) - **rust-tokenizer**: return token vector in `tokenize` even on failure *(PR [#5155](https://github.com/tobymao/sqlglot/pull/5155) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5148](https://github.com/tobymao/sqlglot/issues/5148) opened by [@kamoser](https://github.com/kamoser)*\n- [`760a606`](https://github.com/tobymao/sqlglot/commit/760a6062d5f259488e471af9c1d33e200066e9dc) - **postgres**: support decimal values in INTERVAL expressions fixes [#5168](https://github.com/tobymao/sqlglot/pull/5168) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`86c6b90`](https://github.com/tobymao/sqlglot/commit/86c6b90d21b204b4376639affa142e8cee509065) - **tsql**: XML_OPTIONS *(commit by [@geooo109](https://github.com/geooo109))*\n\n### :wrench: Chores\n- [`5752a87`](https://github.com/tobymao/sqlglot/commit/5752a87406b736317e4dc5cce9ae05cbc5c19547) - udpate benchmarking framework *(PR [#5146](https://github.com/tobymao/sqlglot/pull/5146) by [@benfdking](https://github.com/benfdking))*\n- [`0ae297a`](https://github.com/tobymao/sqlglot/commit/0ae297a01262cf323e225fe578bdeab2230c6fd5) - compare performance on main vs pr branch *(PR [#5149](https://github.com/tobymao/sqlglot/pull/5149) by [@georgesittas](https://github.com/georgesittas))*\n- [`180963b`](https://github.com/tobymao/sqlglot/commit/180963b8cf25d9ff83d2347859b7f46398af5000) - handle pipe syntax unsupported operators more gracefully *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6c8d61a`](https://github.com/tobymao/sqlglot/commit/6c8d61ae1ef5b645835ccd683063845dd801e8d2) - include optimization benchmarks *(PR [#5152](https://github.com/tobymao/sqlglot/pull/5152) by [@georgesittas](https://github.com/georgesittas))*\n- [`bc5c66c`](https://github.com/tobymao/sqlglot/commit/bc5c66c9210a472147d98a94c34b4bb582ade8b1) - Run benchmark job if /benchmark comment *(PR [#5164](https://github.com/tobymao/sqlglot/pull/5164) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`742b2b7`](https://github.com/tobymao/sqlglot/commit/742b2b770b88a2e901d2f84af00db821da441e4c) - Fix benchmark CI to include issue number *(PR [#5166](https://github.com/tobymao/sqlglot/pull/5166) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`64c37f1`](https://github.com/tobymao/sqlglot/commit/64c37f147366fe87ae187996ecb3c9a5afa7c264) - bump sqlglotrs to 0.6.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.24.0] - 2025-05-30\n### :boom: BREAKING CHANGES\n- due to [`c484ca3`](https://github.com/tobymao/sqlglot/commit/c484ca39bad750a96b62e2edae85612cac66ba30) - recognize ARRAY_CONCAT_AGG as an aggregate function *(PR [#5141](https://github.com/tobymao/sqlglot/pull/5141) by [@georgesittas](https://github.com/georgesittas))*:\n\n  recognize ARRAY_CONCAT_AGG as an aggregate function (#5141)\n\n\n### :sparkles: New Features\n- [`bb4f428`](https://github.com/tobymao/sqlglot/commit/bb4f4283b53bc060a8c7e0f12c1e7ef5b521c4e6) - bubble up comments nested under a Bracket, fixes [#5131](https://github.com/tobymao/sqlglot/pull/5131) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`9f318eb`](https://github.com/tobymao/sqlglot/commit/9f318ebe4502bb484a34873252cf4a40c7e440e4) - **snowflake**: Transpile BQ's `ARRAY(SELECT AS STRUCT ...)` *(PR [#5140](https://github.com/tobymao/sqlglot/pull/5140) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`a3fccd9`](https://github.com/tobymao/sqlglot/commit/a3fccd9be294499b53477da931f8b097cdbe09fc) - **snowflake**: generate SELECT for UNNEST without JOIN or FROM *(PR [#5138](https://github.com/tobymao/sqlglot/pull/5138) by [@geooo109](https://github.com/geooo109))*\n- [`993919d`](https://github.com/tobymao/sqlglot/commit/993919d05d5d3c814471607b56831bb65d349eb4) - **snowflake**: Properly transpile ARRAY_AGG, IGNORE/RESPECT NULLS *(PR [#5137](https://github.com/tobymao/sqlglot/pull/5137) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`6e57619`](https://github.com/tobymao/sqlglot/commit/6e57619f85375e789bb39a6478aa01cd7c7758f0) - **snowflake**: Transpile ISOWEEK to WEEKISO *(PR [#5139](https://github.com/tobymao/sqlglot/pull/5139) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`c484ca3`](https://github.com/tobymao/sqlglot/commit/c484ca39bad750a96b62e2edae85612cac66ba30) - **bigquery**: recognize ARRAY_CONCAT_AGG as an aggregate function *(PR [#5141](https://github.com/tobymao/sqlglot/pull/5141) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.23.0] - 2025-05-29\n### :boom: BREAKING CHANGES\n- due to [`6910744`](https://github.com/tobymao/sqlglot/commit/6910744e6260793b3f9190782cf60fbbd9adcd38) - update py03 version *(PR [#5136](https://github.com/tobymao/sqlglot/pull/5136) by [@benfdking](https://github.com/benfdking))*:\n\n  update py03 version (#5136)\n\n- due to [`a56deab`](https://github.com/tobymao/sqlglot/commit/a56deabc2b9543209fb5e41f19c3bef89177a577) - bump sqlglotrs to 0.5.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.5.0\n\n\n### :bug: Bug Fixes\n- [`e9b3156`](https://github.com/tobymao/sqlglot/commit/e9b3156aa1ed95fdee4c6b419134d8ca746964b6) - **athena**: Handle transpilation of FileFormatProperty from dialects that treat it as a variable and not a string literal *(PR [#5133](https://github.com/tobymao/sqlglot/pull/5133) by [@erindru](https://github.com/erindru))*\n\n### :wrench: Chores\n- [`6910744`](https://github.com/tobymao/sqlglot/commit/6910744e6260793b3f9190782cf60fbbd9adcd38) - update py03 version *(PR [#5136](https://github.com/tobymao/sqlglot/pull/5136) by [@benfdking](https://github.com/benfdking))*\n  - :arrow_lower_right: *addresses issue [#5134](https://github.com/tobymao/sqlglot/issues/5134) opened by [@mgorny](https://github.com/mgorny)*\n- [`a56deab`](https://github.com/tobymao/sqlglot/commit/a56deabc2b9543209fb5e41f19c3bef89177a577) - bump sqlglotrs to 0.5.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.22.1] - 2025-05-28\n### :bug: Bug Fixes\n- [`f7401fd`](https://github.com/tobymao/sqlglot/commit/f7401fdc29a35738eb23f424ceba03463a4d8af9) - **bigquery**: avoid getting stuck in infinite loop when parsing tables *(PR [#5130](https://github.com/tobymao/sqlglot/pull/5130) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.22.0] - 2025-05-27\n### :boom: BREAKING CHANGES\n- due to [`f2bf000`](https://github.com/tobymao/sqlglot/commit/f2bf000a410fb18531bb90ef1d767baf0e8bce7a) - avoid creating new alias for qualifying unpivot *(PR [#5121](https://github.com/tobymao/sqlglot/pull/5121) by [@geooo109](https://github.com/geooo109))*:\n\n  avoid creating new alias for qualifying unpivot (#5121)\n\n- due to [`a126ce8`](https://github.com/tobymao/sqlglot/commit/a126ce8a25287cf3531d815035fa3d567dc772fb) - make coalesce simplification optional, skip by default *(PR [#5123](https://github.com/tobymao/sqlglot/pull/5123) by [@barakalon](https://github.com/barakalon))*:\n\n  make coalesce simplification optional, skip by default (#5123)\n\n\n### :sparkles: New Features\n- [`82c50ce`](https://github.com/tobymao/sqlglot/commit/82c50ce68d9a1ad25095086ae3645f5c4996c18b) - **duckdb**: extend time travel parsing to take VERSION into account *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`f2bf000`](https://github.com/tobymao/sqlglot/commit/f2bf000a410fb18531bb90ef1d767baf0e8bce7a) - **optimizer**: avoid creating new alias for qualifying unpivot *(PR [#5121](https://github.com/tobymao/sqlglot/pull/5121) by [@geooo109](https://github.com/geooo109))*\n- [`a126ce8`](https://github.com/tobymao/sqlglot/commit/a126ce8a25287cf3531d815035fa3d567dc772fb) - **optimizer**: make coalesce simplification optional, skip by default *(PR [#5123](https://github.com/tobymao/sqlglot/pull/5123) by [@barakalon](https://github.com/barakalon))*\n\n\n## [v26.21.0] - 2025-05-26\n### :boom: BREAKING CHANGES\n- due to [`de67d3c`](https://github.com/tobymao/sqlglot/commit/de67d3c953191d77ecf8cf57e375e7d203cd8857) - error on unsupported dialect settings *(PR [#5119](https://github.com/tobymao/sqlglot/pull/5119) by [@georgesittas](https://github.com/georgesittas))*:\n\n  error on unsupported dialect settings (#5119)\n\n\n### :sparkles: New Features\n- [`344f2f1`](https://github.com/tobymao/sqlglot/commit/344f2f12b6ed02d3cfd265c33fe4428741bcf6d6) - store line/col position for Anonymous functions *(PR [#5120](https://github.com/tobymao/sqlglot/pull/5120) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`de67d3c`](https://github.com/tobymao/sqlglot/commit/de67d3c953191d77ecf8cf57e375e7d203cd8857) - error on unsupported dialect settings *(PR [#5119](https://github.com/tobymao/sqlglot/pull/5119) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.20.0] - 2025-05-25\n### :sparkles: New Features\n- [`a51744f`](https://github.com/tobymao/sqlglot/commit/a51744f84945dbb99a2ab3b576eccf1543e21e17) - **optimizer**: annotate SORT_ARRAY *(PR [#5110](https://github.com/tobymao/sqlglot/pull/5110) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5107](https://github.com/tobymao/sqlglot/issues/5107) opened by [@fernandomorato](https://github.com/fernandomorato)*\n\n### :bug: Bug Fixes\n- [`ef93832`](https://github.com/tobymao/sqlglot/commit/ef938328ea18dede07ea4be7425a203770b4ca7d) - **tsql**: separate ISNULL from COALESCE *(PR [#5105](https://github.com/tobymao/sqlglot/pull/5105) by [@geooo109](https://github.com/geooo109))*\n- [`10fe4c0`](https://github.com/tobymao/sqlglot/commit/10fe4c039a15f12c97bdf74e2e4cf547691f8546) - Return parameterized `type2` in `_maybe_coerce` *(PR [#5106](https://github.com/tobymao/sqlglot/pull/5106) by [@aninhalbuquerque](https://github.com/aninhalbuquerque))*\n- [`2a95777`](https://github.com/tobymao/sqlglot/commit/2a957772fb2d95442604cf19451bf8cb58be0aeb) - **snowflake**: Put COPY GRANTS in the right place for materialized views *(PR [#5109](https://github.com/tobymao/sqlglot/pull/5109) by [@erindru](https://github.com/erindru))*\n- [`8ba0eca`](https://github.com/tobymao/sqlglot/commit/8ba0ecaa4c3b81594a0bc0a6a88f205dc64fb9aa) - **optimizer**: avoid creating extra ARRAY for annotate SORT_ARRAY *(commit by [@geooo109](https://github.com/geooo109))*\n- [`a2ba1aa`](https://github.com/tobymao/sqlglot/commit/a2ba1aa14891db9edb853296501fac6995f8d802) - **optimizer**: annotate DPipe with VARCHAR *(PR [#5111](https://github.com/tobymao/sqlglot/pull/5111) by [@geooo109](https://github.com/geooo109))*\n- [`57db62a`](https://github.com/tobymao/sqlglot/commit/57db62ac9cf115b699076af2fb951188b54639be) - ignore/respect nulls generation edge case *(PR [#5117](https://github.com/tobymao/sqlglot/pull/5117) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`a330093`](https://github.com/tobymao/sqlglot/commit/a33009349b49f244f857976dad72bee3943b80c0) - **executor**: add type hints in table module *(PR [#5113](https://github.com/tobymao/sqlglot/pull/5113) by [@esadek](https://github.com/esadek))*\n\n\n## [v26.19.0] - 2025-05-22\n### :boom: BREAKING CHANGES\n- due to [`886f85b`](https://github.com/tobymao/sqlglot/commit/886f85bf61d23ef968b9bcfd98ab606c8a590526) - pass dialect to ensure_schema *(PR [#5100](https://github.com/tobymao/sqlglot/pull/5100) by [@georgesittas](https://github.com/georgesittas))*:\n\n  pass dialect to ensure_schema (#5100)\n\n- due to [`7570f8a`](https://github.com/tobymao/sqlglot/commit/7570f8a8e77b045b5fd97dde8b4112b901df7e15) - hive, spark2, spark, databricks type coercion for IF and COALESCE functions *(PR [#5096](https://github.com/tobymao/sqlglot/pull/5096) by [@geooo109](https://github.com/geooo109))*:\n\n  hive, spark2, spark, databricks type coercion for IF and COALESCE functions (#5096)\n\n\n### :sparkles: New Features\n- [`f5f4ca1`](https://github.com/tobymao/sqlglot/commit/f5f4ca195b57007afa80fd3d9ef69953e36536ea) - **starrocks**: Support parsing \"NONE\" as security option *(PR [#5099](https://github.com/tobymao/sqlglot/pull/5099) by [@alpolishchuk](https://github.com/alpolishchuk))*\n- [`2b928e2`](https://github.com/tobymao/sqlglot/commit/2b928e238cba63e5e043207dae1bfe2f140a1c2b) - improve pretty-printing of MERGE statement *(PR [#5102](https://github.com/tobymao/sqlglot/pull/5102) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5101](https://github.com/tobymao/sqlglot/issues/5101) opened by [@maoxingda](https://github.com/maoxingda)*\n- [`69ce6b4`](https://github.com/tobymao/sqlglot/commit/69ce6b4e5d597288e4001f9696713aee083617be) - **duckdb**: add support for TRY *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`886f85b`](https://github.com/tobymao/sqlglot/commit/886f85bf61d23ef968b9bcfd98ab606c8a590526) - **optimizer**: pass dialect to ensure_schema *(PR [#5100](https://github.com/tobymao/sqlglot/pull/5100) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5098](https://github.com/tobymao/sqlglot/issues/5098) opened by [@sh-rp](https://github.com/sh-rp)*\n- [`7570f8a`](https://github.com/tobymao/sqlglot/commit/7570f8a8e77b045b5fd97dde8b4112b901df7e15) - **optimizer**: hive, spark2, spark, databricks type coercion for IF and COALESCE functions *(PR [#5096](https://github.com/tobymao/sqlglot/pull/5096) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5067](https://github.com/tobymao/sqlglot/issues/5067) opened by [@fernandomorato](https://github.com/fernandomorato)*\n\n### :wrench: Chores\n- [`cb96a0c`](https://github.com/tobymao/sqlglot/commit/cb96a0c57d94b172e6a46f8498d726cec65cfb3f) - **duckdb**: add test for UUIDV7 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.18.1] - 2025-05-20\n### :wrench: Chores\n- [`db2af6f`](https://github.com/tobymao/sqlglot/commit/db2af6fa1e2c2bf0f4cebb272287d0b2e8e69f76) - bump sqlglotrs to 0.4.2 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.18.0] - 2025-05-20\n### :boom: BREAKING CHANGES\n- due to [`1df7f61`](https://github.com/tobymao/sqlglot/commit/1df7f611bc96616cb07950a80f6669d0bc331b0e) - refactor length_sql so it handles any type, not just varchar/blob *(PR [#4935](https://github.com/tobymao/sqlglot/pull/4935) by [@tekumara](https://github.com/tekumara))*:\n\n  refactor length_sql so it handles any type, not just varchar/blob (#4935)\n\n- due to [`52719f3`](https://github.com/tobymao/sqlglot/commit/52719f37f6541e8ec9f66642ac23ed9015048092) - parse CREATE STAGE *(PR [#4947](https://github.com/tobymao/sqlglot/pull/4947) by [@tekumara](https://github.com/tekumara))*:\n\n  parse CREATE STAGE (#4947)\n\n- due to [`fd39b30`](https://github.com/tobymao/sqlglot/commit/fd39b30209d068b787619b8137a105aca9c3e607) - parse CREATE FILE FORMAT *(PR [#4948](https://github.com/tobymao/sqlglot/pull/4948) by [@tekumara](https://github.com/tekumara))*:\n\n  parse CREATE FILE FORMAT (#4948)\n\n- due to [`f835756`](https://github.com/tobymao/sqlglot/commit/f835756257f735643584b89e93693e8577744731) - Fix CREATE EXTERNAL TABLE properties *(PR [#4951](https://github.com/tobymao/sqlglot/pull/4951) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix CREATE EXTERNAL TABLE properties (#4951)\n\n- due to [`44b955b`](https://github.com/tobymao/sqlglot/commit/44b955bd537bfb8f5b6e84ecbcd5f6e3da852260) - Fix generation of exp.Values *(PR [#4930](https://github.com/tobymao/sqlglot/pull/4930) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix generation of exp.Values (#4930)\n\n- due to [`1f506b1`](https://github.com/tobymao/sqlglot/commit/1f506b186f1b954829195eefda318e231d474208) - support SHOW (ALL) TABLES *(PR [#4961](https://github.com/tobymao/sqlglot/pull/4961) by [@mscolnick](https://github.com/mscolnick))*:\n\n  support SHOW (ALL) TABLES (#4961)\n\n- due to [`72cf4a4`](https://github.com/tobymao/sqlglot/commit/72cf4a4501a8d122041a28b71be5a41ffb53602a) - Add support for PIVOT multiple IN clauses *(PR [#4964](https://github.com/tobymao/sqlglot/pull/4964) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for PIVOT multiple IN clauses (#4964)\n\n- due to [`400ea54`](https://github.com/tobymao/sqlglot/commit/400ea54d3a9cab256bfa5e496439bb9be6072d0b) - ensure JSON_FORMAT type is JSON when targeting Presto *(PR [#4968](https://github.com/tobymao/sqlglot/pull/4968) by [@georgesittas](https://github.com/georgesittas))*:\n\n  ensure JSON_FORMAT type is JSON when targeting Presto (#4968)\n\n- due to [`cb20038`](https://github.com/tobymao/sqlglot/commit/cb2003875fc6e149bd4a631e99c312a04435a46b) - treat GO as command *(PR [#4978](https://github.com/tobymao/sqlglot/pull/4978) by [@georgesittas](https://github.com/georgesittas))*:\n\n  treat GO as command (#4978)\n\n- due to [`60e26b8`](https://github.com/tobymao/sqlglot/commit/60e26b868242a05a7fdc2725bd21a127910a6fb7) - improve transpilability of GET_JSON_OBJECT by parsing json path *(PR [#4980](https://github.com/tobymao/sqlglot/pull/4980) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve transpilability of GET_JSON_OBJECT by parsing json path (#4980)\n\n- due to [`2b7845a`](https://github.com/tobymao/sqlglot/commit/2b7845a3a821d366ae90ba9ef5e7d61194a34874) - Add support for Athena's Iceberg partitioning transforms *(PR [#4976](https://github.com/tobymao/sqlglot/pull/4976) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for Athena's Iceberg partitioning transforms (#4976)\n\n- due to [`ee794e9`](https://github.com/tobymao/sqlglot/commit/ee794e9c6a3b2fdb142114327d904b6c94a16cd0) - use the standard POWER function instead of ^ fixes [#4982](https://github.com/tobymao/sqlglot/pull/4982) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  use the standard POWER function instead of ^ fixes #4982\n\n- due to [`2369195`](https://github.com/tobymao/sqlglot/commit/2369195635e25dabd5ce26c13e402076508bba04) - consistently parse INTERVAL value as a string *(PR [#4986](https://github.com/tobymao/sqlglot/pull/4986) by [@georgesittas](https://github.com/georgesittas))*:\n\n  consistently parse INTERVAL value as a string (#4986)\n\n- due to [`e866cff`](https://github.com/tobymao/sqlglot/commit/e866cffbaac3b62255d0d5c8be043ab2394af619) - support RELY option for PRIMARY KEY, FOREIGN KEY, and UNIQUE constraints *(PR [#4987](https://github.com/tobymao/sqlglot/pull/4987) by [@geooo109](https://github.com/geooo109))*:\n\n  support RELY option for PRIMARY KEY, FOREIGN KEY, and UNIQUE constraints (#4987)\n\n- due to [`510984f`](https://github.com/tobymao/sqlglot/commit/510984f2ddc6ff13b8a8030f698aed9ad0e6f46b) - stop generating redundant TO_DATE calls *(PR [#4990](https://github.com/tobymao/sqlglot/pull/4990) by [@georgesittas](https://github.com/georgesittas))*:\n\n  stop generating redundant TO_DATE calls (#4990)\n\n- due to [`da9ec61`](https://github.com/tobymao/sqlglot/commit/da9ec61e8edd5049e246390e1b638cf14d50fa2d) - Fix pretty generation of exp.Window *(PR [#4994](https://github.com/tobymao/sqlglot/pull/4994) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix pretty generation of exp.Window (#4994)\n\n- due to [`fb83fac`](https://github.com/tobymao/sqlglot/commit/fb83fac2d097d8d3e8e2556c072792857609bd94) - remove recursion from `simplify` *(PR [#4988](https://github.com/tobymao/sqlglot/pull/4988) by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove recursion from `simplify` (#4988)\n\n- due to [`890b24a`](https://github.com/tobymao/sqlglot/commit/890b24a5cec269f5595743d0a86024a23217a3f1) - remove `connector_depth` as it is now dead code *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove `connector_depth` as it is now dead code\n\n- due to [`1dc501b`](https://github.com/tobymao/sqlglot/commit/1dc501b8ed68638375d869e11f3bf188948a4990) - remove `max_depth` argument in simplify as it is now dead code *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove `max_depth` argument in simplify as it is now dead code\n\n- due to [`f5358d8`](https://github.com/tobymao/sqlglot/commit/f5358d8a3e2743b5ac0d540f10502d333ad4e082) - add support for GET statements *(PR [#5019](https://github.com/tobymao/sqlglot/pull/5019) by [@eruditmorina](https://github.com/eruditmorina))*:\n\n  add support for GET statements (#5019)\n\n- due to [`bafa7f3`](https://github.com/tobymao/sqlglot/commit/bafa7f3a03c57e573b793ed2c83c3a549dfb789c) - parse DOW and DOY *(PR [#5037](https://github.com/tobymao/sqlglot/pull/5037) by [@geooo109](https://github.com/geooo109))*:\n\n  parse DOW and DOY (#5037)\n\n- due to [`eb0a989`](https://github.com/tobymao/sqlglot/commit/eb0a989a7f3bbddb49c66ad5cd42043532568e25) - support udf environment property *(PR [#5045](https://github.com/tobymao/sqlglot/pull/5045) by [@geooo109](https://github.com/geooo109))*:\n\n  support udf environment property (#5045)\n\n- due to [`807fbbc`](https://github.com/tobymao/sqlglot/commit/807fbbc5a89925fd3c98e823003a9dc929fcaff6) - transpile timestamp without time zone *(PR [#5047](https://github.com/tobymao/sqlglot/pull/5047) by [@geooo109](https://github.com/geooo109))*:\n\n  transpile timestamp without time zone (#5047)\n\n- due to [`c48fc8f`](https://github.com/tobymao/sqlglot/commit/c48fc8fefc13becff92d0546cec1730f038af6b2) - support translate with error *(PR [#5052](https://github.com/tobymao/sqlglot/pull/5052) by [@geooo109](https://github.com/geooo109))*:\n\n  support translate with error (#5052)\n\n- due to [`2e9704e`](https://github.com/tobymao/sqlglot/commit/2e9704ede255ef17b412c6905aad69afd70ccbf3) - Change `COLLATE` expression to `Var` for `ALTER TABLE` *(PR [#5055](https://github.com/tobymao/sqlglot/pull/5055) by [@MarcusRisanger](https://github.com/MarcusRisanger))*:\n\n  Change `COLLATE` expression to `Var` for `ALTER TABLE` (#5055)\n\n- due to [`63f505e`](https://github.com/tobymao/sqlglot/commit/63f505e036928ed94df61a8b213bf84198e33d35) - unqualify UNNEST only the left most part of a column *(PR [#5069](https://github.com/tobymao/sqlglot/pull/5069) by [@geooo109](https://github.com/geooo109))*:\n\n  unqualify UNNEST only the left most part of a column (#5069)\n\n- due to [`56da962`](https://github.com/tobymao/sqlglot/commit/56da9629899e72ab1e15cfc45ede838c4c38c16e) - to_timestamp without format *(PR [#5070](https://github.com/tobymao/sqlglot/pull/5070) by [@geooo109](https://github.com/geooo109))*:\n\n  to_timestamp without format (#5070)\n\n- due to [`1ddfcbe`](https://github.com/tobymao/sqlglot/commit/1ddfcbe6c1d30d70533774da38d842bb3af6c205) - support CONVERT function *(PR [#5074](https://github.com/tobymao/sqlglot/pull/5074) by [@geooo109](https://github.com/geooo109))*:\n\n  support CONVERT function (#5074)\n\n- due to [`ba52f01`](https://github.com/tobymao/sqlglot/commit/ba52f014f0d53ce8a179f1b140876274a01b38ac) - respect normalization strategy overrides *(PR [#5080](https://github.com/tobymao/sqlglot/pull/5080) by [@georgesittas](https://github.com/georgesittas))*:\n\n  respect normalization strategy overrides (#5080)\n\n\n### :sparkles: New Features\n- [`52719f3`](https://github.com/tobymao/sqlglot/commit/52719f37f6541e8ec9f66642ac23ed9015048092) - **snowflake**: parse CREATE STAGE *(PR [#4947](https://github.com/tobymao/sqlglot/pull/4947) by [@tekumara](https://github.com/tekumara))*\n- [`fd39b30`](https://github.com/tobymao/sqlglot/commit/fd39b30209d068b787619b8137a105aca9c3e607) - **snowflake**: parse CREATE FILE FORMAT *(PR [#4948](https://github.com/tobymao/sqlglot/pull/4948) by [@tekumara](https://github.com/tekumara))*\n- [`da9a6a1`](https://github.com/tobymao/sqlglot/commit/da9a6a1d56323319b87e9b193d12ad1c644b9239) - **snowflake**: parse SHOW STAGES *(PR [#4949](https://github.com/tobymao/sqlglot/pull/4949) by [@tekumara](https://github.com/tekumara))*\n- [`bfdcdf0`](https://github.com/tobymao/sqlglot/commit/bfdcdf0afc0f4af3dacdfc3e8dca243793552b74) - **snowflake**: parse SHOW FILE FORMATS *(PR [#4950](https://github.com/tobymao/sqlglot/pull/4950) by [@tekumara](https://github.com/tekumara))*\n- [`c591443`](https://github.com/tobymao/sqlglot/commit/c591443b6b2328780e08179144557e181db0cbb6) - **duckdb**: add support for GROUP clause in standard PIVOT syntax *(PR [#4953](https://github.com/tobymao/sqlglot/pull/4953) by [@georgesittas](https://github.com/georgesittas))*\n- [`b011ee2`](https://github.com/tobymao/sqlglot/commit/b011ee2df0beaac75b982261a25d3e787dead54a) - **bigquery**: Add support for side & kind on set operators *(PR [#4959](https://github.com/tobymao/sqlglot/pull/4959) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4942](https://github.com/tobymao/sqlglot/issues/4942) opened by [@z3z1ma](https://github.com/z3z1ma)*\n- [`1f506b1`](https://github.com/tobymao/sqlglot/commit/1f506b186f1b954829195eefda318e231d474208) - **duckdb**: support SHOW (ALL) TABLES *(PR [#4961](https://github.com/tobymao/sqlglot/pull/4961) by [@mscolnick](https://github.com/mscolnick))*\n  - :arrow_lower_right: *addresses issue [#4956](https://github.com/tobymao/sqlglot/issues/4956) opened by [@mscolnick](https://github.com/mscolnick)*\n- [`ad5b595`](https://github.com/tobymao/sqlglot/commit/ad5b595049a16a27a7f249afea43dbcfcf43b5f4) - allow explicit aliasing in if(...) expressions *(PR [#4963](https://github.com/tobymao/sqlglot/pull/4963) by [@georgesittas](https://github.com/georgesittas))*\n- [`72cf4a4`](https://github.com/tobymao/sqlglot/commit/72cf4a4501a8d122041a28b71be5a41ffb53602a) - **duckdb**: Add support for PIVOT multiple IN clauses *(PR [#4964](https://github.com/tobymao/sqlglot/pull/4964) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4944](https://github.com/tobymao/sqlglot/issues/4944) opened by [@nph](https://github.com/nph)*\n- [`7bc5a21`](https://github.com/tobymao/sqlglot/commit/7bc5a217c3cc68d0cb1eaedc0c18f5188de80bf1) - **postgres**: support laterals with ordinality fixes [#4965](https://github.com/tobymao/sqlglot/pull/4965) *(PR [#4966](https://github.com/tobymao/sqlglot/pull/4966) by [@georgesittas](https://github.com/georgesittas))*\n- [`400ea54`](https://github.com/tobymao/sqlglot/commit/400ea54d3a9cab256bfa5e496439bb9be6072d0b) - ensure JSON_FORMAT type is JSON when targeting Presto *(PR [#4968](https://github.com/tobymao/sqlglot/pull/4968) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4967](https://github.com/tobymao/sqlglot/issues/4967) opened by [@jmsmdy](https://github.com/jmsmdy)*\n- [`a762993`](https://github.com/tobymao/sqlglot/commit/a762993c53d7ae91a831a8be448010e17e60f497) - **generator**: unsupported warning for T-SQL query option *(PR [#4972](https://github.com/tobymao/sqlglot/pull/4972) by [@geooo109](https://github.com/geooo109))*\n- [`e866cff`](https://github.com/tobymao/sqlglot/commit/e866cffbaac3b62255d0d5c8be043ab2394af619) - **parser**: support RELY option for PRIMARY KEY, FOREIGN KEY, and UNIQUE constraints *(PR [#4987](https://github.com/tobymao/sqlglot/pull/4987) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#4983](https://github.com/tobymao/sqlglot/issues/4983) opened by [@ggadon](https://github.com/ggadon)*\n- [`76535ce`](https://github.com/tobymao/sqlglot/commit/76535ce9487186d2eb7071fac2f224238de7a9ba) - **optimizer**: add support for Spark's TRANSFORM clause *(PR [#4993](https://github.com/tobymao/sqlglot/pull/4993) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4991](https://github.com/tobymao/sqlglot/issues/4991) opened by [@karta0807913](https://github.com/karta0807913)*\n- [`27a9fb2`](https://github.com/tobymao/sqlglot/commit/27a9fb26a1936512a09b8b09ed2656e22918f2c6) - **clickhouse**: Support parsing CTAS with alias *(PR [#5003](https://github.com/tobymao/sqlglot/pull/5003) by [@dorranh](https://github.com/dorranh))*\n- [`45cd165`](https://github.com/tobymao/sqlglot/commit/45cd165eaca96b33f1de753a147bdc352b9d56d0) - **clickhouse**: Support ClickHouse Nothing type *(PR [#5004](https://github.com/tobymao/sqlglot/pull/5004) by [@dorranh](https://github.com/dorranh))*\n- [`ca61a61`](https://github.com/tobymao/sqlglot/commit/ca61a617fa67082bc0fc94853dee4d70b8ca5c59) - Support exp.PartitionByProperty for parse_into() *(PR [#5006](https://github.com/tobymao/sqlglot/pull/5006) by [@erindru](https://github.com/erindru))*\n- [`a6d4c3c`](https://github.com/tobymao/sqlglot/commit/a6d4c3c901f828cdd96a16a0e55eac1b244f63be) - **snowflake**: Add numeric parameter support *(PR [#5008](https://github.com/tobymao/sqlglot/pull/5008) by [@hovaesco](https://github.com/hovaesco))*\n- [`5feae00`](https://github.com/tobymao/sqlglot/commit/5feae00ec7a4826285e7fd0be85d377cc0de09b5) - **databricks**: add support for the VOID type *(PR [#5012](https://github.com/tobymao/sqlglot/pull/5012) by [@georgesittas](https://github.com/georgesittas))*\n- [`6010302`](https://github.com/tobymao/sqlglot/commit/60103020879db5f23a6c4a1775848e31cce13415) - **postgres**: transpile QUARTER interval unit *(PR [#5015](https://github.com/tobymao/sqlglot/pull/5015) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5013](https://github.com/tobymao/sqlglot/issues/5013) opened by [@Wiill007](https://github.com/Wiill007)*\n- [`f5358d8`](https://github.com/tobymao/sqlglot/commit/f5358d8a3e2743b5ac0d540f10502d333ad4e082) - **snowflake**: add support for GET statements *(PR [#5019](https://github.com/tobymao/sqlglot/pull/5019) by [@eruditmorina](https://github.com/eruditmorina))*\n- [`df5ecdb`](https://github.com/tobymao/sqlglot/commit/df5ecdbebcdce491031538f6baa0f87ec7eefee8) - Include token refereces in the meta of identifier expressions *(PR [#5022](https://github.com/tobymao/sqlglot/pull/5022) by [@izeigerman](https://github.com/izeigerman))*\n- [`bafa7f3`](https://github.com/tobymao/sqlglot/commit/bafa7f3a03c57e573b793ed2c83c3a549dfb789c) - **presto**: parse DOW and DOY *(PR [#5037](https://github.com/tobymao/sqlglot/pull/5037) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5036](https://github.com/tobymao/sqlglot/issues/5036) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`eb0a989`](https://github.com/tobymao/sqlglot/commit/eb0a989a7f3bbddb49c66ad5cd42043532568e25) - support udf environment property *(PR [#5045](https://github.com/tobymao/sqlglot/pull/5045) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5043](https://github.com/tobymao/sqlglot/issues/5043) opened by [@aersam](https://github.com/aersam)*\n- [`c48fc8f`](https://github.com/tobymao/sqlglot/commit/c48fc8fefc13becff92d0546cec1730f038af6b2) - **teradata**: support translate with error *(PR [#5052](https://github.com/tobymao/sqlglot/pull/5052) by [@geooo109](https://github.com/geooo109))*\n- [`6791849`](https://github.com/tobymao/sqlglot/commit/679184943f7ffa79a2a466546f9bdfccd69034a3) - **executor**: support conversion from table to pylist *(PR [#5053](https://github.com/tobymao/sqlglot/pull/5053) by [@esadek](https://github.com/esadek))*\n- [`07bf71b`](https://github.com/tobymao/sqlglot/commit/07bf71bae5d2a5c381104a86bb52c06809c21174) - **parser**: FK REFERENCES without specifying column *(PR [#5064](https://github.com/tobymao/sqlglot/pull/5064) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5057](https://github.com/tobymao/sqlglot/issues/5057) opened by [@Steven-Wright](https://github.com/Steven-Wright)*\n- [`1ddfcbe`](https://github.com/tobymao/sqlglot/commit/1ddfcbe6c1d30d70533774da38d842bb3af6c205) - **oracle**: support CONVERT function *(PR [#5074](https://github.com/tobymao/sqlglot/pull/5074) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5071](https://github.com/tobymao/sqlglot/issues/5071) opened by [@tchamwam](https://github.com/tchamwam)*\n- [`2cca655`](https://github.com/tobymao/sqlglot/commit/2cca655430ccf4542dcb3fd0e95b776739ef91eb) - allow PIVOT to follow a JOIN *(PR [#5075](https://github.com/tobymao/sqlglot/pull/5075) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5073](https://github.com/tobymao/sqlglot/issues/5073) opened by [@tchamwam](https://github.com/tchamwam)*\n- [`c7a56d7`](https://github.com/tobymao/sqlglot/commit/c7a56d7616cfb99de942d527e80ccec36cfc5cc3) - **oracle**: PRIOR in SELECT *(PR [#5077](https://github.com/tobymao/sqlglot/pull/5077) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#5072](https://github.com/tobymao/sqlglot/issues/5072) opened by [@tchamwam](https://github.com/tchamwam)*\n- [`5c66679`](https://github.com/tobymao/sqlglot/commit/5c66679208b34b480b9a0a0c538a15ab98f872b6) - **clickhouse**: allow EXCHANGE to be parsed as Command *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`3bcf989`](https://github.com/tobymao/sqlglot/commit/3bcf9899bbdac54bf8923ab3aa13ec66c65f0c44) - **snowflake**: Transpile DataType.BIGDECIMAL to DOUBLE *(PR [#5092](https://github.com/tobymao/sqlglot/pull/5092) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`b63b60e`](https://github.com/tobymao/sqlglot/commit/b63b60ebd10ca51f05e3f54532767bd98ccc34e3) - treat `CHAR[ACTER] VARYING` as `VARCHAR` for all dialects *(PR [#5093](https://github.com/tobymao/sqlglot/pull/5093) by [@ewhitley](https://github.com/ewhitley))*\n- [`aa26aad`](https://github.com/tobymao/sqlglot/commit/aa26aad2608cd55b8bbd1d9e268444307a7224dc) - transpile WINDOW clause *(PR [#5097](https://github.com/tobymao/sqlglot/pull/5097) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`7042603`](https://github.com/tobymao/sqlglot/commit/7042603ecb5693795b15219ec9cebf2f76032c03) - **optimizer**: Merge subqueries when inner query has name conflict with outer query *(PR [#4931](https://github.com/tobymao/sqlglot/pull/4931) by [@barakalon](https://github.com/barakalon))*\n- [`1df7f61`](https://github.com/tobymao/sqlglot/commit/1df7f611bc96616cb07950a80f6669d0bc331b0e) - **duckdb**: refactor length_sql so it handles any type, not just varchar/blob *(PR [#4935](https://github.com/tobymao/sqlglot/pull/4935) by [@tekumara](https://github.com/tekumara))*\n  - :arrow_lower_right: *fixes issue [#4934](https://github.com/tobymao/sqlglot/issues/4934) opened by [@tekumara](https://github.com/tekumara)*\n- [`09882e3`](https://github.com/tobymao/sqlglot/commit/09882e32f057670a9cbd97c1e5cf1a00c774b5d2) - **tsql**: remove assert call from _build_formatted_time *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bf39a95`](https://github.com/tobymao/sqlglot/commit/bf39a95426ed6637e424da1be070cc9a8affc358) - **sqlite**: transpile double quoted PRIMARY KEY *(PR [#4941](https://github.com/tobymao/sqlglot/pull/4941) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4938](https://github.com/tobymao/sqlglot/issues/4938) opened by [@rgeronimi](https://github.com/rgeronimi)*\n- [`f835756`](https://github.com/tobymao/sqlglot/commit/f835756257f735643584b89e93693e8577744731) - **snowflake**: Fix CREATE EXTERNAL TABLE properties *(PR [#4951](https://github.com/tobymao/sqlglot/pull/4951) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4945](https://github.com/tobymao/sqlglot/issues/4945) opened by [@tekumara](https://github.com/tekumara)*\n- [`61ed971`](https://github.com/tobymao/sqlglot/commit/61ed971213c979c3777e57853bd6989bc169adb1) - **athena**: Correctly handle CTAS queries that contain Union's *(PR [#4955](https://github.com/tobymao/sqlglot/pull/4955) by [@erindru](https://github.com/erindru))*\n- [`44b955b`](https://github.com/tobymao/sqlglot/commit/44b955bd537bfb8f5b6e84ecbcd5f6e3da852260) - **clickhouse**: Fix generation of exp.Values *(PR [#4930](https://github.com/tobymao/sqlglot/pull/4930) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4056](https://github.com/TobikoData/sqlmesh/issues/4056) opened by [@dnbnero](https://github.com/dnbnero)*\n- [`61bc01c`](https://github.com/tobymao/sqlglot/commit/61bc01ceec2f801490f3f1a571aee655c5109962) - **clickhouse**: allow string literal for clickhouse ON CLUSTER clause *(PR [#4971](https://github.com/tobymao/sqlglot/pull/4971) by [@lepfhty](https://github.com/lepfhty))*\n- [`1353b79`](https://github.com/tobymao/sqlglot/commit/1353b79bd9810788a02163928b044fe038267078) - **Snowflake**: Enhance parity for FILE_FORMAT & CREDENTIALS in CREATE STAGE *(PR [#4969](https://github.com/tobymao/sqlglot/pull/4969) by [@whummer](https://github.com/whummer))*\n- [`9693dbd`](https://github.com/tobymao/sqlglot/commit/9693dbd18b98b2699cade738a254f71f2ee8ce74) - **clickhouse**: avoid superfluous parentheses in DISTINCT ON (...) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`cb20038`](https://github.com/tobymao/sqlglot/commit/cb2003875fc6e149bd4a631e99c312a04435a46b) - **tsql**: treat GO as command *(PR [#4978](https://github.com/tobymao/sqlglot/pull/4978) by [@georgesittas](https://github.com/georgesittas))*\n- [`60e26b8`](https://github.com/tobymao/sqlglot/commit/60e26b868242a05a7fdc2725bd21a127910a6fb7) - **hive**: improve transpilability of GET_JSON_OBJECT by parsing json path *(PR [#4980](https://github.com/tobymao/sqlglot/pull/4980) by [@georgesittas](https://github.com/georgesittas))*\n- [`2b7845a`](https://github.com/tobymao/sqlglot/commit/2b7845a3a821d366ae90ba9ef5e7d61194a34874) - Add support for Athena's Iceberg partitioning transforms *(PR [#4976](https://github.com/tobymao/sqlglot/pull/4976) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`fa6af23`](https://github.com/tobymao/sqlglot/commit/fa6af2302f8482c5d89ead481afe4195aaa41a9c) - **optimizer**: compare the whole type to determine if a cast can be removed *(PR [#4981](https://github.com/tobymao/sqlglot/pull/4981) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4977](https://github.com/tobymao/sqlglot/issues/4977) opened by [@MeinAccount](https://github.com/MeinAccount)*\n- [`830c9b8`](https://github.com/tobymao/sqlglot/commit/830c9b8bbf906cf5d4fa8028b67dadda73fc58a9) - **unnest_subqueries**: avoid adding GROUP BY on aggregate projections in lateral subqueries *(PR [#4970](https://github.com/tobymao/sqlglot/pull/4970) by [@skadel](https://github.com/skadel))*\n- [`ee794e9`](https://github.com/tobymao/sqlglot/commit/ee794e9c6a3b2fdb142114327d904b6c94a16cd0) - **postgres**: use the standard POWER function instead of ^ fixes [#4982](https://github.com/tobymao/sqlglot/pull/4982) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`85e62b8`](https://github.com/tobymao/sqlglot/commit/85e62b88df2822797f527dce4eaa230c778cbe9e) - **bigquery**: Do not consume JOIN keywords after WITH OFFSET *(PR [#4984](https://github.com/tobymao/sqlglot/pull/4984) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2369195`](https://github.com/tobymao/sqlglot/commit/2369195635e25dabd5ce26c13e402076508bba04) - consistently parse INTERVAL value as a string *(PR [#4986](https://github.com/tobymao/sqlglot/pull/4986) by [@georgesittas](https://github.com/georgesittas))*\n- [`510984f`](https://github.com/tobymao/sqlglot/commit/510984f2ddc6ff13b8a8030f698aed9ad0e6f46b) - **hive**: stop generating redundant TO_DATE calls *(PR [#4990](https://github.com/tobymao/sqlglot/pull/4990) by [@georgesittas](https://github.com/georgesittas))*\n- [`da9ec61`](https://github.com/tobymao/sqlglot/commit/da9ec61e8edd5049e246390e1b638cf14d50fa2d) - **generator**: Fix pretty generation of exp.Window *(PR [#4994](https://github.com/tobymao/sqlglot/pull/4994) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4098](https://github.com/TobikoData/sqlmesh/issues/4098) opened by [@tanghyd](https://github.com/tanghyd)*\n- [`aae9aa8`](https://github.com/tobymao/sqlglot/commit/aae9aa8f96ccaa7686cda3cdabec208ae4c3d60a) - **optimizer**: ensure there are no shared refs after qualify_tables *(PR [#4995](https://github.com/tobymao/sqlglot/pull/4995) by [@georgesittas](https://github.com/georgesittas))*\n- [`adaef42`](https://github.com/tobymao/sqlglot/commit/adaef42234d8f1c9c331f53bee2c42686f29bdec) - **trino**: Dont quote identifiers in string literals for the partitioned_by property *(PR [#4998](https://github.com/tobymao/sqlglot/pull/4998) by [@erindru](https://github.com/erindru))*\n- [`a547f8d`](https://github.com/tobymao/sqlglot/commit/a547f8d4292f3b3a4c85f9d6466ead2ad976dfd2) - **postgres**: Capture optional minus sign in interval regex *(PR [#5000](https://github.com/tobymao/sqlglot/pull/5000) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4999](https://github.com/tobymao/sqlglot/issues/4999) opened by [@cpimhoff](https://github.com/cpimhoff)*\n- [`8e9dbd4`](https://github.com/tobymao/sqlglot/commit/8e9dbd491b9516c614554e05f05cc1cb976838e3) - **duckdb**: warn on unsupported IGNORE/RESPECT NULLS *(PR [#5002](https://github.com/tobymao/sqlglot/pull/5002) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5001](https://github.com/tobymao/sqlglot/issues/5001) opened by [@MarcoGorelli](https://github.com/MarcoGorelli)*\n- [`10b02bc`](https://github.com/tobymao/sqlglot/commit/10b02bce304042fea09e9cb2369db3c873452245) - **clickhouse**: Support optional timezone argument in date_diff() *(PR [#5005](https://github.com/tobymao/sqlglot/pull/5005) by [@dorranh](https://github.com/dorranh))*\n- [`c594b63`](https://github.com/tobymao/sqlglot/commit/c594b630c1c940e9a47abfce1633b435a2607f13) - Add MAX_BY & MIN_BY to FUNCTION_PARSER *(PR [#5021](https://github.com/tobymao/sqlglot/pull/5021) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5020](https://github.com/tobymao/sqlglot/issues/5020) opened by [@omerhadari](https://github.com/omerhadari)*\n- [`c1c892c`](https://github.com/tobymao/sqlglot/commit/c1c892cebb89ddf29369ff3c7647f96d217acb71) - **parser**: parse column ops after no-paren type casting *(PR [#5025](https://github.com/tobymao/sqlglot/pull/5025) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5024](https://github.com/tobymao/sqlglot/issues/5024) opened by [@MagdaSousa](https://github.com/MagdaSousa)*\n- [`52e068f`](https://github.com/tobymao/sqlglot/commit/52e068f74bd6844d0273ddcc7637d249e6ed51c1) - **databricks**: Preserve colon operators in TRY_CAST *(PR [#5028](https://github.com/tobymao/sqlglot/pull/5028) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5027](https://github.com/tobymao/sqlglot/issues/5027) opened by [@aersam](https://github.com/aersam)*\n- [`91e5036`](https://github.com/tobymao/sqlglot/commit/91e5036831b87fd4670424e6a49e81efead432f2) - **parser**: Do not parse set ops if input expr is None *(PR [#5030](https://github.com/tobymao/sqlglot/pull/5030) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`8f77b30`](https://github.com/tobymao/sqlglot/commit/8f77b301a267eadb4c4792201e112159db554d1c) - **snowflake**: get function *(commit by [@tobymao](https://github.com/tobymao))*\n- [`281ab21`](https://github.com/tobymao/sqlglot/commit/281ab21969d3937cef55adc3032f74b00173e948) - **snowflake**: generate expression DayOfWeekIso using DAYOFWEEKISO *(PR [#5034](https://github.com/tobymao/sqlglot/pull/5034) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5032](https://github.com/tobymao/sqlglot/issues/5032) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`2fa9684`](https://github.com/tobymao/sqlglot/commit/2fa96843a29323b97229842f7cf993b72bc86677) - preserve non-participating joins in eliminate_join_marks rule fixes [#5039](https://github.com/tobymao/sqlglot/pull/5039) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d10fdf5`](https://github.com/tobymao/sqlglot/commit/d10fdf5f9388dc3848617cfbf4e6f7b1aa73be1a) - **optimizer**: prevent incorrect predicate pushdown into RHS of CROSS JOIN UNNEST *(PR [#5033](https://github.com/tobymao/sqlglot/pull/5033) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5023](https://github.com/tobymao/sqlglot/issues/5023) opened by [@schelip](https://github.com/schelip)*\n- [`7c55c48`](https://github.com/tobymao/sqlglot/commit/7c55c48ec2088e776fd4ec5b6c0f4989450a39c6) - prevent redundant backslash escapes in rawstring generator *(PR [#5040](https://github.com/tobymao/sqlglot/pull/5040) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5038](https://github.com/tobymao/sqlglot/issues/5038) opened by [@ihvol-freenome](https://github.com/ihvol-freenome)*\n- [`167c547`](https://github.com/tobymao/sqlglot/commit/167c547171fa3f2de1c2fdd64ca51bb9ccb3ee52) - **tsql**: ALTER COLUMN syntax *(PR [#5051](https://github.com/tobymao/sqlglot/pull/5051) by [@MarcusRisanger](https://github.com/MarcusRisanger))*\n  - :arrow_lower_right: *fixes issue [#5050](https://github.com/tobymao/sqlglot/issues/5050) opened by [@MarcusRisanger](https://github.com/MarcusRisanger)*\n- [`807fbbc`](https://github.com/tobymao/sqlglot/commit/807fbbc5a89925fd3c98e823003a9dc929fcaff6) - **duckdb**: transpile timestamp without time zone *(PR [#5047](https://github.com/tobymao/sqlglot/pull/5047) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4859](https://github.com/tobymao/sqlglot/issues/4859) opened by [@eakmanrq](https://github.com/eakmanrq)*\n- [`2e9704e`](https://github.com/tobymao/sqlglot/commit/2e9704ede255ef17b412c6905aad69afd70ccbf3) - **tsql**: Change `COLLATE` expression to `Var` for `ALTER TABLE` *(PR [#5055](https://github.com/tobymao/sqlglot/pull/5055) by [@MarcusRisanger](https://github.com/MarcusRisanger))*\n- [`60f9420`](https://github.com/tobymao/sqlglot/commit/60f9420660d8d48bd98560a9bf8aec1f497fdeff) - **druid**: preserve MOD function fixes [#5060](https://github.com/tobymao/sqlglot/pull/5060) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7866b48`](https://github.com/tobymao/sqlglot/commit/7866b48275c830aeb51592e1888c751bcb58a361) - **druid**: support current_timestamp *(PR [#5061](https://github.com/tobymao/sqlglot/pull/5061) by [@ALongJohnson](https://github.com/ALongJohnson))*\n  - :arrow_lower_right: *fixes issue [#5059](https://github.com/tobymao/sqlglot/issues/5059) opened by [@ALongJohnson](https://github.com/ALongJohnson)*\n- [`626f3a3`](https://github.com/tobymao/sqlglot/commit/626f3a3987c2a96a8fd6e329d237c0c7bc8bf264) - Support EXCLUDE in window definition *(PR [#5058](https://github.com/tobymao/sqlglot/pull/5058) by [@rafasofizada](https://github.com/rafasofizada))*\n- [`63f505e`](https://github.com/tobymao/sqlglot/commit/63f505e036928ed94df61a8b213bf84198e33d35) - unqualify UNNEST only the left most part of a column *(PR [#5069](https://github.com/tobymao/sqlglot/pull/5069) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5062](https://github.com/tobymao/sqlglot/issues/5062) opened by [@goldmedal](https://github.com/goldmedal)*\n- [`56da962`](https://github.com/tobymao/sqlglot/commit/56da9629899e72ab1e15cfc45ede838c4c38c16e) - **oracle**: to_timestamp without format *(PR [#5070](https://github.com/tobymao/sqlglot/pull/5070) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5068](https://github.com/tobymao/sqlglot/issues/5068) opened by [@kosta-foundational](https://github.com/kosta-foundational)*\n- [`ba52f01`](https://github.com/tobymao/sqlglot/commit/ba52f014f0d53ce8a179f1b140876274a01b38ac) - **bigquery**: respect normalization strategy overrides *(PR [#5080](https://github.com/tobymao/sqlglot/pull/5080) by [@georgesittas](https://github.com/georgesittas))*\n- [`03ace87`](https://github.com/tobymao/sqlglot/commit/03ace877e3f9e5d56fcbcbe260849f5d1247e5d9) - **optimizer**: keep ORDER BY when merging subqueries *(PR [#5084](https://github.com/tobymao/sqlglot/pull/5084) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#5065](https://github.com/tobymao/sqlglot/issues/5065) opened by [@udaykrishna-eng](https://github.com/udaykrishna-eng)*\n- [`ba7b5a8`](https://github.com/tobymao/sqlglot/commit/ba7b5a8566dc15f438dcd0c03397b2e93e9c75cb) - **bigquery**: respect normalization strategy patching *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`4558bb7`](https://github.com/tobymao/sqlglot/commit/4558bb7a3a00629194f969d05d4b151f9ccd6172) - **bigquery**: always infer concat type as either bytes or string *(PR [#5085](https://github.com/tobymao/sqlglot/pull/5085) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5083](https://github.com/tobymao/sqlglot/issues/5083) opened by [@tobymao](https://github.com/tobymao)*\n- [`612a2da`](https://github.com/tobymao/sqlglot/commit/612a2daeb0e93c5cc77b3c78c0b53905f4bee19c) - **tokenizer**: fix token col attribute when there is leading whitespace after a newline *(PR [#5094](https://github.com/tobymao/sqlglot/pull/5094) by [@chgiff](https://github.com/chgiff))*\n- [`9d3a929`](https://github.com/tobymao/sqlglot/commit/9d3a929ba9006ebac67ff315c55da74a724ec975) - preserve `ARRAY_JOIN` for StarRocks, Doris (fixes [#5095](https://github.com/tobymao/sqlglot/pull/5095)) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`fb83fac`](https://github.com/tobymao/sqlglot/commit/fb83fac2d097d8d3e8e2556c072792857609bd94) - **optimizer**: remove recursion from `simplify` *(PR [#4988](https://github.com/tobymao/sqlglot/pull/4988) by [@georgesittas](https://github.com/georgesittas))*\n- [`1b3ea34`](https://github.com/tobymao/sqlglot/commit/1b3ea344af1d71d3eee239a5c4996a0aecd091de) - **clickhouse**: override _parse_property_assignment to handle null engine *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`890b24a`](https://github.com/tobymao/sqlglot/commit/890b24a5cec269f5595743d0a86024a23217a3f1) - remove `connector_depth` as it is now dead code *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1dc501b`](https://github.com/tobymao/sqlglot/commit/1dc501b8ed68638375d869e11f3bf188948a4990) - remove `max_depth` argument in simplify as it is now dead code *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6572517`](https://github.com/tobymao/sqlglot/commit/6572517c1ec76f14cbd661aacc15c84bef065284) - improve tooling around benchmarks *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1d4d906`](https://github.com/tobymao/sqlglot/commit/1d4d906abc60d29b6606bc8eee50c92cef21d3fd) - use _try_parse for parsing ClickHouse's CREATE TABLE .. AS <table> *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`fc58c27`](https://github.com/tobymao/sqlglot/commit/fc58c273690734263b971b138ec8f0186f524672) - Refactor placeholder parsing for TokenType.COLON *(PR [#5009](https://github.com/tobymao/sqlglot/pull/5009) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`da90228`](https://github.com/tobymao/sqlglot/commit/da90228f1550715646106dd6f9a170d0973f138f) - put a lock around the lazy dialect module loading call *(PR [#5011](https://github.com/tobymao/sqlglot/pull/5011) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5010](https://github.com/tobymao/sqlglot/issues/5010) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`abbcf26`](https://github.com/tobymao/sqlglot/commit/abbcf26b2101b2d806466353dcd29b79d1af5219) - bump sqlglotrs to 0.4.1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.16.4] - 2025-05-02\n### :bug: Bug Fixes\n- [`52e068f`](https://github.com/tobymao/sqlglot/commit/52e068f74bd6844d0273ddcc7637d249e6ed51c1) - **databricks**: Preserve colon operators in TRY_CAST *(PR [#5028](https://github.com/tobymao/sqlglot/pull/5028) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5027](https://github.com/tobymao/sqlglot/issues/5027) opened by [@aersam](https://github.com/aersam)*\n- [`91e5036`](https://github.com/tobymao/sqlglot/commit/91e5036831b87fd4670424e6a49e81efead432f2) - **parser**: Do not parse set ops if input expr is None *(PR [#5030](https://github.com/tobymao/sqlglot/pull/5030) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`8f77b30`](https://github.com/tobymao/sqlglot/commit/8f77b301a267eadb4c4792201e112159db554d1c) - **snowflake**: get function *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v26.16.3] - 2025-05-01\n### :boom: BREAKING CHANGES\n- due to [`f5358d8`](https://github.com/tobymao/sqlglot/commit/f5358d8a3e2743b5ac0d540f10502d333ad4e082) - add support for GET statements *(PR [#5019](https://github.com/tobymao/sqlglot/pull/5019) by [@eruditmorina](https://github.com/eruditmorina))*:\n\n  add support for GET statements (#5019)\n\n\n### :sparkles: New Features\n- [`6010302`](https://github.com/tobymao/sqlglot/commit/60103020879db5f23a6c4a1775848e31cce13415) - **postgres**: transpile QUARTER interval unit *(PR [#5015](https://github.com/tobymao/sqlglot/pull/5015) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5013](https://github.com/tobymao/sqlglot/issues/5013) opened by [@Wiill007](https://github.com/Wiill007)*\n- [`f5358d8`](https://github.com/tobymao/sqlglot/commit/f5358d8a3e2743b5ac0d540f10502d333ad4e082) - **snowflake**: add support for GET statements *(PR [#5019](https://github.com/tobymao/sqlglot/pull/5019) by [@eruditmorina](https://github.com/eruditmorina))*\n- [`df5ecdb`](https://github.com/tobymao/sqlglot/commit/df5ecdbebcdce491031538f6baa0f87ec7eefee8) - Include token refereces in the meta of identifier expressions *(PR [#5022](https://github.com/tobymao/sqlglot/pull/5022) by [@izeigerman](https://github.com/izeigerman))*\n\n### :bug: Bug Fixes\n- [`c594b63`](https://github.com/tobymao/sqlglot/commit/c594b630c1c940e9a47abfce1633b435a2607f13) - Add MAX_BY & MIN_BY to FUNCTION_PARSER *(PR [#5021](https://github.com/tobymao/sqlglot/pull/5021) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#5020](https://github.com/tobymao/sqlglot/issues/5020) opened by [@omerhadari](https://github.com/omerhadari)*\n- [`c1c892c`](https://github.com/tobymao/sqlglot/commit/c1c892cebb89ddf29369ff3c7647f96d217acb71) - **parser**: parse column ops after no-paren type casting *(PR [#5025](https://github.com/tobymao/sqlglot/pull/5025) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5024](https://github.com/tobymao/sqlglot/issues/5024) opened by [@MagdaSousa](https://github.com/MagdaSousa)*\n\n\n## [v26.16.2] - 2025-04-24\n### :sparkles: New Features\n- [`5feae00`](https://github.com/tobymao/sqlglot/commit/5feae00ec7a4826285e7fd0be85d377cc0de09b5) - **databricks**: add support for the VOID type *(PR [#5012](https://github.com/tobymao/sqlglot/pull/5012) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`da90228`](https://github.com/tobymao/sqlglot/commit/da90228f1550715646106dd6f9a170d0973f138f) - put a lock around the lazy dialect module loading call *(PR [#5011](https://github.com/tobymao/sqlglot/pull/5011) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#5010](https://github.com/tobymao/sqlglot/issues/5010) opened by [@NickCrews](https://github.com/NickCrews)*\n\n\n## [v26.16.1] - 2025-04-24\n### :sparkles: New Features\n- [`27a9fb2`](https://github.com/tobymao/sqlglot/commit/27a9fb26a1936512a09b8b09ed2656e22918f2c6) - **clickhouse**: Support parsing CTAS with alias *(PR [#5003](https://github.com/tobymao/sqlglot/pull/5003) by [@dorranh](https://github.com/dorranh))*\n- [`45cd165`](https://github.com/tobymao/sqlglot/commit/45cd165eaca96b33f1de753a147bdc352b9d56d0) - **clickhouse**: Support ClickHouse Nothing type *(PR [#5004](https://github.com/tobymao/sqlglot/pull/5004) by [@dorranh](https://github.com/dorranh))*\n- [`ca61a61`](https://github.com/tobymao/sqlglot/commit/ca61a617fa67082bc0fc94853dee4d70b8ca5c59) - Support exp.PartitionByProperty for parse_into() *(PR [#5006](https://github.com/tobymao/sqlglot/pull/5006) by [@erindru](https://github.com/erindru))*\n- [`a6d4c3c`](https://github.com/tobymao/sqlglot/commit/a6d4c3c901f828cdd96a16a0e55eac1b244f63be) - **snowflake**: Add numeric parameter support *(PR [#5008](https://github.com/tobymao/sqlglot/pull/5008) by [@hovaesco](https://github.com/hovaesco))*\n\n### :bug: Bug Fixes\n- [`8e9dbd4`](https://github.com/tobymao/sqlglot/commit/8e9dbd491b9516c614554e05f05cc1cb976838e3) - **duckdb**: warn on unsupported IGNORE/RESPECT NULLS *(PR [#5002](https://github.com/tobymao/sqlglot/pull/5002) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#5001](https://github.com/tobymao/sqlglot/issues/5001) opened by [@MarcoGorelli](https://github.com/MarcoGorelli)*\n- [`10b02bc`](https://github.com/tobymao/sqlglot/commit/10b02bce304042fea09e9cb2369db3c873452245) - **clickhouse**: Support optional timezone argument in date_diff() *(PR [#5005](https://github.com/tobymao/sqlglot/pull/5005) by [@dorranh](https://github.com/dorranh))*\n\n### :wrench: Chores\n- [`1d4d906`](https://github.com/tobymao/sqlglot/commit/1d4d906abc60d29b6606bc8eee50c92cef21d3fd) - use _try_parse for parsing ClickHouse's CREATE TABLE .. AS <table> *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`fc58c27`](https://github.com/tobymao/sqlglot/commit/fc58c273690734263b971b138ec8f0186f524672) - Refactor placeholder parsing for TokenType.COLON *(PR [#5009](https://github.com/tobymao/sqlglot/pull/5009) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v26.16.0] - 2025-04-22\n### :boom: BREAKING CHANGES\n- due to [`510984f`](https://github.com/tobymao/sqlglot/commit/510984f2ddc6ff13b8a8030f698aed9ad0e6f46b) - stop generating redundant TO_DATE calls *(PR [#4990](https://github.com/tobymao/sqlglot/pull/4990) by [@georgesittas](https://github.com/georgesittas))*:\n\n  stop generating redundant TO_DATE calls (#4990)\n\n- due to [`da9ec61`](https://github.com/tobymao/sqlglot/commit/da9ec61e8edd5049e246390e1b638cf14d50fa2d) - Fix pretty generation of exp.Window *(PR [#4994](https://github.com/tobymao/sqlglot/pull/4994) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix pretty generation of exp.Window (#4994)\n\n- due to [`fb83fac`](https://github.com/tobymao/sqlglot/commit/fb83fac2d097d8d3e8e2556c072792857609bd94) - remove recursion from `simplify` *(PR [#4988](https://github.com/tobymao/sqlglot/pull/4988) by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove recursion from `simplify` (#4988)\n\n- due to [`890b24a`](https://github.com/tobymao/sqlglot/commit/890b24a5cec269f5595743d0a86024a23217a3f1) - remove `connector_depth` as it is now dead code *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove `connector_depth` as it is now dead code\n\n- due to [`1dc501b`](https://github.com/tobymao/sqlglot/commit/1dc501b8ed68638375d869e11f3bf188948a4990) - remove `max_depth` argument in simplify as it is now dead code *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  remove `max_depth` argument in simplify as it is now dead code\n\n\n### :sparkles: New Features\n- [`76535ce`](https://github.com/tobymao/sqlglot/commit/76535ce9487186d2eb7071fac2f224238de7a9ba) - **optimizer**: add support for Spark's TRANSFORM clause *(PR [#4993](https://github.com/tobymao/sqlglot/pull/4993) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4991](https://github.com/tobymao/sqlglot/issues/4991) opened by [@karta0807913](https://github.com/karta0807913)*\n\n### :bug: Bug Fixes\n- [`510984f`](https://github.com/tobymao/sqlglot/commit/510984f2ddc6ff13b8a8030f698aed9ad0e6f46b) - **hive**: stop generating redundant TO_DATE calls *(PR [#4990](https://github.com/tobymao/sqlglot/pull/4990) by [@georgesittas](https://github.com/georgesittas))*\n- [`da9ec61`](https://github.com/tobymao/sqlglot/commit/da9ec61e8edd5049e246390e1b638cf14d50fa2d) - **generator**: Fix pretty generation of exp.Window *(PR [#4994](https://github.com/tobymao/sqlglot/pull/4994) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4098](https://github.com/TobikoData/sqlmesh/issues/4098) opened by [@tanghyd](https://github.com/tanghyd)*\n- [`aae9aa8`](https://github.com/tobymao/sqlglot/commit/aae9aa8f96ccaa7686cda3cdabec208ae4c3d60a) - **optimizer**: ensure there are no shared refs after qualify_tables *(PR [#4995](https://github.com/tobymao/sqlglot/pull/4995) by [@georgesittas](https://github.com/georgesittas))*\n- [`adaef42`](https://github.com/tobymao/sqlglot/commit/adaef42234d8f1c9c331f53bee2c42686f29bdec) - **trino**: Dont quote identifiers in string literals for the partitioned_by property *(PR [#4998](https://github.com/tobymao/sqlglot/pull/4998) by [@erindru](https://github.com/erindru))*\n- [`a547f8d`](https://github.com/tobymao/sqlglot/commit/a547f8d4292f3b3a4c85f9d6466ead2ad976dfd2) - **postgres**: Capture optional minus sign in interval regex *(PR [#5000](https://github.com/tobymao/sqlglot/pull/5000) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4999](https://github.com/tobymao/sqlglot/issues/4999) opened by [@cpimhoff](https://github.com/cpimhoff)*\n\n### :recycle: Refactors\n- [`fb83fac`](https://github.com/tobymao/sqlglot/commit/fb83fac2d097d8d3e8e2556c072792857609bd94) - **optimizer**: remove recursion from `simplify` *(PR [#4988](https://github.com/tobymao/sqlglot/pull/4988) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`890b24a`](https://github.com/tobymao/sqlglot/commit/890b24a5cec269f5595743d0a86024a23217a3f1) - remove `connector_depth` as it is now dead code *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1dc501b`](https://github.com/tobymao/sqlglot/commit/1dc501b8ed68638375d869e11f3bf188948a4990) - remove `max_depth` argument in simplify as it is now dead code *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6572517`](https://github.com/tobymao/sqlglot/commit/6572517c1ec76f14cbd661aacc15c84bef065284) - improve tooling around benchmarks *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.15.0] - 2025-04-17\n### :boom: BREAKING CHANGES\n- due to [`2b7845a`](https://github.com/tobymao/sqlglot/commit/2b7845a3a821d366ae90ba9ef5e7d61194a34874) - Add support for Athena's Iceberg partitioning transforms *(PR [#4976](https://github.com/tobymao/sqlglot/pull/4976) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for Athena's Iceberg partitioning transforms (#4976)\n\n- due to [`ee794e9`](https://github.com/tobymao/sqlglot/commit/ee794e9c6a3b2fdb142114327d904b6c94a16cd0) - use the standard POWER function instead of ^ fixes [#4982](https://github.com/tobymao/sqlglot/pull/4982) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  use the standard POWER function instead of ^ fixes #4982\n\n- due to [`2369195`](https://github.com/tobymao/sqlglot/commit/2369195635e25dabd5ce26c13e402076508bba04) - consistently parse INTERVAL value as a string *(PR [#4986](https://github.com/tobymao/sqlglot/pull/4986) by [@georgesittas](https://github.com/georgesittas))*:\n\n  consistently parse INTERVAL value as a string (#4986)\n\n- due to [`e866cff`](https://github.com/tobymao/sqlglot/commit/e866cffbaac3b62255d0d5c8be043ab2394af619) - support RELY option for PRIMARY KEY, FOREIGN KEY, and UNIQUE constraints *(PR [#4987](https://github.com/tobymao/sqlglot/pull/4987) by [@geooo109](https://github.com/geooo109))*:\n\n  support RELY option for PRIMARY KEY, FOREIGN KEY, and UNIQUE constraints (#4987)\n\n\n### :sparkles: New Features\n- [`e866cff`](https://github.com/tobymao/sqlglot/commit/e866cffbaac3b62255d0d5c8be043ab2394af619) - **parser**: support RELY option for PRIMARY KEY, FOREIGN KEY, and UNIQUE constraints *(PR [#4987](https://github.com/tobymao/sqlglot/pull/4987) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#4983](https://github.com/tobymao/sqlglot/issues/4983) opened by [@ggadon](https://github.com/ggadon)*\n\n### :bug: Bug Fixes\n- [`2b7845a`](https://github.com/tobymao/sqlglot/commit/2b7845a3a821d366ae90ba9ef5e7d61194a34874) - Add support for Athena's Iceberg partitioning transforms *(PR [#4976](https://github.com/tobymao/sqlglot/pull/4976) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`fa6af23`](https://github.com/tobymao/sqlglot/commit/fa6af2302f8482c5d89ead481afe4195aaa41a9c) - **optimizer**: compare the whole type to determine if a cast can be removed *(PR [#4981](https://github.com/tobymao/sqlglot/pull/4981) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4977](https://github.com/tobymao/sqlglot/issues/4977) opened by [@MeinAccount](https://github.com/MeinAccount)*\n- [`830c9b8`](https://github.com/tobymao/sqlglot/commit/830c9b8bbf906cf5d4fa8028b67dadda73fc58a9) - **unnest_subqueries**: avoid adding GROUP BY on aggregate projections in lateral subqueries *(PR [#4970](https://github.com/tobymao/sqlglot/pull/4970) by [@skadel](https://github.com/skadel))*\n- [`ee794e9`](https://github.com/tobymao/sqlglot/commit/ee794e9c6a3b2fdb142114327d904b6c94a16cd0) - **postgres**: use the standard POWER function instead of ^ fixes [#4982](https://github.com/tobymao/sqlglot/pull/4982) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`85e62b8`](https://github.com/tobymao/sqlglot/commit/85e62b88df2822797f527dce4eaa230c778cbe9e) - **bigquery**: Do not consume JOIN keywords after WITH OFFSET *(PR [#4984](https://github.com/tobymao/sqlglot/pull/4984) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2369195`](https://github.com/tobymao/sqlglot/commit/2369195635e25dabd5ce26c13e402076508bba04) - consistently parse INTERVAL value as a string *(PR [#4986](https://github.com/tobymao/sqlglot/pull/4986) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.14.0] - 2025-04-15\n### :boom: BREAKING CHANGES\n- due to [`cb20038`](https://github.com/tobymao/sqlglot/commit/cb2003875fc6e149bd4a631e99c312a04435a46b) - treat GO as command *(PR [#4978](https://github.com/tobymao/sqlglot/pull/4978) by [@georgesittas](https://github.com/georgesittas))*:\n\n  treat GO as command (#4978)\n\n- due to [`60e26b8`](https://github.com/tobymao/sqlglot/commit/60e26b868242a05a7fdc2725bd21a127910a6fb7) - improve transpilability of GET_JSON_OBJECT by parsing json path *(PR [#4980](https://github.com/tobymao/sqlglot/pull/4980) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve transpilability of GET_JSON_OBJECT by parsing json path (#4980)\n\n\n### :bug: Bug Fixes\n- [`cb20038`](https://github.com/tobymao/sqlglot/commit/cb2003875fc6e149bd4a631e99c312a04435a46b) - **tsql**: treat GO as command *(PR [#4978](https://github.com/tobymao/sqlglot/pull/4978) by [@georgesittas](https://github.com/georgesittas))*\n- [`60e26b8`](https://github.com/tobymao/sqlglot/commit/60e26b868242a05a7fdc2725bd21a127910a6fb7) - **hive**: improve transpilability of GET_JSON_OBJECT by parsing json path *(PR [#4980](https://github.com/tobymao/sqlglot/pull/4980) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.13.2] - 2025-04-14\n### :bug: Bug Fixes\n- [`9693dbd`](https://github.com/tobymao/sqlglot/commit/9693dbd18b98b2699cade738a254f71f2ee8ce74) - **clickhouse**: avoid superfluous parentheses in DISTINCT ON (...) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.13.1] - 2025-04-14\n### :sparkles: New Features\n- [`a762993`](https://github.com/tobymao/sqlglot/commit/a762993c53d7ae91a831a8be448010e17e60f497) - **generator**: unsupported warning for T-SQL query option *(PR [#4972](https://github.com/tobymao/sqlglot/pull/4972) by [@geooo109](https://github.com/geooo109))*\n\n### :bug: Bug Fixes\n- [`61bc01c`](https://github.com/tobymao/sqlglot/commit/61bc01ceec2f801490f3f1a571aee655c5109962) - **clickhouse**: allow string literal for clickhouse ON CLUSTER clause *(PR [#4971](https://github.com/tobymao/sqlglot/pull/4971) by [@lepfhty](https://github.com/lepfhty))*\n- [`1353b79`](https://github.com/tobymao/sqlglot/commit/1353b79bd9810788a02163928b044fe038267078) - **Snowflake**: Enhance parity for FILE_FORMAT & CREDENTIALS in CREATE STAGE *(PR [#4969](https://github.com/tobymao/sqlglot/pull/4969) by [@whummer](https://github.com/whummer))*\n\n\n## [v26.13.0] - 2025-04-11\n### :boom: BREAKING CHANGES\n- due to [`1df7f61`](https://github.com/tobymao/sqlglot/commit/1df7f611bc96616cb07950a80f6669d0bc331b0e) - refactor length_sql so it handles any type, not just varchar/blob *(PR [#4935](https://github.com/tobymao/sqlglot/pull/4935) by [@tekumara](https://github.com/tekumara))*:\n\n  refactor length_sql so it handles any type, not just varchar/blob (#4935)\n\n- due to [`52719f3`](https://github.com/tobymao/sqlglot/commit/52719f37f6541e8ec9f66642ac23ed9015048092) - parse CREATE STAGE *(PR [#4947](https://github.com/tobymao/sqlglot/pull/4947) by [@tekumara](https://github.com/tekumara))*:\n\n  parse CREATE STAGE (#4947)\n\n- due to [`fd39b30`](https://github.com/tobymao/sqlglot/commit/fd39b30209d068b787619b8137a105aca9c3e607) - parse CREATE FILE FORMAT *(PR [#4948](https://github.com/tobymao/sqlglot/pull/4948) by [@tekumara](https://github.com/tekumara))*:\n\n  parse CREATE FILE FORMAT (#4948)\n\n- due to [`f835756`](https://github.com/tobymao/sqlglot/commit/f835756257f735643584b89e93693e8577744731) - Fix CREATE EXTERNAL TABLE properties *(PR [#4951](https://github.com/tobymao/sqlglot/pull/4951) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix CREATE EXTERNAL TABLE properties (#4951)\n\n- due to [`44b955b`](https://github.com/tobymao/sqlglot/commit/44b955bd537bfb8f5b6e84ecbcd5f6e3da852260) - Fix generation of exp.Values *(PR [#4930](https://github.com/tobymao/sqlglot/pull/4930) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix generation of exp.Values (#4930)\n\n- due to [`1f506b1`](https://github.com/tobymao/sqlglot/commit/1f506b186f1b954829195eefda318e231d474208) - support SHOW (ALL) TABLES *(PR [#4961](https://github.com/tobymao/sqlglot/pull/4961) by [@mscolnick](https://github.com/mscolnick))*:\n\n  support SHOW (ALL) TABLES (#4961)\n\n- due to [`72cf4a4`](https://github.com/tobymao/sqlglot/commit/72cf4a4501a8d122041a28b71be5a41ffb53602a) - Add support for PIVOT multiple IN clauses *(PR [#4964](https://github.com/tobymao/sqlglot/pull/4964) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for PIVOT multiple IN clauses (#4964)\n\n- due to [`400ea54`](https://github.com/tobymao/sqlglot/commit/400ea54d3a9cab256bfa5e496439bb9be6072d0b) - ensure JSON_FORMAT type is JSON when targeting Presto *(PR [#4968](https://github.com/tobymao/sqlglot/pull/4968) by [@georgesittas](https://github.com/georgesittas))*:\n\n  ensure JSON_FORMAT type is JSON when targeting Presto (#4968)\n\n\n### :sparkles: New Features\n- [`52719f3`](https://github.com/tobymao/sqlglot/commit/52719f37f6541e8ec9f66642ac23ed9015048092) - **snowflake**: parse CREATE STAGE *(PR [#4947](https://github.com/tobymao/sqlglot/pull/4947) by [@tekumara](https://github.com/tekumara))*\n- [`fd39b30`](https://github.com/tobymao/sqlglot/commit/fd39b30209d068b787619b8137a105aca9c3e607) - **snowflake**: parse CREATE FILE FORMAT *(PR [#4948](https://github.com/tobymao/sqlglot/pull/4948) by [@tekumara](https://github.com/tekumara))*\n- [`da9a6a1`](https://github.com/tobymao/sqlglot/commit/da9a6a1d56323319b87e9b193d12ad1c644b9239) - **snowflake**: parse SHOW STAGES *(PR [#4949](https://github.com/tobymao/sqlglot/pull/4949) by [@tekumara](https://github.com/tekumara))*\n- [`bfdcdf0`](https://github.com/tobymao/sqlglot/commit/bfdcdf0afc0f4af3dacdfc3e8dca243793552b74) - **snowflake**: parse SHOW FILE FORMATS *(PR [#4950](https://github.com/tobymao/sqlglot/pull/4950) by [@tekumara](https://github.com/tekumara))*\n- [`c591443`](https://github.com/tobymao/sqlglot/commit/c591443b6b2328780e08179144557e181db0cbb6) - **duckdb**: add support for GROUP clause in standard PIVOT syntax *(PR [#4953](https://github.com/tobymao/sqlglot/pull/4953) by [@georgesittas](https://github.com/georgesittas))*\n- [`b011ee2`](https://github.com/tobymao/sqlglot/commit/b011ee2df0beaac75b982261a25d3e787dead54a) - **bigquery**: Add support for side & kind on set operators *(PR [#4959](https://github.com/tobymao/sqlglot/pull/4959) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4942](https://github.com/tobymao/sqlglot/issues/4942) opened by [@z3z1ma](https://github.com/z3z1ma)*\n- [`1f506b1`](https://github.com/tobymao/sqlglot/commit/1f506b186f1b954829195eefda318e231d474208) - **duckdb**: support SHOW (ALL) TABLES *(PR [#4961](https://github.com/tobymao/sqlglot/pull/4961) by [@mscolnick](https://github.com/mscolnick))*\n  - :arrow_lower_right: *addresses issue [#4956](https://github.com/tobymao/sqlglot/issues/4956) opened by [@mscolnick](https://github.com/mscolnick)*\n- [`ad5b595`](https://github.com/tobymao/sqlglot/commit/ad5b595049a16a27a7f249afea43dbcfcf43b5f4) - allow explicit aliasing in if(...) expressions *(PR [#4963](https://github.com/tobymao/sqlglot/pull/4963) by [@georgesittas](https://github.com/georgesittas))*\n- [`72cf4a4`](https://github.com/tobymao/sqlglot/commit/72cf4a4501a8d122041a28b71be5a41ffb53602a) - **duckdb**: Add support for PIVOT multiple IN clauses *(PR [#4964](https://github.com/tobymao/sqlglot/pull/4964) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4944](https://github.com/tobymao/sqlglot/issues/4944) opened by [@nph](https://github.com/nph)*\n- [`7bc5a21`](https://github.com/tobymao/sqlglot/commit/7bc5a217c3cc68d0cb1eaedc0c18f5188de80bf1) - **postgres**: support laterals with ordinality fixes [#4965](https://github.com/tobymao/sqlglot/pull/4965) *(PR [#4966](https://github.com/tobymao/sqlglot/pull/4966) by [@georgesittas](https://github.com/georgesittas))*\n- [`400ea54`](https://github.com/tobymao/sqlglot/commit/400ea54d3a9cab256bfa5e496439bb9be6072d0b) - ensure JSON_FORMAT type is JSON when targeting Presto *(PR [#4968](https://github.com/tobymao/sqlglot/pull/4968) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4967](https://github.com/tobymao/sqlglot/issues/4967) opened by [@jmsmdy](https://github.com/jmsmdy)*\n\n### :bug: Bug Fixes\n- [`7042603`](https://github.com/tobymao/sqlglot/commit/7042603ecb5693795b15219ec9cebf2f76032c03) - **optimizer**: Merge subqueries when inner query has name conflict with outer query *(PR [#4931](https://github.com/tobymao/sqlglot/pull/4931) by [@barakalon](https://github.com/barakalon))*\n- [`1df7f61`](https://github.com/tobymao/sqlglot/commit/1df7f611bc96616cb07950a80f6669d0bc331b0e) - **duckdb**: refactor length_sql so it handles any type, not just varchar/blob *(PR [#4935](https://github.com/tobymao/sqlglot/pull/4935) by [@tekumara](https://github.com/tekumara))*\n  - :arrow_lower_right: *fixes issue [#4934](https://github.com/tobymao/sqlglot/issues/4934) opened by [@tekumara](https://github.com/tekumara)*\n- [`09882e3`](https://github.com/tobymao/sqlglot/commit/09882e32f057670a9cbd97c1e5cf1a00c774b5d2) - **tsql**: remove assert call from _build_formatted_time *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bf39a95`](https://github.com/tobymao/sqlglot/commit/bf39a95426ed6637e424da1be070cc9a8affc358) - **sqlite**: transpile double quoted PRIMARY KEY *(PR [#4941](https://github.com/tobymao/sqlglot/pull/4941) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4938](https://github.com/tobymao/sqlglot/issues/4938) opened by [@rgeronimi](https://github.com/rgeronimi)*\n- [`f835756`](https://github.com/tobymao/sqlglot/commit/f835756257f735643584b89e93693e8577744731) - **snowflake**: Fix CREATE EXTERNAL TABLE properties *(PR [#4951](https://github.com/tobymao/sqlglot/pull/4951) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4945](https://github.com/tobymao/sqlglot/issues/4945) opened by [@tekumara](https://github.com/tekumara)*\n- [`61ed971`](https://github.com/tobymao/sqlglot/commit/61ed971213c979c3777e57853bd6989bc169adb1) - **athena**: Correctly handle CTAS queries that contain Union's *(PR [#4955](https://github.com/tobymao/sqlglot/pull/4955) by [@erindru](https://github.com/erindru))*\n- [`44b955b`](https://github.com/tobymao/sqlglot/commit/44b955bd537bfb8f5b6e84ecbcd5f6e3da852260) - **clickhouse**: Fix generation of exp.Values *(PR [#4930](https://github.com/tobymao/sqlglot/pull/4930) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4056](https://github.com/TobikoData/sqlmesh/issues/4056) opened by [@dnbnero](https://github.com/dnbnero)*\n\n\n## [v26.12.0] - 2025-03-27\n### :boom: BREAKING CHANGES\n- due to [`8a692b9`](https://github.com/tobymao/sqlglot/commit/8a692b9b5b7982ed54444bddfe974e5f629183ff) - support select...into #temp_table syntax *(PR [#4893](https://github.com/tobymao/sqlglot/pull/4893) by [@hhubbell](https://github.com/hhubbell))*:\n\n  support select...into #temp_table syntax (#4893)\n\n- due to [`bcf311a`](https://github.com/tobymao/sqlglot/commit/bcf311a4af4b1a95e038befc0bc84627c4851e5f) - Preserve PARSE_JSON() *(PR [#4901](https://github.com/tobymao/sqlglot/pull/4901) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Preserve PARSE_JSON() (#4901)\n\n- due to [`937b7bd`](https://github.com/tobymao/sqlglot/commit/937b7bdd5b06daffee379390796c76ffb07c2588) - handle string interval values in DATE ADD/SUB *(PR [#4902](https://github.com/tobymao/sqlglot/pull/4902) by [@georgesittas](https://github.com/georgesittas))*:\n\n  handle string interval values in DATE ADD/SUB (#4902)\n\n- due to [`96749c1`](https://github.com/tobymao/sqlglot/commit/96749c144832b491f01de387cd2f7a9b769af626) - improve LATERAL VIEW EXPLODE transpilation *(PR [#4905](https://github.com/tobymao/sqlglot/pull/4905) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve LATERAL VIEW EXPLODE transpilation (#4905)\n\n- due to [`71c529a`](https://github.com/tobymao/sqlglot/commit/71c529a13db1690412829ac03b82ff72d44ce6c2) - disable lateral alias expansion for Oracle fixes [#4910](https://github.com/tobymao/sqlglot/pull/4910) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  disable lateral alias expansion for Oracle fixes #4910\n\n- due to [`f17004e`](https://github.com/tobymao/sqlglot/commit/f17004e1691c9d834e295452a960a6e3a2830e88) - only use ARRAY[...] syntax for Schema if parent is partitioned by prop *(PR [#4913](https://github.com/tobymao/sqlglot/pull/4913) by [@georgesittas](https://github.com/georgesittas))*:\n\n  only use ARRAY[...] syntax for Schema if parent is partitioned by prop (#4913)\n\n- due to [`2fbbf6a`](https://github.com/tobymao/sqlglot/commit/2fbbf6a8525385f53bcb3e588d665208ac6811c1) - infer timestamp function types as TIMESTAMPTZ for bigquery *(PR [#4914](https://github.com/tobymao/sqlglot/pull/4914) by [@georgesittas](https://github.com/georgesittas))*:\n\n  infer timestamp function types as TIMESTAMPTZ for bigquery (#4914)\n\n- due to [`c0b3448`](https://github.com/tobymao/sqlglot/commit/c0b3448e7a4ec46485dd65b7498855ab57e029ef) - parse at sign as ABS function *(PR [#4915](https://github.com/tobymao/sqlglot/pull/4915) by [@geooo109](https://github.com/geooo109))*:\n\n  parse at sign as ABS function (#4915)\n\n- due to [`aa9734d`](https://github.com/tobymao/sqlglot/commit/aa9734df473d1aed8e5a53a7ef8e4d3208c8296d) - improve pretty-formatting of IN (...) *(PR [#4920](https://github.com/tobymao/sqlglot/pull/4920) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve pretty-formatting of IN (...) (#4920)\n\n\n### :sparkles: New Features\n- [`1d6218e`](https://github.com/tobymao/sqlglot/commit/1d6218e386b3b1a5081272e179b8e48ec57153b4) - **snowflake**: add support for CREATE USING TEMPLATE closes [#4883](https://github.com/tobymao/sqlglot/pull/4883) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`fa9f9bd`](https://github.com/tobymao/sqlglot/commit/fa9f9bde626ddb3b8b5ad3dedc6aa3399b8c1716) - **tsql**: allow MERGE to be used in place of a subquery *(PR [#4890](https://github.com/tobymao/sqlglot/pull/4890) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4884](https://github.com/tobymao/sqlglot/issues/4884) opened by [@AndysonEjvind](https://github.com/AndysonEjvind)*\n- [`0de4503`](https://github.com/tobymao/sqlglot/commit/0de4503655ae9169ae02fdc8c48fb1edcd868cc8) - add a check and error message for set operations in pushdown_projections *(PR [#4897](https://github.com/tobymao/sqlglot/pull/4897) by [@snovik75](https://github.com/snovik75))*\n- [`96749c1`](https://github.com/tobymao/sqlglot/commit/96749c144832b491f01de387cd2f7a9b769af626) - **presto**: improve LATERAL VIEW EXPLODE transpilation *(PR [#4905](https://github.com/tobymao/sqlglot/pull/4905) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4879](https://github.com/tobymao/sqlglot/issues/4879) opened by [@Juanpeterjuanpa](https://github.com/Juanpeterjuanpa)*\n- [`c0b3448`](https://github.com/tobymao/sqlglot/commit/c0b3448e7a4ec46485dd65b7498855ab57e029ef) - **duckdb**: parse at sign as ABS function *(PR [#4915](https://github.com/tobymao/sqlglot/pull/4915) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#4912](https://github.com/tobymao/sqlglot/issues/4912) opened by [@suresh-summation](https://github.com/suresh-summation)*\n- [`aa9734d`](https://github.com/tobymao/sqlglot/commit/aa9734df473d1aed8e5a53a7ef8e4d3208c8296d) - **generator**: improve pretty-formatting of IN (...) *(PR [#4920](https://github.com/tobymao/sqlglot/pull/4920) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4015](https://github.com/TobikoData/sqlmesh/issues/4015) opened by [@petrikoro](https://github.com/petrikoro)*\n\n### :bug: Bug Fixes\n- [`1617509`](https://github.com/tobymao/sqlglot/commit/1617509d44124ffaba7eaf139023df07c3ad1636) - **bigquery**: preserve time zone info in FORMAT_TIMESTAMP roundtrip *(PR [#4895](https://github.com/tobymao/sqlglot/pull/4895) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4894](https://github.com/tobymao/sqlglot/issues/4894) opened by [@lenare1K5](https://github.com/lenare1K5)*\n- [`8a692b9`](https://github.com/tobymao/sqlglot/commit/8a692b9b5b7982ed54444bddfe974e5f629183ff) - **tsql**: support select...into #temp_table syntax *(PR [#4893](https://github.com/tobymao/sqlglot/pull/4893) by [@hhubbell](https://github.com/hhubbell))*\n- [`bcf311a`](https://github.com/tobymao/sqlglot/commit/bcf311a4af4b1a95e038befc0bc84627c4851e5f) - **databricks**: Preserve PARSE_JSON() *(PR [#4901](https://github.com/tobymao/sqlglot/pull/4901) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4898](https://github.com/tobymao/sqlglot/issues/4898) opened by [@h2o1](https://github.com/h2o1)*\n- [`3040a5e`](https://github.com/tobymao/sqlglot/commit/3040a5e4ebc778795251a74cf3de2169337aca55) - preserve whitespace in quoted identifiers and strings *(PR [#4903](https://github.com/tobymao/sqlglot/pull/4903) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4900](https://github.com/tobymao/sqlglot/issues/4900) opened by [@npochhi](https://github.com/npochhi)*\n- [`937b7bd`](https://github.com/tobymao/sqlglot/commit/937b7bdd5b06daffee379390796c76ffb07c2588) - **hive**: handle string interval values in DATE ADD/SUB *(PR [#4902](https://github.com/tobymao/sqlglot/pull/4902) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4899](https://github.com/tobymao/sqlglot/issues/4899) opened by [@ricardo-rolo](https://github.com/ricardo-rolo)*\n- [`71c529a`](https://github.com/tobymao/sqlglot/commit/71c529a13db1690412829ac03b82ff72d44ce6c2) - **optimizer**: disable lateral alias expansion for Oracle fixes [#4910](https://github.com/tobymao/sqlglot/pull/4910) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`3b7c699`](https://github.com/tobymao/sqlglot/commit/3b7c699267bf4f041a033017a894f0c1e2ae4068) - **snowflake**: quote identifiers in stage references *(PR [#4906](https://github.com/tobymao/sqlglot/pull/4906) by [@whummer](https://github.com/whummer))*\n- [`f17004e`](https://github.com/tobymao/sqlglot/commit/f17004e1691c9d834e295452a960a6e3a2830e88) - **presto**: only use ARRAY[...] syntax for Schema if parent is partitioned by prop *(PR [#4913](https://github.com/tobymao/sqlglot/pull/4913) by [@georgesittas](https://github.com/georgesittas))*\n- [`2fbbf6a`](https://github.com/tobymao/sqlglot/commit/2fbbf6a8525385f53bcb3e588d665208ac6811c1) - **optimizer**: infer timestamp function types as TIMESTAMPTZ for bigquery *(PR [#4914](https://github.com/tobymao/sqlglot/pull/4914) by [@georgesittas](https://github.com/georgesittas))*\n- [`ff6be71`](https://github.com/tobymao/sqlglot/commit/ff6be715b7d44700b595bbd5c83f65c28b52e191) - **optimizer**: avoid merging window function nested under a projection *(PR [#4919](https://github.com/tobymao/sqlglot/pull/4919) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4907](https://github.com/tobymao/sqlglot/issues/4907) opened by [@Rejudge-F](https://github.com/Rejudge-F)*\n\n### :recycle: Refactors\n- [`d386f37`](https://github.com/tobymao/sqlglot/commit/d386f374a6108ecce4e48324fe487c0955ab63b3) - **sqlite**: move generator methods within SQLite class *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.11.1] - 2025-03-18\n### :bug: Bug Fixes\n- [`d7b3b3e`](https://github.com/tobymao/sqlglot/commit/d7b3b3e89720d1783d092a2c60a9c2209d9984a2) - **optimizer**: handle TableFromRows properly in annotate_types *(PR [#4889](https://github.com/tobymao/sqlglot/pull/4889) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4004](https://github.com/TobikoData/sqlmesh/issues/4004) opened by [@hmeng-taproot](https://github.com/hmeng-taproot)*\n\n\n## [v26.11.0] - 2025-03-17\n### :boom: BREAKING CHANGES\n- due to [`ac3d311`](https://github.com/tobymao/sqlglot/commit/ac3d311c4184ca2ced603a100588e3e7435ce352) - do not expand having expressions if they conflict with a projection *(PR [#4881](https://github.com/tobymao/sqlglot/pull/4881) by [@tobymao](https://github.com/tobymao))*:\n\n  do not expand having expressions if they conflict with a projection (#4881)\n\n- due to [`081994e`](https://github.com/tobymao/sqlglot/commit/081994ea85c7aa1cbbbc40a24857dba4fd6c1c61) - Fix parsing multi-part format name *(PR [#4885](https://github.com/tobymao/sqlglot/pull/4885) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix parsing multi-part format name (#4885)\n\n- due to [`491c407`](https://github.com/tobymao/sqlglot/commit/491c407d48a24b6d4093e9c9bfdc3d8c27c29e4c) - parse parameter key as Var instead of Identifier *(PR [#4888](https://github.com/tobymao/sqlglot/pull/4888) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse parameter key as Var instead of Identifier (#4888)\n\n\n### :bug: Bug Fixes\n- [`ac3d311`](https://github.com/tobymao/sqlglot/commit/ac3d311c4184ca2ced603a100588e3e7435ce352) - do not expand having expressions if they conflict with a projection *(PR [#4881](https://github.com/tobymao/sqlglot/pull/4881) by [@tobymao](https://github.com/tobymao))*\n- [`44b7b09`](https://github.com/tobymao/sqlglot/commit/44b7b09deca881e274ad03068eee5d4d594c8ca8) - **parser**: Fix separator generation for STRING_AGG *(PR [#4887](https://github.com/tobymao/sqlglot/pull/4887) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`081994e`](https://github.com/tobymao/sqlglot/commit/081994ea85c7aa1cbbbc40a24857dba4fd6c1c61) - **snowflake**: Fix parsing multi-part format name *(PR [#4885](https://github.com/tobymao/sqlglot/pull/4885) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4882](https://github.com/tobymao/sqlglot/issues/4882) opened by [@kharigardner](https://github.com/kharigardner)*\n- [`38111a5`](https://github.com/tobymao/sqlglot/commit/38111a5eaa6bde640e25aa408ff7ea9ea6864c0b) - apply unpivot alias string conversion only for UNPIVOT *(PR [#4886](https://github.com/tobymao/sqlglot/pull/4886) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4003](https://github.com/TobikoData/sqlmesh/issues/4003) opened by [@lucargir](https://github.com/lucargir)*\n- [`491c407`](https://github.com/tobymao/sqlglot/commit/491c407d48a24b6d4093e9c9bfdc3d8c27c29e4c) - **clickhouse**: parse parameter key as Var instead of Identifier *(PR [#4888](https://github.com/tobymao/sqlglot/pull/4888) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4002](https://github.com/TobikoData/sqlmesh/issues/4002) opened by [@petrjanda](https://github.com/petrjanda)*\n\n\n## [v26.10.1] - 2025-03-13\n### :bug: Bug Fixes\n- [`2b3824f`](https://github.com/tobymao/sqlglot/commit/2b3824f0bac5dae48ea7eecbe2168afe79038d06) - **duckdb**: revert timestamp/datetime -> timestampntz parsing temporarily *(PR [#4878](https://github.com/tobymao/sqlglot/pull/4878) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.10.0] - 2025-03-13\n### :boom: BREAKING CHANGES\n- due to [`c0bfcc6`](https://github.com/tobymao/sqlglot/commit/c0bfcc66b97ce667a1ead608c4fbbee69db633fa) - postgres case insesitive formats closes [#4860](https://github.com/tobymao/sqlglot/pull/4860) *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  postgres case insesitive formats closes #4860\n\n- due to [`6914684`](https://github.com/tobymao/sqlglot/commit/69146842d005ae0edecbd7f6f842f648ae0622e7) - duckdb defaults timestampntz closes [#4859](https://github.com/tobymao/sqlglot/pull/4859) *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  duckdb defaults timestampntz closes #4859\n\n- due to [`ceb1f02`](https://github.com/tobymao/sqlglot/commit/ceb1f026dd04926a6a210de9d16da4dffef4717c) - support TO_CHAR to duckdb STRFTIME *(PR [#4866](https://github.com/tobymao/sqlglot/pull/4866) by [@geooo109](https://github.com/geooo109))*:\n\n  support TO_CHAR to duckdb STRFTIME (#4866)\n\n- due to [`d748e53`](https://github.com/tobymao/sqlglot/commit/d748e53f6a77196bef6550b6d9fddf41076c01fa) - Introduce pyproject.toml and switch to packaging via build *(PR [#4865](https://github.com/tobymao/sqlglot/pull/4865) by [@erindru](https://github.com/erindru))*:\n\n  Introduce pyproject.toml and switch to packaging via build (#4865)\n\n- due to [`038da09`](https://github.com/tobymao/sqlglot/commit/038da09f620cf057e4576b719c4e2f6712cbb804) - treat TABLE(...) as a UDTF *(PR [#4875](https://github.com/tobymao/sqlglot/pull/4875) by [@georgesittas](https://github.com/georgesittas))*:\n\n  treat TABLE(...) as a UDTF (#4875)\n\n- due to [`92e479e`](https://github.com/tobymao/sqlglot/commit/92e479ea7d70efc4bdccd17cb12b719aec603830) - support STRUCT(*) and MAP(*) *(PR [#4876](https://github.com/tobymao/sqlglot/pull/4876) by [@geooo109](https://github.com/geooo109))*:\n\n  support STRUCT(*) and MAP(*) (#4876)\n\n- due to [`87c94fe`](https://github.com/tobymao/sqlglot/commit/87c94fe91aa2a4bc2c255191d92aed450f3c7998) - turn off multi-arg coalesce simplification *(PR [#4877](https://github.com/tobymao/sqlglot/pull/4877) by [@georgesittas](https://github.com/georgesittas))*:\n\n  turn off multi-arg coalesce simplification (#4877)\n\n\n### :sparkles: New Features\n- [`54be278`](https://github.com/tobymao/sqlglot/commit/54be278361496367fb2f7d380634d3390879e58d) - **snowflake**: add support for HEX_DECODE_BINARY *(PR [#4855](https://github.com/tobymao/sqlglot/pull/4855) by [@sk-](https://github.com/sk-))*\n  - :arrow_lower_right: *addresses issue [#4852](https://github.com/tobymao/sqlglot/issues/4852) opened by [@sk-](https://github.com/sk-)*\n- [`47959a9`](https://github.com/tobymao/sqlglot/commit/47959a94a4693cb904cfb2e50ce8cc8ca5c2e22f) - **duckdb**: add support for prefix aliases *(PR [#4869](https://github.com/tobymao/sqlglot/pull/4869) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`e942391`](https://github.com/tobymao/sqlglot/commit/e942391edcefb40f927887450765b4365b0e980d) - spark zone offset closes [#4858](https://github.com/tobymao/sqlglot/pull/4858) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`c0bfcc6`](https://github.com/tobymao/sqlglot/commit/c0bfcc66b97ce667a1ead608c4fbbee69db633fa) - postgres case insesitive formats closes [#4860](https://github.com/tobymao/sqlglot/pull/4860) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`6914684`](https://github.com/tobymao/sqlglot/commit/69146842d005ae0edecbd7f6f842f648ae0622e7) - duckdb defaults timestampntz closes [#4859](https://github.com/tobymao/sqlglot/pull/4859) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`ceb1f02`](https://github.com/tobymao/sqlglot/commit/ceb1f026dd04926a6a210de9d16da4dffef4717c) - **snowflake**: support TO_CHAR to duckdb STRFTIME *(PR [#4866](https://github.com/tobymao/sqlglot/pull/4866) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4857](https://github.com/tobymao/sqlglot/issues/4857) opened by [@asarama](https://github.com/asarama)*\n- [`80466f1`](https://github.com/tobymao/sqlglot/commit/80466f16aa081860bc9e65f425924a0620840cdf) - expand util - align normalization behaviour with lazy and non-lazy source providers. *(PR [#4874](https://github.com/tobymao/sqlglot/pull/4874) by [@omerhadari](https://github.com/omerhadari))*\n- [`038da09`](https://github.com/tobymao/sqlglot/commit/038da09f620cf057e4576b719c4e2f6712cbb804) - **snowflake**: treat TABLE(...) as a UDTF *(PR [#4875](https://github.com/tobymao/sqlglot/pull/4875) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4861](https://github.com/tobymao/sqlglot/issues/4861) opened by [@mattijsdp](https://github.com/mattijsdp)*\n- [`92e479e`](https://github.com/tobymao/sqlglot/commit/92e479ea7d70efc4bdccd17cb12b719aec603830) - **hive**: support STRUCT(*) and MAP(*) *(PR [#4876](https://github.com/tobymao/sqlglot/pull/4876) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4871](https://github.com/tobymao/sqlglot/issues/4871) opened by [@btyuhas](https://github.com/btyuhas)*\n- [`87c94fe`](https://github.com/tobymao/sqlglot/commit/87c94fe91aa2a4bc2c255191d92aed450f3c7998) - **redshift**: turn off multi-arg coalesce simplification *(PR [#4877](https://github.com/tobymao/sqlglot/pull/4877) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`d748e53`](https://github.com/tobymao/sqlglot/commit/d748e53f6a77196bef6550b6d9fddf41076c01fa) - Introduce pyproject.toml and switch to packaging via build *(PR [#4865](https://github.com/tobymao/sqlglot/pull/4865) by [@erindru](https://github.com/erindru))*\n\n\n## [v26.9.0] - 2025-03-07\n### :boom: BREAKING CHANGES\n- due to [`6a3973b`](https://github.com/tobymao/sqlglot/commit/6a3973b7da639a19634bc352ea76f75735114c38) - Refactor exp.GroupConcat generation *(PR [#4823](https://github.com/tobymao/sqlglot/pull/4823) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Refactor exp.GroupConcat generation (#4823)\n\n- due to [`813d2ad`](https://github.com/tobymao/sqlglot/commit/813d2ada7afd653b2aaff75cbddd7f011750f861) - use _parse_table_parts for udf parsing *(PR [#4829](https://github.com/tobymao/sqlglot/pull/4829) by [@geooo109](https://github.com/geooo109))*:\n\n  use _parse_table_parts for udf parsing (#4829)\n\n- due to [`7cdbad6`](https://github.com/tobymao/sqlglot/commit/7cdbad688cad7e7ce40df99802e93deb6a4d7abf) - add initial support for PUT statements *(PR [#4818](https://github.com/tobymao/sqlglot/pull/4818) by [@whummer](https://github.com/whummer))*:\n\n  add initial support for PUT statements (#4818)\n\n- due to [`8c0a6be`](https://github.com/tobymao/sqlglot/commit/8c0a6bec6e38f3f6ce9a90b6a9b6457de70c7228) - BLOB transpilation *(PR [#4844](https://github.com/tobymao/sqlglot/pull/4844) by [@geooo109](https://github.com/geooo109))*:\n\n  BLOB transpilation (#4844)\n\n\n### :sparkles: New Features\n- [`7e8975e`](https://github.com/tobymao/sqlglot/commit/7e8975efce0af350142f8fb437cf46dd46f2b8d9) - **oracle**: add FORCE property *(PR [#4828](https://github.com/tobymao/sqlglot/pull/4828) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#4826](https://github.com/tobymao/sqlglot/issues/4826) opened by [@Duchyna1](https://github.com/Duchyna1)*\n- [`7cdbad6`](https://github.com/tobymao/sqlglot/commit/7cdbad688cad7e7ce40df99802e93deb6a4d7abf) - **snowflake**: add initial support for PUT statements *(PR [#4818](https://github.com/tobymao/sqlglot/pull/4818) by [@whummer](https://github.com/whummer))*\n  - :arrow_lower_right: *addresses issue [#4813](https://github.com/tobymao/sqlglot/issues/4813) opened by [@whummer](https://github.com/whummer)*\n- [`f4d1a1f`](https://github.com/tobymao/sqlglot/commit/f4d1a1f4d8104b2efd56f568ca99c7e768466d19) - **hive**: add support for STORED BY syntax for storage handlers *(PR [#4832](https://github.com/tobymao/sqlglot/pull/4832) by [@tsamaras](https://github.com/tsamaras))*\n- [`b7a0df1`](https://github.com/tobymao/sqlglot/commit/b7a0df1b9a9cff2cd57db77ac0095c189b9d67ab) - **parser**: Support trailing commas after from *(PR [#4854](https://github.com/tobymao/sqlglot/pull/4854) by [@omerhadari](https://github.com/omerhadari))*\n\n### :bug: Bug Fixes\n- [`6a3973b`](https://github.com/tobymao/sqlglot/commit/6a3973b7da639a19634bc352ea76f75735114c38) - **duckdb, snowflake**: Refactor exp.GroupConcat generation *(PR [#4823](https://github.com/tobymao/sqlglot/pull/4823) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4821](https://github.com/tobymao/sqlglot/issues/4821) opened by [@asarama](https://github.com/asarama)*\n- [`08eb7f2`](https://github.com/tobymao/sqlglot/commit/08eb7f2032957c2fe3119963f344538b90d8f631) - **snowflake**: clean up PUT implementation *(PR [#4830](https://github.com/tobymao/sqlglot/pull/4830) by [@georgesittas](https://github.com/georgesittas))*\n- [`adf2fef`](https://github.com/tobymao/sqlglot/commit/adf2fef27dc341508c3b9c710da0f835277094a1) - **mysql**: Support for USING BTREE/HASH in PK *(PR [#4837](https://github.com/tobymao/sqlglot/pull/4837) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4833](https://github.com/tobymao/sqlglot/issues/4833) opened by [@Gohoy](https://github.com/Gohoy)*\n- [`8c0a6be`](https://github.com/tobymao/sqlglot/commit/8c0a6bec6e38f3f6ce9a90b6a9b6457de70c7228) - **mysql**: BLOB transpilation *(PR [#4844](https://github.com/tobymao/sqlglot/pull/4844) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4839](https://github.com/tobymao/sqlglot/issues/4839) opened by [@Gohoy](https://github.com/Gohoy)*\n- [`0cb7a71`](https://github.com/tobymao/sqlglot/commit/0cb7a719de33ab1f6cfedf0833df7c79324b21f9) - **postgres**: Fix arrow extraction for string keys representing numbers *(PR [#4842](https://github.com/tobymao/sqlglot/pull/4842) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4840](https://github.com/tobymao/sqlglot/issues/4840) opened by [@superkashyap](https://github.com/superkashyap)*\n- [`2e223cb`](https://github.com/tobymao/sqlglot/commit/2e223cb3e0bc946b8aa97e115e4c0dc02e58d1c9) - **parser**: properly parse qualified columns when parsing \"columns ops\" *(PR [#4847](https://github.com/tobymao/sqlglot/pull/4847) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4845](https://github.com/tobymao/sqlglot/issues/4845) opened by [@maudlel](https://github.com/maudlel)*\n\n### :recycle: Refactors\n- [`813d2ad`](https://github.com/tobymao/sqlglot/commit/813d2ada7afd653b2aaff75cbddd7f011750f861) - use _parse_table_parts for udf parsing *(PR [#4829](https://github.com/tobymao/sqlglot/pull/4829) by [@geooo109](https://github.com/geooo109))*\n\n### :wrench: Chores\n- [`e4fd354`](https://github.com/tobymao/sqlglot/commit/e4fd354c8fb55752cb883eb3912950c17020a1df) - Simplify Hive's STORED BY property *(PR [#4838](https://github.com/tobymao/sqlglot/pull/4838) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`8115b58`](https://github.com/tobymao/sqlglot/commit/8115b5853e621423eb2697b7253b17ef709dbdf0) - (duckdb): treat auto-increment DDL property as unsupported *(PR [#4849](https://github.com/tobymao/sqlglot/pull/4849) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4841](https://github.com/tobymao/sqlglot/issues/4841) opened by [@sk-](https://github.com/sk-)*\n- [`b05dddb`](https://github.com/tobymao/sqlglot/commit/b05dddbe5a7d45dfebefc3e04cb95d8c4d9802e9) - fix pdoc deployment *(PR [#4856](https://github.com/tobymao/sqlglot/pull/4856) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4853](https://github.com/tobymao/sqlglot/issues/4853) opened by [@tsamaras](https://github.com/tsamaras)*\n\n\n## [v26.8.0] - 2025-03-03\n### :boom: BREAKING CHANGES\n- due to [`596b66f`](https://github.com/tobymao/sqlglot/commit/596b66fc289140109db8f689c6e84264d643a47a) - add support for and/2 and or/2 functions *(PR [#4806](https://github.com/tobymao/sqlglot/pull/4806) by [@georgesittas](https://github.com/georgesittas))*:\n\n  add support for and/2 and or/2 functions (#4806)\n\n- due to [`eae860c`](https://github.com/tobymao/sqlglot/commit/eae860ce5b59b9e0b791fe79686899efb83df1dd) - expand DISTINCT ON expressions like we do for GROUP/ORDER by *(PR [#4807](https://github.com/tobymao/sqlglot/pull/4807) by [@georgesittas](https://github.com/georgesittas))*:\n\n  expand DISTINCT ON expressions like we do for GROUP/ORDER by (#4807)\n\n- due to [`83e6a87`](https://github.com/tobymao/sqlglot/commit/83e6a87f8d233eac6d3bcd3a49451a14dc10e06e) - Parse SHA256 *(PR [#4816](https://github.com/tobymao/sqlglot/pull/4816) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Parse SHA256 (#4816)\n\n\n### :sparkles: New Features\n- [`50539ce`](https://github.com/tobymao/sqlglot/commit/50539ced46de3949f6a70acdab86129fb50c9385) - **trino**: add support for ON ... ERROR/NULL syntax for JSON_QUERY *(PR [#4805](https://github.com/tobymao/sqlglot/pull/4805) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3905](https://github.com/TobikoData/sqlmesh/issues/3905) opened by [@darkcofy](https://github.com/darkcofy)*\n- [`596b66f`](https://github.com/tobymao/sqlglot/commit/596b66fc289140109db8f689c6e84264d643a47a) - **clickhouse**: add support for and/2 and or/2 functions *(PR [#4806](https://github.com/tobymao/sqlglot/pull/4806) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4803](https://github.com/tobymao/sqlglot/issues/4803) opened by [@xtess16](https://github.com/xtess16)*\n- [`c5bf122`](https://github.com/tobymao/sqlglot/commit/c5bf122a6aa7ca315ad726e6ea3d4a98eebd68d0) - **mysql**: support setting visibility on `ALTER COLUMN`. *(PR [#4809](https://github.com/tobymao/sqlglot/pull/4809) by [@burnison](https://github.com/burnison))*\n\n### :bug: Bug Fixes\n- [`6441d00`](https://github.com/tobymao/sqlglot/commit/6441d0041ccec7f1c28763f5775b6195d2049dc6) - orphan node(s) in eliminate_join_marks *(PR [#4808](https://github.com/tobymao/sqlglot/pull/4808) by [@snovik75](https://github.com/snovik75))*\n- [`eae860c`](https://github.com/tobymao/sqlglot/commit/eae860ce5b59b9e0b791fe79686899efb83df1dd) - **optimizer**: expand DISTINCT ON expressions like we do for GROUP/ORDER by *(PR [#4807](https://github.com/tobymao/sqlglot/pull/4807) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4791](https://github.com/tobymao/sqlglot/issues/4791) opened by [@Fosly](https://github.com/Fosly)*\n- [`5ef35f2`](https://github.com/tobymao/sqlglot/commit/5ef35f2dc622d96b013a2651c71e1a32933f51cb) - **clickhouse**: unparseable `AggregateFunction(count)` *(PR [#4812](https://github.com/tobymao/sqlglot/pull/4812) by [@pkit](https://github.com/pkit))*\n- [`83e6a87`](https://github.com/tobymao/sqlglot/commit/83e6a87f8d233eac6d3bcd3a49451a14dc10e06e) - **duckdb**: Parse SHA256 *(PR [#4816](https://github.com/tobymao/sqlglot/pull/4816) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4815](https://github.com/tobymao/sqlglot/issues/4815) opened by [@muuuuwa](https://github.com/muuuuwa)*\n- [`faf6d41`](https://github.com/tobymao/sqlglot/commit/faf6d416afe30bf0bc24649fcceccf79fbfb8ca1) - allow duplicate nodes in matchings *(PR [#4817](https://github.com/tobymao/sqlglot/pull/4817) by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`e34f3bc`](https://github.com/tobymao/sqlglot/commit/e34f3bc99f832ce2affb3a0297329f3d1cd7244e) - **optimizer**: refactor DISTINCT ON qualification to better match ORDER BY *(PR [#4811](https://github.com/tobymao/sqlglot/pull/4811) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.7.0] - 2025-02-26\n### :boom: BREAKING CHANGES\n- due to [`466c839`](https://github.com/tobymao/sqlglot/commit/466c839c2cfc94b398dd619b738df165f2876cdb) - Remove extra MAP bracket and ARRAY wrap *(PR [#4712](https://github.com/tobymao/sqlglot/pull/4712) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Remove extra MAP bracket and ARRAY wrap (#4712)\n\n- due to [`79ab311`](https://github.com/tobymao/sqlglot/commit/79ab3116758c240786ab4353a26f1646e242a61b) - add generate_series table column alias *(PR [#4741](https://github.com/tobymao/sqlglot/pull/4741) by [@georgesittas](https://github.com/georgesittas))*:\n\n  add generate_series table column alias (#4741)\n\n- due to [`66b3ea9`](https://github.com/tobymao/sqlglot/commit/66b3ea905af34cfedc961c68ba738ba90d16221d) - respect type nullability when casting in strtodate_sql *(PR [#4744](https://github.com/tobymao/sqlglot/pull/4744) by [@sleshJdev](https://github.com/sleshJdev))*:\n\n  respect type nullability when casting in strtodate_sql (#4744)\n\n- due to [`91f47fe`](https://github.com/tobymao/sqlglot/commit/91f47fec2a8c727f7e4c93fa54b6f06a36a6b42f) - Support for exp.HexString in DuckDB/Presto/Trino *(PR [#4743](https://github.com/tobymao/sqlglot/pull/4743) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Support for exp.HexString in DuckDB/Presto/Trino (#4743)\n\n- due to [`0596176`](https://github.com/tobymao/sqlglot/commit/0596176dd59737f945624d6453259072917e2fee) - generate CASE expression instead of COUNT_IF for <v1.2 *(PR [#4755](https://github.com/tobymao/sqlglot/pull/4755) by [@georgesittas](https://github.com/georgesittas))*:\n\n  generate CASE expression instead of COUNT_IF for <v1.2 (#4755)\n\n- due to [`01008a9`](https://github.com/tobymao/sqlglot/commit/01008a91144a20e830df3783ff04beba7d029fec) - enable transpilation of CURDATE fixes [#4758](https://github.com/tobymao/sqlglot/pull/4758) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  enable transpilation of CURDATE fixes #4758\n\n- due to [`dc69192`](https://github.com/tobymao/sqlglot/commit/dc691923655d022f2a4b23e9df24a9d1518e7048) - column qualify not applying in an order by inside within group when it also the alias *(PR [#4780](https://github.com/tobymao/sqlglot/pull/4780) by [@ran-lakeway](https://github.com/ran-lakeway))*:\n\n  column qualify not applying in an order by inside within group when it also the alias (#4780)\n\n- due to [`ae00c92`](https://github.com/tobymao/sqlglot/commit/ae00c9203197a436bdb81289580eba0e7fc374ec) - properly alias generate series *(PR [#4789](https://github.com/tobymao/sqlglot/pull/4789) by [@georgesittas](https://github.com/georgesittas))*:\n\n  properly alias generate series (#4789)\n\n- due to [`14c3de9`](https://github.com/tobymao/sqlglot/commit/14c3de91072435a68dd8d0f024dff0a0fd236cb8) - parse unqualified names in DISTINCT ON (...) as identifiers *(PR [#4795](https://github.com/tobymao/sqlglot/pull/4795) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse unqualified names in DISTINCT ON (...) as identifiers (#4795)\n\n- due to [`00c7f05`](https://github.com/tobymao/sqlglot/commit/00c7f05f348eeb6b60d4e70d7e29e564caf38f64) - do not coerce parameterized types *(PR [#4796](https://github.com/tobymao/sqlglot/pull/4796) by [@georgesittas](https://github.com/georgesittas))*:\n\n  do not coerce parameterized types (#4796)\n\n- due to [`513fad4`](https://github.com/tobymao/sqlglot/commit/513fad47cd321fe8431e673351bdd5f0674d5af1) - show databases, functions, procedures and warehouses  *(PR [#4787](https://github.com/tobymao/sqlglot/pull/4787) by [@tekumara](https://github.com/tekumara))*:\n\n  show databases, functions, procedures and warehouses  (#4787)\n\n- due to [`a9ae2d2`](https://github.com/tobymao/sqlglot/commit/a9ae2d229640f22134410ca5826c6f19df7ef523) - support TOP with PERCENT and WITH TIES *(PR [#4801](https://github.com/tobymao/sqlglot/pull/4801) by [@geooo109](https://github.com/geooo109))*:\n\n  support TOP with PERCENT and WITH TIES (#4801)\n\n- due to [`e05983a`](https://github.com/tobymao/sqlglot/commit/e05983a62ee9b3689e8a277924f2b1e36744a2a9) - make hex literal tokenization more robust *(PR [#4802](https://github.com/tobymao/sqlglot/pull/4802) by [@georgesittas](https://github.com/georgesittas))*:\n\n  make hex literal tokenization more robust (#4802)\n\n- due to [`e9c8eae`](https://github.com/tobymao/sqlglot/commit/e9c8eae060c05d02f6f65916d59f1792098679c8) - bump sqlglotrs to 0.4.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.4.0\n\n\n### :sparkles: New Features\n- [`5c59943`](https://github.com/tobymao/sqlglot/commit/5c599434fbe4d4f1059196b1530d02d7d204d3a9) - **mysql**: add unsigned double type *(PR [#4734](https://github.com/tobymao/sqlglot/pull/4734) by [@GabrielVSMachado](https://github.com/GabrielVSMachado))*\n- [`31b2139`](https://github.com/tobymao/sqlglot/commit/31b21391cc521cf15a098c335d4202378ad96c0b) - **duckdb**: transpile JSONB into JSON *(PR [#4753](https://github.com/tobymao/sqlglot/pull/4753) by [@georgesittas](https://github.com/georgesittas))*\n- [`0596176`](https://github.com/tobymao/sqlglot/commit/0596176dd59737f945624d6453259072917e2fee) - **duckdb**: generate CASE expression instead of COUNT_IF for <v1.2 *(PR [#4755](https://github.com/tobymao/sqlglot/pull/4755) by [@georgesittas](https://github.com/georgesittas))*\n- [`2b3ec0f`](https://github.com/tobymao/sqlglot/commit/2b3ec0fbd6bd84dd893c244892fc1a63e2eab7bd) - **snowflake**: add support for USE SECONDARY ROLES *(PR [#4761](https://github.com/tobymao/sqlglot/pull/4761) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4760](https://github.com/tobymao/sqlglot/issues/4760) opened by [@merlindso](https://github.com/merlindso)*\n- [`2079bdb`](https://github.com/tobymao/sqlglot/commit/2079bdbb5d72d7fe02019a9ba6d1684403e30b00) - **postgres**: Add support for WITH RECURSIVE search options *(PR [#4773](https://github.com/tobymao/sqlglot/pull/4773) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4763](https://github.com/tobymao/sqlglot/issues/4763) opened by [@psanjeev-cellino](https://github.com/psanjeev-cellino)*\n- [`70eefc6`](https://github.com/tobymao/sqlglot/commit/70eefc694c20600cdd99d682393cae81812d0b00) - **duckdb**: add support for underscored numbers fixes [#4777](https://github.com/tobymao/sqlglot/pull/4777) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`f6be1d4`](https://github.com/tobymao/sqlglot/commit/f6be1d4b2c34ea4e3e98c3ae5e272e2f2b1912f3) - **clickhouse**: add support for dynamic json casting *(PR [#4784](https://github.com/tobymao/sqlglot/pull/4784) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4776](https://github.com/tobymao/sqlglot/issues/4776) opened by [@pkit](https://github.com/pkit)*\n- [`513fad4`](https://github.com/tobymao/sqlglot/commit/513fad47cd321fe8431e673351bdd5f0674d5af1) - **snowflake**: show databases, functions, procedures and warehouses  *(PR [#4787](https://github.com/tobymao/sqlglot/pull/4787) by [@tekumara](https://github.com/tekumara))*\n- [`575c979`](https://github.com/tobymao/sqlglot/commit/575c979b77831bd356cc7e9b39e78fcca2b344be) - **clickhouse**: support `GLOBAL NOT IN` *(PR [#4798](https://github.com/tobymao/sqlglot/pull/4798) by [@hdhoang](https://github.com/hdhoang))*\n- [`856faa9`](https://github.com/tobymao/sqlglot/commit/856faa9773dea3b214571932c7d65e5773c5dc76) - **mysql**: add support for `ALTER INDEX` *(PR [#4800](https://github.com/tobymao/sqlglot/pull/4800) by [@burnison](https://github.com/burnison))*\n\n### :bug: Bug Fixes\n- [`7cad7f0`](https://github.com/tobymao/sqlglot/commit/7cad7f06d676bf230238bf08aea91701e1fa1d95) - Add additional allowed tokens for parsing aggregate functions in DDL *(PR [#4727](https://github.com/tobymao/sqlglot/pull/4727) by [@dorranh](https://github.com/dorranh))*\n  - :arrow_lower_right: *fixes issue [#4723](https://github.com/tobymao/sqlglot/issues/4723) opened by [@dorranh](https://github.com/dorranh)*\n- [`466c839`](https://github.com/tobymao/sqlglot/commit/466c839c2cfc94b398dd619b738df165f2876cdb) - **duckdb**: Remove extra MAP bracket and ARRAY wrap *(PR [#4712](https://github.com/tobymao/sqlglot/pull/4712) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4710](https://github.com/tobymao/sqlglot/issues/4710) opened by [@eakmanrq](https://github.com/eakmanrq)*\n- [`2c7dbe2`](https://github.com/tobymao/sqlglot/commit/2c7dbe2f84fcba044881776ebdeed6ee980fe0dd) - **duckdb**: Fix INTERVAL roundtrip of DATE_ADD *(PR [#4732](https://github.com/tobymao/sqlglot/pull/4732) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4728](https://github.com/tobymao/sqlglot/issues/4728) opened by [@whao89](https://github.com/whao89)*\n- [`cbad48d`](https://github.com/tobymao/sqlglot/commit/cbad48d4102412ea3fd20272cf75974a8c53bb34) - infinite traversal in eliminate_qualify closes [#4735](https://github.com/tobymao/sqlglot/pull/4735) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`79ab311`](https://github.com/tobymao/sqlglot/commit/79ab3116758c240786ab4353a26f1646e242a61b) - **clickhouse**: add generate_series table column alias *(PR [#4741](https://github.com/tobymao/sqlglot/pull/4741) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4740](https://github.com/tobymao/sqlglot/issues/4740) opened by [@martijnthe](https://github.com/martijnthe)*\n- [`f0ce5ce`](https://github.com/tobymao/sqlglot/commit/f0ce5ce80d5a0ac24da4295a2f660bad9d7934be) - **snowflake**: unqualify ANY ORDER BY columns *(PR [#4737](https://github.com/tobymao/sqlglot/pull/4737) by [@georgesittas](https://github.com/georgesittas))*\n- [`66b3ea9`](https://github.com/tobymao/sqlglot/commit/66b3ea905af34cfedc961c68ba738ba90d16221d) - **clickhouse**: respect type nullability when casting in strtodate_sql *(PR [#4744](https://github.com/tobymao/sqlglot/pull/4744) by [@sleshJdev](https://github.com/sleshJdev))*\n- [`fda40a9`](https://github.com/tobymao/sqlglot/commit/fda40a9e670f30822dee05651d2336494c0d4f3b) - **clickhouse**: add BOTH if trim type is unspecified fixes [#4746](https://github.com/tobymao/sqlglot/pull/4746) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`91f47fe`](https://github.com/tobymao/sqlglot/commit/91f47fec2a8c727f7e4c93fa54b6f06a36a6b42f) - Support for exp.HexString in DuckDB/Presto/Trino *(PR [#4743](https://github.com/tobymao/sqlglot/pull/4743) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4739](https://github.com/tobymao/sqlglot/issues/4739) opened by [@sk-](https://github.com/sk-)*\n- [`f94656d`](https://github.com/tobymao/sqlglot/commit/f94656d06b118cee69ec8305fde948caa250cb69) - **clickhouse**: Generation of exp.ArrayConcat *(PR [#4754](https://github.com/tobymao/sqlglot/pull/4754) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4751](https://github.com/tobymao/sqlglot/issues/4751) opened by [@maximlipiev](https://github.com/maximlipiev)*\n- [`cf33711`](https://github.com/tobymao/sqlglot/commit/cf337114a2259058b2a8cbde22d0a560001df9a5) - **tokenizer-rs**: use is_whitespace in token scan loop *(PR [#4756](https://github.com/tobymao/sqlglot/pull/4756) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4748](https://github.com/tobymao/sqlglot/issues/4748) opened by [@gesoges0](https://github.com/gesoges0)*\n- [`01008a9`](https://github.com/tobymao/sqlglot/commit/01008a91144a20e830df3783ff04beba7d029fec) - **mysql**: enable transpilation of CURDATE fixes [#4758](https://github.com/tobymao/sqlglot/pull/4758) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1fbd108`](https://github.com/tobymao/sqlglot/commit/1fbd108e8e9b85ae873bb702ff8f73c5ae94b392) - **sqlite**: correct json key value pair separator *(PR [#4774](https://github.com/tobymao/sqlglot/pull/4774) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4770](https://github.com/tobymao/sqlglot/issues/4770) opened by [@ori-n](https://github.com/ori-n)*\n- [`ccdb92a`](https://github.com/tobymao/sqlglot/commit/ccdb92a7b676d3454cf9f6a18a2b0becec676169) - **optimizer**: do not qualify ctes that are pivoted *(PR [#4775](https://github.com/tobymao/sqlglot/pull/4775) by [@georgesittas](https://github.com/georgesittas))*\n- [`ec4e97c`](https://github.com/tobymao/sqlglot/commit/ec4e97c31c16a1c8b5eb1acfba0ef77f064505b4) - **parser**: Do not preemptively create chunks for comment only statements *(PR [#4772](https://github.com/tobymao/sqlglot/pull/4772) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4762](https://github.com/tobymao/sqlglot/issues/4762) opened by [@dbittenbender](https://github.com/dbittenbender)*\n- [`fc8aec3`](https://github.com/tobymao/sqlglot/commit/fc8aec35b211395ca603b2feabd1e5a5006fd677) - **parser**: Revert [#4772](https://github.com/tobymao/sqlglot/pull/4772) *(PR [#4783](https://github.com/tobymao/sqlglot/pull/4783) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`dc69192`](https://github.com/tobymao/sqlglot/commit/dc691923655d022f2a4b23e9df24a9d1518e7048) - **optimizer**: column qualify not applying in an order by inside within group when it also the alias *(PR [#4780](https://github.com/tobymao/sqlglot/pull/4780) by [@ran-lakeway](https://github.com/ran-lakeway))*\n- [`ae00c92`](https://github.com/tobymao/sqlglot/commit/ae00c9203197a436bdb81289580eba0e7fc374ec) - **postgres**: properly alias generate series *(PR [#4789](https://github.com/tobymao/sqlglot/pull/4789) by [@georgesittas](https://github.com/georgesittas))*\n- [`14c3de9`](https://github.com/tobymao/sqlglot/commit/14c3de91072435a68dd8d0f024dff0a0fd236cb8) - **optimizer**: parse unqualified names in DISTINCT ON (...) as identifiers *(PR [#4795](https://github.com/tobymao/sqlglot/pull/4795) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4791](https://github.com/tobymao/sqlglot/issues/4791) opened by [@Fosly](https://github.com/Fosly)*\n- [`00c7f05`](https://github.com/tobymao/sqlglot/commit/00c7f05f348eeb6b60d4e70d7e29e564caf38f64) - **optimizer**: do not coerce parameterized types *(PR [#4796](https://github.com/tobymao/sqlglot/pull/4796) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4792](https://github.com/tobymao/sqlglot/issues/4792) opened by [@georgesittas](https://github.com/georgesittas)*\n- [`9f6b4f7`](https://github.com/tobymao/sqlglot/commit/9f6b4f7dd6ea8eb80d8d5d3b75eeff9e262ceba2) - **snowflake**: add missing clauses in SHOW statements *(PR [#4797](https://github.com/tobymao/sqlglot/pull/4797) by [@georgesittas](https://github.com/georgesittas))*\n- [`a9ae2d2`](https://github.com/tobymao/sqlglot/commit/a9ae2d229640f22134410ca5826c6f19df7ef523) - **tsql**: support TOP with PERCENT and WITH TIES *(PR [#4801](https://github.com/tobymao/sqlglot/pull/4801) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4793](https://github.com/tobymao/sqlglot/issues/4793) opened by [@gleb-daax](https://github.com/gleb-daax)*\n- [`e05983a`](https://github.com/tobymao/sqlglot/commit/e05983a62ee9b3689e8a277924f2b1e36744a2a9) - **tokenizer-rs**: make hex literal tokenization more robust *(PR [#4802](https://github.com/tobymao/sqlglot/pull/4802) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`6c4c69e`](https://github.com/tobymao/sqlglot/commit/6c4c69e95d504ec839f7abd571eb2f614ea3c9ca) - move t-sql generators to base, clean up set_sql *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`4bf897d`](https://github.com/tobymao/sqlglot/commit/4bf897da6332175c22b772beb0b0cb6e6e941ebb) - remove redundant \"parts\" arg from CH ast, move gen methods to base *(PR [#4779](https://github.com/tobymao/sqlglot/pull/4779) by [@georgesittas](https://github.com/georgesittas))*\n- [`e9c8eae`](https://github.com/tobymao/sqlglot/commit/e9c8eae060c05d02f6f65916d59f1792098679c8) - bump sqlglotrs to 0.4.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.6.0] - 2025-02-10\n### :boom: BREAKING CHANGES\n- due to [`a790e41`](https://github.com/tobymao/sqlglot/commit/a790e41736884bde7a8172f458db92d80064556f) - avoid redundant casts in FROM/TO_UTC_TIMESTAMP *(PR [#4725](https://github.com/tobymao/sqlglot/pull/4725) by [@georgesittas](https://github.com/georgesittas))*:\n\n  avoid redundant casts in FROM/TO_UTC_TIMESTAMP (#4725)\n\n\n### :bug: Bug Fixes\n- [`a790e41`](https://github.com/tobymao/sqlglot/commit/a790e41736884bde7a8172f458db92d80064556f) - **spark**: avoid redundant casts in FROM/TO_UTC_TIMESTAMP *(PR [#4725](https://github.com/tobymao/sqlglot/pull/4725) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.5.0] - 2025-02-10\n### :boom: BREAKING CHANGES\n- due to [`da52181`](https://github.com/tobymao/sqlglot/commit/da52181f1cd3ec22e5ac597de50036278d2e66e5) - TO_DATE parsing with safe flag true *(PR [#4713](https://github.com/tobymao/sqlglot/pull/4713) by [@geooo109](https://github.com/geooo109))*:\n\n  TO_DATE parsing with safe flag true (#4713)\n\n- due to [`b12aba9`](https://github.com/tobymao/sqlglot/commit/b12aba9be6043053f79ff50f7bdcdfdff19ddf52) - Improve UUID support *(PR [#4718](https://github.com/tobymao/sqlglot/pull/4718) by [@amachanic](https://github.com/amachanic))*:\n\n  Improve UUID support (#4718)\n\n- due to [`27ec74b`](https://github.com/tobymao/sqlglot/commit/27ec74bab67afba930c4ea66130bcba5e9bb5ba1) - Properly set 'this' when parsing IDENTITY *(PR [#4719](https://github.com/tobymao/sqlglot/pull/4719) by [@amachanic](https://github.com/amachanic))*:\n\n  Properly set 'this' when parsing IDENTITY (#4719)\n\n\n### :sparkles: New Features\n- [`c31947b`](https://github.com/tobymao/sqlglot/commit/c31947b2386f579d9d12d2d4053461a75855b9be) - **postgres**: Support generation of exp.CountIf *(PR [#4709](https://github.com/tobymao/sqlglot/pull/4709) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`b842d93`](https://github.com/tobymao/sqlglot/commit/b842d9383827d18482e36d6ea3041180a74d0abf) - **postgres**: enable qualification of queries using the ROWS FROM syntax *(PR [#4699](https://github.com/tobymao/sqlglot/pull/4699) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3777](https://github.com/TobikoData/sqlmesh/issues/3777) opened by [@simon-pactum](https://github.com/simon-pactum)*\n- [`5f90307`](https://github.com/tobymao/sqlglot/commit/5f9030786f8489e70aee70acabee440ecf23699c) - **duckdb**: enable support for user-defined types *(PR [#4702](https://github.com/tobymao/sqlglot/pull/4702) by [@georgesittas](https://github.com/georgesittas))*\n- [`23283ca`](https://github.com/tobymao/sqlglot/commit/23283cacda3c4d6e4f6453cdef1a9e73e3bc8d24) - avoid concealing dialect module exception in _try_load *(PR [#4708](https://github.com/tobymao/sqlglot/pull/4708) by [@georgesittas](https://github.com/georgesittas))*\n- [`707d45e`](https://github.com/tobymao/sqlglot/commit/707d45ecc7e233f57ada8d6dfaf6c621d6ee3f51) - **tsql**: support default values on definitons and the OUTPUT/OUT/READ_ONLY syntax *(PR [#4704](https://github.com/tobymao/sqlglot/pull/4704) by [@geooo109](https://github.com/geooo109))*\n- [`da52181`](https://github.com/tobymao/sqlglot/commit/da52181f1cd3ec22e5ac597de50036278d2e66e5) - **hive**: TO_DATE parsing with safe flag true *(PR [#4713](https://github.com/tobymao/sqlglot/pull/4713) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4707](https://github.com/tobymao/sqlglot/issues/4707) opened by [@jiangli001](https://github.com/jiangli001)*\n- [`b12aba9`](https://github.com/tobymao/sqlglot/commit/b12aba9be6043053f79ff50f7bdcdfdff19ddf52) - **tsql, postgres**: Improve UUID support *(PR [#4718](https://github.com/tobymao/sqlglot/pull/4718) by [@amachanic](https://github.com/amachanic))*\n- [`27ec74b`](https://github.com/tobymao/sqlglot/commit/27ec74bab67afba930c4ea66130bcba5e9bb5ba1) - **tsql**: Properly set 'this' when parsing IDENTITY *(PR [#4719](https://github.com/tobymao/sqlglot/pull/4719) by [@amachanic](https://github.com/amachanic))*\n- [`f7e22d4`](https://github.com/tobymao/sqlglot/commit/f7e22d40cddfdee4a3d4912aef3161546528d400) - don't change query if no join marks in eliminate_join_marks, fixes [#4721](https://github.com/tobymao/sqlglot/pull/4721) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b918ff1`](https://github.com/tobymao/sqlglot/commit/b918ff1bc4e256bd1b84802327ffe4acf36d2d45) - **bigquery**: type-annotated array literal logic edge case *(PR [#4724](https://github.com/tobymao/sqlglot/pull/4724) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.4.1] - 2025-02-03\n### :bug: Bug Fixes\n- [`dd1cdb0`](https://github.com/tobymao/sqlglot/commit/dd1cdb0b91ac597a9cb1f1f517a616c264f5b654) - **redshift**: generate proper syntax for column type alteration *(PR [#4698](https://github.com/tobymao/sqlglot/pull/4698) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.4.0] - 2025-02-03\n### :boom: BREAKING CHANGES\n- due to [`48145a3`](https://github.com/tobymao/sqlglot/commit/48145a399b076bd3189af8ed8187ca45767d018d) - CurrentSchema for SQLite, MySQL, Postgres, and TSQL *(PR [#4658](https://github.com/tobymao/sqlglot/pull/4658) by [@pruzko](https://github.com/pruzko))*:\n\n  CurrentSchema for SQLite, MySQL, Postgres, and TSQL (#4658)\n\n- due to [`1a91913`](https://github.com/tobymao/sqlglot/commit/1a91913eea97e2008a0fe4282d60d7c693a79fc3) - Parse empty bracketed ARRAY with cast *(PR [#4679](https://github.com/tobymao/sqlglot/pull/4679) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Parse empty bracketed ARRAY with cast (#4679)\n\n- due to [`f6482fb`](https://github.com/tobymao/sqlglot/commit/f6482fbb782b13a0f180f67b8b5eb3149eba0251) - transpile postgres DATE_BIN function to duckdb TIME_BUCKET *(PR [#4681](https://github.com/tobymao/sqlglot/pull/4681) by [@dor-bernstein](https://github.com/dor-bernstein))*:\n\n  transpile postgres DATE_BIN function to duckdb TIME_BUCKET (#4681)\n\n- due to [`ade8b82`](https://github.com/tobymao/sqlglot/commit/ade8b826541ecfb00e218d16d995d34adab0335a) - load dialects lazily *(PR [#4687](https://github.com/tobymao/sqlglot/pull/4687) by [@georgesittas](https://github.com/georgesittas))*:\n\n  load dialects lazily (#4687)\n\n\n### :sparkles: New Features\n- [`48145a3`](https://github.com/tobymao/sqlglot/commit/48145a399b076bd3189af8ed8187ca45767d018d) - CurrentSchema for SQLite, MySQL, Postgres, and TSQL *(PR [#4658](https://github.com/tobymao/sqlglot/pull/4658) by [@pruzko](https://github.com/pruzko))*\n  - :arrow_lower_right: *addresses issue [#4655](https://github.com/tobymao/sqlglot/issues/4655) opened by [@pruzko](https://github.com/pruzko)*\n- [`f6482fb`](https://github.com/tobymao/sqlglot/commit/f6482fbb782b13a0f180f67b8b5eb3149eba0251) - transpile postgres DATE_BIN function to duckdb TIME_BUCKET *(PR [#4681](https://github.com/tobymao/sqlglot/pull/4681) by [@dor-bernstein](https://github.com/dor-bernstein))*\n- [`b2a6041`](https://github.com/tobymao/sqlglot/commit/b2a6041ace9a97fab947364b22d5ddf0e842e278) - **oracle**: add support for CAST(... DEFAULT <value> ON CONVERSION FAILURE) *(PR [#4683](https://github.com/tobymao/sqlglot/pull/4683) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4682](https://github.com/tobymao/sqlglot/issues/4682) opened by [@jaredschwartz-ofs](https://github.com/jaredschwartz-ofs)*\n- [`1ad656e`](https://github.com/tobymao/sqlglot/commit/1ad656e4572bf7b3d38805e92f7202f4dcc4f9f8) - enable parsing of (u)int128,256 types for all dialects *(PR [#4685](https://github.com/tobymao/sqlglot/pull/4685) by [@georgesittas](https://github.com/georgesittas))*\n- [`6f5fb04`](https://github.com/tobymao/sqlglot/commit/6f5fb0423e970920fa5abda3f7e4356e2fb441e1) - implement Dune dialect *(PR [#4686](https://github.com/tobymao/sqlglot/pull/4686) by [@georgesittas](https://github.com/georgesittas))*\n- [`9ea15c7`](https://github.com/tobymao/sqlglot/commit/9ea15c732d76e0d6a393e553a42e6b9ed30ef286) - **bigquery**: add EXPORT DATA statement support *(PR [#4688](https://github.com/tobymao/sqlglot/pull/4688) by [@ArnoldHueteG](https://github.com/ArnoldHueteG))*\n\n### :bug: Bug Fixes\n- [`cd53f7e`](https://github.com/tobymao/sqlglot/commit/cd53f7ec03e99129b430c435d23907ef7d0e0c34) - **clickhouse**: Generate bracket notation for exp.VarMap *(PR [#4664](https://github.com/tobymao/sqlglot/pull/4664) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4662](https://github.com/tobymao/sqlglot/issues/4662) opened by [@martijnthe](https://github.com/martijnthe)*\n- [`0920f77`](https://github.com/tobymao/sqlglot/commit/0920f778b2d94d94f3c8cccf280a87a6a14b12f7) - use utf-8 encoding in open calls, fixes [#4676](https://github.com/tobymao/sqlglot/pull/4676) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`e71c4c0`](https://github.com/tobymao/sqlglot/commit/e71c4c0b60811f26828d7719fe941dfbc3693be1) - **trino**: Add more JSON_QUERY options *(PR [#4673](https://github.com/tobymao/sqlglot/pull/4673) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4672](https://github.com/tobymao/sqlglot/issues/4672) opened by [@JustGui](https://github.com/JustGui)*\n- [`1a91913`](https://github.com/tobymao/sqlglot/commit/1a91913eea97e2008a0fe4282d60d7c693a79fc3) - **postgres**: Parse empty bracketed ARRAY with cast *(PR [#4679](https://github.com/tobymao/sqlglot/pull/4679) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4674](https://github.com/tobymao/sqlglot/issues/4674) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`c45f174`](https://github.com/tobymao/sqlglot/commit/c45f17455477790f53ef7e347a7e85cfdb82c4ab) - **bigquery**: Inline type-annotated ARRAY literals *(PR [#4671](https://github.com/tobymao/sqlglot/pull/4671) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4670](https://github.com/tobymao/sqlglot/issues/4670) opened by [@sean-rose](https://github.com/sean-rose)*\n- [`df75edd`](https://github.com/tobymao/sqlglot/commit/df75eddf698af9fe36e7121a63cc2b9fdd468363) - **duckdb**: support postgres JSON/JSONB_OBJECT_AGG to duckdb JSON_GROUP_OBJECT *(PR [#4677](https://github.com/tobymao/sqlglot/pull/4677) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4667](https://github.com/tobymao/sqlglot/issues/4667) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`69680c1`](https://github.com/tobymao/sqlglot/commit/69680c146f67175ab6e4c4d9898b0991033a4188) - **tsql**: Transpile exp.Fetch limits *(PR [#4680](https://github.com/tobymao/sqlglot/pull/4680) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4665](https://github.com/tobymao/sqlglot/issues/4665) opened by [@WillAyd](https://github.com/WillAyd)*\n- [`b3b0962`](https://github.com/tobymao/sqlglot/commit/b3b09624cdefb1baa46ddbb888b24648f330a963) - **hive**: Simplify DATE_FORMAT roundtrip *(PR [#4689](https://github.com/tobymao/sqlglot/pull/4689) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`ade8b82`](https://github.com/tobymao/sqlglot/commit/ade8b826541ecfb00e218d16d995d34adab0335a) - load dialects lazily *(PR [#4687](https://github.com/tobymao/sqlglot/pull/4687) by [@georgesittas](https://github.com/georgesittas))*\n- [`47c0236`](https://github.com/tobymao/sqlglot/commit/47c023650dad8b0091248c608a211018b841042a) - **bigquery**: Refactor EXPORT DATA statement *(PR [#4693](https://github.com/tobymao/sqlglot/pull/4693) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`1904b76`](https://github.com/tobymao/sqlglot/commit/1904b7605a7308608ac64e5cfb3c8424d3e55c17) - **tsql**: remove BEGIN from identifiers *(PR [#4695](https://github.com/tobymao/sqlglot/pull/4695) by [@geooo109](https://github.com/geooo109))*\n- [`a688b6c`](https://github.com/tobymao/sqlglot/commit/a688b6cff01b9cd828c0467b0aa09fba728d751a) - **snowflake**: support correct AUTO INCREMENT transpilation *(PR [#4696](https://github.com/tobymao/sqlglot/pull/4696) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4694](https://github.com/tobymao/sqlglot/issues/4694) opened by [@sfc-gh-tdwojak](https://github.com/sfc-gh-tdwojak)*\n\n\n## [v26.3.9] - 2025-01-27\n### :bug: Bug Fixes\n- [`b091f2f`](https://github.com/tobymao/sqlglot/commit/b091f2f4e4779fb9a4187d1665ca40e1648d9ccb) - **trino**: Correctly render exp.LocationProperty in CREATE TABLE / CREATE SCHEMA *(PR [#4659](https://github.com/tobymao/sqlglot/pull/4659) by [@erindru](https://github.com/erindru))*\n- [`c4de945`](https://github.com/tobymao/sqlglot/commit/c4de94538cd69540f772b9b13e968ee16ffbbe67) - **Trino**: Prevent first_value and last_value from being converted *(PR [#4661](https://github.com/tobymao/sqlglot/pull/4661) by [@MikeWallis42](https://github.com/MikeWallis42))*\n  - :arrow_lower_right: *fixes issue [#4660](https://github.com/tobymao/sqlglot/issues/4660) opened by [@MikeWallis42](https://github.com/MikeWallis42)*\n\n### :wrench: Chores\n- [`bae0489`](https://github.com/tobymao/sqlglot/commit/bae0489044a1368556f03f637c171a1873b6f05c) - reduce sdist size *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v26.3.8] - 2025-01-24\n### :wrench: Chores\n- [`5f54f16`](https://github.com/tobymao/sqlglot/commit/5f54f168ee75c5a344747a035e63e1df70fe652c) - bump sqlglotrs to 0.3.14 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.3.7] - 2025-01-24\n### :wrench: Chores\n- [`14ad1a0`](https://github.com/tobymao/sqlglot/commit/14ad1a04e86fea5ea88f99948e4cc283692e72a2) - bump sqlglotrs to 0.3.13 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.3.6] - 2025-01-24\n### :wrench: Chores\n- [`085fef6`](https://github.com/tobymao/sqlglot/commit/085fef6971a4ebd43b5c7013c6bbcb0d00dfdc30) - bump sqlglotrs to 0.3.12 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.3.5] - 2025-01-24\n### :wrench: Chores\n- [`acb7217`](https://github.com/tobymao/sqlglot/commit/acb7217d89e12de549663b67af4687a08512993f) - bump sqlglotrs to 0.3.11 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.3.4] - 2025-01-24\n### :wrench: Chores\n- [`bb7548d`](https://github.com/tobymao/sqlglot/commit/bb7548d1e9f371d3ce931fcbd86c65c895f159d1) - bump sqlglotrs to 0.3.10 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.3.3] - 2025-01-23\n### :wrench: Chores\n- [`3a188ef`](https://github.com/tobymao/sqlglot/commit/3a188ef0d42a6313625b25003c27195156e7e753) - fix sqlglotrs deployment job *(PR [#4657](https://github.com/tobymao/sqlglot/pull/4657) by [@georgesittas](https://github.com/georgesittas))*\n- [`7e55533`](https://github.com/tobymao/sqlglot/commit/7e55533d9bb06783803f275415640217c89085d0) - bump sqlglotrs to 0.3.9 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.3.2] - 2025-01-23\n### :wrench: Chores\n- [`28f56cb`](https://github.com/tobymao/sqlglot/commit/28f56cb7d9805ce898e7bf6bb884cccb1bd32c52) - fix sqlglotrs deployment job *(PR [#4656](https://github.com/tobymao/sqlglot/pull/4656) by [@georgesittas](https://github.com/georgesittas))*\n- [`846b141`](https://github.com/tobymao/sqlglot/commit/846b1414183e3d193b4aacc82f3861378adb9ec9) - bump sqlglotrs to 0.3.8 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.3.1] - 2025-01-23\n### :wrench: Chores\n- [`ff9ea0c`](https://github.com/tobymao/sqlglot/commit/ff9ea0c4554ef0fa46b3460d01374d4a3f9c36ff) - change upload-artifact to v4 *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`61c4784`](https://github.com/tobymao/sqlglot/commit/61c4784033940e34e91732e2464e4baba77e6b7c) - bump sqlglotrs to 0.3.7 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.3.0] - 2025-01-23\n### :boom: BREAKING CHANGES\n- due to [`8b465d4`](https://github.com/tobymao/sqlglot/commit/8b465d498e0aa9feee53306f631e258443ee3060) - expand single VALUES clause in CTE into a SELECT * *(PR [#4617](https://github.com/tobymao/sqlglot/pull/4617) by [@georgesittas](https://github.com/georgesittas))*:\n\n  expand single VALUES clause in CTE into a SELECT * (#4617)\n\n- due to [`59d886d`](https://github.com/tobymao/sqlglot/commit/59d886d6abfc00726b785a4d468f6b2e0f9d3b1a) - treat LEVEL column in CONNECT BY queries as an identifier *(PR [#4627](https://github.com/tobymao/sqlglot/pull/4627) by [@georgesittas](https://github.com/georgesittas))*:\n\n  treat LEVEL column in CONNECT BY queries as an identifier (#4627)\n\n- due to [`9db09ff`](https://github.com/tobymao/sqlglot/commit/9db09ff91931802c675a219951f28afee1d4019d) - support more compact SAFE_DIVIDE transpilation [#4634](https://github.com/tobymao/sqlglot/pull/4634) *(PR [#4641](https://github.com/tobymao/sqlglot/pull/4641) by [@geooo109](https://github.com/geooo109))*:\n\n  support more compact SAFE_DIVIDE transpilation #4634 (#4641)\n\n- due to [`94af80b`](https://github.com/tobymao/sqlglot/commit/94af80b8bc3c44aa9770d6503f4e07ad4e37e314) - Do not remove parens on bracketed expressions *(PR [#4645](https://github.com/tobymao/sqlglot/pull/4645) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Do not remove parens on bracketed expressions (#4645)\n\n- due to [`35923e9`](https://github.com/tobymao/sqlglot/commit/35923e959ff934093a7b82c58f13c5a89a768f5e) - POSITION and all their variants for all dialects *(PR [#4606](https://github.com/tobymao/sqlglot/pull/4606) by [@pruzko](https://github.com/pruzko))*:\n\n  POSITION and all their variants for all dialects (#4606)\n\n\n### :sparkles: New Features\n- [`e47a7c9`](https://github.com/tobymao/sqlglot/commit/e47a7c943b0beef37e30cd7c71ea98c27b82c11b) - Fix Oracle Integer Type Mapping *(PR [#4616](https://github.com/tobymao/sqlglot/pull/4616) by [@pruzko](https://github.com/pruzko))*\n- [`d8ade83`](https://github.com/tobymao/sqlglot/commit/d8ade830bbca4d2893a7e406868a0bd3a654057e) - **clickhouse**: Dynamic data type *(PR [#4624](https://github.com/tobymao/sqlglot/pull/4624) by [@pkit](https://github.com/pkit))*\n- [`f7628ad`](https://github.com/tobymao/sqlglot/commit/f7628adf12e03a09ec89fe883d5b710a0f7e0151) - **optimizer**: Fix qualify for SEMI/ANTI joins *(PR [#4622](https://github.com/tobymao/sqlglot/pull/4622) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3557](https://github.com/TobikoData/sqlmesh/issues/3557) opened by [@Bilbottom](https://github.com/Bilbottom)*\n- [`a20b663`](https://github.com/tobymao/sqlglot/commit/a20b663964a9845d3eb3c43def5880a531dab4a4) - improve rs tokenizer performance *(PR [#4638](https://github.com/tobymao/sqlglot/pull/4638) by [@benfdking](https://github.com/benfdking))*\n- [`ffa0df7`](https://github.com/tobymao/sqlglot/commit/ffa0df72e36c6a08f1fc707d9c83e98eccc214c1) - **parser**: Support Oracle/Postgres XMLNAMESPACES in XMLTABLE *(PR [#4643](https://github.com/tobymao/sqlglot/pull/4643) by [@rbreejen](https://github.com/rbreejen))*\n  - :arrow_lower_right: *addresses issue [#4642](https://github.com/tobymao/sqlglot/issues/4642) opened by [@rbreejen](https://github.com/rbreejen)*\n- [`35923e9`](https://github.com/tobymao/sqlglot/commit/35923e959ff934093a7b82c58f13c5a89a768f5e) - POSITION and all their variants for all dialects *(PR [#4606](https://github.com/tobymao/sqlglot/pull/4606) by [@pruzko](https://github.com/pruzko))*\n\n### :bug: Bug Fixes\n- [`14474ee`](https://github.com/tobymao/sqlglot/commit/14474ee689025cc67b1f9a07e51d2f414ec5ab49) - **tsql**: support TSQL PRIMARY KEY constraint with DESC, ASC *(PR [#4618](https://github.com/tobymao/sqlglot/pull/4618) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4610](https://github.com/tobymao/sqlglot/issues/4610) opened by [@cchambers-rdi](https://github.com/cchambers-rdi)*\n- [`8b465d4`](https://github.com/tobymao/sqlglot/commit/8b465d498e0aa9feee53306f631e258443ee3060) - **parser**: expand single VALUES clause in CTE into a SELECT * *(PR [#4617](https://github.com/tobymao/sqlglot/pull/4617) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3556](https://github.com/TobikoData/sqlmesh/issues/3556) opened by [@Bilbottom](https://github.com/Bilbottom)*\n- [`647d986`](https://github.com/tobymao/sqlglot/commit/647d98650a3d6ba6aa7d57560555832548dd89aa) - **snowflake**: get rid of incorrect time mappings *(PR [#4629](https://github.com/tobymao/sqlglot/pull/4629) by [@georgesittas](https://github.com/georgesittas))*\n- [`9cbd5ef`](https://github.com/tobymao/sqlglot/commit/9cbd5ef798d1f34d4eebe501cead8295564fc15c) - **trino**: generate ArrayUniqueAgg as ARRAY_AGG(DISTINCT ...) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`59d886d`](https://github.com/tobymao/sqlglot/commit/59d886d6abfc00726b785a4d468f6b2e0f9d3b1a) - **optimizer**: treat LEVEL column in CONNECT BY queries as an identifier *(PR [#4627](https://github.com/tobymao/sqlglot/pull/4627) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4620](https://github.com/tobymao/sqlglot/issues/4620) opened by [@snovik75](https://github.com/snovik75)*\n- [`6107661`](https://github.com/tobymao/sqlglot/commit/6107661424622651447da09fb9d7e456ff453bff) - **snowflake**: Allow parsing of TO_TIME *(PR [#4631](https://github.com/tobymao/sqlglot/pull/4631) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4625](https://github.com/tobymao/sqlglot/issues/4625) opened by [@aletheavilla](https://github.com/aletheavilla)*\n- [`9fdfd4d`](https://github.com/tobymao/sqlglot/commit/9fdfd4d6824702f019223536ba4013a966170ff6) - **trino**: support QUOTES option for JSON_QUERY [#4623](https://github.com/tobymao/sqlglot/pull/4623) *(PR [#4628](https://github.com/tobymao/sqlglot/pull/4628) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4623](https://github.com/tobymao/sqlglot/issues/4623) opened by [@betodealmeida](https://github.com/betodealmeida)*\n- [`43eb0d9`](https://github.com/tobymao/sqlglot/commit/43eb0d9360f3154039e9eb71ee8818b6590d220a) - **tsql**: create schema ast access fixup fixes [#4632](https://github.com/tobymao/sqlglot/pull/4632) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`59f6525`](https://github.com/tobymao/sqlglot/commit/59f652572037940f136508ee60b8e0a137ce18f0) - **duckdb**: Transpile exp.RegexpILike *(PR [#4640](https://github.com/tobymao/sqlglot/pull/4640) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4639](https://github.com/tobymao/sqlglot/issues/4639) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`9db09ff`](https://github.com/tobymao/sqlglot/commit/9db09ff91931802c675a219951f28afee1d4019d) - **bigquery**: support more compact SAFE_DIVIDE transpilation [#4634](https://github.com/tobymao/sqlglot/pull/4634) *(PR [#4641](https://github.com/tobymao/sqlglot/pull/4641) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4634](https://github.com/tobymao/sqlglot/issues/4634) opened by [@bbernst](https://github.com/bbernst)*\n- [`94af80b`](https://github.com/tobymao/sqlglot/commit/94af80b8bc3c44aa9770d6503f4e07ad4e37e314) - **optimizer**: Do not remove parens on bracketed expressions *(PR [#4645](https://github.com/tobymao/sqlglot/pull/4645) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3672](https://github.com/TobikoData/sqlmesh/issues/3672) opened by [@simon-pactum](https://github.com/simon-pactum)*\n- [`761e835`](https://github.com/tobymao/sqlglot/commit/761e835e39fa819ef478b8086bfd814dbecc7927) - qualify using *(PR [#4646](https://github.com/tobymao/sqlglot/pull/4646) by [@tobymao](https://github.com/tobymao))*\n- [`8b0b8ac`](https://github.com/tobymao/sqlglot/commit/8b0b8ac4ccbaf54d5fa948d9900ca53ccca9115b) - **sqlite**: allow 2-arg version of UNHEX closes [#4648](https://github.com/tobymao/sqlglot/pull/4648) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`2f12bd9`](https://github.com/tobymao/sqlglot/commit/2f12bd94d8583ddf9af808dda4df1690179ee592) - **athena**: Generate PartitionedByProperty correctly on CTAS for an Iceberg table *(PR [#4654](https://github.com/tobymao/sqlglot/pull/4654) by [@erindru](https://github.com/erindru))*\n- [`1ea0dc2`](https://github.com/tobymao/sqlglot/commit/1ea0dc296ca2e47d466ddce162ad64945c532586) - **postgres**: Support WITHIN GROUP ( order_by_clause ) FILTER for Postgres *(PR [#4652](https://github.com/tobymao/sqlglot/pull/4652) by [@gl3nnleblanc](https://github.com/gl3nnleblanc))*\n  - :arrow_lower_right: *fixes issue [#4651](https://github.com/tobymao/sqlglot/issues/4651) opened by [@gl3nnleblanc](https://github.com/gl3nnleblanc)*\n\n### :recycle: Refactors\n- [`284a936`](https://github.com/tobymao/sqlglot/commit/284a9360c5d43301da34d8d5199f101423ade289) - simplify WITHIN GROUP ... FILTER support *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`73512f9`](https://github.com/tobymao/sqlglot/commit/73512f9dde03b632b5f9eff0331713f9b44996d7) - set default properly for use_rs_tokenizer *(PR [#4619](https://github.com/tobymao/sqlglot/pull/4619) by [@georgesittas](https://github.com/georgesittas))*\n- [`9ba1db3`](https://github.com/tobymao/sqlglot/commit/9ba1db3436d2afba5821b853cb3c573aada370e7) - add bench command *(PR [#4621](https://github.com/tobymao/sqlglot/pull/4621) by [@benfdking](https://github.com/benfdking))*\n- [`0aa1516`](https://github.com/tobymao/sqlglot/commit/0aa1516cd8bf5f7d77e6d743f30f1526ccf15633) - move to string new *(PR [#4637](https://github.com/tobymao/sqlglot/pull/4637) by [@benfdking](https://github.com/benfdking))*\n- [`2355a91`](https://github.com/tobymao/sqlglot/commit/2355a914752f3add75457849ae8f8ec00754f888) - clean up unnecessary mut *(PR [#4636](https://github.com/tobymao/sqlglot/pull/4636) by [@benfdking](https://github.com/benfdking))*\n- [`0b68af5`](https://github.com/tobymao/sqlglot/commit/0b68af545bc82317ee16903d525e7b47f273d92d) - bump sqlglotrs to 0.3.6 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.2.1] - 2025-01-15\n### :wrench: Chores\n- [`b447322`](https://github.com/tobymao/sqlglot/commit/b4473220f0f50a9ce2463b3a98a77bf2fdd897af) - parser accepts ctes without as keyword again, except for clickhouse *(PR [#4612](https://github.com/tobymao/sqlglot/pull/4612) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.2.0] - 2025-01-14\n### :boom: BREAKING CHANGES\n- due to [`f3fcc10`](https://github.com/tobymao/sqlglot/commit/f3fcc1013dfcfdaa388ba3426ed82c4fe0eefab1) - allow limit, offset to be used as both modifiers and aliases *(PR [#4589](https://github.com/tobymao/sqlglot/pull/4589) by [@georgesittas](https://github.com/georgesittas))*:\n\n  allow limit, offset to be used as both modifiers and aliases (#4589)\n\n- due to [`b7ab3f1`](https://github.com/tobymao/sqlglot/commit/b7ab3f1697bda3d67a1183e6cd78dbd13777112b) - exp.Merge condition for Trino/Postgres *(PR [#4596](https://github.com/tobymao/sqlglot/pull/4596) by [@MikeWallis42](https://github.com/MikeWallis42))*:\n\n  exp.Merge condition for Trino/Postgres (#4596)\n\n- due to [`e617d40`](https://github.com/tobymao/sqlglot/commit/e617d407ece96d3c3311c95936ccdca6ecd35a70) - extend ANALYZE common syntax to cover multiple dialects *(PR [#4591](https://github.com/tobymao/sqlglot/pull/4591) by [@zashroof](https://github.com/zashroof))*:\n\n  extend ANALYZE common syntax to cover multiple dialects (#4591)\n\n\n### :sparkles: New Features\n- [`c75016a`](https://github.com/tobymao/sqlglot/commit/c75016a83cda5eb328f854a8628884b90dec10e4) - parse analyze compute statistics *(PR [#4547](https://github.com/tobymao/sqlglot/pull/4547) by [@zashroof](https://github.com/zashroof))*\n- [`986a1da`](https://github.com/tobymao/sqlglot/commit/986a1da98fa5648bc3e364ae436dc4168a1b33ed) - Druid dialect *(PR [#4579](https://github.com/tobymao/sqlglot/pull/4579) by [@betodealmeida](https://github.com/betodealmeida))*\n- [`bc9975f`](https://github.com/tobymao/sqlglot/commit/bc9975fe80d66b0c25b8755f1757f049edb4d0be) - move to rustc fx hashmap *(PR [#4588](https://github.com/tobymao/sqlglot/pull/4588) by [@benfdking](https://github.com/benfdking))*\n- [`853cbe6`](https://github.com/tobymao/sqlglot/commit/853cbe655f2aa3fa4debb8091b335eb6f9530390) - cleaner IS_ASCII for TSQL *(PR [#4592](https://github.com/tobymao/sqlglot/pull/4592) by [@pruzko](https://github.com/pruzko))*\n- [`3ebd879`](https://github.com/tobymao/sqlglot/commit/3ebd87919a4a9947c077c657c03ba2d2b3799620) - LOGICAL_AND and LOGICAL_OR for Oracle *(PR [#4593](https://github.com/tobymao/sqlglot/pull/4593) by [@pruzko](https://github.com/pruzko))*\n- [`e617d40`](https://github.com/tobymao/sqlglot/commit/e617d407ece96d3c3311c95936ccdca6ecd35a70) - extend ANALYZE common syntax to cover multiple dialects *(PR [#4591](https://github.com/tobymao/sqlglot/pull/4591) by [@zashroof](https://github.com/zashroof))*\n\n### :bug: Bug Fixes\n- [`766d698`](https://github.com/tobymao/sqlglot/commit/766d69886ac088de7dd9a22d71124ffa1b36d003) - **postgres**: Revert exp.StrPos generation *(PR [#4586](https://github.com/tobymao/sqlglot/pull/4586) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`f3fcc10`](https://github.com/tobymao/sqlglot/commit/f3fcc1013dfcfdaa388ba3426ed82c4fe0eefab1) - **parser**: allow limit, offset to be used as both modifiers and aliases *(PR [#4589](https://github.com/tobymao/sqlglot/pull/4589) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4575](https://github.com/tobymao/sqlglot/issues/4575) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`2bea466`](https://github.com/tobymao/sqlglot/commit/2bea466cbef3adfc09185176ee38ddf820b3f7ab) - **optimizer**: unions on nested subqueries *(PR [#4603](https://github.com/tobymao/sqlglot/pull/4603) by [@barakalon](https://github.com/barakalon))*\n- [`199508a`](https://github.com/tobymao/sqlglot/commit/199508a77c62f75b5e12fee47828d34e4903c706) - **snowflake**: treat $ as part of the json path key identifier *(PR [#4604](https://github.com/tobymao/sqlglot/pull/4604) by [@georgesittas](https://github.com/georgesittas))*\n- [`b7ab3f1`](https://github.com/tobymao/sqlglot/commit/b7ab3f1697bda3d67a1183e6cd78dbd13777112b) - exp.Merge condition for Trino/Postgres *(PR [#4596](https://github.com/tobymao/sqlglot/pull/4596) by [@MikeWallis42](https://github.com/MikeWallis42))*\n  - :arrow_lower_right: *fixes issue [#4595](https://github.com/tobymao/sqlglot/issues/4595) opened by [@MikeWallis42](https://github.com/MikeWallis42)*\n\n### :recycle: Refactors\n- [`c0f7309`](https://github.com/tobymao/sqlglot/commit/c0f7309327e21204a0a0f273712d3097f02f6796) - simplify `trie_filter` closure in `Tokenizer` initialization *(PR [#4599](https://github.com/tobymao/sqlglot/pull/4599) by [@gvozdvmozgu](https://github.com/gvozdvmozgu))*\n- [`fb93219`](https://github.com/tobymao/sqlglot/commit/fb932198087e5e3aa1a42e65ac30f28e24c6d84f) - replace `std::mem::replace` with `std::mem::take` and `Vec::drain` *(PR [#4600](https://github.com/tobymao/sqlglot/pull/4600) by [@gvozdvmozgu](https://github.com/gvozdvmozgu))*\n\n### :wrench: Chores\n- [`672d656`](https://github.com/tobymao/sqlglot/commit/672d656eb5a014ba42492ba2c2a9a33ebd145bd8) - clean up ANALYZE implementation *(PR [#4607](https://github.com/tobymao/sqlglot/pull/4607) by [@georgesittas](https://github.com/georgesittas))*\n- [`e58a8cb`](https://github.com/tobymao/sqlglot/commit/e58a8cb4d388d22eff8fd2cca08f38e4c42075d6) - apply clippy fixes *(PR [#4608](https://github.com/tobymao/sqlglot/pull/4608) by [@benfdking](https://github.com/benfdking))*\n- [`5502c94`](https://github.com/tobymao/sqlglot/commit/5502c94d665a2ed354e44beb145e767bab00adfa) - bump sqlglotrs to 0.3.5 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.1.3] - 2025-01-09\n### :bug: Bug Fixes\n- [`d250846`](https://github.com/tobymao/sqlglot/commit/d250846d05711ac62a45efd4930f0ca712841b11) - **snowflake**: generate LIMIT when OFFSET exists [#4575](https://github.com/tobymao/sqlglot/pull/4575) *(PR [#4581](https://github.com/tobymao/sqlglot/pull/4581) by [@geooo109](https://github.com/geooo109))*\n\n### :wrench: Chores\n- [`ffbb935`](https://github.com/tobymao/sqlglot/commit/ffbb9350f8d0decab4555471ec2e468fa2741f5f) - install python 3.7 when building windows wheel for sqlglotrs *(PR [#4585](https://github.com/tobymao/sqlglot/pull/4585) by [@georgesittas](https://github.com/georgesittas))*\n- [`1ea05c0`](https://github.com/tobymao/sqlglot/commit/1ea05c0b4e3cf53482058b22ecac7ec7c1de525d) - bump sqlglotrs to 0.3.4 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.1.2] - 2025-01-08\n### :wrench: Chores\n- [`e33af0b`](https://github.com/tobymao/sqlglot/commit/e33af0bcd859571dab68aef3a1fc9ecbf5c49e71) - try setup-python@v5 in the publish job *(PR [#4582](https://github.com/tobymao/sqlglot/pull/4582) by [@georgesittas](https://github.com/georgesittas))*\n- [`3259f84`](https://github.com/tobymao/sqlglot/commit/3259f84f1faa6f1135ecca7d0f5fcd4b187b4da7) - bump sqlglotrs to 0.3.3 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.1.1] - 2025-01-08\n### :wrench: Chores\n- [`e51d1cf`](https://github.com/tobymao/sqlglot/commit/e51d1cfb0aa1028bb116851b03b759282305217b) - release sqlglotrs for Python 3.13 on windows *(PR [#4580](https://github.com/tobymao/sqlglot/pull/4580) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`975ffa0`](https://github.com/tobymao/sqlglot/commit/975ffa0e10f08243375e5e83384fd0e134730d14) - bump sqlglotrs to 0.3.2 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.1.0] - 2025-01-08\n### :boom: BREAKING CHANGES\n- due to [`07d05da`](https://github.com/tobymao/sqlglot/commit/07d05da95c7d3882a7032dade3cbeefbd96628b7) - normalize before qualifying tables *(PR [#4539](https://github.com/tobymao/sqlglot/pull/4539) by [@tobymao](https://github.com/tobymao))*:\n\n  normalize before qualifying tables (#4539)\n\n- due to [`cead7c3`](https://github.com/tobymao/sqlglot/commit/cead7c32bef44c0efaf48c2038976c7c7f2b709c) - require AS token in CTEs for all dialects except spark, databricks *(PR [#4546](https://github.com/tobymao/sqlglot/pull/4546) by [@georgesittas](https://github.com/georgesittas))*:\n\n  require AS token in CTEs for all dialects except spark, databricks (#4546)\n\n- due to [`231d032`](https://github.com/tobymao/sqlglot/commit/231d03202e4338ee097662d59770dae1a9958617) - support Unicode in sqlite, mysql, tsql, postgres, oracle *(PR [#4554](https://github.com/tobymao/sqlglot/pull/4554) by [@pruzko](https://github.com/pruzko))*:\n\n  support Unicode in sqlite, mysql, tsql, postgres, oracle (#4554)\n\n- due to [`83595b6`](https://github.com/tobymao/sqlglot/commit/83595b67f0aa4cafdfcf4bace7b92c17f9e9f5f3) - parse ASCII into Unicode to facilitate transpilation *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse ASCII into Unicode to facilitate transpilation\n\n- due to [`e141960`](https://github.com/tobymao/sqlglot/commit/e1419607981cd8fe597781faeae429069b13d5fb) - improve transpilation of CHAR[ACTER]_LENGTH *(PR [#4555](https://github.com/tobymao/sqlglot/pull/4555) by [@pruzko](https://github.com/pruzko))*:\n\n  improve transpilation of CHAR[ACTER]_LENGTH (#4555)\n\n\n### :sparkles: New Features\n- [`7a517d7`](https://github.com/tobymao/sqlglot/commit/7a517d71dbcab4b46538263cac604ac38e714e6b) - Introduce meta comment to parse known functions as exp.Anonymous *(PR [#4532](https://github.com/tobymao/sqlglot/pull/4532) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4522](https://github.com/tobymao/sqlglot/issues/4522) opened by [@github-christophe-oudar](https://github.com/github-christophe-oudar)*\n- [`6992c18`](https://github.com/tobymao/sqlglot/commit/6992c1855f343a5d0120a3b4c993d8c406dd29ba) - **tokenizer**: Allow underscore separated number literals *(PR [#4536](https://github.com/tobymao/sqlglot/pull/4536) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4530](https://github.com/tobymao/sqlglot/issues/4530) opened by [@martijnthe](https://github.com/martijnthe)*\n- [`7fe9f7f`](https://github.com/tobymao/sqlglot/commit/7fe9f7f6dbc701580ca17318400203245331704e) - **tsql**: add support for DATETRUNC [#4531](https://github.com/tobymao/sqlglot/pull/4531) *(PR [#4537](https://github.com/tobymao/sqlglot/pull/4537) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *addresses issue [#4531](https://github.com/tobymao/sqlglot/issues/4531) opened by [@rajat-wisdom](https://github.com/rajat-wisdom)*\n- [`931eef6`](https://github.com/tobymao/sqlglot/commit/931eef6958be87ef88f4ff5311441e7a7004b8c5) - **duckdb**: Support simplified UNPIVOT statement *(PR [#4545](https://github.com/tobymao/sqlglot/pull/4545) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4542](https://github.com/tobymao/sqlglot/issues/4542) opened by [@Bilbottom](https://github.com/Bilbottom)*\n- [`923a1f7`](https://github.com/tobymao/sqlglot/commit/923a1f7fda66f3dc61ee12755fe8960f8aeb3cd8) - treat NAMESPACE as a db creatable *(PR [#4556](https://github.com/tobymao/sqlglot/pull/4556) by [@TanviPardeshi](https://github.com/TanviPardeshi))*\n- [`b0cc7d0`](https://github.com/tobymao/sqlglot/commit/b0cc7d029a78c7929daff9b30dc072608d9c80b0) - add support for IS_ASCII *(PR [#4557](https://github.com/tobymao/sqlglot/pull/4557) by [@pruzko](https://github.com/pruzko))*\n- [`231d032`](https://github.com/tobymao/sqlglot/commit/231d03202e4338ee097662d59770dae1a9958617) - support Unicode in sqlite, mysql, tsql, postgres, oracle *(PR [#4554](https://github.com/tobymao/sqlglot/pull/4554) by [@pruzko](https://github.com/pruzko))*\n- [`83595b6`](https://github.com/tobymao/sqlglot/commit/83595b67f0aa4cafdfcf4bace7b92c17f9e9f5f3) - **hive**: parse ASCII into Unicode to facilitate transpilation *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`095fb1e`](https://github.com/tobymao/sqlglot/commit/095fb1e834153eeeea33885dc20e1ba05f8bf814) - generate POSITION instead of STRPOS for Postgres *(PR [#4577](https://github.com/tobymao/sqlglot/pull/4577) by [@pruzko](https://github.com/pruzko))*\n- [`9def0b7`](https://github.com/tobymao/sqlglot/commit/9def0b79ee623a07d8c367e0ec575ed8e63c83c6) - add support for Chr in tsql and sqlite *(PR [#4566](https://github.com/tobymao/sqlglot/pull/4566) by [@pruzko](https://github.com/pruzko))*\n- [`d32d26a`](https://github.com/tobymao/sqlglot/commit/d32d26affaa7b0639abc107505db234aeb7386d4) - **postgres**: add support for XMLTABLE *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`e141960`](https://github.com/tobymao/sqlglot/commit/e1419607981cd8fe597781faeae429069b13d5fb) - improve transpilation of CHAR[ACTER]_LENGTH *(PR [#4555](https://github.com/tobymao/sqlglot/pull/4555) by [@pruzko](https://github.com/pruzko))*\n\n### :bug: Bug Fixes\n- [`07d05da`](https://github.com/tobymao/sqlglot/commit/07d05da95c7d3882a7032dade3cbeefbd96628b7) - normalize before qualifying tables *(PR [#4539](https://github.com/tobymao/sqlglot/pull/4539) by [@tobymao](https://github.com/tobymao))*\n  - :arrow_lower_right: *fixes issue [#4538](https://github.com/tobymao/sqlglot/issues/4538) opened by [@karakanb](https://github.com/karakanb)*\n- [`1ed3235`](https://github.com/tobymao/sqlglot/commit/1ed32358adc6b578e8b8af265ac8afe37aae9ad8) - allow When in exp.merge fixes [#4543](https://github.com/tobymao/sqlglot/pull/4543) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`cead7c3`](https://github.com/tobymao/sqlglot/commit/cead7c32bef44c0efaf48c2038976c7c7f2b709c) - **parser**: require AS token in CTEs for all dialects except spark, databricks *(PR [#4546](https://github.com/tobymao/sqlglot/pull/4546) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4544](https://github.com/tobymao/sqlglot/issues/4544) opened by [@xtess16](https://github.com/xtess16)*\n- [`006b384`](https://github.com/tobymao/sqlglot/commit/006b3842f90186f8932f0dbf02453f138129608b) - **postgres**: add support for WHERE clause in INSERT DML *(PR [#4550](https://github.com/tobymao/sqlglot/pull/4550) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4549](https://github.com/tobymao/sqlglot/issues/4549) opened by [@VigneshChennai](https://github.com/VigneshChennai)*\n- [`795e7e0`](https://github.com/tobymao/sqlglot/commit/795e7e0e857486417ce98246389849fc09ccb60a) - Pin ubuntu to 22.04 for Python 3.7 *(PR [#4571](https://github.com/tobymao/sqlglot/pull/4571) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2495508`](https://github.com/tobymao/sqlglot/commit/2495508fa7b3931d466c36b5ed225b2e1510b01c) - **tsql**: generate correct DateFromParts naming *(PR [#4563](https://github.com/tobymao/sqlglot/pull/4563) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4558](https://github.com/tobymao/sqlglot/issues/4558) opened by [@rajat-wisdom](https://github.com/rajat-wisdom)*\n- [`2aff4ae`](https://github.com/tobymao/sqlglot/commit/2aff4ae861dc5225a616f5e3980cf04805e5b339) - **duckdb**: support parentheses with FROM-First syntax *(PR [#4569](https://github.com/tobymao/sqlglot/pull/4569) by [@geooo109](https://github.com/geooo109))*\n  - :arrow_lower_right: *fixes issue [#4561](https://github.com/tobymao/sqlglot/issues/4561) opened by [@LennartH](https://github.com/LennartH)*\n- [`51ac9a7`](https://github.com/tobymao/sqlglot/commit/51ac9a7b8a91d1bb5b3b6b396e1083c03573a708) - **rust-tokenizer**: increase integer width when converting hex literals *(PR [#4573](https://github.com/tobymao/sqlglot/pull/4573) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4560](https://github.com/tobymao/sqlglot/issues/4560) opened by [@whummer](https://github.com/whummer)*\n- [`94ffdb7`](https://github.com/tobymao/sqlglot/commit/94ffdb7b790c6c2235a0586c6df23c3155c184b1) - addressing mismatch in STR_POSITION argument order in executor. *(PR [#4574](https://github.com/tobymao/sqlglot/pull/4574) by [@cecilycarver](https://github.com/cecilycarver))*\n- [`139b699`](https://github.com/tobymao/sqlglot/commit/139b699f61326bdf9700f0ba9bea9a44e594cf6d) - **tsql**: transpile snowflake TIMESTAMP_NTZ to DATETIME2 *(PR [#4576](https://github.com/tobymao/sqlglot/pull/4576) by [@geooo109](https://github.com/geooo109))*\n- [`ceb42fa`](https://github.com/tobymao/sqlglot/commit/ceb42fabad60312699e4b15936aeebac00e22e4d) - parse & generate Length properly in clickhouse *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`9921528`](https://github.com/tobymao/sqlglot/commit/992152840ffad5fb85315e0bead2c498d4310cc5) - introduce benchmarking for rust *(PR [#4552](https://github.com/tobymao/sqlglot/pull/4552) by [@benfdking](https://github.com/benfdking))*\n- [`0ffe8f9`](https://github.com/tobymao/sqlglot/commit/0ffe8f91eb8295ab8171e029aa4ccbf071028a4a) - temporarily disable sqlglotrs benchmarking *(PR [#4578](https://github.com/tobymao/sqlglot/pull/4578) by [@georgesittas](https://github.com/georgesittas))*\n- [`7a0dbcf`](https://github.com/tobymao/sqlglot/commit/7a0dbcfda26ff7cf20371c84b31f454e63260959) - bump sqlglotrs to 0.3.1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.0.1] - 2024-12-18\n### :sparkles: New Features\n- [`5d3ee4c`](https://github.com/tobymao/sqlglot/commit/5d3ee4cac1c5c9e45cbf6263c32c87fda78f9854) - **snowflake**: transpile date subtraction *(PR [#4506](https://github.com/tobymao/sqlglot/pull/4506) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4485](https://github.com/tobymao/sqlglot/issues/4485) opened by [@cisenbe](https://github.com/cisenbe)*\n- [`efeb4bd`](https://github.com/tobymao/sqlglot/commit/efeb4bd870dd5c017b31d6b95c9bd6311c75b9ae) - **postgres**: add support for XMLELEMENT *(PR [#4513](https://github.com/tobymao/sqlglot/pull/4513) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4512](https://github.com/tobymao/sqlglot/issues/4512) opened by [@fresioAS](https://github.com/fresioAS)*\n- [`e495777`](https://github.com/tobymao/sqlglot/commit/e495777b8612866041050c96d3df700cd829dc9c) - **clickhouse**: add support for bracket map syntax *(PR [#4528](https://github.com/tobymao/sqlglot/pull/4528) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4527](https://github.com/tobymao/sqlglot/issues/4527) opened by [@mrcljx](https://github.com/mrcljx)*\n- [`cc44ed7`](https://github.com/tobymao/sqlglot/commit/cc44ed73fa4489e0bcb457b7eae8a9772415db65) - **mysql**: Support SERIAL data type *(PR [#4533](https://github.com/tobymao/sqlglot/pull/4533) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4529](https://github.com/tobymao/sqlglot/issues/4529) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`ee7dc96`](https://github.com/tobymao/sqlglot/commit/ee7dc966d533228756c3294c66422c27eceae503) - **starrocks**: add partition by range and unique key *(PR [#4509](https://github.com/tobymao/sqlglot/pull/4509) by [@pickfire](https://github.com/pickfire))*\n- [`84ec478`](https://github.com/tobymao/sqlglot/commit/84ec47810e0a5c9e71a2b48e686656f9c2eafb39) - **lineage**: Extend lineage function to work with pivot operation *(PR [#4471](https://github.com/tobymao/sqlglot/pull/4471) by [@step4](https://github.com/step4))*\n- [`52c8374`](https://github.com/tobymao/sqlglot/commit/52c8374876bc4037dcb81a50301fdd62cb14bb2a) - include comments in gen *(PR [#4535](https://github.com/tobymao/sqlglot/pull/4535) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`8f8e84a`](https://github.com/tobymao/sqlglot/commit/8f8e84ae81d60bea224e35b9ca88b0bb4a59512b) - **snowflake**: bitxor third parameter(padside) issue *(PR [#4501](https://github.com/tobymao/sqlglot/pull/4501) by [@ankur334](https://github.com/ankur334))*\n- [`4760246`](https://github.com/tobymao/sqlglot/commit/476024653e5b942faaaaa2b3bce30a3ea1873190) - **snowflake**: generate only one INPUT => clause in unnest_sql *(PR [#4505](https://github.com/tobymao/sqlglot/pull/4505) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4503](https://github.com/tobymao/sqlglot/issues/4503) opened by [@harounp](https://github.com/harounp)*\n- [`7649d50`](https://github.com/tobymao/sqlglot/commit/7649d5053e3305dadb83769bb5cec52ed8235a19) - **optimizer**: only expand stars for select scopes *(PR [#4515](https://github.com/tobymao/sqlglot/pull/4515) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4514](https://github.com/tobymao/sqlglot/issues/4514) opened by [@florian-ernst-alan](https://github.com/florian-ernst-alan)*\n- [`2b68b9b`](https://github.com/tobymao/sqlglot/commit/2b68b9b7967b68465042a1b8c2ee21bb30007712) - **snowflake**: Allow alias expansion inside JOIN statements *(PR [#4504](https://github.com/tobymao/sqlglot/pull/4504) by [@florian-ernst-alan](https://github.com/florian-ernst-alan))*\n  - :arrow_lower_right: *fixes issue [#4502](https://github.com/tobymao/sqlglot/issues/4502) opened by [@florian-ernst-alan](https://github.com/florian-ernst-alan)*\n- [`e15cd0b`](https://github.com/tobymao/sqlglot/commit/e15cd0be1c66e0e72d9815575fa9b210e66cf7c9) - **postgres**: generate float if the type has precision *(PR [#4516](https://github.com/tobymao/sqlglot/pull/4516) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4508](https://github.com/tobymao/sqlglot/issues/4508) opened by [@RedTailedHawk](https://github.com/RedTailedHawk)*\n- [`98906d4`](https://github.com/tobymao/sqlglot/commit/98906d4520a0c582a0534384ee3d0c1449846ee6) - another interval parsing edge case *(PR [#4519](https://github.com/tobymao/sqlglot/pull/4519) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4490](https://github.com/tobymao/sqlglot/issues/4490) opened by [@fuglaeff](https://github.com/fuglaeff)*\n- [`992f6e9`](https://github.com/tobymao/sqlglot/commit/992f6e9fc867aa5ad60a255be593b8982a0fbcba) - **tsql**: Convert exp.Neg literal to number through to_py() *(PR [#4523](https://github.com/tobymao/sqlglot/pull/4523) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4520](https://github.com/tobymao/sqlglot/issues/4520) opened by [@DzianisKryvasheya](https://github.com/DzianisKryvasheya)*\n- [`946cd42`](https://github.com/tobymao/sqlglot/commit/946cd4234a2ca403785b7c6a026a39ef604e8754) - **optimizer**: qualify snowflake queries with `level` pseudocolumn *(PR [#4524](https://github.com/tobymao/sqlglot/pull/4524) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4518](https://github.com/tobymao/sqlglot/issues/4518) opened by [@florian-ernst-alan](https://github.com/florian-ernst-alan)*\n- [`bc68289`](https://github.com/tobymao/sqlglot/commit/bc68289d4d368b29241e56b8f0aefc36db65ad47) - **planner**: ensure aggregate variable is bound *(PR [#4526](https://github.com/tobymao/sqlglot/pull/4526) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4525](https://github.com/tobymao/sqlglot/issues/4525) opened by [@EyalDlph](https://github.com/EyalDlph)*\n\n### :recycle: Refactors\n- [`cd6e00f`](https://github.com/tobymao/sqlglot/commit/cd6e00f55195e26c3d02e255e66b45ab781addad) - clean up pivot lineage *(PR [#4534](https://github.com/tobymao/sqlglot/pull/4534) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v26.0.0] - 2024-12-10\n### :boom: BREAKING CHANGES\n- due to [`1d3c9aa`](https://github.com/tobymao/sqlglot/commit/1d3c9aa604c7bf60166a0e5587f1a8d88b89bea6) - Transpile support for bitor/bit_or snowflake function *(PR [#4486](https://github.com/tobymao/sqlglot/pull/4486) by [@ankur334](https://github.com/ankur334))*:\n\n  Transpile support for bitor/bit_or snowflake function (#4486)\n\n- due to [`ab10851`](https://github.com/tobymao/sqlglot/commit/ab108518c53173ddf71ac1dfd9e45df6ac621b81) - Preserve roundtrips of DATETIME/DATETIME2 *(PR [#4491](https://github.com/tobymao/sqlglot/pull/4491) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Preserve roundtrips of DATETIME/DATETIME2 (#4491)\n\n\n### :sparkles: New Features\n- [`1d3c9aa`](https://github.com/tobymao/sqlglot/commit/1d3c9aa604c7bf60166a0e5587f1a8d88b89bea6) - **snowflake**: Transpile support for bitor/bit_or snowflake function *(PR [#4486](https://github.com/tobymao/sqlglot/pull/4486) by [@ankur334](https://github.com/ankur334))*\n- [`822aea0`](https://github.com/tobymao/sqlglot/commit/822aea0826f09fa773193004acb2af99e495fddd) - **snowflake**: Support for inline FOREIGN KEY *(PR [#4493](https://github.com/tobymao/sqlglot/pull/4493) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4489](https://github.com/tobymao/sqlglot/issues/4489) opened by [@kylekarpack](https://github.com/kylekarpack)*\n\n### :bug: Bug Fixes\n- [`ab10851`](https://github.com/tobymao/sqlglot/commit/ab108518c53173ddf71ac1dfd9e45df6ac621b81) - **tsql**: Preserve roundtrips of DATETIME/DATETIME2 *(PR [#4491](https://github.com/tobymao/sqlglot/pull/4491) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`43975e4`](https://github.com/tobymao/sqlglot/commit/43975e4b7abcd640cd5a0f91aea1fbda8dd893cb) - **duckdb**: Allow escape strings similar to Postgres *(PR [#4497](https://github.com/tobymao/sqlglot/pull/4497) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4496](https://github.com/tobymao/sqlglot/issues/4496) opened by [@LennartH](https://github.com/LennartH)*\n\n\n## [v25.34.1] - 2024-12-10\n### :boom: BREAKING CHANGES\n- due to [`f70f124`](https://github.com/tobymao/sqlglot/commit/f70f12408fbaf021dd105f2eac957b9e6fac045d) - transpile MySQL FORMAT to DuckDB *(PR [#4488](https://github.com/tobymao/sqlglot/pull/4488) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile MySQL FORMAT to DuckDB (#4488)\n\n\n### :sparkles: New Features\n- [`f70f124`](https://github.com/tobymao/sqlglot/commit/f70f12408fbaf021dd105f2eac957b9e6fac045d) - transpile MySQL FORMAT to DuckDB *(PR [#4488](https://github.com/tobymao/sqlglot/pull/4488) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4445](https://github.com/tobymao/sqlglot/issues/4445) opened by [@fanyang01](https://github.com/fanyang01)*\n- [`5a276f3`](https://github.com/tobymao/sqlglot/commit/5a276f33df48dab96e77c560c4b193f9634974f7) - add parse into tuple *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`ddf7483`](https://github.com/tobymao/sqlglot/commit/ddf74833c3e033067e731eab387db658a9a803be) - enable python 3.13 in CI *(PR [#4483](https://github.com/tobymao/sqlglot/pull/4483) by [@simon-pactum](https://github.com/simon-pactum))*\n\n\n## [v25.34.0] - 2024-12-06\n### :boom: BREAKING CHANGES\n- due to [`41c6d24`](https://github.com/tobymao/sqlglot/commit/41c6d24c99e130b3c8e35e348a25a59e9e3d5553) - Alias expanded USING STRUCT fields *(PR [#4474](https://github.com/tobymao/sqlglot/pull/4474) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Alias expanded USING STRUCT fields (#4474)\n\n\n### :sparkles: New Features\n- [`41c6d24`](https://github.com/tobymao/sqlglot/commit/41c6d24c99e130b3c8e35e348a25a59e9e3d5553) - **optimizer**: Alias expanded USING STRUCT fields *(PR [#4474](https://github.com/tobymao/sqlglot/pull/4474) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3465](https://github.com/TobikoData/sqlmesh/issues/3465) opened by [@esciara](https://github.com/esciara)*\n\n### :bug: Bug Fixes\n- [`a34bcde`](https://github.com/tobymao/sqlglot/commit/a34bcde1f7b4b2974a0132555477fa5a788126b4) - **bigquery**: properly consume dashed table parts *(PR [#4477](https://github.com/tobymao/sqlglot/pull/4477) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4476](https://github.com/tobymao/sqlglot/issues/4476) opened by [@matthewcyy](https://github.com/matthewcyy)*\n- [`438ae4c`](https://github.com/tobymao/sqlglot/commit/438ae4c0691fb3ad43ef95e613118a116cb7924c) - **bigquery**: Do not generate NULL ordering on Windows *(PR [#4480](https://github.com/tobymao/sqlglot/pull/4480) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4478](https://github.com/tobymao/sqlglot/issues/4478) opened by [@goldmedal](https://github.com/goldmedal)*\n\n\n## [v25.33.0] - 2024-12-04\n### :boom: BREAKING CHANGES\n- due to [`07fa69d`](https://github.com/tobymao/sqlglot/commit/07fa69dcb8970167ba0c55fff39175ab856ea9f3) - Make TIMESTAMP map to Type.TIMESTAMPTZ *(PR [#4451](https://github.com/tobymao/sqlglot/pull/4451) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Make TIMESTAMP map to Type.TIMESTAMPTZ (#4451)\n\n- due to [`63d8f41`](https://github.com/tobymao/sqlglot/commit/63d8f41794b2e9d22f87d0a8fbfbd83125889ca2) - treat NEXT as a func keyword, parse NEXT VALUE FOR in tsql, oracle *(PR [#4467](https://github.com/tobymao/sqlglot/pull/4467) by [@georgesittas](https://github.com/georgesittas))*:\n\n  treat NEXT as a func keyword, parse NEXT VALUE FOR in tsql, oracle (#4467)\n\n\n### :sparkles: New Features\n- [`3945acc`](https://github.com/tobymao/sqlglot/commit/3945acc4a0dfd58147de929c9a2c71734d8f1ade) - allow tables to be preserved in replace_table *(PR [#4468](https://github.com/tobymao/sqlglot/pull/4468) by [@georgesittas](https://github.com/georgesittas))*\n- [`a9dca8d`](https://github.com/tobymao/sqlglot/commit/a9dca8dd1b523efd703003694d4389f9af9d1a12) - **postgres**: Support generated columns *(PR [#4472](https://github.com/tobymao/sqlglot/pull/4472) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4463](https://github.com/tobymao/sqlglot/issues/4463) opened by [@AKST](https://github.com/AKST)*\n\n### :bug: Bug Fixes\n- [`380dad2`](https://github.com/tobymao/sqlglot/commit/380dad2f5826caa820a69442c42805c7b3c23ada) - **bigquery**: Rename CONTAINS_SUBSTRING to CONTAINS_SUBSTR *(PR [#4457](https://github.com/tobymao/sqlglot/pull/4457) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4456](https://github.com/tobymao/sqlglot/issues/4456) opened by [@romanhaa](https://github.com/romanhaa)*\n- [`ca5023d`](https://github.com/tobymao/sqlglot/commit/ca5023db5ea2a2ece804f6e389640e0bd4987598) - **presto**: Remove parentheses from CURRENT_USER *(PR [#4459](https://github.com/tobymao/sqlglot/pull/4459) by [@MikeWallis42](https://github.com/MikeWallis42))*\n  - :arrow_lower_right: *fixes issue [#4458](https://github.com/tobymao/sqlglot/issues/4458) opened by [@MikeWallis42](https://github.com/MikeWallis42)*\n- [`07fa69d`](https://github.com/tobymao/sqlglot/commit/07fa69dcb8970167ba0c55fff39175ab856ea9f3) - **spark**: Make TIMESTAMP map to Type.TIMESTAMPTZ *(PR [#4451](https://github.com/tobymao/sqlglot/pull/4451) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4442](https://github.com/tobymao/sqlglot/issues/4442) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`63d8f41`](https://github.com/tobymao/sqlglot/commit/63d8f41794b2e9d22f87d0a8fbfbd83125889ca2) - **parser**: treat NEXT as a func keyword, parse NEXT VALUE FOR in tsql, oracle *(PR [#4467](https://github.com/tobymao/sqlglot/pull/4467) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4466](https://github.com/tobymao/sqlglot/issues/4466) opened by [@Harmuth94](https://github.com/Harmuth94)*\n\n\n## [v25.32.1] - 2024-11-27\n### :bug: Bug Fixes\n- [`954d8fd`](https://github.com/tobymao/sqlglot/commit/954d8fd12740071e0951d1df3a405a4b9634868d) - parse DEFAULT in VALUES clause into a Var *(PR [#4448](https://github.com/tobymao/sqlglot/pull/4448) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4446](https://github.com/tobymao/sqlglot/issues/4446) opened by [@ddh-5230](https://github.com/ddh-5230)*\n- [`73afd0f`](https://github.com/tobymao/sqlglot/commit/73afd0f435b7e7ccde831ee311c9a76c14797fdc) - **bigquery**: Make JSONPathTokenizer more lenient for new standards *(PR [#4447](https://github.com/tobymao/sqlglot/pull/4447) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4441](https://github.com/tobymao/sqlglot/issues/4441) opened by [@patricksurry](https://github.com/patricksurry)*\n\n\n## [v25.32.0] - 2024-11-22\n### :boom: BREAKING CHANGES\n- due to [`0eed45c`](https://github.com/tobymao/sqlglot/commit/0eed45cce82681bfbafc8bfb78eb2a1bce86ae53) - Add support for ATTACH/DETACH statements *(PR [#4419](https://github.com/tobymao/sqlglot/pull/4419) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for ATTACH/DETACH statements (#4419)\n\n- due to [`da48b68`](https://github.com/tobymao/sqlglot/commit/da48b68a4f1fa6a754fa2a0a789564675d59546f) - Tokenize hints as comments *(PR [#4426](https://github.com/tobymao/sqlglot/pull/4426) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Tokenize hints as comments (#4426)\n\n- due to [`fe35394`](https://github.com/tobymao/sqlglot/commit/fe3539464a153b1c0bf46975d6221dee48a48f02) - fix datetime coercion in the canonicalize rule *(PR [#4431](https://github.com/tobymao/sqlglot/pull/4431) by [@georgesittas](https://github.com/georgesittas))*:\n\n  fix datetime coercion in the canonicalize rule (#4431)\n\n- due to [`fddcd3d`](https://github.com/tobymao/sqlglot/commit/fddcd3dfc264a645909686c201d2288c0adf9047) - bump sqlglotrs to 0.3.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.3.0\n\n\n### :sparkles: New Features\n- [`0eed45c`](https://github.com/tobymao/sqlglot/commit/0eed45cce82681bfbafc8bfb78eb2a1bce86ae53) - **duckdb**: Add support for ATTACH/DETACH statements *(PR [#4419](https://github.com/tobymao/sqlglot/pull/4419) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2db757d`](https://github.com/tobymao/sqlglot/commit/2db757dfec9ded26572b8e9a71dcc8ea8a2382fe) - **bigquery**: Support FEATURES_AT_TIME *(PR [#4430](https://github.com/tobymao/sqlglot/pull/4430) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4428](https://github.com/tobymao/sqlglot/issues/4428) opened by [@YuvrajSoni-Ksolves](https://github.com/YuvrajSoni-Ksolves)*\n- [`fc591ae`](https://github.com/tobymao/sqlglot/commit/fc591ae2fa80be5821cb53d78906afe8e5505654) - **risingwave**: add support for SINK, SOURCE & other DDL properties *(PR [#4387](https://github.com/tobymao/sqlglot/pull/4387) by [@lin0303-siyuan](https://github.com/lin0303-siyuan))*\n- [`a2bde2e`](https://github.com/tobymao/sqlglot/commit/a2bde2e03e9ef8650756bf304db35b4876746d1f) - **mysql**: improve transpilability of CHAR[ACTER]_LENGTH *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`0acc248`](https://github.com/tobymao/sqlglot/commit/0acc248361f49f68f17d799cbaf6b3de06c57f7e) - **snowflake**: Support CREATE ... WITH TAG *(PR [#4434](https://github.com/tobymao/sqlglot/pull/4434) by [@asikowitz](https://github.com/asikowitz))*\n  - :arrow_lower_right: *addresses issue [#4427](https://github.com/tobymao/sqlglot/issues/4427) opened by [@asikowitz](https://github.com/asikowitz)*\n- [`37863ff`](https://github.com/tobymao/sqlglot/commit/37863ffd747cad9c2b9bed60119cc1551faeffda) - **snowflake**: Transpile non-UNNEST exp.GenerateDateArray refs *(PR [#4433](https://github.com/tobymao/sqlglot/pull/4433) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`83ee97b`](https://github.com/tobymao/sqlglot/commit/83ee97b34cd0fe269b4820f15147d1ed7523612e) - **parser**: Do not parse window function arg as exp.Column *(PR [#4415](https://github.com/tobymao/sqlglot/pull/4415) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4410](https://github.com/tobymao/sqlglot/issues/4410) opened by [@merlindso](https://github.com/merlindso)*\n- [`b22e0c8`](https://github.com/tobymao/sqlglot/commit/b22e0c8680b0ee5a382e57904b698bf21a94f782) - **parser**: Extend DESCRIBE parser for MySQL FORMAT & statements *(PR [#4417](https://github.com/tobymao/sqlglot/pull/4417) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4414](https://github.com/tobymao/sqlglot/issues/4414) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`d1d2ae7`](https://github.com/tobymao/sqlglot/commit/d1d2ae7d1514abc9477d275352e5e126509157c6) - **duckdb**: Allow count arg on exp.ArgMax & exp.ArgMin *(PR [#4413](https://github.com/tobymao/sqlglot/pull/4413) by [@aersam](https://github.com/aersam))*\n  - :arrow_lower_right: *fixes issue [#4412](https://github.com/tobymao/sqlglot/issues/4412) opened by [@aersam](https://github.com/aersam)*\n- [`e3c45d5`](https://github.com/tobymao/sqlglot/commit/e3c45d5ec0ae6827e4b0bcfb047aeac131379732) - presto reset session closes [#4421](https://github.com/tobymao/sqlglot/pull/4421) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`fd81f1b`](https://github.com/tobymao/sqlglot/commit/fd81f1bfee9a566b8df8bb501828c20bd72ac481) - more presto commands *(commit by [@tobymao](https://github.com/tobymao))*\n- [`da48b68`](https://github.com/tobymao/sqlglot/commit/da48b68a4f1fa6a754fa2a0a789564675d59546f) - Tokenize hints as comments *(PR [#4426](https://github.com/tobymao/sqlglot/pull/4426) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4425](https://github.com/tobymao/sqlglot/issues/4425) opened by [@mkmoisen](https://github.com/mkmoisen)*\n- [`69d4a8c`](https://github.com/tobymao/sqlglot/commit/69d4a8ccdf5954f293acbdf61c420b72dde5b8af) - **tsql**: Map weekday to %w *(PR [#4438](https://github.com/tobymao/sqlglot/pull/4438) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4435](https://github.com/tobymao/sqlglot/issues/4435) opened by [@travispaice](https://github.com/travispaice)*\n- [`41d6a13`](https://github.com/tobymao/sqlglot/commit/41d6a13ccfb28fbcf772fd43ea17da3b36567e67) - add return type *(PR [#4440](https://github.com/tobymao/sqlglot/pull/4440) by [@etonlels](https://github.com/etonlels))*\n- [`fe35394`](https://github.com/tobymao/sqlglot/commit/fe3539464a153b1c0bf46975d6221dee48a48f02) - **optimizer**: fix datetime coercion in the canonicalize rule *(PR [#4431](https://github.com/tobymao/sqlglot/pull/4431) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4429](https://github.com/tobymao/sqlglot/issues/4429) opened by [@Ca1ypso](https://github.com/Ca1ypso)*\n- [`6aea9f3`](https://github.com/tobymao/sqlglot/commit/6aea9f346ef8f91467e1d5da5a3f94cf862b44fe) - Refactor NORMALIZE_FUNCTIONS flag usage *(PR [#4437](https://github.com/tobymao/sqlglot/pull/4437) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :recycle: Refactors\n- [`f32a435`](https://github.com/tobymao/sqlglot/commit/f32a435205ec288f310ad57748ac66805e27f7f5) - **risingwave**: clean up SINK/SOURCE logic *(PR [#4432](https://github.com/tobymao/sqlglot/pull/4432) by [@georgesittas](https://github.com/georgesittas))*\n- [`b24aced`](https://github.com/tobymao/sqlglot/commit/b24aced2dbb7e471d2dd0eb830ea4f2e24f9d267) - **snowflake**: clean up [WITH] TAG property / constraint *(PR [#4439](https://github.com/tobymao/sqlglot/pull/4439) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`fddcd3d`](https://github.com/tobymao/sqlglot/commit/fddcd3dfc264a645909686c201d2288c0adf9047) - bump sqlglotrs to 0.3.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.31.4] - 2024-11-17\n### :bug: Bug Fixes\n- [`59b8b6d`](https://github.com/tobymao/sqlglot/commit/59b8b6d1409b4112d425cc31db45519d5936b6fa) - preserve column quoting in DISTINCT ON elimination *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.31.3] - 2024-11-17\n### :sparkles: New Features\n- [`835e717`](https://github.com/tobymao/sqlglot/commit/835e71795f994599dbc19f1a5969b464154926e1) - **clickhouse**: transform function support *(PR [#4408](https://github.com/tobymao/sqlglot/pull/4408) by [@GaliFFun](https://github.com/GaliFFun))*\n\n### :bug: Bug Fixes\n- [`0479743`](https://github.com/tobymao/sqlglot/commit/047974393cebbddbbfb878071d159a3e538b0e4d) - **snowflake**: cast to TimeToStr arg to TIMESTAMP more conservatively *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.31.2] - 2024-11-17\n### :bug: Bug Fixes\n- [`d851269`](https://github.com/tobymao/sqlglot/commit/d851269780c7f0a0c756289c3dea9b1aa58d2a69) - use existing aliases in DISTINCT ON elimination, if any *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.31.1] - 2024-11-17\n### :sparkles: New Features\n- [`b00d857`](https://github.com/tobymao/sqlglot/commit/b00d857cd8a6d2452c2170077cbfa82352f708dd) - add support for specifying column in row_number function *(PR [#4406](https://github.com/tobymao/sqlglot/pull/4406) by [@GaliFFun](https://github.com/GaliFFun))*\n\n### :bug: Bug Fixes\n- [`0e46cc7`](https://github.com/tobymao/sqlglot/commit/0e46cc7fa2d80ba4e92182b3fa5f1075a63f4754) - refactor DISTINCT ON elimination transformation *(PR [#4407](https://github.com/tobymao/sqlglot/pull/4407) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.31.0] - 2024-11-16\n### :boom: BREAKING CHANGES\n- due to [`f4abfd5`](https://github.com/tobymao/sqlglot/commit/f4abfd59b8255cf8c39bf51028ee5f6ed704927f) - Support FORMAT_TIMESTAMP *(PR [#4383](https://github.com/tobymao/sqlglot/pull/4383) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Support FORMAT_TIMESTAMP (#4383)\n\n- due to [`45eef60`](https://github.com/tobymao/sqlglot/commit/45eef600064ad024b34e32e7acc3aca409fbd9c4) - use select star when eliminating distinct on *(PR [#4401](https://github.com/tobymao/sqlglot/pull/4401) by [@agrigoroi-palantir](https://github.com/agrigoroi-palantir))*:\n\n  use select star when eliminating distinct on (#4401)\n\n\n### :sparkles: New Features\n- [`72ffdcb`](https://github.com/tobymao/sqlglot/commit/72ffdcb631bf7afdeda2ce96911442a94b7f11eb) - **bigquery**: Add parsing support for STRPOS(...) *(PR [#4378](https://github.com/tobymao/sqlglot/pull/4378) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`e7b67e0`](https://github.com/tobymao/sqlglot/commit/e7b67e0c280179188ce1bca650735978b758dca1) - **bigquery**: Support MAKE_INTERVAL *(PR [#4384](https://github.com/tobymao/sqlglot/pull/4384) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`37c4809`](https://github.com/tobymao/sqlglot/commit/37c4809dfda48224fd982ea8a48d3dbc5c17f9ae) - **bigquery**: Support INT64(...) *(PR [#4391](https://github.com/tobymao/sqlglot/pull/4391) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`9694999`](https://github.com/tobymao/sqlglot/commit/96949999d394e27df8b0287a14e9ac82d52bc0f9) - Add support for CONTAINS(...) *(PR [#4399](https://github.com/tobymao/sqlglot/pull/4399) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`f4abfd5`](https://github.com/tobymao/sqlglot/commit/f4abfd59b8255cf8c39bf51028ee5f6ed704927f) - **bigquery**: Support FORMAT_TIMESTAMP *(PR [#4383](https://github.com/tobymao/sqlglot/pull/4383) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`bb46ee3`](https://github.com/tobymao/sqlglot/commit/bb46ee33d481a888882cbbb26a9240dd2dbb10ee) - **parser**: Parse exp.Column for DROP COLUMN *(PR [#4390](https://github.com/tobymao/sqlglot/pull/4390) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4388](https://github.com/tobymao/sqlglot/issues/4388) opened by [@AhlamHani](https://github.com/AhlamHani)*\n- [`79f6783`](https://github.com/tobymao/sqlglot/commit/79f67830d7d3ba92bff91eeb95b4dc8bdfa6c44e) - **snowflake**: Wrap DIV0 operands if they're binary expressions *(PR [#4393](https://github.com/tobymao/sqlglot/pull/4393) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4392](https://github.com/tobymao/sqlglot/issues/4392) opened by [@diogo-fernan](https://github.com/diogo-fernan)*\n- [`647b98d`](https://github.com/tobymao/sqlglot/commit/647b98d84643b88a41218fb67f6a2bd83ca4c702) - **starrocks**: Add RESERVED_KEYWORDS specific to starrocks *(PR [#4402](https://github.com/tobymao/sqlglot/pull/4402) by [@notexistence](https://github.com/notexistence))*\n- [`45eef60`](https://github.com/tobymao/sqlglot/commit/45eef600064ad024b34e32e7acc3aca409fbd9c4) - use select star when eliminating distinct on *(PR [#4401](https://github.com/tobymao/sqlglot/pull/4401) by [@agrigoroi-palantir](https://github.com/agrigoroi-palantir))*\n\n### :recycle: Refactors\n- [`a3af2af`](https://github.com/tobymao/sqlglot/commit/a3af2af3a893dfd6c6946b732aa086d1f1d91570) - attach stamement comments consistently *(PR [#4377](https://github.com/tobymao/sqlglot/pull/4377) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4376](https://github.com/tobymao/sqlglot/issues/4376) opened by [@YieldRay](https://github.com/YieldRay)*\n\n### :wrench: Chores\n- [`858c5b1`](https://github.com/tobymao/sqlglot/commit/858c5b1a43f74e11b8c357986c78b5068792b3af) - improve contribution guide *(PR [#4379](https://github.com/tobymao/sqlglot/pull/4379) by [@georgesittas](https://github.com/georgesittas))*\n- [`160e688`](https://github.com/tobymao/sqlglot/commit/160e6883225cd6ad41a218213f73aa9f91b5fc5e) - fix relative benchmark import, comment out sqltree *(PR [#4403](https://github.com/tobymao/sqlglot/pull/4403) by [@georgesittas](https://github.com/georgesittas))*\n- [`8d78add`](https://github.com/tobymao/sqlglot/commit/8d78addccaaffa4ea2dcfe1de002f8a653f137b7) - bump PYO3 to v\"0.22.6\" *(PR [#4400](https://github.com/tobymao/sqlglot/pull/4400) by [@MartinSahlen](https://github.com/MartinSahlen))*\n- [`f78e755`](https://github.com/tobymao/sqlglot/commit/f78e755adaf52823642d2b0e1cae54da835ec653) - bump sqlglotrs to v0.2.14 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.30.0] - 2024-11-11\n### :boom: BREAKING CHANGES\n- due to [`60625ea`](https://github.com/tobymao/sqlglot/commit/60625eae34deb6a6fc36c0f3996f1281eae0ef6f) - Fix STRUCT cast generation *(PR [#4366](https://github.com/tobymao/sqlglot/pull/4366) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix STRUCT cast generation (#4366)\n\n\n### :sparkles: New Features\n- [`87ab8fe`](https://github.com/tobymao/sqlglot/commit/87ab8fe9cc4d6d060d8fe8a9c3faf8c47c2c9ed6) - **spark, bigquery**: Add support for UNIX_SECONDS(...) *(PR [#4350](https://github.com/tobymao/sqlglot/pull/4350) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`42da638`](https://github.com/tobymao/sqlglot/commit/42da63812ed489d1d8bbef0fc14c7dfa5ce57b7a) - **bigquery**: Support JSON_VALUE_ARRAY(...) *(PR [#4356](https://github.com/tobymao/sqlglot/pull/4356) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`e337a42`](https://github.com/tobymao/sqlglot/commit/e337a42dd56f5358e617750e7a70a0d4b7eab3f9) - **bigquery**: Parse REGEXP_SUBSTR as exp.RegexpExtract *(PR [#4358](https://github.com/tobymao/sqlglot/pull/4358) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`602dbf8`](https://github.com/tobymao/sqlglot/commit/602dbf84ce23f41fba6a87db70ecec6113044bac) - Support REGEXP_EXTRACT_ALL *(PR [#4359](https://github.com/tobymao/sqlglot/pull/4359) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`27a44a2`](https://github.com/tobymao/sqlglot/commit/27a44a22ff78cc35e8ab7c91b94311ef93d86c5a) - improve Levenshtein expression transpilation *(PR [#4360](https://github.com/tobymao/sqlglot/pull/4360) by [@krzysztof-kwitt](https://github.com/krzysztof-kwitt))*\n- [`79c675a`](https://github.com/tobymao/sqlglot/commit/79c675a49fb44a6a7a97ea0de79822d8571724be) - **bigquery**: Support JSON_QUERY_ARRAY & JSON_EXTRACT_ARRAY *(PR [#4361](https://github.com/tobymao/sqlglot/pull/4361) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`57722db`](https://github.com/tobymao/sqlglot/commit/57722db90394d9a102c0e76a3e4d32a9f72f9ff9) - optionally wrap connectors when using builders *(PR [#4369](https://github.com/tobymao/sqlglot/pull/4369) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4362](https://github.com/tobymao/sqlglot/issues/4362) opened by [@gabrielteotonio](https://github.com/gabrielteotonio)*\n  - :arrow_lower_right: *addresses issue [#4367](https://github.com/tobymao/sqlglot/issues/4367) opened by [@gabrielteotonio](https://github.com/gabrielteotonio)*\n\n### :bug: Bug Fixes\n- [`eb8e2fe`](https://github.com/tobymao/sqlglot/commit/eb8e2fe3ab3fb4b88f72843a5bd21f4a3c1d895c) - bubble up comments in qualified column refs fixes [#4353](https://github.com/tobymao/sqlglot/pull/4353) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`efcbfdb`](https://github.com/tobymao/sqlglot/commit/efcbfdb67b12853581fbfc0d4c4a450c0281849b) - **clickhouse**: Generate exp.Median as lowercase *(PR [#4355](https://github.com/tobymao/sqlglot/pull/4355) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4354](https://github.com/tobymao/sqlglot/issues/4354) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`60625ea`](https://github.com/tobymao/sqlglot/commit/60625eae34deb6a6fc36c0f3996f1281eae0ef6f) - **duckdb**: Fix STRUCT cast generation *(PR [#4366](https://github.com/tobymao/sqlglot/pull/4366) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4365](https://github.com/tobymao/sqlglot/issues/4365) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`a665030`](https://github.com/tobymao/sqlglot/commit/a665030323b200f3bed241bb928993b9807c4100) - safe removal while iterating expression list for multiple UNNEST expressions *(PR [#4364](https://github.com/tobymao/sqlglot/pull/4364) by [@gauravsagar483](https://github.com/gauravsagar483))*\n- [`a71cee4`](https://github.com/tobymao/sqlglot/commit/a71cee4b4eafad9988b945c69dc75583ae105ec7) - Transpilation of exp.ArraySize from Postgres (read) *(PR [#4370](https://github.com/tobymao/sqlglot/pull/4370) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4368](https://github.com/tobymao/sqlglot/issues/4368) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`702fe31`](https://github.com/tobymao/sqlglot/commit/702fe318dadbe6cb83676e2a23ee830774697bb0) - Remove flaky timing test *(PR [#4371](https://github.com/tobymao/sqlglot/pull/4371) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`4d3904e`](https://github.com/tobymao/sqlglot/commit/4d3904e8906f0573f3352ad82282ea09c571daa8) - **spark**: Support DB's TIMESTAMP_DIFF *(PR [#4373](https://github.com/tobymao/sqlglot/pull/4373) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4372](https://github.com/tobymao/sqlglot/issues/4372) opened by [@nikmalviya](https://github.com/nikmalviya)*\n- [`060ecfc`](https://github.com/tobymao/sqlglot/commit/060ecfc75fd8a07ffbc19f34959155a0fce317b6) - don't generate comments in table_name *(PR [#4375](https://github.com/tobymao/sqlglot/pull/4375) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`e19fb62`](https://github.com/tobymao/sqlglot/commit/e19fb620dbe6e405518aee381183e4640b638aa4) - improve error handling for unnest_to_explode *(PR [#4339](https://github.com/tobymao/sqlglot/pull/4339) by [@gauravsagar483](https://github.com/gauravsagar483))*\n\n\n## [v25.29.0] - 2024-11-05\n### :boom: BREAKING CHANGES\n- due to [`e92904e`](https://github.com/tobymao/sqlglot/commit/e92904e61ab3b14fe18d472df19311f9b014f6cc) - Transpile ANY to EXISTS *(PR [#4305](https://github.com/tobymao/sqlglot/pull/4305) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Transpile ANY to EXISTS (#4305)\n\n- due to [`23e620f`](https://github.com/tobymao/sqlglot/commit/23e620f7cd2860fbce45a5377a75ae0c8f031ce0) - Support MEDIAN() function *(PR [#4317](https://github.com/tobymao/sqlglot/pull/4317) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Support MEDIAN() function (#4317)\n\n- due to [`a093ae7`](https://github.com/tobymao/sqlglot/commit/a093ae750af8a351e54f1431deba1f2ce6843666) - always wrap value in NOT value IS ... *(PR [#4331](https://github.com/tobymao/sqlglot/pull/4331) by [@georgesittas](https://github.com/georgesittas))*:\n\n  always wrap value in NOT value IS ... (#4331)\n\n- due to [`84f78aa`](https://github.com/tobymao/sqlglot/commit/84f78aafd5d7e74da407167cd394d2bff0718cfb) - parse information schema views into a single identifier *(PR [#4336](https://github.com/tobymao/sqlglot/pull/4336) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse information schema views into a single identifier (#4336)\n\n\n### :sparkles: New Features\n- [`efd9b4e`](https://github.com/tobymao/sqlglot/commit/efd9b4ed5a761a2ebfc47a1582e9d1b2eb7cb277) - **postgres**: Support JSONB_EXISTS *(PR [#4302](https://github.com/tobymao/sqlglot/pull/4302) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4299](https://github.com/tobymao/sqlglot/issues/4299) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`e92904e`](https://github.com/tobymao/sqlglot/commit/e92904e61ab3b14fe18d472df19311f9b014f6cc) - **spark**: Transpile ANY to EXISTS *(PR [#4305](https://github.com/tobymao/sqlglot/pull/4305) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4298](https://github.com/tobymao/sqlglot/issues/4298) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`2af4936`](https://github.com/tobymao/sqlglot/commit/2af4936bd9b318c695aae249324ff67bcd1292f6) - **snowflake**: Transpile BQ's TIMESTAMP() function *(PR [#4309](https://github.com/tobymao/sqlglot/pull/4309) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`50a1c91`](https://github.com/tobymao/sqlglot/commit/50a1c919d0d46384e3bd9ba1d45c24dd07efe6d2) - **snowflake**: Transpile exp.TimestampAdd *(PR [#4320](https://github.com/tobymao/sqlglot/pull/4320) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`01671ce`](https://github.com/tobymao/sqlglot/commit/01671ce137c9cf8d0f12dadc66e0db141f797d16) - **teradata**: add support for hexadecimal literals *(PR [#4323](https://github.com/tobymao/sqlglot/pull/4323) by [@thomascjohnson](https://github.com/thomascjohnson))*\n- [`23e620f`](https://github.com/tobymao/sqlglot/commit/23e620f7cd2860fbce45a5377a75ae0c8f031ce0) - Support MEDIAN() function *(PR [#4317](https://github.com/tobymao/sqlglot/pull/4317) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4315](https://github.com/tobymao/sqlglot/issues/4315) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`9faef8d`](https://github.com/tobymao/sqlglot/commit/9faef8d1ceff91dd88db46b2c187d64f15490bf4) - **snowflake**: Transpile exp.TimestampSub *(PR [#4329](https://github.com/tobymao/sqlglot/pull/4329) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2d98cac`](https://github.com/tobymao/sqlglot/commit/2d98cacc723bc0c0df8ce11895983fb7cb9f5237) - **BigQuery**: Support JSON_VALUE() *(PR [#4332](https://github.com/tobymao/sqlglot/pull/4332) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`f8fec0a`](https://github.com/tobymao/sqlglot/commit/f8fec0ab098df37c3b54d91c24e5d8ec84f7cdbe) - **snowflake**: Transpile exp.DatetimeDiff *(PR [#4334](https://github.com/tobymao/sqlglot/pull/4334) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`16fd1ea`](https://github.com/tobymao/sqlglot/commit/16fd1ea2653a602bdc0d8b81e971fb1acadee585) - **BigQuery**: Support JSON_QUERY *(PR [#4333](https://github.com/tobymao/sqlglot/pull/4333) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`c09b6a2`](https://github.com/tobymao/sqlglot/commit/c09b6a2a37807795ead251f4fb81a9ba144cce27) - **duckdb**: support flags for RegexpExtract *(PR [#4326](https://github.com/tobymao/sqlglot/pull/4326) by [@NickCrews](https://github.com/NickCrews))*\n- [`536973c`](https://github.com/tobymao/sqlglot/commit/536973cfc9d00110e388e8af1ed91d73607e07c2) - **trino**: add support for the ON OVERFLOW clause in LISTAGG *(PR [#4340](https://github.com/tobymao/sqlglot/pull/4340) by [@georgesittas](https://github.com/georgesittas))*\n- [`4584935`](https://github.com/tobymao/sqlglot/commit/4584935cab328eced61c62a998cc013cab5cc3e3) - **snowflake**: Transpile exp.StrToDate *(PR [#4348](https://github.com/tobymao/sqlglot/pull/4348) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`71f4a47`](https://github.com/tobymao/sqlglot/commit/71f4a47910d5db97fa1a286891d72b5c4694d294) - **snowflake**: Transpile exp.DatetimeAdd *(PR [#4349](https://github.com/tobymao/sqlglot/pull/4349) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`551afff`](https://github.com/tobymao/sqlglot/commit/551afff58ea7bc1047775bfcd5d80b812fb3f682) - handle a Move edge case in the semantic differ *(PR [#4295](https://github.com/tobymao/sqlglot/pull/4295) by [@georgesittas](https://github.com/georgesittas))*\n- [`a66e721`](https://github.com/tobymao/sqlglot/commit/a66e721dcd63488f7f3b427569a2115ae044c71b) - **generator**: Add NULL FILTER on ARRAY_AGG only for columns *(PR [#4301](https://github.com/tobymao/sqlglot/pull/4301) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4300](https://github.com/tobymao/sqlglot/issues/4300) opened by [@elad-sachs](https://github.com/elad-sachs)*\n- [`b4ea602`](https://github.com/tobymao/sqlglot/commit/b4ea602ab17b0e8e85ddb090156c7bd2c6354de4) - **clickhouse**: improve parsing of WITH FILL ... INTERPOLATE *(PR [#4311](https://github.com/tobymao/sqlglot/pull/4311) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4310](https://github.com/tobymao/sqlglot/issues/4310) opened by [@brunorpinho](https://github.com/brunorpinho)*\n- [`749886b`](https://github.com/tobymao/sqlglot/commit/749886b574a5dfa03aeb78b76d9cc097aa0f3e65) - **tsql**: Generate LOG(...) for exp.Ln *(PR [#4318](https://github.com/tobymao/sqlglot/pull/4318) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4316](https://github.com/tobymao/sqlglot/issues/4316) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`5c1b1f4`](https://github.com/tobymao/sqlglot/commit/5c1b1f43014967f6853752ba8d0899757a3efcd5) - **parser**: optionally parse a Stream expression *(PR [#4325](https://github.com/tobymao/sqlglot/pull/4325) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4324](https://github.com/tobymao/sqlglot/issues/4324) opened by [@lancewl](https://github.com/lancewl)*\n- [`bb49a00`](https://github.com/tobymao/sqlglot/commit/bb49a00b16487356369bbb77aff9c2ff3f9cda52) - **oracle**: Do not normalize time units for exp.DateTrunc *(PR [#4328](https://github.com/tobymao/sqlglot/pull/4328) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4321](https://github.com/tobymao/sqlglot/issues/4321) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`a093ae7`](https://github.com/tobymao/sqlglot/commit/a093ae750af8a351e54f1431deba1f2ce6843666) - **clickhouse**: always wrap value in NOT value IS ... *(PR [#4331](https://github.com/tobymao/sqlglot/pull/4331) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4330](https://github.com/tobymao/sqlglot/issues/4330) opened by [@elchyn-cheliabiyeu](https://github.com/elchyn-cheliabiyeu)*\n- [`def4f1e`](https://github.com/tobymao/sqlglot/commit/def4f1e3a9eac7545dfad223a5d49cee4fb7eeb8) - Refactor exp.RegexpExtract (follow up 4326) *(PR [#4341](https://github.com/tobymao/sqlglot/pull/4341) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`c1456d0`](https://github.com/tobymao/sqlglot/commit/c1456d07097c42a2ba2078ad30a8afe4cc89597d) - presto/trino current_time closes [#4344](https://github.com/tobymao/sqlglot/pull/4344) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`8e16abe`](https://github.com/tobymao/sqlglot/commit/8e16abe2fed324b7ed6c718753cc623a8eb37814) - **duckdb**: we ALWAYS need to render group if params is present for RegexpExtract *(PR [#4343](https://github.com/tobymao/sqlglot/pull/4343) by [@NickCrews](https://github.com/NickCrews))*\n- [`1689dc7`](https://github.com/tobymao/sqlglot/commit/1689dc7adbb913fe603b5e37eba29cc10d344cd2) - **bigquery**: Parse timezone for DATE_TRUNC *(PR [#4347](https://github.com/tobymao/sqlglot/pull/4347) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4346](https://github.com/tobymao/sqlglot/issues/4346) opened by [@CYFish](https://github.com/CYFish)*\n- [`84f78aa`](https://github.com/tobymao/sqlglot/commit/84f78aafd5d7e74da407167cd394d2bff0718cfb) - **bigquery**: parse information schema views into a single identifier *(PR [#4336](https://github.com/tobymao/sqlglot/pull/4336) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.28.0] - 2024-10-25\n### :boom: BREAKING CHANGES\n- due to [`1691388`](https://github.com/tobymao/sqlglot/commit/16913887f5573f01eb8cd2b9336d4b37b84a449a) - Fix chained exp.SetOperation type annotation *(PR [#4274](https://github.com/tobymao/sqlglot/pull/4274) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Fix chained exp.SetOperation type annotation (#4274)\n\n- due to [`c3c1997`](https://github.com/tobymao/sqlglot/commit/c3c199714df04edfe3698594680bac06575ca285) - Add support for STRING function *(PR [#4284](https://github.com/tobymao/sqlglot/pull/4284) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for STRING function (#4284)\n\n\n### :sparkles: New Features\n- [`379f487`](https://github.com/tobymao/sqlglot/commit/379f487080d95ef6e87cbbae8003541cde381ac0) - **bigquery**: transpile EDIT_DISTANCE, closes [#4283](https://github.com/tobymao/sqlglot/pull/4283) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c3c1997`](https://github.com/tobymao/sqlglot/commit/c3c199714df04edfe3698594680bac06575ca285) - **bigquery**: Add support for STRING function *(PR [#4284](https://github.com/tobymao/sqlglot/pull/4284) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`1a26bff`](https://github.com/tobymao/sqlglot/commit/1a26bff619315a6e9dc3eab4dec07746b4820796) - **snowflake**: Transpile exp.SafeDivide *(PR [#4294](https://github.com/tobymao/sqlglot/pull/4294) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`ac66d2f`](https://github.com/tobymao/sqlglot/commit/ac66d2f4b94e6a984adbf3df01139b6378248158) - **clickhouse**: properly parse CREATE FUNCTION DDLs *(PR [#4282](https://github.com/tobymao/sqlglot/pull/4282) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3276](https://github.com/TobikoData/sqlmesh/issues/3276) opened by [@jwhitaker-gridcog](https://github.com/jwhitaker-gridcog)*\n- [`1691388`](https://github.com/tobymao/sqlglot/commit/16913887f5573f01eb8cd2b9336d4b37b84a449a) - **optimizer**: Fix chained exp.SetOperation type annotation *(PR [#4274](https://github.com/tobymao/sqlglot/pull/4274) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4261](https://github.com/tobymao/sqlglot/issues/4261) opened by [@gabrielteotonio](https://github.com/gabrielteotonio)*\n- [`559e7bc`](https://github.com/tobymao/sqlglot/commit/559e7bc5bbc77e94dea6de0470659b3c3fa6851f) - **clickhouse**: Wrap subquery if it's LHS of IS NOT NULL *(PR [#4287](https://github.com/tobymao/sqlglot/pull/4287) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4285](https://github.com/tobymao/sqlglot/issues/4285) opened by [@EugeneTorap](https://github.com/EugeneTorap)*\n- [`47bc09a`](https://github.com/tobymao/sqlglot/commit/47bc09a85a3781682f5e58bfde5f453fb1a7c50b) - **sqlite**: Fix UNIQUE parsing *(PR [#4293](https://github.com/tobymao/sqlglot/pull/4293) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4291](https://github.com/tobymao/sqlglot/issues/4291) opened by [@tshu-w](https://github.com/tshu-w)*\n- [`ee266ef`](https://github.com/tobymao/sqlglot/commit/ee266ef8f92fe72252eea36b56e8825715644a4f) - improve support for identifier delimiter escaping *(PR [#4288](https://github.com/tobymao/sqlglot/pull/4288) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`c6ff7f1`](https://github.com/tobymao/sqlglot/commit/c6ff7f1a0b6e443d80bc0f0ad1086d5c7b13b9f4) - bump sqlglotrs to v0.2.13 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.27.0] - 2024-10-22\n### :boom: BREAKING CHANGES\n- due to [`4d86499`](https://github.com/tobymao/sqlglot/commit/4d8649940d02ac319f2fec372a52674488f01de5) - include the target node for Move edits *(PR [#4277](https://github.com/tobymao/sqlglot/pull/4277) by [@georgesittas](https://github.com/georgesittas))*:\n\n  include the target node for Move edits (#4277)\n\n- due to [`9771965`](https://github.com/tobymao/sqlglot/commit/97719657d1b2074dabfbe54af0e1ea3acd6d4744) - Add support for TIMESTAMP_NTZ_FROM_PARTS *(PR [#4280](https://github.com/tobymao/sqlglot/pull/4280) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add support for TIMESTAMP_NTZ_FROM_PARTS (#4280)\n\n- due to [`768adb3`](https://github.com/tobymao/sqlglot/commit/768adb3d85ed88931d761e5ecc8fb4a3a40d0dc5) - time string literals containing fractional seconds *(PR [#4269](https://github.com/tobymao/sqlglot/pull/4269) by [@treysp](https://github.com/treysp))*:\n\n  time string literals containing fractional seconds (#4269)\n\n\n### :sparkles: New Features\n- [`9771965`](https://github.com/tobymao/sqlglot/commit/97719657d1b2074dabfbe54af0e1ea3acd6d4744) - **snowflake**: Add support for TIMESTAMP_NTZ_FROM_PARTS *(PR [#4280](https://github.com/tobymao/sqlglot/pull/4280) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`9e11654`](https://github.com/tobymao/sqlglot/commit/9e11654c6ebf7451f14d46c006070effe452519a) - **clickhouse**: add geometry types *(PR [#4278](https://github.com/tobymao/sqlglot/pull/4278) by [@jwhitaker-gridcog](https://github.com/jwhitaker-gridcog))*\n\n### :bug: Bug Fixes\n- [`c25a9ab`](https://github.com/tobymao/sqlglot/commit/c25a9ab577d7f0a1056e8afab680ca7801c47fff) - **tsql**: Keep CTE's attached to the query when emulating IF NOT EXISTS *(PR [#4279](https://github.com/tobymao/sqlglot/pull/4279) by [@erindru](https://github.com/erindru))*\n- [`768adb3`](https://github.com/tobymao/sqlglot/commit/768adb3d85ed88931d761e5ecc8fb4a3a40d0dc5) - **clickhouse**: time string literals containing fractional seconds *(PR [#4269](https://github.com/tobymao/sqlglot/pull/4269) by [@treysp](https://github.com/treysp))*\n\n### :recycle: Refactors\n- [`4d86499`](https://github.com/tobymao/sqlglot/commit/4d8649940d02ac319f2fec372a52674488f01de5) - **diff**: include the target node for Move edits *(PR [#4277](https://github.com/tobymao/sqlglot/pull/4277) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.26.0] - 2024-10-21\n### :boom: BREAKING CHANGES\n- due to [`142c3e7`](https://github.com/tobymao/sqlglot/commit/142c3e75b25374ba9259f21b51cd728bbeb280ef) - Support TO_DOUBLE function *(PR [#4255](https://github.com/tobymao/sqlglot/pull/4255) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Support TO_DOUBLE function (#4255)\n\n- due to [`13d0696`](https://github.com/tobymao/sqlglot/commit/13d06966a2ca9264f35d5a58e1eaff1baa7dc66e) - Support TRY_TO_TIMESTAMP function *(PR [#4257](https://github.com/tobymao/sqlglot/pull/4257) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Support TRY_TO_TIMESTAMP function (#4257)\n\n- due to [`7fc0055`](https://github.com/tobymao/sqlglot/commit/7fc0055fb04713ba047baa5eda1ce0baf1cc79e2) - dont parse right-hand side operands of ARRAY JOIN as Tables *(PR [#4258](https://github.com/tobymao/sqlglot/pull/4258) by [@georgesittas](https://github.com/georgesittas))*:\n\n  dont parse right-hand side operands of ARRAY JOIN as Tables (#4258)\n\n- due to [`222152e`](https://github.com/tobymao/sqlglot/commit/222152e32521dbc6de3384b18ab4c677239c6088) - Add type hints for optimizer rules eliminate & merge subqueries *(PR [#4267](https://github.com/tobymao/sqlglot/pull/4267) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add type hints for optimizer rules eliminate & merge subqueries (#4267)\n\n\n### :sparkles: New Features\n- [`6f32e53`](https://github.com/tobymao/sqlglot/commit/6f32e5348d9aeba9c5d51a892023b2e14e072119) - support non-strict qualify_columns *(PR [#4243](https://github.com/tobymao/sqlglot/pull/4243) by [@hsheth2](https://github.com/hsheth2))*\n- [`ed97954`](https://github.com/tobymao/sqlglot/commit/ed97954ecd7c2d7d4fe1bbf2ec0ecc000dd02b32) - **duckdb**: Transpile Spark's LATERAL VIEW EXPLODE *(PR [#4252](https://github.com/tobymao/sqlglot/pull/4252) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4247](https://github.com/tobymao/sqlglot/issues/4247) opened by [@idanyadgar-clutch](https://github.com/idanyadgar-clutch)*\n- [`8f5efc7`](https://github.com/tobymao/sqlglot/commit/8f5efc7bc01ba5923584cd6ef38a4d81e763ccae) - **oracle**: parse hints  *(PR [#4249](https://github.com/tobymao/sqlglot/pull/4249) by [@mkmoisen](https://github.com/mkmoisen))*\n- [`8b7ff5e`](https://github.com/tobymao/sqlglot/commit/8b7ff5ee8713a3ba50c48addd3700927a0240cf5) - **starrocks**: support for ALTER TABLE SWAP WITH *(PR [#4256](https://github.com/tobymao/sqlglot/pull/4256) by [@mrhamburg](https://github.com/mrhamburg))*\n- [`1c43348`](https://github.com/tobymao/sqlglot/commit/1c433487a45379298ef27b3688723df2bd740fd1) - **trino**: Support for LISTAGG function *(PR [#4253](https://github.com/tobymao/sqlglot/pull/4253) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4250](https://github.com/tobymao/sqlglot/issues/4250) opened by [@npochhi](https://github.com/npochhi)*\n- [`142c3e7`](https://github.com/tobymao/sqlglot/commit/142c3e75b25374ba9259f21b51cd728bbeb280ef) - **snowflake**: Support TO_DOUBLE function *(PR [#4255](https://github.com/tobymao/sqlglot/pull/4255) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`13d0696`](https://github.com/tobymao/sqlglot/commit/13d06966a2ca9264f35d5a58e1eaff1baa7dc66e) - **snowflake**: Support TRY_TO_TIMESTAMP function *(PR [#4257](https://github.com/tobymao/sqlglot/pull/4257) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`04dccf3`](https://github.com/tobymao/sqlglot/commit/04dccf3cdaf1c3a0466dda113aba5439f1639ae0) - **tsql**: Support for stored procedure options *(PR [#4260](https://github.com/tobymao/sqlglot/pull/4260) by [@rsanchez-xtillion](https://github.com/rsanchez-xtillion))*\n- [`36f6841`](https://github.com/tobymao/sqlglot/commit/36f68416b3dd0d9ac703dd926d1f74bc43566e0d) - **bigquery**: support EDIT_DISTANCE (Levinshtein) function *(PR [#4276](https://github.com/tobymao/sqlglot/pull/4276) by [@esciara](https://github.com/esciara))*\n  - :arrow_lower_right: *addresses issue [#4275](https://github.com/tobymao/sqlglot/issues/4275) opened by [@esciara](https://github.com/esciara)*\n\n### :bug: Bug Fixes\n- [`fcc05c9`](https://github.com/tobymao/sqlglot/commit/fcc05c9daa31c7a51474ec9c72ceafd682359f90) - **bigquery**: Early expand only aliased names in GROUP BY *(PR [#4246](https://github.com/tobymao/sqlglot/pull/4246) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5655cfb`](https://github.com/tobymao/sqlglot/commit/5655cfba7afdf8f95dea53d5ededfde209b77c30) - add support for negative intervals in to_interval *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`51f4d26`](https://github.com/tobymao/sqlglot/commit/51f4d26ed8694365c61fdefd810a420fcfefdeca) - generate single argument ArrayConcat without trailing comma fixes [#4259](https://github.com/tobymao/sqlglot/pull/4259) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7fc0055`](https://github.com/tobymao/sqlglot/commit/7fc0055fb04713ba047baa5eda1ce0baf1cc79e2) - **clickhouse**: dont parse right-hand side operands of ARRAY JOIN as Tables *(PR [#4258](https://github.com/tobymao/sqlglot/pull/4258) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4254](https://github.com/tobymao/sqlglot/issues/4254) opened by [@xtess16](https://github.com/xtess16)*\n- [`8f49ad8`](https://github.com/tobymao/sqlglot/commit/8f49ad87fa795349183d13129110ad59387bfe11) - **clickhouse**: traverse_scope with FINAL modifier *(PR [#4263](https://github.com/tobymao/sqlglot/pull/4263) by [@pkit](https://github.com/pkit))*\n  - :arrow_lower_right: *fixes issue [#4262](https://github.com/tobymao/sqlglot/issues/4262) opened by [@obazna](https://github.com/obazna)*\n- [`83167ea`](https://github.com/tobymao/sqlglot/commit/83167eaa3039195f756c7b1ad95fc9162f19b1b1) - hive dialect hierarchy has no CURRENT_TIME func *(PR [#4264](https://github.com/tobymao/sqlglot/pull/4264) by [@georgesittas](https://github.com/georgesittas))*\n- [`7a5c7e0`](https://github.com/tobymao/sqlglot/commit/7a5c7e036fa84eb30bcae75829f3cb94503fa99e) - **presto**: transpile BIT to BOOLEAN *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`48be3d8`](https://github.com/tobymao/sqlglot/commit/48be3d89b1df96c7b8d81536862f53a98e414f11) - make the semantic diffing aware of changes to non-expression leaves *(PR [#4268](https://github.com/tobymao/sqlglot/pull/4268) by [@georgesittas](https://github.com/georgesittas))*\n- [`4543fb3`](https://github.com/tobymao/sqlglot/commit/4543fb3cd052dfb20428f5a6254b38def9e756ee) - **optimizer**: Fix merge_subqueries.py::rename_inner_sources() *(PR [#4266](https://github.com/tobymao/sqlglot/pull/4266) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4245](https://github.com/tobymao/sqlglot/issues/4245) opened by [@daniel769](https://github.com/daniel769)*\n- [`222152e`](https://github.com/tobymao/sqlglot/commit/222152e32521dbc6de3384b18ab4c677239c6088) - **optimizer**: Add type hints for optimizer rules eliminate & merge subqueries *(PR [#4267](https://github.com/tobymao/sqlglot/pull/4267) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :recycle: Refactors\n- [`94013a2`](https://github.com/tobymao/sqlglot/commit/94013a21ca69b90da78dc47b16cd86503736597a) - simplify _expression_only_args helper in diff module *(PR [#4251](https://github.com/tobymao/sqlglot/pull/4251) by [@georgesittas](https://github.com/georgesittas))*\n- [`41e2eba`](https://github.com/tobymao/sqlglot/commit/41e2eba1a01c1a5b784ad9dc6c5191f3d3bc0d74) - **Oracle**: simplify hint arg formatting *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`cfd692f`](https://github.com/tobymao/sqlglot/commit/cfd692ff28a59f413671aafbc8dcd61eab3558c3) - move SwapTable logic to the base Parser/Generator classes *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.25.1] - 2024-10-15\n### :bug: Bug Fixes\n- [`e6567ae`](https://github.com/tobymao/sqlglot/commit/e6567ae11650834874808a844a19836fbb9ee753) - small overload fix for ensure list taking None *(PR [#4248](https://github.com/tobymao/sqlglot/pull/4248) by [@benfdking](https://github.com/benfdking))*\n\n\n## [v25.25.0] - 2024-10-14\n### :boom: BREAKING CHANGES\n- due to [`275b64b`](https://github.com/tobymao/sqlglot/commit/275b64b6a28722232a24870e443b249994220d54) - refactor set operation builders so they can work with N expressions *(PR [#4226](https://github.com/tobymao/sqlglot/pull/4226) by [@georgesittas](https://github.com/georgesittas))*:\n\n  refactor set operation builders so they can work with N expressions (#4226)\n\n- due to [`aee76da`](https://github.com/tobymao/sqlglot/commit/aee76da1cadec242f7428d23999f1752cb0708ca) - Native annotations for string functions *(PR [#4231](https://github.com/tobymao/sqlglot/pull/4231) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Native annotations for string functions (#4231)\n\n- due to [`202aaa0`](https://github.com/tobymao/sqlglot/commit/202aaa0e7390142ee3ade41c28e2e77cde31f295) - Native annotations for string functions *(PR [#4234](https://github.com/tobymao/sqlglot/pull/4234) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Native annotations for string functions (#4234)\n\n- due to [`5741180`](https://github.com/tobymao/sqlglot/commit/5741180e895eaaa75a07af388d36a0d2df97b28c) - produce exp.Column for the RHS of <value> IN <name> *(PR [#4239](https://github.com/tobymao/sqlglot/pull/4239) by [@georgesittas](https://github.com/georgesittas))*:\n\n  produce exp.Column for the RHS of <value> IN <name> (#4239)\n\n- due to [`4da2502`](https://github.com/tobymao/sqlglot/commit/4da25029b1c6f1425b4602f42da4fa1bcd3fccdb) - make Explode a UDTF subclass *(PR [#4242](https://github.com/tobymao/sqlglot/pull/4242) by [@georgesittas](https://github.com/georgesittas))*:\n\n  make Explode a UDTF subclass (#4242)\n\n\n### :sparkles: New Features\n- [`163e943`](https://github.com/tobymao/sqlglot/commit/163e943cdaf449599640c198f69e73d2398eb323) - **tsql**: SPLIT_PART function and conversion to PARSENAME in tsql *(PR [#4211](https://github.com/tobymao/sqlglot/pull/4211) by [@daihuynh](https://github.com/daihuynh))*\n- [`275b64b`](https://github.com/tobymao/sqlglot/commit/275b64b6a28722232a24870e443b249994220d54) - refactor set operation builders so they can work with N expressions *(PR [#4226](https://github.com/tobymao/sqlglot/pull/4226) by [@georgesittas](https://github.com/georgesittas))*\n- [`3f6ba3e`](https://github.com/tobymao/sqlglot/commit/3f6ba3e69c9ba92429d2b3b00cac33f45518aa56) - **clickhouse**: Support varlen arrays for ARRAY JOIN *(PR [#4229](https://github.com/tobymao/sqlglot/pull/4229) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4227](https://github.com/tobymao/sqlglot/issues/4227) opened by [@brunorpinho](https://github.com/brunorpinho)*\n- [`aee76da`](https://github.com/tobymao/sqlglot/commit/aee76da1cadec242f7428d23999f1752cb0708ca) - **bigquery**: Native annotations for string functions *(PR [#4231](https://github.com/tobymao/sqlglot/pull/4231) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`202aaa0`](https://github.com/tobymao/sqlglot/commit/202aaa0e7390142ee3ade41c28e2e77cde31f295) - **bigquery**: Native annotations for string functions *(PR [#4234](https://github.com/tobymao/sqlglot/pull/4234) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`eeae25e`](https://github.com/tobymao/sqlglot/commit/eeae25e03a883671f9d5e514f9bd3021fb6c0d32) - support EXPLAIN in mysql *(PR [#4235](https://github.com/tobymao/sqlglot/pull/4235) by [@xiaoyu-meng-mxy](https://github.com/xiaoyu-meng-mxy))*\n- [`06748d9`](https://github.com/tobymao/sqlglot/commit/06748d93ccd232528003c37fdda25ae8163f3c18) - **mysql**: add support for operation modifiers like HIGH_PRIORITY *(PR [#4238](https://github.com/tobymao/sqlglot/pull/4238) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4236](https://github.com/tobymao/sqlglot/issues/4236) opened by [@asdfsx](https://github.com/asdfsx)*\n\n### :bug: Bug Fixes\n- [`dcdec95`](https://github.com/tobymao/sqlglot/commit/dcdec95f986426ae90469baca993b47ac390081b) - Make exp.Update a DML node *(PR [#4223](https://github.com/tobymao/sqlglot/pull/4223) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4221](https://github.com/tobymao/sqlglot/issues/4221) opened by [@rahul-ve](https://github.com/rahul-ve)*\n- [`79caf51`](https://github.com/tobymao/sqlglot/commit/79caf519987718390a086bee19fdc89f6094496c) - **clickhouse**: rename BOOLEAN type to Bool fixes [#4230](https://github.com/tobymao/sqlglot/pull/4230) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b26a3f6`](https://github.com/tobymao/sqlglot/commit/b26a3f67b7113802ba1b4b3b211431e98258dc15) - satisfy mypy *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`5741180`](https://github.com/tobymao/sqlglot/commit/5741180e895eaaa75a07af388d36a0d2df97b28c) - **parser**: produce exp.Column for the RHS of <value> IN <name> *(PR [#4239](https://github.com/tobymao/sqlglot/pull/4239) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4237](https://github.com/tobymao/sqlglot/issues/4237) opened by [@rustyconover](https://github.com/rustyconover)*\n- [`daa6e78`](https://github.com/tobymao/sqlglot/commit/daa6e78e4b810eff826f995aa52f9e38197f1b7e) - **optimizer**: handle subquery predicate substitution correctly in de morgan's rule *(PR [#4240](https://github.com/tobymao/sqlglot/pull/4240) by [@georgesittas](https://github.com/georgesittas))*\n- [`c0a8355`](https://github.com/tobymao/sqlglot/commit/c0a83556acffcd77521f69bf51503a07310f749d) - **parser**: parse a column reference for the RHS of the IN clause *(PR [#4241](https://github.com/tobymao/sqlglot/pull/4241) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`0882f03`](https://github.com/tobymao/sqlglot/commit/0882f03d526f593b2d415e85b7d7a7c113721806) - Rename exp.RenameTable to exp.AlterRename *(PR [#4224](https://github.com/tobymao/sqlglot/pull/4224) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4222](https://github.com/tobymao/sqlglot/issues/4222) opened by [@s1101010110](https://github.com/s1101010110)*\n- [`fd42b5c`](https://github.com/tobymao/sqlglot/commit/fd42b5cdaf9421abb11e71d82726536af09e3ae3) - Simplify PARSENAME <-> SPLIT_PART transpilation *(PR [#4225](https://github.com/tobymao/sqlglot/pull/4225) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`4da2502`](https://github.com/tobymao/sqlglot/commit/4da25029b1c6f1425b4602f42da4fa1bcd3fccdb) - make Explode a UDTF subclass *(PR [#4242](https://github.com/tobymao/sqlglot/pull/4242) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.24.5] - 2024-10-08\n### :sparkles: New Features\n- [`22a1684`](https://github.com/tobymao/sqlglot/commit/22a16848d80a2fa6d310f99d21f7d81f90eb9440) - **bigquery**: Native annotations for more math functions *(PR [#4212](https://github.com/tobymao/sqlglot/pull/4212) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`354cfff`](https://github.com/tobymao/sqlglot/commit/354cfff13ab30d01c6123fca74eed0669d238aa0) - add builder methods to exp.Update and add with_ arg to exp.update *(PR [#4217](https://github.com/tobymao/sqlglot/pull/4217) by [@brdbry](https://github.com/brdbry))*\n\n### :bug: Bug Fixes\n- [`2c513b7`](https://github.com/tobymao/sqlglot/commit/2c513b71c7d4b1ff5c7c4e12d6c38694210b1a12) - Attach CTE comments before commas *(PR [#4218](https://github.com/tobymao/sqlglot/pull/4218) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4216](https://github.com/tobymao/sqlglot/issues/4216) opened by [@ajfriend](https://github.com/ajfriend)*\n\n\n## [v25.24.4] - 2024-10-04\n### :bug: Bug Fixes\n- [`484df7d`](https://github.com/tobymao/sqlglot/commit/484df7d50df5cb314943e1810db18a7d7d5bb3eb) - tsql union with limit *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v25.24.3] - 2024-10-03\n### :sparkles: New Features\n- [`25b18d2`](https://github.com/tobymao/sqlglot/commit/25b18d28e5ad7b3687e2848ff92a0a1fc17b06fa) - **trino**: Support JSON_QUERY *(PR [#4206](https://github.com/tobymao/sqlglot/pull/4206) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4200](https://github.com/tobymao/sqlglot/issues/4200) opened by [@Harmuth94](https://github.com/Harmuth94)*\n- [`5781b45`](https://github.com/tobymao/sqlglot/commit/5781b455fa3ec495b65f3f3f4a959192389bd816) - **duckdb**: Add more Postgres operators *(PR [#4199](https://github.com/tobymao/sqlglot/pull/4199) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4189](https://github.com/tobymao/sqlglot/issues/4189) opened by [@rustyconover](https://github.com/rustyconover)*\n- [`89c0703`](https://github.com/tobymao/sqlglot/commit/89c07039da402fb2ad77e00edb4f09079ecbb41d) - **bigquery**: Native math function annotations *(PR [#4201](https://github.com/tobymao/sqlglot/pull/4201) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`977d9e5`](https://github.com/tobymao/sqlglot/commit/977d9e5a854b58b4469be1af6aa14a5bf5a4b8c6) - allow supplying dialect in diff, conditionally copy ASTs *(PR [#4208](https://github.com/tobymao/sqlglot/pull/4208) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4203](https://github.com/tobymao/sqlglot/issues/4203) opened by [@mkmoisen](https://github.com/mkmoisen)*\n\n### :bug: Bug Fixes\n- [`332c74b`](https://github.com/tobymao/sqlglot/commit/332c74b881487cd9ce711ca3bd065a8992872098) - attach comments to subquery predicates properly, fix comment case *(PR [#4207](https://github.com/tobymao/sqlglot/pull/4207) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4205](https://github.com/tobymao/sqlglot/issues/4205) opened by [@mkmoisen](https://github.com/mkmoisen)*\n- [`55da21d`](https://github.com/tobymao/sqlglot/commit/55da21dd043dfcbefa3653fe168eb9cae5dc5bf5) - Unexpected row deduplication using eliminate_full_outer_join *(PR [#4178](https://github.com/tobymao/sqlglot/pull/4178) by [@liaco](https://github.com/liaco))*\n\n\n## [v25.24.2] - 2024-10-02\n### :sparkles: New Features\n- [`c8b7c1e`](https://github.com/tobymao/sqlglot/commit/c8b7c1ef7c6070a51638af18833c649a77e735cb) - **optimizer**: Fixture file for function annotations *(PR [#4182](https://github.com/tobymao/sqlglot/pull/4182) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`0adbbf7`](https://github.com/tobymao/sqlglot/commit/0adbbf7ad8f16700adc48c6757c07768199860d9) - **duckdb**: Parse ** and ^ operators as POW *(PR [#4193](https://github.com/tobymao/sqlglot/pull/4193) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4186](https://github.com/tobymao/sqlglot/issues/4186) opened by [@rustyconover](https://github.com/rustyconover)*\n- [`4949906`](https://github.com/tobymao/sqlglot/commit/4949906e9dd0c3039a161e06ddb970f37067b88f) - **duckdb**: Parse ~~~ as GLOB *(PR [#4194](https://github.com/tobymao/sqlglot/pull/4194) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4187](https://github.com/tobymao/sqlglot/issues/4187) opened by [@rustyconover](https://github.com/rustyconover)*\n- [`6ba2bb0`](https://github.com/tobymao/sqlglot/commit/6ba2bb03f973c30788508768c3ba716aa94b0299) - **oracle**: Add support for BULK COLLECT INTO *(PR [#4181](https://github.com/tobymao/sqlglot/pull/4181) by [@mkmoisen](https://github.com/mkmoisen))*\n- [`0de59ce`](https://github.com/tobymao/sqlglot/commit/0de59cebe550b33ac34a92c1ded1d3f9b8f679c4) - mark `expressions` as unsupported in Into generator *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`475f7a3`](https://github.com/tobymao/sqlglot/commit/475f7a3c639c7b8c5f3af1b2e5fcce9174be39ec) - **redshift**: Add unsupported warnings for UNNEST *(PR [#4173](https://github.com/tobymao/sqlglot/pull/4173) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4169](https://github.com/tobymao/sqlglot/issues/4169) opened by [@bjabes](https://github.com/bjabes)*\n- [`d38e023`](https://github.com/tobymao/sqlglot/commit/d38e023966c32b208fe5ae9843bbd716e2181521) - **spark**: Offset TRY_ELEMENT_AT by one *(PR [#4183](https://github.com/tobymao/sqlglot/pull/4183) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`3d1c643`](https://github.com/tobymao/sqlglot/commit/3d1c6430791dcce05f1a71f17311e294d9fc9d3d) - rename SHA function to SHA1 for DuckDB *(PR [#4191](https://github.com/tobymao/sqlglot/pull/4191) by [@rustyconover](https://github.com/rustyconover))*\n- [`0388a51`](https://github.com/tobymao/sqlglot/commit/0388a519dba63636a9aac3e3272cdea0f0b8312d) - add support for UHUGEINT for duckdb *(PR [#4190](https://github.com/tobymao/sqlglot/pull/4190) by [@rustyconover](https://github.com/rustyconover))*\n  - :arrow_lower_right: *fixes issue [#4184](https://github.com/tobymao/sqlglot/issues/4184) opened by [@rustyconover](https://github.com/rustyconover)*\n- [`9eba00d`](https://github.com/tobymao/sqlglot/commit/9eba00dca517efe7df171b09ed916af3ea5e350d) - **duckdb**: Parse ~~ as LIKE *(PR [#4195](https://github.com/tobymao/sqlglot/pull/4195) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4188](https://github.com/tobymao/sqlglot/issues/4188) opened by [@rustyconover](https://github.com/rustyconover)*\n- [`6a65973`](https://github.com/tobymao/sqlglot/commit/6a659736f3a176e335c68fdd07d8265c3d0421dc) - expand UPDATABLE_EXPRESSION_TYPES to account for Identifier changes *(PR [#4197](https://github.com/tobymao/sqlglot/pull/4197) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4192](https://github.com/tobymao/sqlglot/issues/4192) opened by [@mkmoisen](https://github.com/mkmoisen)*\n- [`a6c28c6`](https://github.com/tobymao/sqlglot/commit/a6c28c63f4e44bb62ba8df30f1407c728eb215f2) - **sqlite**: generate StrPosition as INSTR *(PR [#4198](https://github.com/tobymao/sqlglot/pull/4198) by [@pruzko](https://github.com/pruzko))*\n  - :arrow_lower_right: *fixes issue [#4196](https://github.com/tobymao/sqlglot/issues/4196) opened by [@pruzko](https://github.com/pruzko)*\n- [`5a123a5`](https://github.com/tobymao/sqlglot/commit/5a123a54ecd033c0a104e33476b17d816a09caac) - **oracle**: retreat properly when parsing BULK COLLECT INTO *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`f935e42`](https://github.com/tobymao/sqlglot/commit/f935e42130724e032b294074f3b552f21e20bc57) - properly escape closing identifier delimiters *(PR [#4202](https://github.com/tobymao/sqlglot/pull/4202) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.24.1] - 2024-10-01\n### :sparkles: New Features\n- [`7af33a2`](https://github.com/tobymao/sqlglot/commit/7af33a2f74dd1300bcd45f1974b7fd28abe66b8e) - **spark**: Custom annotation for more string functions *(PR [#4156](https://github.com/tobymao/sqlglot/pull/4156) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`81df4e1`](https://github.com/tobymao/sqlglot/commit/81df4e104ff3d60e3c23d3ac321e719b1f0962c0) - **athena**: Case sensitivity in CTAS property names *(PR [#4171](https://github.com/tobymao/sqlglot/pull/4171) by [@erindru](https://github.com/erindru))*\n- [`0703152`](https://github.com/tobymao/sqlglot/commit/0703152a25afced183dc5efd5f62311a48545420) - **bigquery**: Do not generate null ordering on agg funcs *(PR [#4172](https://github.com/tobymao/sqlglot/pull/4172) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4170](https://github.com/tobymao/sqlglot/issues/4170) opened by [@yjabri](https://github.com/yjabri)*\n\n\n## [v25.24.0] - 2024-09-26\n### :boom: BREAKING CHANGES\n- due to [`3ab6dfb`](https://github.com/tobymao/sqlglot/commit/3ab6dfb486f18d036bfac6a90d5f81b0ce7a91ea) - Generalize COLUMNS(...) APPLY *(PR [#4161](https://github.com/tobymao/sqlglot/pull/4161) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Generalize COLUMNS(...) APPLY (#4161)\n\n\n### :sparkles: New Features\n- [`93cef30`](https://github.com/tobymao/sqlglot/commit/93cef30bc534a155bce06f35d441d20e5dd78cf6) - **postgres**: Support OVERLAY function *(PR [#4165](https://github.com/tobymao/sqlglot/pull/4165) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4159](https://github.com/tobymao/sqlglot/issues/4159) opened by [@s1101010110](https://github.com/s1101010110)*\n- [`0a5444d`](https://github.com/tobymao/sqlglot/commit/0a5444dc822b7c53c008bc946eb3b54ca2147f3c) - expose a flag to automatically exclude Keep diff nodes *(PR [#4168](https://github.com/tobymao/sqlglot/pull/4168) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`9c17264`](https://github.com/tobymao/sqlglot/commit/9c172643aa3f3f0ffcc2e62242b62ba9c6141925) - **hive**: Enclose exp.Split with \\E *(PR [#4163](https://github.com/tobymao/sqlglot/pull/4163) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4158](https://github.com/tobymao/sqlglot/issues/4158) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`3ab6dfb`](https://github.com/tobymao/sqlglot/commit/3ab6dfb486f18d036bfac6a90d5f81b0ce7a91ea) - **clickhouse**: Generalize COLUMNS(...) APPLY *(PR [#4161](https://github.com/tobymao/sqlglot/pull/4161) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4157](https://github.com/tobymao/sqlglot/issues/4157) opened by [@elchyn-cheliabiyeu](https://github.com/elchyn-cheliabiyeu)*\n\n### :recycle: Refactors\n- [`2540e50`](https://github.com/tobymao/sqlglot/commit/2540e50d2b0df12f940c68acc574e540d19546cf) - simplify check_deploy job *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`f6d3bdd`](https://github.com/tobymao/sqlglot/commit/f6d3bdd740d0fe128d4d5dd99833a6f71c890ed3) - update supported dialect count (21 -> 23) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.23.2] - 2024-09-25\n### :wrench: Chores\n- [`eca05d3`](https://github.com/tobymao/sqlglot/commit/eca05d3b08645d7a984ee65b438282b35cb41960) - tweak should_deploy_rs script to avoid marking CI as failed *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.23.1] - 2024-09-25\n### :wrench: Chores\n- [`349b8f8`](https://github.com/tobymao/sqlglot/commit/349b8f81ed69a3708e1afd15816b3b58e2bf8b3f) - fetch all history to allow workflow script to skip sqlglotrs deployments *(PR [#4162](https://github.com/tobymao/sqlglot/pull/4162) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.23.0] - 2024-09-25\n### :boom: BREAKING CHANGES\n- due to [`da51ea5`](https://github.com/tobymao/sqlglot/commit/da51ea5c8f405859d877a25176e8e48ef8b4b112) - refactor exp.Chr *(PR [#4081](https://github.com/tobymao/sqlglot/pull/4081) by [@georgesittas](https://github.com/georgesittas))*:\n\n  refactor exp.Chr (#4081)\n\n- due to [`9c527b5`](https://github.com/tobymao/sqlglot/commit/9c527b549cc56db9d8f44579397d9f9fe1440573) - treat Nullable as an arg instead of a DataType.TYPE *(PR [#4094](https://github.com/tobymao/sqlglot/pull/4094) by [@georgesittas](https://github.com/georgesittas))*:\n\n  treat Nullable as an arg instead of a DataType.TYPE (#4094)\n\n- due to [`ba015dc`](https://github.com/tobymao/sqlglot/commit/ba015dc1102a4fe0c35cbfe6e3d23dc24263c20f) - add `returning` to merge expression builder *(PR [#4125](https://github.com/tobymao/sqlglot/pull/4125) by [@max-muoto](https://github.com/max-muoto))*:\n\n  add `returning` to merge expression builder (#4125)\n\n- due to [`77a514d`](https://github.com/tobymao/sqlglot/commit/77a514dd7cfa9feb847c429411809092e5578bad) - Parse VALUES & query modifiers in wrapped FROM clause *(PR [#4135](https://github.com/tobymao/sqlglot/pull/4135) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Parse VALUES & query modifiers in wrapped FROM clause (#4135)\n\n\n### :sparkles: New Features\n- [`5771d8d`](https://github.com/tobymao/sqlglot/commit/5771d8da94aff104206f93482e7b248d725f1843) - add merge expression builder *(PR [#4084](https://github.com/tobymao/sqlglot/pull/4084) by [@max-muoto](https://github.com/max-muoto))*\n- [`1d52709`](https://github.com/tobymao/sqlglot/commit/1d5270915d14f3f92341d5057b88b58fff6c0d97) - **postgres**: Parse DO NOTHING and RETURNING in MERGE statement *(PR [#4087](https://github.com/tobymao/sqlglot/pull/4087) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4083](https://github.com/tobymao/sqlglot/issues/4083) opened by [@max-muoto](https://github.com/max-muoto)*\n- [`1615bad`](https://github.com/tobymao/sqlglot/commit/1615bad98eeddc8e67e8002c3b1fe93bd7c3b690) - Add support for UUID function *(PR [#4089](https://github.com/tobymao/sqlglot/pull/4089) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5733600`](https://github.com/tobymao/sqlglot/commit/57336006795d32e9253a9df4813d3029d1d32ef1) - **bigquery**: transpile UUID type to STRING *(PR [#4093](https://github.com/tobymao/sqlglot/pull/4093) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4091](https://github.com/tobymao/sqlglot/issues/4091) opened by [@gigatexal](https://github.com/gigatexal)*\n- [`75230f5`](https://github.com/tobymao/sqlglot/commit/75230f5970c240add1cff7349fc65fb67541fa34) - **bigquery**: add support for the MERGE ... THEN INSERT ROW syntax *(PR [#4096](https://github.com/tobymao/sqlglot/pull/4096) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4095](https://github.com/tobymao/sqlglot/issues/4095) opened by [@ericist](https://github.com/ericist)*\n- [`f8d4dc4`](https://github.com/tobymao/sqlglot/commit/f8d4dc4bab90cd369eef090c23b81160a7ae78fc) - **parser**: add support for ALTER INDEX closes [#4105](https://github.com/tobymao/sqlglot/pull/4105) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`28c6f27`](https://github.com/tobymao/sqlglot/commit/28c6f27291d57d85917c62b387b86a598ee3c1d6) - **duckdb**: Support *COLUMNS() function *(PR [#4106](https://github.com/tobymao/sqlglot/pull/4106) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4101](https://github.com/tobymao/sqlglot/issues/4101) opened by [@aersam](https://github.com/aersam)*\n- [`3cb0041`](https://github.com/tobymao/sqlglot/commit/3cb00417f45624f012e5ce8ababfe3250e813b80) - **snowflake**: Fix exp.Pivot FOR IN clause *(PR [#4109](https://github.com/tobymao/sqlglot/pull/4109) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4108](https://github.com/tobymao/sqlglot/issues/4108) opened by [@kharigardner](https://github.com/kharigardner)*\n- [`7ac21a0`](https://github.com/tobymao/sqlglot/commit/7ac21a07c73cdf156ae4dc6a848b9f781b265d16) - **athena**: Improve DDL query support *(PR [#4099](https://github.com/tobymao/sqlglot/pull/4099) by [@erindru](https://github.com/erindru))*\n- [`a34f8b6`](https://github.com/tobymao/sqlglot/commit/a34f8b6ff9b0aa8595214da75fe7cbfbc8285476) - **oracle**: support TRUNC without fmt argument fixes [#4116](https://github.com/tobymao/sqlglot/pull/4116) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bd8e050`](https://github.com/tobymao/sqlglot/commit/bd8e050e7df905082fd32e26c4ee0c6e2d36c897) - **clickhouse**: support ON CLUSTER clause in DELETE *(PR [#4119](https://github.com/tobymao/sqlglot/pull/4119) by [@treysp](https://github.com/treysp))*\n- [`1fac6a9`](https://github.com/tobymao/sqlglot/commit/1fac6a9f46e147a5583042d6f82deffd04cd58c9) - expose sqlglot.expressions.delete as a sqlglot module function *(PR [#4126](https://github.com/tobymao/sqlglot/pull/4126) by [@max-muoto](https://github.com/max-muoto))*\n- [`4506b3b`](https://github.com/tobymao/sqlglot/commit/4506b3b58fde8e8fe711df8fd0c9c245a98ca86b) - **duckdb**: add support for the UNION type *(PR [#4128](https://github.com/tobymao/sqlglot/pull/4128) by [@georgesittas](https://github.com/georgesittas))*\n- [`ba015dc`](https://github.com/tobymao/sqlglot/commit/ba015dc1102a4fe0c35cbfe6e3d23dc24263c20f) - add `returning` to merge expression builder *(PR [#4125](https://github.com/tobymao/sqlglot/pull/4125) by [@max-muoto](https://github.com/max-muoto))*\n- [`3ec96ab`](https://github.com/tobymao/sqlglot/commit/3ec96ab7318dc5fc07802d31c825a95db7f5b303) - **clickhouse**: Add support for APPLY query modifier *(PR [#4141](https://github.com/tobymao/sqlglot/pull/4141) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4139](https://github.com/tobymao/sqlglot/issues/4139) opened by [@elchyn-cheliabiyeu](https://github.com/elchyn-cheliabiyeu)*\n- [`04ddc54`](https://github.com/tobymao/sqlglot/commit/04ddc543159bc55e2cf8098cd96b2a5c881ebbc6) - **bigquery**: Support RANGE<T> type *(PR [#4148](https://github.com/tobymao/sqlglot/pull/4148) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4146](https://github.com/tobymao/sqlglot/issues/4146) opened by [@plaflamme](https://github.com/plaflamme)*\n- [`17533ee`](https://github.com/tobymao/sqlglot/commit/17533ee6361c30731a9a14666ac952b44982b69d) - Add support for GRANT DDL *(PR [#4138](https://github.com/tobymao/sqlglot/pull/4138) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`1a240ec`](https://github.com/tobymao/sqlglot/commit/1a240ec1cbdaf15abab8df642e189b89de239e84) - Add SUBSTR Support *(PR [#4153](https://github.com/tobymao/sqlglot/pull/4153) by [@mwc360](https://github.com/mwc360))*\n\n### :bug: Bug Fixes\n- [`da51ea5`](https://github.com/tobymao/sqlglot/commit/da51ea5c8f405859d877a25176e8e48ef8b4b112) - **parser**: refactor exp.Chr *(PR [#4081](https://github.com/tobymao/sqlglot/pull/4081) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4080](https://github.com/tobymao/sqlglot/issues/4080) opened by [@EugeneTorap](https://github.com/EugeneTorap)*\n- [`6294f9e`](https://github.com/tobymao/sqlglot/commit/6294f9e6d08111c6088f5ed9846e7a64f7724801) - **starrocks**: generate ARRAY_FILTER for exp.ArrayFilter *(PR [#4088](https://github.com/tobymao/sqlglot/pull/4088) by [@gauravsagar483](https://github.com/gauravsagar483))*\n- [`1e02c02`](https://github.com/tobymao/sqlglot/commit/1e02c0221ea8445ccb5537b0a77e120c0b2c108c) - **mysql**: convert VARCHAR without size to TEXT for DDLs *(PR [#4092](https://github.com/tobymao/sqlglot/pull/4092) by [@georgesittas](https://github.com/georgesittas))*\n- [`cb5bcff`](https://github.com/tobymao/sqlglot/commit/cb5bcfff1f96972e75681bb2411bca8b60a4bff1) - **clickhouse**: generate formatDateTime instead of DATE_FORMAT fixes [#4098](https://github.com/tobymao/sqlglot/pull/4098) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b10255e`](https://github.com/tobymao/sqlglot/commit/b10255eb8b6b73bf5084fdf6bffd5a7fa351b1ec) - **snowflake**: Manually escape single quotes in colon operator *(PR [#4104](https://github.com/tobymao/sqlglot/pull/4104) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4090](https://github.com/tobymao/sqlglot/issues/4090) opened by [@jussihe-rec](https://github.com/jussihe-rec)*\n- [`67a9ad8`](https://github.com/tobymao/sqlglot/commit/67a9ad89abfce84f78dd1a34caa9dc8143233609) - Move JSON path escape to generation *(PR [#4110](https://github.com/tobymao/sqlglot/pull/4110) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4090](https://github.com/tobymao/sqlglot/issues/4090) opened by [@jussihe-rec](https://github.com/jussihe-rec)*\n- [`06c76f7`](https://github.com/tobymao/sqlglot/commit/06c76f7471cdb679a7a7a35064204d94841fd929) - calling interval without unit *(commit by [@tobymao](https://github.com/tobymao))*\n- [`66c3295`](https://github.com/tobymao/sqlglot/commit/66c32958a9e46642077813adf90079098e41c87e) - **optimizer**: Enable USING expansion with multiple joins *(PR [#4113](https://github.com/tobymao/sqlglot/pull/4113) by [@dg-hellotwin](https://github.com/dg-hellotwin))*\n  - :arrow_lower_right: *fixes issue [#4112](https://github.com/tobymao/sqlglot/issues/4112) opened by [@dg-hellotwin](https://github.com/dg-hellotwin)*\n- [`21f5bcd`](https://github.com/tobymao/sqlglot/commit/21f5bcd13eb9c567c711cec5879c4d08a052b91c) - parse struct(...)[] type properly *(PR [#4123](https://github.com/tobymao/sqlglot/pull/4123) by [@georgesittas](https://github.com/georgesittas))*\n- [`22c456d`](https://github.com/tobymao/sqlglot/commit/22c456d032b457244b397598d4480ae22ad316bd) - Do not generate DISTINCT keyword in FILTER *(PR [#4130](https://github.com/tobymao/sqlglot/pull/4130) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4129](https://github.com/tobymao/sqlglot/issues/4129) opened by [@tekumara](https://github.com/tekumara)*\n- [`089b77e`](https://github.com/tobymao/sqlglot/commit/089b77ec7efcd6decbf9a7be500e73dc88ba4dec) - **athena**: DDL fixes *(PR [#4132](https://github.com/tobymao/sqlglot/pull/4132) by [@erindru](https://github.com/erindru))*\n- [`e6c9902`](https://github.com/tobymao/sqlglot/commit/e6c990225e2685c617dfd1594c83778036405f6b) - invalid regex *(commit by [@tobymao](https://github.com/tobymao))*\n- [`77a514d`](https://github.com/tobymao/sqlglot/commit/77a514dd7cfa9feb847c429411809092e5578bad) - **parser**: Parse VALUES & query modifiers in wrapped FROM clause *(PR [#4135](https://github.com/tobymao/sqlglot/pull/4135) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4133](https://github.com/tobymao/sqlglot/issues/4133) opened by [@danxmoran](https://github.com/danxmoran)*\n- [`8822d6c`](https://github.com/tobymao/sqlglot/commit/8822d6c1b3b62cfd76fd481db473bf8ea1c12b1a) - **parser**: handle brackets in column op json extract arrow parser *(PR [#4140](https://github.com/tobymao/sqlglot/pull/4140) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3151](https://github.com/TobikoData/sqlmesh/issues/3151) opened by [@markgraphene](https://github.com/markgraphene)*\n- [`be0a4a8`](https://github.com/tobymao/sqlglot/commit/be0a4a85d41bfb617360bd9a59aa9e631e4d6d6c) - **bigquery**: Consume dashed identifiers only if they're connected *(PR [#4144](https://github.com/tobymao/sqlglot/pull/4144) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`0444819`](https://github.com/tobymao/sqlglot/commit/044481926d4b008027a2c7fb20501514ef507811) - **optimizer**: don't reorder subquery predicates in simplify *(PR [#4147](https://github.com/tobymao/sqlglot/pull/4147) by [@georgesittas](https://github.com/georgesittas))*\n- [`89519bb`](https://github.com/tobymao/sqlglot/commit/89519bba99fc11f17e8e00bf8e3f6dde213e99be) - **clickhouse**: make ToTableProperty appear right after the DDL name *(PR [#4151](https://github.com/tobymao/sqlglot/pull/4151) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4150](https://github.com/tobymao/sqlglot/issues/4150) opened by [@tzinyama](https://github.com/tzinyama)*\n\n### :recycle: Refactors\n- [`9c527b5`](https://github.com/tobymao/sqlglot/commit/9c527b549cc56db9d8f44579397d9f9fe1440573) - treat Nullable as an arg instead of a DataType.TYPE *(PR [#4094](https://github.com/tobymao/sqlglot/pull/4094) by [@georgesittas](https://github.com/georgesittas))*\n- [`2961049`](https://github.com/tobymao/sqlglot/commit/296104950f2e679aea37810a48eb490e170518d3) - implement decorator to easily mark args as unsupported *(PR [#4111](https://github.com/tobymao/sqlglot/pull/4111) by [@georgesittas](https://github.com/georgesittas))*\n- [`7cf1d70`](https://github.com/tobymao/sqlglot/commit/7cf1d70e909ae319ff659e1455e6fcad1e8cf905) - **optimizer**: Optimize USING expansion *(PR [#4115](https://github.com/tobymao/sqlglot/pull/4115) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :wrench: Chores\n- [`75e6406`](https://github.com/tobymao/sqlglot/commit/75e640672eef0ddf752ee36dbc6f904f8e06510f) - **prql**: rewrite tests to use `validate_all()` *(PR [#4097](https://github.com/tobymao/sqlglot/pull/4097) by [@JJHCool](https://github.com/JJHCool))*\n- [`e1f6ae3`](https://github.com/tobymao/sqlglot/commit/e1f6ae393fa2857dbbb9a14b03cbe39910207233) - **prql**: use validate_all instead of validate_identity *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`2dc0b86`](https://github.com/tobymao/sqlglot/commit/2dc0b8620e16b3cdf85f6bb6bea10c2527497933) - **optimizer**: rename helper function in expand_using *(PR [#4117](https://github.com/tobymao/sqlglot/pull/4117) by [@georgesittas](https://github.com/georgesittas))*\n- [`fd8b8ba`](https://github.com/tobymao/sqlglot/commit/fd8b8ba7dedaee5d237b080db5c4f7e83ba079e9) - create ARRAY_TYPES set under DataType *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.22.0] - 2024-09-19\n### :boom: BREAKING CHANGES\n- due to [`ba015dc`](https://github.com/tobymao/sqlglot/commit/ba015dc1102a4fe0c35cbfe6e3d23dc24263c20f) - add `returning` to merge expression builder *(PR [#4125](https://github.com/tobymao/sqlglot/pull/4125) by [@max-muoto](https://github.com/max-muoto))*:\n\n  add `returning` to merge expression builder (#4125)\n\n\n### :sparkles: New Features\n- [`1fac6a9`](https://github.com/tobymao/sqlglot/commit/1fac6a9f46e147a5583042d6f82deffd04cd58c9) - expose sqlglot.expressions.delete as a sqlglot module function *(PR [#4126](https://github.com/tobymao/sqlglot/pull/4126) by [@max-muoto](https://github.com/max-muoto))*\n- [`4506b3b`](https://github.com/tobymao/sqlglot/commit/4506b3b58fde8e8fe711df8fd0c9c245a98ca86b) - **duckdb**: add support for the UNION type *(PR [#4128](https://github.com/tobymao/sqlglot/pull/4128) by [@georgesittas](https://github.com/georgesittas))*\n- [`ba015dc`](https://github.com/tobymao/sqlglot/commit/ba015dc1102a4fe0c35cbfe6e3d23dc24263c20f) - add `returning` to merge expression builder *(PR [#4125](https://github.com/tobymao/sqlglot/pull/4125) by [@max-muoto](https://github.com/max-muoto))*\n\n### :bug: Bug Fixes\n- [`22c456d`](https://github.com/tobymao/sqlglot/commit/22c456d032b457244b397598d4480ae22ad316bd) - Do not generate DISTINCT keyword in FILTER *(PR [#4130](https://github.com/tobymao/sqlglot/pull/4130) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4129](https://github.com/tobymao/sqlglot/issues/4129) opened by [@tekumara](https://github.com/tekumara)*\n- [`089b77e`](https://github.com/tobymao/sqlglot/commit/089b77ec7efcd6decbf9a7be500e73dc88ba4dec) - **athena**: DDL fixes *(PR [#4132](https://github.com/tobymao/sqlglot/pull/4132) by [@erindru](https://github.com/erindru))*\n- [`e6c9902`](https://github.com/tobymao/sqlglot/commit/e6c990225e2685c617dfd1594c83778036405f6b) - invalid regex *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v25.21.3] - 2024-09-14\n### :sparkles: New Features\n- [`bd8e050`](https://github.com/tobymao/sqlglot/commit/bd8e050e7df905082fd32e26c4ee0c6e2d36c897) - **clickhouse**: support ON CLUSTER clause in DELETE *(PR [#4119](https://github.com/tobymao/sqlglot/pull/4119) by [@treysp](https://github.com/treysp))*\n\n### :bug: Bug Fixes\n- [`21f5bcd`](https://github.com/tobymao/sqlglot/commit/21f5bcd13eb9c567c711cec5879c4d08a052b91c) - parse struct(...)[] type properly *(PR [#4123](https://github.com/tobymao/sqlglot/pull/4123) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.21.2] - 2024-09-13\n### :sparkles: New Features\n- [`a34f8b6`](https://github.com/tobymao/sqlglot/commit/a34f8b6ff9b0aa8595214da75fe7cbfbc8285476) - **oracle**: support TRUNC without fmt argument fixes [#4116](https://github.com/tobymao/sqlglot/pull/4116) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`66c3295`](https://github.com/tobymao/sqlglot/commit/66c32958a9e46642077813adf90079098e41c87e) - **optimizer**: Enable USING expansion with multiple joins *(PR [#4113](https://github.com/tobymao/sqlglot/pull/4113) by [@dg-hellotwin](https://github.com/dg-hellotwin))*\n  - :arrow_lower_right: *fixes issue [#4112](https://github.com/tobymao/sqlglot/issues/4112) opened by [@dg-hellotwin](https://github.com/dg-hellotwin)*\n\n### :recycle: Refactors\n- [`7cf1d70`](https://github.com/tobymao/sqlglot/commit/7cf1d70e909ae319ff659e1455e6fcad1e8cf905) - **optimizer**: Optimize USING expansion *(PR [#4115](https://github.com/tobymao/sqlglot/pull/4115) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :wrench: Chores\n- [`2dc0b86`](https://github.com/tobymao/sqlglot/commit/2dc0b8620e16b3cdf85f6bb6bea10c2527497933) - **optimizer**: rename helper function in expand_using *(PR [#4117](https://github.com/tobymao/sqlglot/pull/4117) by [@georgesittas](https://github.com/georgesittas))*\n- [`fd8b8ba`](https://github.com/tobymao/sqlglot/commit/fd8b8ba7dedaee5d237b080db5c4f7e83ba079e9) - create ARRAY_TYPES set under DataType *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.21.1] - 2024-09-13\n### :bug: Bug Fixes\n- [`06c76f7`](https://github.com/tobymao/sqlglot/commit/06c76f7471cdb679a7a7a35064204d94841fd929) - calling interval without unit *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v25.21.0] - 2024-09-12\n### :boom: BREAKING CHANGES\n- due to [`da51ea5`](https://github.com/tobymao/sqlglot/commit/da51ea5c8f405859d877a25176e8e48ef8b4b112) - refactor exp.Chr *(PR [#4081](https://github.com/tobymao/sqlglot/pull/4081) by [@georgesittas](https://github.com/georgesittas))*:\n\n  refactor exp.Chr (#4081)\n\n- due to [`9c527b5`](https://github.com/tobymao/sqlglot/commit/9c527b549cc56db9d8f44579397d9f9fe1440573) - treat Nullable as an arg instead of a DataType.TYPE *(PR [#4094](https://github.com/tobymao/sqlglot/pull/4094) by [@georgesittas](https://github.com/georgesittas))*:\n\n  treat Nullable as an arg instead of a DataType.TYPE (#4094)\n\n\n### :sparkles: New Features\n- [`5771d8d`](https://github.com/tobymao/sqlglot/commit/5771d8da94aff104206f93482e7b248d725f1843) - add merge expression builder *(PR [#4084](https://github.com/tobymao/sqlglot/pull/4084) by [@max-muoto](https://github.com/max-muoto))*\n- [`1d52709`](https://github.com/tobymao/sqlglot/commit/1d5270915d14f3f92341d5057b88b58fff6c0d97) - **postgres**: Parse DO NOTHING and RETURNING in MERGE statement *(PR [#4087](https://github.com/tobymao/sqlglot/pull/4087) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4083](https://github.com/tobymao/sqlglot/issues/4083) opened by [@max-muoto](https://github.com/max-muoto)*\n- [`1615bad`](https://github.com/tobymao/sqlglot/commit/1615bad98eeddc8e67e8002c3b1fe93bd7c3b690) - Add support for UUID function *(PR [#4089](https://github.com/tobymao/sqlglot/pull/4089) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5733600`](https://github.com/tobymao/sqlglot/commit/57336006795d32e9253a9df4813d3029d1d32ef1) - **bigquery**: transpile UUID type to STRING *(PR [#4093](https://github.com/tobymao/sqlglot/pull/4093) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4091](https://github.com/tobymao/sqlglot/issues/4091) opened by [@gigatexal](https://github.com/gigatexal)*\n- [`75230f5`](https://github.com/tobymao/sqlglot/commit/75230f5970c240add1cff7349fc65fb67541fa34) - **bigquery**: add support for the MERGE ... THEN INSERT ROW syntax *(PR [#4096](https://github.com/tobymao/sqlglot/pull/4096) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4095](https://github.com/tobymao/sqlglot/issues/4095) opened by [@ericist](https://github.com/ericist)*\n- [`f8d4dc4`](https://github.com/tobymao/sqlglot/commit/f8d4dc4bab90cd369eef090c23b81160a7ae78fc) - **parser**: add support for ALTER INDEX closes [#4105](https://github.com/tobymao/sqlglot/pull/4105) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`28c6f27`](https://github.com/tobymao/sqlglot/commit/28c6f27291d57d85917c62b387b86a598ee3c1d6) - **duckdb**: Support *COLUMNS() function *(PR [#4106](https://github.com/tobymao/sqlglot/pull/4106) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4101](https://github.com/tobymao/sqlglot/issues/4101) opened by [@aersam](https://github.com/aersam)*\n- [`3cb0041`](https://github.com/tobymao/sqlglot/commit/3cb00417f45624f012e5ce8ababfe3250e813b80) - **snowflake**: Fix exp.Pivot FOR IN clause *(PR [#4109](https://github.com/tobymao/sqlglot/pull/4109) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4108](https://github.com/tobymao/sqlglot/issues/4108) opened by [@kharigardner](https://github.com/kharigardner)*\n- [`7ac21a0`](https://github.com/tobymao/sqlglot/commit/7ac21a07c73cdf156ae4dc6a848b9f781b265d16) - **athena**: Improve DDL query support *(PR [#4099](https://github.com/tobymao/sqlglot/pull/4099) by [@erindru](https://github.com/erindru))*\n\n### :bug: Bug Fixes\n- [`da51ea5`](https://github.com/tobymao/sqlglot/commit/da51ea5c8f405859d877a25176e8e48ef8b4b112) - **parser**: refactor exp.Chr *(PR [#4081](https://github.com/tobymao/sqlglot/pull/4081) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4080](https://github.com/tobymao/sqlglot/issues/4080) opened by [@EugeneTorap](https://github.com/EugeneTorap)*\n- [`6294f9e`](https://github.com/tobymao/sqlglot/commit/6294f9e6d08111c6088f5ed9846e7a64f7724801) - **starrocks**: generate ARRAY_FILTER for exp.ArrayFilter *(PR [#4088](https://github.com/tobymao/sqlglot/pull/4088) by [@gauravsagar483](https://github.com/gauravsagar483))*\n- [`1e02c02`](https://github.com/tobymao/sqlglot/commit/1e02c0221ea8445ccb5537b0a77e120c0b2c108c) - **mysql**: convert VARCHAR without size to TEXT for DDLs *(PR [#4092](https://github.com/tobymao/sqlglot/pull/4092) by [@georgesittas](https://github.com/georgesittas))*\n- [`cb5bcff`](https://github.com/tobymao/sqlglot/commit/cb5bcfff1f96972e75681bb2411bca8b60a4bff1) - **clickhouse**: generate formatDateTime instead of DATE_FORMAT fixes [#4098](https://github.com/tobymao/sqlglot/pull/4098) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b10255e`](https://github.com/tobymao/sqlglot/commit/b10255eb8b6b73bf5084fdf6bffd5a7fa351b1ec) - **snowflake**: Manually escape single quotes in colon operator *(PR [#4104](https://github.com/tobymao/sqlglot/pull/4104) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4090](https://github.com/tobymao/sqlglot/issues/4090) opened by [@jussihe-rec](https://github.com/jussihe-rec)*\n- [`67a9ad8`](https://github.com/tobymao/sqlglot/commit/67a9ad89abfce84f78dd1a34caa9dc8143233609) - Move JSON path escape to generation *(PR [#4110](https://github.com/tobymao/sqlglot/pull/4110) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4090](https://github.com/tobymao/sqlglot/issues/4090) opened by [@jussihe-rec](https://github.com/jussihe-rec)*\n\n### :recycle: Refactors\n- [`9c527b5`](https://github.com/tobymao/sqlglot/commit/9c527b549cc56db9d8f44579397d9f9fe1440573) - treat Nullable as an arg instead of a DataType.TYPE *(PR [#4094](https://github.com/tobymao/sqlglot/pull/4094) by [@georgesittas](https://github.com/georgesittas))*\n- [`2961049`](https://github.com/tobymao/sqlglot/commit/296104950f2e679aea37810a48eb490e170518d3) - implement decorator to easily mark args as unsupported *(PR [#4111](https://github.com/tobymao/sqlglot/pull/4111) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`75e6406`](https://github.com/tobymao/sqlglot/commit/75e640672eef0ddf752ee36dbc6f904f8e06510f) - **prql**: rewrite tests to use `validate_all()` *(PR [#4097](https://github.com/tobymao/sqlglot/pull/4097) by [@JJHCool](https://github.com/JJHCool))*\n- [`e1f6ae3`](https://github.com/tobymao/sqlglot/commit/e1f6ae393fa2857dbbb9a14b03cbe39910207233) - **prql**: use validate_all instead of validate_identity *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.20.1] - 2024-09-06\n### :wrench: Chores\n- [`8a357cc`](https://github.com/tobymao/sqlglot/commit/8a357ccbcf2301f6a8d60c237a6397bf6547de14) - bump sqlglotrs to 0.2.12 -- remove relative readme path *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.20.0] - 2024-09-06\n### :boom: BREAKING CHANGES\n- due to [`3e1af21`](https://github.com/tobymao/sqlglot/commit/3e1af21787ee81df5cbb5eb8b4b7f808b404c870) - Canonicalize exp.RegexpExtract group default value *(PR [#4051](https://github.com/tobymao/sqlglot/pull/4051) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Canonicalize exp.RegexpExtract group default value (#4051)\n\n- due to [`c8e2eae`](https://github.com/tobymao/sqlglot/commit/c8e2eaecad0b3b0fff725512ef571de41c5be0a1) - do not canonicalize INTERVAL values to number literals *(commit by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  do not canonicalize INTERVAL values to number literals\n\n\n### :sparkles: New Features\n- [`d3ee5ea`](https://github.com/tobymao/sqlglot/commit/d3ee5ea6abd0dfb6e5216bf212e9e737c163eeb9) - **oracle**: parse TRUNC to facilitate transpilation closes [#4054](https://github.com/tobymao/sqlglot/pull/4054) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`143f176`](https://github.com/tobymao/sqlglot/commit/143f176a893f060eecc1fbf4a5b5c54d35a3acc7) - **clickhouse**: transpile oracle functions chr, lag, lead *(PR [#4053](https://github.com/tobymao/sqlglot/pull/4053) by [@sleshJdev](https://github.com/sleshJdev))*\n- [`d89757e`](https://github.com/tobymao/sqlglot/commit/d89757e21665913d49a3ccc19deeea86ab59820c) - **postgres**: add support for the NOT VALID clause in ALTER TABLE fixes [#4077](https://github.com/tobymao/sqlglot/pull/4077) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`3e1af21`](https://github.com/tobymao/sqlglot/commit/3e1af21787ee81df5cbb5eb8b4b7f808b404c870) - Canonicalize exp.RegexpExtract group default value *(PR [#4051](https://github.com/tobymao/sqlglot/pull/4051) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4039](https://github.com/tobymao/sqlglot/issues/4039) opened by [@hellozepp](https://github.com/hellozepp)*\n- [`75cafad`](https://github.com/tobymao/sqlglot/commit/75cafad45fec02eb27f42676ddca4d1777e800f7) - **starrocks**: Move the parse distribute and duplicate to Parser *(PR [#4062](https://github.com/tobymao/sqlglot/pull/4062) by [@hellozepp](https://github.com/hellozepp))*\n- [`74352d5`](https://github.com/tobymao/sqlglot/commit/74352d523333e5eff464f97b49a1bcfb11ec291b) - **tsql**: use plus operator for string concat to support more systems that use tsql *(PR [#4067](https://github.com/tobymao/sqlglot/pull/4067) by [@cpcloud](https://github.com/cpcloud))*\n  - :arrow_lower_right: *fixes issue [#4066](https://github.com/tobymao/sqlglot/issues/4066) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`c8e2eae`](https://github.com/tobymao/sqlglot/commit/c8e2eaecad0b3b0fff725512ef571de41c5be0a1) - do not canonicalize INTERVAL values to number literals *(commit by [@VaggelisD](https://github.com/VaggelisD))*\n- [`532a024`](https://github.com/tobymao/sqlglot/commit/532a024e1a1dbc422e603dc0336149362c5763df) - **snowflake, bigquery**: Remove exp.Trim generation *(PR [#4070](https://github.com/tobymao/sqlglot/pull/4070) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3095](https://github.com/TobikoData/sqlmesh/issues/3095) opened by [@plaflamme](https://github.com/plaflamme)*\n- [`ad7e582`](https://github.com/tobymao/sqlglot/commit/ad7e582504955c5dba84566994e81873a46d1c28) - **athena**: Apply correct quoting to queries depending on type (DML or DDL) *(PR [#4073](https://github.com/tobymao/sqlglot/pull/4073) by [@erindru](https://github.com/erindru))*\n- [`cc5b877`](https://github.com/tobymao/sqlglot/commit/cc5b8774c469250fd403ca3379f1c2dcab9d4017) - **parser**: Wrap column constraints in _parse_column_def() *(PR [#4078](https://github.com/tobymao/sqlglot/pull/4078) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4065](https://github.com/tobymao/sqlglot/issues/4065) opened by [@ajuszczak](https://github.com/ajuszczak)*\n- [`4eb384a`](https://github.com/tobymao/sqlglot/commit/4eb384a799b3ad0f152893eb6217131a3a698ff1) - **clickhouse**: Remove CURRENT_TIMESTAMP from NO_PAREN_FUNCTIONS *(PR [#4079](https://github.com/tobymao/sqlglot/pull/4079) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4076](https://github.com/tobymao/sqlglot/issues/4076) opened by [@hellozepp](https://github.com/hellozepp)*\n\n### :recycle: Refactors\n- [`534f882`](https://github.com/tobymao/sqlglot/commit/534f88280a895d9f7503e48eedf600628d34aa82) - **clickhouse**: clean up chr, lag, lead generation *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`b986ebe`](https://github.com/tobymao/sqlglot/commit/b986ebe99c95ce6f76a76bfd0ea79ee4ac3757f0) - fill in more details for sqlglotrs pypi page *(PR [#4071](https://github.com/tobymao/sqlglot/pull/4071) by [@georgesittas](https://github.com/georgesittas))*\n- [`0310926`](https://github.com/tobymao/sqlglot/commit/0310926297b18714a02873b649061b50c7080ac9) - bump sqlglotrs to 0.2.11 (update pypi details) *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.19.0] - 2024-09-03\n### :boom: BREAKING CHANGES\n- due to [`6da9328`](https://github.com/tobymao/sqlglot/commit/6da932889c2d60c82be118842f4edee031009f8a) - refactor SET OPERATION handling to set correct defaults *(PR [#4009](https://github.com/tobymao/sqlglot/pull/4009) by [@georgesittas](https://github.com/georgesittas))*:\n\n  refactor SET OPERATION handling to set correct defaults (#4009)\n\n- due to [`4b69d18`](https://github.com/tobymao/sqlglot/commit/4b69d18e8e23c9ba2b0a886be497df9c1071f26c) - use TO_GEOGRAPHY, TO_GEOMETRY instead of casts *(PR [#4017](https://github.com/tobymao/sqlglot/pull/4017) by [@georgesittas](https://github.com/georgesittas))*:\n\n  use TO_GEOGRAPHY, TO_GEOMETRY instead of casts (#4017)\n\n- due to [`0985907`](https://github.com/tobymao/sqlglot/commit/098590718104b9a6e9c0340fbe05fd89759c142b) - Add UnsupportedError to unnest_to_explode transform *(PR [#4016](https://github.com/tobymao/sqlglot/pull/4016) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add UnsupportedError to unnest_to_explode transform (#4016)\n\n- due to [`7d63d23`](https://github.com/tobymao/sqlglot/commit/7d63d235c1f8ce7a76db3d31f11050dc65c0fef1) - Support JSON_EXISTS, refactor ON handling *(PR [#4032](https://github.com/tobymao/sqlglot/pull/4032) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Support JSON_EXISTS, refactor ON handling (#4032)\n\n\n### :sparkles: New Features\n- [`f550ba1`](https://github.com/tobymao/sqlglot/commit/f550ba1068eaa4be45c19b4a3ea11baad48b27c1) - **presto**: support [ SECURITY { DEFINER | INVOKER } ] *(PR [#4008](https://github.com/tobymao/sqlglot/pull/4008) by [@usmanovbf](https://github.com/usmanovbf))*\n- [`dedd757`](https://github.com/tobymao/sqlglot/commit/dedd75790ecc2549fa7b28b3612125ca2aaeb762) - **oracle**: Parse multitable inserts *(PR [#4000](https://github.com/tobymao/sqlglot/pull/4000) by [@usefulalgorithm](https://github.com/usefulalgorithm))*\n- [`0985907`](https://github.com/tobymao/sqlglot/commit/098590718104b9a6e9c0340fbe05fd89759c142b) - Add UnsupportedError to unnest_to_explode transform *(PR [#4016](https://github.com/tobymao/sqlglot/pull/4016) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`8f5fccf`](https://github.com/tobymao/sqlglot/commit/8f5fccfca8502e0fe00420662825845cc640a1cb) - **presto**: generate non-iso DayOfWeek *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`7d63d23`](https://github.com/tobymao/sqlglot/commit/7d63d235c1f8ce7a76db3d31f11050dc65c0fef1) - **oracle**: Support JSON_EXISTS, refactor ON handling *(PR [#4032](https://github.com/tobymao/sqlglot/pull/4032) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4026](https://github.com/tobymao/sqlglot/issues/4026) opened by [@ashishshukla19](https://github.com/ashishshukla19)*\n- [`85cc7ad`](https://github.com/tobymao/sqlglot/commit/85cc7ad68599dde59ffab460d49010f167cab85d) - **duckdb**: Transpile BQ's exp.ArrayToString *(PR [#4034](https://github.com/tobymao/sqlglot/pull/4034) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`7f2c7f1`](https://github.com/tobymao/sqlglot/commit/7f2c7f17f5d79c3ea93b43cdeacdb5339955c9a8) - Support for NORMALIZE() function *(PR [#4041](https://github.com/tobymao/sqlglot/pull/4041) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#4037](https://github.com/tobymao/sqlglot/issues/4037) opened by [@jasonrosendale](https://github.com/jasonrosendale)*\n- [`a1b9803`](https://github.com/tobymao/sqlglot/commit/a1b980327ff94519a4cba1e0e48066c0ea51d359) - support COMPRESS column constraint wihout a value *(PR [#4045](https://github.com/tobymao/sqlglot/pull/4045) by [@thomascjohnson](https://github.com/thomascjohnson))*\n\n### :bug: Bug Fixes\n- [`8583772`](https://github.com/tobymao/sqlglot/commit/85837729e746743755294727d0394534834f4c4c) - **tsql**: Use count_big instead of count *(PR [#3996](https://github.com/tobymao/sqlglot/pull/3996) by [@colin-ho](https://github.com/colin-ho))*\n  - :arrow_lower_right: *fixes issue [#3995](https://github.com/tobymao/sqlglot/issues/3995) opened by [@colin-ho](https://github.com/colin-ho)*\n- [`4b7ca2b`](https://github.com/tobymao/sqlglot/commit/4b7ca2be353e7432b84384ff9cfd43f3c43438e0) - **spark**: Custom annotation for SUBSTRING() *(PR [#4004](https://github.com/tobymao/sqlglot/pull/4004) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4002](https://github.com/tobymao/sqlglot/issues/4002) opened by [@racevedoo](https://github.com/racevedoo)*\n- [`cb172db`](https://github.com/tobymao/sqlglot/commit/cb172dbe4d13fd3badad352ea79d2fd6e5271576) - **clickhouse**: ensure that ALL and DISTINCT are rendered for except and intersect *(PR [#4007](https://github.com/tobymao/sqlglot/pull/4007) by [@cpcloud](https://github.com/cpcloud))*\n  - :arrow_lower_right: *fixes issue [#4005](https://github.com/tobymao/sqlglot/issues/4005) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`829fdcb`](https://github.com/tobymao/sqlglot/commit/829fdcb1dbe52710269823bda93e3e49c02dbf63) - **starrocks**: exp.Unnest transpilation *(PR [#3999](https://github.com/tobymao/sqlglot/pull/3999) by [@hellozepp](https://github.com/hellozepp))*\n  - :arrow_lower_right: *fixes issue [#3962](https://github.com/tobymao/sqlglot/issues/3962) opened by [@hellozepp](https://github.com/hellozepp)*\n- [`6da9328`](https://github.com/tobymao/sqlglot/commit/6da932889c2d60c82be118842f4edee031009f8a) - refactor SET OPERATION handling to set correct defaults *(PR [#4009](https://github.com/tobymao/sqlglot/pull/4009) by [@georgesittas](https://github.com/georgesittas))*\n- [`23a928e`](https://github.com/tobymao/sqlglot/commit/23a928edc1d204a13516e0db38336774962a135e) - **mysql**: Preserve roundtrip of %a, %W time formats *(PR [#4014](https://github.com/tobymao/sqlglot/pull/4014) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#4011](https://github.com/tobymao/sqlglot/issues/4011) opened by [@hellozepp](https://github.com/hellozepp)*\n- [`2d4483c`](https://github.com/tobymao/sqlglot/commit/2d4483c0f79c5c72438a7093c938b1f178e5d48a) - don't log warning in to_json_path conditionally *(PR [#4015](https://github.com/tobymao/sqlglot/pull/4015) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4013](https://github.com/tobymao/sqlglot/issues/4013) opened by [@hellozepp](https://github.com/hellozepp)*\n- [`4b69d18`](https://github.com/tobymao/sqlglot/commit/4b69d18e8e23c9ba2b0a886be497df9c1071f26c) - **snowflake**: use TO_GEOGRAPHY, TO_GEOMETRY instead of casts *(PR [#4017](https://github.com/tobymao/sqlglot/pull/4017) by [@georgesittas](https://github.com/georgesittas))*\n- [`1108426`](https://github.com/tobymao/sqlglot/commit/1108426a0eb23bbcaec8bed946f1dae6682bc1dd) - **optimizer**: annotate unary expressions correctly *(PR [#4019](https://github.com/tobymao/sqlglot/pull/4019) by [@georgesittas](https://github.com/georgesittas))*\n- [`5fad18c`](https://github.com/tobymao/sqlglot/commit/5fad18c6cb5f62630cdfa2616231436586c41d67) - **presto**: exp.DayOfWeek *(PR [#4024](https://github.com/tobymao/sqlglot/pull/4024) by [@hellozepp](https://github.com/hellozepp))*\n- [`ea9a494`](https://github.com/tobymao/sqlglot/commit/ea9a4948a3e1619b885fc0b1522a7382d68c9cbe) - **parser**: consume STREAM in _parse_select only if it's a VAR, closes [#4029](https://github.com/tobymao/sqlglot/pull/4029) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`c239a74`](https://github.com/tobymao/sqlglot/commit/c239a741233bf858ce686d2d32a657cbedb49699) - transpile null exclusion for ARRAY_AGG *(PR [#4033](https://github.com/tobymao/sqlglot/pull/4033) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#4031](https://github.com/tobymao/sqlglot/issues/4031) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`60a8f16`](https://github.com/tobymao/sqlglot/commit/60a8f16b5386fe334b6e15afa967ad7bdd2a83de) - **parser**: don't consume strings in match_text_seq *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`a726583`](https://github.com/tobymao/sqlglot/commit/a726583995716f815946b4c81c07a916ade727b7) - **parser**: don't consume strings in match_texts *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`551d32f`](https://github.com/tobymao/sqlglot/commit/551d32fdebfc78506b77e4f6e6882d2a8cbd457c) - **postgres**: Support for DROP INDEX CONCURRENTLY. *(PR [#4040](https://github.com/tobymao/sqlglot/pull/4040) by [@EdgyEdgemond](https://github.com/EdgyEdgemond))*\n  - :arrow_lower_right: *fixes issue [#3783](https://github.com/tobymao/sqlglot/issues/3783) opened by [@EdgyEdgemond](https://github.com/EdgyEdgemond)*\n- [`f55647d`](https://github.com/tobymao/sqlglot/commit/f55647d9d9c088880c0c16efff23ef8d22c2be44) - **starrocks**: exp.Create transpilation *(PR [#4023](https://github.com/tobymao/sqlglot/pull/4023) by [@hellozepp](https://github.com/hellozepp))*\n  - :arrow_lower_right: *fixes issue [#3997](https://github.com/tobymao/sqlglot/issues/3997) opened by [@hellozepp](https://github.com/hellozepp)*\n- [`bf0f5fa`](https://github.com/tobymao/sqlglot/commit/bf0f5fa1ab44daa74102b0f16ae16f905b175fbc) - **parser**: Ensure exp.Coalesce expressions is a list *(PR [#4050](https://github.com/tobymao/sqlglot/pull/4050) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3080](https://github.com/TobikoData/sqlmesh/issues/3080) opened by [@Ziemin](https://github.com/Ziemin)*\n\n### :recycle: Refactors\n- [`6494776`](https://github.com/tobymao/sqlglot/commit/6494776a45ae4975cee21f70b5f383d29530d155) - simplify multi-insert generation, fix pretty mode *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b11c73e`](https://github.com/tobymao/sqlglot/commit/b11c73e38aa495715c327f44586714e19f699c9c) - clean up starrocks DISTRIBUTED BY property generation *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`c35a62c`](https://github.com/tobymao/sqlglot/commit/c35a62cdbc29c796bf0728d3f26e5ae5474881a8) - set the license for sqlglotrs *(PR [#4048](https://github.com/tobymao/sqlglot/pull/4048) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#4047](https://github.com/tobymao/sqlglot/issues/4047) opened by [@chriscc2](https://github.com/chriscc2)*\n- [`9b7eb2e`](https://github.com/tobymao/sqlglot/commit/9b7eb2e40e4bec4d18664f09e01c1165122dd43f) - bump sqlglotrs to v0.2.10 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.18.0] - 2024-08-28\n### :boom: BREAKING CHANGES\n- due to [`22bb9a0`](https://github.com/tobymao/sqlglot/commit/22bb9a0e5c64ae344c9e25ed34200ed743e7b8f0) - stop normalizing qualified anonymous functions *(PR [#3969](https://github.com/tobymao/sqlglot/pull/3969) by [@georgesittas](https://github.com/georgesittas))*:\n\n  stop normalizing qualified anonymous functions (#3969)\n\n- due to [`8aec682`](https://github.com/tobymao/sqlglot/commit/8aec68253b10dcbfe7cc5b3d6e1145ae714ca346) - mysql/tsql datetime precision, formatting, exp.AtTimeZone *(PR [#3951](https://github.com/tobymao/sqlglot/pull/3951) by [@erindru](https://github.com/erindru))*:\n\n  mysql/tsql datetime precision, formatting, exp.AtTimeZone (#3951)\n\n- due to [`2f3626a`](https://github.com/tobymao/sqlglot/commit/2f3626a4fc20c46411cd91bf8beda2bdd103ca4a) - Generation of exp.SHA2, exp.Transform, exp.IgnoreNulls *(PR [#3980](https://github.com/tobymao/sqlglot/pull/3980) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Generation of exp.SHA2, exp.Transform, exp.IgnoreNulls (#3980)\n\n- due to [`905b722`](https://github.com/tobymao/sqlglot/commit/905b7226ae4a6dc505fe303bb4df3818cb586826) - preserve each distinct CUBE/ROLLUP/GROUPING SET clause *(PR [#3985](https://github.com/tobymao/sqlglot/pull/3985) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve each distinct CUBE/ROLLUP/GROUPING SET clause (#3985)\n\n\n### :sparkles: New Features\n- [`48b214d`](https://github.com/tobymao/sqlglot/commit/48b214da7e39d36938d12059deb827d0a5f6a5a2) - **postgres**: Support for IS JSON predicate *(PR [#3971](https://github.com/tobymao/sqlglot/pull/3971) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3965](https://github.com/tobymao/sqlglot/issues/3965) opened by [@faisal-ksolves](https://github.com/faisal-ksolves)*\n- [`f7e4e4a`](https://github.com/tobymao/sqlglot/commit/f7e4e4adc64aaef73d23c2550a4bfa9958d4851b) - **duckdb**: add support for the GLOB table function closes [#3973](https://github.com/tobymao/sqlglot/pull/3973) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`a0d1377`](https://github.com/tobymao/sqlglot/commit/a0d137787885627aae07f11a9c18a4cc133baa0a) - **spark**: add support for table statement in INSERT *(PR [#3986](https://github.com/tobymao/sqlglot/pull/3986) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3984](https://github.com/tobymao/sqlglot/issues/3984) opened by [@madeirak](https://github.com/madeirak)*\n- [`f5bfd67`](https://github.com/tobymao/sqlglot/commit/f5bfd67341518d0ecb1c3693e0b41ed5c1cf0596) - **mysql**: Parse JSON_VALUE() *(PR [#3987](https://github.com/tobymao/sqlglot/pull/3987) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3983](https://github.com/tobymao/sqlglot/issues/3983) opened by [@ashishshukla19](https://github.com/ashishshukla19)*\n- [`79e92ad`](https://github.com/tobymao/sqlglot/commit/79e92ad565c42098ff7b7921fe04e6aac7859dd8) - **spark**: Default naming of STRUCT fields *(PR [#3991](https://github.com/tobymao/sqlglot/pull/3991) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3988](https://github.com/tobymao/sqlglot/issues/3988) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n\n### :bug: Bug Fixes\n- [`22bb9a0`](https://github.com/tobymao/sqlglot/commit/22bb9a0e5c64ae344c9e25ed34200ed743e7b8f0) - stop normalizing qualified anonymous functions *(PR [#3969](https://github.com/tobymao/sqlglot/pull/3969) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3967](https://github.com/tobymao/sqlglot/issues/3967) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`8aec682`](https://github.com/tobymao/sqlglot/commit/8aec68253b10dcbfe7cc5b3d6e1145ae714ca346) - mysql/tsql datetime precision, formatting, exp.AtTimeZone *(PR [#3951](https://github.com/tobymao/sqlglot/pull/3951) by [@erindru](https://github.com/erindru))*\n- [`d37a5bb`](https://github.com/tobymao/sqlglot/commit/d37a5bbfcd5732aa64a24bd83dde4abcac8b0bed) - **snowflake**: handle DIV0 case where divident is null *(PR [#3975](https://github.com/tobymao/sqlglot/pull/3975) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3974](https://github.com/tobymao/sqlglot/issues/3974) opened by [@Nathan-Fenner](https://github.com/Nathan-Fenner)*\n- [`b2f877b`](https://github.com/tobymao/sqlglot/commit/b2f877ba5fc9ec9fdafad74196dda1631fdfc0c1) - **oracle**: Use LTRIM/RTRIM unless BOTH is specified *(PR [#3977](https://github.com/tobymao/sqlglot/pull/3977) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`201b51a`](https://github.com/tobymao/sqlglot/commit/201b51a860d4db2b2e49e04f6534b7ad22ae287c) - **sqlite**: Make IS parser more lenient *(PR [#3981](https://github.com/tobymao/sqlglot/pull/3981) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3978](https://github.com/tobymao/sqlglot/issues/3978) opened by [@focafull](https://github.com/focafull)*\n- [`2f3626a`](https://github.com/tobymao/sqlglot/commit/2f3626a4fc20c46411cd91bf8beda2bdd103ca4a) - **duckdb**: Generation of exp.SHA2, exp.Transform, exp.IgnoreNulls *(PR [#3980](https://github.com/tobymao/sqlglot/pull/3980) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3972](https://github.com/tobymao/sqlglot/issues/3972) opened by [@dor-bernstein](https://github.com/dor-bernstein)*\n- [`905b722`](https://github.com/tobymao/sqlglot/commit/905b7226ae4a6dc505fe303bb4df3818cb586826) - **parser**: preserve each distinct CUBE/ROLLUP/GROUPING SET clause *(PR [#3985](https://github.com/tobymao/sqlglot/pull/3985) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3979](https://github.com/tobymao/sqlglot/issues/3979) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`ee9dc39`](https://github.com/tobymao/sqlglot/commit/ee9dc399134ad86720abe480ee2565de822336cf) - Fix binding of TABLESAMPLE to exp.Subquery instead of top-level exp.Select *(PR [#3994](https://github.com/tobymao/sqlglot/pull/3994) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3992](https://github.com/tobymao/sqlglot/issues/3992) opened by [@cpcloud](https://github.com/cpcloud)*\n\n\n## [v25.17.0] - 2024-08-26\n### :boom: BREAKING CHANGES\n- due to [`0a9ba05`](https://github.com/tobymao/sqlglot/commit/0a9ba0536235e10aed02d4ff5e571e435a00febc) - 0 is falsey *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  0 is falsey\n\n\n### :bug: Bug Fixes\n- [`42b725e`](https://github.com/tobymao/sqlglot/commit/42b725e4821a1426fe7c93f9fecbd4ec372accc9) - flaky test closes [#3961](https://github.com/tobymao/sqlglot/pull/3961) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`cc29921`](https://github.com/tobymao/sqlglot/commit/cc299217f5d31a0406ba3c4778bb1ce581fe3f4a) - Parse LTRIM/RTRIM functions as positional exp.Trim *(PR [#3958](https://github.com/tobymao/sqlglot/pull/3958) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3957](https://github.com/tobymao/sqlglot/issues/3957) opened by [@sleshJdev](https://github.com/sleshJdev)*\n- [`678e692`](https://github.com/tobymao/sqlglot/commit/678e6926fdbefb16efbbcaef9cd6c5ca284af54a) - make sample an arg of table, not a wrapper *(PR [#3963](https://github.com/tobymao/sqlglot/pull/3963) by [@barakalon](https://github.com/barakalon))*\n- [`0a9ba05`](https://github.com/tobymao/sqlglot/commit/0a9ba0536235e10aed02d4ff5e571e435a00febc) - 0 is falsey *(commit by [@tobymao](https://github.com/tobymao))*\n- [`c1ac987`](https://github.com/tobymao/sqlglot/commit/c1ac9872a6f77acd52546edbc9da53e350ebf080) - **starrocks**: exp.Array generation, exp.Unnest alias *(PR [#3964](https://github.com/tobymao/sqlglot/pull/3964) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3962](https://github.com/tobymao/sqlglot/issues/3962) opened by [@hellozepp](https://github.com/hellozepp)*\n\n\n## [v25.16.1] - 2024-08-23\n### :bug: Bug Fixes\n- [`c4e5be7`](https://github.com/tobymao/sqlglot/commit/c4e5be7d3f4d7a9075d11dc56ece02774f32e749) - include dialect when parsing inside cast *(PR [#3960](https://github.com/tobymao/sqlglot/pull/3960) by [@eakmanrq](https://github.com/eakmanrq))*\n\n### :wrench: Chores\n- [`794dc4c`](https://github.com/tobymao/sqlglot/commit/794dc4cea3c4298c8986ade8e0fee88479851b34) - update readme to include onboarding doc *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.16.0] - 2024-08-22\n### :boom: BREAKING CHANGES\n- due to [`f68d155`](https://github.com/tobymao/sqlglot/commit/f68d155c38a79a6527685c37f8de8773ce790bca) - exp.Merge, for Trino and Postgres, dont strip the target alias from then WHEN MATCHED condition to prevent an ambiguous column error *(PR [#3940](https://github.com/tobymao/sqlglot/pull/3940) by [@erindru](https://github.com/erindru))*:\n\n  exp.Merge, for Trino and Postgres, dont strip the target alias from then WHEN MATCHED condition to prevent an ambiguous column error (#3940)\n\n- due to [`667f7d9`](https://github.com/tobymao/sqlglot/commit/667f7d9e94e14ff619998d2001b6116d363f2a1f) - attach INTERPOLATE expressions to WithFill *(PR [#3944](https://github.com/tobymao/sqlglot/pull/3944) by [@georgesittas](https://github.com/georgesittas))*:\n\n  attach INTERPOLATE expressions to WithFill (#3944)\n\n- due to [`145fdbf`](https://github.com/tobymao/sqlglot/commit/145fdbf6bb02fa1c55087bfd9f6b3a15fbd4b684) - Redshift date format *(PR [#3942](https://github.com/tobymao/sqlglot/pull/3942) by [@erindru](https://github.com/erindru))*:\n\n  Redshift date format (#3942)\n\n- due to [`a84a21a`](https://github.com/tobymao/sqlglot/commit/a84a21aaef0e65754e67ecebdfcbf7136c77acc7) - Add timezone support to exp.TimeStrToTime *(PR [#3938](https://github.com/tobymao/sqlglot/pull/3938) by [@erindru](https://github.com/erindru))*:\n\n  Add timezone support to exp.TimeStrToTime (#3938)\n\n\n### :sparkles: New Features\n- [`a84a21a`](https://github.com/tobymao/sqlglot/commit/a84a21aaef0e65754e67ecebdfcbf7136c77acc7) - Add timezone support to exp.TimeStrToTime *(PR [#3938](https://github.com/tobymao/sqlglot/pull/3938) by [@erindru](https://github.com/erindru))*\n- [`70a052a`](https://github.com/tobymao/sqlglot/commit/70a052a672d0c72a3e53b19316defb01144f2907) - transpile from_iso8601_timestamp from presto/trino to duckdb *(PR [#3956](https://github.com/tobymao/sqlglot/pull/3956) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`f68d155`](https://github.com/tobymao/sqlglot/commit/f68d155c38a79a6527685c37f8de8773ce790bca) - exp.Merge, for Trino and Postgres, dont strip the target alias from then WHEN MATCHED condition to prevent an ambiguous column error *(PR [#3940](https://github.com/tobymao/sqlglot/pull/3940) by [@erindru](https://github.com/erindru))*\n- [`0458dc0`](https://github.com/tobymao/sqlglot/commit/0458dc0fa1978388336b9fa459b28508d7b40f9e) - **optimizer**: expand alias refs recursive CTE edge case patch *(PR [#3943](https://github.com/tobymao/sqlglot/pull/3943) by [@georgesittas](https://github.com/georgesittas))*\n- [`145fdbf`](https://github.com/tobymao/sqlglot/commit/145fdbf6bb02fa1c55087bfd9f6b3a15fbd4b684) - Redshift date format *(PR [#3942](https://github.com/tobymao/sqlglot/pull/3942) by [@erindru](https://github.com/erindru))*\n- [`6233c2c`](https://github.com/tobymao/sqlglot/commit/6233c2c75ab3a3bc0dfbf28d3fa8adc1be719281) - **parser**: Support sqls with DESCRIBE partition  *(PR [#3945](https://github.com/tobymao/sqlglot/pull/3945) by [@gp1105739](https://github.com/gp1105739))*\n  - :arrow_lower_right: *fixes issue [#3941](https://github.com/tobymao/sqlglot/issues/3941) opened by [@gp1105739](https://github.com/gp1105739)*\n- [`85cd6e5`](https://github.com/tobymao/sqlglot/commit/85cd6e507b73be89d2d9b2c88c7370a14b813b5c) - **bigquery**: Map %e to %-d *(PR [#3946](https://github.com/tobymao/sqlglot/pull/3946) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`1ba0f03`](https://github.com/tobymao/sqlglot/commit/1ba0f03fbfe5dadc3411c7ff26e6dfbef852491a) - **duckdb**: TIME does not support modifiers *(PR [#3947](https://github.com/tobymao/sqlglot/pull/3947) by [@georgesittas](https://github.com/georgesittas))*\n- [`d5d3615`](https://github.com/tobymao/sqlglot/commit/d5d361571cd463869e2243d257f9b6ad0615c070) - **optimizer**: convert TsOrDsToDate to Cast more conservatively *(PR [#3949](https://github.com/tobymao/sqlglot/pull/3949) by [@barakalon](https://github.com/barakalon))*\n- [`fb6edc7`](https://github.com/tobymao/sqlglot/commit/fb6edc774539704b48e7d2805ef3211636af18aa) - oracle/snowflake comments closes [#3950](https://github.com/tobymao/sqlglot/pull/3950) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`1284fd0`](https://github.com/tobymao/sqlglot/commit/1284fd0a64890d3548af7ed0a0cc05bb6166ccb2) - **oracle**: Revert NVL() being parsed into exp.Anonymous *(PR [#3954](https://github.com/tobymao/sqlglot/pull/3954) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3952](https://github.com/tobymao/sqlglot/issues/3952) opened by [@sleshJdev](https://github.com/sleshJdev)*\n- [`c99f8d5`](https://github.com/tobymao/sqlglot/commit/c99f8d5bda79f16fb0d71ae73127cc826860e104) - **duckdb**: Fix exp.Unnest generation for BQ's nested arrays *(PR [#3931](https://github.com/tobymao/sqlglot/pull/3931) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :recycle: Refactors\n- [`f16b0e7`](https://github.com/tobymao/sqlglot/commit/f16b0e7203ad60f0ce50861c4d78176ca53eb2cf) - iteratively generate binary expressions *(PR [#3926](https://github.com/tobymao/sqlglot/pull/3926) by [@MatMoore](https://github.com/MatMoore))*\n- [`667f7d9`](https://github.com/tobymao/sqlglot/commit/667f7d9e94e14ff619998d2001b6116d363f2a1f) - **clickhouse**: attach INTERPOLATE expressions to WithFill *(PR [#3944](https://github.com/tobymao/sqlglot/pull/3944) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`c697357`](https://github.com/tobymao/sqlglot/commit/c6973572dfd953b5539bb4e9dcba402c0c3c6acf) - slightly refactor Generator.binary, add stress test *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6a5f619`](https://github.com/tobymao/sqlglot/commit/6a5f6199f6da0053fa4564e71a17e3b9f91f0496) - New doc - Onboarding Doc *(PR [#3902](https://github.com/tobymao/sqlglot/pull/3902) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v25.15.0] - 2024-08-19\n### :boom: BREAKING CHANGES\n- due to [`a668655`](https://github.com/tobymao/sqlglot/commit/a668655440815605a566c52b65b28decdfb551eb) - preserve SYSDATE *(PR [#3935](https://github.com/tobymao/sqlglot/pull/3935) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve SYSDATE (#3935)\n\n\n### :sparkles: New Features\n- [`be11f4c`](https://github.com/tobymao/sqlglot/commit/be11f4c57c7842f69950bafc3225fb9c139af014) - **clickhouse**: add support for \"@\"-style parameters *(PR [#3939](https://github.com/tobymao/sqlglot/pull/3939) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`a668655`](https://github.com/tobymao/sqlglot/commit/a668655440815605a566c52b65b28decdfb551eb) - **oracle**: preserve SYSDATE *(PR [#3935](https://github.com/tobymao/sqlglot/pull/3935) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3934](https://github.com/tobymao/sqlglot/issues/3934) opened by [@Hal-H2Apps](https://github.com/Hal-H2Apps)*\n- [`b824f8a`](https://github.com/tobymao/sqlglot/commit/b824f8a4148ace01750db301daf4a663dc03b580) - **parser**: allow complex expressions for UNPIVOT alias *(PR [#3937](https://github.com/tobymao/sqlglot/pull/3937) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3936](https://github.com/tobymao/sqlglot/issues/3936) opened by [@dbittenbender](https://github.com/dbittenbender)*\n\n### :recycle: Refactors\n- [`f4c34d3`](https://github.com/tobymao/sqlglot/commit/f4c34d37c5773c37a13437c7e0e7eb27b4e98877) - move \"MINUS\": TokenType.EXCEPT to hive instead of spark *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.14.0] - 2024-08-19\n### :boom: BREAKING CHANGES\n- due to [`605f1b2`](https://github.com/tobymao/sqlglot/commit/605f1b217d5d1de654cfe2fa1b51435a1a71ae62) - use creatable kind mapping dict for schema<-->database substitution *(PR [#3924](https://github.com/tobymao/sqlglot/pull/3924) by [@treysp](https://github.com/treysp))*:\n\n  use creatable kind mapping dict for schema<-->database substitution (#3924)\n\n- due to [`f418caa`](https://github.com/tobymao/sqlglot/commit/f418caafa8ed317f9e360c6c8f01bdac596258e5) - skip nullable comparison in is_type by default *(PR [#3927](https://github.com/tobymao/sqlglot/pull/3927) by [@georgesittas](https://github.com/georgesittas))*:\n\n  skip nullable comparison in is_type by default (#3927)\n\n\n### :sparkles: New Features\n- [`f418caa`](https://github.com/tobymao/sqlglot/commit/f418caafa8ed317f9e360c6c8f01bdac596258e5) - skip nullable comparison in is_type by default *(PR [#3927](https://github.com/tobymao/sqlglot/pull/3927) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`605f1b2`](https://github.com/tobymao/sqlglot/commit/605f1b217d5d1de654cfe2fa1b51435a1a71ae62) - **clickhouse**: use creatable kind mapping dict for schema<-->database substitution *(PR [#3924](https://github.com/tobymao/sqlglot/pull/3924) by [@treysp](https://github.com/treysp))*\n\n\n## [v25.13.0] - 2024-08-17\n### :boom: BREAKING CHANGES\n- due to [`102f5d4`](https://github.com/tobymao/sqlglot/commit/102f5d48279ac1a7a1851737f55a13bd08512f3d) - infer set op types more accurately *(PR [#3918](https://github.com/tobymao/sqlglot/pull/3918) by [@georgesittas](https://github.com/georgesittas))*:\n\n  infer set op types more accurately (#3918)\n\n- due to [`46496a6`](https://github.com/tobymao/sqlglot/commit/46496a6af80bd49d36ef8d265800679d2b07c4db) - improve transpilation of nullable/non-nullable data types *(PR [#3921](https://github.com/tobymao/sqlglot/pull/3921) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve transpilation of nullable/non-nullable data types (#3921)\n\n\n### :bug: Bug Fixes\n- [`c74a8fd`](https://github.com/tobymao/sqlglot/commit/c74a8fd2acd859f5947f27a8f091f13fba1d39e4) - **clickhouse**: make try_cast toXXXOrNull() functions case-specific *(PR [#3917](https://github.com/tobymao/sqlglot/pull/3917) by [@treysp](https://github.com/treysp))*\n- [`102f5d4`](https://github.com/tobymao/sqlglot/commit/102f5d48279ac1a7a1851737f55a13bd08512f3d) - **optimizer**: infer set op types more accurately *(PR [#3918](https://github.com/tobymao/sqlglot/pull/3918) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3916](https://github.com/tobymao/sqlglot/issues/3916) opened by [@racevedoo](https://github.com/racevedoo)*\n\n### :recycle: Refactors\n- [`1d436d4`](https://github.com/tobymao/sqlglot/commit/1d436d45b4469bb8195dd3597319b6fc5c3f2344) - **clickhouse**: transpile TRY_CAST(x AS T) to CAST(x AS Nullable(T)) *(PR [#3919](https://github.com/tobymao/sqlglot/pull/3919) by [@georgesittas](https://github.com/georgesittas))*\n- [`46496a6`](https://github.com/tobymao/sqlglot/commit/46496a6af80bd49d36ef8d265800679d2b07c4db) - **clickhouse**: improve transpilation of nullable/non-nullable data types *(PR [#3921](https://github.com/tobymao/sqlglot/pull/3921) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.12.0] - 2024-08-15\n### :boom: BREAKING CHANGES\n- due to [`e8e70f3`](https://github.com/tobymao/sqlglot/commit/e8e70f3a6cc2ca24de2afe622bbcbccb1ac8aeb3) - treat DATABASE kind as SCHEMA (and conversely) in exp.Create *(PR [#3912](https://github.com/tobymao/sqlglot/pull/3912) by [@georgesittas](https://github.com/georgesittas))*:\n\n  treat DATABASE kind as SCHEMA (and conversely) in exp.Create (#3912)\n\n\n### :sparkles: New Features\n- [`9a66903`](https://github.com/tobymao/sqlglot/commit/9a66903975f16a09d84337a8405bf70945706412) - **clickhouse**: add support for TryCast generation *(PR [#3913](https://github.com/tobymao/sqlglot/pull/3913) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`7965cac`](https://github.com/tobymao/sqlglot/commit/7965cace1d9632c865cae257781072b0932b709d) - **clickhouse**: wrap query in CTAS when COMMENT prop is present *(PR [#3911](https://github.com/tobymao/sqlglot/pull/3911) by [@georgesittas](https://github.com/georgesittas))*\n- [`e8e70f3`](https://github.com/tobymao/sqlglot/commit/e8e70f3a6cc2ca24de2afe622bbcbccb1ac8aeb3) - **clickhouse**: treat DATABASE kind as SCHEMA (and conversely) in exp.Create *(PR [#3912](https://github.com/tobymao/sqlglot/pull/3912) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.11.3] - 2024-08-14\n### :bug: Bug Fixes\n- [`57f7aa9`](https://github.com/tobymao/sqlglot/commit/57f7aa9108ed38c0e83ef5bf4fac900434fac777) - **clickhouse**: COMMENT property in CTAS needs to come last *(PR [#3910](https://github.com/tobymao/sqlglot/pull/3910) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.11.2] - 2024-08-14\n### :bug: Bug Fixes\n- [`c22f411`](https://github.com/tobymao/sqlglot/commit/c22f41129985ecfd3b3906b9594ca1692b91708c) - **clickhouse**: ensure we generate the Table in creatable_sql if it represents a db ref *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`19eee93`](https://github.com/tobymao/sqlglot/commit/19eee93c8027e6c612611d3b54980e193e0b6f49) - various fixups for unnest(generatedatearray) transpilation *(PR [#3906](https://github.com/tobymao/sqlglot/pull/3906) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.11.1] - 2024-08-13\n### :sparkles: New Features\n- [`790c1b1`](https://github.com/tobymao/sqlglot/commit/790c1b141d4bc2206df017c70416b589932886a4) - **clickhouse**: support PARTITION BY, SETTINGS in Insert expression *(PR [#3904](https://github.com/tobymao/sqlglot/pull/3904) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.11.0] - 2024-08-13\n### :boom: BREAKING CHANGES\n- due to [`0428c37`](https://github.com/tobymao/sqlglot/commit/0428c37e11f42be8eba352e69c1d2e7425824d38) - Support ALTER VIEW AS SELECT *(PR [#3873](https://github.com/tobymao/sqlglot/pull/3873) by [@xiaohui-sun](https://github.com/xiaohui-sun))*:\n\n  Support ALTER VIEW AS SELECT (#3873)\n\n- due to [`a666117`](https://github.com/tobymao/sqlglot/commit/a666117dcb887031f5995c50d687405b9c145fbd) - parse v NOT IN (subquery) as v <> ALL (subquery) *(PR [#3891](https://github.com/tobymao/sqlglot/pull/3891) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse v NOT IN (subquery) as v <> ALL (subquery) (#3891)\n\n- due to [`d968932`](https://github.com/tobymao/sqlglot/commit/d968932ef742e97ccf3ec6cdca0bc3319830f0a9) - treat identifiers as case-sensitive, handle EMPTY table property, generate DateStrToDate *(PR [#3895](https://github.com/tobymao/sqlglot/pull/3895) by [@jwhitaker-gridcog](https://github.com/jwhitaker-gridcog))*:\n\n  treat identifiers as case-sensitive, handle EMPTY table property, generate DateStrToDate (#3895)\n\n- due to [`1d7319a`](https://github.com/tobymao/sqlglot/commit/1d7319a8425aace6c11f59552fdd19bdbf5efd03) - transpile Unnest(GenerateDateArray(...)) to various dialects *(PR [#3899](https://github.com/tobymao/sqlglot/pull/3899) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile Unnest(GenerateDateArray(...)) to various dialects (#3899)\n\n\n### :sparkles: New Features\n- [`0428c37`](https://github.com/tobymao/sqlglot/commit/0428c37e11f42be8eba352e69c1d2e7425824d38) - **parser**: Support ALTER VIEW AS SELECT *(PR [#3873](https://github.com/tobymao/sqlglot/pull/3873) by [@xiaohui-sun](https://github.com/xiaohui-sun))*\n- [`8a48458`](https://github.com/tobymao/sqlglot/commit/8a48458e20e6d0833638e750565da138bdcd5d55) - **athena**: parse UNLOAD into exp.Command closes [#3896](https://github.com/tobymao/sqlglot/pull/3896) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6f1527f`](https://github.com/tobymao/sqlglot/commit/6f1527fd3ebd16f49edb351f050a1db687824530) - **bigquery**: transpile format_datetime, datetime_trunc to duckdb *(PR [#3894](https://github.com/tobymao/sqlglot/pull/3894) by [@skadel](https://github.com/skadel))*\n- [`1d7319a`](https://github.com/tobymao/sqlglot/commit/1d7319a8425aace6c11f59552fdd19bdbf5efd03) - transpile Unnest(GenerateDateArray(...)) to various dialects *(PR [#3899](https://github.com/tobymao/sqlglot/pull/3899) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`2cac14f`](https://github.com/tobymao/sqlglot/commit/2cac14f480dcaf458b1eb36b694770ce24f56e61) - generate set ops in ALTER VIEW AS statement *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`a666117`](https://github.com/tobymao/sqlglot/commit/a666117dcb887031f5995c50d687405b9c145fbd) - **snowflake**: parse v NOT IN (subquery) as v <> ALL (subquery) *(PR [#3891](https://github.com/tobymao/sqlglot/pull/3891) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3890](https://github.com/tobymao/sqlglot/issues/3890) opened by [@ajuszczak](https://github.com/ajuszczak)*\n- [`924a4af`](https://github.com/tobymao/sqlglot/commit/924a4af146952e84688fdccb7b63883fcd7fb255) - **oracle**: preserve function-style MOD syntax fixes [#3897](https://github.com/tobymao/sqlglot/pull/3897) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d968932`](https://github.com/tobymao/sqlglot/commit/d968932ef742e97ccf3ec6cdca0bc3319830f0a9) - **clickhouse**: treat identifiers as case-sensitive, handle EMPTY table property, generate DateStrToDate *(PR [#3895](https://github.com/tobymao/sqlglot/pull/3895) by [@jwhitaker-gridcog](https://github.com/jwhitaker-gridcog))*\n- [`3e5e730`](https://github.com/tobymao/sqlglot/commit/3e5e7300ec184024f871669db48d68476b3fa4df) - **clickhouse**: generate exp.Values correctly, handle `FORMAT Values` *(PR [#3900](https://github.com/tobymao/sqlglot/pull/3900) by [@georgesittas](https://github.com/georgesittas))*\n- [`bea3c08`](https://github.com/tobymao/sqlglot/commit/bea3c08e46a020d8545b702c77f0db18c99f1c55) - **parser**: improve performance of OUTER/CROSS APPLY parsing *(PR [#3901](https://github.com/tobymao/sqlglot/pull/3901) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3898](https://github.com/tobymao/sqlglot/issues/3898) opened by [@ewhitley](https://github.com/ewhitley)*\n\n\n## [v25.10.0] - 2024-08-08\n### :boom: BREAKING CHANGES\n- due to [`3eb46db`](https://github.com/tobymao/sqlglot/commit/3eb46db5c429f50b5bb6c0c5517a5f7c1084b5ea) - switch off CSV file schema inference by default *(PR [#3879](https://github.com/tobymao/sqlglot/pull/3879) by [@georgesittas](https://github.com/georgesittas))*:\n\n  switch off CSV file schema inference by default (#3879)\n\n\n### :sparkles: New Features\n- [`3e4fcf7`](https://github.com/tobymao/sqlglot/commit/3e4fcf7e8f6a322c14470de6c5dbba152bc9b2fe) - **databricks**: Add support for STREAMING tables *(PR [#3878](https://github.com/tobymao/sqlglot/pull/3878) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3876](https://github.com/tobymao/sqlglot/issues/3876) opened by [@ericvergnaud](https://github.com/ericvergnaud)*\n- [`528f690`](https://github.com/tobymao/sqlglot/commit/528f6908001db2f132edfa3c61c21815f7e9dc2f) - **duckdb**: Transpile Snowflake's CONVERT_TIMEZONE 3-arg version *(PR [#3883](https://github.com/tobymao/sqlglot/pull/3883) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3875](https://github.com/tobymao/sqlglot/issues/3875) opened by [@milonimrod](https://github.com/milonimrod)*\n- [`411f62a`](https://github.com/tobymao/sqlglot/commit/411f62ad27f8cbe0d9a429e0cafdf4bd9eb2749f) - **bigquery**: Support for GENERATE_TIMESTAMP_ARRAY, DDB transpilation *(PR [#3888](https://github.com/tobymao/sqlglot/pull/3888) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`7169e6e`](https://github.com/tobymao/sqlglot/commit/7169e6ef52d24754059b9ee4324398d22ddff0da) - **bigquery**: ensure Funcs are preserved when used as Tables *(PR [#3877](https://github.com/tobymao/sqlglot/pull/3877) by [@georgesittas](https://github.com/georgesittas))*\n- [`62ceed2`](https://github.com/tobymao/sqlglot/commit/62ceed2fa3cd7b41919839d837b860f3814fa769) - **redshift**: parse first arg in DATE_PART into a Var fixes [#3882](https://github.com/tobymao/sqlglot/pull/3882) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`2ad9bfe`](https://github.com/tobymao/sqlglot/commit/2ad9bfef71ae707b83f604f16b47aa583d082c3b) - **snowflake**: support table qualification in USING clause *(PR [#3885](https://github.com/tobymao/sqlglot/pull/3885) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3881](https://github.com/tobymao/sqlglot/issues/3881) opened by [@dlahyani](https://github.com/dlahyani)*\n- [`ef16b1d`](https://github.com/tobymao/sqlglot/commit/ef16b1da6b43647a0ca08d69eaf3610e3b72671f) - Fix COLLATE's RHS parsing *(PR [#3887](https://github.com/tobymao/sqlglot/pull/3887) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3880](https://github.com/tobymao/sqlglot/issues/3880) opened by [@ewhitley](https://github.com/ewhitley)*\n\n### :recycle: Refactors\n- [`3eb46db`](https://github.com/tobymao/sqlglot/commit/3eb46db5c429f50b5bb6c0c5517a5f7c1084b5ea) - **optimizer**: switch off CSV file schema inference by default *(PR [#3879](https://github.com/tobymao/sqlglot/pull/3879) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.9.0] - 2024-08-05\n### :boom: BREAKING CHANGES\n- due to [`64e187c`](https://github.com/tobymao/sqlglot/commit/64e187c52cd9725ba79e6afbd444382eba9e5827) - transpile postgres impliclitly exploding GENERATE_SERIES proje… *(PR [#3853](https://github.com/tobymao/sqlglot/pull/3853) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile postgres impliclitly exploding GENERATE_SERIES proje… (#3853)\n\n- due to [`e53e7cc`](https://github.com/tobymao/sqlglot/commit/e53e7cc02a224563d0a61b0a39298d606b9bac80) - Generation of exp.ArrayConcat for 2-arg based dialects *(PR [#3864](https://github.com/tobymao/sqlglot/pull/3864) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Generation of exp.ArrayConcat for 2-arg based dialects (#3864)\n\n- due to [`659b8bf`](https://github.com/tobymao/sqlglot/commit/659b8bf12e396856d1562ee4678b4f687629e081) - Support for BQ's exp.GenerateDateArray generation *(PR [#3865](https://github.com/tobymao/sqlglot/pull/3865) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Support for BQ's exp.GenerateDateArray generation (#3865)\n\n\n### :sparkles: New Features\n- [`6afed2a`](https://github.com/tobymao/sqlglot/commit/6afed2aecc0ce186ff6c484b1ad32ac6a2fb61bc) - **duckdb**: Support for exp.TimeDiff generation *(PR [#3856](https://github.com/tobymao/sqlglot/pull/3856) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`64e187c`](https://github.com/tobymao/sqlglot/commit/64e187c52cd9725ba79e6afbd444382eba9e5827) - transpile postgres impliclitly exploding GENERATE_SERIES proje… *(PR [#3853](https://github.com/tobymao/sqlglot/pull/3853) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3818](https://github.com/tobymao/sqlglot/issues/3818) opened by [@wojciechowski-p](https://github.com/wojciechowski-p)*\n- [`8a948c8`](https://github.com/tobymao/sqlglot/commit/8a948c805f7534e266557e1aa08bee0982340685) - **teradata**: Parse RENAME TABLE as Command *(PR [#3863](https://github.com/tobymao/sqlglot/pull/3863) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3861](https://github.com/tobymao/sqlglot/issues/3861) opened by [@EdouardW](https://github.com/EdouardW)*\n- [`659b8bf`](https://github.com/tobymao/sqlglot/commit/659b8bf12e396856d1562ee4678b4f687629e081) - **duckdb**: Support for BQ's exp.GenerateDateArray generation *(PR [#3865](https://github.com/tobymao/sqlglot/pull/3865) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`734f54b`](https://github.com/tobymao/sqlglot/commit/734f54bb6ec697a5213f046fbb1e8174b2c31115) - **snowflake**: add support for a a couple of missing clauses in PIVOT clause *(PR [#3867](https://github.com/tobymao/sqlglot/pull/3867) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`8710763`](https://github.com/tobymao/sqlglot/commit/87107631378b0972115a01cc0bb99dbfc44a66d7) - **presto**: map %W to %A in the TIME_MAPPING *(PR [#3855](https://github.com/tobymao/sqlglot/pull/3855) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3854](https://github.com/tobymao/sqlglot/issues/3854) opened by [@ddelzell](https://github.com/ddelzell)*\n- [`532f3c8`](https://github.com/tobymao/sqlglot/commit/532f3c8714220058170790b13977cc66760841dc) - **duckdb**: Add implicit casts to DATE_DIFF *(PR [#3857](https://github.com/tobymao/sqlglot/pull/3857) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`299c4a5`](https://github.com/tobymao/sqlglot/commit/299c4a559dd04047d5a4c4691f8965972842fe7d) - **clickhouse**: Fix SETTINGS parsing *(PR [#3859](https://github.com/tobymao/sqlglot/pull/3859) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3858](https://github.com/tobymao/sqlglot/issues/3858) opened by [@obazna](https://github.com/obazna)*\n- [`810d23d`](https://github.com/tobymao/sqlglot/commit/810d23d4e42f9a7de83015ec425dff9223598219) - **parser**: make assignment parsing more lenient by allowing keyword in LHS *(PR [#3866](https://github.com/tobymao/sqlglot/pull/3866) by [@georgesittas](https://github.com/georgesittas))*\n- [`e53e7cc`](https://github.com/tobymao/sqlglot/commit/e53e7cc02a224563d0a61b0a39298d606b9bac80) - Generation of exp.ArrayConcat for 2-arg based dialects *(PR [#3864](https://github.com/tobymao/sqlglot/pull/3864) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`813f127`](https://github.com/tobymao/sqlglot/commit/813f127b293e7087d174f3f632b65ba7b24bc9e3) - **duckdb**: Allow DESCRIBE as a _parse_select() path *(PR [#3871](https://github.com/tobymao/sqlglot/pull/3871) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3869](https://github.com/tobymao/sqlglot/issues/3869) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`6ff0c01`](https://github.com/tobymao/sqlglot/commit/6ff0c01a5b8b19e3090b8cf08aabbb4b27425abb) - Fixed size array parsing *(PR [#3870](https://github.com/tobymao/sqlglot/pull/3870) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3868](https://github.com/tobymao/sqlglot/issues/3868) opened by [@tekumara](https://github.com/tekumara)*\n\n\n## [v25.8.1] - 2024-07-30\n### :bug: Bug Fixes\n- [`a295b3a`](https://github.com/tobymao/sqlglot/commit/a295b3adbef0eff0b3f6c3b8b97b1eaa8c13f144) - **tsql**: regression related to CTEs in CREATE VIEW AS statements *(PR [#3852](https://github.com/tobymao/sqlglot/pull/3852) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.8.0] - 2024-07-29\n### :sparkles: New Features\n- [`e37d63a`](https://github.com/tobymao/sqlglot/commit/e37d63a17d4709135c1de7876b2898cf7bd2e641) - **bigquery**: add support for BYTEINT closes [#3838](https://github.com/tobymao/sqlglot/pull/3838) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`4c912cd`](https://github.com/tobymao/sqlglot/commit/4c912cd2302874b8abeed3cafa93ff3771b8dcba) - **clickhouse**: improve parsing/transpilation of StrToDate *(PR [#3839](https://github.com/tobymao/sqlglot/pull/3839) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3837](https://github.com/tobymao/sqlglot/issues/3837) opened by [@ace-xc](https://github.com/ace-xc)*\n- [`45f45ea`](https://github.com/tobymao/sqlglot/commit/45f45eaaac5a9130168dddaef4713542886a83cb) - **duckdb**: add support for SUMMARIZE *(PR [#3840](https://github.com/tobymao/sqlglot/pull/3840) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3823](https://github.com/tobymao/sqlglot/issues/3823) opened by [@cpcloud](https://github.com/cpcloud)*\n\n### :bug: Bug Fixes\n- [`57ecc84`](https://github.com/tobymao/sqlglot/commit/57ecc8465a3c4d1e0ab1db71dc185c80efc5d0aa) - **duckdb**: wrap left IN clause json extract arrow operand fixes [#3836](https://github.com/tobymao/sqlglot/pull/3836) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`2ffb070`](https://github.com/tobymao/sqlglot/commit/2ffb07070952cde7ac9a1883cbf9b4c477c55abb) - **duckdb**: allow fixed length array casts closes [#3841](https://github.com/tobymao/sqlglot/pull/3841) *(PR [#3842](https://github.com/tobymao/sqlglot/pull/3842) by [@tobymao](https://github.com/tobymao))*\n- [`d71eb4e`](https://github.com/tobymao/sqlglot/commit/d71eb4ebc2a0f82c567b32de51298f0d82f400a1) - pretty gen for tuples *(commit by [@tobymao](https://github.com/tobymao))*\n- [`12ae9cd`](https://github.com/tobymao/sqlglot/commit/12ae9cdc1c1f52735f8c60488b5d98a4872bf764) - **tsql**: handle JSON_QUERY with a single argument *(PR [#3847](https://github.com/tobymao/sqlglot/pull/3847) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3843](https://github.com/tobymao/sqlglot/issues/3843) opened by [@zachary62](https://github.com/zachary62)*\n- [`f8ca6b4`](https://github.com/tobymao/sqlglot/commit/f8ca6b4048ee22585cd7635f83b25fe2df9bd748) - **tsql**: bubble up exp.Create CTEs to improve transpilability *(PR [#3848](https://github.com/tobymao/sqlglot/pull/3848) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3844](https://github.com/tobymao/sqlglot/issues/3844) opened by [@zachary62](https://github.com/zachary62)*\n- [`89976c1`](https://github.com/tobymao/sqlglot/commit/89976c1dbb61bdfe3bbb98702b18365e90a69acb) - **parser**: allow 'cube' to be used for identifiers *(PR [#3850](https://github.com/tobymao/sqlglot/pull/3850) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`d00ea9c`](https://github.com/tobymao/sqlglot/commit/d00ea9c4d39f686fabbe864e88cfe5c071fd4f66) - exclude boolean args in Generator.format_args *(PR [#3849](https://github.com/tobymao/sqlglot/pull/3849) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.7.1] - 2024-07-25\n### :bug: Bug Fixes\n- [`ae95c18`](https://github.com/tobymao/sqlglot/commit/ae95c18f636d34c7f92b48cd5970f4fa6ad81b08) - alter table add columns closes [#3835](https://github.com/tobymao/sqlglot/pull/3835) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`9b5839d`](https://github.com/tobymao/sqlglot/commit/9b5839d7fb04f78c9ef50b112cd9d4d24558c912) - make ast consistent *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v25.7.0] - 2024-07-25\n### :sparkles: New Features\n- [`ba0aa50`](https://github.com/tobymao/sqlglot/commit/ba0aa50072f623c299eb4d2dbb69993541fff27b) - **duckdb**: Transpile BQ's exp.DatetimeAdd, exp.DatetimeSub *(PR [#3777](https://github.com/tobymao/sqlglot/pull/3777) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5da91fb`](https://github.com/tobymao/sqlglot/commit/5da91fb50d0f8029ddda16040ebd316c1a651e2d) - **postgres**: Support for CREATE INDEX CONCURRENTLY *(PR [#3787](https://github.com/tobymao/sqlglot/pull/3787) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3783](https://github.com/tobymao/sqlglot/issues/3783) opened by [@EdgyEdgemond](https://github.com/EdgyEdgemond)*\n- [`00722eb`](https://github.com/tobymao/sqlglot/commit/00722eb41795e7454d0ecb4c3d0e1caf96a19465) - Move ANNOTATORS to Dialect for dialect-aware annotation *(PR [#3786](https://github.com/tobymao/sqlglot/pull/3786) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3778](https://github.com/tobymao/sqlglot/issues/3778) opened by [@ddelzell](https://github.com/ddelzell)*\n- [`a6d84fb`](https://github.com/tobymao/sqlglot/commit/a6d84fbd9b4120f42b31bb01d4bf3e6258e51562) - **postgres**: Parse TO_DATE as exp.StrToDate *(PR [#3799](https://github.com/tobymao/sqlglot/pull/3799) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3797](https://github.com/tobymao/sqlglot/issues/3797) opened by [@dioptre](https://github.com/dioptre)*\n- [`3582644`](https://github.com/tobymao/sqlglot/commit/358264478e5449b7e4ebddce1cc463d140f266f5) - **hive, spark, db**: Support for exp.GenerateSeries *(PR [#3798](https://github.com/tobymao/sqlglot/pull/3798) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3793](https://github.com/tobymao/sqlglot/issues/3793) opened by [@wojciechowski-p](https://github.com/wojciechowski-p)*\n- [`80b4a12`](https://github.com/tobymao/sqlglot/commit/80b4a12b779b661e42d31cf75ead8aff25257f8a) - **tsql**: Support for COLUMNSTORE option on CREATE INDEX *(PR [#3805](https://github.com/tobymao/sqlglot/pull/3805) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3801](https://github.com/tobymao/sqlglot/issues/3801) opened by [@na399](https://github.com/na399)*\n- [`bf6c126`](https://github.com/tobymao/sqlglot/commit/bf6c12687f3ed032ea7be40875c19fc00e5927ad) - **databricks**: Support USE CATALOG *(PR [#3812](https://github.com/tobymao/sqlglot/pull/3812) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3811](https://github.com/tobymao/sqlglot/issues/3811) opened by [@grusin-db](https://github.com/grusin-db)*\n- [`624d411`](https://github.com/tobymao/sqlglot/commit/624d4115e3ee4b8db2dbf2970bf0047e14b23e92) - **snowflake**: Support for OBJECT_INSERT, transpile to DDB *(PR [#3807](https://github.com/tobymao/sqlglot/pull/3807) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3802](https://github.com/tobymao/sqlglot/issues/3802) opened by [@buremba](https://github.com/buremba)*\n- [`5b393fb`](https://github.com/tobymao/sqlglot/commit/5b393fb4d2db47b9229ca12a03aba82cdd510615) - **postgres**: Add missing constraint options *(PR [#3816](https://github.com/tobymao/sqlglot/pull/3816) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3814](https://github.com/tobymao/sqlglot/issues/3814) opened by [@DTovstohan](https://github.com/DTovstohan)*\n\n### :bug: Bug Fixes\n- [`898f523`](https://github.com/tobymao/sqlglot/commit/898f523a8db9f73b59055f1e38cf4acb07157f00) - **duckdb**: Wrap JSON_EXTRACT if it's subscripted *(PR [#3785](https://github.com/tobymao/sqlglot/pull/3785) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3782](https://github.com/tobymao/sqlglot/issues/3782) opened by [@egan8888](https://github.com/egan8888)*\n- [`db3748d`](https://github.com/tobymao/sqlglot/commit/db3748d56b138a6427d6f4fc3e32c895ffb993fa) - **mysql**: don't wrap VALUES clause *(PR [#3792](https://github.com/tobymao/sqlglot/pull/3792) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3789](https://github.com/tobymao/sqlglot/issues/3789) opened by [@stephenprater](https://github.com/stephenprater)*\n- [`44d6506`](https://github.com/tobymao/sqlglot/commit/44d650637d5d7a662b57ec1d8ca74dffe0f7ad73) - with as comments closes [#3794](https://github.com/tobymao/sqlglot/pull/3794) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`8ca6a61`](https://github.com/tobymao/sqlglot/commit/8ca6a613692e7339717c449ba6966d7c2911b584) - **tsql**: Fix roundtrip of exp.Stddev *(PR [#3806](https://github.com/tobymao/sqlglot/pull/3806) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3804](https://github.com/tobymao/sqlglot/issues/3804) opened by [@JonaGeishauser](https://github.com/JonaGeishauser)*\n- [`8551063`](https://github.com/tobymao/sqlglot/commit/855106377c97ee313b45046041fafabb2810dab2) - **duckdb**: Fix STRUCT_PACK -> ROW due to is_struct_cast *(PR [#3809](https://github.com/tobymao/sqlglot/pull/3809) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3808](https://github.com/tobymao/sqlglot/issues/3808) opened by [@aersam](https://github.com/aersam)*\n- [`98f80ed`](https://github.com/tobymao/sqlglot/commit/98f80eda3863b5ff40d566330e6ab35a99f569ca) - **clickhouse**: allow like as an identifier closes [#3813](https://github.com/tobymao/sqlglot/pull/3813) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`556ba35`](https://github.com/tobymao/sqlglot/commit/556ba35e4ce9efa51561ef0578bfb24a51ce4dcd) - allow parse_identifier to handle single quotes *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f9810d2`](https://github.com/tobymao/sqlglot/commit/f9810d213f3992881fc13291a681da6553701083) - **snowflake**: Don't consume LPAREN when parsing staged file path *(PR [#3815](https://github.com/tobymao/sqlglot/pull/3815) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`416f4a1`](https://github.com/tobymao/sqlglot/commit/416f4a1b6a04b858ff8ed94509aacd9bacca145b) - **postgres**: Fix COLLATE column constraint *(PR [#3820](https://github.com/tobymao/sqlglot/pull/3820) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3817](https://github.com/tobymao/sqlglot/issues/3817) opened by [@DTovstohan](https://github.com/DTovstohan)*\n- [`69b9395`](https://github.com/tobymao/sqlglot/commit/69b93953c35bd7f1d53cf15d9937117edb38f512) - Do not preemptively consume SELECT [ALL] if ALL is connected *(PR [#3822](https://github.com/tobymao/sqlglot/pull/3822) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3819](https://github.com/tobymao/sqlglot/issues/3819) opened by [@nfx](https://github.com/nfx)*\n- [`1c19abe`](https://github.com/tobymao/sqlglot/commit/1c19abe5b3f3187a2e0ba420cf8c5e5b5ecc788e) - **presto, trino**: Fix StrToUnix transpilation *(PR [#3824](https://github.com/tobymao/sqlglot/pull/3824) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3796](https://github.com/tobymao/sqlglot/issues/3796) opened by [@ddelzell](https://github.com/ddelzell)*\n\n\n## [v25.6.1] - 2024-07-18\n### :bug: Bug Fixes\n- [`19370d5`](https://github.com/tobymao/sqlglot/commit/19370d5d16b555e25def503323ec3dc4e5d40e6c) - **postgres**: Decouple UNIQUE from DEFAULT constraints *(PR [#3775](https://github.com/tobymao/sqlglot/pull/3775) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3774](https://github.com/tobymao/sqlglot/issues/3774) opened by [@EdgyEdgemond](https://github.com/EdgyEdgemond)*\n- [`e99146b`](https://github.com/tobymao/sqlglot/commit/e99146b0989599772c020905f69496ea80e7e2e5) - make copy a dml statement for qualify_tables *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v25.6.0] - 2024-07-17\n### :boom: BREAKING CHANGES\n- due to [`89fc63c`](https://github.com/tobymao/sqlglot/commit/89fc63c5831dc5d63feff9e39fea1e90d65e9a09) - QUALIFY comes after WINDOW clause in queries *(PR [#3745](https://github.com/tobymao/sqlglot/pull/3745) by [@georgesittas](https://github.com/georgesittas))*:\n\n  QUALIFY comes after WINDOW clause in queries (#3745)\n\n- due to [`a2a6efb`](https://github.com/tobymao/sqlglot/commit/a2a6efb45dc0f380747aa4afdaa19122389f3c28) - Canonicalize struct & array inline constructor *(PR [#3751](https://github.com/tobymao/sqlglot/pull/3751) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Canonicalize struct & array inline constructor (#3751)\n\n\n### :sparkles: New Features\n- [`e9c4bbb`](https://github.com/tobymao/sqlglot/commit/e9c4bbbb0d0a03d1b1efaad9abe0068b3b7efa9d) - Support for ORDER BY ALL *(PR [#3756](https://github.com/tobymao/sqlglot/pull/3756) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3755](https://github.com/tobymao/sqlglot/issues/3755) opened by [@Hunterlige](https://github.com/Hunterlige)*\n- [`4a843e6`](https://github.com/tobymao/sqlglot/commit/4a843e6cca7bcc0d9956fe975dbc77e67038f1b8) - **postgres**: Support FROM ROWS FROM (...) *(PR [#3753](https://github.com/tobymao/sqlglot/pull/3753) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`321051a`](https://github.com/tobymao/sqlglot/commit/321051aef30f11f2778444040a2078633e617144) - **presto, trino**: Add support for exp.TimestampAdd *(PR [#3765](https://github.com/tobymao/sqlglot/pull/3765) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3762](https://github.com/tobymao/sqlglot/issues/3762) opened by [@ddelzell](https://github.com/ddelzell)*\n- [`82a1bb4`](https://github.com/tobymao/sqlglot/commit/82a1bb42856d628651bb5f1ef9aa8f440736c450) - Support for RPAD & LPAD functions *(PR [#3757](https://github.com/tobymao/sqlglot/pull/3757) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`89fc63c`](https://github.com/tobymao/sqlglot/commit/89fc63c5831dc5d63feff9e39fea1e90d65e9a09) - **duckdb, clickhouse**: QUALIFY comes after WINDOW clause in queries *(PR [#3745](https://github.com/tobymao/sqlglot/pull/3745) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3744](https://github.com/tobymao/sqlglot/issues/3744) opened by [@taylorbarstow](https://github.com/taylorbarstow)*\n- [`15ca924`](https://github.com/tobymao/sqlglot/commit/15ca924ac6e8a72396a882c394856e466cae9ac3) - **optimizer**: Fix expansion of SELECT * REPLACE, RENAME *(PR [#3742](https://github.com/tobymao/sqlglot/pull/3742) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`0363fef`](https://github.com/tobymao/sqlglot/commit/0363fefd3ddd490ddddae47f7eb0192f0ff3cc5e) - attach comments to Commands *(PR [#3758](https://github.com/tobymao/sqlglot/pull/3758) by [@georgesittas](https://github.com/georgesittas))*\n- [`a2a6efb`](https://github.com/tobymao/sqlglot/commit/a2a6efb45dc0f380747aa4afdaa19122389f3c28) - **bigquery**: Canonicalize struct & array inline constructor *(PR [#3751](https://github.com/tobymao/sqlglot/pull/3751) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`5df3f52`](https://github.com/tobymao/sqlglot/commit/5df3f5292488df6a8e21abf3b49086c823797e78) - Remove number matching from COLON placeholder parser *(PR [#3761](https://github.com/tobymao/sqlglot/pull/3761) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3759](https://github.com/tobymao/sqlglot/issues/3759) opened by [@egan8888](https://github.com/egan8888)*\n- [`0606af6`](https://github.com/tobymao/sqlglot/commit/0606af66dba7c290fee65926dcb74baad82c84ac) - **duckdb**: Transpile UDFs from Databricks *(PR [#3768](https://github.com/tobymao/sqlglot/pull/3768) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3764](https://github.com/tobymao/sqlglot/issues/3764) opened by [@aersam](https://github.com/aersam)*\n- [`dcc783a`](https://github.com/tobymao/sqlglot/commit/dcc783aad7c2e7184224e90fed7710eb08ddc76a) - **clickhouse**: Allow TokenType.SELECT as a Tuple field identifier *(PR [#3766](https://github.com/tobymao/sqlglot/pull/3766) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3763](https://github.com/tobymao/sqlglot/issues/3763) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`b42b7ac`](https://github.com/tobymao/sqlglot/commit/b42b7ac5bb1785a9028235c1557b9842ea1d7524) - extract from time/date *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v25.5.1] - 2024-07-05\n### :bug: Bug Fixes\n- [`2bdde22`](https://github.com/tobymao/sqlglot/commit/2bdde2221b8017791ce4cc619abb2706464ca408) - **optimizer**: only qualify coalesced USING columns if they exist in table schemas *(PR [#3740](https://github.com/tobymao/sqlglot/pull/3740) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.5.0] - 2024-07-04\n### :boom: BREAKING CHANGES\n- due to [`8335ba1`](https://github.com/tobymao/sqlglot/commit/8335ba10e60c7c63881d7559a6f1fada11b0e55d) - preserve EXTRACT(date_part FROM datetime) calls *(PR [#3729](https://github.com/tobymao/sqlglot/pull/3729) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve EXTRACT(date_part FROM datetime) calls (#3729)\n\n- due to [`fb066a6`](https://github.com/tobymao/sqlglot/commit/fb066a6167e1f887bd8c1a1369d063fe70f36a8a) - Decouple NVL() from COALESCE() *(PR [#3734](https://github.com/tobymao/sqlglot/pull/3734) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Decouple NVL() from COALESCE() (#3734)\n\n\n### :sparkles: New Features\n- [`0c03299`](https://github.com/tobymao/sqlglot/commit/0c032992fac622ebaee114cd9f6e405be1820054) - **teradata**: random lower upper closes [#3721](https://github.com/tobymao/sqlglot/pull/3721) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`37b6e2d`](https://github.com/tobymao/sqlglot/commit/37b6e2d806f6da1338c75803919c602f8705acac) - **snowflake**: add support for VECTOR(type, size) *(PR [#3724](https://github.com/tobymao/sqlglot/pull/3724) by [@georgesittas](https://github.com/georgesittas))*\n- [`1e07c4d`](https://github.com/tobymao/sqlglot/commit/1e07c4d29a43192fb57c120f3b9c1c2fa27d0fa6) - **presto, trino**: Configurable transpilation of Snowflake VARIANT *(PR [#3725](https://github.com/tobymao/sqlglot/pull/3725) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3713](https://github.com/tobymao/sqlglot/issues/3713) opened by [@Leonti](https://github.com/Leonti)*\n- [`e5a53aa`](https://github.com/tobymao/sqlglot/commit/e5a53aaa015806574cd3c4bbe46b5788e960903e) - **snowflake**: Support for FROM CHANGES *(PR [#3731](https://github.com/tobymao/sqlglot/pull/3731) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3730](https://github.com/tobymao/sqlglot/issues/3730) opened by [@achicoine-coveo](https://github.com/achicoine-coveo)*\n- [`820d664`](https://github.com/tobymao/sqlglot/commit/820d66430bb23bff88d0057b22842d313e1431c5) - **presto**: wrap md5 string arguments in to_utf8 *(PR [#3732](https://github.com/tobymao/sqlglot/pull/3732) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2855](https://github.com/TobikoData/sqlmesh/issues/2855) opened by [@MikeWallis42](https://github.com/MikeWallis42)*\n- [`912bc84`](https://github.com/tobymao/sqlglot/commit/912bc84791008ecce545cfbd3b0c9d4362131eb3) - **spark, databricks**: Support view schema binding options *(PR [#3739](https://github.com/tobymao/sqlglot/pull/3739) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3738](https://github.com/tobymao/sqlglot/issues/3738) opened by [@aersam](https://github.com/aersam)*\n\n### :bug: Bug Fixes\n- [`3454f86`](https://github.com/tobymao/sqlglot/commit/3454f861f22f680f6b8c18cca466154d3b9fe8d1) - **teradata**: use timestamp with time zone over timestamptz *(PR [#3723](https://github.com/tobymao/sqlglot/pull/3723) by [@mtagle](https://github.com/mtagle))*\n  - :arrow_lower_right: *fixes issue [#3722](https://github.com/tobymao/sqlglot/issues/3722) opened by [@mtagle](https://github.com/mtagle)*\n- [`f4a2872`](https://github.com/tobymao/sqlglot/commit/f4a28721fd33edb3178c1d99746209dadfbba487) - **clickhouse**: switch off table alias columns generation *(PR [#3727](https://github.com/tobymao/sqlglot/pull/3727) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3726](https://github.com/tobymao/sqlglot/issues/3726) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`8335ba1`](https://github.com/tobymao/sqlglot/commit/8335ba10e60c7c63881d7559a6f1fada11b0e55d) - **clickhouse**: preserve EXTRACT(date_part FROM datetime) calls *(PR [#3729](https://github.com/tobymao/sqlglot/pull/3729) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3728](https://github.com/tobymao/sqlglot/issues/3728) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`fb066a6`](https://github.com/tobymao/sqlglot/commit/fb066a6167e1f887bd8c1a1369d063fe70f36a8a) - **oracle**: Decouple NVL() from COALESCE() *(PR [#3734](https://github.com/tobymao/sqlglot/pull/3734) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3733](https://github.com/tobymao/sqlglot/issues/3733) opened by [@Hal-H2Apps](https://github.com/Hal-H2Apps)*\n- [`c790c3b`](https://github.com/tobymao/sqlglot/commit/c790c3b1fa274d7b0faf9f75e7dbc62bc4f55c67) - **tsql**: parse rhs of x::varchar(max) into a type *(PR [#3737](https://github.com/tobymao/sqlglot/pull/3737) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`84416d2`](https://github.com/tobymao/sqlglot/commit/84416d207a2e397aba12a4138fcbd1fab382c22d) - **teradata**: clean up CurrentTimestamp generation logic *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.4.1] - 2024-06-29\n### :bug: Bug Fixes\n- [`6bf9853`](https://github.com/tobymao/sqlglot/commit/6bf9853fd0b26d5a4e93e37447c3b275cd108872) - **tsql**: cast shorthand closes [#3760](https://github.com/tobymao/sqlglot/pull/3760) *(PR [#3720](https://github.com/tobymao/sqlglot/pull/3720) by [@tobymao](https://github.com/tobymao))*\n\n\n## [v25.4.0] - 2024-06-28\n### :boom: BREAKING CHANGES\n- due to [`9fb1d79`](https://github.com/tobymao/sqlglot/commit/9fb1d79398769edb452e075eb3b6416e69f239bf) - extract unit should be a var, not a column *(PR [#3712](https://github.com/tobymao/sqlglot/pull/3712) by [@tobymao](https://github.com/tobymao))*:\n\n  extract unit should be a var, not a column (#3712)\n\n- due to [`ae1816f`](https://github.com/tobymao/sqlglot/commit/ae1816fc71a5a164d1aae6644a9c3bc4cec484d2) - simplify no longer removes neg, add to_py *(PR [#3714](https://github.com/tobymao/sqlglot/pull/3714) by [@tobymao](https://github.com/tobymao))*:\n\n  simplify no longer removes neg, add to_py (#3714)\n\n- due to [`beaf9cc`](https://github.com/tobymao/sqlglot/commit/beaf9cc1f07ff4223f99c84ad6645d3f29af5801) - coalesce left-hand side of join conditions produced by expanding USING *(PR [#3715](https://github.com/tobymao/sqlglot/pull/3715) by [@georgesittas](https://github.com/georgesittas))*:\n\n  coalesce left-hand side of join conditions produced by expanding USING (#3715)\n\n\n### :sparkles: New Features\n- [`97739fe`](https://github.com/tobymao/sqlglot/commit/97739fe692a883a45247d92b2a3efaed33c4b5bf) - add Select expression parser *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1c2279c`](https://github.com/tobymao/sqlglot/commit/1c2279c0659d5cbe30c19afee85308ef7bf4c9c5) - **duckdb**: Transpile exp.Length from other dialects *(PR [#3708](https://github.com/tobymao/sqlglot/pull/3708) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`23dac71`](https://github.com/tobymao/sqlglot/commit/23dac7147883d559acca7d21e3600c28576ec950) - **snowflake**: add support for CONNECT_BY_ROOT expression *(PR [#3717](https://github.com/tobymao/sqlglot/pull/3717) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3716](https://github.com/tobymao/sqlglot/issues/3716) opened by [@niklaslorenzatalligator](https://github.com/niklaslorenzatalligator)*\n- [`4f050e0`](https://github.com/tobymao/sqlglot/commit/4f050e0aefcde8fb3c65abaf49c6aa4e2bbe5e2b) - transpile BigQuery's SAFE_CAST with FORMAT to DuckDB *(PR [#3718](https://github.com/tobymao/sqlglot/pull/3718) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2837](https://github.com/TobikoData/sqlmesh/issues/2837) opened by [@hustic](https://github.com/hustic)*\n\n### :bug: Bug Fixes\n- [`3a86d7e`](https://github.com/tobymao/sqlglot/commit/3a86d7e4ec02e96326021c417dc972b64076567f) - non deterministic aggs in planner closes [#3709](https://github.com/tobymao/sqlglot/pull/3709) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`3b8568d`](https://github.com/tobymao/sqlglot/commit/3b8568d37792c1916f05faf5df8af1841144b338) - **clickhouse**: extract closes [#3711](https://github.com/tobymao/sqlglot/pull/3711) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`9fb1d79`](https://github.com/tobymao/sqlglot/commit/9fb1d79398769edb452e075eb3b6416e69f239bf) - extract unit should be a var, not a column *(PR [#3712](https://github.com/tobymao/sqlglot/pull/3712) by [@tobymao](https://github.com/tobymao))*\n- [`ae1816f`](https://github.com/tobymao/sqlglot/commit/ae1816fc71a5a164d1aae6644a9c3bc4cec484d2) - simplify no longer removes neg, add to_py *(PR [#3714](https://github.com/tobymao/sqlglot/pull/3714) by [@tobymao](https://github.com/tobymao))*\n- [`beaf9cc`](https://github.com/tobymao/sqlglot/commit/beaf9cc1f07ff4223f99c84ad6645d3f29af5801) - **optimizer**: coalesce left-hand side of join conditions produced by expanding USING *(PR [#3715](https://github.com/tobymao/sqlglot/pull/3715) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.3.3] - 2024-06-26\n### :recycle: Refactors\n- [`972ce7d`](https://github.com/tobymao/sqlglot/commit/972ce7d27d9f083d8ef02ded9278e320da3aa0b6) - control ParseJSON generation logic with a flag *(PR [#3707](https://github.com/tobymao/sqlglot/pull/3707) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.3.2] - 2024-06-26\n### :sparkles: New Features\n- [`a1327c7`](https://github.com/tobymao/sqlglot/commit/a1327c7f4ae74ae25617cd448448ae89c915c744) - **tsql**: Add support for scope qualifier operator *(PR [#3703](https://github.com/tobymao/sqlglot/pull/3703) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#2817](https://github.com/TobikoData/sqlmesh/issues/2817) opened by [@elauser](https://github.com/elauser)*\n\n### :bug: Bug Fixes\n- [`842a9f0`](https://github.com/tobymao/sqlglot/commit/842a9f0cf6fd49cf1d6ed31a5ad9b40eaa483bff) - **parser**: preserve Cast expression when it's 'safe' and has a format *(PR [#3705](https://github.com/tobymao/sqlglot/pull/3705) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2827](https://github.com/TobikoData/sqlmesh/issues/2827) opened by [@hustic](https://github.com/hustic)*\n- [`fc0411d`](https://github.com/tobymao/sqlglot/commit/fc0411dc6236c040ce12c036e1ce1165a5143fa1) - **parser**: explicitly check for identifiers in _parse_types *(PR [#3704](https://github.com/tobymao/sqlglot/pull/3704) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2826](https://github.com/TobikoData/sqlmesh/issues/2826) opened by [@plaflamme](https://github.com/plaflamme)*\n\n### :recycle: Refactors\n- [`e9236e3`](https://github.com/tobymao/sqlglot/commit/e9236e36c94464af21c7e2f35a083eef316feab1) - add EXPAND_ALIAS_REFS_ONLY_IN_GROUP_BY dialect constant *(PR [#3702](https://github.com/tobymao/sqlglot/pull/3702) by [@georgesittas](https://github.com/georgesittas))*\n- [`92c6ebb`](https://github.com/tobymao/sqlglot/commit/92c6ebb8f703486cf3132c9d2c3c58568c10aea4) - **tsql**: make ScopeResolution round-trippable *(PR [#3706](https://github.com/tobymao/sqlglot/pull/3706) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.3.1] - 2024-06-25\n### :sparkles: New Features\n- [`4ed02b0`](https://github.com/tobymao/sqlglot/commit/4ed02b0a24eeabf813525ba09d646763970dd33b) - transpile TRY_PARSE_JSON Snowflake -> DuckDB *(PR [#3696](https://github.com/tobymao/sqlglot/pull/3696) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3690](https://github.com/tobymao/sqlglot/issues/3690) opened by [@achicoine-coveo](https://github.com/achicoine-coveo)*\n- [`60fa5e3`](https://github.com/tobymao/sqlglot/commit/60fa5e3f8a6eab3abb12064366a6bde907d9e9de) - **snowflake**: add support for dynamic table DDL *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`442c61d`](https://github.com/tobymao/sqlglot/commit/442c61defe05f4c168a7909d0a5fc5c043a2d2b4) - **tokenizer**: don't treat escapes in raw strings as such for some dialects *(PR [#3689](https://github.com/tobymao/sqlglot/pull/3689) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3686](https://github.com/tobymao/sqlglot/issues/3686) opened by [@aersam](https://github.com/aersam)*\n- [`f3e928e`](https://github.com/tobymao/sqlglot/commit/f3e928e771e1973a13afe09e4dc295ad492b783f) - **parser**: make parse_var_or_string more lenient *(PR [#3695](https://github.com/tobymao/sqlglot/pull/3695) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3693](https://github.com/tobymao/sqlglot/issues/3693) opened by [@WSKINGS](https://github.com/WSKINGS)*\n- [`806a7e4`](https://github.com/tobymao/sqlglot/commit/806a7e421a9b5a54a2859d7bb4c3ea131a4a8640) - remove tokenizer cache for multi-threading *(commit by [@tobymao](https://github.com/tobymao))*\n- [`3fba603`](https://github.com/tobymao/sqlglot/commit/3fba6035ac0263beab73ab62013a64a56dea9165) - don't treat /*+ as a HINT token in dialects that don't support hints *(PR [#3697](https://github.com/tobymao/sqlglot/pull/3697) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3692](https://github.com/tobymao/sqlglot/issues/3692) opened by [@sandband](https://github.com/sandband)*\n- [`e5d534c`](https://github.com/tobymao/sqlglot/commit/e5d534ce96381f42f26d43c4fcab7eff23946c90) - **optimizer**: Force early alias expansion in BQ & CH *(PR [#3699](https://github.com/tobymao/sqlglot/pull/3699) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3687](https://github.com/tobymao/sqlglot/issues/3687) opened by [@viplazylmht](https://github.com/viplazylmht)*\n- [`1cfb1ff`](https://github.com/tobymao/sqlglot/commit/1cfb1ff850fb4fcf69fc5962e01c879ce51bec8b) - proper parsing of unit in spark/databricks date_diff *(PR [#3701](https://github.com/tobymao/sqlglot/pull/3701) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3700](https://github.com/tobymao/sqlglot/issues/3700) opened by [@cheesefactory](https://github.com/cheesefactory)*\n\n### :wrench: Chores\n- [`8b16199`](https://github.com/tobymao/sqlglot/commit/8b16199af3743aee292df5429e1f0087704e1cbc) - bump sqlglotrs to v0.2.8 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.3.0] - 2024-06-21\n### :boom: BREAKING CHANGES\n- due to [`84d820f`](https://github.com/tobymao/sqlglot/commit/84d820f96b161fdd5b00f265890b5c75c65a36f0) - Time/Datetime/Timestamp function additions *(PR [#3666](https://github.com/tobymao/sqlglot/pull/3666) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Time/Datetime/Timestamp function additions (#3666)\n\n- due to [`acbc81d`](https://github.com/tobymao/sqlglot/commit/acbc81d47a2e721c4334ac86b5e17177429cd1c6) - Preserve JSON/VARIANT path with operators *(PR [#3678](https://github.com/tobymao/sqlglot/pull/3678) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Preserve JSON/VARIANT path with operators (#3678)\n\n\n### :sparkles: New Features\n- [`84d820f`](https://github.com/tobymao/sqlglot/commit/84d820f96b161fdd5b00f265890b5c75c65a36f0) - **bigquery**: Time/Datetime/Timestamp function additions *(PR [#3666](https://github.com/tobymao/sqlglot/pull/3666) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`d46ad95`](https://github.com/tobymao/sqlglot/commit/d46ad95bb623f1931d9e373d8444d9ed947362c5) - **tokenizer**: add support for nested comments *(PR [#3670](https://github.com/tobymao/sqlglot/pull/3670) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3668](https://github.com/tobymao/sqlglot/issues/3668) opened by [@aersam](https://github.com/aersam)*\n- [`ac0e89c`](https://github.com/tobymao/sqlglot/commit/ac0e89c4401f2f278d32c3e956670b262ab21ce7) - **snowflake**: add SECURE post table property fixes [#3677](https://github.com/tobymao/sqlglot/pull/3677) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`acbc81d`](https://github.com/tobymao/sqlglot/commit/acbc81d47a2e721c4334ac86b5e17177429cd1c6) - **databricks**: Preserve JSON/VARIANT path with operators *(PR [#3678](https://github.com/tobymao/sqlglot/pull/3678) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3673](https://github.com/tobymao/sqlglot/issues/3673) opened by [@aersam](https://github.com/aersam)*\n- [`07158c7`](https://github.com/tobymao/sqlglot/commit/07158c77ae7879aa83b7982cefb4ec9d01c11857) - **clickhouse**: Fix roundtrips of DATE/TIMESTAMP functions *(PR [#3683](https://github.com/tobymao/sqlglot/pull/3683) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3679](https://github.com/tobymao/sqlglot/issues/3679) opened by [@TacoBel42](https://github.com/TacoBel42)*\n\n### :bug: Bug Fixes\n- [`79aea2a`](https://github.com/tobymao/sqlglot/commit/79aea2affece72acfac52b3ac85cf740d55ccff0) - **doris**: ensure LAG/LEAD are generated with three arguments *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`08fb2ec`](https://github.com/tobymao/sqlglot/commit/08fb2ecf808f25eae74b579f8e5c4369edc7c604) - **parser**: check if FROM exists when making implicit unnest explicit fixes [#3671](https://github.com/tobymao/sqlglot/pull/3671) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`049fc5a`](https://github.com/tobymao/sqlglot/commit/049fc5a430ad6fa2998fd94d6e20b23da3b545c3) - **bigquery**: handle the case-sensitive strategy in normalize_identifier *(PR [#3667](https://github.com/tobymao/sqlglot/pull/3667) by [@georgesittas](https://github.com/georgesittas))*\n- [`9e1b6aa`](https://github.com/tobymao/sqlglot/commit/9e1b6aa5d9e2abb141143327c835c8f3b4bbcb0f) - **parser**: handle another edge case in struct field type parser *(PR [#3682](https://github.com/tobymao/sqlglot/pull/3682) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3680](https://github.com/tobymao/sqlglot/issues/3680) opened by [@plaflamme](https://github.com/plaflamme)*\n- [`a1a0278`](https://github.com/tobymao/sqlglot/commit/a1a02782f22b471ee3c896d57f15237dc86565d1) - jsonbcontains default gen *(commit by [@tobymao](https://github.com/tobymao))*\n- [`bf44942`](https://github.com/tobymao/sqlglot/commit/bf44942a7d35eb83685ad3aa2b360c7105a9f5b7) - **oracle**: Fix default NULL_ORDERING *(PR [#3688](https://github.com/tobymao/sqlglot/pull/3688) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3684](https://github.com/tobymao/sqlglot/issues/3684) opened by [@ncclementi](https://github.com/ncclementi)*\n\n### :wrench: Chores\n- [`7ae99fe`](https://github.com/tobymao/sqlglot/commit/7ae99fe8284cf2e60819b3992bc79a020dfd00c5) - bump sqlglotrs to 0.2.7 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.2.0] - 2024-06-17\n### :boom: BREAKING CHANGES\n- due to [`d331e56`](https://github.com/tobymao/sqlglot/commit/d331e56aad7784a122dc36d7bffe5cf0565e38d1) - Normalize time units in their full singular form *(PR [#3652](https://github.com/tobymao/sqlglot/pull/3652) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Normalize time units in their full singular form (#3652)\n\n- due to [`468123e`](https://github.com/tobymao/sqlglot/commit/468123e4b7612287e128529de62f3a88f4e1d579) - create SetOperation class *(PR [#3661](https://github.com/tobymao/sqlglot/pull/3661) by [@georgesittas](https://github.com/georgesittas))*:\n\n  create SetOperation class (#3661)\n\n\n### :sparkles: New Features\n- [`e7a158b`](https://github.com/tobymao/sqlglot/commit/e7a158b6f0990db00a4890dfb456de6112f50fd2) - set misc. dialect settings if available *(PR [#3649](https://github.com/tobymao/sqlglot/pull/3649) by [@georgesittas](https://github.com/georgesittas))*\n- [`ff3dabc`](https://github.com/tobymao/sqlglot/commit/ff3dabc75f9a03627caa988b85f88be04a6c70a4) - **tsql**: index on closes [#3658](https://github.com/tobymao/sqlglot/pull/3658) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`fb4d908`](https://github.com/tobymao/sqlglot/commit/fb4d9080a042d40455bcf631ca6a0afaacb19683) - **tsql**: clustered index closes [#3659](https://github.com/tobymao/sqlglot/pull/3659) *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`88c4965`](https://github.com/tobymao/sqlglot/commit/88c49651ecc9d55967f5c8056352de0f0981989f) - **mysql**: delete redundant keywords *(PR [#3646](https://github.com/tobymao/sqlglot/pull/3646) by [@Toms1999](https://github.com/Toms1999))*\n- [`4c82c0d`](https://github.com/tobymao/sqlglot/commit/4c82c0d01086e0a622a1448d25f51b0e760d053f) - Parse UNNEST as a function in base dialect *(PR [#3650](https://github.com/tobymao/sqlglot/pull/3650) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3647](https://github.com/tobymao/sqlglot/issues/3647) opened by [@ronnix](https://github.com/ronnix)*\n- [`d331e56`](https://github.com/tobymao/sqlglot/commit/d331e56aad7784a122dc36d7bffe5cf0565e38d1) - **redshift**: Normalize time units in their full singular form *(PR [#3652](https://github.com/tobymao/sqlglot/pull/3652) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3651](https://github.com/tobymao/sqlglot/issues/3651) opened by [@vidit-wisdom](https://github.com/vidit-wisdom)*\n- [`a06ee36`](https://github.com/tobymao/sqlglot/commit/a06ee3695d4d23626c1ef0700b373fc84600d374) - **parser**: edge case in _parse_types *(PR [#3656](https://github.com/tobymao/sqlglot/pull/3656) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3655](https://github.com/tobymao/sqlglot/issues/3655) opened by [@dangoldin](https://github.com/dangoldin)*\n- [`a739741`](https://github.com/tobymao/sqlglot/commit/a739741dca5eefd7d4a2c450dd4506cb951d7efb) - teradata warning *(commit by [@tobymao](https://github.com/tobymao))*\n- [`868f30d`](https://github.com/tobymao/sqlglot/commit/868f30d1ff46ec9b8a048bb79fbb511f458fd769) - improve schema error handling *(PR [#3663](https://github.com/tobymao/sqlglot/pull/3663) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3662](https://github.com/tobymao/sqlglot/issues/3662) opened by [@dexhorthy](https://github.com/dexhorthy)*\n\n### :recycle: Refactors\n- [`e8cab58`](https://github.com/tobymao/sqlglot/commit/e8cab58c4c44e84ee21d11e8554ee7aed5dc5901) - clean up join mark elimination rule *(PR [#3653](https://github.com/tobymao/sqlglot/pull/3653) by [@georgesittas](https://github.com/georgesittas))*\n- [`468123e`](https://github.com/tobymao/sqlglot/commit/468123e4b7612287e128529de62f3a88f4e1d579) - create SetOperation class *(PR [#3661](https://github.com/tobymao/sqlglot/pull/3661) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3660](https://github.com/tobymao/sqlglot/issues/3660) opened by [@sorgfresser](https://github.com/sorgfresser)*\n\n\n## [v25.1.0] - 2024-06-12\n### :boom: BREAKING CHANGES\n- due to [`d6cfb41`](https://github.com/tobymao/sqlglot/commit/d6cfb41d63893eadf23a81adf413952f3bd4f0ad) - Support for DATE_ADD functions *(PR [#3609](https://github.com/tobymao/sqlglot/pull/3609) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Support for DATE_ADD functions (#3609)\n\n\n### :sparkles: New Features\n- [`d6cfb41`](https://github.com/tobymao/sqlglot/commit/d6cfb41d63893eadf23a81adf413952f3bd4f0ad) - **spark, databricks**: Support for DATE_ADD functions *(PR [#3609](https://github.com/tobymao/sqlglot/pull/3609) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3607](https://github.com/tobymao/sqlglot/issues/3607) opened by [@justinbt21](https://github.com/justinbt21)*\n- [`4b30b87`](https://github.com/tobymao/sqlglot/commit/4b30b872b6db73da51e81ef72e1f3bf8763b652b) - **postgres**: Support DIV() func for integer division *(PR [#3602](https://github.com/tobymao/sqlglot/pull/3602) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3601](https://github.com/tobymao/sqlglot/issues/3601) opened by [@andrrreasss](https://github.com/andrrreasss)*\n- [`ee9b01d`](https://github.com/tobymao/sqlglot/commit/ee9b01d5631f8f0942b61dfaf0632ae0ac2543bb) - **mysql**: support ADD INDEX/KEY/UNIQUE in ALTER TABLE *(PR [#3621](https://github.com/tobymao/sqlglot/pull/3621) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3615](https://github.com/tobymao/sqlglot/issues/3615) opened by [@hubg398](https://github.com/hubg398)*\n- [`c49cefa`](https://github.com/tobymao/sqlglot/commit/c49cefafaf5e9e51778ab85499fde29600d66ed7) - **mysql**: support STRAIGHT_JOIN *(PR [#3623](https://github.com/tobymao/sqlglot/pull/3623) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3622](https://github.com/tobymao/sqlglot/issues/3622) opened by [@gabocic](https://github.com/gabocic)*\n- [`e998308`](https://github.com/tobymao/sqlglot/commit/e998308be079bca343af053b99e3826606811df5) - eliminate join marks *(PR [#3580](https://github.com/tobymao/sqlglot/pull/3580) by [@mrhopko](https://github.com/mrhopko))*\n- [`227e054`](https://github.com/tobymao/sqlglot/commit/227e0544ede5dfe3063f3497e865be6e383db524) - **oracle**: support unicode strings u'...' *(PR [#3641](https://github.com/tobymao/sqlglot/pull/3641) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3638](https://github.com/tobymao/sqlglot/issues/3638) opened by [@deebify](https://github.com/deebify)*\n- [`6df5757`](https://github.com/tobymao/sqlglot/commit/6df5757d7714269c035b3a3a015c81bde436f2bb) - bq datetime -> timestampfromparts *(PR [#3642](https://github.com/tobymao/sqlglot/pull/3642) by [@tobymao](https://github.com/tobymao))*\n- [`6abd2c9`](https://github.com/tobymao/sqlglot/commit/6abd2c943896e65b6c9bb5343304dcd8f01b425e) - **oracle**: Support for WITH READ ONLY / CHECK OPTION *(PR [#3639](https://github.com/tobymao/sqlglot/pull/3639) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3635](https://github.com/tobymao/sqlglot/issues/3635) opened by [@deebify](https://github.com/deebify)*\n\n### :bug: Bug Fixes\n- [`514b3a5`](https://github.com/tobymao/sqlglot/commit/514b3a52384fc9164bc5c63fda6b779d68e427b0) - **redshift**: add support for Oracle style outer join markers [#3611](https://github.com/tobymao/sqlglot/pull/3611) *(PR [#3612](https://github.com/tobymao/sqlglot/pull/3612) by [@sandband](https://github.com/sandband))*\n  - :arrow_lower_right: *fixes issue [#3611](https://github.com/tobymao/sqlglot/issues/3611) opened by [@sandband](https://github.com/sandband)*\n- [`6a607d3`](https://github.com/tobymao/sqlglot/commit/6a607d3fa604be7fdbd51e7de06aeedae73039b7) - unnest should also be a function *(commit by [@tobymao](https://github.com/tobymao))*\n- [`0e1a1fb`](https://github.com/tobymao/sqlglot/commit/0e1a1fb31de5fefc16a978162d6c6dd4141e1c4d) - **optimizer**: don't use datetrunc type for right side *(PR [#3614](https://github.com/tobymao/sqlglot/pull/3614) by [@barakalon](https://github.com/barakalon))*\n- [`d96459f`](https://github.com/tobymao/sqlglot/commit/d96459f18b308466fbbfd9fcbe658e33ec931f1e) - **postgres**: sha256 support *(commit by [@tobymao](https://github.com/tobymao))*\n- [`05fe847`](https://github.com/tobymao/sqlglot/commit/05fe847aeb6525836d4eadb908c65a50755dc0c5) - **snowflake**: support fqns in masking/projection policy constraint *(PR [#3620](https://github.com/tobymao/sqlglot/pull/3620) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3617](https://github.com/tobymao/sqlglot/issues/3617) opened by [@kosta-foundational](https://github.com/kosta-foundational)*\n- [`caa3051`](https://github.com/tobymao/sqlglot/commit/caa305161893079f87d4d51d9042b5103a850be4) - **snowflake**: Allow SELECT keyword as JSON path key *(PR [#3627](https://github.com/tobymao/sqlglot/pull/3627) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3619](https://github.com/tobymao/sqlglot/issues/3619) opened by [@kosta-foundational](https://github.com/kosta-foundational)*\n- [`96efb64`](https://github.com/tobymao/sqlglot/commit/96efb6458ad5c6b92990d8ea69545e60b2eaa8a5) - **tokenizer**: properly handle tags that need to be identifiers in heredocs *(PR [#3630](https://github.com/tobymao/sqlglot/pull/3630) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3618](https://github.com/tobymao/sqlglot/issues/3618) opened by [@bigluck](https://github.com/bigluck)*\n- [`4f8edba`](https://github.com/tobymao/sqlglot/commit/4f8edba78d070e2d4b50da56ddb5ed139120c587) - **oracle**: Allow optional format in TO_DATE *(PR [#3637](https://github.com/tobymao/sqlglot/pull/3637) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3636](https://github.com/tobymao/sqlglot/issues/3636) opened by [@deebify](https://github.com/deebify)*\n- [`d8c6153`](https://github.com/tobymao/sqlglot/commit/d8c61534f2b11287af22eb70948dfb735cd778bc) - **oracle**: don't apply eliminate_join_markers at parse time *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1afe6ac`](https://github.com/tobymao/sqlglot/commit/1afe6ac62b9c827a001c5a6ab917304c5756fb09) - don't generate neq(0) if subquery predicate in ensure_bools *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`fc050bd`](https://github.com/tobymao/sqlglot/commit/fc050bddf937509961cfd83e9fa86ed7e931da11) - **sqlite**: Fix transpilation of GENERATED AS IDENTITY *(PR [#3634](https://github.com/tobymao/sqlglot/pull/3634) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3632](https://github.com/tobymao/sqlglot/issues/3632) opened by [@lelandbatey](https://github.com/lelandbatey)*\n- [`47472d9`](https://github.com/tobymao/sqlglot/commit/47472d9c0a27070fd5f4f9b8c12a8bd8c86b1de1) - **duckdb**: get rid of TEXT length to facilitate transpilation *(PR [#3633](https://github.com/tobymao/sqlglot/pull/3633) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`6194c0f`](https://github.com/tobymao/sqlglot/commit/6194c0f37fd322ee2c33ebe30dcee6c836a66943) - clean up logic related to join marker parsing/generation *(PR [#3613](https://github.com/tobymao/sqlglot/pull/3613) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`5396a8e`](https://github.com/tobymao/sqlglot/commit/5396a8e6ea29876c824b741c2812ad15f4768e4c) - fix SQLFrame casing *(PR [#3616](https://github.com/tobymao/sqlglot/pull/3616) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`0397d6f`](https://github.com/tobymao/sqlglot/commit/0397d6f7638c658528cdfef3c85f89afc7fc8952) - bump sqlglotrs to v0.2.6 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.0.3] - 2024-06-06\n### :sparkles: New Features\n- [`97f8d1a`](https://github.com/tobymao/sqlglot/commit/97f8d1a05801bcd7fd237dac0470c232d3106ca4) - add materialize dialect *(PR [#3577](https://github.com/tobymao/sqlglot/pull/3577) by [@bobbyiliev](https://github.com/bobbyiliev))*\n- [`bde5a8d`](https://github.com/tobymao/sqlglot/commit/bde5a8de346125704f757ed6a2de444905fe146e) - add risingwave dialect *(PR [#3598](https://github.com/tobymao/sqlglot/pull/3598) by [@neverchanje](https://github.com/neverchanje))*\n\n### :recycle: Refactors\n- [`5140817`](https://github.com/tobymao/sqlglot/commit/51408172ce940b6ab0ad783d98e632d972da6a0a) - **risingwave**: clean up initial implementation of RisingWave *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`f920014`](https://github.com/tobymao/sqlglot/commit/f920014709c2d3ccb7ec18fb622ecd6b6ee0afcd) - **materialize**: clean up initial implementation of Materialize *(PR [#3608](https://github.com/tobymao/sqlglot/pull/3608) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.0.2] - 2024-06-05\n### :sparkles: New Features\n- [`472058d`](https://github.com/tobymao/sqlglot/commit/472058daccf8dc2a7f7f4b7082309a06802017a5) - **bigquery**: add support for GAP_FILL function *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v25.0.0] - 2024-06-05\n### :bug: Bug Fixes\n- [`f7081c4`](https://github.com/tobymao/sqlglot/commit/f7081c455cf2f61af61dcfd0859a1bf272b84258) - builder other props closes [#3588](https://github.com/tobymao/sqlglot/pull/3588) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`13009ca`](https://github.com/tobymao/sqlglot/commit/13009ca5c14d81b7a07311a38f329b967f909926) - **doris**: use CSV syntax for GROUP_CONCAT *(PR [#3592](https://github.com/tobymao/sqlglot/pull/3592) by [@Toms1999](https://github.com/Toms1999))*\n- [`bf7fd12`](https://github.com/tobymao/sqlglot/commit/bf7fd12f9a19bf91dd89f76cf376bf6004d83dc0) - no_ilike_sql to lower both sides *(PR [#3593](https://github.com/tobymao/sqlglot/pull/3593) by [@barakalon](https://github.com/barakalon))*\n- [`8d87568`](https://github.com/tobymao/sqlglot/commit/8d875681403a43282e1f414ca90f3cf955f26027) - stop normalization_distance early *(PR [#3594](https://github.com/tobymao/sqlglot/pull/3594) by [@barakalon](https://github.com/barakalon))*\n- [`3e38912`](https://github.com/tobymao/sqlglot/commit/3e38912cd0de2e3939221b6ad8ae194e68cfe288) - **duckdb**: add reserved keywords *(PR [#3597](https://github.com/tobymao/sqlglot/pull/3597) by [@georgesittas](https://github.com/georgesittas))*\n- [`5683d5f`](https://github.com/tobymao/sqlglot/commit/5683d5fe7eeae8f70751de962644c0981c21c7fc) - **hive**: generate TRUNC for TimestampTrunc *(PR [#3600](https://github.com/tobymao/sqlglot/pull/3600) by [@Toms1999](https://github.com/Toms1999))*\n- [`ff55ec1`](https://github.com/tobymao/sqlglot/commit/ff55ec1ca8c259f3c304aa7f6039c033f1fe728c) - **hive**: generate string unit for TRUNC, parse it into TimestampTrunc too *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`12b6aa7`](https://github.com/tobymao/sqlglot/commit/12b6aa7006bbf005c750070d9e266153057ff281) - **snowflake**: Fix COPY INTO with subquery *(PR [#3605](https://github.com/tobymao/sqlglot/pull/3605) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3604](https://github.com/tobymao/sqlglot/issues/3604) opened by [@ajuszczak](https://github.com/ajuszczak)*\n- [`061be9b`](https://github.com/tobymao/sqlglot/commit/061be9bda9e03b17590a0ac58fa2fec0540e2e77) - optimize absorb_and_eliminate and remove_complements *(PR [#3595](https://github.com/tobymao/sqlglot/pull/3595) by [@barakalon](https://github.com/barakalon))*\n\n### :wrench: Chores\n- [`7dd244b`](https://github.com/tobymao/sqlglot/commit/7dd244b6a57e4e8cc9d07cbaf3e89c60fa665a69) - **hive**: test TRUNC roundtrip *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v24.1.2] - 2024-06-04\n### :sparkles: New Features\n- [`158ca97`](https://github.com/tobymao/sqlglot/commit/158ca9724c23e7a58f6782719b477f2adb57acae) - **duckdb**: transpile TIMESTAMPNTZ into TIMESTAMP *(PR [#3587](https://github.com/tobymao/sqlglot/pull/3587) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v24.1.1] - 2024-06-03\n### :bug: Bug Fixes\n- [`60d9085`](https://github.com/tobymao/sqlglot/commit/60d9085a4ec2d0c39aa904bf81b7e15b5bac8ea5) - **postgres**: collate with identifier closes [#3578](https://github.com/tobymao/sqlglot/pull/3578) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`317e3a9`](https://github.com/tobymao/sqlglot/commit/317e3a96a49f439aa06af31abb06990f9a1b0d63) - **bigquery**: expanding positional refs with ambiguous references *(PR [#3585](https://github.com/tobymao/sqlglot/pull/3585) by [@tobymao](https://github.com/tobymao))*\n- [`5e321f1`](https://github.com/tobymao/sqlglot/commit/5e321f15ac4e54c78b9f90475e1bac4a94eaa48d) - div aliases closes [#3583](https://github.com/tobymao/sqlglot/pull/3583) *(PR [#3586](https://github.com/tobymao/sqlglot/pull/3586) by [@tobymao](https://github.com/tobymao))*\n\n\n## [v24.1.0] - 2024-05-30\n### :boom: BREAKING CHANGES\n- due to [`0788c94`](https://github.com/tobymao/sqlglot/commit/0788c944a85d7323b61109ee1ccb5859e3d08404) - Expand stars on BigQuery's tbl.struct_col.* selections *(PR [#3531](https://github.com/tobymao/sqlglot/pull/3531) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Expand stars on BigQuery's tbl.struct_col.* selections (#3531)\n\n- due to [`3e71393`](https://github.com/tobymao/sqlglot/commit/3e71393cb8e201a75321fbc179289eb15b1dc6ce) - Refactor struct star expansion in BQ *(PR [#3576](https://github.com/tobymao/sqlglot/pull/3576) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Refactor struct star expansion in BQ (#3576)\n\n\n### :sparkles: New Features\n- [`0788c94`](https://github.com/tobymao/sqlglot/commit/0788c944a85d7323b61109ee1ccb5859e3d08404) - **optimizer**: Expand stars on BigQuery's tbl.struct_col.* selections *(PR [#3531](https://github.com/tobymao/sqlglot/pull/3531) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3484](https://github.com/tobymao/sqlglot/issues/3484) opened by [@Bladieblah](https://github.com/Bladieblah)*\n\n### :bug: Bug Fixes\n- [`14d63ee`](https://github.com/tobymao/sqlglot/commit/14d63ee8172ddc972d6677071cae3880c748c3aa) - bubble up Identifier comments to TableAliases *(PR [#3571](https://github.com/tobymao/sqlglot/pull/3571) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3570](https://github.com/tobymao/sqlglot/issues/3570) opened by [@fangxingli](https://github.com/fangxingli)*\n- [`ba90c22`](https://github.com/tobymao/sqlglot/commit/ba90c22921448ef6b5a0497a9a48918d0e8a9654) - **snowflake**: COPY Postfix *(PR [#3564](https://github.com/tobymao/sqlglot/pull/3564) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`3e71393`](https://github.com/tobymao/sqlglot/commit/3e71393cb8e201a75321fbc179289eb15b1dc6ce) - **optimizer**: Refactor struct star expansion in BQ *(PR [#3576](https://github.com/tobymao/sqlglot/pull/3576) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :recycle: Refactors\n- [`1e1dc3f`](https://github.com/tobymao/sqlglot/commit/1e1dc3fea8c5fc1f86fefe6af384e38c8531f2d2) - **optimizer**: minor improvements in the struct star expansion *(PR [#3568](https://github.com/tobymao/sqlglot/pull/3568) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`b12ea8c`](https://github.com/tobymao/sqlglot/commit/b12ea8c126d5debef59e9d9bcbbc6fd5ecf56682) - minor style changes related to COPY INTO *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v24.0.3] - 2024-05-29\n### :bug: Bug Fixes\n- [`fb8db9f`](https://github.com/tobymao/sqlglot/commit/fb8db9f2219cfd578fda5c3f51737c180d5aecc6) - **parser**: edge case where TYPE_CONVERTERS leads to type instead of column *(PR [#3566](https://github.com/tobymao/sqlglot/pull/3566) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3565](https://github.com/tobymao/sqlglot/issues/3565) opened by [@galunto](https://github.com/galunto)*\n- [`aac8570`](https://github.com/tobymao/sqlglot/commit/aac85705c43edfcd1ebb552573f496c14dce519b) - use index2 instead of self._index in _parse_type index difference *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v24.0.2] - 2024-05-28\n### :sparkles: New Features\n- [`078471d`](https://github.com/tobymao/sqlglot/commit/078471d3643da418c91b71dc7bfce5453b924028) - **mysql,doris**: improve transpilation of INTERVAL (plural to singular) *(PR [#3543](https://github.com/tobymao/sqlglot/pull/3543) by [@Toms1999](https://github.com/Toms1999))*\n- [`fe56e64`](https://github.com/tobymao/sqlglot/commit/fe56e64aff775002c52843b6b9df973d96349400) - **postgres**: add support for col int[size] column def syntax *(PR [#3548](https://github.com/tobymao/sqlglot/pull/3548) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3544](https://github.com/tobymao/sqlglot/issues/3544) opened by [@judahrand](https://github.com/judahrand)*\n  - :arrow_lower_right: *addresses issue [#3545](https://github.com/tobymao/sqlglot/issues/3545) opened by [@judahrand](https://github.com/judahrand)*\n- [`188dce8`](https://github.com/tobymao/sqlglot/commit/188dce8ae98f23b5741882c698109563445f11f6) - **snowflake**: add support for WITH-prefixed column constraints *(PR [#3549](https://github.com/tobymao/sqlglot/pull/3549) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3537](https://github.com/tobymao/sqlglot/issues/3537) opened by [@barino86](https://github.com/barino86)*\n- [`712d247`](https://github.com/tobymao/sqlglot/commit/712d24704f1be9e54fd6385d6fdbd05173b007aa) - add support for ALTER COLUMN DROP NOT NULL *(PR [#3550](https://github.com/tobymao/sqlglot/pull/3550) by [@noklam](https://github.com/noklam))*\n  - :arrow_lower_right: *addresses issue [#3534](https://github.com/tobymao/sqlglot/issues/3534) opened by [@barino86](https://github.com/barino86)*\n- [`7c323bd`](https://github.com/tobymao/sqlglot/commit/7c323bde83f1804d7a1e98fcf94e6832385a03d6) - add option in schema's find method to ensure types are DataTypes *(PR [#3560](https://github.com/tobymao/sqlglot/pull/3560) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`1a8a16b`](https://github.com/tobymao/sqlglot/commit/1a8a16b459c7fe20fc2c689ad601b5beac57a206) - **clickhouse**: improve struct type parsing *(PR [#3547](https://github.com/tobymao/sqlglot/pull/3547) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3546](https://github.com/tobymao/sqlglot/issues/3546) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`970d3b0`](https://github.com/tobymao/sqlglot/commit/970d3b03750d58ec236ce205bc250616e1fb1349) - **postgres**: setting un-suffixed FLOAT as DOUBLE ([#3551](https://github.com/tobymao/sqlglot/pull/3551)) *(PR [#3552](https://github.com/tobymao/sqlglot/pull/3552) by [@sandband](https://github.com/sandband))*\n  - :arrow_lower_right: *fixes issue [#3551](https://github.com/tobymao/sqlglot/issues/3551) opened by [@sandband](https://github.com/sandband)*\n- [`e1a9a8b`](https://github.com/tobymao/sqlglot/commit/e1a9a8b6b7fbd44e62cee626540f90425d22d50c) - **redshift**: add support for MINUS operator [#3553](https://github.com/tobymao/sqlglot/pull/3553) *(PR [#3555](https://github.com/tobymao/sqlglot/pull/3555) by [@sandband](https://github.com/sandband))*\n- [`beb0269`](https://github.com/tobymao/sqlglot/commit/beb0269b39e848897eaf56e1966d342db72e5c7c) - **tsql**: adapt TimeStrToTime to avoid superfluous casts *(PR [#3558](https://github.com/tobymao/sqlglot/pull/3558) by [@Themiscodes](https://github.com/Themiscodes))*\n- [`eae3c51`](https://github.com/tobymao/sqlglot/commit/eae3c5165c16b61c7b524a55776bdb1127005c7d) - use regex to split interval strings *(PR [#3556](https://github.com/tobymao/sqlglot/pull/3556) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3554](https://github.com/tobymao/sqlglot/issues/3554) opened by [@kevinjqiu](https://github.com/kevinjqiu)*\n\n### :recycle: Refactors\n- [`a67de5f`](https://github.com/tobymao/sqlglot/commit/a67de5faaa88c1fb5d9857a69c9df06506520cbc) - get rid of redundant dict_depth check in schema find *(PR [#3561](https://github.com/tobymao/sqlglot/pull/3561) by [@georgesittas](https://github.com/georgesittas))*\n- [`89a8984`](https://github.com/tobymao/sqlglot/commit/89a8984b8db3817d934b4395e190f3848b1ee77a) - move UNESCAPED_SEQUENCES out of the _Dialect metaclass *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`893addf`](https://github.com/tobymao/sqlglot/commit/893addf9d07602ec3a77097f38d696b6760c6038) - add SET NOT NULL test *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v24.0.1] - 2024-05-23\n### :boom: BREAKING CHANGES\n- due to [`80c622e`](https://github.com/tobymao/sqlglot/commit/80c622e0c252ef3be9e469c1cf116c1cd4eaef94) - add reserved keywords fixes [#3526](https://github.com/tobymao/sqlglot/pull/3526) *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  add reserved keywords fixes #3526\n\n\n### :sparkles: New Features\n- [`a255610`](https://github.com/tobymao/sqlglot/commit/a2556101c8d04907ae49252def84c55d2daf78b2) - add StringToArray expression (postgres), improve its transpilation *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`8f46d48`](https://github.com/tobymao/sqlglot/commit/8f46d48d4ef4e6be022aff5739992f149519c19d) - **redshift**: transpile SPLIT_TO_STRING *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`80c622e`](https://github.com/tobymao/sqlglot/commit/80c622e0c252ef3be9e469c1cf116c1cd4eaef94) - **doris**: add reserved keywords fixes [#3526](https://github.com/tobymao/sqlglot/pull/3526) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`ebf5fc7`](https://github.com/tobymao/sqlglot/commit/ebf5fc70d8936b5e1522a3ae1b9e231cefe49623) - **hive**: generate correct names for weekofyear, dayofmonth, dayofweek *(PR [#3533](https://github.com/tobymao/sqlglot/pull/3533) by [@oshyun](https://github.com/oshyun))*\n  - :arrow_lower_right: *fixes issue [#3532](https://github.com/tobymao/sqlglot/issues/3532) opened by [@oshyun](https://github.com/oshyun)*\n- [`3fe3c2c`](https://github.com/tobymao/sqlglot/commit/3fe3c2c0a3e5f465a0c62261c5a0ba6faf8f0846) - **parser**: make _parse_type less aggressive, only parse column as last resort *(PR [#3541](https://github.com/tobymao/sqlglot/pull/3541) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3539](https://github.com/tobymao/sqlglot/issues/3539) opened by [@crash-g](https://github.com/crash-g)*\n  - :arrow_lower_right: *fixes issue [#3540](https://github.com/tobymao/sqlglot/issues/3540) opened by [@crash-g](https://github.com/crash-g)*\n- [`8afff02`](https://github.com/tobymao/sqlglot/commit/8afff028977593789abe31c6168a93b7e32ac890) - **tsql**: preserve REPLICATE roundtrip *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v24.0.0] - 2024-05-21\n### :boom: BREAKING CHANGES\n- due to [`a077f17`](https://github.com/tobymao/sqlglot/commit/a077f17d10200980769ff69dd9044c95d6d718f2) - add reserved keywords *(PR [#3525](https://github.com/tobymao/sqlglot/pull/3525) by [@georgesittas](https://github.com/georgesittas))*:\n\n  add reserved keywords (#3525)\n\n\n### :sparkles: New Features\n- [`d958bba`](https://github.com/tobymao/sqlglot/commit/d958bba8494b8bca9cf3ffef0384690bafd78393) - **snowflake**: add support for CREATE WAREHOUSE *(PR [#3510](https://github.com/tobymao/sqlglot/pull/3510) by [@yingw787](https://github.com/yingw787))*\n  - :arrow_lower_right: *addresses issue [#3502](https://github.com/tobymao/sqlglot/issues/3502) opened by [@yingw787](https://github.com/yingw787)*\n- [`2105300`](https://github.com/tobymao/sqlglot/commit/21053004dbb4c6dc3bcb078c4ab93f267e2c63b2) - **databricks**: Enable hex string literals *(PR [#3522](https://github.com/tobymao/sqlglot/pull/3522) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3521](https://github.com/tobymao/sqlglot/issues/3521) opened by [@aersam](https://github.com/aersam)*\n- [`1ef3bb6`](https://github.com/tobymao/sqlglot/commit/1ef3bb6ab49eff66a50c4d3983f19292b6979e98) - **snowflake**: Add support for `CREATE STREAMLIT` *(PR [#3519](https://github.com/tobymao/sqlglot/pull/3519) by [@yingw787](https://github.com/yingw787))*\n  - :arrow_lower_right: *addresses issue [#3516](https://github.com/tobymao/sqlglot/issues/3516) opened by [@yingw787](https://github.com/yingw787)*\n\n### :bug: Bug Fixes\n- [`5cecbfa`](https://github.com/tobymao/sqlglot/commit/5cecbfa63a770c4d623f4a5f76d1a7a5f59d087d) - unnest identifier closes [#3512](https://github.com/tobymao/sqlglot/pull/3512) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`33ab353`](https://github.com/tobymao/sqlglot/commit/33ab3536d68203f4fceee63507b5c73076d48ed7) - **snowflake**: parse certain DB_CREATABLES as identifiers *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`d468f92`](https://github.com/tobymao/sqlglot/commit/d468f92a16decabdf847d7de19f82d65d1939d92) - **doris**: dont generate arrows for JSONExtract* closes [#3513](https://github.com/tobymao/sqlglot/pull/3513) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`bfb9f98`](https://github.com/tobymao/sqlglot/commit/bfb9f983d35e080ec1f8c171a65d576af873c0ea) - **postgres**: parse @> into ArrayContainsAll, improve transpilation *(PR [#3515](https://github.com/tobymao/sqlglot/pull/3515) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3511](https://github.com/tobymao/sqlglot/issues/3511) opened by [@Toms1999](https://github.com/Toms1999)*\n- [`4def45b`](https://github.com/tobymao/sqlglot/commit/4def45bb553f6fbc65dcf0fa3d6e8c3f5ec000ea) - make UDF DDL property parsing more lenient closes [#3517](https://github.com/tobymao/sqlglot/pull/3517) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`a077f17`](https://github.com/tobymao/sqlglot/commit/a077f17d10200980769ff69dd9044c95d6d718f2) - **mysql**: add reserved keywords *(PR [#3525](https://github.com/tobymao/sqlglot/pull/3525) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3520](https://github.com/tobymao/sqlglot/issues/3520) opened by [@Toms1999](https://github.com/Toms1999)*\n  - :arrow_lower_right: *fixes issue [#3524](https://github.com/tobymao/sqlglot/issues/3524) opened by [@Toms1999](https://github.com/Toms1999)*\n\n### :wrench: Chores\n- [`358f30c`](https://github.com/tobymao/sqlglot/commit/358f30cc02959275c53a2ee9eccde04ddc6a74a5) - remove redundant postgres JSONB token mapping *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.17.0] - 2024-05-19\n### :boom: BREAKING CHANGES\n- due to [`77d21d9`](https://github.com/tobymao/sqlglot/commit/77d21d9379c3f130b803ea651ec3d36256bb84a4) - parse : operator as JSONExtract (similar to Snowflake) *(PR [#3508](https://github.com/tobymao/sqlglot/pull/3508) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse : operator as JSONExtract (similar to Snowflake) (#3508)\n\n\n### :sparkles: New Features\n- [`1125662`](https://github.com/tobymao/sqlglot/commit/11256629d74c4721ed13ed534509d266e260dde6) - add support for snowflake lambdas with type annotations closes … *(PR [#3506](https://github.com/tobymao/sqlglot/pull/3506) by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`77d21d9`](https://github.com/tobymao/sqlglot/commit/77d21d9379c3f130b803ea651ec3d36256bb84a4) - **databricks**: parse : operator as JSONExtract (similar to Snowflake) *(PR [#3508](https://github.com/tobymao/sqlglot/pull/3508) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.16.0] - 2024-05-18\n### :boom: BREAKING CHANGES\n- due to [`e281db8`](https://github.com/tobymao/sqlglot/commit/e281db8784682649be305e9a05c45211402f107c) - Add ALTER TABLE SET *(PR [#3485](https://github.com/tobymao/sqlglot/pull/3485) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Add ALTER TABLE SET (#3485)\n\n\n### :sparkles: New Features\n- [`e281db8`](https://github.com/tobymao/sqlglot/commit/e281db8784682649be305e9a05c45211402f107c) - Add ALTER TABLE SET *(PR [#3485](https://github.com/tobymao/sqlglot/pull/3485) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`9aee21b`](https://github.com/tobymao/sqlglot/commit/9aee21b88e73809e2cdc4e48f04e16edcf1141d7) - add RETURNS NULL ON NULL and STRICT properties *(PR [#3504](https://github.com/tobymao/sqlglot/pull/3504) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3503](https://github.com/tobymao/sqlglot/issues/3503) opened by [@krzysztof-kwitt](https://github.com/krzysztof-kwitt)*\n\n### :wrench: Chores\n- [`0896d11`](https://github.com/tobymao/sqlglot/commit/0896d113b94aaea82e90dd04cdf917dfa546d08e) - lint *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.15.10] - 2024-05-17\n### :sparkles: New Features\n- [`89c1d3a`](https://github.com/tobymao/sqlglot/commit/89c1d3a4dd3387576c384413b3a8991a2dd030de) - **clickhouse**: support generate TimestampTrunc, Variance, Stddev *(PR [#3489](https://github.com/tobymao/sqlglot/pull/3489) by [@longxiaofei](https://github.com/longxiaofei))*\n\n### :bug: Bug Fixes\n- [`03879bb`](https://github.com/tobymao/sqlglot/commit/03879bb3249ee83cce34d629f1016575d3b932e3) - **postgres**: date_trunc supports time zone *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6e7f37a`](https://github.com/tobymao/sqlglot/commit/6e7f37af86a4f36ec47ea4ef3519e5c97376e090) - copy into pretty printing and default dialect *(PR [#3496](https://github.com/tobymao/sqlglot/pull/3496) by [@tobymao](https://github.com/tobymao))*\n- [`e8600e2`](https://github.com/tobymao/sqlglot/commit/e8600e24370a131a0b375a1a9943fdf590968198) - property eq needs highest precedence *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.15.9] - 2024-05-17\n### :boom: BREAKING CHANGES\n- due to [`846d5cd`](https://github.com/tobymao/sqlglot/commit/846d5cd2fe85f836f5ad888e783fedfa2108d579) - set default precision / width for DECIMAL type *(PR [#3472](https://github.com/tobymao/sqlglot/pull/3472) by [@georgesittas](https://github.com/georgesittas))*:\n\n  set default precision / width for DECIMAL type (#3472)\n\n- due to [`e3ff67b`](https://github.com/tobymao/sqlglot/commit/e3ff67b0327a217a0523f82e6a11940feab1a8ac) - preserve star clauses (EXCLUDE, RENAME, REPLACE) *(PR [#3477](https://github.com/tobymao/sqlglot/pull/3477) by [@georgesittas](https://github.com/georgesittas))*:\n\n  preserve star clauses (EXCLUDE, RENAME, REPLACE) (#3477)\n\n- due to [`b417c80`](https://github.com/tobymao/sqlglot/commit/b417c80b4208df1b97363db53af42158aa97bbd6) - parse TININT into UTINYINT to improve transpilation *(PR [#3486](https://github.com/tobymao/sqlglot/pull/3486) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse TININT into UTINYINT to improve transpilation (#3486)\n\n- due to [`54e31af`](https://github.com/tobymao/sqlglot/commit/54e31af7d86138662c9619d50b4ae2e68e04942b) - add DECLARE statement parsing *(PR [#3462](https://github.com/tobymao/sqlglot/pull/3462) by [@jlucas-fsp](https://github.com/jlucas-fsp))*:\n\n  add DECLARE statement parsing (#3462)\n\n- due to [`7287bb9`](https://github.com/tobymao/sqlglot/commit/7287bb9bf578b2b3afaf25647f505b9d73040dc7) - nested cte ordering closes [#3488](https://github.com/tobymao/sqlglot/pull/3488) *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  nested cte ordering closes #3488\n\n\n### :sparkles: New Features\n- [`2c29bf3`](https://github.com/tobymao/sqlglot/commit/2c29bf3b7a163b88754c4593996bbba9b3c791b6) - **snowflake**: add support for CREATE TAG DDL statement *(PR [#3473](https://github.com/tobymao/sqlglot/pull/3473) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3468](https://github.com/tobymao/sqlglot/issues/3468) opened by [@tekumara](https://github.com/tekumara)*\n- [`2433993`](https://github.com/tobymao/sqlglot/commit/24339934167e651f2afd6966024e4d96ef55c677) - **transpiler**: handle different hex behavior for dialects *(PR [#3463](https://github.com/tobymao/sqlglot/pull/3463) by [@viplazylmht](https://github.com/viplazylmht))*\n  - :arrow_lower_right: *addresses issue [#3460](https://github.com/tobymao/sqlglot/issues/3460) opened by [@viplazylmht](https://github.com/viplazylmht)*\n- [`0009e09`](https://github.com/tobymao/sqlglot/commit/0009e09b1a7f94f85985670a09bb0be92c673b46) - add epoch_ms of duckdb to other dialects *(PR [#3471](https://github.com/tobymao/sqlglot/pull/3471) by [@longxiaofei](https://github.com/longxiaofei))*\n- [`461215b`](https://github.com/tobymao/sqlglot/commit/461215b259de98125ea6b09d7bd875edb3ccce75) - **clickhouse**: add support for PROJECTION in CREATE TABLE statement *(PR [#3465](https://github.com/tobymao/sqlglot/pull/3465) by [@GaliFFun](https://github.com/GaliFFun))*\n- [`54e31af`](https://github.com/tobymao/sqlglot/commit/54e31af7d86138662c9619d50b4ae2e68e04942b) - **tsql**: add DECLARE statement parsing *(PR [#3462](https://github.com/tobymao/sqlglot/pull/3462) by [@jlucas-fsp](https://github.com/jlucas-fsp))*\n- [`c811adb`](https://github.com/tobymao/sqlglot/commit/c811adb73e6f83265fedc26274c7d4b40f8a1c85) - snowflake array_construct_compact to spark *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`58d5f2b`](https://github.com/tobymao/sqlglot/commit/58d5f2bece42acdda5f8c08d30e6f61a5e538d4c) - **presto**: fix parsing and generating hash functions presto/trino *(PR [#3459](https://github.com/tobymao/sqlglot/pull/3459) by [@viplazylmht](https://github.com/viplazylmht))*\n  - :arrow_lower_right: *fixes issue [#3458](https://github.com/tobymao/sqlglot/issues/3458) opened by [@viplazylmht](https://github.com/viplazylmht)*\n- [`065281e`](https://github.com/tobymao/sqlglot/commit/065281e28be75597f3f97cee22995423ed483660) - **optimizer**: fix multiple bugs in unnest_subqueries, clean up test suite *(PR [#3464](https://github.com/tobymao/sqlglot/pull/3464) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3448](https://github.com/tobymao/sqlglot/issues/3448) opened by [@yesemsanthoshkumar](https://github.com/yesemsanthoshkumar)*\n- [`80ba1e8`](https://github.com/tobymao/sqlglot/commit/80ba1e8786a6347b8f20f340c185a0b41d017c73) - preserve quotes for projections produced by the eliminate_qualify rule *(PR [#3470](https://github.com/tobymao/sqlglot/pull/3470) by [@aersam](https://github.com/aersam))*\n  - :arrow_lower_right: *fixes issue [#3467](https://github.com/tobymao/sqlglot/issues/3467) opened by [@aersam](https://github.com/aersam)*\n- [`3bc1fbe`](https://github.com/tobymao/sqlglot/commit/3bc1fbed40d9d0d05f189ca60fdc7af19b815e8b) - make quoting of alias_or_name in eliminate_qualify more robust *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`1843e9b`](https://github.com/tobymao/sqlglot/commit/1843e9b825da6e97bda8c7b4fffce40baf199af1) - allow parameters in user-defined types *(PR [#3474](https://github.com/tobymao/sqlglot/pull/3474) by [@georgesittas](https://github.com/georgesittas))*\n- [`e004d2a`](https://github.com/tobymao/sqlglot/commit/e004d2a3d88ea77d34ecdb8290df1e73511e6b6c) - **duckdb**: preserve precedence of json extraction when converting to arrow syntax *(PR [#3478](https://github.com/tobymao/sqlglot/pull/3478) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3476](https://github.com/tobymao/sqlglot/issues/3476) opened by [@asiunov](https://github.com/asiunov)*\n- [`e3ff67b`](https://github.com/tobymao/sqlglot/commit/e3ff67b0327a217a0523f82e6a11940feab1a8ac) - **snowflake**: preserve star clauses (EXCLUDE, RENAME, REPLACE) *(PR [#3477](https://github.com/tobymao/sqlglot/pull/3477) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3475](https://github.com/tobymao/sqlglot/issues/3475) opened by [@asiunov](https://github.com/asiunov)*\n- [`428fd61`](https://github.com/tobymao/sqlglot/commit/428fd61574e10be9afab23ac711758b229cc174f) - **mysql**: generate CONCAT for DPipe *(PR [#3482](https://github.com/tobymao/sqlglot/pull/3482) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3481](https://github.com/tobymao/sqlglot/issues/3481) opened by [@Toms1999](https://github.com/Toms1999)*\n- [`b417c80`](https://github.com/tobymao/sqlglot/commit/b417c80b4208df1b97363db53af42158aa97bbd6) - **tsql**: parse TININT into UTINYINT to improve transpilation *(PR [#3486](https://github.com/tobymao/sqlglot/pull/3486) by [@georgesittas](https://github.com/georgesittas))*\n- [`a3ff49e`](https://github.com/tobymao/sqlglot/commit/a3ff49e93f2c6752f512192ca8b6b6ad18fc925a) - **presto**: fix DELETE DML statement for presto/trino *(PR [#3466](https://github.com/tobymao/sqlglot/pull/3466) by [@viplazylmht](https://github.com/viplazylmht))*\n- [`7287bb9`](https://github.com/tobymao/sqlglot/commit/7287bb9bf578b2b3afaf25647f505b9d73040dc7) - nested cte ordering closes [#3488](https://github.com/tobymao/sqlglot/pull/3488) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`5b64475`](https://github.com/tobymao/sqlglot/commit/5b64475bfd2d6a0ddcb3d0adb60d06dca62421a0) - allow rollup to be used as an identifier *(PR [#3495](https://github.com/tobymao/sqlglot/pull/3495) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3494](https://github.com/tobymao/sqlglot/issues/3494) opened by [@dangoldin](https://github.com/dangoldin)*\n\n### :recycle: Refactors\n- [`846d5cd`](https://github.com/tobymao/sqlglot/commit/846d5cd2fe85f836f5ad888e783fedfa2108d579) - **snowflake**: set default precision / width for DECIMAL type *(PR [#3472](https://github.com/tobymao/sqlglot/pull/3472) by [@georgesittas](https://github.com/georgesittas))*\n- [`930f923`](https://github.com/tobymao/sqlglot/commit/930f923c6da182be33ad4c912b64ec052a63af30) - clean up Hex / LowerHex implementation *(PR [#3483](https://github.com/tobymao/sqlglot/pull/3483) by [@georgesittas](https://github.com/georgesittas))*\n- [`883fcd7`](https://github.com/tobymao/sqlglot/commit/883fcd78645539a275b66472f0bd1dfe1d3d4401) - **presto**: make DELETE transpilation more robust *(PR [#3487](https://github.com/tobymao/sqlglot/pull/3487) by [@georgesittas](https://github.com/georgesittas))*\n- [`49f7f85`](https://github.com/tobymao/sqlglot/commit/49f7f857634ae85547c805ac53911895407dd7cb) - **tsql**: handle TABLE <schema> more gracefully for DeclareItem *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.15.8] - 2024-05-11\n### :boom: BREAKING CHANGES\n- due to [`510f8b5`](https://github.com/tobymao/sqlglot/commit/510f8b5726c59a13284e9482dc47d488559e6c9e) - improve transpilation of TABLESAMPLE clause *(PR [#3457](https://github.com/tobymao/sqlglot/pull/3457) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve transpilation of TABLESAMPLE clause (#3457)\n\n\n### :sparkles: New Features\n- [`510f8b5`](https://github.com/tobymao/sqlglot/commit/510f8b5726c59a13284e9482dc47d488559e6c9e) - improve transpilation of TABLESAMPLE clause *(PR [#3457](https://github.com/tobymao/sqlglot/pull/3457) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3456](https://github.com/tobymao/sqlglot/issues/3456) opened by [@whummer](https://github.com/whummer)*\n- [`e28c959`](https://github.com/tobymao/sqlglot/commit/e28c959bf44208bdb3821b38c13fde59f1944fbb) - make create table cmd parsing less aggressive so that they can be used in sqlmesh @if macros *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.15.7] - 2024-05-11\n### :wrench: Chores\n- [`c3bb3da`](https://github.com/tobymao/sqlglot/commit/c3bb3da670d06cb2eef545a909635224b6e7c205) - change python-version to 3.11 for build-rs *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.15.6] - 2024-05-11\n### :wrench: Chores\n- [`cd8f568`](https://github.com/tobymao/sqlglot/commit/cd8f568dba53efe6b9883035c48a67134016e612) - fix rust deployment workflow bug *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.15.3] - 2024-05-10\n### :wrench: Chores\n- [`130255e`](https://github.com/tobymao/sqlglot/commit/130255ebc927c48b3d3e479e17c38269bd7d8056) - update rust *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.15.2] - 2024-05-10\n### :sparkles: New Features\n- [`116172a`](https://github.com/tobymao/sqlglot/commit/116172a41119e72aaf618a83761f73d52f0440d2) - add support for ON property in ALTER and DROP statements *(PR [#3450](https://github.com/tobymao/sqlglot/pull/3450) by [@GaliFFun](https://github.com/GaliFFun))*\n- [`aa104fd`](https://github.com/tobymao/sqlglot/commit/aa104fd2ccd73a13ca60fa3de3296ed4c007e8da) - add semi colon comments *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`2c62267`](https://github.com/tobymao/sqlglot/commit/2c62267e2ae908d10d8164f080bc66a133596bf6) - **bigquery**: fix SHA1 generator *(PR [#3453](https://github.com/tobymao/sqlglot/pull/3453) by [@viplazylmht](https://github.com/viplazylmht))*\n  - :arrow_lower_right: *fixes issue [#3451](https://github.com/tobymao/sqlglot/issues/3451) opened by [@viplazylmht](https://github.com/viplazylmht)*\n- [`fb3dea9`](https://github.com/tobymao/sqlglot/commit/fb3dea9a803157b4684cd62e2ef0b6a6b612f7e1) - **clickhouse**: fix parsing and generating hash functions *(PR [#3454](https://github.com/tobymao/sqlglot/pull/3454) by [@viplazylmht](https://github.com/viplazylmht))*\n  - :arrow_lower_right: *fixes issue [#3452](https://github.com/tobymao/sqlglot/issues/3452) opened by [@viplazylmht](https://github.com/viplazylmht)*\n- [`b76dfda`](https://github.com/tobymao/sqlglot/commit/b76dfda7b4122a59c52bcbb445cffc6617e68b8c) - **snowflake**: COPY Subquery postfix *(PR [#3449](https://github.com/tobymao/sqlglot/pull/3449) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3434](https://github.com/tobymao/sqlglot/issues/3434) opened by [@whummer](https://github.com/whummer)*\n\n### :wrench: Chores\n- [`684df5f`](https://github.com/tobymao/sqlglot/commit/684df5f7e11bb89def9ff71da0913de222bdaf3c) - remove unnecessary set_op *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.15.1] - 2024-05-10\n### :bug: Bug Fixes\n- [`33ac4fc`](https://github.com/tobymao/sqlglot/commit/33ac4fca3e5f162500ddde529cd69c338a6fecc5) - add create view tsql *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.15.0] - 2024-05-09\n### :boom: BREAKING CHANGES\n- due to [`9338ebc`](https://github.com/tobymao/sqlglot/commit/9338ebc6dc9635f12639b562ee2af140cf708b6b) - tsql drop view no catalog *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  tsql drop view no catalog\n\n\n### :sparkles: New Features\n- [`80670bb`](https://github.com/tobymao/sqlglot/commit/80670bbd1e062cc476dcee17d0b9972ff7dc0424) - **snowflake**: Support for APPROX_PERCENTILE *(PR [#3426](https://github.com/tobymao/sqlglot/pull/3426) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3424](https://github.com/tobymao/sqlglot/issues/3424) opened by [@baruchoxman](https://github.com/baruchoxman)*\n- [`b46c5b3`](https://github.com/tobymao/sqlglot/commit/b46c5b3ddaed359fb59264f00d7033c7b36bd9a4) - **clickhouse**: add support for partition expression *(PR [#3428](https://github.com/tobymao/sqlglot/pull/3428) by [@GaliFFun](https://github.com/GaliFFun))*\n- [`07badc9`](https://github.com/tobymao/sqlglot/commit/07badc9d155cfd6d0c70e4419ed763b8c52b4973) - **clickhouse**: add support for ALTER TABLE REPLACE PARTITION statement *(PR [#3441](https://github.com/tobymao/sqlglot/pull/3441) by [@GaliFFun](https://github.com/GaliFFun))*\n- [`baf39e7`](https://github.com/tobymao/sqlglot/commit/baf39e78cdebf5478b59f83120c43b39b27d1a31) - **redshift**: improve ALTER TABLE .. ALTER .. support *(PR [#3444](https://github.com/tobymao/sqlglot/pull/3444) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`e8014e2`](https://github.com/tobymao/sqlglot/commit/e8014e2a479c37ef75510e7d5ca90ed30522ce60) - **mysql**: Parse REPLACE statement as Command *(PR [#3425](https://github.com/tobymao/sqlglot/pull/3425) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3423](https://github.com/tobymao/sqlglot/issues/3423) opened by [@DyCheer](https://github.com/DyCheer)*\n- [`273731f`](https://github.com/tobymao/sqlglot/commit/273731fd8cba4d6bda0d7ce109f25c49de0ec95c) - **snowflake**: parse CREATE SEQUENCE with commas *(PR [#3436](https://github.com/tobymao/sqlglot/pull/3436) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3435](https://github.com/tobymao/sqlglot/issues/3435) opened by [@whummer](https://github.com/whummer)*\n- [`761ba6f`](https://github.com/tobymao/sqlglot/commit/761ba6fb507158d4e5ea51ca396809be91c11ebf) - don't generate connector comments when comments=False closes [#3439](https://github.com/tobymao/sqlglot/pull/3439) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`a2a6eaa`](https://github.com/tobymao/sqlglot/commit/a2a6eaa5d7ace2879ded7c3a4cf4192b75c07f26) - handle empty string in connector comment padding *(PR [#3437](https://github.com/tobymao/sqlglot/pull/3437) by [@uncledata](https://github.com/uncledata))*\n- [`1bc0ce5`](https://github.com/tobymao/sqlglot/commit/1bc0ce57eca5e401a4c39237b52ee722bdfb46af) - func to binary MOD generation *(PR [#3440](https://github.com/tobymao/sqlglot/pull/3440) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3431](https://github.com/tobymao/sqlglot/issues/3431) opened by [@daniel769](https://github.com/daniel769)*\n- [`5cfb29c`](https://github.com/tobymao/sqlglot/commit/5cfb29c7ff6015e39d7fd5b94ed2aa66436e33ae) - **bigquery**: MOD edge case *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`9825c4c`](https://github.com/tobymao/sqlglot/commit/9825c4cb616af07a048109c499666081bc6e4eba) - improve error handling for nested schema levels *(PR [#3445](https://github.com/tobymao/sqlglot/pull/3445) by [@tobymao](https://github.com/tobymao))*\n- [`c309def`](https://github.com/tobymao/sqlglot/commit/c309defa450f755dbed1d1b6f276b4b1765166e2) - **duckdb**: use name sequence instead of single _t for unnest alias *(PR [#3446](https://github.com/tobymao/sqlglot/pull/3446) by [@georgesittas](https://github.com/georgesittas))*\n- [`0927ae3`](https://github.com/tobymao/sqlglot/commit/0927ae3c448ebf068b89bfa5e46b8f135121b470) - **executor**: use timezone-aware object to represent datetime in UTC *(PR [#3447](https://github.com/tobymao/sqlglot/pull/3447) by [@georgesittas](https://github.com/georgesittas))*\n- [`9338ebc`](https://github.com/tobymao/sqlglot/commit/9338ebc6dc9635f12639b562ee2af140cf708b6b) - tsql drop view no catalog *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`30f9d30`](https://github.com/tobymao/sqlglot/commit/30f9d30d8ab3727a43b1e6f363f28631cbfa7f92) - bump ruff to 0.4.3 *(PR [#3430](https://github.com/tobymao/sqlglot/pull/3430) by [@georgesittas](https://github.com/georgesittas))*\n- [`91bed56`](https://github.com/tobymao/sqlglot/commit/91bed5607e442d416021a1f93e4a457fb47b6a1f) - test 3.12 *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.14.0] - 2024-05-07\n### :boom: BREAKING CHANGES\n- due to [`258ad3b`](https://github.com/tobymao/sqlglot/commit/258ad3bbf73f55d02ed78a93fa0f16d4630159e3) - parse column instead of identifier for SET assignment LHS *(PR [#3417](https://github.com/tobymao/sqlglot/pull/3417) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse column instead of identifier for SET assignment LHS (#3417)\n\n\n### :bug: Bug Fixes\n- [`258ad3b`](https://github.com/tobymao/sqlglot/commit/258ad3bbf73f55d02ed78a93fa0f16d4630159e3) - parse column instead of identifier for SET assignment LHS *(PR [#3417](https://github.com/tobymao/sqlglot/pull/3417) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3415](https://github.com/tobymao/sqlglot/issues/3415) opened by [@tekumara](https://github.com/tekumara)*\n- [`17c31da`](https://github.com/tobymao/sqlglot/commit/17c31da9e159dc1cdd91bd6df38c43606bdc48c9) - **lineage**: get rid of comments in Node names *(PR [#3418](https://github.com/tobymao/sqlglot/pull/3418) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3414](https://github.com/tobymao/sqlglot/issues/3414) opened by [@jaspev123](https://github.com/jaspev123)*\n- [`ea197ea`](https://github.com/tobymao/sqlglot/commit/ea197eae2fcdbeba395b53cf4864fc2e44134c71) - **snowflake**: ensure OBJECT_CONSTRUCT is not generated inside of VALUES *(PR [#3419](https://github.com/tobymao/sqlglot/pull/3419) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.13.7] - 2024-05-04\n### :wrench: Chores\n- [`4dbcd4f`](https://github.com/tobymao/sqlglot/commit/4dbcd4f7147204b7bafa32d14dfe615882562b6b) - refactor publish workflow for sqlglotrs releasing *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.13.6] - 2024-05-04\n### :wrench: Chores\n- [`aa4f90a`](https://github.com/tobymao/sqlglot/commit/aa4f90acde9c022fb7f984b30763c732977c1b4c) - refactor publish workflow for sqlglotrs releasing *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.13.5] - 2024-05-04\n### :wrench: Chores\n- [`0deffd8`](https://github.com/tobymao/sqlglot/commit/0deffd89a8c6d2da90c9a654c22b78dd4c7dd8f6) - refactor publish workflow for sqlglotrs releasing *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.13.4] - 2024-05-04\n### :wrench: Chores\n- [`5125732`](https://github.com/tobymao/sqlglot/commit/5125732f05408750aceefba99b48aeb4def89557) - refactor publish workflow for sqlglotrs releasing *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.13.3] - 2024-05-04\n### :wrench: Chores\n- [`0a36dd8`](https://github.com/tobymao/sqlglot/commit/0a36dd85cd7de544a509f7e4ccdddf0cb0c1f697) - fix should-deploy-rs bash condition *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.13.2] - 2024-05-04\n### :bug: Bug Fixes\n- [`fc979a0`](https://github.com/tobymao/sqlglot/commit/fc979a0055c0f402cda77448d9c7dfecf45a901f) - **snowflake**: make FILE_FORMAT option always be uppercase in COPY INTO *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`1b5b77d`](https://github.com/tobymao/sqlglot/commit/1b5b77d849260589a2f7d3593c4472e47cae0280) - improve unsupported error documentation *(PR [#3406](https://github.com/tobymao/sqlglot/pull/3406) by [@georgesittas](https://github.com/georgesittas))*\n- [`fcb51af`](https://github.com/tobymao/sqlglot/commit/fcb51afc4631cfc5f494c9114d4aba667aa46087) - release sqlglotrs only when Cargo.toml is updated *(PR [#3408](https://github.com/tobymao/sqlglot/pull/3408) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.13.1] - 2024-05-04\n### :bug: Bug Fixes\n- [`2c2a788`](https://github.com/tobymao/sqlglot/commit/2c2a788bb3a5a46e7729a117a6e6b62d33beb020) - **snowflake**: COPY postfix *(PR [#3398](https://github.com/tobymao/sqlglot/pull/3398) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3388](https://github.com/tobymao/sqlglot/issues/3388) opened by [@dangoldin](https://github.com/dangoldin)*\n\n\n## [v23.13.0] - 2024-05-03\n### :boom: BREAKING CHANGES\n- due to [`cc6259d`](https://github.com/tobymao/sqlglot/commit/cc6259de3d68831ded31bfb7fafe1ce654aa89dd) - Mark UDTF child scopes as ScopeType.SUBQUERY *(PR [#3390](https://github.com/tobymao/sqlglot/pull/3390) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Mark UDTF child scopes as ScopeType.SUBQUERY (#3390)\n\n- due to [`33bae9b`](https://github.com/tobymao/sqlglot/commit/33bae9b527b27f02dfafff3f45534f85aa9e0d9d) - get rid of superfluous \"parameters\" arg in RegexpReplace *(PR [#3394](https://github.com/tobymao/sqlglot/pull/3394) by [@georgesittas](https://github.com/georgesittas))*:\n\n  get rid of superfluous \"parameters\" arg in RegexpReplace (#3394)\n\n- due to [`3768514`](https://github.com/tobymao/sqlglot/commit/3768514e3b2f256b69553e173b40f17180744ab0) - snowflake optional merge insert *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  snowflake optional merge insert\n\n- due to [`d1b4f1f`](https://github.com/tobymao/sqlglot/commit/d1b4f1f256cd772bec366d6bf13d9589e1fdfc4b) - Introducing TIMESTAMP_NTZ token and data type *(PR [#3386](https://github.com/tobymao/sqlglot/pull/3386) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Introducing TIMESTAMP_NTZ token and data type (#3386)\n\n\n### :sparkles: New Features\n- [`d1b4f1f`](https://github.com/tobymao/sqlglot/commit/d1b4f1f256cd772bec366d6bf13d9589e1fdfc4b) - Introducing TIMESTAMP_NTZ token and data type *(PR [#3386](https://github.com/tobymao/sqlglot/pull/3386) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3379](https://github.com/tobymao/sqlglot/issues/3379) opened by [@aersam](https://github.com/aersam)*\n- [`16691f9`](https://github.com/tobymao/sqlglot/commit/16691f962822a132e233d61c2b67ec0fc3da51eb) - **prql**: add support for AGGREGATE *(PR [#3395](https://github.com/tobymao/sqlglot/pull/3395) by [@fool1280](https://github.com/fool1280))*\n- [`534fb80`](https://github.com/tobymao/sqlglot/commit/534fb80462370b5236061472496c35a16e9bab4a) - **postgres**: add support for anonymos index DDL syntax *(PR [#3403](https://github.com/tobymao/sqlglot/pull/3403) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`a2afcca`](https://github.com/tobymao/sqlglot/commit/a2afccafd300939eaa5a3b075820f3bf8e8dcaac) - **mysql**: don't cast into invalid numeric/text types *(PR [#3375](https://github.com/tobymao/sqlglot/pull/3375) by [@georgesittas](https://github.com/georgesittas))*\n- [`60b5c3b`](https://github.com/tobymao/sqlglot/commit/60b5c3b1b5dfb4aa00754f4b2473ad054b8dd14a) - **spark**: transpile presto TRY, fix JSON casting issue *(PR [#3376](https://github.com/tobymao/sqlglot/pull/3376) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3374](https://github.com/tobymao/sqlglot/issues/3374) opened by [@cploonker](https://github.com/cploonker)*\n- [`3e8de71`](https://github.com/tobymao/sqlglot/commit/3e8de7124b735a6ab52971a3e51702c4e7b74be5) - **postgres**: allow FOR clause without FROM in SUBSTRING closes [#3377](https://github.com/tobymao/sqlglot/pull/3377) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`b2a7e55`](https://github.com/tobymao/sqlglot/commit/b2a7e550b25fd95eb0abba63228c9e285be168e0) - **optimizer**: Remove XOR from connector simplifications *(PR [#3380](https://github.com/tobymao/sqlglot/pull/3380) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3372](https://github.com/tobymao/sqlglot/issues/3372) opened by [@colincointe](https://github.com/colincointe)*\n- [`477754c`](https://github.com/tobymao/sqlglot/commit/477754c72c47b6dc9dd01463b8f6fae6686cb1ac) - **trino**: bring back TRIM parsing *(PR [#3385](https://github.com/tobymao/sqlglot/pull/3385) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3384](https://github.com/tobymao/sqlglot/issues/3384) opened by [@dmelchor-stripe](https://github.com/dmelchor-stripe)*\n- [`cc6259d`](https://github.com/tobymao/sqlglot/commit/cc6259de3d68831ded31bfb7fafe1ce654aa89dd) - **optimizer**: Mark UDTF child scopes as ScopeType.SUBQUERY *(PR [#3390](https://github.com/tobymao/sqlglot/pull/3390) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`0d23b20`](https://github.com/tobymao/sqlglot/commit/0d23b20352a8931adf8224d322da324b18e8282d) - allow joins in FROM expression parser *(PR [#3389](https://github.com/tobymao/sqlglot/pull/3389) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3387](https://github.com/tobymao/sqlglot/issues/3387) opened by [@MikeWallis42](https://github.com/MikeWallis42)*\n- [`e7021df`](https://github.com/tobymao/sqlglot/commit/e7021df397a1dc5e726d1e391ef6428a3190856d) - **duckdb**: Preserve DATE_SUB roundtrip *(PR [#3382](https://github.com/tobymao/sqlglot/pull/3382) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3373](https://github.com/tobymao/sqlglot/issues/3373) opened by [@zergar](https://github.com/zergar)*\n- [`641b296`](https://github.com/tobymao/sqlglot/commit/641b296017591b65ffc223d28b37e51886789ca7) - **postgres**: tokenize INT8 as BIGINT *(PR [#3392](https://github.com/tobymao/sqlglot/pull/3392) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3391](https://github.com/tobymao/sqlglot/issues/3391) opened by [@fuzi1996](https://github.com/fuzi1996)*\n- [`33bae9b`](https://github.com/tobymao/sqlglot/commit/33bae9b527b27f02dfafff3f45534f85aa9e0d9d) - get rid of superfluous \"parameters\" arg in RegexpReplace *(PR [#3394](https://github.com/tobymao/sqlglot/pull/3394) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3393](https://github.com/tobymao/sqlglot/issues/3393) opened by [@rzykov](https://github.com/rzykov)*\n- [`3768514`](https://github.com/tobymao/sqlglot/commit/3768514e3b2f256b69553e173b40f17180744ab0) - snowflake optional merge insert *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f44cd24`](https://github.com/tobymao/sqlglot/commit/f44cd248a82f5519afd0edba5112a499b804fe8f) - make generated constraint parsing more lenient fixes [#3397](https://github.com/tobymao/sqlglot/pull/3397) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`00ff877`](https://github.com/tobymao/sqlglot/commit/00ff87719ab4d6e3a407334c8d811366d0c7ead5) - **tsql**: quote hash sign as well for quoted temporary tables *(PR [#3401](https://github.com/tobymao/sqlglot/pull/3401) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3399](https://github.com/tobymao/sqlglot/issues/3399) opened by [@gforsyth](https://github.com/gforsyth)*\n- [`84b7026`](https://github.com/tobymao/sqlglot/commit/84b7026e2fbc4e73c3b4c0c86cb764b95541841e) - **trino**: support for data type 'tdigest' *(PR [#3402](https://github.com/tobymao/sqlglot/pull/3402) by [@suryaiyer95](https://github.com/suryaiyer95))*\n- [`24e1115`](https://github.com/tobymao/sqlglot/commit/24e1115c957d42a5511c1c428516e3ce5426cd88) - **trino|presto**: adding cast support for \"hyperloglog\" column type *(PR [#3405](https://github.com/tobymao/sqlglot/pull/3405) by [@uncledata](https://github.com/uncledata))*\n\n\n## [v23.12.2] - 2024-04-30\n### :sparkles: New Features\n- [`d2a6f16`](https://github.com/tobymao/sqlglot/commit/d2a6f16c35cbe355932d0e0eab2fc6ba096d8a97) - COPY TO/FROM statement *(PR [#3359](https://github.com/tobymao/sqlglot/pull/3359) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`f034ea0`](https://github.com/tobymao/sqlglot/commit/f034ea0fdd7429bf6694e07b4aff06c665c10951) - **mysql**: Transpile TimestampTrunc *(PR [#3367](https://github.com/tobymao/sqlglot/pull/3367) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3366](https://github.com/tobymao/sqlglot/issues/3366) opened by [@sivpr2000](https://github.com/sivpr2000)*\n\n### :bug: Bug Fixes\n- [`f697cb1`](https://github.com/tobymao/sqlglot/commit/f697cb16b6d744253febb2f83476853e63e06f88) - duckdb describe query closes [#3353](https://github.com/tobymao/sqlglot/pull/3353) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`6e0fc5d`](https://github.com/tobymao/sqlglot/commit/6e0fc5dd8e1921aac1e3f9834dd6a1c0e30b9e50) - export optimizer functions explicitly in init *(PR [#3358](https://github.com/tobymao/sqlglot/pull/3358) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3354](https://github.com/tobymao/sqlglot/issues/3354) opened by [@tekumara](https://github.com/tekumara)*\n- [`23d45ee`](https://github.com/tobymao/sqlglot/commit/23d45eefb8b5f650d2e723499a12ac6801d5cd14) - **postgres**: don't generate CommentColumnConstraint *(PR [#3357](https://github.com/tobymao/sqlglot/pull/3357) by [@georgesittas](https://github.com/georgesittas))*\n- [`e87685b`](https://github.com/tobymao/sqlglot/commit/e87685b6971d6ddb7d222993b38aa224c39c5154) - **lineage**: use source names of derived table sources for laterals *(PR [#3360](https://github.com/tobymao/sqlglot/pull/3360) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3356](https://github.com/tobymao/sqlglot/issues/3356) opened by [@eliaperantoni](https://github.com/eliaperantoni)*\n- [`e82a30b`](https://github.com/tobymao/sqlglot/commit/e82a30b6563547daea0bb087e1b6b5bf3b0532d3) - **postgres**: don't generate SchemaCommentProperty *(PR [#3364](https://github.com/tobymao/sqlglot/pull/3364) by [@georgesittas](https://github.com/georgesittas))*\n- [`47dc52c`](https://github.com/tobymao/sqlglot/commit/47dc52c99ea50b55d08f2b57885eebbd577b8b46) - **mysql**: convert epoch extraction into UNIX_TIMESTAMP call *(PR [#3369](https://github.com/tobymao/sqlglot/pull/3369) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3368](https://github.com/tobymao/sqlglot/issues/3368) opened by [@FaizelK](https://github.com/FaizelK)*\n- [`b8f0979`](https://github.com/tobymao/sqlglot/commit/b8f0979537cf3ad9ef83f2c30d6cfb23cd4d2d1e) - **mysql**: generate GROUP_CONCAT for ArrayAgg *(PR [#3370](https://github.com/tobymao/sqlglot/pull/3370) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3368](https://github.com/tobymao/sqlglot/issues/3368) opened by [@FaizelK](https://github.com/FaizelK)*\n\n### :recycle: Refactors\n- [`b928f54`](https://github.com/tobymao/sqlglot/commit/b928f542a81d299311d01bd8f1eb762a13adf5c8) - don't mutate the AST when creating DDL scopes *(PR [#3371](https://github.com/tobymao/sqlglot/pull/3371) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.12.1] - 2024-04-25\n### :wrench: Chores\n- [`719d394`](https://github.com/tobymao/sqlglot/commit/719d3949b75bcdac0d19b86d7398c5d9c4b5bdc3) - add a test for quoted aliases *(commit by [@tobymao](https://github.com/tobymao))*\n- [`6d7a9f4`](https://github.com/tobymao/sqlglot/commit/6d7a9f4ec0cd87efe19128dc9e55967172bf324e) - use unknown token types *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.12.0] - 2024-04-25\n### :boom: BREAKING CHANGES\n- due to [`c5ce47b`](https://github.com/tobymao/sqlglot/commit/c5ce47ba7863e0c536e076ea78ec27cb52324493) - Combine aggregate functions with orderby from WITHIN GROUP *(PR [#3352](https://github.com/tobymao/sqlglot/pull/3352) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Combine aggregate functions with orderby from WITHIN GROUP (#3352)\n\n\n### :sparkles: New Features\n- [`80793cc`](https://github.com/tobymao/sqlglot/commit/80793ccdb52b1975d93c64a20380047bc6cf4479) - parse (a,) as a tuple instead of a paren *(PR [#3341](https://github.com/tobymao/sqlglot/pull/3341) by [@georgesittas](https://github.com/georgesittas))*\n- [`b3826f8`](https://github.com/tobymao/sqlglot/commit/b3826f873dc81adbfe4fbe35e83b71f4c37c3b16) - allow comments to be attached for identifiers used in definitions *(PR [#3340](https://github.com/tobymao/sqlglot/pull/3340) by [@georgesittas](https://github.com/georgesittas))*\n- [`ce7d893`](https://github.com/tobymao/sqlglot/commit/ce7d893c7e0d627b94e9225a06b83b863bd61a40) - **clickhouse**: Parse window functions in ParameterizedAggFuncs *(PR [#3347](https://github.com/tobymao/sqlglot/pull/3347) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3344](https://github.com/tobymao/sqlglot/issues/3344) opened by [@alesk](https://github.com/alesk)*\n\n### :bug: Bug Fixes\n- [`0e54975`](https://github.com/tobymao/sqlglot/commit/0e54975bf27f8d765378f47872d372ba3817088e) - **tsql**: only use target table name when generating sp_rename *(PR [#3342](https://github.com/tobymao/sqlglot/pull/3342) by [@georgesittas](https://github.com/georgesittas))*\n- [`52bdd0c`](https://github.com/tobymao/sqlglot/commit/52bdd0ce104606c520ad4edf8c781ccc502d5a0e) - **tsql**: Convert TIMESTAMP to ROWVERSION, transpile both to BINARY *(PR [#3348](https://github.com/tobymao/sqlglot/pull/3348) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3345](https://github.com/tobymao/sqlglot/issues/3345) opened by [@aersam](https://github.com/aersam)*\n- [`c5ce47b`](https://github.com/tobymao/sqlglot/commit/c5ce47ba7863e0c536e076ea78ec27cb52324493) - **duckdb**: Combine aggregate functions with orderby from WITHIN GROUP *(PR [#3352](https://github.com/tobymao/sqlglot/pull/3352) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3350](https://github.com/tobymao/sqlglot/issues/3350) opened by [@btyuhas](https://github.com/btyuhas)*\n\n### :wrench: Chores\n- [`eae2f6b`](https://github.com/tobymao/sqlglot/commit/eae2f6be8f13eb44c404dc638ec50d08f203b094) - update sqlglot logo *(commit by [@tobymao](https://github.com/tobymao))*\n- [`fb9a7ad`](https://github.com/tobymao/sqlglot/commit/fb9a7ad8f2af98a248e4576677b7b615b9d4c3e7) - copy png *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.11.2] - 2024-04-19\n### :bug: Bug Fixes\n- [`68595eb`](https://github.com/tobymao/sqlglot/commit/68595eba02ca9f3a01359566104b4315a313ec0a) - edge case *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.11.1] - 2024-04-19\n### :bug: Bug Fixes\n- [`9cf6f4e`](https://github.com/tobymao/sqlglot/commit/9cf6f4e49208d5a41bca1bd437d31b1ed894e6eb) - don't allow any_token on reserved keywords *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.11.0] - 2024-04-19\n### :boom: BREAKING CHANGES\n- due to [`290e408`](https://github.com/tobymao/sqlglot/commit/290e408ccf0d0eeec767d4b58bc1293878a3a3ae) - Preserve DPipe in simplify_concat *(PR [#3317](https://github.com/tobymao/sqlglot/pull/3317) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Preserve DPipe in simplify_concat (#3317)\n\n- due to [`83cff79`](https://github.com/tobymao/sqlglot/commit/83cff79633225fe3d8606ec3a5a9e8c1081edd0c) - add comprehensive reserved keywords for presto and redshift *(PR [#3322](https://github.com/tobymao/sqlglot/pull/3322) by [@tobymao](https://github.com/tobymao))*:\n\n  add comprehensive reserved keywords for presto and redshift (#3322)\n\n- due to [`61f5b12`](https://github.com/tobymao/sqlglot/commit/61f5b1274cc1f3d68f0f16d4b3efcdc082f67257) - Introduce partition in exp.Table *(PR [#3327](https://github.com/tobymao/sqlglot/pull/3327) by [@VaggelisD](https://github.com/VaggelisD))*:\n\n  Introduce partition in exp.Table (#3327)\n\n- due to [`1832ff1`](https://github.com/tobymao/sqlglot/commit/1832ff130da06ec905835583f101c031dc4faf1d) - dynamic styling for inline arrays *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  dynamic styling for inline arrays\n\n- due to [`5fb7f5b`](https://github.com/tobymao/sqlglot/commit/5fb7f5b21bc441af8d6fabaff7c3d542d96d3811) - dont double indent comments *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  dont double indent comments\n\n\n### :sparkles: New Features\n- [`4f1691a`](https://github.com/tobymao/sqlglot/commit/4f1691a221f3d7395774f8c131a656a3ec531534) - allow qualify to also annotate on the fly for unnest support *(PR [#3316](https://github.com/tobymao/sqlglot/pull/3316) by [@tobymao](https://github.com/tobymao))*\n- [`83cff79`](https://github.com/tobymao/sqlglot/commit/83cff79633225fe3d8606ec3a5a9e8c1081edd0c) - add comprehensive reserved keywords for presto and redshift *(PR [#3322](https://github.com/tobymao/sqlglot/pull/3322) by [@tobymao](https://github.com/tobymao))*\n- [`ef3311a`](https://github.com/tobymao/sqlglot/commit/ef3311a8ece67e6300e5ff121660dea8cfd80480) - **hive**: Add 'STORED AS' option in INSERT DIRECTORY *(PR [#3326](https://github.com/tobymao/sqlglot/pull/3326) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3320](https://github.com/tobymao/sqlglot/issues/3320) opened by [@bkyryliuk](https://github.com/bkyryliuk)*\n- [`7f9cb2d`](https://github.com/tobymao/sqlglot/commit/7f9cb2d2fe2c09e94f9dbaafcc0a808428b5b21c) - **clickhouse**: Add support for DATE_FORMAT / formatDateTime *(PR [#3329](https://github.com/tobymao/sqlglot/pull/3329) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3324](https://github.com/tobymao/sqlglot/issues/3324) opened by [@PaienNate](https://github.com/PaienNate)*\n- [`61f5b12`](https://github.com/tobymao/sqlglot/commit/61f5b1274cc1f3d68f0f16d4b3efcdc082f67257) - Introduce partition in exp.Table *(PR [#3327](https://github.com/tobymao/sqlglot/pull/3327) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3319](https://github.com/tobymao/sqlglot/issues/3319) opened by [@bkyryliuk](https://github.com/bkyryliuk)*\n- [`31744b2`](https://github.com/tobymao/sqlglot/commit/31744b26ed97c12fd3cb1e3a0661695fac4c0736) - **prql**: handle NULL *(PR [#3331](https://github.com/tobymao/sqlglot/pull/3331) by [@fool1280](https://github.com/fool1280))*\n- [`1105044`](https://github.com/tobymao/sqlglot/commit/1105044fa8c5af8269eeddfe8e160f0c52de913c) - **tsql**: add alter table rename *(commit by [@tobymao](https://github.com/tobymao))*\n- [`1832ff1`](https://github.com/tobymao/sqlglot/commit/1832ff130da06ec905835583f101c031dc4faf1d) - dynamic styling for inline arrays *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`ef84f17`](https://github.com/tobymao/sqlglot/commit/ef84f177b7d76b7bf43d6ef38a89cfbe47f4e13b) - **optimizer**: don't simplify parentheses when parent is SubqueryPredicate *(PR [#3315](https://github.com/tobymao/sqlglot/pull/3315) by [@georgesittas](https://github.com/georgesittas))*\n- [`290e408`](https://github.com/tobymao/sqlglot/commit/290e408ccf0d0eeec767d4b58bc1293878a3a3ae) - **optimizer**: Preserve DPipe in simplify_concat *(PR [#3317](https://github.com/tobymao/sqlglot/pull/3317) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#2439](https://github.com/TobikoData/sqlmesh/issues/2439) opened by [@ma1f](https://github.com/ma1f)*\n- [`52b957a`](https://github.com/tobymao/sqlglot/commit/52b957a0691b09dc43628703b9b3633d7238df5b) - transform eliminate_qualify on generated columns *(PR [#3307](https://github.com/tobymao/sqlglot/pull/3307) by [@viplazylmht](https://github.com/viplazylmht))*\n- [`eb8d7b8`](https://github.com/tobymao/sqlglot/commit/eb8d7b80c74850d791ac51a117ed5381b3431b3b) - remove e*s mapping because it's not equivalent to %f *(commit by [@tobymao](https://github.com/tobymao))*\n- [`9de1494`](https://github.com/tobymao/sqlglot/commit/9de1494899bfc9ad13270a38054a8deab2fc926e) - allow bigquery udf with resered keyword closes [#3332](https://github.com/tobymao/sqlglot/pull/3332) *(PR [#3333](https://github.com/tobymao/sqlglot/pull/3333) by [@tobymao](https://github.com/tobymao))*\n- [`e2b6213`](https://github.com/tobymao/sqlglot/commit/e2b62133add5a39e3a2df1d0c8e634fcab3487ff) - don't double comment unions *(commit by [@tobymao](https://github.com/tobymao))*\n- [`5fb7f5b`](https://github.com/tobymao/sqlglot/commit/5fb7f5b21bc441af8d6fabaff7c3d542d96d3811) - dont double indent comments *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`81b28c2`](https://github.com/tobymao/sqlglot/commit/81b28c2a7882b642069afb80cee16991542f84e3) - fix tests with latest duckdb *(commit by [@tobymao](https://github.com/tobymao))*\n- [`17f7eaf`](https://github.com/tobymao/sqlglot/commit/17f7eaff564790b1fe7faa414143accf362f550e) - add test *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.10.0] - 2024-04-12\n### :bug: Bug Fixes\n- [`506760d`](https://github.com/tobymao/sqlglot/commit/506760d2597779e287be4fffdeb1b375994320b1) - **redshift**: unqualify unnest columns *(PR [#3314](https://github.com/tobymao/sqlglot/pull/3314) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`0450521`](https://github.com/tobymao/sqlglot/commit/0450521a4470633be26ad5399247d5c9083e2afc) - get rid of 1st projection pad for leading comma formatting *(PR [#3308](https://github.com/tobymao/sqlglot/pull/3308) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.9.0] - 2024-04-12\n### :boom: BREAKING CHANGES\n- due to [`32cdc36`](https://github.com/tobymao/sqlglot/commit/32cdc3635b22e3e5d0cd5caf5a6ad171ca7c34fb) - allow unions to be limited directly and stop subquerying since… *(PR [#3301](https://github.com/tobymao/sqlglot/pull/3301) by [@tobymao](https://github.com/tobymao))*:\n\n  allow unions to be limited directly and stop subquerying since… (#3301)\n\n- due to [`3c97d34`](https://github.com/tobymao/sqlglot/commit/3c97d3437ea573fd3764eab05ed619353fced580) - parse right-hand side of <value> IN (<query>) as a Subquery *(PR [#3304](https://github.com/tobymao/sqlglot/pull/3304) by [@georgesittas](https://github.com/georgesittas))*:\n\n  parse right-hand side of <value> IN (<query>) as a Subquery (#3304)\n\n- due to [`75e0c69`](https://github.com/tobymao/sqlglot/commit/75e0c69e33922168fcadb4e457ae93815bf533e1) - cast less aggressively *(PR [#3302](https://github.com/tobymao/sqlglot/pull/3302) by [@georgesittas](https://github.com/georgesittas))*:\n\n  cast less aggressively (#3302)\n\n\n### :sparkles: New Features\n- [`a721923`](https://github.com/tobymao/sqlglot/commit/a72192306c8fad6253ad9a03661edcfaa15757c7) - **prql**: Add support for SORT *(PR [#3297](https://github.com/tobymao/sqlglot/pull/3297) by [@fool1280](https://github.com/fool1280))*\n- [`2ea438b`](https://github.com/tobymao/sqlglot/commit/2ea438b89f76a357390d657fe3f9e01d2a79e7e4) - is_negative helper method *(commit by [@tobymao](https://github.com/tobymao))*\n- [`b28cd89`](https://github.com/tobymao/sqlglot/commit/b28cd89823a38f3a90c57344a44719364d66d723) - improve transpilation of datetime functions to Teradata *(PR [#3295](https://github.com/tobymao/sqlglot/pull/3295) by [@maureen-daum](https://github.com/maureen-daum))*\n- [`32cdc36`](https://github.com/tobymao/sqlglot/commit/32cdc3635b22e3e5d0cd5caf5a6ad171ca7c34fb) - allow unions to be limited directly and stop subquerying since… *(PR [#3301](https://github.com/tobymao/sqlglot/pull/3301) by [@tobymao](https://github.com/tobymao))*\n  - :arrow_lower_right: *addresses issue [#3300](https://github.com/tobymao/sqlglot/issues/3300) opened by [@williaster](https://github.com/williaster)*\n- [`1bc51df`](https://github.com/tobymao/sqlglot/commit/1bc51dfa9d8fd5d7dbea42d3d55aa1db66776ce5) - **teradata**: handle transpile of quarter function *(PR [#3303](https://github.com/tobymao/sqlglot/pull/3303) by [@maureen-daum](https://github.com/maureen-daum))*\n- [`4790414`](https://github.com/tobymao/sqlglot/commit/4790414b887b347cb94d810eeb3fe4713970984e) - **prql**: Handle DESC with sort *(PR [#3299](https://github.com/tobymao/sqlglot/pull/3299) by [@fool1280](https://github.com/fool1280))*\n\n### :bug: Bug Fixes\n- [`3c97d34`](https://github.com/tobymao/sqlglot/commit/3c97d3437ea573fd3764eab05ed619353fced580) - parse right-hand side of <value> IN (<query>) as a Subquery *(PR [#3304](https://github.com/tobymao/sqlglot/pull/3304) by [@georgesittas](https://github.com/georgesittas))*\n- [`75e0c69`](https://github.com/tobymao/sqlglot/commit/75e0c69e33922168fcadb4e457ae93815bf533e1) - cast less aggressively *(PR [#3302](https://github.com/tobymao/sqlglot/pull/3302) by [@georgesittas](https://github.com/georgesittas))*\n- [`d3472c6`](https://github.com/tobymao/sqlglot/commit/d3472c664fdfb7c9cfa9a54c6b0491b605cf4913) - Add postgres transpilation for TIME_TO_UNIX *(PR [#3305](https://github.com/tobymao/sqlglot/pull/3305) by [@crericha](https://github.com/crericha))*\n- [`2224881`](https://github.com/tobymao/sqlglot/commit/2224881ed378abe075ebcd3bfbc3eee901f89d71) - case when / if should ignore null types *(commit by [@tobymao](https://github.com/tobymao))*\n- [`5b2feb7`](https://github.com/tobymao/sqlglot/commit/5b2feb760ecd4c8ee64f8c464518e7e874f9b9bb) - allow unnesting to bring struct fields into scope *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`b523bb6`](https://github.com/tobymao/sqlglot/commit/b523bb630b28866ebab581d43e99f0b2b821ec12) - cleanup teradata to simplify first *(commit by [@tobymao](https://github.com/tobymao))*\n- [`6f73186`](https://github.com/tobymao/sqlglot/commit/6f73186681e8eb9f100a1fe4104c82cbae9d0f61) - refactor to use inline lambda *(commit by [@tobymao](https://github.com/tobymao))*\n- [`6b21bba`](https://github.com/tobymao/sqlglot/commit/6b21bba378e411797a57d3de8bd06d3efb6afa8c) - make test runnable *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.8.2] - 2024-04-10\n### :sparkles: New Features\n- [`eabb708`](https://github.com/tobymao/sqlglot/commit/eabb708db9ce7255f947542d57c31a6c93103985) - **prql**: add filter, set operations  *(PR [#3291](https://github.com/tobymao/sqlglot/pull/3291) by [@fool1280](https://github.com/fool1280))*\n\n### :bug: Bug Fixes\n- [`94c188d`](https://github.com/tobymao/sqlglot/commit/94c188d4920fd03e978253ed98711de259d6acb2) - **optimizer**: propagate recursive CTE source to children scopes early *(PR [#3294](https://github.com/tobymao/sqlglot/pull/3294) by [@georgesittas](https://github.com/georgesittas))*\n- [`281db61`](https://github.com/tobymao/sqlglot/commit/281db61009ee01d10690dcc1f2039062b2a1a58c) - replace fully qualified columns with generated table aliases since they become invalid *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.8.1] - 2024-04-09\n### :sparkles: New Features\n- [`942856d`](https://github.com/tobymao/sqlglot/commit/942856d1bdae43b13114a15a66c84467a0f90e75) - **postgres**: add COMMENT ON MATERIALIZED VIEW *(PR [#3293](https://github.com/tobymao/sqlglot/pull/3293) by [@l-vincent-l](https://github.com/l-vincent-l))*\n\n### :bug: Bug Fixes\n- [`fd24b27`](https://github.com/tobymao/sqlglot/commit/fd24b2779fa962077e84d234b6821e67f3815551) - make exp.to_column more lenient *(PR [#3292](https://github.com/tobymao/sqlglot/pull/3292) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.8.0] - 2024-04-08\n### :boom: BREAKING CHANGES\n- due to [`6bba030`](https://github.com/tobymao/sqlglot/commit/6bba0308b590aed73e454c2c40d600c670e0ad7f) - transpile map retrieval to duckdb, transpile TRY_ELEMENT_AT *(PR [#3277](https://github.com/tobymao/sqlglot/pull/3277) by [@georgesittas](https://github.com/georgesittas))*:\n\n  transpile map retrieval to duckdb, transpile TRY_ELEMENT_AT (#3277)\n\n- due to [`02218fc`](https://github.com/tobymao/sqlglot/commit/02218fc4f75d22487976572f51bd131170a728e5) - allow to_column to properly parse quoted column paths, make types simpler *(PR [#3289](https://github.com/tobymao/sqlglot/pull/3289) by [@tobymao](https://github.com/tobymao))*:\n\n  allow to_column to properly parse quoted column paths, make types simpler (#3289)\n\n\n### :sparkles: New Features\n- [`08222c2`](https://github.com/tobymao/sqlglot/commit/08222c2c626353be108347b95644660fe04dfcd1) - **clickhouse**: add support for MATERIALIZED, EPHEMERAL column constraints *(PR [#3275](https://github.com/tobymao/sqlglot/pull/3275) by [@pkit](https://github.com/pkit))*\n- [`6bba030`](https://github.com/tobymao/sqlglot/commit/6bba0308b590aed73e454c2c40d600c670e0ad7f) - transpile map retrieval to duckdb, transpile TRY_ELEMENT_AT *(PR [#3277](https://github.com/tobymao/sqlglot/pull/3277) by [@georgesittas](https://github.com/georgesittas))*\n- [`1726923`](https://github.com/tobymao/sqlglot/commit/17269232ea7f1f2ebf6daae7a49d55ccadc31798) - desc history databricks closes [#3280](https://github.com/tobymao/sqlglot/pull/3280) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`0690cbc`](https://github.com/tobymao/sqlglot/commit/0690cbc14f023589f38bcceea443642c5a9cc586) - **snowflake**: FINAL/RUNNING keywords in MATCH_RECOGNIZE MEASURES *(PR [#3284](https://github.com/tobymao/sqlglot/pull/3284) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3282](https://github.com/tobymao/sqlglot/issues/3282) opened by [@galunto](https://github.com/galunto)*\n- [`1311ba3`](https://github.com/tobymao/sqlglot/commit/1311ba3da3b5e05f148d602885fcc34cc73c3c6f) - **presto**: add support for DISTINCT / ALL after GROUP BY *(PR [#3290](https://github.com/tobymao/sqlglot/pull/3290) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3286](https://github.com/tobymao/sqlglot/issues/3286) opened by [@bkyryliuk](https://github.com/bkyryliuk)*\n\n### :bug: Bug Fixes\n- [`f65d812`](https://github.com/tobymao/sqlglot/commit/f65d8129b0ae887ff882cf5117f04f64b7e7db6f) - move EphemeralColumnConstraint generation to base generator *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`6d1c44d`](https://github.com/tobymao/sqlglot/commit/6d1c44d5b7ac9e3e929de84a761906ad42a07aee) - **optimizer**: unnest union subqueries *(PR [#3278](https://github.com/tobymao/sqlglot/pull/3278) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3276](https://github.com/tobymao/sqlglot/issues/3276) opened by [@khabiri](https://github.com/khabiri)*\n- [`a37d231`](https://github.com/tobymao/sqlglot/commit/a37d231200af1dc99fc45fc40627671ee82f6d5e) - **presto**: allow qualify to be an alias closes [#3287](https://github.com/tobymao/sqlglot/pull/3287) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`02218fc`](https://github.com/tobymao/sqlglot/commit/02218fc4f75d22487976572f51bd131170a728e5) - allow to_column to properly parse quoted column paths, make types simpler *(PR [#3289](https://github.com/tobymao/sqlglot/pull/3289) by [@tobymao](https://github.com/tobymao))*\n- [`fe0eb57`](https://github.com/tobymao/sqlglot/commit/fe0eb57feecce413e3e2992db73424c8cf585599) - pass quoted to the identifier *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`c793629`](https://github.com/tobymao/sqlglot/commit/c79362953a5bf12278f861b8b5d39e6847b22e3b) - another test case *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.7.0] - 2024-04-04\n### :sparkles: New Features\n- [`e33fb01`](https://github.com/tobymao/sqlglot/commit/e33fb012b47892fab03fab7de896495951f23174) - **prql**: Add support for TAKE  *(PR [#3258](https://github.com/tobymao/sqlglot/pull/3258) by [@fool1280](https://github.com/fool1280))*\n\n### :bug: Bug Fixes\n- [`19302ab`](https://github.com/tobymao/sqlglot/commit/19302abe17a6828e1928075de45c1e2a4f3008ce) - **optimizer**: preserve the original type when creating a date literal *(PR [#3273](https://github.com/tobymao/sqlglot/pull/3273) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.6.4] - 2024-04-03\n### :bug: Bug Fixes\n- [`803fc9e`](https://github.com/tobymao/sqlglot/commit/803fc9e8f245e48e8b0e13760c5fa60cd596a464) - allow placeholders in units closes [#3265](https://github.com/tobymao/sqlglot/pull/3265) *(PR [#3267](https://github.com/tobymao/sqlglot/pull/3267) by [@tobymao](https://github.com/tobymao))*\n- [`64ae85b`](https://github.com/tobymao/sqlglot/commit/64ae85ba1344b293ba01dfa300d100ff144cdd7b) - nested cte ordering closes [#3266](https://github.com/tobymao/sqlglot/pull/3266) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`09287d9`](https://github.com/tobymao/sqlglot/commit/09287d9b2a39d2476d1f72880f9d2dccfdb210ec) - amend interval unit parsing regression *(PR [#3269](https://github.com/tobymao/sqlglot/pull/3269) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3268](https://github.com/tobymao/sqlglot/issues/3268) opened by [@LilyFoote](https://github.com/LilyFoote)*\n- [`bc26e84`](https://github.com/tobymao/sqlglot/commit/bc26e840d171dd03e6053f22ecd785d59cbd4f80) - **optimizer**: tweaks to date simplification *(PR [#3270](https://github.com/tobymao/sqlglot/pull/3270) by [@barakalon](https://github.com/barakalon))*\n\n\n## [v23.6.0] - 2024-04-02\n### :wrench: Chores\n- [`4eec748`](https://github.com/tobymao/sqlglot/commit/4eec748d7fd0c73d9593cb3da2b9ebc1d2440436) - deploy sqlglot and then sqlglotrs *(PR [#3264](https://github.com/tobymao/sqlglot/pull/3264) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.4.0] - 2024-04-02\n### :boom: BREAKING CHANGES\n- due to [`e148fe1`](https://github.com/tobymao/sqlglot/commit/e148fe1ace1fe647369c14f2649f428307686a2f) - describe formatted closes [#3244](https://github.com/tobymao/sqlglot/pull/3244) *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  describe formatted closes #3244\n\n- due to [`2c359e7`](https://github.com/tobymao/sqlglot/commit/2c359e790a58e4df9008282401a5578d3ce9d3a4) - properly transpile escape sequences *(PR [#3256](https://github.com/tobymao/sqlglot/pull/3256) by [@georgesittas](https://github.com/georgesittas))*:\n\n  properly transpile escape sequences (#3256)\n\n- due to [`9787567`](https://github.com/tobymao/sqlglot/commit/978756783b639e174f3f614f3e39382fef296640) - bump sqlglotrs to 0.2.0 *(commit by [@georgesittas](https://github.com/georgesittas))*:\n\n  bump sqlglotrs to 0.2.0\n\n\n### :sparkles: New Features\n- [`8dba8e2`](https://github.com/tobymao/sqlglot/commit/8dba8e2508f04ccdf9b10eaa8a456478190a53a5) - **optimizer**: Support for small integer CAST elimination *(PR [#3234](https://github.com/tobymao/sqlglot/pull/3234) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3229](https://github.com/tobymao/sqlglot/issues/3229) opened by [@NickCrews](https://github.com/NickCrews)*\n- [`e148fe1`](https://github.com/tobymao/sqlglot/commit/e148fe1ace1fe647369c14f2649f428307686a2f) - describe formatted closes [#3244](https://github.com/tobymao/sqlglot/pull/3244) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`a48d7eb`](https://github.com/tobymao/sqlglot/commit/a48d7eb9f3d1f9b3d1ffb9b3ec99b1024b7c3da9) - allow non func hints closes [#3248](https://github.com/tobymao/sqlglot/pull/3248) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d90ec95`](https://github.com/tobymao/sqlglot/commit/d90ec95001a9747d6066d1872c5a9402e2837f62) - add conversion of named tuples and classes to structs *(PR [#3245](https://github.com/tobymao/sqlglot/pull/3245) by [@tobymao](https://github.com/tobymao))*\n- [`f88640b`](https://github.com/tobymao/sqlglot/commit/f88640b8df22e29ad2fa845b580cf78ad4fb2262) - **clickhouse**: CREATE TABLE computed columns, column compression, index *(PR [#3252](https://github.com/tobymao/sqlglot/pull/3252) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3243](https://github.com/tobymao/sqlglot/issues/3243) opened by [@lksv](https://github.com/lksv)*\n- [`a64ec1b`](https://github.com/tobymao/sqlglot/commit/a64ec1bf60fda00e6dd7122a338c6dac80d005e4) - **snowflake**: MATCH_CONDITION in ASOF JOIN *(PR [#3255](https://github.com/tobymao/sqlglot/pull/3255) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3254](https://github.com/tobymao/sqlglot/issues/3254) opened by [@Bilbottom](https://github.com/Bilbottom)*\n\n### :bug: Bug Fixes\n- [`a630c50`](https://github.com/tobymao/sqlglot/commit/a630c50737bb9deb6f44e1afd374b113612a1d24) - allow interval spans closes [#3246](https://github.com/tobymao/sqlglot/pull/3246) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`28c5ee7`](https://github.com/tobymao/sqlglot/commit/28c5ee7243af9fb8aa5abf2d5d36d6fa4ef47681) - **mysql**: Duplicate parsing of ENGINE_ATTRIBUTE *(PR [#3253](https://github.com/tobymao/sqlglot/pull/3253) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`2c359e7`](https://github.com/tobymao/sqlglot/commit/2c359e790a58e4df9008282401a5578d3ce9d3a4) - properly transpile escape sequences *(PR [#3256](https://github.com/tobymao/sqlglot/pull/3256) by [@georgesittas](https://github.com/georgesittas))*\n- [`6badfd1`](https://github.com/tobymao/sqlglot/commit/6badfd17b416380a4077f2ef48f1efcbed3c78d3) - Fix STRPOS for Presto & Trino *(PR [#3261](https://github.com/tobymao/sqlglot/pull/3261) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3259](https://github.com/tobymao/sqlglot/issues/3259) opened by [@amitgilad3](https://github.com/amitgilad3)*\n\n### :wrench: Chores\n- [`9787567`](https://github.com/tobymao/sqlglot/commit/978756783b639e174f3f614f3e39382fef296640) - bump sqlglotrs to 0.2.0 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.3.0] - 2024-03-29\n### :boom: BREAKING CHANGES\n- due to [`0919be5`](https://github.com/tobymao/sqlglot/commit/0919be5eea7aba175e173dbfc0b6547e5c9473a8) - StrToUnix Hive parsing, Presto generation fixes *(PR [#3225](https://github.com/tobymao/sqlglot/pull/3225) by [@georgesittas](https://github.com/georgesittas))*:\n\n  StrToUnix Hive parsing, Presto generation fixes (#3225)\n\n- due to [`163c85c`](https://github.com/tobymao/sqlglot/commit/163c85c8ed327150a6e5c79f1a4b52a8848d4408) - convert dt with isoformat sep space for better compat, trino doesnt accept T *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  convert dt with isoformat sep space for better compat, trino doesnt accept T\n\n\n### :sparkles: New Features\n- [`59f1d13`](https://github.com/tobymao/sqlglot/commit/59f1d13bc5e37ebe6636b05e0381facc9725f7b0) - **oracle**: Support for CONNECT BY [NOCYCLE] *(PR [#3238](https://github.com/tobymao/sqlglot/pull/3238) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3237](https://github.com/tobymao/sqlglot/issues/3237) opened by [@Hal-H2Apps](https://github.com/Hal-H2Apps)*\n- [`12563ae`](https://github.com/tobymao/sqlglot/commit/12563ae0645487d5e63343224e1016cce4be447b) - mvp for transpling sqlite's STRFTIME *(PR [#3242](https://github.com/tobymao/sqlglot/pull/3242) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3240](https://github.com/tobymao/sqlglot/issues/3240) opened by [@markhalonen](https://github.com/markhalonen)*\n\n### :bug: Bug Fixes\n- [`0919be5`](https://github.com/tobymao/sqlglot/commit/0919be5eea7aba175e173dbfc0b6547e5c9473a8) - StrToUnix Hive parsing, Presto generation fixes *(PR [#3225](https://github.com/tobymao/sqlglot/pull/3225) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3221](https://github.com/tobymao/sqlglot/issues/3221) opened by [@luhea](https://github.com/luhea)*\n- [`163c85c`](https://github.com/tobymao/sqlglot/commit/163c85c8ed327150a6e5c79f1a4b52a8848d4408) - convert dt with isoformat sep space for better compat, trino doesnt accept T *(commit by [@tobymao](https://github.com/tobymao))*\n- [`555647d`](https://github.com/tobymao/sqlglot/commit/555647d5541c2e52b40d098ee42f6454518e8401) - make property value parsing more lenient *(PR [#3230](https://github.com/tobymao/sqlglot/pull/3230) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3228](https://github.com/tobymao/sqlglot/issues/3228) opened by [@hsheth2](https://github.com/hsheth2)*\n- [`8325039`](https://github.com/tobymao/sqlglot/commit/83250398c7804863a6b3f339305600df39515ccc) - **duckdb**: wrap columns inside of INTERVAL expressions *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`fd5783f`](https://github.com/tobymao/sqlglot/commit/fd5783f34cb0cb7052477f25a9847a5efd61c04f) - don't evaluate Rand twice when ordering by it *(PR [#3233](https://github.com/tobymao/sqlglot/pull/3233) by [@georgesittas](https://github.com/georgesittas))*\n- [`b097da5`](https://github.com/tobymao/sqlglot/commit/b097da5a624fa467830464427ec57bf3b303de3f) - index error when comment sql is none *(commit by [@tobymao](https://github.com/tobymao))*\n- [`bf94ce3`](https://github.com/tobymao/sqlglot/commit/bf94ce317497ab92e9fe0562b3034f3482601072) - > 1 nested joins closes [#3231](https://github.com/tobymao/sqlglot/pull/3231) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`2a3a5cd`](https://github.com/tobymao/sqlglot/commit/2a3a5cdcffe39d42153b3e960a580d084a27c0eb) - properly parse/generate duckdb MAP {..} syntax, annotate MAPs *(PR [#3241](https://github.com/tobymao/sqlglot/pull/3241) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`647611e`](https://github.com/tobymao/sqlglot/commit/647611e16bdb5ecfc2eec30111cc6689200836b7) - only set vars with necessary *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.1.0] - 2024-03-26\n### :boom: BREAKING CHANGES\n- due to [`71b82b4`](https://github.com/tobymao/sqlglot/commit/71b82b424b9c336072b011785a0e3e9650ae1380) - allow transformations that mutate the visited node's parent *(PR [#3182](https://github.com/tobymao/sqlglot/pull/3182) by [@georgesittas](https://github.com/georgesittas))*:\n\n  allow transformations that mutate the visited node's parent (#3182)\n\n\n### :sparkles: New Features\n- [`c19878a`](https://github.com/tobymao/sqlglot/commit/c19878a329078ce6ebfbb4337316ff5e43b8c924) - transpile Snowflake's ADDTIME *(PR [#3180](https://github.com/tobymao/sqlglot/pull/3180) by [@georgesittas](https://github.com/georgesittas))*\n- [`66e2e49`](https://github.com/tobymao/sqlglot/commit/66e2e497626a77540b9addd35f2edb287c7b62fe) - improve lineage perf *(commit by [@tobymao](https://github.com/tobymao))*\n- [`ad23608`](https://github.com/tobymao/sqlglot/commit/ad23608f9f3724f0c35e5d517bba51f77a84f6cb) - **mysql**: Parse MODIFY COLUMN *(PR [#3189](https://github.com/tobymao/sqlglot/pull/3189) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3186](https://github.com/tobymao/sqlglot/issues/3186) opened by [@kosti-hokkanen-supermetrics](https://github.com/kosti-hokkanen-supermetrics)*\n- [`a18444d`](https://github.com/tobymao/sqlglot/commit/a18444dbd7ccfc05b189dcb2005c85a1048cc8de) - add expressions for CORR, COVAR_SAMP, COVAR_POP *(PR [#3193](https://github.com/tobymao/sqlglot/pull/3193) by [@ttzhou](https://github.com/ttzhou))*\n- [`3620b99`](https://github.com/tobymao/sqlglot/commit/3620b9974c28df7d4d189ebd5fdcb675f41a275d) - add support for converting `bytes` to sqlglot AST *(PR [#3198](https://github.com/tobymao/sqlglot/pull/3198) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3195](https://github.com/tobymao/sqlglot/issues/3195) opened by [@aersam](https://github.com/aersam)*\n- [`648c819`](https://github.com/tobymao/sqlglot/commit/648c819071082f7a1f2f6587336ae765d4915034) - redshift starts with support *(PR [#3194](https://github.com/tobymao/sqlglot/pull/3194) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`c355a4a`](https://github.com/tobymao/sqlglot/commit/c355a4a821c7eaf76df510020d825a9f326068de) - **tsql**: add support for WITH <view_attribute> in view DDL *(PR [#3203](https://github.com/tobymao/sqlglot/pull/3203) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3202](https://github.com/tobymao/sqlglot/issues/3202) opened by [@mr-miles](https://github.com/mr-miles)*\n- [`8622eb2`](https://github.com/tobymao/sqlglot/commit/8622eb21c89a0d7569e27b3c739592cb96946a3a) - **duckdb**: add support for heredoc string syntax *(PR [#3212](https://github.com/tobymao/sqlglot/pull/3212) by [@georgesittas](https://github.com/georgesittas))*\n- [`b50dc5e`](https://github.com/tobymao/sqlglot/commit/b50dc5ecc7d29bce43229d050da8c4e37951853c) - Support for MySQL & Redshift UnixTotime *(PR [#3223](https://github.com/tobymao/sqlglot/pull/3223) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3214](https://github.com/tobymao/sqlglot/issues/3214) opened by [@exgalibas](https://github.com/exgalibas)*\n- [`2f6a2f1`](https://github.com/tobymao/sqlglot/commit/2f6a2f13bbd40f3d5348b0ed1b8cf6736ef9d1c5) - **optimizer**: Support for UNION BY NAME *(PR [#3224](https://github.com/tobymao/sqlglot/pull/3224) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3222](https://github.com/tobymao/sqlglot/issues/3222) opened by [@yiyuanliu](https://github.com/yiyuanliu)*\n\n### :bug: Bug Fixes\n- [`71b82b4`](https://github.com/tobymao/sqlglot/commit/71b82b424b9c336072b011785a0e3e9650ae1380) - allow transformations that mutate the visited node's parent *(PR [#3182](https://github.com/tobymao/sqlglot/pull/3182) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3181](https://github.com/tobymao/sqlglot/issues/3181) opened by [@l-vincent-l](https://github.com/l-vincent-l)*\n- [`6827edd`](https://github.com/tobymao/sqlglot/commit/6827edd108bbc6ecfcc0f03495f00c08022efb3b) - **postgres**: Fix ARROW/DARROW column operators *(PR [#3191](https://github.com/tobymao/sqlglot/pull/3191) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3185](https://github.com/tobymao/sqlglot/issues/3185) opened by [@ZipBrandon](https://github.com/ZipBrandon)*\n- [`0dd9ba5`](https://github.com/tobymao/sqlglot/commit/0dd9ba5ef57d29b6406a5d2a7e381eb6e6f56221) - Fix backtracking through try/catch exceptions *(PR [#3190](https://github.com/tobymao/sqlglot/pull/3190) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3175](https://github.com/tobymao/sqlglot/issues/3175) opened by [@herry13](https://github.com/herry13)*\n- [`5cdd874`](https://github.com/tobymao/sqlglot/commit/5cdd8749bacb101711a477798ff96bace44ccfb1) - **generator**: compute csv leading comma pad length correctly *(PR [#3201](https://github.com/tobymao/sqlglot/pull/3201) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3199](https://github.com/tobymao/sqlglot/issues/3199) opened by [@giovannipcarvalho](https://github.com/giovannipcarvalho)*\n- [`73fc807`](https://github.com/tobymao/sqlglot/commit/73fc807a48bfadc5bbe5594b55ba45480e93be3c) - **tokenizer**: don't increment array cursor by 2 on CRLF *(PR [#3204](https://github.com/tobymao/sqlglot/pull/3204) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3200](https://github.com/tobymao/sqlglot/issues/3200) opened by [@mr-miles](https://github.com/mr-miles)*\n- [`af1b026`](https://github.com/tobymao/sqlglot/commit/af1b02697303160050ee32b1c89ff80f14d9d0fa) - **snowflake**: convert VALUES with invalid expressions into UNION ALL *(PR [#3213](https://github.com/tobymao/sqlglot/pull/3213) by [@georgesittas](https://github.com/georgesittas))*\n- [`ec4648f`](https://github.com/tobymao/sqlglot/commit/ec4648f7eb982bf48b5bf09271c1955b892867fa) - **optimizer**: don't merge ORDER BY into UNION *(PR [#3215](https://github.com/tobymao/sqlglot/pull/3215) by [@barakalon](https://github.com/barakalon))*\n  - :arrow_lower_right: *fixes issue [#3211](https://github.com/tobymao/sqlglot/issues/3211) opened by [@rorynormaness](https://github.com/rorynormaness)*\n- [`e4dd052`](https://github.com/tobymao/sqlglot/commit/e4dd0526031591179156a1eea45089213b23cdf7) - allow snowflake object_construct with string keys to transpile to sqlglot dialect *(commit by [@tobymao](https://github.com/tobymao))*\n- [`9e39076`](https://github.com/tobymao/sqlglot/commit/9e39076b7f581dc68e10c558ff8f6c9809bfe841) - **tsql**: datestrtodate for tsql closes [#3216](https://github.com/tobymao/sqlglot/pull/3216) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`e7c9158`](https://github.com/tobymao/sqlglot/commit/e7c91584ac7fb35082ebd1d4873f13307ea848af) - bq datetime to timestamp *(PR [#3220](https://github.com/tobymao/sqlglot/pull/3220) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`e6b8d1f`](https://github.com/tobymao/sqlglot/commit/e6b8d1f0061d55bf434d1a838f858b9fa412e312) - **optimizer**: constrain UDTF scope boundary *(PR [#3226](https://github.com/tobymao/sqlglot/pull/3226) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3209](https://github.com/tobymao/sqlglot/issues/3209) opened by [@rorynormaness](https://github.com/rorynormaness)*\n\n### :recycle: Refactors\n- [`4cd0e17`](https://github.com/tobymao/sqlglot/commit/4cd0e1719a55a75dac1114736fbbe48a8aa8f294) - get rid of redundant condition in Expression.replace *(PR [#3192](https://github.com/tobymao/sqlglot/pull/3192) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`3879518`](https://github.com/tobymao/sqlglot/commit/3879518f951233fed3434c493a5786573ee814fd) - bump sqlglotrs to 0.1.3 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v23.0.5] - 2024-03-20\n### :bug: Bug Fixes\n- [`ed2c9e1`](https://github.com/tobymao/sqlglot/commit/ed2c9e126cc7e679c543adaa2827c1f5c47b96d7) - move varchar max conversion to base *(commit by [@tobymao](https://github.com/tobymao))*\n- [`e3b6139`](https://github.com/tobymao/sqlglot/commit/e3b61392b1d050447f77fcf1b04efd6dcbfc311e) - move comment from window function to Window expression *(PR [#3178](https://github.com/tobymao/sqlglot/pull/3178) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2299](https://github.com/TobikoData/sqlmesh/issues/2299) opened by [@georgesittas](https://github.com/georgesittas)*\n- [`a452276`](https://github.com/tobymao/sqlglot/commit/a452276da4daaa436a9ac95566bcbb2954d149e3) - **clickhouse**: Fixing FORMAT being parsed as implicit alias *(PR [#3179](https://github.com/tobymao/sqlglot/pull/3179) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3176](https://github.com/tobymao/sqlglot/issues/3176) opened by [@mlipiev](https://github.com/mlipiev)*\n\n\n## [v23.0.4] - 2024-03-20\n### :bug: Bug Fixes\n- [`42cf703`](https://github.com/tobymao/sqlglot/commit/42cf70351e7811a077da29af42b28662ede203ac) - redshift varchar(max) catch lower case *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`20cd803`](https://github.com/tobymao/sqlglot/commit/20cd8038268b162af7bae63d54ed2f349502042a) - cleanup redundant check *(commit by [@tobymao](https://github.com/tobymao))*\n- [`7e12342`](https://github.com/tobymao/sqlglot/commit/7e12342029d33ff139a3566243789f54e36f4525) - add superset to readme *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.0.3] - 2024-03-19\n### :sparkles: New Features\n- [`bc8bc7f`](https://github.com/tobymao/sqlglot/commit/bc8bc7f8c9a6a20a35bab8ea7b34cf6431616b50) - replace a nested child node with a list convenience *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`eaaeab0`](https://github.com/tobymao/sqlglot/commit/eaaeab088010f55ccc221a9a4968f0d4ff67d8b1) - **snowflake**: Allow non-literal expressions too in DATE functions *(PR [#3167](https://github.com/tobymao/sqlglot/pull/3167) by [@VaggelisD](https://github.com/VaggelisD))*\n\n\n## [v23.0.2] - 2024-03-19\n### :sparkles: New Features\n- [`32cc2be`](https://github.com/tobymao/sqlglot/commit/32cc2be1b19ade551b42cc70a96f1675ac8773f4) - **postgres**: add support for materialized CTEs *(PR [#3171](https://github.com/tobymao/sqlglot/pull/3171) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3170](https://github.com/tobymao/sqlglot/issues/3170) opened by [@betodealmeida](https://github.com/betodealmeida)*\n\n### :bug: Bug Fixes\n- [`df4ce17`](https://github.com/tobymao/sqlglot/commit/df4ce17f24bbb16a64172e351f4e27ac74de668a) - can't expand group by for nulls and bools *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d859fc0`](https://github.com/tobymao/sqlglot/commit/d859fc0f6eeb0971dab5b22748d1e84425829444) - unnest annotation with generate_date_array *(PR [#3169](https://github.com/tobymao/sqlglot/pull/3169) by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.0.1] - 2024-03-19\n### :sparkles: New Features\n- [`931774d`](https://github.com/tobymao/sqlglot/commit/931774dde50aa04efecd1ae9cdd6965655670d71) - iterative connector sql *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`5e18d49`](https://github.com/tobymao/sqlglot/commit/5e18d490be3990116bbacd1b09dd52542f51c151) - fill in missing implementation details for replace(None) *(PR [#3166](https://github.com/tobymao/sqlglot/pull/3166) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3165](https://github.com/tobymao/sqlglot/issues/3165) opened by [@streamnsight](https://github.com/streamnsight)*\n- [`a0df28f`](https://github.com/tobymao/sqlglot/commit/a0df28f4092ca84d07111cead550b9d6772993ad) - can't simplify null parens *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`eb0a6c3`](https://github.com/tobymao/sqlglot/commit/eb0a6c31c92d16abe785087271b14f7611ff24bc) - actually pop the where statement *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f6778ef`](https://github.com/tobymao/sqlglot/commit/f6778ef039a646fb5641f0e91b28f6cbc2f52e78) - add recursion test *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v23.0.0] - 2024-03-18\n### :sparkles: New Features\n- [`e838713`](https://github.com/tobymao/sqlglot/commit/e838713bdb3da8a5d04eed43b2015a9d3a71addd) - **mysql**: Support for multi arg GROUP_CONCAT *(PR [#3150](https://github.com/tobymao/sqlglot/pull/3150) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3142](https://github.com/tobymao/sqlglot/issues/3142) opened by [@optionals](https://github.com/optionals)*\n- [`7e8f134`](https://github.com/tobymao/sqlglot/commit/7e8f134fb2d78940b27f81be7a347caee371601c) - **test**: Add standard alias to some TPC-DS query *(PR [#3151](https://github.com/tobymao/sqlglot/pull/3151) by [@fool1280](https://github.com/fool1280))*\n- [`6d0e965`](https://github.com/tobymao/sqlglot/commit/6d0e9658733c672154ec69fd2a4140332954b466) - add skip limit token kwarg *(PR [#3149](https://github.com/tobymao/sqlglot/pull/3149) by [@z3z1ma](https://github.com/z3z1ma))*\n- [`3ed5845`](https://github.com/tobymao/sqlglot/commit/3ed58458f9c89a1241a6fa6bb787e236289af58d) - include table alias in bigquery unnest *(PR [#3156](https://github.com/tobymao/sqlglot/pull/3156) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`706fac3`](https://github.com/tobymao/sqlglot/commit/706fac382fbde6c1c6af8acd277291a3f18f94ee) - add bigquery mod op *(PR [#3157](https://github.com/tobymao/sqlglot/pull/3157) by [@eakmanrq](https://github.com/eakmanrq))*\n- [`6ffdc25`](https://github.com/tobymao/sqlglot/commit/6ffdc25c673db33c3e9ac5a2c6970c4331a3f978) - **clickhouse**: Support for INSERT INTO TABLE FUNCTION *(PR [#3162](https://github.com/tobymao/sqlglot/pull/3162) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3161](https://github.com/tobymao/sqlglot/issues/3161) opened by [@mlipiev](https://github.com/mlipiev)*\n- [`021af42`](https://github.com/tobymao/sqlglot/commit/021af4206f4ff2ad4bd57d30cf1f2f78f24fc844) - **snowflake**: Adding support for DATE, TO_DATE, TRY_TO_DATE functions *(PR [#3160](https://github.com/tobymao/sqlglot/pull/3160) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3152](https://github.com/tobymao/sqlglot/issues/3152) opened by [@lei0zhou](https://github.com/lei0zhou)*\n\n### :bug: Bug Fixes\n- [`cfde552`](https://github.com/tobymao/sqlglot/commit/cfde552005e1682d6dd1b71850e163021bf4532f) - asof identifier closes [#3153](https://github.com/tobymao/sqlglot/pull/3153) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`b1e6eef`](https://github.com/tobymao/sqlglot/commit/b1e6eefcd4dd60f541047a10ed35c1ac733a636c) - bigquery values transpilation with no column alias *(commit by [@tobymao](https://github.com/tobymao))*\n- [`c0760b3`](https://github.com/tobymao/sqlglot/commit/c0760b3be11af701273e55c2c976d67d9a575cc4) - parse over any closes [#3155](https://github.com/tobymao/sqlglot/pull/3155) *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`38b931e`](https://github.com/tobymao/sqlglot/commit/38b931ebed9255ce5d0d6185414b6f01ca02b0fd) - pin ruff *(commit by [@tobymao](https://github.com/tobymao))*\n- [`66a6284`](https://github.com/tobymao/sqlglot/commit/66a62847342f15b8d412fb91814342951fe23247) - improve type hints of Query methods *(PR [#3148](https://github.com/tobymao/sqlglot/pull/3148) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v22.5.0] - 2024-03-14\n### :boom: BREAKING CHANGES\n- due to [`2b4952e`](https://github.com/tobymao/sqlglot/commit/2b4952eb151b3f20739803e7bf443b56da457b1f) - desugar LOG2 and LOG10 by converting them into LOG *(PR [#3139](https://github.com/tobymao/sqlglot/pull/3139) by [@georgesittas](https://github.com/georgesittas))*:\n\n  desugar LOG2 and LOG10 by converting them into LOG (#3139)\n\n\n### :sparkles: New Features\n- [`c01ff44`](https://github.com/tobymao/sqlglot/commit/c01ff44b036526807624ba2d1f4b247081e8c56f) - **snowflake**: Add TO_TIMESTAMP test and update env.py *(PR [#3130](https://github.com/tobymao/sqlglot/pull/3130) by [@fool1280](https://github.com/fool1280))*\n- [`8526c8e`](https://github.com/tobymao/sqlglot/commit/8526c8e30376c0826ab31a0a342656d5ebced662) - **tsql**: transpile LIMIT with OFFSET properly *(PR [#3145](https://github.com/tobymao/sqlglot/pull/3145) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3144](https://github.com/tobymao/sqlglot/issues/3144) opened by [@iMayK](https://github.com/iMayK)*\n\n### :bug: Bug Fixes\n- [`a9db8ff`](https://github.com/tobymao/sqlglot/commit/a9db8ff6ac528da8c3a7a66f0b80a3f0d1a0ed7e) - don't mutate parent nested classes if undefined in a dialect *(PR [#3134](https://github.com/tobymao/sqlglot/pull/3134) by [@georgesittas](https://github.com/georgesittas))*\n- [`d6bac3e`](https://github.com/tobymao/sqlglot/commit/d6bac3e54c6445c52daa04015b1b2e4a6933e682) - **duckdb**: Slice + Array bug *(PR [#3137](https://github.com/tobymao/sqlglot/pull/3137) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3136](https://github.com/tobymao/sqlglot/issues/3136) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`230a845`](https://github.com/tobymao/sqlglot/commit/230a845d82576b24ef8a3bbcc83677ed637e8247) - optimizer bugs *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :recycle: Refactors\n- [`2b4952e`](https://github.com/tobymao/sqlglot/commit/2b4952eb151b3f20739803e7bf443b56da457b1f) - desugar LOG2 and LOG10 by converting them into LOG *(PR [#3139](https://github.com/tobymao/sqlglot/pull/3139) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3138](https://github.com/tobymao/sqlglot/issues/3138) opened by [@baruchoxman](https://github.com/baruchoxman)*\n\n### :wrench: Chores\n- [`ebbf5a1`](https://github.com/tobymao/sqlglot/commit/ebbf5a14da12b442bff84d93f8542d4322e0811d) - copy sqlglot.svg in docs/ to also display logo in website *(PR [#3147](https://github.com/tobymao/sqlglot/pull/3147) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3146](https://github.com/tobymao/sqlglot/issues/3146) opened by [@lostmygithubaccount](https://github.com/lostmygithubaccount)*\n\n\n## [v22.4.0] - 2024-03-12\n### :boom: BREAKING CHANGES\n- due to [`b1c8cac`](https://github.com/tobymao/sqlglot/commit/b1c8cace6ed3e58657726fa5617a6df63d91f737) - traverse union scopes iteratively *(PR [#3112](https://github.com/tobymao/sqlglot/pull/3112) by [@georgesittas](https://github.com/georgesittas))*:\n\n  traverse union scopes iteratively (#3112)\n\n\n### :sparkles: New Features\n- [`88033da`](https://github.com/tobymao/sqlglot/commit/88033dad05550cde05dcb86cce61a621c071382c) - **test**: add more passing tpcds tests *(PR [#3110](https://github.com/tobymao/sqlglot/pull/3110) by [@fool1280](https://github.com/fool1280))*\n- [`804af34`](https://github.com/tobymao/sqlglot/commit/804af347a7cefac251b78fdcb8ff35b63c249d82) - **duckdb**: add support for positional joins *(PR [#3111](https://github.com/tobymao/sqlglot/pull/3111) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3109](https://github.com/tobymao/sqlglot/issues/3109) opened by [@dylanscott](https://github.com/dylanscott)*\n- [`c4e7bbf`](https://github.com/tobymao/sqlglot/commit/c4e7bbfd3d88f3efb1fea806f85091dbe32379cf) - improve transpilation of TO_NUMBER *(commit by [@codeDing18](https://github.com/codeDing18))*\n- [`80d484c`](https://github.com/tobymao/sqlglot/commit/80d484c428329fb53c905fff9f86ea0ee7bcef3b) - **postgres**: generate StrToDate *(PR [#3124](https://github.com/tobymao/sqlglot/pull/3124) by [@georgesittas](https://github.com/georgesittas))*\n- [`09708f5`](https://github.com/tobymao/sqlglot/commit/09708f571bb7b62e96bbfba363b00714243d1a17) - Adding EXCLUDE constraint support *(PR [#3116](https://github.com/tobymao/sqlglot/pull/3116) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3097](https://github.com/tobymao/sqlglot/issues/3097) opened by [@dezhin](https://github.com/dezhin)*\n- [`9b25a8e`](https://github.com/tobymao/sqlglot/commit/9b25a8e3788c4cc7a299c703fe5b4086fe86015d) - Adding BACKUP property *(PR [#3127](https://github.com/tobymao/sqlglot/pull/3127) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3125](https://github.com/tobymao/sqlglot/issues/3125) opened by [@hsheth2](https://github.com/hsheth2)*\n- [`0ea849b`](https://github.com/tobymao/sqlglot/commit/0ea849b35bd3dd980c4f851d3ea7b5bc628e6fb5) - Adding NAME data type in Postgres/Redshift *(PR [#3128](https://github.com/tobymao/sqlglot/pull/3128) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3123](https://github.com/tobymao/sqlglot/issues/3123) opened by [@hsheth2](https://github.com/hsheth2)*\n\n### :bug: Bug Fixes\n- [`c333017`](https://github.com/tobymao/sqlglot/commit/c333017fe49c0645cdaa3a75d0a7cc6a5b46dddc) - correctly generate ArrayJoin in various dialects *(PR [#3120](https://github.com/tobymao/sqlglot/pull/3120) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3119](https://github.com/tobymao/sqlglot/issues/3119) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`12d72a6`](https://github.com/tobymao/sqlglot/commit/12d72a6ff6534919979f77a5f045aa9d947d9a09) - make the lineage sources dict type covariant *(PR [#3122](https://github.com/tobymao/sqlglot/pull/3122) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3121](https://github.com/tobymao/sqlglot/issues/3121) opened by [@rexledesma](https://github.com/rexledesma)*\n- [`b1c8cac`](https://github.com/tobymao/sqlglot/commit/b1c8cace6ed3e58657726fa5617a6df63d91f737) - traverse union scopes iteratively *(PR [#3112](https://github.com/tobymao/sqlglot/pull/3112) by [@georgesittas](https://github.com/georgesittas))*\n- [`94b5a2f`](https://github.com/tobymao/sqlglot/commit/94b5a2fcba3c41d38734f045b7f1d5d4735e4828) - **athena**: Fix CREATE TABLE properties, STRING data type *(PR [#3129](https://github.com/tobymao/sqlglot/pull/3129) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *fixes issue [#3126](https://github.com/tobymao/sqlglot/issues/3126) opened by [@matthias-Q](https://github.com/matthias-Q)*\n\n### :recycle: Refactors\n- [`0ce9ef1`](https://github.com/tobymao/sqlglot/commit/0ce9ef12d9c030b145d7a7a7432bfc188d6c179a) - improve parsing of storage provider setting in index params *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v22.3.1] - 2024-03-09\n### :sparkles: New Features\n- [`80b2320`](https://github.com/tobymao/sqlglot/commit/80b23201f9668a5845002c1c21b0a394003847f9) - no recursion dfs *(PR [#3105](https://github.com/tobymao/sqlglot/pull/3105) by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`fa84e2c`](https://github.com/tobymao/sqlglot/commit/fa84e2c2d9ae349033039ec649decc371561e421) - copy all arg keys, including those set to None *(PR [#3108](https://github.com/tobymao/sqlglot/pull/3108) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v22.3.0] - 2024-03-08\n### :sparkles: New Features\n- [`46c9c2c`](https://github.com/tobymao/sqlglot/commit/46c9c2c35ea5132995cb07a99b94d18d959e6172) - **snowflake**: parse CREATE SEQUENCE *(PR [#3072](https://github.com/tobymao/sqlglot/pull/3072) by [@tekumara](https://github.com/tekumara))*\n  - :arrow_lower_right: *addresses issue [#2954](https://github.com/tobymao/sqlglot/issues/2954) opened by [@tharwan](https://github.com/tharwan)*\n- [`9f1e1ad`](https://github.com/tobymao/sqlglot/commit/9f1e1ad4350fb412319511825ca3da9b9af14084) - add Athena dialect *(PR [#3089](https://github.com/tobymao/sqlglot/pull/3089) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3087](https://github.com/tobymao/sqlglot/issues/3087) opened by [@sbrandtb](https://github.com/sbrandtb)*\n- [`efee388`](https://github.com/tobymao/sqlglot/commit/efee38858c4501ccace4b3eb3f066cb352f3ac60) - no more recursion for union generation *(PR [#3101](https://github.com/tobymao/sqlglot/pull/3101) by [@tobymao](https://github.com/tobymao))*\n- [`ddab9df`](https://github.com/tobymao/sqlglot/commit/ddab9dff663985d9473ce4b2dbe4fe266ae1bdf7) - **duckdb**: add support for exp.ArrayJoin *(PR [#3102](https://github.com/tobymao/sqlglot/pull/3102) by [@seruman](https://github.com/seruman))*\n- [`8d5be0c`](https://github.com/tobymao/sqlglot/commit/8d5be0cf54e77000b220cfcca0edfdeb1759b70b) - **duckdb**: make ARRAY_TO_STRING transpilable to other dialects *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`a38db01`](https://github.com/tobymao/sqlglot/commit/a38db014cce8ada9554c205c879ae0c0dfda1b14) - Generalizing CREATE SEQUENCE *(PR [#3090](https://github.com/tobymao/sqlglot/pull/3090) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`18fd079`](https://github.com/tobymao/sqlglot/commit/18fd0794302a1ecaa91be9dfbc7feddd0b8a3b05) - no recursion copy *(PR [#3103](https://github.com/tobymao/sqlglot/pull/3103) by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`b101013`](https://github.com/tobymao/sqlglot/commit/b101013336d0aef6dc99b5ebef85afc12591e212) - subquery edge cases *(PR [#3076](https://github.com/tobymao/sqlglot/pull/3076) by [@tobymao](https://github.com/tobymao))*\n- [`8c4400b`](https://github.com/tobymao/sqlglot/commit/8c4400ba194661d1e1ee4aa4ea2649b2356a5f02) - **bigquery**: more table qualification edge cases closes [#3083](https://github.com/tobymao/sqlglot/pull/3083) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d898f55`](https://github.com/tobymao/sqlglot/commit/d898f559fac44789da08689e835619f978c05a3e) - **bigquery**: even more edge cases *(commit by [@tobymao](https://github.com/tobymao))*\n- [`4fb74ff`](https://github.com/tobymao/sqlglot/commit/4fb74ff61effd9e5fa8593cdf1c9229d5106ab7e) - dataframe optimize user input *(PR [#3092](https://github.com/tobymao/sqlglot/pull/3092) by [@eakmanrq](https://github.com/eakmanrq))*\n  - :arrow_lower_right: *fixes issue [#3091](https://github.com/tobymao/sqlglot/issues/3091) opened by [@alexdemeo](https://github.com/alexdemeo)*\n\n### :recycle: Refactors\n- [`cea7508`](https://github.com/tobymao/sqlglot/commit/cea7508c5f2b5838e889486d28df47ad9b263345) - **lineage**: simplify `Node.walk()` *(PR [#3098](https://github.com/tobymao/sqlglot/pull/3098) by [@rexledesma](https://github.com/rexledesma))*\n- [`ebe5a46`](https://github.com/tobymao/sqlglot/commit/ebe5a462ed50711d6ded18b454c5294e487e323f) - **executor**: simplify column type inference *(PR [#3104](https://github.com/tobymao/sqlglot/pull/3104) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`6c67a2b`](https://github.com/tobymao/sqlglot/commit/6c67a2b0dbe2a66ea4ce2e008101f4cf41b1c517) - reduce size of tpcds *(commit by [@tobymao](https://github.com/tobymao))*\n- [`21e4fca`](https://github.com/tobymao/sqlglot/commit/21e4fca2b744a22981d8ff1696986061d3344d40) - update dialect count in README to include Athena *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v22.2.1] - 2024-03-04\n### :sparkles: New Features\n- [`19e07f3`](https://github.com/tobymao/sqlglot/commit/19e07f39688f53fafac25655616883363f20b1cf) - initial commit prql *(commit by [@tobymao](https://github.com/tobymao))*\n- [`13b64fd`](https://github.com/tobymao/sqlglot/commit/13b64fdcdde35e8fe022f76f4f2a5d55d53b982f) - more prql *(commit by [@tobymao](https://github.com/tobymao))*\n- [`3d263aa`](https://github.com/tobymao/sqlglot/commit/3d263aafbfb8d45bc678914e1eb925592c30eaf8) - **oracle**: Support for INSERT hint *(PR [#3077](https://github.com/tobymao/sqlglot/pull/3077) by [@VaggelisD](https://github.com/VaggelisD))*\n  - :arrow_lower_right: *addresses issue [#3074](https://github.com/tobymao/sqlglot/issues/3074) opened by [@sunrutcon](https://github.com/sunrutcon)*\n\n### :bug: Bug Fixes\n- [`c51b64f`](https://github.com/tobymao/sqlglot/commit/c51b64fa6a437698fd8b347d98ffaf9fb543d2d5) - json extract precedence closes [#3068](https://github.com/tobymao/sqlglot/pull/3068) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`223a475`](https://github.com/tobymao/sqlglot/commit/223a4751f88809710872fa7d757d22d9eeeb4f40) - **planner**: don't overwrite JOIN step name *(PR [#3071](https://github.com/tobymao/sqlglot/pull/3071) by [@georgesittas](https://github.com/georgesittas))*\n- [`2770ddc`](https://github.com/tobymao/sqlglot/commit/2770ddcc34148f85caeabf2b6f4f799b3e825a6c) - drop CLUSTER/DISTRIBUTED/SORT BY modifiers when unsupported *(PR [#3069](https://github.com/tobymao/sqlglot/pull/3069) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3066](https://github.com/tobymao/sqlglot/issues/3066) opened by [@cploonker](https://github.com/cploonker)*\n- [`4173ea2`](https://github.com/tobymao/sqlglot/commit/4173ea29bbd8944896c259fe45209de69fcbdc46) - handle lineage of subqueries *(PR [#3075](https://github.com/tobymao/sqlglot/pull/3075) by [@tobymao](https://github.com/tobymao))*\n- [`0ebce40`](https://github.com/tobymao/sqlglot/commit/0ebce40f5e524c61ece022bbf8640556e880a4bf) - **redshift**: don't transform multi-arg DISTINCT clause *(PR [#3079](https://github.com/tobymao/sqlglot/pull/3079) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v22.2.0] - 2024-03-01\n### :boom: BREAKING CHANGES\n- due to [`08bafbd`](https://github.com/tobymao/sqlglot/commit/08bafbd597b6a81e222832fac9f068f1290e41fa) - handle unnesting groups closes [#3056](https://github.com/tobymao/sqlglot/pull/3056) *(PR [#3058](https://github.com/tobymao/sqlglot/pull/3058) by [@tobymao](https://github.com/tobymao))*:\n\n  handle unnesting groups closes #3056 (#3058)\n\n- due to [`4029fab`](https://github.com/tobymao/sqlglot/commit/4029fab81e9abcedd6321baaf5baf9aa192f643d) - expand alias refs of double aggs if it is a window func *(PR [#3059](https://github.com/tobymao/sqlglot/pull/3059) by [@tobymao](https://github.com/tobymao))*:\n\n  expand alias refs of double aggs if it is a window func (#3059)\n\n\n### :sparkles: New Features\n- [`8662e31`](https://github.com/tobymao/sqlglot/commit/8662e31bd115eb668c4b74377ed2985937e81510) - **postgres**: improve transpilation of JSON array unnesting *(PR [#3063](https://github.com/tobymao/sqlglot/pull/3063) by [@georgesittas](https://github.com/georgesittas))*\n- [`c9bde44`](https://github.com/tobymao/sqlglot/commit/c9bde44bac5f7026388ec6357a6c1e00ee760edc) - Making parse_number & parse_string more lenient *(PR [#3064](https://github.com/tobymao/sqlglot/pull/3064) by [@VaggelisD](https://github.com/VaggelisD))*\n\n### :bug: Bug Fixes\n- [`08bafbd`](https://github.com/tobymao/sqlglot/commit/08bafbd597b6a81e222832fac9f068f1290e41fa) - handle unnesting groups closes [#3056](https://github.com/tobymao/sqlglot/pull/3056) *(PR [#3058](https://github.com/tobymao/sqlglot/pull/3058) by [@tobymao](https://github.com/tobymao))*\n- [`4029fab`](https://github.com/tobymao/sqlglot/commit/4029fab81e9abcedd6321baaf5baf9aa192f643d) - expand alias refs of double aggs if it is a window func *(PR [#3059](https://github.com/tobymao/sqlglot/pull/3059) by [@tobymao](https://github.com/tobymao))*\n- [`4e6e82c`](https://github.com/tobymao/sqlglot/commit/4e6e82c9d4d9ef33635446e19b4b44f3ae27160c) - **snowflake**: allow any identifier after : closes [#3061](https://github.com/tobymao/sqlglot/pull/3061) *(PR [#3062](https://github.com/tobymao/sqlglot/pull/3062) by [@georgesittas](https://github.com/georgesittas))*\n- [`e2becea`](https://github.com/tobymao/sqlglot/commit/e2becead1e6be12ddf8bde703d2c403220506784) - is distinct from parsing *(commit by [@tobymao](https://github.com/tobymao))*\n- [`c8a753b`](https://github.com/tobymao/sqlglot/commit/c8a753b488d99172db9df10616e8bd3431452ff8) - Ignore Identifier nodes in the diffing algorithm *(PR [#3065](https://github.com/tobymao/sqlglot/pull/3065) by [@izeigerman](https://github.com/izeigerman))*\n\n\n## [v22.1.1] - 2024-02-29\n### :sparkles: New Features\n- [`1e25ec9`](https://github.com/tobymao/sqlglot/commit/1e25ec984510a1ffee76956b0dcb15bcd84f5d44) - **test**: handle NULL value in TPC-DS  *(PR [#3052](https://github.com/tobymao/sqlglot/pull/3052) by [@fool1280](https://github.com/fool1280))*\n- [`ad21b6b`](https://github.com/tobymao/sqlglot/commit/ad21b6b47716d394ca6b8fb3b82d58b887d5adb3) - **test**: add more passing tpc-ds test *(PR [#3053](https://github.com/tobymao/sqlglot/pull/3053) by [@fool1280](https://github.com/fool1280))*\n\n### :bug: Bug Fixes\n- [`08249af`](https://github.com/tobymao/sqlglot/commit/08249af50351a24277e1f3f1574629eb5c68d3a5) - Hive UnixToTime regression, README stale results *(PR [#3055](https://github.com/tobymao/sqlglot/pull/3055) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`39b3813`](https://github.com/tobymao/sqlglot/commit/39b381341fe697ae54f5d3a438b4035447fe552a) - **redshift**: don't pop recursive cte table columns *(commit by [@tobymao](https://github.com/tobymao))*\n- [`6a9501f`](https://github.com/tobymao/sqlglot/commit/6a9501f7407be3682ce3b9cc73b7340ad9a0c2e8) - ensure UDF identifier quotes are preserved *(PR [#3057](https://github.com/tobymao/sqlglot/pull/3057) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v22.1.0] - 2024-02-29\n### :sparkles: New Features\n- [`6393979`](https://github.com/tobymao/sqlglot/commit/63939796b39c69b25adfc6f224ccd4761f23cb66) - **oracle**: connect_by_root closes [#3050](https://github.com/tobymao/sqlglot/pull/3050) *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`bd0a40d`](https://github.com/tobymao/sqlglot/commit/bd0a40dde2ab2ad168ada0d5bae0c99fba9d762f) - normalize column for lineage and raise if cannot find closes [#3049](https://github.com/tobymao/sqlglot/pull/3049) *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v22.0.2] - 2024-02-28\n### :sparkles: New Features\n- [`51f8d58`](https://github.com/tobymao/sqlglot/commit/51f8d5897b18e6f7c0bc66881a3e36c8842ff2ff) - **tsql**: add support for OPTION clause, select only *(PR [#3025](https://github.com/tobymao/sqlglot/pull/3025) by [@nadav-botanica](https://github.com/nadav-botanica))*\n- [`c9eef99`](https://github.com/tobymao/sqlglot/commit/c9eef99b8fe3367c22a8186fb397ad550ac11386) - Support for TRUNCATE TABLE/DATABASE DDL *(PR [#3026](https://github.com/tobymao/sqlglot/pull/3026) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`703b878`](https://github.com/tobymao/sqlglot/commit/703b87816c3e5f7b50407d2f2a14f3a9cba4e3f8) - **mysql**: add LOCK property, allow properties after ALTER TABLE *(PR [#3027](https://github.com/tobymao/sqlglot/pull/3027) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3020](https://github.com/tobymao/sqlglot/issues/3020) opened by [@samotarnik](https://github.com/samotarnik)*\n\n### :bug: Bug Fixes\n- [`bc4acb9`](https://github.com/tobymao/sqlglot/commit/bc4acb9582a80a6c3d4b491b48a68f110e399e3a) - allow trailing comma in ORDER BY list *(PR [#3031](https://github.com/tobymao/sqlglot/pull/3031) by [@georgesittas](https://github.com/georgesittas))*\n- [`4105639`](https://github.com/tobymao/sqlglot/commit/4105639ddbbc504d4bd4607511ac35e8ca30c774) - **bigquery**: unquoted project-0.x closes [#3029](https://github.com/tobymao/sqlglot/pull/3029) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f1f2aec`](https://github.com/tobymao/sqlglot/commit/f1f2aecb09c6c0d9a965d87669368945abd112cc) - bigquery edgecase *(commit by [@tobymao](https://github.com/tobymao))*\n- [`5c01c01`](https://github.com/tobymao/sqlglot/commit/5c01c010348271e8cfddea3ed0ac51293c3819b3) - handle falsey values for replace_placeholders kwargs *(PR [#3036](https://github.com/tobymao/sqlglot/pull/3036) by [@sarchila](https://github.com/sarchila))*\n- [`ccfbb22`](https://github.com/tobymao/sqlglot/commit/ccfbb2238131bda8fc7a3ad8a9c50a0f009dac52) - **clickhouse**: make CTE expression parser more flexible fixes [#3038](https://github.com/tobymao/sqlglot/pull/3038) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`30e0bb1`](https://github.com/tobymao/sqlglot/commit/30e0bb13162e75c53b031bbb69c66093f8ad4a96) - another edge case *(commit by [@tobymao](https://github.com/tobymao))*\n- [`0d93852`](https://github.com/tobymao/sqlglot/commit/0d938524a618b4bd7c057623a2c8755ca3afec6d) - **oracle**: handle GLOBAL/PRIVATE keyword in temp table DDL *(PR [#3045](https://github.com/tobymao/sqlglot/pull/3045) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3037](https://github.com/tobymao/sqlglot/issues/3037) opened by [@gforsyth](https://github.com/gforsyth)*\n- [`e89d38d`](https://github.com/tobymao/sqlglot/commit/e89d38ddd5f699f2ac09baf77238ad5fab00acb8) - **duckdb**: recognize ENUM as a type *(PR [#3044](https://github.com/tobymao/sqlglot/pull/3044) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#3043](https://github.com/tobymao/sqlglot/issues/3043) opened by [@joouha](https://github.com/joouha)*\n- [`4db7781`](https://github.com/tobymao/sqlglot/commit/4db77816a44652b3edc8aae5aab24242854f9a14) - avoid raising a KeyError in the lineage module, log a warning *(PR [#3048](https://github.com/tobymao/sqlglot/pull/3048) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`5337980`](https://github.com/tobymao/sqlglot/commit/53379805454f0e6f325581b839d2fcb37c10de1b) - simplify parsing of keyword sequences as Vars *(PR [#3034](https://github.com/tobymao/sqlglot/pull/3034) by [@georgesittas](https://github.com/georgesittas))*\n- [`bc35c59`](https://github.com/tobymao/sqlglot/commit/bc35c59004cb3fb9849f0ee8e5f06b356396c0b0) - use _parse_var_from_options for USE statement parser *(PR [#3035](https://github.com/tobymao/sqlglot/pull/3035) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`c0d355a`](https://github.com/tobymao/sqlglot/commit/c0d355a27d86539dfd95a87fea7e1bd75c4fabe4) - bump sqlglotrs to 0.1.2 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v22.0.1] - 2024-02-26\n### :bug: Bug Fixes\n- [`e2fc6e8`](https://github.com/tobymao/sqlglot/commit/e2fc6e88dc7ae52d956dd84721de197c6c698d90) - **optimizer**: fix parent mutation of new_projections in column qualifier *(PR [#3030](https://github.com/tobymao/sqlglot/pull/3030) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v22.0.0] - 2024-02-26\n### :boom: BREAKING CHANGES\n- due to [`2507aa2`](https://github.com/tobymao/sqlglot/commit/2507aa2dbad3304304558565f266a7f94acd9e98) - consolidate Subqueryable and Unionable into Query expression *(PR [#2992](https://github.com/tobymao/sqlglot/pull/2992) by [@georgesittas](https://github.com/georgesittas))*:\n\n  consolidate Subqueryable and Unionable into Query expression (#2992)\n\n- due to [`d5eb2b1`](https://github.com/tobymao/sqlglot/commit/d5eb2b1e0907026e6e981a8f453f747cb16f44d6) - make implicit unnest syntax explicit by using UNNEST calls *(PR [#3005](https://github.com/tobymao/sqlglot/pull/3005) by [@georgesittas](https://github.com/georgesittas))*:\n\n  make implicit unnest syntax explicit by using UNNEST calls (#3005)\n\n- due to [`238f9aa`](https://github.com/tobymao/sqlglot/commit/238f9aa7c32037d0c280cfe6ece77eed9c311cc5) - refactor structs to always be aliases *(PR [#3017](https://github.com/tobymao/sqlglot/pull/3017) by [@tobymao](https://github.com/tobymao))*:\n\n  refactor structs to always be aliases (#3017)\n\n- due to [`06bcfcd`](https://github.com/tobymao/sqlglot/commit/06bcfcdf69f850693d941675bbcfce1aa80482f6) - select expressions not statements closes [#3022](https://github.com/tobymao/sqlglot/pull/3022), statements can be parsed without into *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  select expressions not statements closes #3022, statements can be parsed without into\n\n- due to [`1612e62`](https://github.com/tobymao/sqlglot/commit/1612e622bd3514d9ca366837f47452969e5267d8) - Add reference to lineage node *(PR [#3018](https://github.com/tobymao/sqlglot/pull/3018) by [@vchan](https://github.com/vchan))*:\n\n  Add reference to lineage node (#3018)\n\n\n### :sparkles: New Features\n- [`e50609b`](https://github.com/tobymao/sqlglot/commit/e50609b119c65407f4f7fe27f06510187dc750a0) - Supporting RANGE <-> GENERATE_SERIES between DuckDB & SQLite *(PR [#3010](https://github.com/tobymao/sqlglot/pull/3010) by [@VaggelisD](https://github.com/VaggelisD))*\n- [`1709ec2`](https://github.com/tobymao/sqlglot/commit/1709ec2519edc4b1a91f435d76f1b962355be326) - bigquery e6s format *(commit by [@tobymao](https://github.com/tobymao))*\n- [`17e34e7`](https://github.com/tobymao/sqlglot/commit/17e34e79d22e3c8211f1bf42047d4ed3557628b6) - add unnest type annotations *(PR [#3019](https://github.com/tobymao/sqlglot/pull/3019) by [@tobymao](https://github.com/tobymao))*\n- [`efdbc12`](https://github.com/tobymao/sqlglot/commit/efdbc127a06b1c6204327caa0d6b0cb01590da13) - clickhouse prewhere closes [#3024](https://github.com/tobymao/sqlglot/pull/3024) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`1612e62`](https://github.com/tobymao/sqlglot/commit/1612e622bd3514d9ca366837f47452969e5267d8) - Add reference to lineage node *(PR [#3018](https://github.com/tobymao/sqlglot/pull/3018) by [@vchan](https://github.com/vchan))*\n- [`5c3bd10`](https://github.com/tobymao/sqlglot/commit/5c3bd1074960874b4557b13df6d30782fe7b0757) - **test**: add more passing tests of tpc-ds *(PR [#3016](https://github.com/tobymao/sqlglot/pull/3016) by [@fool1280](https://github.com/fool1280))*\n\n### :bug: Bug Fixes\n- [`7f547e6`](https://github.com/tobymao/sqlglot/commit/7f547e641f7a0ecaa804d5bea14bd24abce1d346) - it's actually seconds + fraction *(commit by [@tobymao](https://github.com/tobymao))*\n- [`238f9aa`](https://github.com/tobymao/sqlglot/commit/238f9aa7c32037d0c280cfe6ece77eed9c311cc5) - refactor structs to always be aliases *(PR [#3017](https://github.com/tobymao/sqlglot/pull/3017) by [@tobymao](https://github.com/tobymao))*\n  - :arrow_lower_right: *fixes issue [#3015](https://github.com/tobymao/sqlglot/issues/3015) opened by [@wizardxz](https://github.com/wizardxz)*\n- [`06bcfcd`](https://github.com/tobymao/sqlglot/commit/06bcfcdf69f850693d941675bbcfce1aa80482f6) - select expressions not statements closes [#3022](https://github.com/tobymao/sqlglot/pull/3022), statements can be parsed without into *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :recycle: Refactors\n- [`2507aa2`](https://github.com/tobymao/sqlglot/commit/2507aa2dbad3304304558565f266a7f94acd9e98) - consolidate Subqueryable and Unionable into Query expression *(PR [#2992](https://github.com/tobymao/sqlglot/pull/2992) by [@georgesittas](https://github.com/georgesittas))*\n- [`d5eb2b1`](https://github.com/tobymao/sqlglot/commit/d5eb2b1e0907026e6e981a8f453f747cb16f44d6) - make implicit unnest syntax explicit by using UNNEST calls *(PR [#3005](https://github.com/tobymao/sqlglot/pull/3005) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2996](https://github.com/tobymao/sqlglot/issues/2996) opened by [@wizardxz](https://github.com/wizardxz)*\n- [`8943179`](https://github.com/tobymao/sqlglot/commit/8943179dfadba4ed36740322e1e5d3611032b51e) - move limit method to Query, get rid of Subquery.subquery override *(PR [#3013](https://github.com/tobymao/sqlglot/pull/3013) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`9595240`](https://github.com/tobymao/sqlglot/commit/9595240a1c0f0e5ace9f67f31564e5d5edb9a9d2) - make prewhere clickhouse only *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v21.2.1] - 2024-02-22\n### :sparkles: New Features\n- [`2a88e40`](https://github.com/tobymao/sqlglot/commit/2a88e40da89fa083bbd8fd0174082fa8e677780a) - **bigquery**: support ELSE and ELSEIF procedural statements *(PR [#3011](https://github.com/tobymao/sqlglot/pull/3011) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#3009](https://github.com/tobymao/sqlglot/issues/3009) opened by [@razvan-am](https://github.com/razvan-am)*\n- [`d2e15ed`](https://github.com/tobymao/sqlglot/commit/d2e15ed9b2ab2699f7105f73170b9d780293d432) - improve transpilation of Doris' MONTHS_ADD *(PR [#3012](https://github.com/tobymao/sqlglot/pull/3012) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`c23ac05`](https://github.com/tobymao/sqlglot/commit/c23ac05379e2aa5cb5681e26e2c0b8137300baa3) - bigquery group by order by rewriting with indices *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v21.2.0] - 2024-02-22\n### :boom: BREAKING CHANGES\n- due to [`2940417`](https://github.com/tobymao/sqlglot/commit/2940417116761f821c913bf093759243db33c343) - simplify ADD CONSTRAINT handling *(PR [#2990](https://github.com/tobymao/sqlglot/pull/2990) by [@georgesittas](https://github.com/georgesittas))*:\n\n  simplify ADD CONSTRAINT handling (#2990)\n\n\n### :sparkles: New Features\n- [`7c48079`](https://github.com/tobymao/sqlglot/commit/7c4807918de53d18fbfe0295b2644f0ad46003a8) - support parameters in BigQuery / DuckDB *(PR [#2991](https://github.com/tobymao/sqlglot/pull/2991) by [@r1b](https://github.com/r1b))*\n- [`b7c2744`](https://github.com/tobymao/sqlglot/commit/b7c2744eba3df631b575e8ab35f29f46419f83ba) - **tests**: update test_executor with tpc-ds  *(PR [#2983](https://github.com/tobymao/sqlglot/pull/2983) by [@fool1280](https://github.com/fool1280))*\n- [`c433cad`](https://github.com/tobymao/sqlglot/commit/c433cad7df383e97308ceb946d7f1dc171a5d60b) - allow more leniant bigquery wildcard parsing *(PR [#2998](https://github.com/tobymao/sqlglot/pull/2998) by [@tobymao](https://github.com/tobymao))*\n- [`8607247`](https://github.com/tobymao/sqlglot/commit/860724732b70b5557221998a45c3c950b39d664a) - support LEFT JOIN UNNEST in duckdb *(PR [#2999](https://github.com/tobymao/sqlglot/pull/2999) by [@r1b](https://github.com/r1b))*\n- [`64e38ed`](https://github.com/tobymao/sqlglot/commit/64e38edb32f9a66a9503e75424d0545da3dbe5df) - add support for more Snowflake SHOW commands *(PR [#3002](https://github.com/tobymao/sqlglot/pull/3002) by [@DanCardin](https://github.com/DanCardin))*\n\n### :bug: Bug Fixes\n- [`bc18f56`](https://github.com/tobymao/sqlglot/commit/bc18f56a39e0034e2b285efd7a882a417c517a99) - **optimizer**: don't coerce nested arg types in annotate_by_args *(PR [#2997](https://github.com/tobymao/sqlglot/pull/2997) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2152](https://github.com/TobikoData/sqlmesh/issues/2152) opened by [@plaflamme](https://github.com/plaflamme)*\n- [`ccd8cc0`](https://github.com/tobymao/sqlglot/commit/ccd8cc01429d21653198edce079679e17dbb22f6) - doris to_char closes [#3001](https://github.com/tobymao/sqlglot/pull/3001) *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :recycle: Refactors\n- [`2940417`](https://github.com/tobymao/sqlglot/commit/2940417116761f821c913bf093759243db33c343) - simplify ADD CONSTRAINT handling *(PR [#2990](https://github.com/tobymao/sqlglot/pull/2990) by [@georgesittas](https://github.com/georgesittas))*\n- [`d2711f7`](https://github.com/tobymao/sqlglot/commit/d2711f717aac4a7b624225d31c7fa827f8287476) - clean up duplicative placeholder_sql implementations *(PR [#2993](https://github.com/tobymao/sqlglot/pull/2993) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`53efb58`](https://github.com/tobymao/sqlglot/commit/53efb587a642a171bdb4fb6ad4c33a83c4391908) - cleanup tests *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v21.1.2] - 2024-02-19\n### :sparkles: New Features\n- [`b8cbf66`](https://github.com/tobymao/sqlglot/commit/b8cbf66471158371a27d9145b3b553b7a1384c9d) - **bigquery**: parse procedural EXCEPTION WHEN statement into a Command closes [#2981](https://github.com/tobymao/sqlglot/pull/2981) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`906ceee`](https://github.com/tobymao/sqlglot/commit/906ceee0946c547f83177916c3f8d6aeb23023a8) - **duckdb**: implement generation logic for exp.ArrayAny *(PR [#2984](https://github.com/tobymao/sqlglot/pull/2984) by [@georgesittas](https://github.com/georgesittas))*\n- [`92455e4`](https://github.com/tobymao/sqlglot/commit/92455e4d4e2c8d5a874a5050d9a38f943479cdca) - **snowflake**: create storage integration  *(PR [#2985](https://github.com/tobymao/sqlglot/pull/2985) by [@tekumara](https://github.com/tekumara))*\n- [`bedf6e9`](https://github.com/tobymao/sqlglot/commit/bedf6e9dabf9da25e1fff2f3c8ae22fbf7face0b) - improve transpilation support for ArrayAny *(PR [#2986](https://github.com/tobymao/sqlglot/pull/2986) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2707](https://github.com/tobymao/sqlglot/issues/2707) opened by [@HuashiSCNU0303](https://github.com/HuashiSCNU0303)*\n\n### :bug: Bug Fixes\n- [`cc67ab2`](https://github.com/tobymao/sqlglot/commit/cc67ab2513c71a6b9574f8c3cf4c8ba2927d798f) - **tsql**: map StrPosition back to CHARINDEX fixes [#2968](https://github.com/tobymao/sqlglot/pull/2968) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`46f15ce`](https://github.com/tobymao/sqlglot/commit/46f15cef87de3159bc1d422b2620278e9e27ec16) - **postgres**: ensure json extraction can roundtrip unaltered *(PR [#2974](https://github.com/tobymao/sqlglot/pull/2974) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2971](https://github.com/tobymao/sqlglot/issues/2971) opened by [@l-vincent-l](https://github.com/l-vincent-l)*\n- [`7ee4fe7`](https://github.com/tobymao/sqlglot/commit/7ee4fe73b29234f2837a212b6c872efd7f5c30ea) - expand using with star except *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :recycle: Refactors\n- [`5a34f3d`](https://github.com/tobymao/sqlglot/commit/5a34f3d5f652ac209fd122aa25e46d99d8e5cba6) - clean up tech debt in dialect implementations *(PR [#2977](https://github.com/tobymao/sqlglot/pull/2977) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`ae92789`](https://github.com/tobymao/sqlglot/commit/ae92789cdac4c4f0bb3d5f542bd9fe93aee4ea70) - rephrase some sentences in the FAQ section *(PR [#2980](https://github.com/tobymao/sqlglot/pull/2980) by [@georgesittas](https://github.com/georgesittas))*\n- [`22ed4d0`](https://github.com/tobymao/sqlglot/commit/22ed4d0a976dbba15962670873422e86874680b0) - cleanup kv defs from brackets *(PR [#2987](https://github.com/tobymao/sqlglot/pull/2987) by [@tobymao](https://github.com/tobymao))*\n\n\n## [v21.1.1] - 2024-02-14\n### :sparkles: New Features\n- [`1d0b3d3`](https://github.com/tobymao/sqlglot/commit/1d0b3d3a22ba5a8128505d636a2ff71d0ea03d03) - add support for multi-part interval addition syntax *(PR [#2970](https://github.com/tobymao/sqlglot/pull/2970) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2969](https://github.com/tobymao/sqlglot/issues/2969) opened by [@aersam](https://github.com/aersam)*\n\n### :bug: Bug Fixes\n- [`1c67f03`](https://github.com/tobymao/sqlglot/commit/1c67f030cd9df530e26c620079b2298b1db97d50) - **parser**: enable parsing of values into Identifier for some dialects *(PR [#2962](https://github.com/tobymao/sqlglot/pull/2962) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2957](https://github.com/tobymao/sqlglot/issues/2957) opened by [@hsheth2](https://github.com/hsheth2)*\n- [`d8b0d4f`](https://github.com/tobymao/sqlglot/commit/d8b0d4fcc82662004056a68b05ca20f30996661f) - don't treat VALUES as a keyword in BigQuery, Redshift *(PR [#2965](https://github.com/tobymao/sqlglot/pull/2965) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2963](https://github.com/tobymao/sqlglot/issues/2963) opened by [@sean-rose](https://github.com/sean-rose)*\n- [`5b7fd10`](https://github.com/tobymao/sqlglot/commit/5b7fd107f279c2f83c9d66d4353032c6d830202c) - **optimizer**: more optimizations for qualifying wide tables *(PR [#2972](https://github.com/tobymao/sqlglot/pull/2972) by [@barakalon](https://github.com/barakalon))*\n- [`6cb985a`](https://github.com/tobymao/sqlglot/commit/6cb985ae1346c1a912ed6f81be30310ee1c91dfa) - pass dialect in to_table call inside replace_tables *(PR [#2973](https://github.com/tobymao/sqlglot/pull/2973) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v21.1.0] - 2024-02-12\n### :sparkles: New Features\n- [`e71d489`](https://github.com/tobymao/sqlglot/commit/e71d4899e6744812fdefc2704c66bbd6043b5bc9) - add array and tuple helpers *(commit by [@tobymao](https://github.com/tobymao))*\n- [`876e075`](https://github.com/tobymao/sqlglot/commit/876e07580bb2de06b587fc8ad40eb67604ae8507) - **postgres**: root operator closes [#2940](https://github.com/tobymao/sqlglot/pull/2940) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`e731276`](https://github.com/tobymao/sqlglot/commit/e731276dd5490a7d294430e0887eebf19e16d28f) - **snowflake**: add support for SHOW USERS *(PR [#2948](https://github.com/tobymao/sqlglot/pull/2948) by [@DanCardin](https://github.com/DanCardin))*\n- [`b9d4468`](https://github.com/tobymao/sqlglot/commit/b9d44688c2b785212db635f121b686df02e2dec9) - **tableau**: identifier and quotes closes [#2950](https://github.com/tobymao/sqlglot/pull/2950) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f8d9dbf`](https://github.com/tobymao/sqlglot/commit/f8d9dbf6744f95bf4b7517e8bcc35dd3a6f70c5d) - **sqlite**: add support for IIF *(PR [#2951](https://github.com/tobymao/sqlglot/pull/2951) by [@georgesittas](https://github.com/georgesittas))*\n- [`b755551`](https://github.com/tobymao/sqlglot/commit/b7555516c6bf038dc39c4bba2b243839ceb6e3b5) - **clickhouse**: add basic support for system statement *(PR [#2953](https://github.com/tobymao/sqlglot/pull/2953) by [@GaliFFun](https://github.com/GaliFFun))*\n\n### :bug: Bug Fixes\n- [`844018b`](https://github.com/tobymao/sqlglot/commit/844018b8d3a3398d746fdc04c966c7e19d311998) - explode_outer to unnest closes [#2941](https://github.com/tobymao/sqlglot/pull/2941) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`159da45`](https://github.com/tobymao/sqlglot/commit/159da4523d6eb3ca6853d631bb98dc8f13c7b0fb) - posexplode_outer to unnest *(PR [#2942](https://github.com/tobymao/sqlglot/pull/2942) by [@chelsea-lin](https://github.com/chelsea-lin))*\n- [`76d6634`](https://github.com/tobymao/sqlglot/commit/76d66340e566bd9fa8c783f5d311101eb2e80480) - **spark**: CREATE TABLE ... PARTITIONED BY fixes *(PR [#2937](https://github.com/tobymao/sqlglot/pull/2937) by [@barakalon](https://github.com/barakalon))*\n- [`d07ddf9`](https://github.com/tobymao/sqlglot/commit/d07ddf9b460c1b6f672fda4f34dc9231419e6c9d) - **optimizer**: remove redundant casts *(PR [#2945](https://github.com/tobymao/sqlglot/pull/2945) by [@barakalon](https://github.com/barakalon))*\n- [`b70a394`](https://github.com/tobymao/sqlglot/commit/b70a394222bf209298026fd100f6b9498acf9fff) - if doesn't support different types *(commit by [@tobymao](https://github.com/tobymao))*\n- [`6a988e0`](https://github.com/tobymao/sqlglot/commit/6a988e0160022d33623cd036bf84bb0b222c9062) - **bigquery**: fix annotation of timestamp(x) *(PR [#2946](https://github.com/tobymao/sqlglot/pull/2946) by [@georgesittas](https://github.com/georgesittas))*\n- [`78e6d0d`](https://github.com/tobymao/sqlglot/commit/78e6d0de83efbff1d3b61c8550db56c1819f7c22) - **optimizer**: qualify_columns optimizations for wide tables *(PR [#2955](https://github.com/tobymao/sqlglot/pull/2955) by [@barakalon](https://github.com/barakalon))*\n- [`c20cc70`](https://github.com/tobymao/sqlglot/commit/c20cc70dfc7f6395af157521c7e99074d697beb4) - **redshift**: don't assume Table is an unnested Column if Join has a predicate *(PR [#2956](https://github.com/tobymao/sqlglot/pull/2956) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2952](https://github.com/tobymao/sqlglot/issues/2952) opened by [@vidit-wisdom](https://github.com/vidit-wisdom)*\n\n### :wrench: Chores\n- [`c4524ce`](https://github.com/tobymao/sqlglot/commit/c4524ce1e6a85e16db7ea0289116d0160732dc51) - fix unit test *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v21.0.2] - 2024-02-08\n### :sparkles: New Features\n- [`1842c96`](https://github.com/tobymao/sqlglot/commit/1842c96611cadb0227dd3ce8f42457679ab0e08b) - **clickhouse**: add support for LIMIT BY clause *(PR [#2926](https://github.com/tobymao/sqlglot/pull/2926) by [@georgesittas](https://github.com/georgesittas))*\n- [`9241858`](https://github.com/tobymao/sqlglot/commit/9241858e559f089b166d9b794e3ebb395624d84a) - add typing for explode closes [#2927](https://github.com/tobymao/sqlglot/pull/2927) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`85073d1`](https://github.com/tobymao/sqlglot/commit/85073d1538de8ceef3e5c622a901efd9e6bd38e3) - transpile multi-arg DISTINCT expression *(PR [#2936](https://github.com/tobymao/sqlglot/pull/2936) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2930](https://github.com/tobymao/sqlglot/issues/2930) opened by [@Jake-00](https://github.com/Jake-00)*\n\n### :bug: Bug Fixes\n- [`b827626`](https://github.com/tobymao/sqlglot/commit/b8276262bdca57e358284fadfdd468d2bc957e84) - remove find method from Schema *(PR [#2934](https://github.com/tobymao/sqlglot/pull/2934) by [@georgesittas](https://github.com/georgesittas))*\n- [`08cd117`](https://github.com/tobymao/sqlglot/commit/08cd117322302f08c95889ebf8699f4171c1d504) - **postgres**: fallback to parameter parser if heredoc is untokenizable *(PR [#2935](https://github.com/tobymao/sqlglot/pull/2935) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2931](https://github.com/tobymao/sqlglot/issues/2931) opened by [@eric-zhu](https://github.com/eric-zhu)*\n\n### :wrench: Chores\n- [`e4b5edb`](https://github.com/tobymao/sqlglot/commit/e4b5edbef42944b44d11c35aea31411ce3d79826) - bump sqlglotrs to 0.1.1 *(commit by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v21.0.1] - 2024-02-07\n### :sparkles: New Features\n- [`3a20eac`](https://github.com/tobymao/sqlglot/commit/3a20eaccbf5d5a80bd24b95c837cca8103dfe70a) - **clickhouse**: add support for JSONExtractString, clean up some helpers *(PR [#2925](https://github.com/tobymao/sqlglot/pull/2925) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2051](https://github.com/tobymao/sqlglot/issues/2051) opened by [@BTheunissen](https://github.com/BTheunissen)*\n\n\n## [v21.0.0] - 2024-02-07\n### :boom: BREAKING CHANGES\n- due to [`b4e8868`](https://github.com/tobymao/sqlglot/commit/b4e886877ecfbafdd64c515c765c3c54764bd987) - improve transpilation of JSON paths across dialects *(PR [#2883](https://github.com/tobymao/sqlglot/pull/2883) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve transpilation of JSON paths across dialects (#2883)\n\n- due to [`aa388ea`](https://github.com/tobymao/sqlglot/commit/aa388ea64404a26550dbb0734f4d3e35111f9e2c) - ignore nulls closes [#2896](https://github.com/tobymao/sqlglot/pull/2896) *(PR [#2898](https://github.com/tobymao/sqlglot/pull/2898) by [@tobymao](https://github.com/tobymao))*:\n\n  ignore nulls closes #2896 (#2898)\n\n- due to [`617a8c0`](https://github.com/tobymao/sqlglot/commit/617a8c0dfc5e9f2716f7827381af0db2e135059e) - timestamp diff for mysql and databricks *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  timestamp diff for mysql and databricks\n\n- due to [`b00b393`](https://github.com/tobymao/sqlglot/commit/b00b393d853ae05a3fce4ef78d7673edbcabf67d) - use raise instead of assert for assert_is *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  use raise instead of assert for assert_is\n\n- due to [`326aa31`](https://github.com/tobymao/sqlglot/commit/326aa31e32e511f4e40d3a5a7b1d599b5e2c1307) - deprecate case where transforms can be plain strs *(PR [#2919](https://github.com/tobymao/sqlglot/pull/2919) by [@georgesittas](https://github.com/georgesittas))*:\n\n  deprecate case where transforms can be plain strs (#2919)\n\n\n### :sparkles: New Features\n- [`fb450f0`](https://github.com/tobymao/sqlglot/commit/fb450f0263ecd6b7c9d0f49d84441327d50b9d83) - add tsql right left auto casting closes [#2899](https://github.com/tobymao/sqlglot/pull/2899) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`617a8c0`](https://github.com/tobymao/sqlglot/commit/617a8c0dfc5e9f2716f7827381af0db2e135059e) - timestamp diff for mysql and databricks *(commit by [@tobymao](https://github.com/tobymao))*\n- [`3fa92ca`](https://github.com/tobymao/sqlglot/commit/3fa92cac285cbb2bd9d8b5724dadb77be7e12731) - **redshift**: parse GETDATE *(PR [#2904](https://github.com/tobymao/sqlglot/pull/2904) by [@erickpeirson](https://github.com/erickpeirson))*\n- [`d262139`](https://github.com/tobymao/sqlglot/commit/d26213998b27fa9b6a66b6d21ab5a3a15f65635e) - **snowflake**: implement parsing logic for SHOW TABLES *(PR [#2913](https://github.com/tobymao/sqlglot/pull/2913) by [@tekumara](https://github.com/tekumara))*\n- [`838e780`](https://github.com/tobymao/sqlglot/commit/838e7800c32ad16074efef6a188ebd89083a9717) - improve transpilation of CREATE TABLE LIKE statement *(PR [#2923](https://github.com/tobymao/sqlglot/pull/2923) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2922](https://github.com/tobymao/sqlglot/issues/2922) opened by [@tharwan](https://github.com/tharwan)*\n- [`cbbad1f`](https://github.com/tobymao/sqlglot/commit/cbbad1fc40b6b2ca837ddb0f798b1802ad4063da) - improve transpilation of JSON path wildcards *(PR [#2924](https://github.com/tobymao/sqlglot/pull/2924) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`aa388ea`](https://github.com/tobymao/sqlglot/commit/aa388ea64404a26550dbb0734f4d3e35111f9e2c) - ignore nulls closes [#2896](https://github.com/tobymao/sqlglot/pull/2896) *(PR [#2898](https://github.com/tobymao/sqlglot/pull/2898) by [@tobymao](https://github.com/tobymao))*\n- [`b00b393`](https://github.com/tobymao/sqlglot/commit/b00b393d853ae05a3fce4ef78d7673edbcabf67d) - use raise instead of assert for assert_is *(commit by [@tobymao](https://github.com/tobymao))*\n- [`ab97246`](https://github.com/tobymao/sqlglot/commit/ab972462b1c545b4a60bb88cb40cdb98cb64e360) - array overlaps closes [#2903](https://github.com/tobymao/sqlglot/pull/2903) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f3bdcb0`](https://github.com/tobymao/sqlglot/commit/f3bdcb087bb993289c4a1a5d2de54155ae2d2681) - **duckdb**: fix JSON pointer path parsing, reduce warning noise *(PR [#2911](https://github.com/tobymao/sqlglot/pull/2911) by [@georgesittas](https://github.com/georgesittas))*\n- [`072264f`](https://github.com/tobymao/sqlglot/commit/072264f8af25737050f7becd27af5a9331bde896) - **mysql**: SHOW SCHEMAS *(PR [#2916](https://github.com/tobymao/sqlglot/pull/2916) by [@barakalon](https://github.com/barakalon))*\n- [`15fdff2`](https://github.com/tobymao/sqlglot/commit/15fdff2df3363ab8d3595e7eeb8baee65e525733) - **optimizer**: don't remove NOT parenthesis *(PR [#2917](https://github.com/tobymao/sqlglot/pull/2917) by [@barakalon](https://github.com/barakalon))*\n- [`d20d826`](https://github.com/tobymao/sqlglot/commit/d20d826e9cc4a9b0d636a9b56b5547cd906a5903) - have table exclude this if schema target *(PR [#2921](https://github.com/tobymao/sqlglot/pull/2921) by [@eakmanrq](https://github.com/eakmanrq))*\n\n### :recycle: Refactors\n- [`b4e8868`](https://github.com/tobymao/sqlglot/commit/b4e886877ecfbafdd64c515c765c3c54764bd987) - improve transpilation of JSON paths across dialects *(PR [#2883](https://github.com/tobymao/sqlglot/pull/2883) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2839](https://github.com/tobymao/sqlglot/issues/2839) opened by [@FlaviovLeal](https://github.com/FlaviovLeal)*\n- [`9481f94`](https://github.com/tobymao/sqlglot/commit/9481f946b068e43d99c9aaae6e1c59abf384eeac) - several JSON path improvements *(PR [#2914](https://github.com/tobymao/sqlglot/pull/2914) by [@georgesittas](https://github.com/georgesittas))*\n- [`326aa31`](https://github.com/tobymao/sqlglot/commit/326aa31e32e511f4e40d3a5a7b1d599b5e2c1307) - deprecate case where transforms can be plain strs *(PR [#2919](https://github.com/tobymao/sqlglot/pull/2919) by [@georgesittas](https://github.com/georgesittas))*\n- [`15582f4`](https://github.com/tobymao/sqlglot/commit/15582f40bd18da3fa7adbe454b401ef8d31a131e) - move JSON path generation logic in Generator *(PR [#2920](https://github.com/tobymao/sqlglot/pull/2920) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`afb4f9b`](https://github.com/tobymao/sqlglot/commit/afb4f9bfe074200e60b5a870267fe21aa04a87c5) - switch to ruff *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f9fdf7b`](https://github.com/tobymao/sqlglot/commit/f9fdf7b3bb25aa7e830b70600728bb35ee1e4ff7) - switch to ruff *(PR [#2912](https://github.com/tobymao/sqlglot/pull/2912) by [@tobymao](https://github.com/tobymao))*\n- [`71c33fa`](https://github.com/tobymao/sqlglot/commit/71c33fa13b9c416ae50acb10a9b08dcfcfd35f92) - pandas warning *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v20.11.0] - 2024-01-29\n### :boom: BREAKING CHANGES\n- due to [`eb8b40a`](https://github.com/tobymao/sqlglot/commit/eb8b40aade54eec8b34a808dda95420dcf7a7e13) - deprecate NULL, TRUE, FALSE constant expressions *(PR [#2884](https://github.com/tobymao/sqlglot/pull/2884) by [@georgesittas](https://github.com/georgesittas))*:\n\n  deprecate NULL, TRUE, FALSE constant expressions (#2884)\n\n\n### :sparkles: New Features\n- [`3a8ed85`](https://github.com/tobymao/sqlglot/commit/3a8ed8573d5562110b312586ae6fca22038e5d05) - add alter table alter comment closes [#2889](https://github.com/tobymao/sqlglot/pull/2889) *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :bug: Bug Fixes\n- [`dc2d7d7`](https://github.com/tobymao/sqlglot/commit/dc2d7d7dd4253fe6b247d534bd92327f186e9aa8) - **tsql**: len text transpilation closes [#2885](https://github.com/tobymao/sqlglot/pull/2885) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`ad50f47`](https://github.com/tobymao/sqlglot/commit/ad50f479c47d5b4990f1b41272c69079a453cf21) - type imports *(PR [#2886](https://github.com/tobymao/sqlglot/pull/2886) by [@tobymao](https://github.com/tobymao))*\n- [`e4fb7f6`](https://github.com/tobymao/sqlglot/commit/e4fb7f6e1b8ab15ceb5acc6a93256c849c738740) - union should return union *(commit by [@tobymao](https://github.com/tobymao))*\n- [`8f795ea`](https://github.com/tobymao/sqlglot/commit/8f795ea00164b69acba093c3684ab54b62138e8e) - don't expand star except/replace refs *(commit by [@tobymao](https://github.com/tobymao))*\n- [`218121c`](https://github.com/tobymao/sqlglot/commit/218121c274656a1b252143a7d0fc2d73407115ca) - alter table cluster by closes [#2887](https://github.com/tobymao/sqlglot/pull/2887) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`5cec283`](https://github.com/tobymao/sqlglot/commit/5cec2839f8ed8477821bf766025f4b5de0621fe2) - bigquery script if statement closes [#2888](https://github.com/tobymao/sqlglot/pull/2888) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`5fc7791`](https://github.com/tobymao/sqlglot/commit/5fc7791a4d19d704c0d4fafe8924cf8f76fcb867) - all view column options without types closes [#2891](https://github.com/tobymao/sqlglot/pull/2891) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`102304e`](https://github.com/tobymao/sqlglot/commit/102304e28f2ed7126840789837ed797a75bae44e) - **postgres**: generate CurrentUser without parentheses closes [#2893](https://github.com/tobymao/sqlglot/pull/2893) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`43902db`](https://github.com/tobymao/sqlglot/commit/43902db25706a2434fe7e9ba39addd1c31c2aa64) - error level ignore comments closes [#2895](https://github.com/tobymao/sqlglot/pull/2895) *(commit by [@tobymao](https://github.com/tobymao))*\n\n### :wrench: Chores\n- [`eb8b40a`](https://github.com/tobymao/sqlglot/commit/eb8b40aade54eec8b34a808dda95420dcf7a7e13) - deprecate NULL, TRUE, FALSE constant expressions *(PR [#2884](https://github.com/tobymao/sqlglot/pull/2884) by [@georgesittas](https://github.com/georgesittas))*\n- [`29cddd5`](https://github.com/tobymao/sqlglot/commit/29cddd5c3f5401033197d47e7544cedd91b8046c) - change warning message *(commit by [@tobymao](https://github.com/tobymao))*\n- [`9eac93e`](https://github.com/tobymao/sqlglot/commit/9eac93e0acd5ae8b034045759fc48937586cbc2e) - upgrade black *(commit by [@tobymao](https://github.com/tobymao))*\n- [`4f3fac7`](https://github.com/tobymao/sqlglot/commit/4f3fac7815e0d8206c80f1f255336ab630503d4d) - cleanup command parsing and warnings *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v20.10.0] - 2024-01-24\n### :boom: BREAKING CHANGES\n- due to [`1f5fc39`](https://github.com/tobymao/sqlglot/commit/1f5fc39c10b92b94bd94afa5fd038fdb9afeb4b4) - jsonpath parsing *(PR [#2867](https://github.com/tobymao/sqlglot/pull/2867) by [@tobymao](https://github.com/tobymao))*:\n\n  jsonpath parsing (#2867)\n\n\n### :sparkles: New Features\n- [`89b439e`](https://github.com/tobymao/sqlglot/commit/89b439e2f93b6c3bedb4e58fe4b5014d42dd5080) - **postgres**: support the INCLUDE clause in INDEX creation *(PR [#2857](https://github.com/tobymao/sqlglot/pull/2857) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2855](undefined) opened by [@dezhin](https://github.com/dezhin)*\n- [`90ffff8`](https://github.com/tobymao/sqlglot/commit/90ffff83266b5714b1371a576d9484dfbe4be155) - **clickhouse**: AggregateFunction data type *(PR [#2832](https://github.com/tobymao/sqlglot/pull/2832) by [@pkit](https://github.com/pkit))*\n- [`326d3ae`](https://github.com/tobymao/sqlglot/commit/326d3ae7113cca4a67cca3ab3335f7b8dde91f71) - improve transpilation of Spark's TO_UTC_TIMESTAMP *(PR [#2861](https://github.com/tobymao/sqlglot/pull/2861) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2846](undefined) opened by [@hdemers](https://github.com/hdemers)*\n- [`6d03587`](https://github.com/tobymao/sqlglot/commit/6d035871feee2b206d73e37b5f9b8c99fc40708f) - **snowflake**: SHOW SCHEMAS/OBJECTS *(PR [#2845](https://github.com/tobymao/sqlglot/pull/2845) by [@tekumara](https://github.com/tekumara))*\n  - :arrow_lower_right: *addresses issue [#2784](undefined) opened by [@tekumara](https://github.com/tekumara)*\n- [`d5a08b8`](https://github.com/tobymao/sqlglot/commit/d5a08b8f3a1cdd87e9560c0fe4f11b0f1586978b) - **optimizer**: improve struct type annotation support for EQ-delimited kv pairs *(PR [#2863](https://github.com/tobymao/sqlglot/pull/2863) by [@fool1280](https://github.com/fool1280))*\n- [`1f5fc39`](https://github.com/tobymao/sqlglot/commit/1f5fc39c10b92b94bd94afa5fd038fdb9afeb4b4) - jsonpath parsing *(PR [#2867](https://github.com/tobymao/sqlglot/pull/2867) by [@tobymao](https://github.com/tobymao))*\n- [`7fd9045`](https://github.com/tobymao/sqlglot/commit/7fd9045488beb88b2726ae906b8769b7963d1b37) - add support for rename column *(PR [#2866](https://github.com/tobymao/sqlglot/pull/2866) by [@gableh](https://github.com/gableh))*\n- [`89b781b`](https://github.com/tobymao/sqlglot/commit/89b781b991ce264cd7f8c44fa67860eb9a587b07) - **postgres**: add support for the INHERITS property closes [#2871](https://github.com/tobymao/sqlglot/pull/2871) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`970c202`](https://github.com/tobymao/sqlglot/commit/970c2022a27d4fc355fedb7af830367a5dd96009) - **postgres**: add support for the SET property *(PR [#2873](https://github.com/tobymao/sqlglot/pull/2873) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2872](undefined) opened by [@edongashi](https://github.com/edongashi)*\n- [`6845c37`](https://github.com/tobymao/sqlglot/commit/6845c37e749448972a231926236c08affb71a64f) - make the CREATE parser more lenient *(PR [#2875](https://github.com/tobymao/sqlglot/pull/2875) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`bf03a45`](https://github.com/tobymao/sqlglot/commit/bf03a45d8df9abd63b8102e431c13ca0eb0b0fb0) - **snowflake**: extend _parse_range to gracefully handle colon operator *(PR [#2856](https://github.com/tobymao/sqlglot/pull/2856) by [@georgesittas](https://github.com/georgesittas))*\n- [`ad14f4e`](https://github.com/tobymao/sqlglot/commit/ad14f4ed1ed3870ae0d7370643c67c235fc89b4b) - qualify alter table table refs in optimizer qualify *(PR [#2862](https://github.com/tobymao/sqlglot/pull/2862) by [@z3z1ma](https://github.com/z3z1ma))*\n- [`8599903`](https://github.com/tobymao/sqlglot/commit/859990356210f3091b1b66647c1a674fdb0f2ad9) - **optimizer**: compute external columns for union sopes correctly *(PR [#2864](https://github.com/tobymao/sqlglot/pull/2864) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2860](undefined) opened by [@derekpaulsen](https://github.com/derekpaulsen)*\n- [`3e065f9`](https://github.com/tobymao/sqlglot/commit/3e065f9503607e6c620fc187e2bdc2d45f7fa1dd) - **optimizer**: don't copy projection in qualify_outputs when attaching alias *(PR [#2868](https://github.com/tobymao/sqlglot/pull/2868) by [@georgesittas](https://github.com/georgesittas))*\n- [`a642758`](https://github.com/tobymao/sqlglot/commit/a6427585e16fa4b5adc9e01cc22baeb09b2f69bb) - avoid dag cycle with unnesting subqueries closes [#2876](https://github.com/tobymao/sqlglot/pull/2876) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`0648453`](https://github.com/tobymao/sqlglot/commit/06484532f0e222004e844501a192b8d4aec654c7) - set div type on multiplication closes [#2878](https://github.com/tobymao/sqlglot/pull/2878) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`b7fb800`](https://github.com/tobymao/sqlglot/commit/b7fb8006b825ec36d62135877b4e32fccad8d044) - **oracle**: generate with time zone for timestamptz fixes [#2879](https://github.com/tobymao/sqlglot/pull/2879) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`8770e3b`](https://github.com/tobymao/sqlglot/commit/8770e3b7855110a82cb3bc05f3cb6c36a88cfdb2) - **optimizer**: don't qualify CTEs for DDL/DML statements *(PR [#2880](https://github.com/tobymao/sqlglot/pull/2880) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2877](undefined) opened by [@hsheth2](https://github.com/hsheth2)*\n\n### :wrench: Chores\n- [`d55cfba`](https://github.com/tobymao/sqlglot/commit/d55cfba7226a61cc8419b3a83eaa0a2ead23be10) - move postgres tests *(commit by [@tobymao](https://github.com/tobymao))*\n- [`0e43c58`](https://github.com/tobymao/sqlglot/commit/0e43c58a38bb6af63f653062e79626b85f605b63) - **parser**: warn when parsing (>1 tokens) SQL into exp.Command *(PR [#2874](https://github.com/tobymao/sqlglot/pull/2874) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v20.9.0] - 2024-01-18\n### :boom: BREAKING CHANGES\n- due to [`1be93e4`](https://github.com/tobymao/sqlglot/commit/1be93e45d8347e5fa8a4e39dad625c6dd66ea461) - properly support all unix time scales *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  properly support all unix time scales\n\n\n### :sparkles: New Features\n- [`816976f`](https://github.com/tobymao/sqlglot/commit/816976f52865fb8ade580c727a890a90378c8e50) - extend submodule annotate_types to handle STRUCT *(PR [#2783](https://github.com/tobymao/sqlglot/pull/2783) by [@fool1280](https://github.com/fool1280))*\n- [`7bce2f6`](https://github.com/tobymao/sqlglot/commit/7bce2f6abe79dfd8064c625294d94364042207c5) - **oracle**: add support for ORDER SIBLINGS BY clause *(PR [#2821](https://github.com/tobymao/sqlglot/pull/2821) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2819](undefined) opened by [@Shweta-BI-Lead](https://github.com/Shweta-BI-Lead)*\n- [`ce8d254`](https://github.com/tobymao/sqlglot/commit/ce8d254305f56724982eed8e099ab1abeb8750a1) - **snowflake**: parse RM/REMOVE as commands *(PR [#2825](https://github.com/tobymao/sqlglot/pull/2825) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2824](undefined) opened by [@sfc-gh-jlambert](https://github.com/sfc-gh-jlambert)*\n- [`1902778`](https://github.com/tobymao/sqlglot/commit/19027786facf8ff730af49c1693149e244502cb0) - add support for multi-unit intervals *(PR [#2822](https://github.com/tobymao/sqlglot/pull/2822) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2793](undefined) opened by [@nikmalviya](https://github.com/nikmalviya)*\n- [`a537898`](https://github.com/tobymao/sqlglot/commit/a53789840b56be747fa5d670a9d5ea120aee371a) - add support for DESCRIBE EXTENDED *(PR [#2828](https://github.com/tobymao/sqlglot/pull/2828) by [@georgesittas](https://github.com/georgesittas))*\n- [`6e50759`](https://github.com/tobymao/sqlglot/commit/6e50759fb19c8d00825f626fb8c1ab6792fabd56) - **clickhouse**: support Date32 type *(PR [#2830](https://github.com/tobymao/sqlglot/pull/2830) by [@pkit](https://github.com/pkit))*\n- [`9560e8f`](https://github.com/tobymao/sqlglot/commit/9560e8fa93d6ac7f4f015bd55091d2fe75e85508) - add support for Heredocs in Databricks Python UDFs *(PR [#2801](https://github.com/tobymao/sqlglot/pull/2801) by [@viethungle-vt1401](https://github.com/viethungle-vt1401))*\n- [`52ed590`](https://github.com/tobymao/sqlglot/commit/52ed590b0fa75ef8a9f6e4cb2fb48b4fff65996f) - transpile SELECT .. INTO to dialects that do not support it *(PR [#2820](https://github.com/tobymao/sqlglot/pull/2820) by [@giorgosnikolaou](https://github.com/giorgosnikolaou))*\n- [`ea536c4`](https://github.com/tobymao/sqlglot/commit/ea536c4bd7bae0b2916d4bdf9a0ae6a7c5106135) - remove target alias in trino merge *(PR [#2852](https://github.com/tobymao/sqlglot/pull/2852) by [@eakmanrq](https://github.com/eakmanrq))*\n\n### :bug: Bug Fixes\n- [`6ddbefc`](https://github.com/tobymao/sqlglot/commit/6ddbefcb08ef933454ff8501ac4a3ea4cba2fe60) - **snowflake**: apply range parser after colon, if any *(PR [#2800](https://github.com/tobymao/sqlglot/pull/2800) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2798](undefined) opened by [@mbarugelCA](https://github.com/mbarugelCA)*\n- [`0c4f44e`](https://github.com/tobymao/sqlglot/commit/0c4f44e8027b28613c72285313493c4683c65275) - **oracle**: regexp_replace replacement is optional closes [#2803](https://github.com/tobymao/sqlglot/pull/2803) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`5072d5a`](https://github.com/tobymao/sqlglot/commit/5072d5af9a9f629e857071a66228317afd89b1a6) - **oracle**: improve parsing of JSON_OBJECT[AGG] functions *(PR [#2807](https://github.com/tobymao/sqlglot/pull/2807) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2806](undefined) opened by [@Shweta-BI-Lead](https://github.com/Shweta-BI-Lead)*\n- [`ea39f10`](https://github.com/tobymao/sqlglot/commit/ea39f10150916f2624cb6efcefb6752154c2f88c) - **optimizer**: pushdown predicates more conservatively to avoid DAG cycles *(PR [#2808](https://github.com/tobymao/sqlglot/pull/2808) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2797](undefined) opened by [@Konntroll](https://github.com/Konntroll)*\n- [`ea58003`](https://github.com/tobymao/sqlglot/commit/ea58003caeed17861085825c19a1a5823e065691) - **snowflake**: insert overwrite into closes [#2815](https://github.com/tobymao/sqlglot/pull/2815) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`d5fa5be`](https://github.com/tobymao/sqlglot/commit/d5fa5be010a2656ead5524a0d756da6e25ab31dc) - **optimizer**: handle table alias columns for (UN)PIVOTs *(PR [#2816](https://github.com/tobymao/sqlglot/pull/2816) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2791](undefined) opened by [@billstark](https://github.com/billstark)*\n- [`24177f3`](https://github.com/tobymao/sqlglot/commit/24177f39b448490618d3b04f8b8ad75ec2069fd3) - unnest star expansion closes [#2811](https://github.com/tobymao/sqlglot/pull/2811) *(PR [#2818](https://github.com/tobymao/sqlglot/pull/2818) by [@tobymao](https://github.com/tobymao))*\n- [`3dba6c1`](https://github.com/tobymao/sqlglot/commit/3dba6c194e2fff90f93f0370255a94a1b2a2365a) - add parentheses to no_safe_divide args *(PR [#2826](https://github.com/tobymao/sqlglot/pull/2826) by [@j1ah0ng](https://github.com/j1ah0ng))*\n- [`ed68d6b`](https://github.com/tobymao/sqlglot/commit/ed68d6be51e8054eaf8b7bf64048f20c999c6cd2) - **clickhouse**: add ipv4/6 data type parser *(PR [#2829](https://github.com/tobymao/sqlglot/pull/2829) by [@pkit](https://github.com/pkit))*\n- [`70b280f`](https://github.com/tobymao/sqlglot/commit/70b280f24df0f4a0d4cc8262d72ce46412f76be3) - allow insert columns to have spaces *(commit by [@tobymao](https://github.com/tobymao))*\n- [`57917b8`](https://github.com/tobymao/sqlglot/commit/57917b88ddd9d7d85bc76fc8cb46ffcbf228453d) - **oracle**: TO_TIMESTAMP not parsed as StrToTime *(PR [#2833](https://github.com/tobymao/sqlglot/pull/2833) by [@pkit](https://github.com/pkit))*\n  - :arrow_lower_right: *fixes issue [#2831](undefined) opened by [@Rhiyo](https://github.com/Rhiyo)*\n- [`9960e11`](https://github.com/tobymao/sqlglot/commit/9960e114818640f26aaa6d911ad3e7ee53df1842) - **optimizer**: annotate struct value without alias correctly *(PR [#2812](https://github.com/tobymao/sqlglot/pull/2812) by [@fool1280](https://github.com/fool1280))*\n- [`a6d396b`](https://github.com/tobymao/sqlglot/commit/a6d396b79cfa199a66b04af9ed62bcd7cd619096) - **doris**: add transformation of aggregation function and last_day function *(PR [#2835](https://github.com/tobymao/sqlglot/pull/2835) by [@echo-hhj](https://github.com/echo-hhj))*\n- [`8cc252b`](https://github.com/tobymao/sqlglot/commit/8cc252b61b418004bbd6380a8447cb383cf51282) - interval without unit alias closes [#2838](https://github.com/tobymao/sqlglot/pull/2838) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`607817f`](https://github.com/tobymao/sqlglot/commit/607817f7e43edefe0a077bfeb81a77dd78e170e5) - schema with period name closes [#2842](https://github.com/tobymao/sqlglot/pull/2842) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`1be93e4`](https://github.com/tobymao/sqlglot/commit/1be93e45d8347e5fa8a4e39dad625c6dd66ea461) - properly support all unix time scales *(commit by [@tobymao](https://github.com/tobymao))*\n- [`a657fc0`](https://github.com/tobymao/sqlglot/commit/a657fc0ea21aff7452f292fecfcb4bc08ca2e4e9) - **clickhouse,doris**: fix the transformation of ArraySum *(PR [#2843](https://github.com/tobymao/sqlglot/pull/2843) by [@echo-hhj](https://github.com/echo-hhj))*\n- [`c92888c`](https://github.com/tobymao/sqlglot/commit/c92888c6b49d2ba60ce789281535679fd93cd235) - **parser**: fix order of query modifier parsing for nested subqueries *(PR [#2851](https://github.com/tobymao/sqlglot/pull/2851) by [@georgesittas](https://github.com/georgesittas))*\n- [`7949a4f`](https://github.com/tobymao/sqlglot/commit/7949a4f295fc0a9f0becca9b6460f8517ec733f1) - **clickhouse**: ensure arraySum generation is preserved, add tests *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`17a6ac6`](https://github.com/tobymao/sqlglot/commit/17a6ac6f5fb96a42668842b093a823662b5850b8) - move comment in expr as alias next to the alias *(PR [#2853](https://github.com/tobymao/sqlglot/pull/2853) by [@georgesittas](https://github.com/georgesittas))*\n\n### :recycle: Refactors\n- [`9e5ae50`](https://github.com/tobymao/sqlglot/commit/9e5ae50e02879cfb4915584df90f8dcfadbca321) - use flag instead of regex *(commit by [@tobymao](https://github.com/tobymao))*\n- [`5c13a1e`](https://github.com/tobymao/sqlglot/commit/5c13a1e8e2ede284d12920734cea8ff82ebaf054) - simplify merge without target transformation *(PR [#2854](https://github.com/tobymao/sqlglot/pull/2854) by [@georgesittas](https://github.com/georgesittas))*\n\n### :wrench: Chores\n- [`5996a69`](https://github.com/tobymao/sqlglot/commit/5996a6949979dcfceee133f943a010ec4820e808) - **presto**: get rid of assert in ELEMENT_AT parser *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`f9a02ec`](https://github.com/tobymao/sqlglot/commit/f9a02ecc44f4d0139aae5edda922cc95d4a3e892) - fix table, column instantiation in schema normalization tests *(PR [#2817](https://github.com/tobymao/sqlglot/pull/2817) by [@georgesittas](https://github.com/georgesittas))*\n\n\n## [v20.8.0] - 2024-01-08\n### :boom: BREAKING CHANGES\n- due to [`68e1214`](https://github.com/tobymao/sqlglot/commit/68e121462b2c3dc388f3ae0d1d392ee8afc63133) - column field typing *(commit by [@tobymao](https://github.com/tobymao))*:\n\n  column field typing\n\n\n### :sparkles: New Features\n- [`2d822f3`](https://github.com/tobymao/sqlglot/commit/2d822f3972bf0f77baaadb135a5e19c1bc0c4040) - improve support for Doris' TO_DATE, Oracle's SYSDATE *(PR [#2775](https://github.com/tobymao/sqlglot/pull/2775) by [@georgesittas](https://github.com/georgesittas))*\n- [`7187215`](https://github.com/tobymao/sqlglot/commit/71872159114b85324d08191b854ab8462a298742) - desc builder *(commit by [@tobymao](https://github.com/tobymao))*\n- [`ba62639`](https://github.com/tobymao/sqlglot/commit/ba62639aa6d81a062c867ebe20af64446b931b7d) - add support for CREATE FUNCTION (SQL) characteristics for MySQL and Databricks *(PR [#2777](https://github.com/tobymao/sqlglot/pull/2777) by [@viethungle-vt1401](https://github.com/viethungle-vt1401))*\n  - :arrow_lower_right: *addresses issue [#1980](undefined) opened by [@xinglin-zhao](https://github.com/xinglin-zhao)*\n- [`963e2dc`](https://github.com/tobymao/sqlglot/commit/963e2dc9a4b699938d0477bc379e9d2da01818af) - **snowflake**: add support for SHOW COLUMNS  *(PR [#2778](https://github.com/tobymao/sqlglot/pull/2778) by [@andrew-sha](https://github.com/andrew-sha))*\n- [`46c9733`](https://github.com/tobymao/sqlglot/commit/46c973309850d4e32b1a0f0594d7b143eb14d059) - **tsql**: round func closes [#2790](https://github.com/tobymao/sqlglot/pull/2790) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`2dfb7e8`](https://github.com/tobymao/sqlglot/commit/2dfb7e806c52715d9e83d2201ed63974ff238ad3) - **optimizer**: add support for the UNPIVOT operator *(PR [#2771](https://github.com/tobymao/sqlglot/pull/2771) by [@georgesittas](https://github.com/georgesittas))*\n\n### :bug: Bug Fixes\n- [`68e1214`](https://github.com/tobymao/sqlglot/commit/68e121462b2c3dc388f3ae0d1d392ee8afc63133) - column field typing *(commit by [@tobymao](https://github.com/tobymao))*\n- [`3f31706`](https://github.com/tobymao/sqlglot/commit/3f31706b913e53d13e45fe94b41ee115cc7bd5c5) - tsql exec command [#2772](https://github.com/tobymao/sqlglot/pull/2772) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f6cbadb`](https://github.com/tobymao/sqlglot/commit/f6cbadb7a293035720460f869dd1a6d48a707d04) - **snowflake**: add a couple of special fn types *(PR [#2774](https://github.com/tobymao/sqlglot/pull/2774) by [@georgesittas](https://github.com/georgesittas))*\n- [`0634f73`](https://github.com/tobymao/sqlglot/commit/0634f738d3a935cde3e7df1671c65e666c7a52b4) - **optimizer**: replace star with outer column list *(PR [#2776](https://github.com/tobymao/sqlglot/pull/2776) by [@georgesittas](https://github.com/georgesittas))*\n- [`d31ae0d`](https://github.com/tobymao/sqlglot/commit/d31ae0decb46678851744356c7b113f8c1c3e8c9) - allow string aliases closes [#2788](https://github.com/tobymao/sqlglot/pull/2788) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`8f8f00e`](https://github.com/tobymao/sqlglot/commit/8f8f00ec66beb6dc3d90898ead29828eee8f5e32) - don't transform null ordering with positional orders closes [#2779](https://github.com/tobymao/sqlglot/pull/2779) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`f85ce3b`](https://github.com/tobymao/sqlglot/commit/f85ce3b354366b2e206e6d2815f34a8e345d10ba) - **tsql**: gracefully handle complex formats in FORMAT *(PR [#2794](https://github.com/tobymao/sqlglot/pull/2794) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2787](undefined) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`a2499f5`](https://github.com/tobymao/sqlglot/commit/a2499f591eeb7538db86abd8cc9341c8d91e325d) - **tsql**: generate correct TRIM syntax closes [#2786](https://github.com/tobymao/sqlglot/pull/2786) *(commit by [@georgesittas](https://github.com/georgesittas))*\n- [`59ecd2f`](https://github.com/tobymao/sqlglot/commit/59ecd2f17cac61b1ed7d206437d2fab4497e58fa) - **clickhouse**: allow transpilation of countIf, fix 2 arg variant parsing *(PR [#2795](https://github.com/tobymao/sqlglot/pull/2795) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2792](undefined) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`3f7748b`](https://github.com/tobymao/sqlglot/commit/3f7748b08d3f6616d8ea7eac6ded4980b6507ddf) - don't transpile nulls last in window specs *(commit by [@tobymao](https://github.com/tobymao))*\n\n\n## [v20.7.1] - 2024-01-04\n### :bug: Bug Fixes\n- [`9d94b9c`](https://github.com/tobymao/sqlglot/commit/9d94b9c77f5fbd22ad147789f23e971ec6bb3c72) - don't normalize schema if normalize is set to false *(PR [#2767](https://github.com/tobymao/sqlglot/pull/2767) by [@tobymao](https://github.com/tobymao))*\n\n\n## [v20.6.0] - 2024-01-04\n### :boom: BREAKING CHANGES\n- due to [`4648c6a`](https://github.com/tobymao/sqlglot/commit/4648c6acbeab8d5155be3f8e53d11d8f00c33a2e) - set sample clause keyword(s) as class constant to enable transpilation *(PR [#2750](https://github.com/tobymao/sqlglot/pull/2750) by [@georgesittas](https://github.com/georgesittas))*:\n\n  set sample clause keyword(s) as class constant to enable transpilation (#2750)\n\n- due to [`0b6bdc4`](https://github.com/tobymao/sqlglot/commit/0b6bdc4513aa417dae0a00565b56e78b544306e7) - improve transpilation of JSON value extraction *(PR [#2744](https://github.com/tobymao/sqlglot/pull/2744) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve transpilation of JSON value extraction (#2744)\n\n- due to [`862b305`](https://github.com/tobymao/sqlglot/commit/862b305db47c0cea4a66b61211a3ed088e935ea5) - improve table sampling transpilation *(PR [#2761](https://github.com/tobymao/sqlglot/pull/2761) by [@georgesittas](https://github.com/georgesittas))*:\n\n  improve table sampling transpilation (#2761)\n\n\n### :sparkles: New Features\n- [`202f035`](https://github.com/tobymao/sqlglot/commit/202f035e88e78c15b34c9cc85c4396eab8da1d29) - clickhouse: add aggregate parsing *(PR [#2734](https://github.com/tobymao/sqlglot/pull/2734) by [@pkit](https://github.com/pkit))*\n- [`e772e26`](https://github.com/tobymao/sqlglot/commit/e772e262c551f6aa5bb726579253a55db5686da3) - guess the correct dialect in case we get an unknown one *(PR [#2753](https://github.com/tobymao/sqlglot/pull/2753) by [@georgesittas](https://github.com/georgesittas))*\n- [`7a07862`](https://github.com/tobymao/sqlglot/commit/7a07862dba744a969c98b2c1069531423673461d) - implement to_s method in Expression for verbose repr mode *(PR [#2756](https://github.com/tobymao/sqlglot/pull/2756) by [@georgesittas](https://github.com/georgesittas))*\n- [`4072184`](https://github.com/tobymao/sqlglot/commit/4072184cc498a509bceba2a3dfe12f43794273df) - improve transpilation of TIME/TIMESTAMP_FROM_PARTS *(PR [#2755](https://github.com/tobymao/sqlglot/pull/2755) by [@georgesittas](https://github.com/georgesittas))*\n- [`3bd811d`](https://github.com/tobymao/sqlglot/commit/3bd811d67e7001046812f2a8590bd07e23b81c88) - **optimizer**: allow star expansion to be turned off *(PR [#2762](https://github.com/tobymao/sqlglot/pull/2762) by [@georgesittas](https://github.com/georgesittas))*\n- [`c246285`](https://github.com/tobymao/sqlglot/commit/c24628531bbd587473e0a43ded7ba8b5e4f35cd8) - bigquery unix_date closes [#2758](https://github.com/tobymao/sqlglot/pull/2758) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`a2abbc7`](https://github.com/tobymao/sqlglot/commit/a2abbc773fb330e669c81abc115a81e1055a060f) - improve transpilation of LAST_DAY *(PR [#2766](https://github.com/tobymao/sqlglot/pull/2766) by [@georgesittas](https://github.com/georgesittas))*\n- [`7e7ac65`](https://github.com/tobymao/sqlglot/commit/7e7ac65bb67ef5a45ef487859a9cff4f2d0fc07a) - **snowflake**: add support for OBJECT_CONSTRUCT_KEEP_NULL *(PR [#2769](https://github.com/tobymao/sqlglot/pull/2769) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2768](undefined) opened by [@tekumara](https://github.com/tekumara)*\n\n### :bug: Bug Fixes\n- [`ed972f9`](https://github.com/tobymao/sqlglot/commit/ed972f9f50fcdc612b7770739a249a918c4d4011) - alter table rename should not qualify with db in postgres *(PR [#2736](https://github.com/tobymao/sqlglot/pull/2736) by [@z3z1ma](https://github.com/z3z1ma))*\n- [`1ebfb36`](https://github.com/tobymao/sqlglot/commit/1ebfb3688975e420a70bac10c49ad127446c4c65) - else interval *(commit by [@tobymao](https://github.com/tobymao))*\n- [`a43174f`](https://github.com/tobymao/sqlglot/commit/a43174f8ebdb4a51ad12d7dc9c332372a5a0bd84) - generate `CROSS JOIN` instead of comma in `explode_to_unnest` transformation *(PR [#2739](https://github.com/tobymao/sqlglot/pull/2739) by [@cpcloud](https://github.com/cpcloud))*\n  - :arrow_lower_right: *fixes issue [#2735](undefined) opened by [@cpcloud](https://github.com/cpcloud)*\n- [`e543c55`](https://github.com/tobymao/sqlglot/commit/e543c558a3efea960028bf4c9864cad48e616e82) - interval is null *(commit by [@tobymao](https://github.com/tobymao))*\n- [`fb3188f`](https://github.com/tobymao/sqlglot/commit/fb3188f43bfdef2fb315b8b1280aaa207bd7888a) - lineage closes [#2742](https://github.com/tobymao/sqlglot/pull/2742) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`b608b2d`](https://github.com/tobymao/sqlglot/commit/b608b2d944934d9da4a2cb373bdf69322f25041a) - **duckdb**: percentile_cont closes [#2741](https://github.com/tobymao/sqlglot/pull/2741) *(commit by [@tobymao](https://github.com/tobymao))*\n- [`0b6bdc4`](https://github.com/tobymao/sqlglot/commit/0b6bdc4513aa417dae0a00565b56e78b544306e7) - improve transpilation of JSON value extraction *(PR [#2744](https://github.com/tobymao/sqlglot/pull/2744) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2738](undefined) opened by [@tekumara](https://github.com/tekumara)*\n- [`33d6e5f`](https://github.com/tobymao/sqlglot/commit/33d6e5f2636451ffcdd914c100a446246d8031df) - **bigquery**: enable transpilation of single-argument TIME func *(PR [#2752](https://github.com/tobymao/sqlglot/pull/2752) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2749](undefined) opened by [@jherrmannNetfonds](https://github.com/jherrmannNetfonds)*\n- [`72f8cfa`](https://github.com/tobymao/sqlglot/commit/72f8cfa2c156efb586bc91d20efd8d0cf9c18735) - **snowflake**: parse two argument version of TIMESTAMP_FROM_PARTS *(PR [#2754](https://github.com/tobymao/sqlglot/pull/2754) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2751](undefined) opened by [@notgerry](https://github.com/notgerry)*\n- [`2a94f2b`](https://github.com/tobymao/sqlglot/commit/2a94f2ba0d93ee1454a2d19cc8577e211fd5cbe0) - **bigquery**: fix parsing of COUNTIF *(PR [#2765](https://github.com/tobymao/sqlglot/pull/2765) by [@giovannipcarvalho](https://github.com/giovannipcarvalho))*\n  - :arrow_lower_right: *fixes issue [#2764](undefined) opened by [@giovannipcarvalho](https://github.com/giovannipcarvalho)*\n- [`862b305`](https://github.com/tobymao/sqlglot/commit/862b305db47c0cea4a66b61211a3ed088e935ea5) - improve table sampling transpilation *(PR [#2761](https://github.com/tobymao/sqlglot/pull/2761) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *fixes issue [#2757](undefined) opened by [@tekumara](https://github.com/tekumara)*\n\n### :recycle: Refactors\n- [`4648c6a`](https://github.com/tobymao/sqlglot/commit/4648c6acbeab8d5155be3f8e53d11d8f00c33a2e) - set sample clause keyword(s) as class constant to enable transpilation *(PR [#2750](https://github.com/tobymao/sqlglot/pull/2750) by [@georgesittas](https://github.com/georgesittas))*\n  - :arrow_lower_right: *addresses issue [#2747](undefined) opened by [@tekumara](https://github.com/tekumara)*\n\n[v11.6.3]: https://github.com/tobymao/sqlglot/compare/v11.6.2...v11.6.3\n\n[v11.7.0]: https://github.com/tobymao/sqlglot/compare/v11.6.3...v11.7.0\n[v12.0.0]: https://github.com/tobymao/sqlglot/compare/v11.7.1...v12.0.0\n[v12.1.0]: https://github.com/tobymao/sqlglot/compare/v12.0.0...v12.1.0\n[v12.2.0]: https://github.com/tobymao/sqlglot/compare/v12.1.0...v12.2.0\n[v12.4.0]: https://github.com/tobymao/sqlglot/compare/v12.3.0...v12.4.0\n[v13.0.0]: https://github.com/tobymao/sqlglot/compare/v12.4.0...v13.0.0\n[v13.0.1]: https://github.com/tobymao/sqlglot/compare/v13.0.0...v13.0.1\n[v13.0.2]: https://github.com/tobymao/sqlglot/compare/v13.0.1...v13.0.2\n[v13.1.0]: https://github.com/tobymao/sqlglot/compare/v13.0.2...v13.1.0\n[v13.2.0]: https://github.com/tobymao/sqlglot/compare/v13.1.0...v13.2.0\n[v13.2.1]: https://github.com/tobymao/sqlglot/compare/v13.2.0...v13.2.1\n[v13.2.2]: https://github.com/tobymao/sqlglot/compare/v13.2.1...v13.2.2\n[v13.3.0]: https://github.com/tobymao/sqlglot/compare/v13.2.2...v13.3.0\n[v13.3.1]: https://github.com/tobymao/sqlglot/compare/v13.3.0...v13.3.1\n[v14.0.0]: https://github.com/tobymao/sqlglot/compare/v13.3.1...v14.0.0\n[v14.1.0]: https://github.com/tobymao/sqlglot/compare/show...v14.1.0\n[v14.1.1]: https://github.com/tobymao/sqlglot/compare/v14.1.0...v14.1.1\n[v15.0.0]: https://github.com/tobymao/sqlglot/compare/v14.1.1...v15.0.0\n[v15.1.0]: https://github.com/tobymao/sqlglot/compare/v15.0.0...v15.1.0\n[v15.2.0]: https://github.com/tobymao/sqlglot/compare/v15.1.0...v15.2.0\n[v15.3.0]: https://github.com/tobymao/sqlglot/compare/v15.2.0...v15.3.0\n[v16.0.0]: https://github.com/tobymao/sqlglot/compare/v15.2.0...v16.0.0\n[v16.1.0]: https://github.com/tobymao/sqlglot/compare/v16.0.0...v16.1.0\n[v16.1.1]: https://github.com/tobymao/sqlglot/compare/v16.1.0...v16.1.1\n[v16.1.3]: https://github.com/tobymao/sqlglot/compare/v16.1.2...v16.1.3\n[v16.1.4]: https://github.com/tobymao/sqlglot/compare/v16.1.3...v16.1.4\n[v16.2.0]: https://github.com/tobymao/sqlglot/compare/v16.1.4...v16.2.0\n[v16.2.1]: https://github.com/tobymao/sqlglot/compare/v16.2.0...v16.2.1\n[v16.3.0]: https://github.com/tobymao/sqlglot/compare/v16.2.1...v16.3.0\n[v16.3.1]: https://github.com/tobymao/sqlglot/compare/v16.3.0...v16.3.1\n[v16.4.0]: https://github.com/tobymao/sqlglot/compare/v16.3.1...v16.4.0\n[v16.4.1]: https://github.com/tobymao/sqlglot/compare/v16.4.0...v16.4.1\n[v16.4.2]: https://github.com/tobymao/sqlglot/compare/v16.4.1...v16.4.2\n[v16.5.0]: https://github.com/tobymao/sqlglot/compare/v16.4.2...v16.5.0\n[v16.6.0]: https://github.com/tobymao/sqlglot/compare/v16.5.0...v16.6.0\n[v16.7.0]: https://github.com/tobymao/sqlglot/compare/v16.6.0...v16.7.0\n[v16.7.1]: https://github.com/tobymao/sqlglot/compare/v16.7.0...v16.7.1\n[v16.7.2]: https://github.com/tobymao/sqlglot/compare/v16.7.1...v16.7.2\n[v16.7.4]: https://github.com/tobymao/sqlglot/compare/v16.7.3...v16.7.4\n[v16.7.6]: https://github.com/tobymao/sqlglot/compare/v16.7.5...v16.7.6\n[v16.7.7]: https://github.com/tobymao/sqlglot/compare/v16.7.6...v16.7.7\n[v16.8.0]: https://github.com/tobymao/sqlglot/compare/v16.7.7...v16.8.0\n[v16.8.1]: https://github.com/tobymao/sqlglot/compare/v16.8.0...v16.8.1\n[v17.0.0]: https://github.com/tobymao/sqlglot/compare/v16.8.1...v17.0.0\n[v17.1.0]: https://github.com/tobymao/sqlglot/compare/v17.0.0...v17.1.0\n[v17.2.0]: https://github.com/tobymao/sqlglot/compare/v17.1.0...v17.2.0\n[v17.3.0]: https://github.com/tobymao/sqlglot/compare/v17.2.0...v17.3.0\n[v17.4.0]: https://github.com/tobymao/sqlglot/compare/v17.3.0...v17.4.0\n[v17.4.1]: https://github.com/tobymao/sqlglot/compare/v17.4.0...v17.4.1\n[v17.5.0]: https://github.com/tobymao/sqlglot/compare/v17.4.1...v17.5.0\n[v17.6.0]: https://github.com/tobymao/sqlglot/compare/v17.5.0...v17.6.0\n[v17.6.1]: https://github.com/tobymao/sqlglot/compare/v17.6.0...v17.6.1\n[v17.7.0]: https://github.com/tobymao/sqlglot/compare/v17.6.1...v17.7.0\n[v17.8.0]: https://github.com/tobymao/sqlglot/compare/v17.7.0...v17.8.0\n[v17.8.1]: https://github.com/tobymao/sqlglot/compare/v17.8.0...v17.8.1\n[v17.8.2]: https://github.com/tobymao/sqlglot/compare/v17.8.1...v17.8.2\n[v17.8.3]: https://github.com/tobymao/sqlglot/compare/v17.8.2...v17.8.3\n[v17.8.4]: https://github.com/tobymao/sqlglot/compare/v17.8.3...v17.8.4\n[v17.8.5]: https://github.com/tobymao/sqlglot/compare/v17.8.4...v17.8.5\n[v17.9.0]: https://github.com/tobymao/sqlglot/compare/v17.8.6...v17.9.0\n[v17.9.1]: https://github.com/tobymao/sqlglot/compare/v17.9.0...v17.9.1\n[v17.10.0]: https://github.com/tobymao/sqlglot/compare/v17.9.1...v17.10.0\n[v17.10.1]: https://github.com/tobymao/sqlglot/compare/v17.10.0...v17.10.1\n[v17.10.2]: https://github.com/tobymao/sqlglot/compare/v17.10.1...v17.10.2\n[v17.12.0]: https://github.com/tobymao/sqlglot/compare/v17.11.0...v17.12.0\n[v17.13.0]: https://github.com/tobymao/sqlglot/compare/v17.12.0...v17.13.0\n[v17.14.0]: https://github.com/tobymao/sqlglot/compare/v17.13.0...v17.14.0\n[v17.14.1]: https://github.com/tobymao/sqlglot/compare/v17.14.0...v17.14.1\n[v17.14.2]: https://github.com/tobymao/sqlglot/compare/v17.14.1...v17.14.2\n[v17.15.0]: https://github.com/tobymao/sqlglot/compare/v17.14.2...v17.15.0\n[v17.15.1]: https://github.com/tobymao/sqlglot/compare/v17.15.0...v17.15.1\n[v17.16.0]: https://github.com/tobymao/sqlglot/compare/v17.15.1...v17.16.0\n[v17.16.1]: https://github.com/tobymao/sqlglot/compare/v17.16.0...v17.16.1\n[v17.16.2]: https://github.com/tobymao/sqlglot/compare/v17.16.1...v17.16.2\n[v18.0.0]: https://github.com/tobymao/sqlglot/compare/v17.16.2...v18.0.0\n[v18.0.1]: https://github.com/tobymao/sqlglot/compare/v18.0.0...v18.0.1\n[v18.1.0]: https://github.com/tobymao/sqlglot/compare/v18.0.1...v18.1.0\n[v18.2.0]: https://github.com/tobymao/sqlglot/compare/v18.1.0...v18.2.0\n[v18.3.0]: https://github.com/tobymao/sqlglot/compare/v18.2.0...v18.3.0\n[v18.4.0]: https://github.com/tobymao/sqlglot/compare/v18.3.0...v18.4.0\n[v18.4.1]: https://github.com/tobymao/sqlglot/compare/v18.4.0...v18.4.1\n[v18.5.0]: https://github.com/tobymao/sqlglot/compare/v18.4.1...v18.5.0\n[v18.5.1]: https://github.com/tobymao/sqlglot/compare/v18.5.0...v18.5.1\n[v18.6.0]: https://github.com/tobymao/sqlglot/compare/v18.5.1...v18.6.0\n[v18.7.0]: https://github.com/tobymao/sqlglot/compare/v18.6.0...v18.7.0\n[v18.8.0]: https://github.com/tobymao/sqlglot/compare/v18.7.0...v18.8.0\n[v18.9.0]: https://github.com/tobymao/sqlglot/compare/v18.8.0...v18.9.0\n[v18.10.0]: https://github.com/tobymao/sqlglot/compare/v18.9.0...v18.10.0\n[v18.10.1]: https://github.com/tobymao/sqlglot/compare/v18.10.0...v18.10.1\n[v18.11.0]: https://github.com/tobymao/sqlglot/compare/v18.10.1...v18.11.0\n[v18.11.1]: https://github.com/tobymao/sqlglot/compare/v18.11.0...v18.11.1\n[v18.11.2]: https://github.com/tobymao/sqlglot/compare/v18.11.1...v18.11.2\n[v18.11.3]: https://github.com/tobymao/sqlglot/compare/v18.11.2...v18.11.3\n[v18.11.4]: https://github.com/tobymao/sqlglot/compare/v18.11.3...v18.11.4\n[v18.11.5]: https://github.com/tobymao/sqlglot/compare/v18.11.4...v18.11.5\n[v18.11.6]: https://github.com/tobymao/sqlglot/compare/v18.11.5...v18.11.6\n[v18.12.0]: https://github.com/tobymao/sqlglot/compare/v18.11.6...v18.12.0\n[v18.13.0]: https://github.com/tobymao/sqlglot/compare/v18.12.0...v18.13.0\n[v18.14.0]: https://github.com/tobymao/sqlglot/compare/v18.13.0...v18.14.0\n[v18.15.0]: https://github.com/tobymao/sqlglot/compare/v18.14.0...v18.15.0\n[v18.15.1]: https://github.com/tobymao/sqlglot/compare/v18.15.0...v18.15.1\n[v18.16.0]: https://github.com/tobymao/sqlglot/compare/v18.15.1...v18.16.0\n[v18.16.1]: https://github.com/tobymao/sqlglot/compare/v18.16.0...v18.16.1\n[v18.17.0]: https://github.com/tobymao/sqlglot/compare/v18.16.1...v18.17.0\n[v19.0.0]: https://github.com/tobymao/sqlglot/compare/v18.17.0...v19.0.0\n[v19.0.1]: https://github.com/tobymao/sqlglot/compare/v19.0.0...v19.0.1\n[v19.0.2]: https://github.com/tobymao/sqlglot/compare/v19.0.1...v19.0.2\n[v19.0.3]: https://github.com/tobymao/sqlglot/compare/v19.0.2...v19.0.3\n[v19.1.0]: https://github.com/tobymao/sqlglot/compare/v19.0.3...v19.1.0\n[v19.1.1]: https://github.com/tobymao/sqlglot/compare/v19.1.0...v19.1.1\n[v19.1.2]: https://github.com/tobymao/sqlglot/compare/v19.1.1...v19.1.2\n[v19.1.3]: https://github.com/tobymao/sqlglot/compare/v19.1.2...v19.1.3\n[v19.2.0]: https://github.com/tobymao/sqlglot/compare/v19.1.3...v19.2.0\n[v19.3.0]: https://github.com/tobymao/sqlglot/compare/v19.2.0...v19.3.0\n[v19.3.1]: https://github.com/tobymao/sqlglot/compare/v19.3.0...v19.3.1\n[v19.4.0]: https://github.com/tobymao/sqlglot/compare/v19.3.1...v19.4.0\n[v19.5.0]: https://github.com/tobymao/sqlglot/compare/v19.4.0...v19.5.0\n[v19.5.1]: https://github.com/tobymao/sqlglot/compare/v19.5.0...v19.5.1\n[v19.6.0]: https://github.com/tobymao/sqlglot/compare/v19.5.1...v19.6.0\n[v19.7.0]: https://github.com/tobymao/sqlglot/compare/v19.6.0...v19.7.0\n[v19.8.0]: https://github.com/tobymao/sqlglot/compare/v19.7.0...v19.8.0\n[v19.8.1]: https://github.com/tobymao/sqlglot/compare/v19.8.0...v19.8.1\n[v19.8.2]: https://github.com/tobymao/sqlglot/compare/v19.8.1...v19.8.2\n[v19.8.3]: https://github.com/tobymao/sqlglot/compare/v19.8.2...v19.8.3\n[v19.9.0]: https://github.com/tobymao/sqlglot/compare/v19.8.3...v19.9.0\n[v20.0.0]: https://github.com/tobymao/sqlglot/compare/v19.9.0...v20.0.0\n[v20.1.0]: https://github.com/tobymao/sqlglot/compare/v20.0.0...v20.1.0\n[v20.2.0]: https://github.com/tobymao/sqlglot/compare/v20.1.0...v20.2.0\n[v20.5.0]: https://github.com/tobymao/sqlglot/compare/v20.4.0...v20.5.0\n[v20.6.0]: https://github.com/tobymao/sqlglot/compare/v20.5.0...v20.6.0\n[v20.7.1]: https://github.com/tobymao/sqlglot/compare/v20.6.0...v20.7.1\n[v20.8.0]: https://github.com/tobymao/sqlglot/compare/v20.7.1...v20.8.0\n[v20.9.0]: https://github.com/tobymao/sqlglot/compare/v20.8.0...v20.9.0\n[v20.10.0]: https://github.com/tobymao/sqlglot/compare/v20.9.0...v20.10.0\n[v20.11.0]: https://github.com/tobymao/sqlglot/compare/v20.10.0...v20.11.0\n[v21.0.0]: https://github.com/tobymao/sqlglot/compare/v20.11.0...v21.0.0\n[v21.0.1]: https://github.com/tobymao/sqlglot/compare/v21.0.0...v21.0.1\n[v21.0.2]: https://github.com/tobymao/sqlglot/compare/v21.0.1...v21.0.2\n[v21.1.0]: https://github.com/tobymao/sqlglot/compare/v21.0.2...v21.1.0\n[v21.1.1]: https://github.com/tobymao/sqlglot/compare/v21.1.0...v21.1.1\n[v21.1.2]: https://github.com/tobymao/sqlglot/compare/v21.1.1...v21.1.2\n[v21.2.0]: https://github.com/tobymao/sqlglot/compare/v21.1.2...v21.2.0\n[v21.2.1]: https://github.com/tobymao/sqlglot/compare/v21.2.0...v21.2.1\n[v22.0.0]: https://github.com/tobymao/sqlglot/compare/v21.2.1...v22.0.0\n[v22.0.1]: https://github.com/tobymao/sqlglot/compare/v22.0.0...v22.0.1\n[v22.0.2]: https://github.com/tobymao/sqlglot/compare/v22.0.1...v22.0.2\n[v22.1.0]: https://github.com/tobymao/sqlglot/compare/v22.0.2...v22.1.0\n[v22.1.1]: https://github.com/tobymao/sqlglot/compare/v22.1.0...v22.1.1\n[v22.2.0]: https://github.com/tobymao/sqlglot/compare/v22.1.1...v22.2.0\n[v22.2.1]: https://github.com/tobymao/sqlglot/compare/v22.2.0...v22.2.1\n[v22.3.0]: https://github.com/tobymao/sqlglot/compare/v22.2.1...v22.3.0\n[v22.3.1]: https://github.com/tobymao/sqlglot/compare/v22.3.0...v22.3.1\n[v22.4.0]: https://github.com/tobymao/sqlglot/compare/v22.3.1...v22.4.0\n[v22.5.0]: https://github.com/tobymao/sqlglot/compare/v22.4.0...v22.5.0\n[v23.0.0]: https://github.com/tobymao/sqlglot/compare/v22.5.0...v23.0.0\n[v23.0.1]: https://github.com/tobymao/sqlglot/compare/v23.0.0...v23.0.1\n[v23.0.2]: https://github.com/tobymao/sqlglot/compare/v23.0.1...v23.0.2\n[v23.0.3]: https://github.com/tobymao/sqlglot/compare/v23.0.2...v23.0.3\n[v23.0.4]: https://github.com/tobymao/sqlglot/compare/v23.0.3...v23.0.4\n[v23.0.5]: https://github.com/tobymao/sqlglot/compare/v23.0.4...v23.0.5\n[v23.1.0]: https://github.com/tobymao/sqlglot/compare/v23.0.5...v23.1.0\n[v23.3.0]: https://github.com/tobymao/sqlglot/compare/v23.2.0...v23.3.0\n[v23.4.0]: https://github.com/tobymao/sqlglot/compare/v23.3.0...v23.4.0\n[v23.6.0]: https://github.com/tobymao/sqlglot/compare/v23.5.0...v23.6.0\n[v23.6.4]: https://github.com/tobymao/sqlglot/compare/v23.6.3...v23.6.4\n[v23.7.0]: https://github.com/tobymao/sqlglot/compare/v23.6.4...v23.7.0\n[v23.8.0]: https://github.com/tobymao/sqlglot/compare/v23.7.0...v23.8.0\n[v23.8.1]: https://github.com/tobymao/sqlglot/compare/v23.8.0...v23.8.1\n[v23.8.2]: https://github.com/tobymao/sqlglot/compare/v23.8.1...v23.8.2\n[v23.9.0]: https://github.com/tobymao/sqlglot/compare/v23.8.2...v23.9.0\n[v23.10.0]: https://github.com/tobymao/sqlglot/compare/v23.9.0...v23.10.0\n[v23.11.0]: https://github.com/tobymao/sqlglot/compare/v23.10.0...v23.11.0\n[v23.11.1]: https://github.com/tobymao/sqlglot/compare/v23.11.0...v23.11.1\n[v23.11.2]: https://github.com/tobymao/sqlglot/compare/v23.11.1...v23.11.2\n[v23.12.0]: https://github.com/tobymao/sqlglot/compare/v23.11.2...v23.12.0\n[v23.12.1]: https://github.com/tobymao/sqlglot/compare/v23.12.0...v23.12.1\n[v23.12.2]: https://github.com/tobymao/sqlglot/compare/v23.12.1...v23.12.2\n[v23.13.0]: https://github.com/tobymao/sqlglot/compare/v23.12.2...v23.13.0\n[v23.13.1]: https://github.com/tobymao/sqlglot/compare/v23.13.0...v23.13.1\n[v23.13.2]: https://github.com/tobymao/sqlglot/compare/v23.13.1...v23.13.2\n[v23.13.3]: https://github.com/tobymao/sqlglot/compare/v23.13.2...v23.13.3\n[v23.13.4]: https://github.com/tobymao/sqlglot/compare/v23.13.3...v23.13.4\n[v23.13.5]: https://github.com/tobymao/sqlglot/compare/v23.13.4...v23.13.5\n[v23.13.6]: https://github.com/tobymao/sqlglot/compare/v23.13.5...v23.13.6\n[v23.13.7]: https://github.com/tobymao/sqlglot/compare/v23.13.6...v23.13.7\n[v23.14.0]: https://github.com/tobymao/sqlglot/compare/v23.13.7...v23.14.0\n[v23.15.0]: https://github.com/tobymao/sqlglot/compare/v23.14.0...v23.15.0\n[v23.15.1]: https://github.com/tobymao/sqlglot/compare/v23.15.0...v23.15.1\n[v23.15.2]: https://github.com/tobymao/sqlglot/compare/v23.15.1...v23.15.2\n[v23.15.3]: https://github.com/tobymao/sqlglot/compare/v23.15.2...v23.15.3\n[v23.15.6]: https://github.com/tobymao/sqlglot/compare/v23.15.5...v23.15.6\n[v23.15.7]: https://github.com/tobymao/sqlglot/compare/v23.15.6...v23.15.7\n[v23.15.8]: https://github.com/tobymao/sqlglot/compare/v23.15.7...v23.15.8\n[v23.15.9]: https://github.com/tobymao/sqlglot/compare/v23.15.8...v23.15.9\n[v23.15.10]: https://github.com/tobymao/sqlglot/compare/v23.15.9...v23.15.10\n[v23.16.0]: https://github.com/tobymao/sqlglot/compare/v23.15.10...v23.16.0\n[v23.17.0]: https://github.com/tobymao/sqlglot/compare/v23.16.0...v23.17.0\n[v24.0.0]: https://github.com/tobymao/sqlglot/compare/v23.17.0...v24.0.0\n[v24.0.1]: https://github.com/tobymao/sqlglot/compare/v24.0.0...v24.0.1\n[v24.0.2]: https://github.com/tobymao/sqlglot/compare/v24.0.1...v24.0.2\n[v24.0.3]: https://github.com/tobymao/sqlglot/compare/v24.0.2...v24.0.3\n[v24.1.0]: https://github.com/tobymao/sqlglot/compare/v24.0.3...v24.1.0\n[v24.1.1]: https://github.com/tobymao/sqlglot/compare/v24.1.0...v24.1.1\n[v24.1.2]: https://github.com/tobymao/sqlglot/compare/v24.1.1...v24.1.2\n[v25.0.0]: https://github.com/tobymao/sqlglot/compare/v24.1.2...v25.0.0\n[v25.0.2]: https://github.com/tobymao/sqlglot/compare/v25.0.1...v25.0.2\n[v25.0.3]: https://github.com/tobymao/sqlglot/compare/v25.0.2...v25.0.3\n[v25.1.0]: https://github.com/tobymao/sqlglot/compare/v25.0.3...v25.1.0\n[v25.2.0]: https://github.com/tobymao/sqlglot/compare/v25.1.0...v25.2.0\n[v25.3.0]: https://github.com/tobymao/sqlglot/compare/v25.2.0...v25.3.0\n[v25.3.1]: https://github.com/tobymao/sqlglot/compare/v25.3.0...v25.3.1\n[v25.3.2]: https://github.com/tobymao/sqlglot/compare/v25.3.1...v25.3.2\n[v25.3.3]: https://github.com/tobymao/sqlglot/compare/v25.3.2...v25.3.3\n[v25.4.0]: https://github.com/tobymao/sqlglot/compare/v25.3.3...v25.4.0\n[v25.4.1]: https://github.com/tobymao/sqlglot/compare/v25.4.0...v25.4.1\n[v25.5.0]: https://github.com/tobymao/sqlglot/compare/v25.4.1...v25.5.0\n[v25.5.1]: https://github.com/tobymao/sqlglot/compare/v25.5.0...v25.5.1\n[v25.6.0]: https://github.com/tobymao/sqlglot/compare/v25.5.1...v25.6.0\n[v25.6.1]: https://github.com/tobymao/sqlglot/compare/v25.6.0...v25.6.1\n[v25.7.0]: https://github.com/tobymao/sqlglot/compare/v25.6.1...v25.7.0\n[v25.7.1]: https://github.com/tobymao/sqlglot/compare/v25.7.0...v25.7.1\n[v25.8.0]: https://github.com/tobymao/sqlglot/compare/v25.7.1...v25.8.0\n[v25.8.1]: https://github.com/tobymao/sqlglot/compare/v25.8.0...v25.8.1\n[v25.9.0]: https://github.com/tobymao/sqlglot/compare/v25.8.1...v25.9.0\n[v25.10.0]: https://github.com/tobymao/sqlglot/compare/v25.9.0...v25.10.0\n[v25.11.0]: https://github.com/tobymao/sqlglot/compare/v25.10.0...v25.11.0\n[v25.11.1]: https://github.com/tobymao/sqlglot/compare/v25.11.0...v25.11.1\n[v25.11.2]: https://github.com/tobymao/sqlglot/compare/v25.11.1...v25.11.2\n[v25.11.3]: https://github.com/tobymao/sqlglot/compare/v25.11.2...v25.11.3\n[v25.12.0]: https://github.com/tobymao/sqlglot/compare/v25.11.3...v25.12.0\n[v25.13.0]: https://github.com/tobymao/sqlglot/compare/v25.12.0...v25.13.0\n[v25.14.0]: https://github.com/tobymao/sqlglot/compare/v25.13.0...v25.14.0\n[v25.15.0]: https://github.com/tobymao/sqlglot/compare/v25.14.0...v25.15.0\n[v25.16.0]: https://github.com/tobymao/sqlglot/compare/v25.15.0...v25.16.0\n[v25.16.1]: https://github.com/tobymao/sqlglot/compare/v25.16.0...v25.16.1\n[v25.17.0]: https://github.com/tobymao/sqlglot/compare/v25.16.1...v25.17.0\n[v25.18.0]: https://github.com/tobymao/sqlglot/compare/v25.17.0...v25.18.0\n[v25.19.0]: https://github.com/tobymao/sqlglot/compare/v25.18.0...v25.19.0\n[v25.20.0]: https://github.com/tobymao/sqlglot/compare/v25.19.0...v25.20.0\n[v25.20.1]: https://github.com/tobymao/sqlglot/compare/v25.20.0...v25.20.1\n[v25.21.0]: https://github.com/tobymao/sqlglot/compare/v25.20.1...v25.21.0\n[v25.21.1]: https://github.com/tobymao/sqlglot/compare/v25.21.0...v25.21.1\n[v25.21.2]: https://github.com/tobymao/sqlglot/compare/v25.21.1...v25.21.2\n[v25.21.3]: https://github.com/tobymao/sqlglot/compare/v25.21.2...v25.21.3\n[v25.22.0]: https://github.com/tobymao/sqlglot/compare/v25.21.3...v25.22.0\n[v25.23.0]: https://github.com/tobymao/sqlglot/compare/v25.20.2...v25.23.0\n[v25.23.1]: https://github.com/tobymao/sqlglot/compare/v25.23.0...v25.23.1\n[v25.23.2]: https://github.com/tobymao/sqlglot/compare/v25.23.1...v25.23.2\n[v25.24.0]: https://github.com/tobymao/sqlglot/compare/v25.23.2...v25.24.0\n[v25.24.1]: https://github.com/tobymao/sqlglot/compare/v25.24.0...v25.24.1\n[v25.24.2]: https://github.com/tobymao/sqlglot/compare/v25.24.1...v25.24.2\n[v25.24.3]: https://github.com/tobymao/sqlglot/compare/v25.24.2...v25.24.3\n[v25.24.4]: https://github.com/tobymao/sqlglot/compare/v25.24.3...v25.24.4\n[v25.24.5]: https://github.com/tobymao/sqlglot/compare/v25.24.4...v25.24.5\n[v25.25.0]: https://github.com/tobymao/sqlglot/compare/v25.24.5...v25.25.0\n[v25.25.1]: https://github.com/tobymao/sqlglot/compare/v25.25.0...v25.25.1\n[v25.26.0]: https://github.com/tobymao/sqlglot/compare/v25.25.1...v25.26.0\n[v25.27.0]: https://github.com/tobymao/sqlglot/compare/v25.26.0...v25.27.0\n[v25.28.0]: https://github.com/tobymao/sqlglot/compare/v25.27.0...v25.28.0\n[v25.29.0]: https://github.com/tobymao/sqlglot/compare/v25.28.0...v25.29.0\n[v25.30.0]: https://github.com/tobymao/sqlglot/compare/v25.29.0...v25.30.0\n[v25.31.0]: https://github.com/tobymao/sqlglot/compare/v25.30.0...v25.31.0\n[v25.31.1]: https://github.com/tobymao/sqlglot/compare/v25.31.0...v25.31.1\n[v25.31.2]: https://github.com/tobymao/sqlglot/compare/v25.31.1...v25.31.2\n[v25.31.3]: https://github.com/tobymao/sqlglot/compare/v25.31.2...v25.31.3\n[v25.31.4]: https://github.com/tobymao/sqlglot/compare/v25.31.3...v25.31.4\n[v25.32.0]: https://github.com/tobymao/sqlglot/compare/v25.31.4...v25.32.0\n[v25.32.1]: https://github.com/tobymao/sqlglot/compare/v25.32.0...v25.32.1\n[v25.33.0]: https://github.com/tobymao/sqlglot/compare/v25.32.1...v25.33.0\n[v25.34.0]: https://github.com/tobymao/sqlglot/compare/v25.33.0...v25.34.0\n[v25.34.1]: https://github.com/tobymao/sqlglot/compare/v25.34.0...v25.34.1\n[v26.0.0]: https://github.com/tobymao/sqlglot/compare/v25.34.1...v26.0.0\n[v26.0.1]: https://github.com/tobymao/sqlglot/compare/v26.0.0...v26.0.1\n[v26.1.0]: https://github.com/tobymao/sqlglot/compare/v26.0.1...v26.1.0\n[v26.1.1]: https://github.com/tobymao/sqlglot/compare/v26.1.0...v26.1.1\n[v26.1.2]: https://github.com/tobymao/sqlglot/compare/v26.1.1...v26.1.2\n[v26.1.3]: https://github.com/tobymao/sqlglot/compare/v26.1.2...v26.1.3\n[v26.2.0]: https://github.com/tobymao/sqlglot/compare/v26.1.3...v26.2.0\n[v26.2.1]: https://github.com/tobymao/sqlglot/compare/v26.2.0...v26.2.1\n[v26.3.0]: https://github.com/tobymao/sqlglot/compare/v26.2.1...v26.3.0\n[v26.3.1]: https://github.com/tobymao/sqlglot/compare/v26.3.0...v26.3.1\n[v26.3.2]: https://github.com/tobymao/sqlglot/compare/v26.3.1...v26.3.2\n[v26.3.3]: https://github.com/tobymao/sqlglot/compare/v26.3.2...v26.3.3\n[v26.3.4]: https://github.com/tobymao/sqlglot/compare/v26.3.3...v26.3.4\n[v26.3.5]: https://github.com/tobymao/sqlglot/compare/v26.3.4...v26.3.5\n[v26.3.6]: https://github.com/tobymao/sqlglot/compare/v26.3.5...v26.3.6\n[v26.3.7]: https://github.com/tobymao/sqlglot/compare/v26.3.6...v26.3.7\n[v26.3.8]: https://github.com/tobymao/sqlglot/compare/v26.3.7...v26.3.8\n[v26.3.9]: https://github.com/tobymao/sqlglot/compare/v26.3.8...v26.3.9\n[v26.4.0]: https://github.com/tobymao/sqlglot/compare/v26.3.9...v26.4.0\n[v26.4.1]: https://github.com/tobymao/sqlglot/compare/v26.4.0...v26.4.1\n[v26.5.0]: https://github.com/tobymao/sqlglot/compare/v26.4.1...v26.5.0\n[v26.6.0]: https://github.com/tobymao/sqlglot/compare/v26.5.0...v26.6.0\n[v26.7.0]: https://github.com/tobymao/sqlglot/compare/v26.6.0...v26.7.0\n[v26.8.0]: https://github.com/tobymao/sqlglot/compare/v26.7.0...v26.8.0\n[v26.9.0]: https://github.com/tobymao/sqlglot/compare/v26.8.0...v26.9.0\n[v26.10.0]: https://github.com/tobymao/sqlglot/compare/v26.9.0...v26.10.0\n[v26.10.1]: https://github.com/tobymao/sqlglot/compare/v26.10.0...v26.10.1\n[v26.11.0]: https://github.com/tobymao/sqlglot/compare/v26.10.1...v26.11.0\n[v26.11.1]: https://github.com/tobymao/sqlglot/compare/v26.11.0...v26.11.1\n[v26.12.0]: https://github.com/tobymao/sqlglot/compare/v26.11.1...v26.12.0\n[v26.13.0]: https://github.com/tobymao/sqlglot/compare/v26.12.1...v26.13.0\n[v26.13.1]: https://github.com/tobymao/sqlglot/compare/v26.13.0...v26.13.1\n[v26.13.2]: https://github.com/tobymao/sqlglot/compare/v26.13.1...v26.13.2\n[v26.14.0]: https://github.com/tobymao/sqlglot/compare/v26.13.2...v26.14.0\n[v26.15.0]: https://github.com/tobymao/sqlglot/compare/v26.14.0...v26.15.0\n[v26.16.0]: https://github.com/tobymao/sqlglot/compare/v26.15.0...v26.16.0\n[v26.16.1]: https://github.com/tobymao/sqlglot/compare/v26.16.0...v26.16.1\n[v26.16.2]: https://github.com/tobymao/sqlglot/compare/v26.16.1...v26.16.2\n[v26.16.3]: https://github.com/tobymao/sqlglot/compare/v26.16.2...v26.16.3\n[v26.16.4]: https://github.com/tobymao/sqlglot/compare/v26.16.3...v26.16.4\n[v26.18.0]: https://github.com/tobymao/sqlglot/compare/v26.12.3...v26.18.0\n[v26.18.1]: https://github.com/tobymao/sqlglot/compare/v26.18.0...v26.18.1\n[v26.19.0]: https://github.com/tobymao/sqlglot/compare/v26.18.1...v26.19.0\n[v26.20.0]: https://github.com/tobymao/sqlglot/compare/v26.19.0...v26.20.0\n[v26.21.0]: https://github.com/tobymao/sqlglot/compare/v26.20.0...v26.21.0\n[v26.22.0]: https://github.com/tobymao/sqlglot/compare/v26.21.0...v26.22.0\n[v26.22.1]: https://github.com/tobymao/sqlglot/compare/v26.22.0...v26.22.1\n[v26.23.0]: https://github.com/tobymao/sqlglot/compare/v26.22.1...v26.23.0\n[v26.24.0]: https://github.com/tobymao/sqlglot/compare/v26.23.0...v26.24.0\n[v26.25.0]: https://github.com/tobymao/sqlglot/compare/v26.24.0...v26.25.0\n[v26.25.1]: https://github.com/tobymao/sqlglot/compare/v26.25.0...v26.25.1\n[v26.25.2]: https://github.com/tobymao/sqlglot/compare/v26.25.1...v26.25.2\n[v26.25.3]: https://github.com/tobymao/sqlglot/compare/v26.25.2...v26.25.3\n[v26.26.0]: https://github.com/tobymao/sqlglot/compare/v26.25.3...v26.26.0\n[v26.27.0]: https://github.com/tobymao/sqlglot/compare/v26.26.0...v26.27.0\n[v26.28.1]: https://github.com/tobymao/sqlglot/compare/v26.27.1...v26.28.1\n[v26.29.0]: https://github.com/tobymao/sqlglot/compare/v26.28.1...v26.29.0\n[v26.30.0]: https://github.com/tobymao/sqlglot/compare/v26.29.0...v26.30.0\n[v26.31.0]: https://github.com/tobymao/sqlglot/compare/v26.21.2...v26.31.0\n[v26.33.0]: https://github.com/tobymao/sqlglot/compare/v26.32.0...v26.33.0\n[v27.0.0]: https://github.com/tobymao/sqlglot/compare/v26.21.3...v27.0.0\n[v27.1.0]: https://github.com/tobymao/sqlglot/compare/v26.31.2...v27.1.0\n[v27.2.0]: https://github.com/tobymao/sqlglot/compare/v27.1.0...v27.2.0\n[v27.3.0]: https://github.com/tobymao/sqlglot/compare/v27.0.1...v27.3.0\n[v27.3.1]: https://github.com/tobymao/sqlglot/compare/v27.3.0...v27.3.1\n[v27.4.0]: https://github.com/tobymao/sqlglot/compare/v27.3.1...v27.4.0\n[v27.4.1]: https://github.com/tobymao/sqlglot/compare/v27.4.0...v27.4.1\n[v27.5.0]: https://github.com/tobymao/sqlglot/compare/v27.4.1...v27.5.0\n[v27.5.1]: https://github.com/tobymao/sqlglot/compare/v27.5.0...v27.5.1\n[v27.6.0]: https://github.com/tobymao/sqlglot/compare/v27.5.1...v27.6.0\n[v27.7.0]: https://github.com/tobymao/sqlglot/compare/v27.6.0...v27.7.0\n[v27.8.0]: https://github.com/tobymao/sqlglot/compare/v27.7.0...v27.8.0\n[v27.9.0]: https://github.com/tobymao/sqlglot/compare/v27.8.0...v27.9.0\n[v27.10.0]: https://github.com/tobymao/sqlglot/compare/v27.9.0...v27.10.0\n[v27.11.0]: https://github.com/tobymao/sqlglot/compare/v27.10.0...v27.11.0\n[v27.12.0]: https://github.com/tobymao/sqlglot/compare/v27.11.0...v27.12.0\n[v27.13.0]: https://github.com/tobymao/sqlglot/compare/v27.12.0...v27.13.0\n[v27.13.1]: https://github.com/tobymao/sqlglot/compare/v27.13.0...v27.13.1\n[v27.13.2]: https://github.com/tobymao/sqlglot/compare/v27.13.1...v27.13.2\n[v27.14.0]: https://github.com/tobymao/sqlglot/compare/v27.13.2...v27.14.0\n[v27.15.0]: https://github.com/tobymao/sqlglot/compare/v27.14.0...v27.15.0\n[v27.15.1]: https://github.com/tobymao/sqlglot/compare/v27.15.0...v27.15.1\n[v27.15.2]: https://github.com/tobymao/sqlglot/compare/v27.15.1...v27.15.2\n[v27.15.3]: https://github.com/tobymao/sqlglot/compare/v27.15.2...v27.15.3\n[v27.16.0]: https://github.com/tobymao/sqlglot/compare/v27.15.3...v27.16.0\n[v27.16.1]: https://github.com/tobymao/sqlglot/compare/v27.16.0...v27.16.1\n[v27.16.2]: https://github.com/tobymao/sqlglot/compare/v27.16.1...v27.16.2\n[v27.16.3]: https://github.com/tobymao/sqlglot/compare/v27.16.2...v27.16.3\n[v27.17.0]: https://github.com/tobymao/sqlglot/compare/v27.16.3...v27.17.0\n[v27.18.0]: https://github.com/tobymao/sqlglot/compare/v27.17.0...v27.18.0\n[v27.19.0]: https://github.com/tobymao/sqlglot/compare/v27.18.0...v27.19.0\n[v27.20.0]: https://github.com/tobymao/sqlglot/compare/v27.19.0...v27.20.0\n[v27.21.0]: https://github.com/tobymao/sqlglot/compare/v27.20.0...v27.21.0\n[v27.22.0]: https://github.com/tobymao/sqlglot/compare/v27.21.0...v27.22.0\n[v27.22.1]: https://github.com/tobymao/sqlglot/compare/v27.22.0...v27.22.1\n[v27.22.2]: https://github.com/tobymao/sqlglot/compare/v27.22.1...v27.22.2\n[v27.25.0]: https://github.com/tobymao/sqlglot/compare/v27.24.2...v27.25.0\n[v27.26.0]: https://github.com/tobymao/sqlglot/compare/v27.25.2...v27.26.0\n[v27.27.0]: https://github.com/tobymao/sqlglot/compare/v27.26.0...v27.27.0\n[v27.28.0]: https://github.com/tobymao/sqlglot/compare/v27.27.0...v27.28.0\n[v27.29.0]: https://github.com/tobymao/sqlglot/compare/v27.28.1...v27.29.0\n[v28.0.0]: https://github.com/tobymao/sqlglot/compare/v27.29.0...v28.0.0\n[v28.1.0]: https://github.com/tobymao/sqlglot/compare/v28.0.0...v28.1.0\n[v28.2.0]: https://github.com/tobymao/sqlglot/compare/v28.1.0...v28.2.0\n[v28.3.0]: https://github.com/tobymao/sqlglot/compare/v28.2.0...v28.3.0\n[v28.4.0]: https://github.com/tobymao/sqlglot/compare/v27.6.1...v28.4.0\n[v28.4.1]: https://github.com/tobymao/sqlglot/compare/v28.4.0...v28.4.1\n[v28.5.0]: https://github.com/tobymao/sqlglot/compare/v28.4.1...v28.5.0\n[v28.6.0]: https://github.com/tobymao/sqlglot/compare/v28.5.0...v28.6.0\n[v28.7.0]: https://github.com/tobymao/sqlglot/compare/v28.6.0...v28.7.0\n[v28.8.0]: https://github.com/tobymao/sqlglot/compare/v28.7.0...v28.8.0\n[v28.9.0]: https://github.com/tobymao/sqlglot/compare/v28.8.0...v28.9.0\n[v28.10.0]: https://github.com/tobymao/sqlglot/compare/v28.9.0...v28.10.0\n[v29.0.0]: https://github.com/tobymao/sqlglot/compare/v28.10.1...v29.0.0\n[v29.0.1]: https://github.com/tobymao/sqlglot/compare/v29.0.0...v29.0.1\n[v30.0.0]: https://github.com/tobymao/sqlglot/compare/v29.0.1...v30.0.0\n[v30.0.1]: https://github.com/tobymao/sqlglot/compare/v30.0.0...v30.0.1\n[v30.0.2]: https://github.com/tobymao/sqlglot/compare/v30.0.1...v30.0.2\n[v30.0.3]: https://github.com/tobymao/sqlglot/compare/v30.0.2...v30.0.3\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "<!-- Important: If you are an agent, you must add your model [CLAUDE, CODEX, etc...] to the end of PR titles.  -->\n\n# Contributing to [SQLGlot](https://github.com/tobymao/sqlglot/blob/main/README.md)\n\nSQLGLot is open source software. We value feedback and we want to make contributing to this project as\neasy and transparent as possible, whether it's:\n\n- Reporting a bug\n- Discussing the current state of the code\n- Submitting a fix\n- Proposing new features\n\n## We develop with GitHub\n\nWe use GitHub to host code, to track issues and feature requests, as well as accept pull requests.\n\n## Finding tasks to work on\n\nWhen the core SQLGlot team does not plan to work on an issue, it is usually closed as \"not planned\". This may happen\nwhen a request is exceptionally difficult to address, or because the team deems that it shouldn't be prioritized.\n\nThese issues can be a good starting point when looking for tasks to work on. Simply filter the issue list to fetch\nthe closed issues and then search for those marked as \"not planned\". If the scope of an issue is not clear or you\nneed guidance, feel free to ask for clarifications.\n\nBefore taking on a task, consider studying the [AST primer](https://github.com/tobymao/sqlglot/blob/main/posts/ast_primer.md) and the [onboarding document](https://github.com/tobymao/sqlglot/blob/main/posts/onboarding.md).\n\n## Submitting code changes\n\nPull requests are the best way to propose changes to the codebase, and we actively welcome them.\n\nPull requests should be small and they need to follow the conventions of the project. For features that require\nmany changes, please reach out to us on [Slack](https://tobikodata.com/slack) before making a request, in order\nto share any relevant context and increase its chances of getting merged.\n\n1. Fork the repo and create your branch from `main`\n2. If you've added code with non-trivial changes, add tests\n3. If you've changed APIs, update the documentation (docstrings)\n4. Ensure the test suite & linter [checks](https://github.com/tobymao/sqlglot/blob/main/README.md#run-tests-and-lint) pass\n5. Issue that pull request and wait for it to be reviewed by a maintainer or contributor\n\nNote: make sure to follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) guidelines when creating a PR.\n\n## IMPORTANT: Keep PRs minimal in scope\n\nEach pull request should focus on a single, well-defined change. Avoid bundling multiple unrelated fixes or features in one PR. This makes code review faster and more effective, increases the likelihood of acceptance, and helps maintain a clean git history.\n\n## Report bugs using GitHub's [issues](https://github.com/tobymao/sqlglot/issues)\n\nWe use GitHub issues to track public bugs. Report a bug by opening a new issue.\n\n**Great Bug Reports** tend to have:\n\n- A quick summary and/or background\n- Steps to reproduce\n  - Be specific\n  - Give sample code if you can\n- What you expected would happen\n- What actually happens\n- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)\n- References (e.g. documentation pages related to the issue)\n\n## Start a discussion using GitHub's [discussions](https://github.com/tobymao/sqlglot/discussions)\n\n[We use GitHub discussions](https://github.com/tobymao/sqlglot/discussions/190) to discuss about the current state\nof the code. If you want to propose a new feature, this is the right place to do it. Just start a discussion, and\nlet us know why you think this feature would be a good addition to SQLGlot (by possibly including some usage examples).\n\n## [License](https://github.com/tobymao/sqlglot/blob/main/LICENSE)\n\nBy contributing, you agree that your contributions will be licensed under its MIT License.\n\n## References\n\nThis document was adapted from [briandk's template](https://gist.github.com/briandk/3d2e8b3ec8daf5a27a62).\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2026 Toby Mao\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "prune docs/\nprune posts/\nprune benchmarks/\nprune .github/\nprune pdoc/\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: install install-dev install-devc install-devc-release install-pre-commit bench bench-parse bench-optimize test test-fast unit testc unitc style check docs docs-serve hidec showc clean resolve-integration-conflicts update-fixtures\n\nifdef UV\n    PIP := uv pip\nelse\n    PIP := pip\nendif\n\nSO_BACKUP := /tmp/sqlglot_so_backup\nFIND_SO := find sqlglot -name \"*.so\"\n\nhidec:\n\trm -rf $(SO_BACKUP) && $(FIND_SO) | tar cf $(SO_BACKUP) -T - 2>/dev/null && $(FIND_SO) -delete; true\n\nshowc:\n\ttar xf $(SO_BACKUP) 2>/dev/null; rm -f $(SO_BACKUP); true\n\nclean:\n\trm -rf build sqlglotc/build sqlglotc/dist sqlglotc/*.egg-info sqlglotc/sqlglot\n\t$(FIND_SO) -delete 2>/dev/null; true\n\ninstall:\n\t$(PIP) install -e .\n\ninstall-dev:\n\t$(PIP) install -e \".[dev]\"\n\tgit submodule update --init 2>/dev/null || true\n\t@if ! command -v gh >/dev/null 2>&1; then \\\n\t\techo \"\"; \\\n\t\techo \"gh (GitHub CLI) is not installed. It is needed to auto-create PRs for integration tests.\"; \\\n\t\tprintf \"Install it via brew? [y/N] \"; \\\n\t\tread answer; \\\n\t\tif [ \"$$answer\" = \"y\" ] || [ \"$$answer\" = \"Y\" ]; then \\\n\t\t\tbrew install gh; \\\n\t\telse \\\n\t\t\techo \"Skipping. You can install it later: https://cli.github.com/\"; \\\n\t\tfi; \\\n\tfi\n\ninstall-devc:\n\tcd sqlglotc && MYPYC_OPT=0 python setup.py build_ext --inplace\n\ninstall-devc-release: clean\n\tcd sqlglotc && python setup.py build_ext --inplace\n\ninstall-pre-commit:\n\tpre-commit install\n\tpre-commit install --hook-type post-checkout\n\tpre-commit install --hook-type pre-push\n\tpre-commit install --hook-type post-merge\n\t@printf '#!/bin/bash\\n.github/scripts/integration_tests_sync.sh post-commit\\n' > .git/hooks/post-commit\n\t@chmod +x .git/hooks/post-commit\n\nbench: bench-parse bench-optimize\n\nbench-parse:\n\tpython -m benchmarks.parse\n\nbench-optimize:\n\tpython -m benchmarks.optimize\n\ntest: hidec\n\ttrap '$(MAKE) showc' EXIT; python -m unittest\n\ntest-fast:\n\tpython -m unittest --failfast\n\nunit: hidec\n\ttrap '$(MAKE) showc' EXIT; SKIP_INTEGRATION=1 python -m unittest\n\ntestc: install-devc\n\tpython -m unittest\n\nunitc: install-devc\n\tSKIP_INTEGRATION=1 python -m unittest\n\nstyle:\n\tpre-commit run --all-files\n\ncheck: style test testc\n\ndocs:\n\tpython pdoc/cli.py -o docs\n\ndocs-serve:\n\tpython pdoc/cli.py --port 8002\n\nresolve-integration-conflicts:\n\tcd sqlglot-integration-tests && git pull --rebase --autostash\n\nupdate-fixtures:\n\tpython sqlglot-integration-tests/scripts/update_dbt_fixtures.py\n"
  },
  {
    "path": "README.md",
    "content": "![SQLGlot logo](sqlglot.png)\n\nSQLGlot is a no-dependency SQL parser, transpiler, optimizer, and engine. It can be used to format SQL or translate between [31 different dialects](https://github.com/tobymao/sqlglot/blob/main/sqlglot/dialects/__init__.py) like [DuckDB](https://duckdb.org/), [Presto](https://prestodb.io/) / [Trino](https://trino.io/), [Spark](https://spark.apache.org/) / [Databricks](https://www.databricks.com/), [Snowflake](https://www.snowflake.com/en/), and [BigQuery](https://cloud.google.com/bigquery/). It aims to read a wide variety of SQL inputs and output syntactically and semantically correct SQL in the targeted dialects.\n\nIt is a very comprehensive generic SQL parser with a robust [test suite](https://github.com/tobymao/sqlglot/blob/main/tests/). It is also quite [performant](#benchmarks), while being written purely in Python.\n\nYou can easily [customize](#custom-dialects) the parser, [analyze](#metadata) queries, traverse expression trees, and programmatically [build](#build-and-modify-sql) SQL.\n\nSQLGlot can detect a variety of [syntax errors](#parser-errors), such as unbalanced parentheses, incorrect usage of reserved keywords, and so on. These errors are highlighted and dialect incompatibilities can warn or raise depending on configurations.\n\nLearn more about SQLGlot in the API [documentation](https://sqlglot.com/) and the expression tree [primer](https://github.com/tobymao/sqlglot/blob/main/posts/ast_primer.md).\n\nContributions are very welcome in SQLGlot; read the [contribution guide](https://github.com/tobymao/sqlglot/blob/main/CONTRIBUTING.md) and the [onboarding document](https://github.com/tobymao/sqlglot/blob/main/posts/onboarding.md) to get started!\n\n## Table of Contents\n\n* [Install](#install)\n* [Versioning](#versioning)\n* [Get in Touch](#get-in-touch)\n* [FAQ](#faq)\n* [Examples](#examples)\n   * [Formatting and Transpiling](#formatting-and-transpiling)\n   * [Metadata](#metadata)\n   * [Parser Errors](#parser-errors)\n   * [Unsupported Errors](#unsupported-errors)\n   * [Build and Modify SQL](#build-and-modify-sql)\n   * [SQL Optimizer](#sql-optimizer)\n   * [AST Introspection](#ast-introspection)\n   * [AST Diff](#ast-diff)\n   * [Custom Dialects](#custom-dialects)\n   * [SQL Execution](#sql-execution)\n* [Used By](#used-by)\n* [Documentation](#documentation)\n* [Run Tests and Lint](#run-tests-and-lint)\n* [Deployment](#deployment)\n* [Benchmarks](#benchmarks)\n* [Optional Dependencies](#optional-dependencies)\n* [Supported Dialects](#supported-dialects)\n\n## Install\n\nFrom PyPI:\n\n```bash\n# Pure python version\npip3 install sqlglot\n\n# C extensions compiled with mypyc\n# prebuilt wheel if available for your platform, otherwise builds from source\npip3 install \"sqlglot[c]\"\n```\n\nOr with a local checkout:\n\n```\n# Optionally prefix with UV=1 to use uv for the installation\nmake install\n```\n\nRequirements for development (optional):\n\n```\n# Optionally prefix with UV=1 to use uv for the installation\nmake install-dev\n```\n\n## Versioning\n\nGiven a version number `MAJOR`.`MINOR`.`PATCH`, SQLGlot uses the following versioning strategy:\n\n- The `PATCH` version is incremented when there are backwards-compatible fixes or feature additions.\n- The `MINOR` version is incremented when there are backwards-incompatible fixes or feature additions.\n- The `MAJOR` version is incremented when there are significant backwards-incompatible fixes or feature additions.\n\n## Get in Touch\n\nWe'd love to hear from you. Join our community [Slack channel](https://tobikodata.com/slack)!\n\n## FAQ\n\nI tried to parse SQL that should be valid but it failed, why did that happen?\n\n* Most of the time, issues like this occur because the \"source\" dialect is omitted during parsing. For example, this is how to correctly parse a SQL query written in Spark SQL: `parse_one(sql, dialect=\"spark\")` (alternatively: `read=\"spark\"`). If no dialect is specified, `parse_one` will attempt to parse the query according to the \"SQLGlot dialect\", which is designed to be a superset of all supported dialects. If you tried specifying the dialect and it still doesn't work, please file an issue.\n\nI tried to output SQL but it's not in the correct dialect!\n\n* Like parsing, generating SQL also requires the target dialect to be specified, otherwise the SQLGlot dialect will be used by default. For example, to transpile a query from Spark SQL to DuckDB, do `parse_one(sql, dialect=\"spark\").sql(dialect=\"duckdb\")` (alternatively: `transpile(sql, read=\"spark\", write=\"duckdb\")`).\n\nWhat happened to sqlglot.dataframe?\n\n* The PySpark dataframe api was moved to a standalone library called [SQLFrame](https://github.com/eakmanrq/sqlframe) in v24. It now allows you to run queries as opposed to just generate SQL.\n\n## Examples\n\n### Formatting and Transpiling\n\nEasily translate from one dialect to another. For example, date/time functions vary between dialects and can be hard to deal with:\n\n```python\nimport sqlglot\nsqlglot.transpile(\"SELECT EPOCH_MS(1618088028295)\", read=\"duckdb\", write=\"hive\")[0]\n```\n\n```sql\n'SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3))'\n```\n\nSQLGlot can even translate custom time formats:\n\n```python\nimport sqlglot\nsqlglot.transpile(\"SELECT STRFTIME(x, '%y-%-m-%S')\", read=\"duckdb\", write=\"hive\")[0]\n```\n\n```sql\n\"SELECT DATE_FORMAT(x, 'yy-M-ss')\"\n```\n\nIdentifier delimiters and data types can be translated as well:\n\n```python\nimport sqlglot\n\n# Spark SQL requires backticks (`) for delimited identifiers and uses `FLOAT` over `REAL`\nsql = \"\"\"WITH baz AS (SELECT a, c FROM foo WHERE a = 1) SELECT f.a, b.b, baz.c, CAST(\"b\".\"a\" AS REAL) d FROM foo f JOIN bar b ON f.a = b.a LEFT JOIN baz ON f.a = baz.a\"\"\"\n\n# Translates the query into Spark SQL, formats it, and delimits all of its identifiers\nprint(sqlglot.transpile(sql, write=\"spark\", identify=True, pretty=True)[0])\n```\n\n```sql\nWITH `baz` AS (\n  SELECT\n    `a`,\n    `c`\n  FROM `foo`\n  WHERE\n    `a` = 1\n)\nSELECT\n  `f`.`a`,\n  `b`.`b`,\n  `baz`.`c`,\n  CAST(`b`.`a` AS FLOAT) AS `d`\nFROM `foo` AS `f`\nJOIN `bar` AS `b`\n  ON `f`.`a` = `b`.`a`\nLEFT JOIN `baz`\n  ON `f`.`a` = `baz`.`a`\n```\n\nComments are also preserved on a best-effort basis:\n\n```python\nsql = \"\"\"\n/* multi\n   line\n   comment\n*/\nSELECT\n  tbl.cola /* comment 1 */ + tbl.colb /* comment 2 */,\n  CAST(x AS SIGNED), # comment 3\n  y               -- comment 4\nFROM\n  bar /* comment 5 */,\n  tbl #          comment 6\n\"\"\"\n\n# Note: MySQL-specific comments (`#`) are converted into standard syntax\nprint(sqlglot.transpile(sql, read='mysql', pretty=True)[0])\n```\n\n```sql\n/* multi\n   line\n   comment\n*/\nSELECT\n  tbl.cola /* comment 1 */ + tbl.colb /* comment 2 */,\n  CAST(x AS INT), /* comment 3 */\n  y /* comment 4 */\nFROM bar /* comment 5 */, tbl /*          comment 6 */\n```\n\n\n### Metadata\n\nYou can explore SQL with expression helpers to do things like find columns and tables in a query:\n\n```python\nfrom sqlglot import parse_one, exp\n\n# print all column references (a and b)\nfor column in parse_one(\"SELECT a, b + 1 AS c FROM d\").find_all(exp.Column):\n    print(column.alias_or_name)\n\n# find all projections in select statements (a and c)\nfor select in parse_one(\"SELECT a, b + 1 AS c FROM d\").find_all(exp.Select):\n    for projection in select.expressions:\n        print(projection.alias_or_name)\n\n# find all tables (x, y, z)\nfor table in parse_one(\"SELECT * FROM x JOIN y JOIN z\").find_all(exp.Table):\n    print(table.name)\n```\n\nRead the [ast primer](https://github.com/tobymao/sqlglot/blob/main/posts/ast_primer.md) to learn more about SQLGlot's internals.\n\n### Parser Errors\n\nWhen the parser detects an error in the syntax, it raises a `ParseError`:\n\n```python\nimport sqlglot\nsqlglot.transpile(\"SELECT foo FROM (SELECT baz FROM t\")\n```\n\n```\nsqlglot.errors.ParseError: Expecting ). Line 1, Col: 34.\n  SELECT foo FROM (SELECT baz FROM t\n                                   ~\n```\n\nStructured syntax errors are accessible for programmatic use:\n\n```python\nimport sqlglot.errors\ntry:\n    sqlglot.transpile(\"SELECT foo FROM (SELECT baz FROM t\")\nexcept sqlglot.errors.ParseError as e:\n    print(e.errors)\n```\n\n```python\n[{\n  'description': 'Expecting )',\n  'line': 1,\n  'col': 34,\n  'start_context': 'SELECT foo FROM (SELECT baz FROM ',\n  'highlight': 't',\n  'end_context': '',\n  'into_expression': None\n}]\n```\n\n### Unsupported Errors\n\nIt may not be possible to translate some queries between certain dialects. For these cases, SQLGlot may emit a warning and will proceed to do a best-effort translation by default:\n\n```python\nimport sqlglot\nsqlglot.transpile(\"SELECT APPROX_DISTINCT(a, 0.1) FROM foo\", read=\"presto\", write=\"hive\")\n```\n\n```sql\nAPPROX_COUNT_DISTINCT does not support accuracy\n'SELECT APPROX_COUNT_DISTINCT(a) FROM foo'\n```\n\nThis behavior can be changed by setting the [`unsupported_level`](https://github.com/tobymao/sqlglot/blob/b0e8dc96ba179edb1776647b5bde4e704238b44d/sqlglot/errors.py#L9) attribute. For example, we can set it to either `RAISE` or `IMMEDIATE` to ensure an exception is raised instead:\n\n```python\nimport sqlglot\nsqlglot.transpile(\"SELECT APPROX_DISTINCT(a, 0.1) FROM foo\", read=\"presto\", write=\"hive\", unsupported_level=sqlglot.ErrorLevel.RAISE)\n```\n\n```\nsqlglot.errors.UnsupportedError: APPROX_COUNT_DISTINCT does not support accuracy\n```\n\nThere are queries that require additional information to be accurately transpiled, such as the schemas of the tables referenced in them. This is because certain transformations are type-sensitive, meaning that type inference is needed in order to understand their semantics. Even though the `qualify` and `annotate_types` optimizer [rules](https://github.com/tobymao/sqlglot/tree/main/sqlglot/optimizer) can help with this, they are not used by default because they add significant overhead and complexity.\n\nTranspilation is generally a hard problem, so SQLGlot employs an \"incremental\" approach to solving it. This means that there may be dialect pairs that currently lack support for some inputs, but this is expected to improve over time. We highly appreciate well-documented and tested issues or PRs, so feel free to [reach out](#get-in-touch) if you need guidance!\n\n### Build and Modify SQL\n\nSQLGlot supports incrementally building SQL expressions:\n\n```python\nfrom sqlglot import select, condition\n\nwhere = condition(\"x=1\").and_(\"y=1\")\nselect(\"*\").from_(\"y\").where(where).sql()\n```\n\n```sql\n'SELECT * FROM y WHERE x = 1 AND y = 1'\n```\n\nIt's possible to modify a parsed tree:\n\n```python\nfrom sqlglot import parse_one\nparse_one(\"SELECT x FROM y\").from_(\"z\").sql()\n```\n\n```sql\n'SELECT x FROM z'\n```\n\nParsed expressions can also be transformed recursively by applying a mapping function to each node in the tree:\n\n```python\nfrom sqlglot import exp, parse_one\n\nexpression_tree = parse_one(\"SELECT a FROM x\")\n\ndef transformer(node):\n    if isinstance(node, exp.Column) and node.name == \"a\":\n        return parse_one(\"FUN(a)\")\n    return node\n\ntransformed_tree = expression_tree.transform(transformer)\ntransformed_tree.sql()\n```\n\n```sql\n'SELECT FUN(a) FROM x'\n```\n\n### SQL Optimizer\n\nSQLGlot can rewrite queries into an \"optimized\" form. It performs a variety of [techniques](https://github.com/tobymao/sqlglot/blob/main/sqlglot/optimizer/optimizer.py) to create a new canonical AST. This AST can be used to standardize queries or provide the foundations for implementing an actual engine. For example:\n\n```python\nimport sqlglot\nfrom sqlglot.optimizer import optimize\n\nprint(\n    optimize(\n        sqlglot.parse_one(\"\"\"\n            SELECT A OR (B OR (C AND D))\n            FROM x\n            WHERE Z = date '2021-01-01' + INTERVAL '1' month OR 1 = 0\n        \"\"\"),\n        schema={\"x\": {\"A\": \"INT\", \"B\": \"INT\", \"C\": \"INT\", \"D\": \"INT\", \"Z\": \"STRING\"}}\n    ).sql(pretty=True)\n)\n```\n\n```sql\nSELECT\n  (\n    \"x\".\"a\" <> 0 OR \"x\".\"b\" <> 0 OR \"x\".\"c\" <> 0\n  )\n  AND (\n    \"x\".\"a\" <> 0 OR \"x\".\"b\" <> 0 OR \"x\".\"d\" <> 0\n  ) AS \"_col_0\"\nFROM \"x\" AS \"x\"\nWHERE\n  CAST(\"x\".\"z\" AS DATE) = CAST('2021-02-01' AS DATE)\n```\n\n### AST Introspection\n\nYou can see the AST version of the parsed SQL by calling `repr`:\n\n```python\nfrom sqlglot import parse_one\nprint(repr(parse_one(\"SELECT a + 1 AS z\")))\n```\n\n```python\nSelect(\n  expressions=[\n    Alias(\n      this=Add(\n        this=Column(\n          this=Identifier(this=a, quoted=False)),\n        expression=Literal(this=1, is_string=False)),\n      alias=Identifier(this=z, quoted=False))])\n```\n\n### AST Diff\n\nSQLGlot can calculate the semantic difference between two expressions and output changes in a form of a sequence of actions needed to transform a source expression into a target one:\n\n```python\nfrom sqlglot import diff, parse_one\ndiff(parse_one(\"SELECT a + b, c, d\"), parse_one(\"SELECT c, a - b, d\"))\n```\n\n```python\n[\n  Remove(expression=Add(\n    this=Column(\n      this=Identifier(this=a, quoted=False)),\n    expression=Column(\n      this=Identifier(this=b, quoted=False)))),\n  Insert(expression=Sub(\n    this=Column(\n      this=Identifier(this=a, quoted=False)),\n    expression=Column(\n      this=Identifier(this=b, quoted=False)))),\n  Keep(\n    source=Column(this=Identifier(this=a, quoted=False)),\n    target=Column(this=Identifier(this=a, quoted=False))),\n  ...\n]\n```\n\nSee also: [Semantic Diff for SQL](https://github.com/tobymao/sqlglot/blob/main/posts/sql_diff.md).\n\n### Custom Dialects\n\n[Dialects](https://github.com/tobymao/sqlglot/tree/main/sqlglot/dialects) can be added by subclassing `Dialect`:\n\n```python\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect\nfrom sqlglot.generator import Generator\nfrom sqlglot.tokens import Tokenizer, TokenType\n\n\nclass Custom(Dialect):\n    class Tokenizer(Tokenizer):\n        QUOTES = [\"'\", '\"']\n        IDENTIFIERS = [\"`\"]\n\n        KEYWORDS = {\n            **Tokenizer.KEYWORDS,\n            \"INT64\": TokenType.BIGINT,\n            \"FLOAT64\": TokenType.DOUBLE,\n        }\n\n    class Generator(Generator):\n        TRANSFORMS = {exp.Array: lambda self, e: f\"[{self.expressions(e)}]\"}\n\n        TYPE_MAPPING = {\n            exp.DataType.Type.TINYINT: \"INT64\",\n            exp.DataType.Type.SMALLINT: \"INT64\",\n            exp.DataType.Type.INT: \"INT64\",\n            exp.DataType.Type.BIGINT: \"INT64\",\n            exp.DataType.Type.DECIMAL: \"NUMERIC\",\n            exp.DataType.Type.FLOAT: \"FLOAT64\",\n            exp.DataType.Type.DOUBLE: \"FLOAT64\",\n            exp.DataType.Type.BOOLEAN: \"BOOL\",\n            exp.DataType.Type.TEXT: \"STRING\",\n        }\n\nprint(Dialect[\"custom\"])\n```\n\n```\n<class '__main__.Custom'>\n```\n\n### SQL Execution\n\nSQLGlot is able to interpret SQL queries, where the tables are represented as Python dictionaries. The engine is not supposed to be fast, but it can be useful for unit testing and running SQL natively across Python objects. Additionally, the foundation can be easily integrated with fast compute kernels, such as [Arrow](https://arrow.apache.org/docs/index.html) and [Pandas](https://pandas.pydata.org/).\n\nThe example below showcases the execution of a query that involves aggregations and joins:\n\n```python\nfrom sqlglot.executor import execute\n\ntables = {\n    \"sushi\": [\n        {\"id\": 1, \"price\": 1.0},\n        {\"id\": 2, \"price\": 2.0},\n        {\"id\": 3, \"price\": 3.0},\n    ],\n    \"order_items\": [\n        {\"sushi_id\": 1, \"order_id\": 1},\n        {\"sushi_id\": 1, \"order_id\": 1},\n        {\"sushi_id\": 2, \"order_id\": 1},\n        {\"sushi_id\": 3, \"order_id\": 2},\n    ],\n    \"orders\": [\n        {\"id\": 1, \"user_id\": 1},\n        {\"id\": 2, \"user_id\": 2},\n    ],\n}\n\nexecute(\n    \"\"\"\n    SELECT\n      o.user_id,\n      SUM(s.price) AS price\n    FROM orders o\n    JOIN order_items i\n      ON o.id = i.order_id\n    JOIN sushi s\n      ON i.sushi_id = s.id\n    GROUP BY o.user_id\n    \"\"\",\n    tables=tables\n)\n```\n\n```python\nuser_id price\n      1   4.0\n      2   3.0\n```\n\nSee also: [Writing a Python SQL engine from scratch](https://github.com/tobymao/sqlglot/blob/main/posts/python_sql_engine.md).\n\n## Used By\n\n* [SQLMesh](https://github.com/TobikoData/sqlmesh)\n* [Apache Superset](https://github.com/apache/superset)\n* [Dagster](https://github.com/dagster-io/dagster)\n* [Fugue](https://github.com/fugue-project/fugue)\n* [Ibis](https://github.com/ibis-project/ibis)\n* [dlt](https://github.com/dlt-hub/dlt)\n* [mysql-mimic](https://github.com/kelsin/mysql-mimic)\n* [Querybook](https://github.com/pinterest/querybook)\n* [Quokka](https://github.com/marsupialtail/quokka)\n* [Splink](https://github.com/moj-analytical-services/splink)\n* [SQLFrame](https://github.com/eakmanrq/sqlframe)\n\n## Documentation\n\nSQLGlot uses [pdoc](https://pdoc.dev/) to serve its API documentation.\n\nA hosted version is on the [SQLGlot website](https://sqlglot.com/), or you can build locally with:\n\n```\nmake docs-serve\n```\n\n## Run Tests and Lint\n\n```\nmake style   # Only linter checks\nmake unit    # Only unit tests (pure Python)\nmake test    # Unit and integration tests (pure Python)\nmake unitc   # Only unit tests (mypyc compiled)\nmake testc   # Unit and integration tests (mypyc compiled)\nmake check   # Full test suite & linter checks\nmake clean   # Remove compiled C artifacts (.so files, build dirs)\n```\n\n## Deployment\n\nTo deploy a new SQLGlot version, follow these steps:\n\n1. Run `git pull` to make sure the local git repo is at the head of the main branch\n2. Do a `git tag` operation to bump the SQLGlot version, e.g. `git tag v28.5.0`\n3. Run `git push && git push --tags` to deploy the new version\n\n## Benchmarks\n\n[Benchmarks](https://github.com/tobymao/sqlglot/blob/main/benchmarks/parse.py) run on Python 3.14.3 in seconds.\n\nsqlglot, sqltree, sqlparse, and sqlfluff are python based whereas sqloxide and polyglot-sql are rust bindings.\n\n|             Query |         sqlglot |      sqlglot[c] |         sqltree |         sqlparse |          sqlfluff |        sqloxide |    polyglot-sql |\n| ----------------- | --------------- | --------------- | --------------- | ---------------- | ----------------- | --------------- | --------------- |\n|              tpch | 0.002709 (1.00) | 0.000740 (0.27) | 0.002172 (0.80) |  0.014152 (5.22) |  0.241027 (88.97) | 0.000655 (0.24) | 0.000698 (0.26) |\n|             short | 0.000226 (1.00) | 0.000075 (0.33) | 0.000184 (0.81) |  0.000938 (4.15) | 0.031542 (139.47) | 0.000041 (0.18) | 0.000174 (0.77) |\n|   deep_arithmetic | 0.007760 (1.00) | 0.002015 (0.26) | 0.005927 (0.76) |              N/A | 1.359824 (175.22) | 0.003117 (0.40) | 0.002964 (0.38) |\n|          large_in | 0.407987 (1.00) | 0.101644 (0.25) | 0.467943 (1.15) |              N/A |               N/A | 0.147765 (0.36) | 0.105854 (0.26) |\n|            values | 0.466734 (1.00) | 0.113762 (0.24) | 0.522797 (1.12) |              N/A |               N/A | 0.117628 (0.25) | 0.117169 (0.25) |\n|        many_joins | 0.011943 (1.00) | 0.002701 (0.23) | 0.009887 (0.83) |  0.059303 (4.97) | 1.246253 (104.35) | 0.002918 (0.24) | 0.002964 (0.25) |\n|       many_unions | 0.041321 (1.00) | 0.008291 (0.20) | 0.038249 (0.93) |              N/A |  1.826401 (44.20) | 0.012395 (0.30) | 0.013087 (0.32) |\n| nested_subqueries | 0.001200 (1.00) | 0.000235 (0.20) |             N/A |  0.003860 (3.22) |  0.089490 (74.56) | 0.000215 (0.18) | 0.000262 (0.22) |\n|      many_columns | 0.011821 (1.00) | 0.002825 (0.24) | 0.012722 (1.08) | 0.238510 (20.18) |  1.050386 (88.86) | 0.002515 (0.21) | 0.003765 (0.32) |\n|        large_case | 0.035822 (1.00) | 0.008593 (0.24) | 0.033578 (0.94) |              N/A | 4.200220 (117.25) | 0.009870 (0.28) | 0.009442 (0.26) |\n|     complex_where | 0.032710 (1.00) | 0.006602 (0.20) |             N/A |  0.136203 (4.16) |  2.492927 (76.21) | 0.006002 (0.18) | 0.007787 (0.24) |\n|         many_ctes | 0.017610 (1.00) | 0.003630 (0.21) | 0.012377 (0.70) |  0.123620 (7.02) |  0.657611 (37.34) | 0.004197 (0.24) | 0.003273 (0.19) |\n|      many_windows | 0.020790 (1.00) | 0.005751 (0.28) |             N/A |  0.203144 (9.77) |  1.421216 (68.36) | 0.003941 (0.19) | 0.004570 (0.22) |\n|  nested_functions | 0.000703 (1.00) | 0.000189 (0.27) | 0.000754 (1.07) |  0.005082 (7.23) | 0.091007 (129.51) | 0.000168 (0.24) | 0.000225 (0.32) |\n|     large_strings | 0.005073 (1.00) | 0.001480 (0.29) | 0.014533 (2.86) |  0.049392 (9.74) |  0.320672 (63.22) | 0.001616 (0.32) | 0.002151 (0.42) |\n|      many_numbers | 0.103898 (1.00) | 0.024483 (0.24) | 0.120119 (1.16) |              N/A |               N/A | 0.031667 (0.30) | 0.026880 (0.26) |\n\n```\nmake bench            # Run parsing benchmark\nmake bench-optimize   # Run optimization benchmark\n```\n\n## Optional Dependencies\n\nSQLGlot uses [dateutil](https://github.com/dateutil/dateutil) to simplify literal timedelta expressions. The optimizer will not simplify expressions like the following if the module cannot be found:\n\n```sql\nx + interval '1' month\n```\n\n## Supported Dialects\n\n| Dialect | Support Level |\n|---------|---------------|\n| Athena | Official |\n| BigQuery | Official |\n| ClickHouse | Official |\n| Databricks | Official |\n| Doris | Community |\n| Dremio | Community |\n| Drill | Community |\n| Druid | Community |\n| DuckDB | Official |\n| Exasol | Community |\n| Fabric | Community |\n| Hive | Official |\n| Materialize | Community |\n| MySQL | Official |\n| Oracle | Official |\n| Postgres | Official |\n| Presto | Official |\n| PRQL | Community |\n| Redshift | Official |\n| RisingWave | Community |\n| SingleStore | Community |\n| Snowflake | Official |\n| Solr | Community |\n| Spark | Official |\n| SQLite | Official |\n| StarRocks | Official |\n| Tableau | Official |\n| Teradata | Community |\n| Trino | Official |\n| TSQL | Official |\n| YDB | [Plugin](https://pypi.org/project/ydb-sqlglot-plugin) |\n\n**Official Dialects** are maintained by the core SQLGlot team with higher priority for bug fixes and feature additions.\n\n**Community Dialects** are developed and maintained primarily through community contributions. These are fully functional but may receive lower priority for issue resolution compared to officially supported dialects. We welcome and encourage community contributions to improve these dialects.\n\n**Plugin Dialects** (supported since v28.6.0) are third-party dialects developed and maintained in external repositories by independent contributors. These dialects are not part of the SQLGlot codebase and are distributed as separate packages. The SQLGlot team does not provide support or maintenance for plugin dialects — please direct any issues or feature requests to their respective repositories. See [Creating a Dialect Plugin](#creating-a-dialect-plugin) below for information on how to build your own.\n\n### Creating a Dialect Plugin\n\nIf your database isn't supported, you can create a plugin that registers a custom dialect via entry points. Create a package with your dialect class and register it in `setup.py`:\n\n```python\nfrom setuptools import setup\n\nsetup(\n    name=\"mydb-sqlglot-dialect\",\n    entry_points={\n        \"sqlglot.dialects\": [\n            \"mydb = my_package.dialect:MyDB\",\n        ],\n    },\n)\n```\n\nThe dialect will be automatically discovered and can be used like any built-in dialect:\n\n```python\nfrom sqlglot import transpile\ntranspile(\"SELECT * FROM t\", read=\"mydb\", write=\"postgres\")\n```\n\nSee the [Custom Dialects](#custom-dialects) section for implementation details.\n"
  },
  {
    "path": "benchmarks/__init__.py",
    "content": ""
  },
  {
    "path": "benchmarks/compare.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Compare two benchmark JSON files and output a markdown table with diff indicators.\"\"\"\n\nimport json\nimport sys\n\n\ndef _fmt_time(seconds):\n    if seconds >= 1:\n        return f\"{seconds:.2f}s\"\n    if seconds >= 1e-3:\n        return f\"{seconds * 1e3:.1f}ms\"\n    return f\"{seconds * 1e6:.0f}us\"\n\n\ndef _indicator(ratio):\n    \"\"\"Return an emoji indicator based on the speedup/slowdown ratio (pr_time / main_time).\"\"\"\n    if ratio <= 0.95:\n        return \"\\U0001f7e2\\U0001f7e2\"  # 5%+ faster\n    if ratio <= 0.97:\n        return \"\\U0001f7e2\"  # 3-5% faster\n    if ratio <= 0.99:\n        return \"\\U0001f7e9\"  # 1-3% faster\n    if ratio <= 1.01:\n        return \"\\u26aa\"  # no significant change\n    if ratio <= 1.03:\n        return \"\\U0001f7e7\"  # 1-3% slower\n    if ratio <= 1.05:\n        return \"\\U0001f534\"  # 3-5% slower\n    return \"\\U0001f534\\U0001f534\"  # 5%+ slower\n\n\ndef _diff_text(ratio):\n    pct = (ratio - 1.0) * 100\n    if abs(pct) < 0.05:\n        return \"0.0%\"\n    if pct < 0:\n        return f\"{-pct:.1f}% faster\"\n    return f\"{pct:.1f}% slower\"\n\n\ndef compare(main_file, pr_file):\n    with open(main_file) as f:\n        main = json.load(f)\n    with open(pr_file) as f:\n        pr = json.load(f)\n\n    # Collect all query names across both parsers\n    parsers = set()\n    queries = []\n    seen_queries = set()\n    for key in list(main.keys()) + list(pr.keys()):\n        parser, query = key.split(\":\", 1)\n        parsers.add(parser)\n        if query not in seen_queries:\n            queries.append(query)\n            seen_queries.add(query)\n\n    # Sort parsers: sqlglot first, then sqlglotc\n    parser_order = sorted(parsers, key=lambda p: (p != \"sqlglot\", p != \"sqlglotc\", p))\n\n    # Build table\n    lines = []\n    lines.append(\"## Benchmark Results\\n\")\n    lines.append(\n        \"**Legend:** \\U0001f7e2\\U0001f7e2 = 5%+ faster | \\U0001f7e2 = 3-5% faster | \\U0001f7e9 = 1-3% faster | \\u26aa = unchanged | \\U0001f7e7 = 1-3% slower | \\U0001f534 = 3-5% slower | \\U0001f534\\U0001f534 = 5%+ slower\\n\"\n    )\n\n    for parser in parser_order:\n        display = (\n            \"sqlglot\" if parser == \"sqlglot\" else \"sqlglot[c]\" if parser == \"sqlglotc\" else parser\n        )\n        lines.append(f\"\\n### {display}\\n\")\n        lines.append(\"| Query | main | PR | diff |  |\")\n        lines.append(\"| ----- | ---: | ---: | ---: | --- |\")\n\n        for query in queries:\n            key = f\"{parser}:{query}\"\n            main_time = main.get(key)\n            pr_time = pr.get(key)\n\n            if main_time is None and pr_time is None:\n                continue\n\n            main_str = _fmt_time(main_time) if main_time else \"N/A\"\n            pr_str = _fmt_time(pr_time) if pr_time else \"N/A\"\n\n            if main_time and pr_time:\n                ratio = pr_time / main_time\n                diff_str = _diff_text(ratio)\n                ind = _indicator(ratio)\n            else:\n                diff_str = \"N/A\"\n                ind = \"\"\n\n            lines.append(f\"| {query} | {main_str} | {pr_str} | {diff_str} | {ind} |\")\n\n    lines.append(\"\\n---\\n*Comment `/benchmark` to re-run.*\")\n\n    return \"\\n\".join(lines)\n\n\nif __name__ == \"__main__\":\n    if len(sys.argv) != 3:\n        print(\"Usage: compare_benchmarks.py <main.json> <pr.json>\", file=sys.stderr)\n        sys.exit(1)\n    print(compare(sys.argv[1], sys.argv[2]))\n"
  },
  {
    "path": "benchmarks/optimize.py",
    "content": "import sys\nimport os\nimport pyperf\n\n# Add the project root to the path so we can import from tests\nsys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))\n\nfrom sqlglot.optimizer import optimize\nfrom sqlglot import parse_one\nfrom tests.helpers import load_sql_fixture_pairs, TPCH_SCHEMA, TPCDS_SCHEMA\n\n# Deeply nested conditions currently require a lot of recursion\nsys.setrecursionlimit(10000)\n\n\ndef gen_condition(n):\n    return parse_one(\" OR \".join(f\"a = {i} AND b = {i}\" for i in range(n)))\n\n\n# Create benchmark functions that return the setup data\ndef get_tpch_setup():\n    return (\n        [parse_one(sql) for _, sql, _ in load_sql_fixture_pairs(\"optimizer/tpc-h/tpc-h.sql\")],\n        TPCH_SCHEMA,\n    )\n\n\ndef get_tpcds_setup():\n    return (\n        [parse_one(sql) for _, sql, _ in load_sql_fixture_pairs(\"optimizer/tpc-ds/tpc-ds.sql\")],\n        TPCDS_SCHEMA,\n    )\n\n\ndef get_condition_10_setup():\n    return ([gen_condition(10)], {})\n\n\ndef get_condition_100_setup():\n    return ([gen_condition(100)], {})\n\n\ndef get_condition_1000_setup():\n    return ([gen_condition(1000)], {})\n\n\n# Optimizer functions that will be benchmarked\ndef optimize_queries(expressions, schema):\n    for e in expressions:\n        optimize(e, schema)\n\n\ndef run_benchmarks():\n    runner = pyperf.Runner()\n\n    # Define benchmarks with their setup functions\n    benchmarks = {\n        \"tpch\": get_tpch_setup,\n        # \"tpcds\": get_tpcds_setup,  # This is left out because it's too slow in CI\n        \"condition_10\": get_condition_10_setup,\n        \"condition_100\": get_condition_100_setup,\n        \"condition_1000\": get_condition_1000_setup,\n    }\n\n    for benchmark_name, benchmark_setup in benchmarks.items():\n        expressions, schema = benchmark_setup()\n\n        runner.bench_func(f\"optimize_{benchmark_name}\", optimize_queries, expressions, schema)\n\n\nif __name__ == \"__main__\":\n    run_benchmarks()\n"
  },
  {
    "path": "benchmarks/parse.py",
    "content": "import argparse\nimport inspect\nimport json\nimport os\nimport subprocess\nimport sys\nimport tempfile\nimport time\n\n\n# --- Query definitions ---\n\nlarge_in = (\n    \"SELECT * FROM t WHERE x IN (\" + \", \".join(f\"'s{i}'\" for i in range(20000)) + \")\"\n    \" OR y IN (\" + \", \".join(str(i) for i in range(20000)) + \")\"\n)\n\nvalues = \"INSERT INTO t VALUES \" + \", \".join(\n    \"(\" + \", \".join(f\"'s{i}_{j}'\" if j % 2 else str(i * 20 + j) for j in range(20)) + \")\"\n    for i in range(2000)\n)\n\nmany_joins = \"SELECT * FROM t0\" + \"\".join(\n    f\"\\nJOIN t{i} ON t{i}.id = t{i - 1}.id\" for i in range(1, 200)\n)\n\nmany_unions = \"\\nUNION ALL\\n\".join(f\"SELECT {i} AS a, 's{i}' AS b FROM t{i}\" for i in range(500))\n\nshort = \"SELECT 1 AS a, CASE WHEN 1 THEN 1 WHEN 2 THEN 2 ELSE 3 END AS b, c FROM x\"\n\ndeep_arithmetic = \"SELECT 1+\"\ndeep_arithmetic += \"+\".join(str(i) for i in range(500))\ndeep_arithmetic += \" AS a, 2*\"\ndeep_arithmetic += \"*\".join(str(i) for i in range(500))\ndeep_arithmetic += \" AS b FROM x\"\n\nnested_subqueries = (\n    \"SELECT * FROM \" + \"\".join(\"(SELECT * FROM \" for _ in range(20)) + \"t\" + \")\" * 20\n)\n\nmany_columns = \"SELECT \" + \", \".join(f\"c{i}\" for i in range(1000)) + \" FROM t\"\n\nlarge_case = (\n    \"SELECT CASE \" + \" \".join(f\"WHEN x = {i} THEN {i}\" for i in range(1000)) + \" ELSE -1 END FROM t\"\n)\n\ncomplex_where = \"SELECT * FROM t WHERE \" + \" AND \".join(\n    f\"(c{i} > {i} OR c{i} LIKE '%s{i}%' OR c{i} BETWEEN {i} AND {i + 10} OR c{i} IS NULL)\"\n    for i in range(200)\n)\n\nmany_ctes = (\n    \"WITH \"\n    + \", \".join(f\"t{i} AS (SELECT {i} AS a FROM t{i - 1 if i else 'base'})\" for i in range(200))\n    + \" SELECT * FROM t199\"\n)\n\nmany_windows = (\n    \"SELECT \"\n    + \", \".join(\n        f\"SUM(c{i}) OVER (PARTITION BY p{i % 10} ORDER BY o{i % 5}) AS w{i}\" for i in range(200)\n    )\n    + \" FROM t\"\n)\n\nnested_functions = \"SELECT \" + \"COALESCE(\" * 20 + \"x\" + \", NULL)\" * 20 + \" FROM t\"\n\nlarge_strings = \"SELECT \" + \", \".join(f\"'{'x' * 100}'\" for i in range(500)) + \" FROM t\"\n\nmany_numbers = \"SELECT \" + \", \".join(str(i) for i in range(10000)) + \" FROM t\"\n\ntpch = \"\"\"\nWITH \"_e_0\" AS (\n  SELECT\n    \"partsupp\".\"ps_partkey\" AS \"ps_partkey\",\n    \"partsupp\".\"ps_suppkey\" AS \"ps_suppkey\",\n    \"partsupp\".\"ps_supplycost\" AS \"ps_supplycost\"\n  FROM \"partsupp\" AS \"partsupp\"\n), \"_e_1\" AS (\n  SELECT\n    \"region\".\"r_regionkey\" AS \"r_regionkey\",\n    \"region\".\"r_name\" AS \"r_name\"\n  FROM \"region\" AS \"region\"\n  WHERE\n    \"region\".\"r_name\" = 'EUROPE'\n)\nSELECT\n  \"supplier\".\"s_acctbal\" AS \"s_acctbal\",\n  \"supplier\".\"s_name\" AS \"s_name\",\n  \"nation\".\"n_name\" AS \"n_name\",\n  \"part\".\"p_partkey\" AS \"p_partkey\",\n  \"part\".\"p_mfgr\" AS \"p_mfgr\",\n  \"supplier\".\"s_address\" AS \"s_address\",\n  \"supplier\".\"s_phone\" AS \"s_phone\",\n  \"supplier\".\"s_comment\" AS \"s_comment\"\nFROM (\n  SELECT\n    \"part\".\"p_partkey\" AS \"p_partkey\",\n    \"part\".\"p_mfgr\" AS \"p_mfgr\",\n    \"part\".\"p_type\" AS \"p_type\",\n    \"part\".\"p_size\" AS \"p_size\"\n  FROM \"part\" AS \"part\"\n  WHERE\n    \"part\".\"p_size\" = 15\n    AND \"part\".\"p_type\" LIKE '%BRASS'\n) AS \"part\"\nLEFT JOIN (\n  SELECT\n    MIN(\"partsupp\".\"ps_supplycost\") AS \"_col_0\",\n    \"partsupp\".\"ps_partkey\" AS \"_u_1\"\n  FROM \"_e_0\" AS \"partsupp\"\n  CROSS JOIN \"_e_1\" AS \"region\"\n  JOIN (\n    SELECT\n      \"nation\".\"n_nationkey\" AS \"n_nationkey\",\n      \"nation\".\"n_regionkey\" AS \"n_regionkey\"\n    FROM \"nation\" AS \"nation\"\n  ) AS \"nation\"\n    ON \"nation\".\"n_regionkey\" = \"region\".\"r_regionkey\"\n  JOIN (\n    SELECT\n      \"supplier\".\"s_suppkey\" AS \"s_suppkey\",\n      \"supplier\".\"s_nationkey\" AS \"s_nationkey\"\n    FROM \"supplier\" AS \"supplier\"\n  ) AS \"supplier\"\n    ON \"supplier\".\"s_nationkey\" = \"nation\".\"n_nationkey\"\n    AND \"supplier\".\"s_suppkey\" = \"partsupp\".\"ps_suppkey\"\n  GROUP BY\n    \"partsupp\".\"ps_partkey\"\n) AS \"_u_0\"\n  ON \"part\".\"p_partkey\" = \"_u_0\".\"_u_1\"\nCROSS JOIN \"_e_1\" AS \"region\"\nJOIN (\n  SELECT\n    \"nation\".\"n_nationkey\" AS \"n_nationkey\",\n    \"nation\".\"n_name\" AS \"n_name\",\n    \"nation\".\"n_regionkey\" AS \"n_regionkey\"\n  FROM \"nation\" AS \"nation\"\n) AS \"nation\"\n  ON \"nation\".\"n_regionkey\" = \"region\".\"r_regionkey\"\nJOIN \"_e_0\" AS \"partsupp\"\n  ON \"part\".\"p_partkey\" = \"partsupp\".\"ps_partkey\"\nJOIN (\n  SELECT\n    \"supplier\".\"s_suppkey\" AS \"s_suppkey\",\n    \"supplier\".\"s_name\" AS \"s_name\",\n    \"supplier\".\"s_address\" AS \"s_address\",\n    \"supplier\".\"s_nationkey\" AS \"s_nationkey\",\n    \"supplier\".\"s_phone\" AS \"s_phone\",\n    \"supplier\".\"s_acctbal\" AS \"s_acctbal\",\n    \"supplier\".\"s_comment\" AS \"s_comment\"\n  FROM \"supplier\" AS \"supplier\"\n) AS \"supplier\"\n  ON \"supplier\".\"s_nationkey\" = \"nation\".\"n_nationkey\"\n  AND \"supplier\".\"s_suppkey\" = \"partsupp\".\"ps_suppkey\"\nWHERE\n  \"partsupp\".\"ps_supplycost\" = \"_u_0\".\"_col_0\"\n  AND NOT \"_u_0\".\"_u_1\" IS NULL\nORDER BY\n  \"supplier\".\"s_acctbal\" DESC,\n  \"nation\".\"n_name\",\n  \"supplier\".\"s_name\",\n  \"part\".\"p_partkey\"\nLIMIT 100\n\"\"\"\n\nQUERIES = {\n    \"tpch\": tpch,\n    \"short\": short,\n    \"deep_arithmetic\": deep_arithmetic,\n    \"large_in\": large_in,\n    \"values\": values,\n    \"many_joins\": many_joins,\n    \"many_unions\": many_unions,\n    \"nested_subqueries\": nested_subqueries,\n    \"many_columns\": many_columns,\n    \"large_case\": large_case,\n    \"complex_where\": complex_where,\n    \"many_ctes\": many_ctes,\n    \"many_windows\": many_windows,\n    \"nested_functions\": nested_functions,\n    \"large_strings\": large_strings,\n    \"many_numbers\": many_numbers,\n}\n\n\n# --- Parser definitions ---\n\n\ndef sqlglot_parse(sql):\n    import sqlglot\n\n    sqlglot.parse_one(sql, error_level=sqlglot.ErrorLevel.IGNORE)\n\n\ndef sqltree_parse(sql):\n    import sqltree\n\n    sqltree.api.sqltree(sql.replace('\"', \"`\").replace(\"''\", '\"'))\n\n\ndef sqlparse_parse(sql):\n    import sqlparse\n\n    sqlparse.parse(sql)\n\n\ndef moz_sql_parser_parse(sql):\n    import moz_sql_parser\n\n    moz_sql_parser.parse(sql)\n\n\ndef sqloxide_parse(sql):\n    import sqloxide\n\n    sqloxide.parse_sql(sql, dialect=\"ansi\")\n\n\ndef sqlfluff_parse(sql):\n    import sqlfluff\n\n    sqlfluff.parse(sql)\n\n\ndef polyglot_sql_parse(sql):\n    import polyglot_sql\n\n    polyglot_sql.parse_one(sql)\n\n\nTHIRD_PARTY_PARSERS = {\n    \"sqltree\": sqltree_parse,\n    \"sqlparse\": sqlparse_parse,\n    \"sqlfluff\": sqlfluff_parse,\n    \"moz_sql_parser\": moz_sql_parser_parse,\n    \"sqloxide\": sqloxide_parse,\n    \"polyglot_sql\": polyglot_sql_parse,\n}\n\nDISPLAY_NAMES = {\n    \"sqlglot\": \"sqlglot\",\n    \"sqlglotc\": \"sqlglot[c]\",\n    \"polyglot_sql\": \"polyglot-sql\",\n    \"sqltree\": \"sqltree\",\n    \"sqlparse\": \"sqlparse\",\n    \"moz_sql_parser\": \"moz_sql_parser\",\n    \"sqlfluff\": \"sqlfluff\",\n    \"sqloxide\": \"sqloxide\",\n}\n\n\n# --- Third-party parser discovery ---\n\n\ndef _check_parser(parse_fn, queries):\n    \"\"\"Check which queries a parser can handle, one subprocess per query (isolates segfaults).\n    Returns None if not installed, else set of query names.\"\"\"\n    fn_name = parse_fn.__name__\n    source = inspect.getsource(parse_fn)\n    supported = set()\n    installed = None\n\n    for name, sql in queries.items():\n        code = f\"\"\"import signal\n\ndef _timeout(signum, frame):\n    raise TimeoutError()\n\nsignal.signal(signal.SIGALRM, _timeout)\nsignal.alarm(5)\n\n{source}\n\n{fn_name}({repr(sql)})\n\"\"\"\n        with tempfile.NamedTemporaryFile(mode=\"w\", encoding=\"utf8\", suffix=\".py\", delete=True) as f:\n            f.write(code)\n            f.flush()\n            try:\n                result = subprocess.run([sys.executable, f.name], capture_output=True, timeout=10)\n            except subprocess.TimeoutExpired:\n                installed = True\n                continue\n            if b\"ModuleNotFoundError\" in result.stderr:\n                return None\n            installed = True\n            if result.returncode == 0:\n                supported.add(name)\n\n    return supported if installed else None\n\n\ndef _discover_parsers():\n    \"\"\"Discover available third-party parsers and which queries they support.\"\"\"\n    valid_pairs = set()\n    available = []\n    for parser_name, parse_fn in THIRD_PARTY_PARSERS.items():\n        supported = _check_parser(parse_fn, QUERIES)\n        if supported is None:\n            continue\n        for query_name in supported:\n            valid_pairs.add((parser_name, query_name))\n        available.append(parser_name)\n    return available, valid_pairs\n\n\n# --- Benchmarking ---\n\n\n_quiet = False\n\n\ndef _bench(name, fn, *args, iterations=5):\n    \"\"\"Benchmark fn(*args) and return the best time in seconds.\"\"\"\n    best = float(\"inf\")\n    for _ in range(iterations):\n        t0 = time.perf_counter()\n        fn(*args)\n        elapsed = time.perf_counter() - t0\n        if elapsed < best:\n            best = elapsed\n        if elapsed > 1:\n            break\n    if not _quiet:\n        print(f\"  {name}: {_fmt_time(best)}\")\n    return best\n\n\ndef _bench_sqlglot(results):\n    \"\"\"Benchmark sqlglot (or sqlglotc if .so loaded) and add to results.\"\"\"\n    import sqlglot.expressions.core as _ec\n\n    prefix = \"sqlglotc\" if _ec.__file__.endswith(\".so\") else \"sqlglot\"\n    for query_name, sql in QUERIES.items():\n        results[f\"{prefix}:{query_name}\"] = _bench(f\"{prefix}:{query_name}\", sqlglot_parse, sql)\n    return prefix\n\n\ndef _bench_third_party(results):\n    \"\"\"Benchmark third-party parsers and add to results. Returns list of available parser names.\"\"\"\n    available, valid_pairs = _discover_parsers()\n    for query_name, sql in QUERIES.items():\n        for parser_name, parse_fn in THIRD_PARTY_PARSERS.items():\n            if (parser_name, query_name) in valid_pairs:\n                results[f\"{parser_name}:{query_name}\"] = _bench(\n                    f\"{parser_name}:{query_name}\", parse_fn, sql\n                )\n    return available\n\n\n# --- Table printing ---\n\n\ndef _fmt_ratio(ratio):\n    return f\"{ratio:.2f}\"\n\n\ndef _fmt_time(seconds):\n    if seconds >= 1:\n        return f\"{seconds:.2f} sec\"\n    if seconds >= 1e-3:\n        return f\"{seconds * 1e3:.2f} ms\"\n    return f\"{seconds * 1e6:.1f} us\"\n\n\ndef _print_table(base_parser, all_parsers, results):\n    query_width = max(len(q) for q in QUERIES)\n    query_width = max(query_width, len(\"Query\"))\n\n    # Pre-compute all cells to determine column widths\n    cells = {}\n    for query_name in QUERIES:\n        base_time = results.get(f\"{base_parser}:{query_name}\")\n        for p in all_parsers:\n            t = results.get(f\"{p}:{query_name}\")\n            if t is not None and base_time:\n                ratio = t / base_time\n                cells[(p, query_name)] = f\"{t:.6f} ({_fmt_ratio(ratio)})\"\n            else:\n                cells[(p, query_name)] = \"N/A\"\n\n    col_widths = {}\n    for p in all_parsers:\n        name = DISPLAY_NAMES.get(p, p)\n        w = len(name)\n        for query_name in QUERIES:\n            w = max(w, len(cells[(p, query_name)]))\n        col_widths[p] = w\n\n    header = f\"| {'Query':>{query_width}} |\"\n    sep = f\"| {'-' * query_width} |\"\n    for p in all_parsers:\n        name = DISPLAY_NAMES.get(p, p)\n        header += f\" {name:>{col_widths[p]}} |\"\n        sep += f\" {'-' * col_widths[p]} |\"\n\n    print()\n    print(header)\n    print(sep)\n\n    for query_name in QUERIES:\n        row = f\"| {query_name:>{query_width}} |\"\n        for p in all_parsers:\n            row += f\" {cells[(p, query_name)]:>{col_widths[p]}} |\"\n        print(row)\n\n\n# --- Subprocess entry point for .so mode ---\n\n\ndef _has_so_files():\n    import glob\n\n    return bool(glob.glob(\"sqlglot/**/*.so\", recursive=True))\n\n\ndef _run_subprocess():\n    \"\"\"Run sqlglot benchmarks and print results to stdout as key=value lines.\"\"\"\n    global _quiet\n    _quiet = bool(os.environ.get(\"_BENCH_QUIET\"))\n    results = {}\n    _bench_sqlglot(results)\n    for key, value in results.items():\n        print(f\"{key}={value}\")\n\n\n# --- Main ---\n\n\ndef _parse_args():\n    parser = argparse.ArgumentParser(description=\"SQLGlot parser benchmarks\")\n    parser.add_argument(\"--json\", metavar=\"FILE\", help=\"Write results as JSON to FILE\")\n    parser.add_argument(\"--quiet\", action=\"store_true\", help=\"Suppress progress output\")\n    parser.add_argument(\n        \"--sqlglot-only\",\n        action=\"store_true\",\n        help=\"Only benchmark sqlglot/sqlglotc (skip third-party parsers)\",\n    )\n    return parser.parse_args()\n\n\nif __name__ == \"__main__\":\n    if os.environ.get(\"_BENCH_SUBPROCESS\"):\n        _run_subprocess()\n    else:\n        args = _parse_args()\n        _quiet = args.quiet\n\n        if _has_so_files():\n            if not _quiet:\n                print(\"=== Running sqlglot[c] ===\", flush=True)\n            env = {**os.environ, \"_BENCH_SUBPROCESS\": \"1\"}\n            if _quiet:\n                env[\"_BENCH_QUIET\"] = \"1\"\n            proc = subprocess.run(\n                [sys.executable, __file__], env=env, capture_output=True, text=True, check=True\n            )\n            results = {}\n            for line in proc.stdout.splitlines():\n                if \"=\" in line:\n                    key, value = line.split(\"=\", 1)\n                    results[key] = float(value)\n                elif not _quiet:\n                    print(line)\n\n            if not _quiet:\n                print(\"\\n=== Hiding .so files ===\", flush=True)\n            subprocess.run([\"make\", \"hidec\"], check=True, capture_output=True)\n\n            try:\n                if not _quiet:\n                    print(\"\\n=== Running pure Python ===\", flush=True)\n                _bench_sqlglot(results)\n                if not args.sqlglot_only:\n                    if not _quiet:\n                        print(\"\\n=== Running third-party parsers ===\", flush=True)\n                    available = _bench_third_party(results)\n                else:\n                    available = []\n            finally:\n                subprocess.run([\"make\", \"showc\"], capture_output=True)\n\n            if args.json:\n                with open(args.json, \"w\") as f:\n                    json.dump(results, f, indent=2)\n            else:\n                _print_table(\"sqlglot\", [\"sqlglot\", \"sqlglotc\"] + available, results)\n        else:\n            results = {}\n            prefix = _bench_sqlglot(results)\n            if not args.sqlglot_only:\n                available = _bench_third_party(results)\n            else:\n                available = []\n\n            if args.json:\n                with open(args.json, \"w\") as f:\n                    json.dump(results, f, indent=2)\n            else:\n                _print_table(prefix, [prefix] + available, results)\n"
  },
  {
    "path": "pdoc/cli.py",
    "content": "#!/usr/bin/env python3\n\nfrom importlib import import_module\nfrom pathlib import Path\nfrom unittest import mock\n\nfrom pdoc.__main__ import cli, parser\n\n# Need this import or else import_module doesn't work\nimport sqlglot  # noqa\nfrom sqlglot.dialects import *  # noqa: F403\n\n# Load all dialects up front because lazy loading breaks pdoc's dynamic importing\nsqlglot.dialects.__all__ = [globals()[attr_name] for attr_name in sqlglot.dialects.__all__]\n\n\ndef mocked_import(*args, **kwargs):\n    \"\"\"Return a MagicMock if import fails for any reason\"\"\"\n    try:\n        return import_module(*args, **kwargs)\n    except Exception:\n        mocked_module = mock.MagicMock()\n        mocked_module.__name__ = args[0]\n        return mocked_module\n\n\nif __name__ == \"__main__\":\n    # Mock uninstalled dependencies so pdoc can still work\n    with mock.patch(\"importlib.import_module\", side_effect=mocked_import):\n        opts = parser.parse_args()\n        opts.docformat = \"google\"\n        opts.modules = [\"sqlglot\"]\n        opts.footer_text = \"Copyright (c) 2023 Toby Mao\"\n        opts.template_directory = Path(__file__).parent.joinpath(\"templates\").absolute()\n        opts.edit_url = [\"sqlglot=https://github.com/tobymao/sqlglot/tree/main/sqlglot/\"]\n\n        with mock.patch(\"pdoc.__main__.parser\", **{\"parse_args.return_value\": opts}):\n            cli()\n"
  },
  {
    "path": "pdoc/templates/module.html.jinja2",
    "content": "{% extends \"default/module.html.jinja2\" %}\n\n{% if module.docstring %}\n    {% macro module_name() %}\n    {% endmacro %}\n{% endif %}\n"
  },
  {
    "path": "posts/ast_primer.md",
    "content": "# Primer on SQLGlot's Abstract Syntax Tree\n\nSQLGlot is a powerful tool for analyzing and transforming SQL, but the learning curve can be intimidating.\n\nThis post is intended to familiarize newbies with SQLGlot's abstract syntax trees, how to traverse them, and how to mutate them.\n\n## The tree\n\nSQLGlot parses SQL into an abstract syntax tree (AST).\n```python\nfrom sqlglot import parse_one\n\nast = parse_one(\"SELECT a FROM (SELECT a FROM x) AS x\")\n```\n\nAn AST is a data structure that represents a SQL statement. The best way to glean the structure of a particular AST is python's builtin `repr` function:\n```python\nrepr(ast)\n\n# Select(\n#   expressions=[\n#     Column(\n#       this=Identifier(this=a, quoted=False))],\n#   from=From(\n#     this=Subquery(\n#       this=Select(\n#         expressions=[\n#           Column(\n#             this=Identifier(this=a, quoted=False))],\n#         from=From(\n#           this=Table(\n#             this=Identifier(this=x, quoted=False)))),\n#       alias=TableAlias(\n#         this=Identifier(this=x, quoted=False)))))\n```\n\nThis is a textual representation of the internal data structure. Here's a breakdown of some of its components:\n```\n`Select` is the expression type\n  |\nSelect(\n  expressions=[  ------------------------------- `expressions` is a child key of `Select`\n    Column(  ----------------------------------- `Column` is the expression type of the child\n      this=Identifier(this=a, quoted=False))],\n  from=From(  ---------------------------------- `from` is another child key of `Select`\n  ...\n```\n\n## Nodes of the tree\n\nThe nodes in this tree are instances of `sqlglot.Expression`. Nodes reference their children in `args` and their parent in `parent`:\n```python\nast.args\n# {\n#    \"expressions\": [Column(this=...)],\n#    \"from\": From(this=...),\n#    ...\n# }\n\nast.args[\"expressions\"][0]\n# Column(this=...)\n\nast.args[\"expressions\"][0].args[\"this\"]\n# Identifier(this=...)\n\nast.args[\"from\"]\n# From(this=...)\n\nassert ast.args[\"expressions\"][0].args[\"this\"].parent.parent is ast\n```\n\nChildren can either be:\n1. An Expression instance\n2. A list of Expression instances\n3. Another Python object, such as str or bool. This will always be a leaf node in the tree.\n\nNavigating this tree requires an understanding of the different Expression types. The best way to browse Expression types is directly in the code at [expressions.py](../sqlglot/expressions.py). Let's look at a simplified version of one Expression type:\n```python\nclass Column(Expression):\n    arg_types = {\n      \"this\": True,\n      \"table\": False,\n      ...\n    }\n```\n\n`Column` subclasses `Expression`.\n\n`arg_types` is a class attribute that specifies the possible children. The `args` keys of an Expression instance correspond to the `arg_types` keys of its class. The values of the `arg_types` dict are `True` if the key is required.\n\nThere are some common `arg_types` keys:\n- \"this\": This is typically used for the primary child. In `Column`, \"this\" is the identifier for the column's name.\n- \"expression\": This is typically used for the secondary child\n- \"expressions\": This is typically used for a primary list of children\n\nThere aren't strict rules for when these keys are used, but they help with some of the convenience methods available on all Expression types:\n- `Expression.this`: shorthand for `self.args.get(\"this\")`\n- `Expression.expression`: similarly, shorthand for the expression arg\n- `Expression.expressions`: similarly, shorthand for the expressions list arg\n- `Expression.name`: text name for whatever `this` is\n\n`arg_types` don't specify the possible Expression types of children. This can be a challenge when you are writing code to traverse a particular AST and you don't know what to expect. A common trick is to parse an example query and print out the `repr`.\n\nYou can traverse an AST using just args, but there are some higher-order functions for programmatic traversal.\n\n> [!NOTE]\n> SQLGlot can parse and generate SQL for many different dialects. However, there is only a single set of Expression types for all dialects. We like to say that the AST can represent the _superset_ of all dialects.\n>\n> Sometimes, SQLGlot will parse SQL from a dialect into Expression types you didn't expect:\n>\n> ```python\n> ast = parse_one(\"SELECT NOW()\", dialect=\"postgres\")\n>\n> repr(ast)\n> # Select(\n> #   expressions=[\n> #     CurrentTimestamp()])\n> ```\n>\n> This is because SQLGlot tries to converge dialects on a standard AST. This means you can often write one piece of code that handles multiple dialects.\n\n## Traversing the AST\n\nAnalyzing a SQL statement requires traversing this data structure. There are a few ways to do this:\n\n### Args\n\nIf you know the structure of an AST, you can use `Expression.args` just like above. However, this can be very limited if you're dealing with arbitrary SQL.\n\n### Walk methods\n\nThe walk methods of `Expression` (`find`, `find_all`, and `walk`) are the simplest way to analyze an AST.\n\n`find` and `find_all` search an AST for specific Expression types:\n```python\nfrom sqlglot import exp\n\nast.find(exp.Select)\n# Select(\n#   expressions=[\n#     Column(\n#       this=Identifier(this=a, quoted=False))],\n# ...\n\nlist(ast.find_all(exp.Select))\n# [Select(\n#   expressions=[\n#     Column(\n#       this=Identifier(this=a, quoted=False))],\n# ...\n```\n\nBoth `find` and `find_all` are built on `walk`, which gives finer grained control:\n```python\nfor node in ast.walk():\n    ...\n```\n\n> [!WARNING]\n> Here's a common pitfall of the walk methods:\n> ```python\n> ast.find_all(exp.Table)\n> ```\n> At first glance, this seems like a great way to find all tables in a query. However, `Table` instances are not always tables in your database. Here's an example where this fails:\n> ```python\n> ast = parse_one(\"\"\"\n> WITH x AS (\n>   SELECT a FROM y\n> )\n> SELECT a FROM x\n> \"\"\")\n>\n> # This is NOT a good way to find all tables in the query!\n> for table in ast.find_all(exp.Table):\n>     print(table)\n>\n> # x  -- this is a common table expression, NOT an actual table\n> # y\n> ```\n>\n> For programmatic traversal of ASTs that requires deeper semantic understanding of a query, you need \"scope\".\n\n### Scope\n\nScope is a traversal module that handles more semantic context of SQL queries. It's harder to use than the `walk` methods but is more powerful:\n```python\nfrom sqlglot.optimizer.scope import build_scope\n\nast = parse_one(\"\"\"\nWITH x AS (\n  SELECT a FROM y\n)\nSELECT a FROM x\n\"\"\")\n\nroot = build_scope(ast)\nfor scope in root.traverse():\n    print(scope)\n\n# Scope<SELECT a FROM y>\n# Scope<WITH x AS (SELECT a FROM y) SELECT a FROM x>\n```\n\nLet's use this for a better way to find all tables in a query:\n```python\ntables = [\n    source\n\n    # Traverse the Scope tree, not the AST\n    for scope in root.traverse()\n\n    # `selected_sources` contains sources that have been selected in this scope, e.g. in a FROM or JOIN clause.\n    # `alias` is the name of this source in this particular scope.\n    # `node` is the AST node instance\n    # if the selected source is a subquery (including common table expressions),\n    #     then `source` will be the Scope instance for that subquery.\n    # if the selected source is a table,\n    #     then `source` will be a Table instance.\n    for alias, (node, source) in scope.selected_sources.items()\n    if isinstance(source, exp.Table)\n]\n\nfor table in tables:\n    print(table)\n\n# y  -- Success!\n```\n\n`build_scope` returns an instance of the `Scope` class. `Scope` has numerous methods for inspecting a query. The best way to browse these methods is directly in the code at [scope.py](../sqlglot/optimizer/scope.py). You can also look for examples of how Scope is used throughout SQLGlot's [optimizer](../sqlglot/optimizer) module.\n\nMany methods of Scope depend on a fully qualified SQL expression. For example, let's say we want to trace the lineage of columns in this query:\n```python\nast = parse_one(\"\"\"\nSELECT\n  a,\n  c\nFROM (\n  SELECT\n    a,\n    b\n  FROM x\n) AS x\nJOIN (\n  SELECT\n    b,\n    c\n  FROM y\n) AS y\n  ON x.b = y.b\n\"\"\")\n```\n\nJust looking at the outer query, it's not obvious that column `a` comes from table `x` without looking at the columns of the subqueries.\n\nWe can use the [qualify](../sqlglot/optimizer/qualify.py) function to prefix all columns in an AST with their table name like so:\n```python\nfrom sqlglot.optimizer.qualify import qualify\n\nqualify(ast)\n# SELECT\n#   x.a AS a,\n#   y.c AS c\n# FROM (\n#   SELECT\n#     x.a AS a,\n#     x.b AS b\n#   FROM x AS x\n# ) AS x\n# JOIN (\n#   SELECT\n#     y.b AS b,\n#     y.c AS c\n#   FROM y AS y\n# ) AS y\n#   ON x.b = y.b\n```\n\nNow we can trace a column to its source. Here's how we might find the table or subquery for all columns in a qualified AST:\n```python\nfrom sqlglot.optimizer.scope import find_all_in_scope\n\nroot = build_scope(ast)\n\n# `find_all_in_scope` is similar to `Expression.find_all`, except it doesn't traverse into subqueries\nfor column in find_all_in_scope(root.expression, exp.Column):\n    print(f\"{column} => {root.sources[column.table]}\")\n\n# x.a => Scope<SELECT x.a AS a, x.b AS b FROM x AS x>\n# y.c => Scope<SELECT y.b AS b, y.c AS c FROM y AS y>\n# x.b => Scope<SELECT x.a AS a, x.b AS b FROM x AS x>\n# y.b => Scope<SELECT y.b AS b, y.c AS c FROM y AS y>\n```\n\nFor a complete example of tracing column lineage, check out the [lineage](../sqlglot/lineage.py) module.\n\n> [!NOTE]\n> Some queries require the database schema for disambiguation. For example:\n>\n> ```sql\n> SELECT a FROM x CROSS JOIN y\n> ```\n>\n> Column `a` might come from table `x` or `y`. In these cases, you must pass the `schema` into `qualify`.\n\n## Mutating the tree\n\nYou can also modify an AST or build one from scratch. There are a few ways to do this.\n\n### High-level builder methods\n\nSQLGlot has methods for programmatically building up expressions similar to how you might in an ORM:\n```python\nast = (\n    exp\n    .select(\"a\", \"b\")\n    .from_(\"x\")\n    .where(\"b < 4\")\n    .limit(10)\n)\n```\n\n> [!WARNING]\n> High-level builder methods will attempt to parse string arguments into Expressions. This can be very convenient, but make sure to keep in mind the dialect of the string. If its written in a specific dialect, you need to set the `dialect` argument.\n>\n> You can avoid parsing by passing Expressions as arguments, e.g. `.where(exp.column(\"b\") < 4)` instead of `.where(\"b < 4\")`\n\nThese methods can be used on any AST, including ones you've parsed:\n```python\nast = parse_one(\"\"\"\nSELECT * FROM (SELECT a, b FROM x)\n\"\"\")\n\n# To modify the AST in-place, set `copy=False`\nast.args[\"from\"].this.this.select(\"c\", copy=False)\n\nprint(ast)\n# SELECT * FROM (SELECT a, b, c FROM x)\n```\n\nThe best place to browse all the available high-level builder methods and their parameters is, as always, directly in the code at [expressions.py](../sqlglot/expressions.py).\n\n### Low-level builder methods\n\nHigh-level builder methods don't account for all possible expressions you might want to build. In the case where a particular high-level method is missing, use the low-level methods. Here are some examples:\n```python\nnode = ast.args[\"from\"].this.this\n\n# These all do the same thing:\n\n# high-level\nnode.select(\"c\", copy=False)\n# low-level\nnode.set(\"expressions\", node.expressions + [exp.column(\"c\")])\nnode.append(\"expressions\", exp.column(\"c\"))\nnode.replace(node.copy().select(\"c\"))\n```\n> [!NOTE]\n> In general, you should use `Expression.set` and `Expression.append` instead of mutating `Expression.args` directly. `set` and `append` take care to update node references like `parent`.\n\nYou can also instantiate AST nodes directly:\n\n```python\ncol = exp.Column(\n    this=exp.to_identifier(\"c\")\n)\nnode.append(\"expressions\", col)\n```\n\n> [!WARNING]\n> Because SQLGlot doesn't verify the types of args, it's easy to instantiate an invalid AST Node that won't generate to SQL properly. Take extra care to inspect the expected types of a node using the methods described above.\n\n### Transform\n\nThe `Expression.transform` method applies a function to all nodes in an AST in depth-first, pre-order.\n\n```python\ndef transformer(node):\n    if isinstance(node, exp.Column) and node.name == \"a\":\n        # Return a new node to replace `node`\n        return exp.func(\"FUN\", node)\n    # Or return `node` to do nothing and continue traversing the tree\n    return node\n\nprint(parse_one(\"SELECT a, b FROM x\").transform(transformer))\n# SELECT FUN(a), b FROM x\n```\n\n> [!WARNING]\n> As with the walk methods, `transform` doesn't manage scope. For safely transforming the columns and tables in complex expressions, you should probably use Scope.\n\n## Summed up\n\nSQLGlot parses SQL statements into an abstract syntax tree (AST) where nodes are instances of `sqlglot.Expression`.\n\nThere are 3 ways to traverse an AST:\n1. **args** - use this when you know the exact structure of the AST you're dealing with.\n2. **walk methods** - this is the easiest way. Use this for simple cases.\n3. **scope** - this is the hardest way. Use this for more complex cases that must handle the semantic context of a query.\n\nThere are 3 ways to mutate an AST\n1. **high-level builder methods** - use this when you know the exact structure of the AST you're dealing with.\n2. **low-level builder methods** - use this only when high-level builder methods don't exist for what you're trying to build.\n3. **transform** - use this for simple transformations on arbitrary statements.\n\nAnd, of course, these mechanisms can be mixed and matched. For example, maybe you need to use scope to traverse an arbitrary AST and the high-level builder methods to mutate it in-place.\n\nStill need help? [Get in touch!](../README.md#get-in-touch)\n"
  },
  {
    "path": "posts/onboarding.md",
    "content": "# Table of Contents\n- [Table of Contents](#table-of-contents)\n  - [Onboarding](#onboarding)\n  - [Tokenizer](#tokenizer)\n  - [Parser](#parser)\n    - [Token Index](#token-index)\n    - [Matching text](#matching-text)\n    - [Utility methods](#utility-methods)\n    - [Command fallback](#command-fallback)\n    - [Dialect-specific parsing](#dialect-specific-parsing)\n  - [Generator](#generator)\n    - [Generating queries](#generating-queries)\n    - [Utility methods](#utility-methods-1)\n    - [Pretty print](#pretty-print)\n  - [Schema](#schema)\n  - [Optimizer](#optimizer)\n    - [Optimization rules](#optimization-rules)\n      - [Qualify](#qualify)\n        - [Identifier normalization](#identifier-normalization)\n        - [Qualifying tables \\& columns](#qualifying-tables--columns)\n      - [Type annotation](#type-annotation)\n  - [Dialects](#dialects)\n    - [Implementing a custom dialect](#implementing-a-custom-dialect)\n  - [Column Level Lineage](#column-level-lineage)\n    - [Implementation details](#implementation-details)\n\n## Onboarding\nThis document aims to familiarize the reader with SQLGlot's codebase & architecture.\n\nGenerally, some background knowledge about programming languages / compilers is required. A good starting point is [Crafting Interpreters](https://craftinginterpreters.com/) by Robert Nystrom, which served as the foundation when SQLGlot was initially created.\n\nAt a high level, SQLGlot transpilation involves three modules:\n\n- [Tokenizer](#tokenizer): Converts raw code into a sequence of “tokens”, one for each word/symbol\n- [Parser](#parser): Converts sequence of tokens into an abstract syntax tree representing the semantics of the raw code\n- [Generator](#generator): Converts an abstract syntax tree into SQL code\n\nSQLGlot can transpile SQL between different _SQL dialects_, which are usually associated with different database systems.\n\nEach dialect has unique syntax that is implemented by overriding the base versions of the three modules. A dialect definition may override any or all of the three base modules.\n\nThe base versions of the modules implement the `sqlglot` dialect (sometimes referred to as \"base dialect\"), which is designed to accommodate as many common syntax elements as possible across the other supported dialects, thus preventing code duplication.\n\nSQLGlot includes other modules which are not required for basic transpilation, such as the [Optimizer](#optimizer) and Executor. The Optimizer modifies an abstract syntax tree for other uses (e.g., inferring column-level lineage). The Executor runs SQL code in Python, but its engine is still in an experimental stage, so some functionality may be missing.\n\nThe rest of this document describes the three base modules, dialect-specific overrides, and the Optimizer module.\n\n## Tokenizer\nThe tokenizer module (`tokens.py`) is responsible for breaking down SQL code into tokens (like keywords, identifiers, etc.), which are the smallest units of meaningful information. This process is also known as lexing/lexical analysis.\n\nPython is not designed for maximum processing speed/performance, so SQLGlot maintains an equivalent [Rust version of the tokenizer](https://tobikodata.com/sqlglot-jumps-on-the-rust-bandwagon.html) in `sqlglotrs/tokenizer.rs` for improved performance.\n\n> [!IMPORTANT]\n> Changes in the tokenization logic must be reflected in both the Python and the Rust tokenizer. The goal is for the two implementations to be similar so that there is less cognitive effort to port code between them.\n\nThis example demonstrates the results of using the Tokenizer to tokenize the SQL query `SELECT b FROM table WHERE c = 1`:\n\n```python\nfrom sqlglot import tokenize\n\ntokens = tokenize(\"SELECT b FROM table WHERE c = 1\")\nfor token in tokens:\n    print(token)\n\n# <Token token_type: <TokenType.SELECT: 'SELECT'>, text: 'SELECT', line: 1, col: 6, start: 0, end: 5, comments: []>\n# <Token token_type: <TokenType.VAR: 'VAR'>, text: 'b', line: 1, col: 8, start: 7, end: 7, comments: []>\n# <Token token_type: <TokenType.FROM: 'FROM'>, text: 'FROM', line: 1, col: 13, start: 9, end: 12, comments: []>\n# <Token token_type: <TokenType.TABLE: 'TABLE'>, text: 'table', line: 1, col: 19, start: 14, end: 18, comments: []>\n# <Token token_type: <TokenType.WHERE: 'WHERE'>, text: 'WHERE', line: 1, col: 25, start: 20, end: 24, comments: []>\n# <Token token_type: <TokenType.VAR: 'VAR'>, text: 'c', line: 1, col: 27, start: 26, end: 26, comments: []>\n# <Token token_type: <TokenType.EQ: 'EQ'>, text: '=', line: 1, col: 29, start: 28, end: 28, comments: []>\n# <Token token_type: <TokenType.NUMBER: 'NUMBER'>, text: '1', line: 1, col: 31, start: 30, end: 30, comments: []>\n```\n\nThe Tokenizer scans the query and converts groups of symbols (or _lexemes_), such as words (e.g., `b`) and operators (e.g., `=`), into tokens. For example, the `VAR` token is used to represent the identifier `b`, whereas the `EQ` token is used to represent the \"equals\" operator.\n\nEach token contains information such as its type (`token_type`), the lexeme (`text`) it encapsulates and other metadata such as the lexeme's line (`line`) and column (`col`), which are used to accurately report errors.\n\nSQLGlot’s `TokenType` enum provides an indirection layer between lexemes and their types. For example, `!=` and `<>` are often used interchangeably to represent \"not equals\", so SQLGlot groups them together by mapping them both to `TokenType.NEQ`.\n\nThe `Tokenizer.KEYWORDS` and `Tokenizer.SINGLE_TOKENS` are two important dictionaries that map lexemes to the corresponding `TokenType` enum values.\n\n## Parser\nThe parser module takes the list of tokens generated by the tokenizer and builds an [abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree) from them.\n\nGenerally, an AST stores the _meaningful_ constituent components of a SQL statement. For example, to represent a `SELECT` query we can only store its projections, filters, aggregation expressions and so on, but the `SELECT`, `WHERE` and `GROUP BY` keywords don't need to be preserved, since they are implied.\n\nIn SQLGlot's case, the AST usually stores information that captures the _semantics_ of an expression, i.e. its meaning, which is what allows it to transpile SQL code between different dialects. A blog post that explains this idea at a high-level can [be found here](https://tobikodata.com/transpiling_sql1.html).\n\nThe parser processes the tokens corresponding to a SQL statement sequentially and produces an AST to represent it. For example, if a statement’s first token is `CREATE`, the parser will determine what is being created by examining the next token (which could be `SCHEMA`, `TABLE`, `VIEW`, or something else).\n\nEach semantic concept is represented by an _expression_ in the AST. Converting tokens to expressions is the primary task of the parser.\n\nAs the AST is a core concept of SQLGlot, a more detailed tutorial on its representation, traversal, and transformation can [be found here](https://github.com/tobymao/sqlglot/blob/main/posts/ast_primer.md).\n\nThe next sections describe how the parser processes a SQL statement’s list of tokens, followed by an explanation of how dialect-specific parsing works.\n\n### Token Index\nThe parser processes a token list sequentially, and maintains an index/cursor that points to the next token to be consumed.\n\nOnce the current token has been successfully processed, the parser moves to the next token in the sequence by calling `_advance()` to increment the index.\n\nIf a token has been consumed but could not be parsed, the parser can move backward to the previous token by calling `_retreat()` to decrement the index.\n\nIn some scenarios, the parser’s behavior depends on both the current and surrounding tokens. The parser can “peek” at the previous and next tokens without consuming them by accessing the `_prev` and `_next` properties, respectively.\n\n### Matching tokens and text\nWhen parsing a SQL statement, the parser must identify specific sets of keywords to correctly build the AST. It does so by “matching” sets of tokens or keywords to infer the structure of the statement.\n\nFor example, these matches occur when parsing the window specification `SUM(x) OVER (PARTITION BY y)`:\n1. The `SUM` and `(` tokens are matched and `x` is parsed as an identifier\n2. The `)` token is matched\n2. The `OVER`  and `(` tokens are matched\n2. The `PARTITION` and `BY` tokens are matched\n3. The partition clause `y` is parsed\n4. The `)` token is matched\n\nThe family of `_match` methods perform different varieties of matching. They return `True` and advance the index if there is a match, or return `None` if the tokens do not match.\n\nThe most commonly used `_match` methods are:\n\nMethod                | Use\n-------------------------|-----\n**`_match(type)`**       | Attempt to match a single token with a specific `TokenType`\n**`_match_set(types)`**  | Attempt to match a single token in a set of `TokenType`s\n**`_match_text_seq(texts)`**   | Attempt to match a continuous sequence of strings/texts\n**`_match_texts(texts)`**   | Attempt to match a keyword in a set of strings/texts\n\n### `_parse` methods\nSQLGlot’s parser follows the “recursive descent” approach, meaning that it relies on mutually recursive methods in order to understand SQL syntax and the various precedence levels between combined SQL expressions.\n\nFor example, the SQL statement `CREATE TABLE a (col1 INT)` will be parsed into an `exp.Create` expression that includes the kind of object being created (table) and its schema (table name, column names and types). The schema will be parsed into an `exp.Schema` node that contains one column definition `exp.ColumnDef` node for each column.\n\nThe relationships between SQL constructs are encoded in methods whose names begin with “_parse_”, such as `_parse_create()`. We refer to these as “parsing methods”.\n\nFor instance, to parse the statement above the entrypoint method `_parse_create()` is called. Then:\n- In `_parse_create()`, the parser determines that a `TABLE` is being created\n- Because a table is being created, the table name is expected next and thus `_parse_table_parts()` is invoked; This is because the table name may have multiple parts such as `catalog.schema.table`\n- Having processed the table name, the parser continues by parsing the schema definition using `_parse_schema()`\n\nA key aspect of the SQLGlot parser is that the parsing methods are composable and can be passed as arguments. We describe how that works in the next section.\n\n### Utility methods\nSQLGlot provides helper methods for common parsing tasks. They should be used whenever possible to reduce code duplication and manual token/text matching.\n\nMany helper methods take a parsing method argument, which is invoked to parse part(s) of the clause they're supposed to parse.\n\nFor example, these helper methods parse collections of SQL objects like `col1, col2`, `(PARTITION BY y)`, and `(colA TEXT, colB INT)`, respectively:\n\nMethod                | Use\n-------------------------|-----\n**`_parse_csv(parse_method)`**  | Attempt to parse comma-separated values using the appropriate `parse_method` callable\n**`_parse_wrapped(parse_method)`**  | Attempt to parse a specific construct that is (optionally) wrapped in parentheses\n**`_parse_wrapped_csv(parse_method)`**  | Attempt to parse comma-separated values that are (optionally) wrapped in parentheses\n\n\n### Command fallback\nThe SQL language specification and dialect variations are vast, and SQLGlot cannot parse every possible statement.\n\nInstead of erroring on statements it cannot parse, SQLGlot falls back to storing the code that cannot be parsed in an `exp.Command` expression.\n\nThis allows SQLGlot to return the code unmodified even though it cannot parse it. Because the code is unmodified, any dialect-specific components will not be transpiled.\n\n### Dialect-specific parsing\nThe base parser’s goal is to represent as many common constructs from different SQL dialects as possible. This makes the parser more lenient and less-repetitive/concise.\n\nDialect-specific parser behavior is implemented in two ways: feature flags and parser overrides.\n\nIf two different parsing behaviors are common across dialects, the base parser may implement both and use feature flags to determine which should be used for a specific dialect. In contrast, parser overrides directly replace specific base parser methods.\n\nTherefore, each dialect’s Parser class may specify:\n\n- Feature flags such as `SUPPORTS_IMPLICIT_UNNEST` or `SUPPORTS_PARTITION_SELECTION`, which are used to control the behavior of base parser methods.\n\n- Sets of tokens that play a similar role, such as `RESERVED_TOKENS` that cannot be used as object names.\n\n- Sets of `token -> Callable` mappings, implemented with Python lambda functions\n    - When the parser comes across the token (string or `TokenType`) on the left-hand side it invokes the mapping function on the right to create the corresponding Expression / AST node.\n    - The lambda function may return an expression directly or a `_parse_ method` that determines what expression should be returned.\n    - The mappings are grouped by general semantic type (e.g., functions, parsers for `ALTER` statements, etc.), with each type having its own dictionary.\n\nAn example `token -> Callable` mapping is the `FUNCTIONS` dictionary that builds the appropriate `exp.Func` node based on the string key:\n\n```Python3\n FUNCTIONS: t.Dict[str, t.Callable] = {\n    \"LOG2\": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)),\n    \"LOG10\": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)),\n    \"MOD\": build_mod,\n     …,\n }\n```\n\nIn this dialect, the `LOG2()` function calculates a logarithm of base 2, which is represented in SQLGlot by the general `exp.Log` expression.\n\nThe logarithm base and the user-specified value to `log` are stored in the node's arguments. The former is stored as an integer literal in the `this` argument, and the latter is stored in the `expression` argument.\n\n## Generator\nAfter the parser has created an AST, the generator module is responsible for converting it back into SQL code.\n\nEach dialect’s Generator class may specify:\n\n- A set of flags such as `ALTER_TABLE_INCLUDE_COLUMN_KEYWORD`. As with the parser, these flags control the behavior of base Generator methods.\n\n- Set of `Expression -> str` mappings\n    - The generator traverses the AST recursively and produces the equivalent string for each expression it visits. This can be thought of as the reverse operation of the parser, where expressions / AST nodes are converted back to strings.\n    - The mappings are grouped by general semantic type (e.g., data type expressions), with each type having its own dictionary.\n\nThe `Expression -> str` mappings can be implemented as either:\n- Entries in the `TRANSFORMS` dictionary, which are best suited for single-line generations.\n- Functions with names of the form `<expr_name>_sql(...)`. For example, to generate SQL for an `exp.SessionParameter` node we can override the base generator method by defining the method `def sessionparameter_sql(...)` in a dialect’s Generator class.\n\n### Generating queries\nThe key method in the Generator module is the `sql()` function, which is responsible for generating the string representation of each expression.\n\nFirst, it locates the mapping of expression to string or generator `Callable` by examining the keys of the `TRANSFORMS` dictionary and searching the `Generator`'s methods for an `<exprname>_sql()` method.\n\nIt then calls the appropriate callable to produce the corresponding SQL code.\n\n> [!IMPORTANT]\n> If there is both a `TRANSFORM` entry and an `<expr_name>_sql(...)` method for a given expression, the generator will use the former to convert the expression into a string.\n\n### Utility methods\nThe Generator defines helper methods containing quality-of-life abstractions over the `sql()` method, such as:\n\nMethod                | Use\n-------------------------|-----\n**`expressions()`**  | Generates the string representation for a list of `Expression`s, employing a series of arguments to help with their formatting\n**`func()`, `rename_func()`**  | Given a function name and an `Expression`, returns the string representation of a function call by generating the expression's args as the func args.\n\nThese methods should be used whenever possible to reduce code duplication.\n\n### Pretty print\nPretty printing refers to the process of generating formatted and consistently styled SQL, which improves readability, debugging, and code reviewing.\n\nSQLGlot generates an AST’s SQL code on a single line by default, but users may specify that it be `pretty` and include new lines and indenting.\n\nIt is up to the developer to ensure that whitespaces and newlines are embedded in the SQL correctly. To aid in that process, the Generator class provides the `sep()` and `seg()` helper methods.\n\n## Dialects\nWe briefly mentioned dialect-specific behaviors while describing the SQLGlot base Tokenizer, Parser, and Generator modules. This section expands on how to specify a new SQL dialect.\n\nAs [described above](#dialect-specific-parsing), SQLGlot attempts to bridge dialect-specific variations in a \"superset\" dialect, which we refer to as the _sqlglot_ dialect.\n\nAll other SQL dialects have their own Dialect subclass whose Tokenizer, Parser and Generator components extend or override the base modules as needed.\n\nThe base dialect components are defined in `tokens.py`, `parser.py` and `generator.py`. Dialect definitions can be found under `dialects/<dialect>.py`.\n\nWhen adding features that will be used by multiple dialects, it's best to place the common/overlapping parts in the base _sqlglot_ dialect to prevent code repetition across other dialects. Any dialect-specific features that cannot (or should not) be repeated can be specified in the dialect’s subclass.\n\nThe Dialect class contains flags that are visible to its Tokenizer, Parser and Generator components. Flags are only added in a Dialect when they need to be visible to at least two components, in order to avoid code duplication.\n\n### Implementing a custom dialect\nCreating a new SQL dialect may seem complicated at first, but it is actually quite simple in SQLGlot.\n\nThis example demonstrates defining a new dialect named `Custom` that extends or overrides components of the base Dialect modules:\n\n```Python\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect\nfrom sqlglot.generator import Generator\nfrom sqlglot.tokens import Tokenizer, TokenType\n\n\nclass Custom(Dialect):\n    class Tokenizer(Tokenizer):\n        QUOTES = [\"'\", '\"']  # Strings can be delimited by either single or double quotes\n        IDENTIFIERS = [\"`\"]  # Identifiers can be delimited by backticks\n\n        # Associates certain meaningful words with tokens that capture their intent\n        KEYWORDS = {\n            **Tokenizer.KEYWORDS,\n            \"INT64\": TokenType.BIGINT,\n            \"FLOAT64\": TokenType.DOUBLE,\n        }\n\n      class Parser(Parser):\n           # Specifies how certain tokens are mapped to function AST nodes\n           FUNCTIONS = {\n             **parser.Parser.FUNCTIONS,\n             \"APPROX_PERCENTILE\": exp.ApproxQuantile.from_arg_list,\n           }\n\n          # Specifies how a specific construct e.g. CONSTRAINT is parsed\n          def _parse_constraint(self) -> t.Optional[exp.Expression]:\n            return super()._parse_constraint() or self._parse_projection_def()\n\n    class Generator(Generator):\n        # Specifies how AST nodes, i.e. subclasses of exp.Expression, should be converted into SQL\n        TRANSFORMS = {\n            exp.Array: lambda self, e: f\"[{self.expressions(e)}]\",\n        }\n\n        # Specifies how AST nodes representing data types should be converted into SQL\n        TYPE_MAPPING = {\n            exp.DataType.Type.TINYINT: \"INT64\",\n            exp.DataType.Type.SMALLINT: \"INT64\",\n            ...\n        }\n```\n\n\nEven though this example is a fairly realistic starting point, we strongly encourage you to study existing dialect implementations to understand how their various components can be modified.\n\n## Schema\nPrevious sections described the SQLGlot components used for transpilation. This section and the next describe components needed to implement other SQLGlot functionality like column-level lineage.\n\nA Schema represents the structure of a database schema, including the tables/views it contains and their column names and data types.\n\nThe file `schema.py` defines the abstract classes `Schema` and `AbstractMappingSchema`; the implementing class is `MappingSchema`.\n\nA `MappingSchema` object is defined with a nested dictionary, as in this example:\n\n```Python\nschema = MappingSchema({\"t\": {\"x\": \"int\", \"y\": \"datetime\"}})\n```\n\nThe keys of the top-level dictionary are table names (`t`), the keys of `t`’s dictionary are its column names (`x` and `y`), and the values are the column data types (`int` and `datetime`).\n\nA schema provides information required by other SQLGlot modules (most importantly, the optimizer and column level lineage).\n\nSchema information is used to enrich ASTs with actions like replacing stars (`SELECT *`) with the names of the columns being selected from the upstream tables.\n\n## Optimizer\nThe optimizer module in SQLGlot (`optimizer.py`) is responsible for producing canonicalized and efficient SQL queries by applying a series of optimization rules.\n\nOptimizations include simplifying expressions, removing redundant operations, and rewriting queries for better performance.\n\nThe optimizer operates on the abstract syntax tree (AST) returned by the parser, transforming it into a more compact form while preserving the semantics of the original query.\n\n> [!NOTE]\n> The optimizer performs only logical optimization; The underlying engine is almost always better at optimizing for performance.\n\n### Optimization rules\nThe optimizer essentially applies [a list of rules](https://sqlglot.com/sqlglot/optimizer.html) in a sequential manner, where each rule takes in an AST and produces a canonicalized and optimized version of it.\n\n> [!WARNING]\n> The rule order is important, as some rules depend on others to work properly. We discourage manually applying individual rules, which may result in erroneous or unpredictable behavior.\n\nThe first, foundational optimization task is to _standardize_ an AST, which is required by other optimization steps. These rules implement canonicalization:\n\n#### Qualify\nThe most important rule in the optimizer is `qualify`, which is responsible for rewriting the AST such that all tables and columns are _normalized_ and _qualified_.\n\n##### Identifier normalization\nThe normalization step (`normalize_identifiers.py`) transforms some identifiers by converting them into lower or upper case, ensuring the semantics are preserved in each case (e.g. by respecting case-sensitivity).\n\nThis transformation reflects how identifiers would be resolved by the engine corresponding to each SQL dialect, and plays a very important role in the standardization of the AST.\n\n> [!NOTE]\n> Some dialects (e.g. DuckDB) treat identifiers as case-insensitive even when they're quoted, so all identifiers are normalized.\n\nDifferent dialects may have different normalization strategies. For instance, in Snowflake unquoted identifiers are normalized to uppercase.\n\nThis example demonstrates how the identifier `Foo` is normalized to lowercase `foo` in the default sqlglot dialect and uppercase `FOO` in the Snowflake dialect:\n\n```Python\nimport sqlglot\nfrom sqlglot.optimizer.normalize_identifiers import normalize_identifiers\n\nnormalize_identifiers(\"Foo\").sql()\n# 'foo'\n\nnormalize_identifiers(\"Foo\", dialect=\"snowflake\").sql(\"snowflake\")\n# 'FOO'\n```\n\n##### Qualifying tables and columns\nAfter normalizing the identifiers, all table and column names are qualified so there is no ambiguity about the data sources referenced by the query.\n\nThis means that all table references are assigned an alias and all column names are prefixed with their source table’s name.\n\nThis example demonstrates qualifying the query `SELECT col FROM tbl`, where `tbl` contains a single column `col`. Note that the table’s schema is passed as an argument to `qualify()`:\n\n```Python\nimport sqlglot\nfrom sqlglot.optimizer.qualify import qualify\n\nschema = {\"tbl\": {\"col\": \"INT\"}}\nexpression = sqlglot.parse_one(\"SELECT col FROM tbl\")\nqualify(expression, schema=schema).sql()\n\n# 'SELECT \"tbl\".\"col\" AS \"col\" FROM \"tbl\" AS \"tbl\"'\n```\n\nThe `qualify` rule also offers a set of sub-rules that further canonicalize the query.\n\nFor instance, `qualify()` can expand stars such that each `SELECT *` is replaced by a projection list of the selected columns.\n\nThis example modifies the previous example’s query to use `SELECT *`. `qualify()` expands the star and returns the same output as before:\n\n```Python\nimport sqlglot\nfrom sqlglot.optimizer.qualify import qualify\n\n\nschema = {\"tbl\": {\"col\": \"INT\"}}\nexpression = sqlglot.parse_one(\"SELECT * FROM tbl\")\nqualify(expression, schema=schema).sql()\n\n\n# 'SELECT \"tbl\".\"col\" AS \"col\" FROM \"tbl\" AS \"tbl\"'\n```\n\n#### Type Inference\nSome SQL operators’ behavior changes based on the data type of its inputs.\n\nFor example, in some SQL dialects `+` can be used for either adding numbers or concatenating strings. The database uses its knowledge of column data types to determine which operation should be executed in a specific situation.\n\nLike the database, SQLGlot needs to know column data types to perform some optimizations. In many cases, both transpilation and optimization need type information to work properly.\n\nType annotation begins with user-provided information about column data types for at least one table. SQLGlot then traverses the AST, propagating that type information and attempting to infer the type returned by each AST expression.\n\nUsers may need to provide column type information for multiple tables to successfully annotate all expressions in the AST.\n\n## Column Level Lineage\nColumn-level lineage (CLL) traces the flow of data from column to column as tables select and modify columns from one another in a SQL codebase.\n\nCLL provides critical information about data lineage that helps in understanding how data is derived, transformed, and used across different parts of a data pipeline or database system.\n\nSQLGlot’s CLL implementation is defined in `lineage.py`. It operates on a collection of queries that `SELECT` from one another. It assumes that the graph/network of queries forms a directed acyclic graph (DAG) where no two tables `SELECT` from each other.\n\nThe user must provide:\nA “target” query whose upstream column lineage should be traced\nThe queries linking all tables upstream of the target query\nThe schemas (column names and types) of the root tables in the DAG\n\nIn this example, we trace the lineage of the column `traced_col`:\n\n```Python\nfrom sqlglot.lineage import lineage\n\ntarget_query = “””\nWITH cte AS (\n  SELECT\n    traced_col\n  FROM\n    intermediate_table\n)\nSELECT\n  traced_col\nFROM\n  cte\n“””\n\nnode = lineage(\n    column=\"traced_col\",\n    sql=target_query,\n    sources={\"intermediate_table\": \"SELECT * FROM root_table\"},\n    schema={\"root_table\": {\"traced_col\": \"int\"}},\n)\n```\n\nThe `lineage()` function’s `sql` argument takes the target query, the `sources` argument takes a dictionary of source table names and the query that produces each table, and the `schema` argument takes the column names/types of the graph’s root tables.\n\n### Implementation details\nThis section describes how SQLGlot traces the lineage in the previous example.\n\nBefore lineage can be traced, the following preparatory steps must be carried out:\n\n1. Replace the `sql` query’s table references with their `sources`’ queries, such that we have a single standalone query.\n\nThis example demonstrates how the earlier example’s reference to `intermediate_table` inside the CTE is replaced with the query that generated it, `SELECT * FROM root_table`, and given the alias `intermediate_table`:\n\n```SQL\nWITH cte AS (\n  SELECT\n    traced_col\n  FROM (\n    SELECT\n      *\n    FROM\n      root_table\n  ) AS intermediate_table\n)\nSELECT\n  traced_col\nFROM\n  cte\n```\n\n2. Qualify the query produced by the earlier step, expanding `*`s and qualifying all column references with the corresponding source tables:\n\n```SQL\nWITH cte AS (\n  SELECT\n    intermediate_table.traced_col\n  FROM (\n    SELECT\n      root_table.traced_col\n    FROM\n      root_table AS root_table\n  ) AS intermediate_table\n)\nSELECT\n  cte.traced_col\nFROM\n  cte AS cte\n```\n\nAfter these operations, the query is canonicalized and the lineage can be traced.\n\nTracing is a top-down process, meaning that the search starts from `cte.traced_col` (outer scope) and traverse inwards to find that its origin is `intermediate_table.traced_col`, which in turn is based on `root_table.traced_col`.\n\nThe search continues until the innermost scope has been visited. At that point all `sources` are exhausted, so the `schema` is searched to validate that the root table (`root_table` in our example) exists and that the target column `traced_col` is defined in it.\n\nAt each step, the column of interest (i.e., `cte.traced_col`, `intermediate_table.traced_col`. etc) is wrapped in a lineage object `Node` that is linked to its upstream nodes.\n\nThis approach incrementally builds a linked list of `Nodes` that traces the lineage for the target column: `root_table.traced_col -> intermediate_table.traced_col -> cte.traced_col`.\n\nThe `lineage()` output can be visualized using the `node.to_html()` function, which generates an HTML lineage graph using `vis.js`.\n\n![Lineage](onboarding_images/lineage_img.png)\n"
  },
  {
    "path": "posts/python_sql_engine.md",
    "content": "# Writing a Python SQL engine from scratch\n[Toby Mao](https://www.linkedin.com/in/toby-mao/)\n\n## Introduction\n\nWhen I first started writing SQLGlot in early 2021, my goal was just to translate SQL queries from SparkSQL to Presto and vice versa. However, over the last year and a half, I've ended up with a full-fledged SQL engine. SQLGlot can now parse and transpile between [18 SQL dialects](https://github.com/tobymao/sqlglot/blob/main/sqlglot/dialects/__init__.py) and can execute all 24 [TPC-H](https://www.tpc.org/tpch/) SQL queries. The parser and engine are all written from scratch using Python.\n\nThis post will cover [why](#why) I went through the effort of creating a Python SQL engine and [how](#how) a simple query goes from a string to actually transforming data. The following steps are briefly summarized:\n\n* [Tokenizing](#tokenizing)\n* [Parsing](#parsing)\n* [Optimizing](#optimizing)\n* [Planning](#planning)\n* [Executing](#executing)\n\n## Why?\nI started working on SQLGlot because of my work on the [experimentation and metrics platform](https://netflixtechblog.com/reimagining-experimentation-analysis-at-netflix-71356393af21) at Netflix, where I built tools that allowed data scientists to define and compute SQL-based metrics. Netflix relied on multiple engines to query data (Spark, Presto, and Druid), so my team built the metrics platform around [PyPika](https://github.com/kayak/pypika), a Python SQL query builder. This way, definitions could be reused across multiple engines. However, it became quickly apparent that writing python code to programmatically generate SQL was challenging for data scientists, especially those with academic backgrounds, since they were mostly familiar with R and SQL. At the time, the only Python SQL parser was [sqlparse]([https://github.com/andialbrecht/sqlparse), which is not actually a parser but a tokenizer, so having users write raw SQL into the platform wasn't really an option. Some time later, I randomly stumbled across [Crafting Interpreters](https://craftinginterpreters.com/) and realized that I could use it as a guide towards creating my own SQL parser/transpiler.\n\nWhy did I do this? Isn't a Python SQL engine going to be extremely slow?\n\nThe main reason why I ended up building a SQL engine was...just for **entertainment**. It's been fun learning about all the things required to actually run a SQL query, and seeing it actually work is extremely rewarding. Before SQLGlot, I had zero experience with lexers, parsers, or compilers.\n\nIn terms of practical use cases, I planned to use the Python SQL engine for unit testing SQL pipelines. Big data pipelines are tough to test because many of the engines are not open source and cannot be run locally. With SQLGlot, you can take a SQL query targeting a warehouse such as [Snowflake](https://www.snowflake.com/en/) and seamlessly run it in CI on mock Python data. It's easy to mock data and create arbitrary [UDFs](https://en.wikipedia.org/wiki/User-defined_function) because everything is just Python. Although the implementation is slow and unsuitable for large amounts of data (> 1 million rows), there's very little overhead/startup and you can run queries on test data in a couple of milliseconds.\n\nFinally, the components that have been built to support execution can be used as a **foundation** for a faster engine. I'm inspired by what [Apache Calcite](https://github.com/apache/calcite) has done for the JVM world. Even though Python is commonly used for data, there hasn't been a Calcite for Python. So, you could say that SQLGlot aims to be that framework. For example, it wouldn't take much work to replace the Python execution engine with numpy/pandas/arrow to become a respectably-performing query engine. The implementation would be able to leverage the parser, optimizer, and logical planner, only needing to implement physical execution. There is a lot of work in the Python ecosystem around high performance vectorized computation, which I think could benefit from a pure Python-based [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)/[plan](https://en.wikipedia.org/wiki/Query_plan). Parsing and planning doesn't have to be fast when the bottleneck of running queries is processing terabytes of data. So, having a Python-based ecosystem around SQL is beneficial given the ease of development in Python, despite not having bare metal performance.\n\nParts of SQLGlot's toolkit are being used today by the following:\n\n* [Ibis](https://github.com/ibis-project/ibis): A Python library that provides a lightweight, universal interface for data wrangling.\n    - Uses the Python SQL expression builder and leverages the optimizer/planner to convert SQL into dataframe operations.\n* [mysql-mimic](https://github.com/kelsin/mysql-mimic): Pure-Python implementation of the MySQL server wire protocol\n    - Parses / transforms SQL and executes INFORMATION_SCHEMA queries.\n* [Quokka](https://github.com/marsupialtail/quokka): Push-based vectorized query engine\n    - Parse and optimizes SQL.\n* [Splink](https://github.com/moj-analytical-services/splink): Fast, accurate and scalable probabilistic data linkage using your choice of SQL backend.\n    - Transpiles queries.\n\n## How?\n\nThere are many steps involved with actually running a simple query like:\n\n```sql\nSELECT\n  bar.a,\n  b + 1 AS b\nFROM bar\nJOIN baz\n  ON bar.a = baz.a\nWHERE bar.a > 1\n```\n\nIn this post, I'll walk through all the steps SQLGlot takes to run this query over Python objects.\n\n## Tokenizing\n\nThe first step is to convert the sql string into a list of tokens. SQLGlot's tokenizer is quite simple and can be found [here](https://github.com/tobymao/sqlglot/blob/main/sqlglot/tokens.py). In a while loop, it checks each character and either appends the character to the current token, or makes a new token.\n\nRunning the SQLGlot tokenizer shows the output.\n\n![Tokenizer Output](python_sql_engine_images/tokenizer.png)\n\nEach keyword has been converted to a SQLGlot Token object. Each token has some metadata associated with it, like line/column information for error messages. Comments are also a part of the token, so that comments can be preserved.\n\n## Parsing\n\nOnce a SQL statement is tokenized, we don't need to worry about white space and other formatting, so it's easier to work with. We can now convert the list of tokens into an AST. The SQLGlot [parser](https://github.com/tobymao/sqlglot/blob/main/sqlglot/parser.py) is a handwritten [recursive descent](https://en.wikipedia.org/wiki/Recursive_descent_parser) parser.\n\nSimilar to the tokenizer, it consumes the tokens sequentially, but it instead uses a recursive algorithm. The tokens are converted into a single AST node that presents the SQL query. The SQLGlot parser was designed to support various dialects, so it contains many options for overriding parsing functionality.\n\n![Parser Output](python_sql_engine_images/parser.png)\n\nThe AST is a generic representation of a given SQL query. Each dialect can override or implement its own generator, which can convert an AST object into syntatically-correct SQL.\n\n## Optimizing\n\nOnce we have our AST, we can transform it into an equivalent query that produces the same results more efficiently. When optimizing queries, most engines first convert the AST into a logical plan and then optimize the plan. However, I chose to **optimize the AST directly** for the following reasons:\n\n1. It's easier to debug and [validate](https://github.com/tobymao/sqlglot/blob/main/tests/fixtures/optimizer) the optimizations when the input and output are both SQL.\n\n2. Rules can be applied a la carte to transform SQL into a more desirable form.\n\n3. I wanted a way to generate 'canonical sql'. Having a canonical representation of SQL is useful for understanding if two queries are semantically equivalent (e.g. `SELECT 1 + 1` and  `SELECT 2`).\n\nI've yet to find another engine that takes this approach, but I'm quite happy with this decision. The optimizer currently does not perform any \"physical optimizations\" such as join reordering. Those are left to the execution layer, as additional statistics and information could become relevant.\n\n![Optimizer Output](python_sql_engine_images/optimizer.png)\n\nThe optimizer currently has [17 rules](https://github.com/tobymao/sqlglot/tree/main/sqlglot/optimizer). Each of these rules is applied, transforming the AST in place. The combination of these rules creates \"canonical\" sql that can then be more easily converted into a logical plan and executed.\n\nSome example rules are:\n\n### qualify\\_tables and qualify_columns\n- Adds all db/catalog qualifiers to tables and forces an alias.\n- Ensure each column is unambiguous and expand stars.\n\n```sql\nSELECT * FROM x;\n\nSELECT \"db\".\"x\" AS \"x\";\n```\n\n### simplify\nBoolean and math simplification. Check out all the [test cases](https://github.com/tobymao/sqlglot/blob/main/tests/fixtures/optimizer/simplify.sql).\n\n```sql\n((NOT FALSE) AND (x = x)) AND (TRUE OR 1 <> 3);\nx = x;\n\n1 + 1;\n2;\n```\n\n### normalize\nAttempts to convert all predicates into [conjunctive normal form](https://en.wikipedia.org/wiki/Conjunctive_normal_form).\n\n```sql\n-- DNF\n(A AND B) OR (B AND C AND D);\n\n-- CNF\n(A OR C) AND (A OR D) AND B;\n```\n\n### unnest\\_subqueries\nConverts subqueries in predicates into joins.\n\n```sql\n-- The subquery can be converted into a left join\nSELECT *\nFROM x AS x\nWHERE (\n  SELECT y.a AS a\n  FROM y AS y\n  WHERE x.a = y.a\n) = 1;\n\nSELECT *\nFROM x AS x\nLEFT JOIN (\n  SELECT y.a AS a\n  FROM y AS y\n  WHERE TRUE\n  GROUP BY y.a\n) AS \"_u_0\"\n  ON x.a = \"_u_0\".a\nWHERE (\"_u_0\".a = 1 AND NOT \"_u_0\".a IS NULL)\n```\n\n### pushdown_predicates\nPush down filters into the innermost query.\n```sql\nSELECT *\nFROM (\n  SELECT *\n  FROM x AS x\n) AS y\nWHERE y.a = 1;\n\nSELECT *\nFROM (\n  SELECT *\n  FROM x AS x\n  WHERE y.a = 1\n) AS y WHERE TRUE\n```\n\n### annotate_types\nInfer all types throughout the AST given schema information and function type definitions.\n\n## Planning\nAfter the SQL AST has been \"optimized\", it's much easier to [convert into a logical plan](https://github.com/tobymao/sqlglot/blob/main/sqlglot/planner.py). The AST is traversed and converted into a [DAG](https://en.wikipedia.org/wiki/Directed_acyclic_graph) consisting of one of five steps. The different steps are:\n\n### Scan\nSelects columns from a table, applies projections, and finally filters the table.\n\n### Sort\nSorts a table for order by expressions.\n\n### Set\nApplies the operators union/union all/except/intersect.\n\n### Aggregate\nApplies an aggregation/group by.\n\n### Join\nJoins multiple tables together.\n\n![Planner Output](python_sql_engine_images/planner.png)\n\nThe logical plan is quite simple and contains the information required to convert it into a physical plan (execution).\n\n## Executing\nFinally, we can actually execute the SQL query. The [Python engine](https://github.com/tobymao/sqlglot/blob/main/sqlglot/executor/python.py) is not fast, but it's very small (~400 LOC)! It iterates the DAG with a queue and runs each step, passing each intermediary table to the next step.\n\nIn order to keep things simple, it evaluates expressions with `eval`. Because SQLGlot was built primarily to be a transpiler, it was simple to create a \"Python SQL\" dialect. So a SQL expression `x + 1` can just be converted into `scope['x'] + 1`.\n\n![Executor Output](python_sql_engine_images/executor.png)\n\n## What's next\nSQLGlot's main focus will always be on parsing/transpiling, but I plan to continue development on the execution engine. I'd like to pass [TPC-DS](https://www.tpc.org/tpcds/). If someone doesn't beat me to it, I may even take a stab at writing a Pandas/Arrow execution engine.\n\nI'm hoping that over time, SQLGlot will spark the Python SQL ecosystem just like Calcite has for Java.\n\n## Special thanks\nSQLGlot would not be what it is without it's core contributors. In particular, the execution engine would not exist without [Barak Alon](https://github.com/barakalon) and [George Sittas](https://github.com/GeorgeSittas).\n\n## Get in touch\nIf you'd like to chat more about SQLGlot, please join my [Slack Channel](https://tobikodata.com/slack)!\n"
  },
  {
    "path": "posts/sql_diff.md",
    "content": "# Semantic Diff for SQL\n*by [Iaroslav Zeigerman](https://github.com/izeigerman)*\n\n## Motivation\n\nSoftware is constantly changing and evolving, and identifying what has changed and reviewing those changes is an integral part of the development process. SQL code is no exception to this.\n\nText-based diff tools such as `git diff`, when applied to a code base, have certain limitations. First, they can only detect insertions and deletions, not movements or updates of individual pieces of code. Second, such tools can only detect changes between lines of text, which is too coarse for something as granular and detailed as source code. Additionally, the outcome of such a diff is dependent on the underlying code formatting, and yields different results if the formatting should change.\n\nConsider the following diff generated by Git:\n\n![Git diff output](sql_diff_images/git_diff_output.png)\n\nSemantically the query hasn’t changed. The two arguments `b` and `c` have been swapped (moved), posing no impact on the output of the query. Yet Git replaced the whole affected expression alongside a bulk of unrelated elements.\n\nThe alternative to text-based diffing is to compare Abstract Syntax Trees (AST) instead. The main advantage of ASTs are that they are a direct product of code parsing, which represents the underlying code structure at any desired level of granularity. Comparing ASTs may yield extremely precise diffs; changes such as code movements and updates can also be detected. Even more importantly, this approach facilitates additional use cases beyond eyeballing two versions of source code side by side.\n\nThe use cases I had in mind for SQL when I decided to embark on this journey of semantic diffing were the following:\n\n* **Query similarity score.** Identifying which parts the two queries have in common to automatically suggest opportunities for consolidation, creation of intermediate/staging tables, and so on.\n* **Differentiating between cosmetic / structural changes and functional ones.** For example when a nested query is refactored into a common table expression (CTE), this kind of change doesn’t have any functional impact on either a query or its outcome.\n* **Automatic suggestions about the need to retroactively backfill data.** This is especially important for pipelines that populate very large tables for which restatement is a runtime-intensive procedure. The ability to discern between simple code movements and actual modifications can help assess the impact of a change and make suggestions accordingly.\n\nThe implementation discussed in this post is now a part of the [SQLGlot](https://github.com/tobymao/sqlglot/) library. You can find a complete source code in the [diff.py](https://github.com/tobymao/sqlglot/blob/main/sqlglot/diff.py) module. The choice of SQLglot was an obvious one due to its simple but powerful API, lack of external dependencies and, more importantly, extensive list of supported SQL dialects.\n\n## The Search for a Solution\n\nWhen it comes to any diffing tool (not just a semantic one), the primary challenge is to match as many elements of compared entities as possible. Once such a set of matching elements is available, deriving a sequence of changes becomes an easy task.\n\nIf our elements have unique identifiers associated with them (for example, an element’s ID in DOM), the matching problem is trivial. However, the SQL syntax trees that we are comparing have neither unique keys nor object identifiers that can be used for the purposes of matching. So, how do we suppose to find pairs of nodes that are related?\n\nTo better illustrate the problem, consider comparing the following SQL expressions: `SELECT a + b + c, d, e` and `SELECT a - b + c, e, f`. Matching individual nodes from respective syntax trees can be visualized as follows:\n\n![Figure 1: Example of node matching for two SQL expression trees](sql_diff_images/figure_1.png)\n*Figure 1: Example of node matching for two SQL expression trees.*\n\nBy looking at the figure of node matching for two SQL expression trees above, we conclude that the following changes should be captured by our solution:\n\n* Inserted nodes: `Sub` and `f`. These are the nodes from the target AST which do not have a matching node in the source AST.\n* Removed nodes: `Add` and `d`. These are the nodes from the source AST which do not have a counterpart in the target AST.\n* Remaining nodes must be identified as unchanged.\n\nIt should be clear at this point that if we manage to match nodes in the source tree with their counterparts in the target tree, then computing the diff becomes a trivial matter.\n\n### Naïve Brute-Force\n\nThe naïve solution would be to try all different permutations of node pair combinations, and see which set of pairs performs the best based on some type of heuristics. The runtime cost of such a solution quickly reaches the escape velocity; if both trees had only 10 nodes each, the number of such sets would approximately be 10! ^ 2 = 3.6M ^ 2 ~= 13 * 10^12. This is a very bad case of factorial complexity (to be precise, it’s actually much worse - O(n! ^ 2) - but I couldn’t come up with a name for it), so there is little need to explore this approach any further.\n\n### Myers Algorithm\n\nAfter the naïve approach was proven to be infeasible, the next question I asked myself was “how does git diff work?”. This question led me to discover the Myers diff algorithm [1]. This algorithm has been designed to compare sequences of strings. At its core, it’s looking for the shortest path on a graph of possible edits that transform the first sequence into the second one, while heavily rewarding those paths that lead to longest subsequences of unchanged elements. There’s a lot of material out there describing this algorithm in greater detail. I found James Coglan’s series of [blog posts](https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/) to be the most comprehensive.\n\nTherefore, I had this “brilliant” (actually not) idea to transform trees into sequences by traversing them in topological order, and then applying the Myers algorithm on resulting sequences while using a custom heuristics when checking the equality of two nodes. Unsurprisingly, comparing sequences of strings is quite different from comparing hierarchical tree structures, and by flattening trees into sequences, we lose a lot of relevant context. This resulted in a terrible performance of this algorithm on ASTs. It often matched completely unrelated nodes, even when the two trees were mostly the same, and produced extremely inaccurate lists of changes overall. After playing around with it a little and tweaking my equality heuristics to improve accuracy, I ultimately scrapped the whole implementation and went back to the drawing board.\n\n## Change Distiller\n\nThe algorithm I settled on at the end was Change Distiller, created by Fluri et al. [2], which in turn is an improvement over the core idea described by Chawathe et al. [3].\n\nThe algorithm consists of two high-level steps:\n\n1. **Finding appropriate matchings between pairs of nodes that are part of compared ASTs.** Identifying what is meant by “appropriate” matching is also a part of this step.\n2. **Generating the so-called “edit script” from the matching set built in the 1st step.** The edit script is a sequence of edit operations (for example, insert, remove, update, etc.) on individual tree nodes, such that when applied as transformations on the source AST, it eventually becomes the target AST. In general, the shorter the sequence, the better. The length of the edit script can be used to compare the performance of different algorithms, though this is not the only metric that matters.\n\nThe rest of this section is dedicated to the Python implementation of the steps above using the AST implementation provided by the SQLGlot library.\n\n### Building the Matching Set\n#### Matching Leaves\n\nWe begin composing the matching set by matching the leaf nodes. Leaf nodes are the nodes that do not have any children nodes (such as literals, identifiers, etc.). In order to match them, we gather all the leaf nodes from the source tree and generate a cartesian product with all the leaves from the target tree, while comparing pairs created this way and assigning them a similarity score. During this stage, we also exclude pairs that don’t pass basic matching criteria. Then, we pick pairs that scored the highest while making sure that each node is matched no more than once.\n\nUsing the example provided at the beginning of the post, the process of building an initial set of candidate matchings can be seen on Figure 2.\n\n![Figure 2: Building a set of candidate matchings between leaf nodes. The third item in each triplet represents a similarity score between two nodes.](sql_diff_images/figure_2.gif)\n*Figure 2: Building a set of candidate matchings between leaf nodes. The third item in each triplet represents a similarity score between two nodes.*\n\nFirst, let’s analyze the similarity score. Then, we’ll discuss matching criteria.\n\nThe similarity score proposed by Fluri et al. [2] is a [dice coefficient ](https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient)applied to [bigrams](https://en.wikipedia.org/wiki/Bigram) of respective node values. A bigram is a sequence of two adjacent elements from a string computed in a sliding window fashion:\n\n```python\ndef bigram(string):\n    count = max(0, len(string) - 1)\n    return [string[i : i + 2] for i in range(count)]\n```\n\nFor reasons that will become clear shortly, we actually need to compute bigram histograms rather than just sequences:\n\n```python\nfrom collections import defaultdict\n\ndef bigram_histo(string):\n    count = max(0, len(string) - 1)\n    bigram_histo = defaultdict(int)\n    for i in range(count):\n        bigram_histo[string[i : i + 2]] += 1\n    return bigram_histo\n```\n\nThe dice coefficient formula looks like following:\n\n![Dice Coefficient](sql_diff_images/dice_coef.png)\n\nWhere X is a bigram of the source node and Y is a bigram of the second one. What this essentially does is count the number of bigram elements the two nodes have in common, multiply it by 2, and then divide by the total number of elements in both bigrams. This is where bigram histograms come in handy:\n\n```python\ndef dice_coefficient(source, target):\n    source_histo = bigram_histo(source.sql())\n    target_histo = bigram_histo(target.sql())\n\n    total_grams = (\n        sum(source_histo.values()) + sum(target_histo.values())\n    )\n    if not total_grams:\n        return 1.0 if source == target else 0.0\n\n    overlap_len = 0\n    overlapping_grams = set(source_histo) & set(target_histo)\n    for g in overlapping_grams:\n        overlap_len += min(source_histo[g], target_histo[g])\n\n    return 2 * overlap_len / total_grams\n```\n\nTo compute a bigram given a tree node, we first transform the node into its canonical SQL representation,so that the `Literal(123)` node becomes just “123” and the `Identifier(“a”)` node becomes just “a”. We also handle a scenario when strings are too short to derive bigrams. In this case, we fallback to checking the two nodes for equality.\n\nNow when we know how to compute the similarity score, we can take care of the matching criteria for leaf nodes. In the original paper [2], the matching criteria is formalized as follows:\n\n![Matching criteria for leaf nodes](sql_diff_images/matching_criteria_1.png)\n\nThe two nodes are matched if two conditions are met:\n\n1. The node labels match (in our case labels are just node types).\n2. The similarity score for node values is greater than or equal to some threshold “f”. The authors of the paper recommend setting the value of “f” to 0.6.\n\nWith building blocks in place, we can now build a matching set for leaf nodes. First, we generate a list of candidates for matching:\n\n```python\nfrom heapq import heappush, heappop\n\ncandidate_matchings = []\nsource_leaves = _get_leaves(self._source)\ntarget_leaves = _get_leaves(self._target)\nfor source_leaf in source_leaves:\n    for target_leaf in target_leaves:\n        if _is_same_type(source_leaf, target_leaf):\n            similarity_score = dice_coefficient(\n                source_leaf, target_leaf\n            )\n            if similarity_score >= 0.6:\n                heappush(\n                    candidate_matchings,\n                    (\n                        -similarity_score,\n                        len(candidate_matchings),\n                        source_leaf,\n                        target_leaf,\n                    ),\n                )\n```\n\nIn the implementation above, we push each matching pair onto the heap to automatically maintain the correct order based on the assigned similarity score.\n\nFinally, we build the initial matching set by picking leaf pairs with the highest score:\n\n```python\nmatching_set = set()\nwhile candidate_matchings:\n    _, _, source_leaf, target_leaf = heappop(candidate_matchings)\n    if (\n        source_leaf in unmatched_source_nodes\n        and target_leaf in unmatched_target_nodes\n    ):\n        matching_set.add((source_leaf, target_leaf))\n        unmatched_source_nodes.remove(source_leaf)\n        unmatched_target_nodes.remove(target_leaf)\n```\n\nTo finalize the matching set, we should now proceed with matching inner nodes.\n\n#### Matching Inner Nodes\n\nMatching inner nodes is quite similar to matching leaf nodes, with the following two distinctions:\n\n* Rather than ranking a set of possible candidates, we pick the first node pair that passes the matching criteria.\n* The matching criteria itself has been extended to account for the number of leaf nodes the pair of inner nodes have in common.\n\n![Figure 3: Matching inner nodes based on their type as well as how many of their leaf nodes have been previously matched.](sql_diff_images/figure_3.gif)\n*Figure 3: Matching inner nodes based on their type as well as how many of their leaf nodes have been previously matched.*\n\nLet’s start with the matching criteria. The criteria is formalized as follows:\n\n![Matching criteria for inner nodes](sql_diff_images/matching_criteria_2.png)\n\nAlongside already familiar similarity score and node type criteria, there is a new one in the middle: the ratio of leaf nodes that the two nodes have in common must exceed some threshold “t”. The recommended value for “t” is also 0.6. Counting the number of common leaf nodes is pretty straightforward, since we already have the complete matching set for leaves. All we need to do is count how many matching pairs do leaf nodes from the two compared inner nodes form.\n\nThere are two additional heuristics associated with this matching criteria:\n\n* Inner node similarity weighting: if the similarity score between the node values doesn’t pass the threshold “f” but the ratio of common leaf nodes (“t”) is greater than or equal to 0.8, then the matching is considered successful.\n* The threshold “t” is reduced to 0.4 for inner nodes with the number of leaf nodes equal to 4 or less, in order to decrease the false negative rate for small subtrees.\n\nWe now only have to iterate through the remaining unmatched nodes and form matching pairs based on the outlined criteria:\n\n```python\nleaves_matching_set = matching_set.copy()\n\nfor source_node in unmatched_source_nodes.copy():\n    for target_node in unmatched_target_nodes:\n        if _is_same_type(source_node, target_node):\n            source_leaves = set(_get_leaves(source_node))\n            target_leaves = set(_get_leaves(target_node))\n\n            max_leaves_num = max(len(source_leaves), len(target_leaves))\n            if max_leaves_num:\n                common_leaves_num = sum(\n                    1 if s in source_leaves and t in target_leaves else 0\n                    for s, t in leaves_matching_set\n                )\n                leaf_similarity_score = common_leaves_num / max_leaves_num\n            else:\n                leaf_similarity_score = 0.0\n\n            adjusted_t = (\n                0.6\n                if min(len(source_leaves), len(target_leaves)) > 4\n                else 0.4\n            )\n\n            if leaf_similarity_score >= 0.8 or (\n                leaf_similarity_score >= adjusted_t\n                and dice_coefficient(source_node, target_node) >= 0.6\n            ):\n                matching_set.add((source_node, target_node))\n                unmatched_source_nodes.remove(source_node)\n                unmatched_target_nodes.remove(target_node)\n                break\n```\n\nAfter the matching set is formed, we can proceed with generation of the edit script, which will be the algorithm’s output.\n\n### Generating the Edit Script\n\nAt this point, we should have the following 3 sets at our disposal:\n\n* The set of matched node pairs.\n* The set of remaining unmatched nodes from the source tree.\n* The set of remaining unmatched nodes from the target tree.\n\nWe can derive 3 kinds of edits from the matching set: either the node’s value was updated (**Update**), the node was moved to a different position within the tree (**Move**), or the node remained unchanged (**Keep**). Note that the **Move** case is not mutually exclusive with the other two. The node could have been updated or could have remained the same while at the same time its position within its parent node or the parent node itself could have changed. All unmatched nodes from the source tree are the ones that were removed (**Remove**), while unmatched nodes from the target tree are the ones that were inserted (**Insert**).\n\nThe latter two cases are pretty straightforward to implement:\n\n```python\nedit_script = []\n\nfor removed_node in unmatched_source_nodes:\n    edit_script.append(Remove(removed_node))\nfor inserted_node in unmatched_target_nodes:\n    edit_script.append(Insert(inserted_node))\n```\n\nTraversing the matching set requires a little more thought:\n\n```python\nfor source_node, target_node in matching_set:\n    if (\n        not isinstance(source_node, LEAF_EXPRESSION_TYPES)\n        or source_node == target_node\n    ):\n        move_edits = generate_move_edits(\n            source_node, target_node, matching_set\n        )\n        edit_script.extend(move_edits)\n        edit_script.append(Keep(source_node, target_node))\n    else:\n        edit_script.append(Update(source_node, target_node))\n```\n\nIf a matching pair represents a pair of leaf nodes, we check if they are the same to decide whether an update took place. For inner node pairs, we also need to compare the positions of their respective children to detect node movements. Chawathe et al. [3] suggest applying the [longest common subsequence ](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem)(LCS) algorithm which, no surprise here, was described by Myers himself [1]. There is a small catch, however: instead of checking the equality of two children nodes, we need to check whether the two nodes form a pair that is a part of our matching set.\n\nNow with this knowledge, the implementation becomes straightforward:\n\n```python\ndef generate_move_edits(source, target, matching_set):\n    source_children = _get_child_nodes(source)\n    target_children = _get_child_nodes(target)\n\n    lcs = set(\n        _longest_common_subsequence(\n            source_children,\n            target_children,\n            lambda l, r: (l, r) in matching_set\n        )\n    )\n\n    move_edits = []\n    for node in source_children:\n        if node not in lcs and node not in unmatched_source_nodes:\n            move_edits.append(Move(node))\n\n    return move_edits\n```\n\nI left out the implementation of the LCS algorithm itself here, but there are plenty of implementation choices out there that can be easily looked up.\n\n### Output\n\nThe implemented algorithm produces the output that resembles the following:\n\n```python\n>>> from sqlglot import parse_one, diff\n>>> diff(parse_one(\"SELECT a + b + c, d, e\"), parse_one(\"SELECT a - b + c, e, f\"))\n\nRemove(Add)\nRemove(Column(d))\nRemove(Identifier(d))\nInsert(Sub)\nInsert(Column(f))\nInsert(Identifier(f))\nKeep(Select, Select)\nKeep(Add, Add)\nKeep(Column(a), Column(a))\nKeep(Identifier(a), Identifier(a))\nKeep(Column(b), Column(b))\nKeep(Identifier(b), Identifier(b))\nKeep(Column(c), Column(c))\nKeep(Identifier(c), Identifier(c))\nKeep(Column(e), Column(e))\nKeep(Identifier(e), Identifier(e))\n```\nNote that the output above is abbreviated. The string representation of actual AST nodes is significantly more verbose.\n\nThe implementation works especially well when coupled with the SQLGlot’s query optimizer which can be used to produce canonical representations of compared queries:\n\n```python\n>>> schema={\"t\": {\"a\": \"INT\", \"b\": \"INT\", \"c\": \"INT\", \"d\": \"INT\"}}\n>>> source = \"\"\"\n... SELECT 1 + 1 + a\n... FROM t\n... WHERE b = 1 OR (c = 2 AND d = 3)\n... \"\"\"\n>>> target = \"\"\"\n... SELECT 2 + a\n... FROM t\n... WHERE (b = 1 OR c = 2) AND (b = 1 OR d = 3)\n... \"\"\"\n>>> optimized_source = optimize(parse_one(source), schema=schema)\n>>> optimized_target = optimize(parse_one(target), schema=schema)\n>>> edit_script = diff(optimized_source, optimized_target)\n>>> sum(0 if isinstance(e, Keep) else 1 for e in edit_script)\n0\n```\n\n### Optimizations\n\nThe worst case runtime complexity of this algorithm is not exactly stellar: O(n^2 * log n^2). This is because of the leaf matching process, which involves ranking a cartesian product between all leaf nodes of compared trees. Unsurprisingly, the algorithm takes a considerable time to finish for bigger queries.\n\nThere are still a few basic things we can do in our implementation to help improve performance:\n\n* Refer to individual node objects using their identifiers (Python’s [id()](https://docs.python.org/3/library/functions.html#id)) instead of direct references in sets. This helps avoid costly recursive hash calculations and equality checks.\n* Cache bigram histograms to avoid computing them more than once for the same node.\n* Compute the canonical SQL string representation for each tree once while caching string representations of all inner nodes. This prevents redundant tree traversals when bigrams are computed.\n\nAt the time of writing only the first two optimizations have been implemented, so there is an opportunity to contribute for anyone who’s interested.\n\n## Alternative Solutions\n\nThis section is dedicated to solutions that I’ve investigated, but haven’t tried.\n\nFirst, this section wouldn’t be complete without Tristan Hume’s [blog post](https://thume.ca/2017/06/17/tree-diffing/). Tristan’s solution has a lot in common with the Myers algorithm plus heuristics that is much more clever than what I came up with. The implementation relies on a combination of [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) and [A* search algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm) to explore the space of possible matchings and pick the best ones. It seemed to have worked well for Tistan’s specific use case, but after my negative experience with the Myers algorithm, I decided to try something different.\n\nAnother notable approach is the Gumtree algorithm by Falleri et al. [4]. I discovered this paper after I’d already implemented the algorithm that is the main focus of this post. In sections 5.2 and 5.3 of their paper, the authors compare the two algorithms side by side and claim that Gumtree is significantly better in terms of both runtime performance and accuracy when evaluated on 12 792 pairs of Java source files. This doesn’t surprise me, as the algorithm takes the height of subtrees into account. In my tests, I definitely saw scenarios in which this context would have helped. On top of that, the authors promise O(n^2) runtime complexity in the worst case which, given the Change Distiller's O(n^2 * log n^2), looks particularly tempting. I hope to try this algorithm out at some point, and there is a good chance you see me writing about it in my future posts.\n\n## Conclusion\n\nThe Change Distiller algorithm yielded quite satisfactory results in most of my tests. The scenarios in which it fell short mostly concerned identical (or very similar) subtrees located in different parts of the AST. In those cases, node mismatches were frequent and, as a result, edit scripts were somewhat suboptimal.\n\nAdditionally, the runtime performance of the algorithm leaves a lot to be desired. On trees with 1000 leaf nodes each, the algorithm takes a little under 2 seconds to complete. My implementation still has room for improvement, but this should give you a rough idea of what to expect. It appears that the Gumtree algorithm [4] can help address both of these points. I hope to find bandwidth to work on it soon and then compare the two algorithms side-by-side to find out which one performs better on SQL specifically. In the meantime, Change Distiller definitely gets the job done, and I can now proceed with applying it to some of the use cases I mentioned at the beginning of this post.\n\nI’m also curious to learn whether other folks in the industry faced a similar problem, and how they approached it. If you did something similar, I’m interested to hear about your experience.\n\n## References\n\n[1] Eugene W. Myers. [An O(ND) Difference Algorithm and Its Variations](http://www.xmailserver.org/diff2.pdf). Algorithmica 1(2): 251-266 (1986)\n\n[2] B. Fluri, M. Wursch, M. Pinzger, and H. Gall. [Change Distilling: Tree differencing for fine-grained source code change extraction](https://www.researchgate.net/publication/3189787_Change_DistillingTree_Differencing_for_Fine-Grained_Source_Code_Change_Extraction). IEEE Trans. Software Eng., 33(11):725–743, 2007.\n\n[3] S.S. Chawathe, A. Rajaraman, H. Garcia-Molina, and J. Widom. [Change Detection in Hierarchically Structured Information](http://ilpubs.stanford.edu:8090/115/1/1995-46.pdf). Proc. ACM Sigmod Int’l Conf. Management of Data, pp. 493-504, June 1996\n\n[4] Jean-Rémy Falleri, Floréal Morandat, Xavier Blanc, Matias Martinez, Martin Monperrus. [Fine-grained and Accurate Source Code Differencing](https://hal.archives-ouvertes.fr/hal-01054552/document). Proceedings of the International Conference on Automated Software Engineering, 2014, Västeras, Sweden. pp.313-324, 10.1145/2642937.2642982. hal-01054552\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"sqlglot\"\ndynamic = [\"version\", \"optional-dependencies\"]\ndescription = \"An easily customizable SQL parser and transpiler\"\nreadme = \"README.md\"\nauthors = [{ name = \"Toby Mao\", email = \"toby.mao@gmail.com\" }]\nlicense = \"MIT\"\nlicense-files = [\"LICENSE\"]\nrequires-python = \">= 3.9\"\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Science/Research\",\n    \"Operating System :: OS Independent\",\n    \"Programming Language :: SQL\",\n    \"Programming Language :: Python :: 3 :: Only\",\n]\n\n[project.urls]\nHomepage = \"https://sqlglot.com/\"\nDocumentation = \"https://sqlglot.com/sqlglot.html\"\nRepository = \"https://github.com/tobymao/sqlglot\"\nIssues = \"https://github.com/tobymao/sqlglot/issues\"\n\n[build-system]\nrequires = [\"setuptools >= 61.0\", \"setuptools_scm\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.setuptools]\ninclude-package-data = false\n\n[tool.setuptools_scm]\nversion_file = \"sqlglot/_version.py\"\nfallback_version = \"0.0.0\"\nlocal_scheme = \"no-local-version\"\n\n[tool.setuptools.packages.find]\ninclude = [\"sqlglot\", \"sqlglot.*\"]\n\n[tool.setuptools.package-data]\n\"*\" = [\"py.typed\"]\n\n[tool.uv.sources]\nsqlglotc = { path = \"./sqlglotc\" }\n\n[tool.ruff]\nline-length = 100\n\n[tool.ruff.lint]\nignore = [\"E721\", \"E741\"]\n"
  },
  {
    "path": "setup.cfg",
    "content": "[mypy]\ndisallow_untyped_calls = False\nno_implicit_optional = True\n\n[mypy-sqlglot.*]\nignore_errors = False\n\n[mypy-sqlglot.dataframe.*]\nignore_errors = False\n\n[mypy-tests.*]\nignore_errors = True\n\n[mypy-tests.dataframe.*]\nignore_errors = False\n"
  },
  {
    "path": "setup.py",
    "content": "from setuptools import setup\nfrom setuptools_scm import get_version\n\nversion = get_version(local_scheme=\"no-local-version\")\n\nsetup(\n    extras_require={\n        \"dev\": [\n            \"duckdb>=0.6\",\n            \"sqlglot-mypy>=1.19.1.post1\",\n            \"setuptools_scm\",\n            \"pandas\",\n            \"pandas-stubs\",\n            \"python-dateutil\",\n            \"pytz\",\n            \"pdoc\",\n            \"pre-commit\",\n            \"ruff==0.15.6\",\n            \"types-python-dateutil\",\n            \"types-pytz\",\n            \"typing_extensions\",\n            \"pyperf\",\n        ],\n        # Compiles from source on the user's machine.\n        \"c\": [f\"sqlglotc=={version}\"],\n        # Deprecated: the Rust tokenizer has been replaced by sqlglotc.\n        \"rs\": [\"sqlglotrs==0.13.0\", f\"sqlglotc=={version}\"],\n    },\n)\n"
  },
  {
    "path": "sqlglot/__init__.py",
    "content": "# ruff: noqa: F401, E402\n\"\"\"\n.. include:: ../README.md\n\n----\n\"\"\"\n\nfrom __future__ import annotations\n\n# bootstrap mypyc runtime: compiled .so modules do a top-level `import HASH__mypyc`,\n# but the runtime .so lives inside sqlglot/. Pre-load it into sys.modules.\n# this is only needed for editable builds\nimport sys\nfrom pathlib import Path\n\nfor path in Path(__file__).parent.glob(\"*__mypyc*.so\"):\n    name = path.stem.split(\".\")[0]\n    if name not in sys.modules:\n        import importlib.util\n\n        spec = importlib.util.spec_from_file_location(name, path)\n        if spec and spec.loader:\n            mod = importlib.util.module_from_spec(spec)\n            sys.modules[name] = mod\n            spec.loader.exec_module(mod)\n\nimport logging\nimport typing as t\n\nfrom sqlglot import expressions as exp\nfrom sqlglot.dialects.dialect import Dialect as Dialect, Dialects as Dialects\nfrom sqlglot.diff import diff as diff\nfrom sqlglot.errors import (\n    ErrorLevel as ErrorLevel,\n    ParseError as ParseError,\n    TokenError as TokenError,\n    UnsupportedError as UnsupportedError,\n)\nfrom sqlglot.expressions import (\n    Expr as Expr,\n    alias_ as alias,\n    and_ as and_,\n    case as case,\n    cast as cast,\n    column as column,\n    condition as condition,\n    delete as delete,\n    except_ as except_,\n    find_tables as find_tables,\n    from_ as from_,\n    func as func,\n    insert as insert,\n    intersect as intersect,\n    maybe_parse as maybe_parse,\n    merge as merge,\n    not_ as not_,\n    or_ as or_,\n    select as select,\n    subquery as subquery,\n    table_ as table,\n    to_column as to_column,\n    to_identifier as to_identifier,\n    to_table as to_table,\n    union as union,\n)\nfrom sqlglot.generator import Generator as Generator\nfrom sqlglot.parser import Parser as Parser\nfrom sqlglot.schema import MappingSchema as MappingSchema, Schema as Schema\nfrom sqlglot.tokens import Token as Token, Tokenizer as Tokenizer, TokenType as TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from sqlglot.dialects.dialect import DialectType as DialectType\n\nlogger = logging.getLogger(\"sqlglot\")\n\n\ntry:\n    from sqlglot._version import __version__, __version_tuple__  # type: ignore[import-not-found]\nexcept ImportError:\n    logger.error(\n        \"Unable to set __version__, run `pip install -e .` or `python setup.py develop` first.\"\n    )\n\n\npretty = False\n\"\"\"Whether to format generated SQL by default.\"\"\"\n\n\ndef tokenize(sql: str, read: DialectType = None, dialect: DialectType = None) -> t.List[Token]:\n    \"\"\"\n    Tokenizes the given SQL string.\n\n    Args:\n        sql: the SQL code string to tokenize.\n        read: the SQL dialect to apply during tokenizing (eg. \"spark\", \"hive\", \"presto\", \"mysql\").\n        dialect: the SQL dialect (alias for read).\n\n    Returns:\n        The resulting list of tokens.\n    \"\"\"\n    return Dialect.get_or_raise(read or dialect).tokenize(sql)\n\n\ndef parse(\n    sql: str, read: DialectType = None, dialect: DialectType = None, **opts\n) -> t.List[t.Optional[Expr]]:\n    \"\"\"\n    Parses the given SQL string into a collection of syntax trees, one per parsed SQL statement.\n\n    Args:\n        sql: the SQL code string to parse.\n        read: the SQL dialect to apply during parsing (eg. \"spark\", \"hive\", \"presto\", \"mysql\").\n        dialect: the SQL dialect (alias for read).\n        **opts: other `sqlglot.parser.Parser` options.\n\n    Returns:\n        The resulting syntax tree collection.\n    \"\"\"\n    return Dialect.get_or_raise(read or dialect).parse(sql, **opts)\n\n\n@t.overload\ndef parse_one(sql: str, *, into: t.Type[E], **opts) -> E: ...\n\n\n@t.overload\ndef parse_one(sql: str, **opts) -> Expr: ...\n\n\ndef parse_one(\n    sql: str,\n    read: DialectType = None,\n    dialect: DialectType = None,\n    into: t.Optional[exp.IntoType] = None,\n    **opts,\n) -> Expr:\n    \"\"\"\n    Parses the given SQL string and returns a syntax tree.\n\n    Args:\n        sql: the SQL code string to parse.\n        read: the SQL dialect to apply during parsing (eg. \"spark\", \"hive\", \"presto\", \"mysql\").\n        dialect: the SQL dialect (alias for read)\n        into: the SQLGlot Expr to parse into.\n        **opts: other `sqlglot.parser.Parser` options.\n\n    Returns:\n        A single syntax tree if one statement is parsed, otherwise a Block expression containing all parsed syntax trees.\n    \"\"\"\n\n    dialect = Dialect.get_or_raise(read or dialect)\n\n    if into:\n        result = dialect.parse_into(into, sql, **opts)\n    else:\n        result = dialect.parse(sql, **opts)\n\n    if not result or result[0] is None:\n        raise ParseError(f\"No expression was parsed from '{sql}'\")\n\n    return exp.Block(expressions=result) if len(result) > 1 else result[0]\n\n\ndef transpile(\n    sql: str,\n    read: DialectType = None,\n    write: DialectType = None,\n    identity: bool = True,\n    error_level: t.Optional[ErrorLevel] = None,\n    **opts,\n) -> t.List[str]:\n    \"\"\"\n    Parses the given SQL string in accordance with the source dialect and returns a list of SQL strings transformed\n    to conform to the target dialect. Each string in the returned list represents a single transformed SQL statement.\n\n    Args:\n        sql: the SQL code string to transpile.\n        read: the source dialect used to parse the input string (eg. \"spark\", \"hive\", \"presto\", \"mysql\").\n        write: the target dialect into which the input should be transformed (eg. \"spark\", \"hive\", \"presto\", \"mysql\").\n        identity: if set to `True` and if the target dialect is not specified the source dialect will be used as both:\n            the source and the target dialect.\n        error_level: the desired error level of the parser.\n        **opts: other `sqlglot.generator.Generator` options.\n\n    Returns:\n        The list of transpiled SQL statements.\n    \"\"\"\n    write = (read if write is None else write) if identity else write\n    write = Dialect.get_or_raise(write)\n    return [\n        write.generate(expression, copy=False, **opts) if expression else \"\"\n        for expression in parse(sql, read, error_level=error_level)\n    ]\n"
  },
  {
    "path": "sqlglot/__main__.py",
    "content": "from __future__ import annotations\n\nimport argparse\nimport sys\nimport typing as t\n\nimport sqlglot\nfrom sqlglot.helper import to_bool\n\nparser = argparse.ArgumentParser(description=\"Transpile SQL\")\nparser.add_argument(\n    \"sql\",\n    metavar=\"sql\",\n    type=str,\n    help=\"SQL statement(s) to transpile, or - to parse stdin.\",\n)\nparser.add_argument(\n    \"--read\",\n    dest=\"read\",\n    type=str,\n    default=None,\n    help=\"Dialect to read default is generic\",\n)\nparser.add_argument(\n    \"--write\",\n    dest=\"write\",\n    type=str,\n    default=None,\n    help=\"Dialect to write default is generic\",\n)\nparser.add_argument(\n    \"--identify\",\n    dest=\"identify\",\n    type=str,\n    default=\"safe\",\n    help=\"Whether to quote identifiers (safe, true, false)\",\n)\nparser.add_argument(\n    \"--no-pretty\",\n    dest=\"pretty\",\n    action=\"store_false\",\n    help=\"Compress sql\",\n)\nparser.add_argument(\n    \"--parse\",\n    dest=\"parse\",\n    action=\"store_true\",\n    help=\"Parse and return the expression tree\",\n)\nparser.add_argument(\n    \"--tokenize\",\n    dest=\"tokenize\",\n    action=\"store_true\",\n    help=\"Tokenize and return the tokens list\",\n)\nparser.add_argument(\n    \"--error-level\",\n    dest=\"error_level\",\n    type=str,\n    default=\"IMMEDIATE\",\n    help=\"IGNORE, WARN, RAISE, IMMEDIATE (default)\",\n)\nparser.add_argument(\n    \"--version\",\n    action=\"version\",\n    version=sqlglot.__version__,\n    help=\"Display the SQLGlot version\",\n)\n\n\nargs = parser.parse_args()\nerror_level = sqlglot.ErrorLevel[args.error_level.upper()]\n\nsql = sys.stdin.read() if args.sql == \"-\" else args.sql\n\nif args.parse:\n    objs: t.Union[t.List[str], t.List[sqlglot.tokens.Token]] = [\n        repr(expression)\n        for expression in sqlglot.parse(\n            sql,\n            read=args.read,\n            error_level=error_level,\n        )\n    ]\nelif args.tokenize:\n    objs = sqlglot.Dialect.get_or_raise(args.read).tokenize(sql)\nelse:\n    objs = sqlglot.transpile(\n        sql,\n        read=args.read,\n        write=args.write,\n        identify=\"safe\" if args.identify == \"safe\" else to_bool(args.identify),\n        pretty=args.pretty,\n        error_level=error_level,\n    )\n\nfor obj in objs:\n    print(obj)\n"
  },
  {
    "path": "sqlglot/_typing.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nif t.TYPE_CHECKING:\n    import sqlglot\n\nB = t.TypeVar(\"B\", bound=\"sqlglot.exp.Binary\")\nE = t.TypeVar(\"E\", bound=\"sqlglot.exp.Expr\")\nF = t.TypeVar(\"F\", bound=\"sqlglot.exp.Func\")\nT = t.TypeVar(\"T\")\n"
  },
  {
    "path": "sqlglot/dialects/__init__.py",
    "content": "# ruff: noqa: F401\n\"\"\"\n## Dialects\n\nWhile there is a SQL standard, most SQL engines support a variation of that standard. This makes it difficult\nto write portable SQL code. SQLGlot bridges all the different variations, called \"dialects\", with an extensible\nSQL transpilation framework.\n\nThe base `sqlglot.dialects.dialect.Dialect` class implements a generic dialect that aims to be as universal as possible.\n\nEach SQL variation has its own `Dialect` subclass, extending the corresponding `Tokenizer`, `Parser` and `Generator`\nclasses as needed.\n\n### Implementing a custom Dialect\n\nCreating a new SQL dialect may seem complicated at first, but it is actually quite simple in SQLGlot:\n\n```python\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect\nfrom sqlglot.generator import Generator\nfrom sqlglot.tokens import Tokenizer, TokenType\n\n\nclass Custom(Dialect):\n    class Tokenizer(Tokenizer):\n        QUOTES = [\"'\", '\"']  # Strings can be delimited by either single or double quotes\n        IDENTIFIERS = [\"`\"]  # Identifiers can be delimited by backticks\n\n        # Associates certain meaningful words with tokens that capture their intent\n        KEYWORDS = {\n            **Tokenizer.KEYWORDS,\n            \"INT64\": TokenType.BIGINT,\n            \"FLOAT64\": TokenType.DOUBLE,\n        }\n\n    class Generator(Generator):\n        # Specifies how AST nodes, i.e. subclasses of exp.Expr, should be converted into SQL\n        TRANSFORMS = {\n            exp.Array: lambda self, e: f\"[{self.expressions(e)}]\",\n        }\n\n        # Specifies how AST nodes representing data types should be converted into SQL\n        TYPE_MAPPING = {\n            exp.DType.TINYINT: \"INT64\",\n            exp.DType.SMALLINT: \"INT64\",\n            exp.DType.INT: \"INT64\",\n            exp.DType.BIGINT: \"INT64\",\n            exp.DType.DECIMAL: \"NUMERIC\",\n            exp.DType.FLOAT: \"FLOAT64\",\n            exp.DType.DOUBLE: \"FLOAT64\",\n            exp.DType.BOOLEAN: \"BOOL\",\n            exp.DType.TEXT: \"STRING\",\n        }\n```\n\nThe above example demonstrates how certain parts of the base `Dialect` class can be overridden to match a different\nspecification. Even though it is a fairly realistic starting point, we strongly encourage the reader to study existing\ndialect implementations in order to understand how their various components can be modified, depending on the use-case.\n\n----\n\"\"\"\n\nimport importlib\nimport threading\n\nDIALECTS = [\n    \"Athena\",\n    \"BigQuery\",\n    \"ClickHouse\",\n    \"Databricks\",\n    \"Doris\",\n    \"Dremio\",\n    \"Drill\",\n    \"Druid\",\n    \"DuckDB\",\n    \"Dune\",\n    \"Exasol\",\n    \"Fabric\",\n    \"Hive\",\n    \"Materialize\",\n    \"MySQL\",\n    \"Oracle\",\n    \"Postgres\",\n    \"Presto\",\n    \"PRQL\",\n    \"Redshift\",\n    \"RisingWave\",\n    \"SingleStore\",\n    \"Snowflake\",\n    \"Solr\",\n    \"Spark\",\n    \"Spark2\",\n    \"SQLite\",\n    \"StarRocks\",\n    \"Tableau\",\n    \"Teradata\",\n    \"Trino\",\n    \"TSQL\",\n]\n\nMODULE_BY_DIALECT = {name: name.lower() for name in DIALECTS}\nDIALECT_MODULE_NAMES = MODULE_BY_DIALECT.values()\n\nMODULE_BY_ATTRIBUTE = {\n    **MODULE_BY_DIALECT,\n    \"Dialect\": \"dialect\",\n    \"Dialects\": \"dialect\",\n}\n\n__all__ = list(MODULE_BY_ATTRIBUTE)\n\n# We use a reentrant lock because a dialect may depend on (i.e., import) other dialects.\n# Without it, the first dialect import would never be completed, because subsequent\n# imports would be blocked on the lock held by the first import.\n_import_lock = threading.RLock()\n\n\ndef __getattr__(name):\n    module_name = MODULE_BY_ATTRIBUTE.get(name)\n    if module_name:\n        with _import_lock:\n            module = importlib.import_module(f\"sqlglot.dialects.{module_name}\")\n        return getattr(module, name)\n\n    raise AttributeError(f\"module {__name__} has no attribute {name}\")\n"
  },
  {
    "path": "sqlglot/dialects/athena.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens\nfrom sqlglot.dialects import Dialect, Hive, Trino\nfrom sqlglot.parsers.athena import AthenaParser\nfrom sqlglot.tokens import TokenType, Token\n\n\nclass Athena(Dialect):\n    \"\"\"\n    Over the years, it looks like AWS has taken various execution engines, bolted on AWS-specific\n    modifications and then built the Athena service around them.\n\n    Thus, Athena is not simply hosted Trino, it's more like a router that routes SQL queries to an\n    execution engine depending on the query type.\n\n    As at 2024-09-10, assuming your Athena workgroup is configured to use \"Athena engine version 3\",\n    the following engines exist:\n\n    Hive:\n     - Accepts mostly the same syntax as Hadoop / Hive\n     - Uses backticks to quote identifiers\n     - Has a distinctive DDL syntax (around things like setting table properties, storage locations etc)\n       that is different from Trino\n     - Used for *most* DDL, with some exceptions that get routed to the Trino engine instead:\n        - CREATE [EXTERNAL] TABLE (without AS SELECT)\n        - ALTER\n        - DROP\n\n    Trino:\n      - Uses double quotes to quote identifiers\n      - Used for DDL operations that involve SELECT queries, eg:\n        - CREATE VIEW / DROP VIEW\n        - CREATE TABLE... AS SELECT\n      - Used for DML operations\n        - SELECT, INSERT, UPDATE, DELETE, MERGE\n\n    The SQLGlot Athena dialect tries to identify which engine a query would be routed to and then uses the\n    tokenizer / parser / generator for that engine. This is unfortunately necessary, as there are certain\n    incompatibilities between the engines' dialects and thus can't be handled by a single, unifying dialect.\n\n    References:\n    - https://docs.aws.amazon.com/athena/latest/ug/ddl-reference.html\n    - https://docs.aws.amazon.com/athena/latest/ug/dml-queries-functions-operators.html\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n\n        self._hive = Hive(**kwargs)\n        self._trino = Trino(**kwargs)\n\n    def tokenize(self, sql: str, **opts) -> t.List[Token]:\n        opts[\"hive\"] = self._hive\n        opts[\"trino\"] = self._trino\n        return super().tokenize(sql, **opts)\n\n    def parse(self, sql: str, **opts) -> t.List[t.Optional[exp.Expr]]:\n        opts[\"hive\"] = self._hive\n        opts[\"trino\"] = self._trino\n        return super().parse(sql, **opts)\n\n    def parse_into(\n        self, expression_type: exp.IntoType, sql: str, **opts\n    ) -> t.List[t.Optional[exp.Expr]]:\n        opts[\"hive\"] = self._hive\n        opts[\"trino\"] = self._trino\n        return super().parse_into(expression_type, sql, **opts)\n\n    def generate(self, expression: exp.Expr, copy: bool = True, **opts) -> str:\n        opts[\"hive\"] = self._hive\n        opts[\"trino\"] = self._trino\n        return super().generate(expression, copy=copy, **opts)\n\n    # This Tokenizer consumes a combination of HiveQL and Trino SQL and then processes the tokens\n    # to disambiguate which dialect needs to be actually used in order to tokenize correctly.\n    class Tokenizer(tokens.Tokenizer):\n        IDENTIFIERS = Trino.Tokenizer.IDENTIFIERS + Hive.Tokenizer.IDENTIFIERS\n        STRING_ESCAPES = Trino.Tokenizer.STRING_ESCAPES + Hive.Tokenizer.STRING_ESCAPES\n        HEX_STRINGS = Trino.Tokenizer.HEX_STRINGS + Hive.Tokenizer.HEX_STRINGS\n        UNICODE_STRINGS = Trino.Tokenizer.UNICODE_STRINGS + Hive.Tokenizer.UNICODE_STRINGS\n\n        NUMERIC_LITERALS = {\n            **Trino.Tokenizer.NUMERIC_LITERALS,\n            **Hive.Tokenizer.NUMERIC_LITERALS,\n        }\n\n        KEYWORDS = {\n            **Hive.Tokenizer.KEYWORDS,\n            **Trino.Tokenizer.KEYWORDS,\n            \"UNLOAD\": TokenType.COMMAND,\n        }\n\n        def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:\n            hive = kwargs.pop(\"hive\", None) or Hive()\n            trino = kwargs.pop(\"trino\", None) or Trino()\n\n            super().__init__(*args, **kwargs)\n\n            self._hive_tokenizer = hive.tokenizer(*args, **{**kwargs, \"dialect\": hive})\n            self._trino_tokenizer = _TrinoTokenizer(*args, **{**kwargs, \"dialect\": trino})\n\n        def tokenize(self, sql: str) -> t.List[Token]:\n            tokens = super().tokenize(sql)\n\n            if _tokenize_as_hive(tokens):\n                return [Token(TokenType.HIVE_TOKEN_STREAM, \"\")] + self._hive_tokenizer.tokenize(sql)\n\n            return self._trino_tokenizer.tokenize(sql)\n\n    Parser = AthenaParser\n\n    class Generator(generator.Generator):\n        def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:\n            hive = kwargs.pop(\"hive\", None) or Hive()\n            trino = kwargs.pop(\"trino\", None) or Trino()\n\n            super().__init__(*args, **kwargs)\n\n            self._hive_generator = _HiveGenerator(*args, **{**kwargs, \"dialect\": hive})\n            self._trino_generator = _TrinoGenerator(*args, **{**kwargs, \"dialect\": trino})\n\n        def generate(self, expression: exp.Expr, copy: bool = True) -> str:\n            if _generate_as_hive(expression):\n                generator = self._hive_generator\n            else:\n                generator = self._trino_generator\n\n            return generator.generate(expression, copy=copy)\n\n\ndef _tokenize_as_hive(tokens: t.List[Token]) -> bool:\n    if len(tokens) < 2:\n        return False\n\n    first, second, *rest = tokens\n\n    first_type = first.token_type\n    first_text = first.text.upper()\n    second_type = second.token_type\n    second_text = second.text.upper()\n\n    if first_type in (TokenType.DESCRIBE, TokenType.SHOW) or first_text == \"MSCK REPAIR\":\n        return True\n\n    if first_type in (TokenType.ALTER, TokenType.CREATE, TokenType.DROP):\n        if second_text in (\"DATABASE\", \"EXTERNAL\", \"SCHEMA\"):\n            return True\n        if second_type == TokenType.VIEW:\n            return False\n\n        return all(t.token_type != TokenType.SELECT for t in rest)\n\n    return False\n\n\ndef _generate_as_hive(expression: exp.Expr) -> bool:\n    if isinstance(expression, exp.Create):\n        if expression.kind == \"TABLE\":\n            properties = expression.args.get(\"properties\")\n\n            # CREATE EXTERNAL TABLE is Hive\n            if properties and properties.find(exp.ExternalProperty):\n                return True\n\n            # Any CREATE TABLE other than CREATE TABLE ... AS <query> is Hive\n            if not isinstance(expression.expression, exp.Query):\n                return True\n        else:\n            # CREATE VIEW is Trino, but CREATE SCHEMA, CREATE DATABASE, etc, is Hive\n            return expression.kind != \"VIEW\"\n    elif isinstance(expression, (exp.Alter, exp.Drop, exp.Describe, exp.Show)):\n        if isinstance(expression, exp.Drop) and expression.kind == \"VIEW\":\n            # DROP VIEW is Trino, because CREATE VIEW is as well\n            return False\n\n        # Everything else, e.g., ALTER statements, is Hive\n        return True\n\n    return False\n\n\ndef _is_iceberg_table(properties: exp.Properties) -> bool:\n    for p in properties.expressions:\n        if isinstance(p, exp.Property) and p.name == \"table_type\":\n            return p.text(\"value\").lower() == \"iceberg\"\n\n    return False\n\n\ndef _location_property_sql(self: Athena.Generator, e: exp.LocationProperty):\n    # If table_type='iceberg', the LocationProperty is called 'location'\n    # Otherwise, it's called 'external_location'\n    # ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html\n\n    prop_name = \"external_location\"\n\n    if isinstance(e.parent, exp.Properties):\n        if _is_iceberg_table(e.parent):\n            prop_name = \"location\"\n\n    return f\"{prop_name}={self.sql(e, 'this')}\"\n\n\ndef _partitioned_by_property_sql(self: Athena.Generator, e: exp.PartitionedByProperty) -> str:\n    # If table_type='iceberg' then the table property for partitioning is called 'partitioning'\n    # If table_type='hive' it's called 'partitioned_by'\n    # ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties\n\n    prop_name = \"partitioned_by\"\n\n    if isinstance(e.parent, exp.Properties):\n        if _is_iceberg_table(e.parent):\n            prop_name = \"partitioning\"\n\n    return f\"{prop_name}={self.sql(e, 'this')}\"\n\n\n# Athena extensions to Hive's generator\nclass _HiveGenerator(Hive.Generator):\n    def alter_sql(self, expression: exp.Alter) -> str:\n        # Package any ALTER TABLE ADD actions into a Schema object, so it gets generated as\n        # `ALTER TABLE .. ADD COLUMNS(...)`, instead of `ALTER TABLE ... ADD COLUMN`, which\n        # is invalid syntax on Athena\n        if isinstance(expression, exp.Alter) and expression.kind == \"TABLE\":\n            if expression.actions and isinstance(expression.actions[0], exp.ColumnDef):\n                new_actions = exp.Schema(expressions=expression.actions)\n                expression.set(\"actions\", [new_actions])\n\n        return super().alter_sql(expression)\n\n\n# Athena extensions to Trino's tokenizer\nclass _TrinoTokenizer(Trino.Tokenizer):\n    KEYWORDS = {\n        **Trino.Tokenizer.KEYWORDS,\n        \"UNLOAD\": TokenType.COMMAND,\n    }\n\n\n# Athena extensions to Trino's parser\n# Athena extensions to Trino's generator\nclass _TrinoGenerator(Trino.Generator):\n    PROPERTIES_LOCATION = {\n        **Trino.Generator.PROPERTIES_LOCATION,\n        exp.LocationProperty: exp.Properties.Location.POST_WITH,\n    }\n\n    TRANSFORMS = {\n        **Trino.Generator.TRANSFORMS,\n        exp.PartitionedByProperty: _partitioned_by_property_sql,\n        exp.LocationProperty: _location_property_sql,\n    }\n"
  },
  {
    "path": "sqlglot/dialects/bigquery.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport re\nimport typing as t\n\n\nfrom sqlglot.optimizer.annotate_types import TypeAnnotator\n\nfrom sqlglot import exp, generator, jsonpath, tokens, transforms\nfrom sqlglot._typing import E\nfrom sqlglot.parsers.bigquery import BigQueryParser\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    NormalizationStrategy,\n    arg_max_or_min_no_count,\n    date_add_interval_sql,\n    datestrtodate_sql,\n    filter_array_using_unnest,\n    generate_series_sql,\n    if_sql,\n    inline_array_unless_query,\n    max_or_greatest,\n    min_or_least,\n    no_ilike_sql,\n    regexp_replace_sql,\n    rename_func,\n    sha256_sql,\n    timestrtotime_sql,\n    ts_or_ds_add_cast,\n    unit_to_var,\n    strposition_sql,\n    groupconcat_sql,\n    sha2_digest_sql,\n)\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.typing.bigquery import EXPRESSION_METADATA\n\nif t.TYPE_CHECKING:\n    from sqlglot.optimizer.annotate_types import TypeAnnotator\n\nlogger = logging.getLogger(\"sqlglot\")\n\n\nJSON_EXTRACT_TYPE = t.Union[exp.JSONExtract, exp.JSONExtractScalar, exp.JSONExtractArray]\n\nDQUOTES_ESCAPING_JSON_FUNCTIONS = (\"JSON_QUERY\", \"JSON_VALUE\", \"JSON_QUERY_ARRAY\")\n\n\ndef _derived_table_values_to_unnest(self: BigQuery.Generator, expression: exp.Values) -> str:\n    if not expression.find_ancestor(exp.From, exp.Join):\n        return self.values_sql(expression)\n\n    structs = []\n    alias = expression.args.get(\"alias\")\n    for tup in expression.find_all(exp.Tuple):\n        field_aliases = (\n            alias.columns\n            if alias and alias.columns\n            else (f\"_c{i}\" for i in range(len(tup.expressions)))\n        )\n        expressions = [\n            exp.PropertyEQ(this=exp.to_identifier(name), expression=fld)\n            for name, fld in zip(field_aliases, tup.expressions)\n        ]\n        structs.append(exp.Struct(expressions=expressions))\n\n    # Due to `UNNEST_COLUMN_ONLY`, it is expected that the table alias be contained in the columns expression\n    alias_name_only = exp.TableAlias(columns=[alias.this]) if alias else None\n    return self.unnest_sql(\n        exp.Unnest(expressions=[exp.array(*structs, copy=False)], alias=alias_name_only)\n    )\n\n\ndef _returnsproperty_sql(self: BigQuery.Generator, expression: exp.ReturnsProperty) -> str:\n    this = expression.this\n    if isinstance(this, exp.Schema):\n        this = f\"{self.sql(this, 'this')} <{self.expressions(this)}>\"\n    else:\n        this = self.sql(this)\n    return f\"RETURNS {this}\"\n\n\ndef _create_sql(self: BigQuery.Generator, expression: exp.Create) -> str:\n    returns = expression.find(exp.ReturnsProperty)\n    if expression.kind == \"FUNCTION\" and returns and returns.args.get(\"is_table\"):\n        expression.set(\"kind\", \"TABLE FUNCTION\")\n\n        if isinstance(expression.expression, (exp.Subquery, exp.Literal)):\n            expression.set(\"expression\", expression.expression.this)\n\n    return self.create_sql(expression)\n\n\n# https://issuetracker.google.com/issues/162294746\n# workaround for bigquery bug when grouping by an expression and then ordering\n# WITH x AS (SELECT 1 y)\n# SELECT y + 1 z\n# FROM x\n# GROUP BY x + 1\n# ORDER by z\ndef _alias_ordered_group(expression: exp.Expr) -> exp.Expr:\n    if isinstance(expression, exp.Select):\n        group = expression.args.get(\"group\")\n        order = expression.args.get(\"order\")\n\n        if group and order:\n            aliases = {\n                select.this: select.args[\"alias\"]\n                for select in expression.selects\n                if isinstance(select, exp.Alias)\n            }\n\n            for grouped in group.expressions:\n                if grouped.is_int:\n                    continue\n                alias = aliases.get(grouped)\n                if alias:\n                    grouped.replace(exp.column(alias))\n\n    return expression\n\n\ndef _pushdown_cte_column_names(expression: exp.Expr) -> exp.Expr:\n    \"\"\"BigQuery doesn't allow column names when defining a CTE, so we try to push them down.\"\"\"\n    if isinstance(expression, exp.CTE) and expression.alias_column_names:\n        cte_query = expression.this\n\n        if cte_query.is_star:\n            logger.warning(\n                \"Can't push down CTE column names for star queries. Run the query through\"\n                \" the optimizer or use 'qualify' to expand the star projections first.\"\n            )\n            return expression\n\n        column_names = expression.alias_column_names\n        expression.args[\"alias\"].set(\"columns\", None)\n\n        for name, select in zip(column_names, cte_query.selects):\n            to_replace = select\n\n            if isinstance(select, exp.Alias):\n                select = select.this\n\n            # Inner aliases are shadowed by the CTE column names\n            to_replace.replace(exp.alias_(select, name))\n\n    return expression\n\n\ndef _array_contains_sql(self: BigQuery.Generator, expression: exp.ArrayContains) -> str:\n    return self.sql(\n        exp.Exists(\n            this=exp.select(\"1\")\n            .from_(exp.Unnest(expressions=[expression.left]).as_(\"_unnest\", table=[\"_col\"]))\n            .where(exp.column(\"_col\").eq(expression.right))\n        )\n    )\n\n\ndef _ts_or_ds_add_sql(self: BigQuery.Generator, expression: exp.TsOrDsAdd) -> str:\n    return date_add_interval_sql(\"DATE\", \"ADD\")(self, ts_or_ds_add_cast(expression))\n\n\ndef _ts_or_ds_diff_sql(self: BigQuery.Generator, expression: exp.TsOrDsDiff) -> str:\n    expression.this.replace(exp.cast(expression.this, exp.DType.TIMESTAMP))\n    expression.expression.replace(exp.cast(expression.expression, exp.DType.TIMESTAMP))\n    unit = unit_to_var(expression)\n    return self.func(\"DATE_DIFF\", expression.this, expression.expression, unit)\n\n\ndef _unix_to_time_sql(self: BigQuery.Generator, expression: exp.UnixToTime) -> str:\n    scale = expression.args.get(\"scale\")\n    timestamp = expression.this\n\n    if scale in (None, exp.UnixToTime.SECONDS):\n        return self.func(\"TIMESTAMP_SECONDS\", timestamp)\n    if scale == exp.UnixToTime.MILLIS:\n        return self.func(\"TIMESTAMP_MILLIS\", timestamp)\n    if scale == exp.UnixToTime.MICROS:\n        return self.func(\"TIMESTAMP_MICROS\", timestamp)\n\n    unix_seconds = exp.cast(\n        exp.Div(this=timestamp, expression=exp.func(\"POW\", 10, scale)), exp.DType.BIGINT\n    )\n    return self.func(\"TIMESTAMP_SECONDS\", unix_seconds)\n\n\ndef _str_to_datetime_sql(\n    self: BigQuery.Generator, expression: exp.StrToDate | exp.StrToTime\n) -> str:\n    this = self.sql(expression, \"this\")\n    dtype = \"DATE\" if isinstance(expression, exp.StrToDate) else \"TIMESTAMP\"\n\n    if expression.args.get(\"safe\"):\n        fmt = self.format_time(\n            expression,\n            self.dialect.INVERSE_FORMAT_MAPPING,\n            self.dialect.INVERSE_FORMAT_TRIE,\n        )\n        return f\"SAFE_CAST({this} AS {dtype} FORMAT {fmt})\"\n\n    fmt = self.format_time(expression)\n    return self.func(f\"PARSE_{dtype}\", fmt, this, expression.args.get(\"zone\"))\n\n\n@unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\")\ndef _levenshtein_sql(self: BigQuery.Generator, expression: exp.Levenshtein) -> str:\n    max_dist = expression.args.get(\"max_dist\")\n    if max_dist:\n        max_dist = exp.Kwarg(this=exp.var(\"max_distance\"), expression=max_dist)\n\n    return self.func(\"EDIT_DISTANCE\", expression.this, expression.expression, max_dist)\n\n\ndef _json_extract_sql(self: BigQuery.Generator, expression: JSON_EXTRACT_TYPE) -> str:\n    name = (expression._meta and expression.meta.get(\"name\")) or expression.sql_name()\n    upper = name.upper()\n\n    dquote_escaping = upper in DQUOTES_ESCAPING_JSON_FUNCTIONS\n\n    if dquote_escaping:\n        self._quote_json_path_key_using_brackets = False\n\n    sql = rename_func(upper)(self, expression)\n\n    if dquote_escaping:\n        self._quote_json_path_key_using_brackets = True\n\n    return sql\n\n\nclass BigQuery(Dialect):\n    WEEK_OFFSET = -1\n    UNNEST_COLUMN_ONLY = True\n    SUPPORTS_USER_DEFINED_TYPES = False\n    LOG_BASE_FIRST = False\n    HEX_LOWERCASE = True\n    FORCE_EARLY_ALIAS_REF_EXPANSION = True\n    EXPAND_ONLY_GROUP_ALIAS_REF = True\n    PRESERVE_ORIGINAL_NAMES = True\n    HEX_STRING_IS_INTEGER_TYPE = True\n    BYTE_STRING_IS_BYTES_TYPE = True\n    UUID_IS_STRING_TYPE = True\n    ANNOTATE_ALL_SCOPES = True\n    PROJECTION_ALIASES_SHADOW_SOURCE_NAMES = True\n    TABLES_REFERENCEABLE_AS_COLUMNS = True\n    SUPPORTS_STRUCT_STAR_EXPANSION = True\n    EXCLUDES_PSEUDOCOLUMNS_FROM_STAR = True\n    QUERY_RESULTS_ARE_STRUCTS = True\n    JSON_EXTRACT_SCALAR_SCALAR_ONLY = True\n    LEAST_GREATEST_IGNORES_NULLS = False\n    DEFAULT_NULL_TYPE = exp.DType.BIGINT\n    PRIORITIZE_NON_LITERAL_TYPES = True\n    ALIAS_POST_VERSION = False\n\n    # https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#initcap\n    INITCAP_DEFAULT_DELIMITER_CHARS = ' \\t\\n\\r\\f\\v\\\\[\\\\](){}/|<>!?@\"^#$&~_,.:;*%+\\\\-'\n\n    # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#case_sensitivity\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n\n    # bigquery udfs are case sensitive\n    NORMALIZE_FUNCTIONS = False\n\n    # https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#format_elements_date_time\n    TIME_MAPPING = {\n        \"%x\": \"%m/%d/%y\",\n        \"%D\": \"%m/%d/%y\",\n        \"%E6S\": \"%S.%f\",\n        \"%e\": \"%-d\",\n        \"%F\": \"%Y-%m-%d\",\n        \"%T\": \"%H:%M:%S\",\n        \"%c\": \"%a %b %e %H:%M:%S %Y\",\n    }\n\n    INVERSE_TIME_MAPPING = {\n        # Preserve %E6S instead of expanding to %T.%f - since both %E6S & %T.%f are semantically different in BigQuery\n        # %E6S is semantically different from %T.%f: %E6S works as a single atomic specifier for seconds with microseconds, while %T.%f expands incorrectly and fails to parse.\n        \"%H:%M:%S.%f\": \"%H:%M:%E6S\",\n    }\n\n    FORMAT_MAPPING = {\n        \"DD\": \"%d\",\n        \"MM\": \"%m\",\n        \"MON\": \"%b\",\n        \"MONTH\": \"%B\",\n        \"YYYY\": \"%Y\",\n        \"YY\": \"%y\",\n        \"HH\": \"%I\",\n        \"HH12\": \"%I\",\n        \"HH24\": \"%H\",\n        \"MI\": \"%M\",\n        \"SS\": \"%S\",\n        \"SSSSS\": \"%f\",\n        \"TZH\": \"%z\",\n    }\n\n    # The _PARTITIONTIME and _PARTITIONDATE pseudo-columns are not returned by a SELECT * statement\n    # https://cloud.google.com/bigquery/docs/querying-partitioned-tables#query_an_ingestion-time_partitioned_table\n    # https://cloud.google.com/bigquery/docs/querying-wildcard-tables#scanning_a_range_of_tables_using_table_suffix\n    # https://cloud.google.com/bigquery/docs/query-cloud-storage-data#query_the_file_name_pseudo-column\n    PSEUDOCOLUMNS = {\n        \"_PARTITIONTIME\",\n        \"_PARTITIONDATE\",\n        \"_TABLE_SUFFIX\",\n        \"_FILE_NAME\",\n        \"_DBT_MAX_PARTITION\",\n    }\n\n    # All set operations require either a DISTINCT or ALL specifier\n    SET_OP_DISTINCT_BY_DEFAULT = dict.fromkeys((exp.Except, exp.Intersect, exp.Union), None)\n\n    # https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions#percentile_cont\n    COERCES_TO = {\n        **TypeAnnotator.COERCES_TO,\n        exp.DType.BIGDECIMAL: {exp.DType.DOUBLE},\n    }\n    COERCES_TO[exp.DType.DECIMAL] |= {exp.DType.BIGDECIMAL}\n    COERCES_TO[exp.DType.BIGINT] |= {exp.DType.BIGDECIMAL}\n    COERCES_TO[exp.DType.VARCHAR] |= {\n        exp.DType.DATE,\n        exp.DType.DATETIME,\n        exp.DType.TIME,\n        exp.DType.TIMESTAMP,\n        exp.DType.TIMESTAMPTZ,\n    }\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    def normalize_identifier(self, expression: E) -> E:\n        if (\n            isinstance(expression, exp.Identifier)\n            and self.normalization_strategy is NormalizationStrategy.CASE_INSENSITIVE\n        ):\n            parent = expression.parent\n            while isinstance(parent, exp.Dot):\n                parent = parent.parent\n\n            # In BigQuery, CTEs are case-insensitive, but UDF and table names are case-sensitive\n            # by default. The following check uses a heuristic to detect tables based on whether\n            # they are qualified. This should generally be correct, because tables in BigQuery\n            # must be qualified with at least a dataset, unless @@dataset_id is set.\n            case_sensitive = (\n                isinstance(parent, exp.UserDefinedFunction)\n                or (\n                    isinstance(parent, exp.Table)\n                    and parent.db\n                    and (parent.meta.get(\"quoted_table\") or not parent.meta.get(\"maybe_column\"))\n                )\n                or expression.meta.get(\"is_table\")\n            )\n            if not case_sensitive:\n                expression.set(\"this\", expression.this.lower())\n\n            return t.cast(E, expression)\n\n        return super().normalize_identifier(expression)\n\n    class JSONPathTokenizer(jsonpath.JSONPathTokenizer):\n        VAR_TOKENS = {\n            *jsonpath.JSONPathTokenizer.VAR_TOKENS,\n            TokenType.DASH,\n        }\n\n    class Tokenizer(tokens.Tokenizer):\n        QUOTES = [\"'\", '\"', '\"\"\"', \"'''\"]\n        COMMENTS = [\"--\", \"#\", (\"/*\", \"*/\")]\n        IDENTIFIERS = [\"`\"]\n        STRING_ESCAPES = [\"\\\\\"]\n\n        HEX_STRINGS = [(\"0x\", \"\"), (\"0X\", \"\")]\n\n        BYTE_STRINGS = [\n            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in (\"b\", \"B\")\n        ]\n\n        RAW_STRINGS = [\n            (prefix + q, q) for q in t.cast(t.List[str], QUOTES) for prefix in (\"r\", \"R\")\n        ]\n\n        NESTED_COMMENTS = False\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"ANY TYPE\": TokenType.VARIANT,\n            \"BEGIN\": TokenType.COMMAND,\n            \"BEGIN TRANSACTION\": TokenType.BEGIN,\n            \"BYTEINT\": TokenType.INT,\n            \"BYTES\": TokenType.BINARY,\n            \"CURRENT_DATETIME\": TokenType.CURRENT_DATETIME,\n            \"DATETIME\": TokenType.TIMESTAMP,\n            \"DECLARE\": TokenType.DECLARE,\n            \"ELSEIF\": TokenType.COMMAND,\n            \"EXCEPTION\": TokenType.COMMAND,\n            \"EXPORT\": TokenType.EXPORT,\n            \"FLOAT64\": TokenType.DOUBLE,\n            \"FOR SYSTEM_TIME\": TokenType.TIMESTAMP_SNAPSHOT,\n            \"LOOP\": TokenType.COMMAND,\n            \"MODEL\": TokenType.MODEL,\n            \"NOT DETERMINISTIC\": TokenType.VOLATILE,\n            \"RECORD\": TokenType.STRUCT,\n            \"REPEAT\": TokenType.COMMAND,\n            \"TIMESTAMP\": TokenType.TIMESTAMPTZ,\n            \"WHILE\": TokenType.COMMAND,\n        }\n        KEYWORDS.pop(\"DIV\")\n        KEYWORDS.pop(\"VALUES\")\n        KEYWORDS.pop(\"/*+\")\n\n    Parser = BigQueryParser\n\n    class Generator(generator.Generator):\n        INTERVAL_ALLOWS_PLURAL_FORM = False\n        JOIN_HINTS = False\n        QUERY_HINTS = False\n        TABLE_HINTS = False\n        LIMIT_FETCH = \"LIMIT\"\n        RENAME_TABLE_WITH_DB = False\n        NVL2_SUPPORTED = False\n        UNNEST_WITH_ORDINALITY = False\n        COLLATE_IS_FUNC = True\n        LIMIT_ONLY_LITERALS = True\n        SUPPORTS_TABLE_ALIAS_COLUMNS = False\n        UNPIVOT_ALIASES_ARE_IDENTIFIERS = False\n        JSON_KEY_VALUE_PAIR_SEP = \",\"\n        NULL_ORDERING_SUPPORTED = False\n        IGNORE_NULLS_IN_FUNC = True\n        JSON_PATH_SINGLE_QUOTE_ESCAPE = True\n        CAN_IMPLEMENT_ARRAY_ANY = True\n        SUPPORTS_TO_NUMBER = False\n        NAMED_PLACEHOLDER_TOKEN = \"@\"\n        HEX_FUNC = \"TO_HEX\"\n        WITH_PROPERTIES_PREFIX = \"OPTIONS\"\n        SUPPORTS_EXPLODING_PROJECTIONS = False\n        EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False\n        SUPPORTS_UNIX_SECONDS = True\n        DECLARE_DEFAULT_ASSIGNMENT = \"DEFAULT\"\n\n        SAFE_JSON_PATH_KEY_RE = re.compile(r\"^[_\\-a-zA-Z][\\-\\w]*$\")\n\n        WINDOW_FUNCS_WITH_NULL_ORDERING = (\n            exp.CumeDist,\n            exp.DenseRank,\n            exp.FirstValue,\n            exp.Lag,\n            exp.LastValue,\n            exp.Lead,\n            exp.NthValue,\n            exp.Ntile,\n            exp.PercentRank,\n            exp.Rank,\n            exp.RowNumber,\n        )\n\n        TS_OR_DS_TYPES = (\n            exp.TsOrDsToDatetime,\n            exp.TsOrDsToTimestamp,\n            exp.TsOrDsToTime,\n            exp.TsOrDsToDate,\n        )\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.ApproxTopK: rename_func(\"APPROX_TOP_COUNT\"),\n            exp.ApproxDistinct: rename_func(\"APPROX_COUNT_DISTINCT\"),\n            exp.ArgMax: arg_max_or_min_no_count(\"MAX_BY\"),\n            exp.ArgMin: arg_max_or_min_no_count(\"MIN_BY\"),\n            exp.Array: inline_array_unless_query,\n            exp.ArrayContains: _array_contains_sql,\n            exp.ArrayFilter: filter_array_using_unnest,\n            exp.ArrayRemove: filter_array_using_unnest,\n            exp.BitwiseAndAgg: rename_func(\"BIT_AND\"),\n            exp.BitwiseOrAgg: rename_func(\"BIT_OR\"),\n            exp.BitwiseXorAgg: rename_func(\"BIT_XOR\"),\n            exp.BitwiseCount: rename_func(\"BIT_COUNT\"),\n            exp.ByteLength: rename_func(\"BYTE_LENGTH\"),\n            exp.Cast: transforms.preprocess([transforms.remove_precision_parameterized_types]),\n            exp.CollateProperty: lambda self, e: (\n                f\"DEFAULT COLLATE {self.sql(e, 'this')}\"\n                if e.args.get(\"default\")\n                else f\"COLLATE {self.sql(e, 'this')}\"\n            ),\n            exp.Commit: lambda *_: \"COMMIT TRANSACTION\",\n            exp.CountIf: rename_func(\"COUNTIF\"),\n            exp.Create: _create_sql,\n            exp.CTE: transforms.preprocess([_pushdown_cte_column_names]),\n            exp.DateAdd: date_add_interval_sql(\"DATE\", \"ADD\"),\n            exp.DateDiff: lambda self, e: self.func(\n                \"DATE_DIFF\", e.this, e.expression, unit_to_var(e)\n            ),\n            exp.DateFromParts: rename_func(\"DATE\"),\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.DateSub: date_add_interval_sql(\"DATE\", \"SUB\"),\n            exp.DatetimeAdd: date_add_interval_sql(\"DATETIME\", \"ADD\"),\n            exp.DatetimeSub: date_add_interval_sql(\"DATETIME\", \"SUB\"),\n            exp.DateFromUnixDate: rename_func(\"DATE_FROM_UNIX_DATE\"),\n            exp.FromTimeZone: lambda self, e: self.func(\n                \"DATETIME\", self.func(\"TIMESTAMP\", e.this, e.args.get(\"zone\")), \"'UTC'\"\n            ),\n            exp.GenerateSeries: generate_series_sql(\"GENERATE_ARRAY\"),\n            exp.GroupConcat: lambda self, e: groupconcat_sql(\n                self, e, func_name=\"STRING_AGG\", within_group=False, sep=None\n            ),\n            exp.Hex: lambda self, e: self.func(\"UPPER\", self.func(\"TO_HEX\", self.sql(e, \"this\"))),\n            exp.HexString: lambda self, e: self.hexstring_sql(e, binary_function_repr=\"FROM_HEX\"),\n            exp.If: if_sql(false_value=\"NULL\"),\n            exp.ILike: no_ilike_sql,\n            exp.IntDiv: rename_func(\"DIV\"),\n            exp.Int64: rename_func(\"INT64\"),\n            exp.JSONBool: rename_func(\"BOOL\"),\n            exp.JSONExtract: _json_extract_sql,\n            exp.JSONExtractArray: _json_extract_sql,\n            exp.JSONExtractScalar: _json_extract_sql,\n            exp.JSONFormat: lambda self, e: self.func(\n                \"TO_JSON\" if e.args.get(\"to_json\") else \"TO_JSON_STRING\",\n                e.this,\n                e.args.get(\"options\"),\n            ),\n            exp.JSONKeysAtDepth: rename_func(\"JSON_KEYS\"),\n            exp.JSONValueArray: rename_func(\"JSON_VALUE_ARRAY\"),\n            exp.Levenshtein: _levenshtein_sql,\n            exp.Max: max_or_greatest,\n            exp.MD5: lambda self, e: self.func(\"TO_HEX\", self.func(\"MD5\", e.this)),\n            exp.MD5Digest: rename_func(\"MD5\"),\n            exp.Min: min_or_least,\n            exp.Normalize: lambda self, e: self.func(\n                \"NORMALIZE_AND_CASEFOLD\" if e.args.get(\"is_casefold\") else \"NORMALIZE\",\n                e.this,\n                e.args.get(\"form\"),\n            ),\n            exp.PartitionedByProperty: lambda self, e: f\"PARTITION BY {self.sql(e, 'this')}\",\n            exp.RegexpExtract: lambda self, e: self.func(\n                \"REGEXP_EXTRACT\",\n                e.this,\n                e.expression,\n                e.args.get(\"position\"),\n                e.args.get(\"occurrence\"),\n            ),\n            exp.RegexpExtractAll: lambda self, e: self.func(\n                \"REGEXP_EXTRACT_ALL\", e.this, e.expression\n            ),\n            exp.RegexpReplace: regexp_replace_sql,\n            exp.RegexpLike: rename_func(\"REGEXP_CONTAINS\"),\n            exp.ReturnsProperty: _returnsproperty_sql,\n            exp.Rollback: lambda *_: \"ROLLBACK TRANSACTION\",\n            exp.ParseTime: lambda self, e: self.func(\"PARSE_TIME\", self.format_time(e), e.this),\n            exp.ParseDatetime: lambda self, e: self.func(\n                \"PARSE_DATETIME\", self.format_time(e), e.this\n            ),\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.explode_projection_to_unnest(),\n                    transforms.unqualify_unnest,\n                    transforms.eliminate_distinct_on,\n                    _alias_ordered_group,\n                    transforms.eliminate_semi_and_anti_joins,\n                ]\n            ),\n            exp.SHA: rename_func(\"SHA1\"),\n            exp.SHA2: sha256_sql,\n            exp.SHA1Digest: rename_func(\"SHA1\"),\n            exp.SHA2Digest: sha2_digest_sql,\n            exp.StabilityProperty: lambda self, e: (\n                \"DETERMINISTIC\" if e.name == \"IMMUTABLE\" else \"NOT DETERMINISTIC\"\n            ),\n            exp.String: rename_func(\"STRING\"),\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self, e, func_name=\"INSTR\", supports_position=True, supports_occurrence=True\n            ),\n            exp.StrToDate: _str_to_datetime_sql,\n            exp.StrToTime: _str_to_datetime_sql,\n            exp.SessionUser: lambda *_: \"SESSION_USER()\",\n            exp.TimeAdd: date_add_interval_sql(\"TIME\", \"ADD\"),\n            exp.TimeFromParts: rename_func(\"TIME\"),\n            exp.TimestampFromParts: rename_func(\"DATETIME\"),\n            exp.TimeSub: date_add_interval_sql(\"TIME\", \"SUB\"),\n            exp.TimestampAdd: date_add_interval_sql(\"TIMESTAMP\", \"ADD\"),\n            exp.TimestampDiff: rename_func(\"TIMESTAMP_DIFF\"),\n            exp.TimestampSub: date_add_interval_sql(\"TIMESTAMP\", \"SUB\"),\n            exp.TimeStrToTime: timestrtotime_sql,\n            exp.Transaction: lambda *_: \"BEGIN TRANSACTION\",\n            exp.TsOrDsAdd: _ts_or_ds_add_sql,\n            exp.TsOrDsDiff: _ts_or_ds_diff_sql,\n            exp.TsOrDsToTime: rename_func(\"TIME\"),\n            exp.TsOrDsToDatetime: rename_func(\"DATETIME\"),\n            exp.TsOrDsToTimestamp: rename_func(\"TIMESTAMP\"),\n            exp.Unhex: rename_func(\"FROM_HEX\"),\n            exp.UnixDate: rename_func(\"UNIX_DATE\"),\n            exp.UnixToTime: _unix_to_time_sql,\n            exp.Uuid: lambda *_: \"GENERATE_UUID()\",\n            exp.Values: _derived_table_values_to_unnest,\n            exp.VariancePop: rename_func(\"VAR_POP\"),\n            exp.SafeDivide: rename_func(\"SAFE_DIVIDE\"),\n        }\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.BIGDECIMAL: \"BIGNUMERIC\",\n            exp.DType.BIGINT: \"INT64\",\n            exp.DType.BINARY: \"BYTES\",\n            exp.DType.BLOB: \"BYTES\",\n            exp.DType.BOOLEAN: \"BOOL\",\n            exp.DType.CHAR: \"STRING\",\n            exp.DType.DECIMAL: \"NUMERIC\",\n            exp.DType.DOUBLE: \"FLOAT64\",\n            exp.DType.FLOAT: \"FLOAT64\",\n            exp.DType.INT: \"INT64\",\n            exp.DType.NCHAR: \"STRING\",\n            exp.DType.NVARCHAR: \"STRING\",\n            exp.DType.SMALLINT: \"INT64\",\n            exp.DType.TEXT: \"STRING\",\n            exp.DType.TIMESTAMP: \"DATETIME\",\n            exp.DType.TIMESTAMPNTZ: \"DATETIME\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPLTZ: \"TIMESTAMP\",\n            exp.DType.TINYINT: \"INT64\",\n            exp.DType.ROWVERSION: \"BYTES\",\n            exp.DType.UUID: \"STRING\",\n            exp.DType.VARBINARY: \"BYTES\",\n            exp.DType.VARCHAR: \"STRING\",\n            exp.DType.VARIANT: \"ANY TYPE\",\n        }\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        # WINDOW comes after QUALIFY\n        # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#window_clause\n        AFTER_HAVING_MODIFIER_TRANSFORMS = {\n            \"qualify\": generator.Generator.AFTER_HAVING_MODIFIER_TRANSFORMS[\"qualify\"],\n            \"windows\": generator.Generator.AFTER_HAVING_MODIFIER_TRANSFORMS[\"windows\"],\n        }\n\n        # from: https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#reserved_keywords\n        RESERVED_KEYWORDS = {\n            \"all\",\n            \"and\",\n            \"any\",\n            \"array\",\n            \"as\",\n            \"asc\",\n            \"assert_rows_modified\",\n            \"at\",\n            \"between\",\n            \"by\",\n            \"case\",\n            \"cast\",\n            \"collate\",\n            \"contains\",\n            \"create\",\n            \"cross\",\n            \"cube\",\n            \"current\",\n            \"default\",\n            \"define\",\n            \"desc\",\n            \"distinct\",\n            \"else\",\n            \"end\",\n            \"enum\",\n            \"escape\",\n            \"except\",\n            \"exclude\",\n            \"exists\",\n            \"extract\",\n            \"false\",\n            \"fetch\",\n            \"following\",\n            \"for\",\n            \"from\",\n            \"full\",\n            \"group\",\n            \"grouping\",\n            \"groups\",\n            \"hash\",\n            \"having\",\n            \"if\",\n            \"ignore\",\n            \"in\",\n            \"inner\",\n            \"intersect\",\n            \"interval\",\n            \"into\",\n            \"is\",\n            \"join\",\n            \"lateral\",\n            \"left\",\n            \"like\",\n            \"limit\",\n            \"lookup\",\n            \"merge\",\n            \"natural\",\n            \"new\",\n            \"no\",\n            \"not\",\n            \"null\",\n            \"nulls\",\n            \"of\",\n            \"on\",\n            \"or\",\n            \"order\",\n            \"outer\",\n            \"over\",\n            \"partition\",\n            \"preceding\",\n            \"proto\",\n            \"qualify\",\n            \"range\",\n            \"recursive\",\n            \"respect\",\n            \"right\",\n            \"rollup\",\n            \"rows\",\n            \"select\",\n            \"set\",\n            \"some\",\n            \"struct\",\n            \"tablesample\",\n            \"then\",\n            \"to\",\n            \"treat\",\n            \"true\",\n            \"unbounded\",\n            \"union\",\n            \"unnest\",\n            \"using\",\n            \"when\",\n            \"where\",\n            \"window\",\n            \"with\",\n            \"within\",\n        }\n\n        def datetrunc_sql(self, expression: exp.DateTrunc) -> str:\n            unit = expression.unit\n            unit_sql = unit.name if unit.is_string else self.sql(unit)\n            return self.func(\"DATE_TRUNC\", expression.this, unit_sql, expression.args.get(\"zone\"))\n\n        def mod_sql(self, expression: exp.Mod) -> str:\n            this = expression.this\n            expr = expression.expression\n            return self.func(\n                \"MOD\",\n                this.unnest() if isinstance(this, exp.Paren) else this,\n                expr.unnest() if isinstance(expr, exp.Paren) else expr,\n            )\n\n        def column_parts(self, expression: exp.Column) -> str:\n            if expression.meta.get(\"quoted_column\"):\n                # If a column reference is of the form `dataset.table`.name, we need\n                # to preserve the quoted table path, otherwise the reference breaks\n                table_parts = \".\".join(p.name for p in expression.parts[:-1])\n                table_path = self.sql(exp.Identifier(this=table_parts, quoted=True))\n                return f\"{table_path}.{self.sql(expression, 'this')}\"\n\n            return super().column_parts(expression)\n\n        def table_parts(self, expression: exp.Table) -> str:\n            # Depending on the context, `x.y` may not resolve to the same data source as `x`.`y`, so\n            # we need to make sure the correct quoting is used in each case.\n            #\n            # For example, if there is a CTE x that clashes with a schema name, then the former will\n            # return the table y in that schema, whereas the latter will return the CTE's y column:\n            #\n            # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x.y`   -> cross join\n            # - WITH x AS (SELECT [1, 2] AS y) SELECT * FROM x, `x`.`y` -> implicit unnest\n            if expression.meta.get(\"quoted_table\"):\n                table_parts = \".\".join(p.name for p in expression.parts)\n                return self.sql(exp.Identifier(this=table_parts, quoted=True))\n\n            return super().table_parts(expression)\n\n        def timetostr_sql(self, expression: exp.TimeToStr) -> str:\n            this = expression.this\n            if isinstance(this, exp.TsOrDsToDatetime):\n                func_name = \"FORMAT_DATETIME\"\n            elif isinstance(this, exp.TsOrDsToTimestamp):\n                func_name = \"FORMAT_TIMESTAMP\"\n            elif isinstance(this, exp.TsOrDsToTime):\n                func_name = \"FORMAT_TIME\"\n            else:\n                func_name = \"FORMAT_DATE\"\n\n            time_expr = this if isinstance(this, self.TS_OR_DS_TYPES) else expression\n            return self.func(\n                func_name, self.format_time(expression), time_expr.this, expression.args.get(\"zone\")\n            )\n\n        def eq_sql(self, expression: exp.EQ) -> str:\n            # Operands of = cannot be NULL in BigQuery\n            if isinstance(expression.left, exp.Null) or isinstance(expression.right, exp.Null):\n                if not isinstance(expression.parent, exp.Update):\n                    return \"NULL\"\n\n            return self.binary(expression, \"=\")\n\n        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:\n            parent = expression.parent\n\n            # BigQuery allows CAST(.. AS {STRING|TIMESTAMP} [FORMAT <fmt> [AT TIME ZONE <tz>]]).\n            # Only the TIMESTAMP one should use the below conversion, when AT TIME ZONE is included.\n            if not isinstance(parent, exp.Cast) or not parent.to.is_type(\"text\"):\n                return self.func(\n                    \"TIMESTAMP\", self.func(\"DATETIME\", expression.this, expression.args.get(\"zone\"))\n                )\n\n            return super().attimezone_sql(expression)\n\n        def trycast_sql(self, expression: exp.TryCast) -> str:\n            return self.cast_sql(expression, safe_prefix=\"SAFE_\")\n\n        def bracket_sql(self, expression: exp.Bracket) -> str:\n            this = expression.this\n            expressions = expression.expressions\n\n            if len(expressions) == 1 and this and this.is_type(exp.DType.STRUCT):\n                arg = expressions[0]\n                if arg.type is None:\n                    from sqlglot.optimizer.annotate_types import annotate_types\n\n                    arg = annotate_types(arg, dialect=self.dialect)\n\n                if arg.type and arg.type.this in exp.DataType.TEXT_TYPES:\n                    # BQ doesn't support bracket syntax with string values for structs\n                    return f\"{self.sql(this)}.{arg.name}\"\n\n            expressions_sql = self.expressions(expression, flat=True)\n            offset = expression.args.get(\"offset\")\n\n            if offset == 0:\n                expressions_sql = f\"OFFSET({expressions_sql})\"\n            elif offset == 1:\n                expressions_sql = f\"ORDINAL({expressions_sql})\"\n            elif offset is not None:\n                self.unsupported(f\"Unsupported array offset: {offset}\")\n\n            if expression.args.get(\"safe\"):\n                expressions_sql = f\"SAFE_{expressions_sql}\"\n\n            return f\"{self.sql(this)}[{expressions_sql}]\"\n\n        def in_unnest_op(self, expression: exp.Unnest) -> str:\n            return self.sql(expression)\n\n        def version_sql(self, expression: exp.Version) -> str:\n            if expression.name == \"TIMESTAMP\":\n                expression.set(\"this\", \"SYSTEM_TIME\")\n            return super().version_sql(expression)\n\n        def contains_sql(self, expression: exp.Contains) -> str:\n            this = expression.this\n            expr = expression.expression\n\n            if isinstance(this, exp.Lower) and isinstance(expr, exp.Lower):\n                this = this.this\n                expr = expr.this\n\n            return self.func(\"CONTAINS_SUBSTR\", this, expr, expression.args.get(\"json_scope\"))\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            this = expression.this\n\n            # This ensures that inline type-annotated ARRAY literals like ARRAY<INT64>[1, 2, 3]\n            # are roundtripped unaffected. The inner check excludes ARRAY(SELECT ...) expressions,\n            # because they aren't literals and so the above syntax is invalid BigQuery.\n            if isinstance(this, exp.Array):\n                elem = seq_get(this.expressions, 0)\n                if not (elem and elem.find(exp.Query)):\n                    return f\"{self.sql(expression, 'to')}{self.sql(this)}\"\n\n            return super().cast_sql(expression, safe_prefix=safe_prefix)\n"
  },
  {
    "path": "sqlglot/dialects/clickhouse.py",
    "content": "from __future__ import annotations\n\nimport datetime\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    NormalizationStrategy,\n    arg_max_or_min_no_count,\n    inline_array_sql,\n    jarowinkler_similarity,\n    json_extract_segments,\n    json_path_key_only_name,\n    length_or_char_length_sql,\n    no_pivot_sql,\n    rename_func,\n    remove_from_array_using_filter,\n    sha256_sql,\n    strposition_sql,\n    var_map_sql,\n    unit_to_str,\n    unit_to_var,\n    trim_sql,\n    sha2_digest_sql,\n)\nfrom sqlglot.generator import Generator, unsupported_args\nfrom sqlglot.helper import is_int\nfrom sqlglot.parsers.clickhouse import ClickHouseParser\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.typing.clickhouse import EXPRESSION_METADATA\n\nDATETIME_DELTA = t.Union[exp.DateAdd, exp.DateDiff, exp.DateSub, exp.TimestampSub, exp.TimestampAdd]\n\n\ndef _unix_to_time_sql(self: ClickHouse.Generator, expression: exp.UnixToTime) -> str:\n    scale = expression.args.get(\"scale\")\n    timestamp = expression.this\n\n    if scale in (None, exp.UnixToTime.SECONDS):\n        return self.func(\"fromUnixTimestamp\", exp.cast(timestamp, exp.DType.BIGINT))\n    if scale == exp.UnixToTime.MILLIS:\n        return self.func(\"fromUnixTimestamp64Milli\", exp.cast(timestamp, exp.DType.BIGINT))\n    if scale == exp.UnixToTime.MICROS:\n        return self.func(\"fromUnixTimestamp64Micro\", exp.cast(timestamp, exp.DType.BIGINT))\n    if scale == exp.UnixToTime.NANOS:\n        return self.func(\"fromUnixTimestamp64Nano\", exp.cast(timestamp, exp.DType.BIGINT))\n\n    return self.func(\n        \"fromUnixTimestamp\",\n        exp.cast(exp.Div(this=timestamp, expression=exp.func(\"POW\", 10, scale)), exp.DType.BIGINT),\n    )\n\n\ndef _lower_func(sql: str) -> str:\n    index = sql.index(\"(\")\n    return sql[:index].lower() + sql[index:]\n\n\ndef _quantile_sql(self: ClickHouse.Generator, expression: exp.Quantile) -> str:\n    quantile = expression.args[\"quantile\"]\n    args = f\"({self.sql(expression, 'this')})\"\n\n    if isinstance(quantile, exp.Array):\n        func = self.func(\"quantiles\", *quantile)\n    else:\n        func = self.func(\"quantile\", quantile)\n\n    return func + args\n\n\ndef _datetime_delta_sql(name: str) -> t.Callable[[Generator, DATETIME_DELTA], str]:\n    def _delta_sql(self: Generator, expression: DATETIME_DELTA) -> str:\n        if not expression.unit:\n            return rename_func(name)(self, expression)\n\n        return self.func(\n            name,\n            unit_to_var(expression),\n            expression.expression,\n            expression.this,\n            expression.args.get(\"zone\"),\n        )\n\n    return _delta_sql\n\n\ndef _timestrtotime_sql(self: ClickHouse.Generator, expression: exp.TimeStrToTime):\n    ts = expression.this\n\n    tz = expression.args.get(\"zone\")\n    if tz and isinstance(ts, exp.Literal):\n        # Clickhouse will not accept timestamps that include a UTC offset, so we must remove them.\n        # The first step to removing is parsing the string with `datetime.datetime.fromisoformat`.\n        #\n        # In python <3.11, `fromisoformat()` can only parse timestamps of millisecond (3 digit)\n        # or microsecond (6 digit) precision. It will error if passed any other number of fractional\n        # digits, so we extract the fractional seconds and pad to 6 digits before parsing.\n        ts_string = ts.name.strip()\n\n        # separate [date and time] from [fractional seconds and UTC offset]\n        ts_parts = ts_string.split(\".\")\n        if len(ts_parts) == 2:\n            # separate fractional seconds and UTC offset\n            offset_sep = \"+\" if \"+\" in ts_parts[1] else \"-\"\n            ts_frac_parts = ts_parts[1].split(offset_sep)\n            num_frac_parts = len(ts_frac_parts)\n\n            # pad to 6 digits if fractional seconds present\n            ts_frac_parts[0] = ts_frac_parts[0].ljust(6, \"0\")\n            ts_string = \"\".join(\n                [\n                    ts_parts[0],  # date and time\n                    \".\",\n                    ts_frac_parts[0],  # fractional seconds\n                    offset_sep if num_frac_parts > 1 else \"\",\n                    ts_frac_parts[1] if num_frac_parts > 1 else \"\",  # utc offset (if present)\n                ]\n            )\n\n        # return literal with no timezone, eg turn '2020-01-01 12:13:14-08:00' into '2020-01-01 12:13:14'\n        # this is because Clickhouse encodes the timezone as a data type parameter and throws an error if\n        # it's part of the timestamp string\n        ts_without_tz = (\n            datetime.datetime.fromisoformat(ts_string).replace(tzinfo=None).isoformat(sep=\" \")\n        )\n        ts = exp.Literal.string(ts_without_tz)\n\n    # Non-nullable DateTime64 with microsecond precision\n    expressions = [exp.DataTypeParam(this=tz)] if tz else []\n    datatype = exp.DataType.build(\n        exp.DType.DATETIME64,\n        expressions=[exp.DataTypeParam(this=exp.Literal.number(6)), *expressions],\n        nullable=False,\n    )\n\n    return self.sql(exp.cast(ts, datatype, dialect=self.dialect))\n\n\ndef _map_sql(self: ClickHouse.Generator, expression: exp.Map | exp.VarMap) -> str:\n    if not (expression.parent and expression.parent.arg_key == \"settings\"):\n        return _lower_func(var_map_sql(self, expression))\n\n    keys = expression.args.get(\"keys\")\n    values = expression.args.get(\"values\")\n\n    if not isinstance(keys, exp.Array) or not isinstance(values, exp.Array):\n        self.unsupported(\"Cannot convert array columns into map.\")\n        return \"\"\n\n    args = []\n    for key, value in zip(keys.expressions, values.expressions):\n        args.append(f\"{self.sql(key)}: {self.sql(value)}\")\n\n    csv_args = \", \".join(args)\n\n    return f\"{{{csv_args}}}\"\n\n\ndef _json_cast_sql(self: ClickHouse.Generator, expression: exp.JSONCast) -> str:\n    this = self.sql(expression, \"this\")\n    to = expression.to\n    to_sql = self.sql(to)\n\n    if to.expressions:\n        to_sql = self.sql(exp.to_identifier(to_sql))\n\n    return f\"{this}.:{to_sql}\"\n\n\nclass ClickHouse(Dialect):\n    INDEX_OFFSET = 1\n    NORMALIZE_FUNCTIONS: bool | str = False\n    NULL_ORDERING = \"nulls_are_last\"\n    SUPPORTS_USER_DEFINED_TYPES = False\n    SAFE_DIVISION = True\n    LOG_BASE_FIRST: t.Optional[bool] = None\n    FORCE_EARLY_ALIAS_REF_EXPANSION = True\n    PRESERVE_ORIGINAL_NAMES = True\n    NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = True\n    IDENTIFIERS_CAN_START_WITH_DIGIT = True\n    HEX_STRING_IS_INTEGER_TYPE = True\n\n    # https://github.com/ClickHouse/ClickHouse/issues/33935#issue-1112165779\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    UNESCAPED_SEQUENCES = {\n        \"\\\\0\": \"\\0\",\n    }\n\n    CREATABLE_KIND_MAPPING = {\"DATABASE\": \"SCHEMA\"}\n\n    SET_OP_DISTINCT_BY_DEFAULT: t.Dict[t.Type[exp.Expr], t.Optional[bool]] = {\n        exp.Except: False,\n        exp.Intersect: False,\n        exp.Union: None,\n    }\n\n    def generate_values_aliases(self, expression: exp.Values) -> t.List[exp.Identifier]:\n        # Clickhouse allows VALUES to have an embedded structure e.g:\n        # VALUES('person String, place String', ('Noah', 'Paris'), ...)\n        # In this case, we don't want to qualify the columns\n        values = expression.expressions[0].expressions\n\n        structure = (\n            values[0]\n            if (len(values) > 1 and values[0].is_string and isinstance(values[1], exp.Tuple))\n            else None\n        )\n        if structure:\n            # Split each column definition into the column name e.g:\n            # 'person String, place String' -> ['person', 'place']\n            structure_coldefs = [coldef.strip() for coldef in structure.name.split(\",\")]\n            column_aliases = [\n                exp.to_identifier(coldef.split(\" \")[0]) for coldef in structure_coldefs\n            ]\n        else:\n            # Default column aliases in CH are \"c1\", \"c2\", etc.\n            column_aliases = [\n                exp.to_identifier(f\"c{i + 1}\") for i in range(len(values[0].expressions))\n            ]\n\n        return column_aliases\n\n    class Tokenizer(tokens.Tokenizer):\n        COMMENTS = [\"--\", \"#\", \"#!\", (\"/*\", \"*/\")]\n        IDENTIFIERS = ['\"', \"`\"]\n        IDENTIFIER_ESCAPES = [\"\\\\\"]\n        STRING_ESCAPES = [\"'\", \"\\\\\"]\n        BIT_STRINGS = [(\"0b\", \"\")]\n        HEX_STRINGS = [(\"0x\", \"\"), (\"0X\", \"\")]\n        HEREDOC_STRINGS = [\"$\"]\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \".:\": TokenType.DOTCOLON,\n            \".^\": TokenType.DOTCARET,\n            \"ATTACH\": TokenType.COMMAND,\n            \"DATE32\": TokenType.DATE32,\n            \"DETACH\": TokenType.DETACH,\n            \"DATETIME64\": TokenType.DATETIME64,\n            \"DICTIONARY\": TokenType.DICTIONARY,\n            \"DYNAMIC\": TokenType.DYNAMIC,\n            \"ENUM8\": TokenType.ENUM8,\n            \"ENUM16\": TokenType.ENUM16,\n            \"EXCHANGE\": TokenType.COMMAND,\n            \"FINAL\": TokenType.FINAL,\n            \"FIXEDSTRING\": TokenType.FIXEDSTRING,\n            \"FLOAT32\": TokenType.FLOAT,\n            \"FLOAT64\": TokenType.DOUBLE,\n            \"GLOBAL\": TokenType.GLOBAL,\n            \"LOWCARDINALITY\": TokenType.LOWCARDINALITY,\n            \"MAP\": TokenType.MAP,\n            \"NESTED\": TokenType.NESTED,\n            \"NOTHING\": TokenType.NOTHING,\n            \"SAMPLE\": TokenType.TABLE_SAMPLE,\n            \"TUPLE\": TokenType.STRUCT,\n            \"UINT16\": TokenType.USMALLINT,\n            \"UINT32\": TokenType.UINT,\n            \"UINT64\": TokenType.UBIGINT,\n            \"UINT8\": TokenType.UTINYINT,\n            \"IPV4\": TokenType.IPV4,\n            \"IPV6\": TokenType.IPV6,\n            \"POINT\": TokenType.POINT,\n            \"RING\": TokenType.RING,\n            \"LINESTRING\": TokenType.LINESTRING,\n            \"MULTILINESTRING\": TokenType.MULTILINESTRING,\n            \"POLYGON\": TokenType.POLYGON,\n            \"MULTIPOLYGON\": TokenType.MULTIPOLYGON,\n            \"AGGREGATEFUNCTION\": TokenType.AGGREGATEFUNCTION,\n            \"SIMPLEAGGREGATEFUNCTION\": TokenType.SIMPLEAGGREGATEFUNCTION,\n            \"SYSTEM\": TokenType.COMMAND,\n            \"PREWHERE\": TokenType.PREWHERE,\n        }\n        KEYWORDS.pop(\"/*+\")\n\n        SINGLE_TOKENS = {\n            **tokens.Tokenizer.SINGLE_TOKENS,\n            \"$\": TokenType.HEREDOC_STRING,\n        }\n\n    Parser = ClickHouseParser\n\n    class Generator(generator.Generator):\n        QUERY_HINTS = False\n        STRUCT_DELIMITER = (\"(\", \")\")\n        NVL2_SUPPORTED = False\n        TABLESAMPLE_REQUIRES_PARENS = False\n        TABLESAMPLE_SIZE_IS_ROWS = False\n        TABLESAMPLE_KEYWORDS = \"SAMPLE\"\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        CAN_IMPLEMENT_ARRAY_ANY = True\n        SUPPORTS_TO_NUMBER = False\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        GROUPINGS_SEP = \"\"\n        SET_OP_MODIFIERS = False\n        ARRAY_SIZE_NAME = \"LENGTH\"\n        WRAP_DERIVED_VALUES = False\n\n        STRING_TYPE_MAPPING = {\n            exp.DType.BLOB: \"String\",\n            exp.DType.CHAR: \"String\",\n            exp.DType.LONGBLOB: \"String\",\n            exp.DType.LONGTEXT: \"String\",\n            exp.DType.MEDIUMBLOB: \"String\",\n            exp.DType.MEDIUMTEXT: \"String\",\n            exp.DType.TINYBLOB: \"String\",\n            exp.DType.TINYTEXT: \"String\",\n            exp.DType.TEXT: \"String\",\n            exp.DType.VARBINARY: \"String\",\n            exp.DType.VARCHAR: \"String\",\n        }\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            **STRING_TYPE_MAPPING,\n            exp.DType.ARRAY: \"Array\",\n            exp.DType.BOOLEAN: \"Bool\",\n            exp.DType.BIGINT: \"Int64\",\n            exp.DType.DATE32: \"Date32\",\n            exp.DType.DATETIME: \"DateTime\",\n            exp.DType.DATETIME2: \"DateTime\",\n            exp.DType.SMALLDATETIME: \"DateTime\",\n            exp.DType.DATETIME64: \"DateTime64\",\n            exp.DType.DECIMAL: \"Decimal\",\n            exp.DType.DECIMAL32: \"Decimal32\",\n            exp.DType.DECIMAL64: \"Decimal64\",\n            exp.DType.DECIMAL128: \"Decimal128\",\n            exp.DType.DECIMAL256: \"Decimal256\",\n            exp.DType.TIMESTAMP: \"DateTime\",\n            exp.DType.TIMESTAMPNTZ: \"DateTime\",\n            exp.DType.TIMESTAMPTZ: \"DateTime\",\n            exp.DType.DOUBLE: \"Float64\",\n            exp.DType.ENUM: \"Enum\",\n            exp.DType.ENUM8: \"Enum8\",\n            exp.DType.ENUM16: \"Enum16\",\n            exp.DType.FIXEDSTRING: \"FixedString\",\n            exp.DType.FLOAT: \"Float32\",\n            exp.DType.INT: \"Int32\",\n            exp.DType.MEDIUMINT: \"Int32\",\n            exp.DType.INT128: \"Int128\",\n            exp.DType.INT256: \"Int256\",\n            exp.DType.LOWCARDINALITY: \"LowCardinality\",\n            exp.DType.MAP: \"Map\",\n            exp.DType.NESTED: \"Nested\",\n            exp.DType.NOTHING: \"Nothing\",\n            exp.DType.SMALLINT: \"Int16\",\n            exp.DType.STRUCT: \"Tuple\",\n            exp.DType.TINYINT: \"Int8\",\n            exp.DType.UBIGINT: \"UInt64\",\n            exp.DType.UINT: \"UInt32\",\n            exp.DType.UINT128: \"UInt128\",\n            exp.DType.UINT256: \"UInt256\",\n            exp.DType.USMALLINT: \"UInt16\",\n            exp.DType.UTINYINT: \"UInt8\",\n            exp.DType.IPV4: \"IPv4\",\n            exp.DType.IPV6: \"IPv6\",\n            exp.DType.POINT: \"Point\",\n            exp.DType.RING: \"Ring\",\n            exp.DType.LINESTRING: \"LineString\",\n            exp.DType.MULTILINESTRING: \"MultiLineString\",\n            exp.DType.POLYGON: \"Polygon\",\n            exp.DType.MULTIPOLYGON: \"MultiPolygon\",\n            exp.DType.AGGREGATEFUNCTION: \"AggregateFunction\",\n            exp.DType.SIMPLEAGGREGATEFUNCTION: \"SimpleAggregateFunction\",\n            exp.DType.DYNAMIC: \"Dynamic\",\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.AnyValue: rename_func(\"any\"),\n            exp.ApproxDistinct: rename_func(\"uniq\"),\n            exp.ArrayDistinct: rename_func(\"arrayDistinct\"),\n            exp.ArrayConcat: rename_func(\"arrayConcat\"),\n            exp.ArrayContains: rename_func(\"has\"),\n            exp.ArrayFilter: lambda self, e: self.func(\"arrayFilter\", e.expression, e.this),\n            exp.ArrayRemove: remove_from_array_using_filter,\n            exp.ArrayReverse: rename_func(\"arrayReverse\"),\n            exp.ArraySlice: rename_func(\"arraySlice\"),\n            exp.ArraySum: rename_func(\"arraySum\"),\n            exp.ArrayMax: rename_func(\"arrayMax\"),\n            exp.ArrayMin: rename_func(\"arrayMin\"),\n            exp.ArgMax: arg_max_or_min_no_count(\"argMax\"),\n            exp.ArgMin: arg_max_or_min_no_count(\"argMin\"),\n            exp.Array: inline_array_sql,\n            exp.CityHash64: rename_func(\"cityHash64\"),\n            exp.CastToStrType: rename_func(\"CAST\"),\n            exp.CurrentDatabase: rename_func(\"CURRENT_DATABASE\"),\n            exp.CurrentSchemas: rename_func(\"CURRENT_SCHEMAS\"),\n            exp.CountIf: rename_func(\"countIf\"),\n            exp.CosineDistance: rename_func(\"cosineDistance\"),\n            exp.CompressColumnConstraint: lambda self, e: (\n                f\"CODEC({self.expressions(e, key='this', flat=True)})\"\n            ),\n            exp.ComputedColumnConstraint: lambda self, e: (\n                f\"{'MATERIALIZED' if e.args.get('persisted') else 'ALIAS'} {self.sql(e, 'this')}\"\n            ),\n            exp.CurrentDate: lambda self, e: self.func(\"CURRENT_DATE\"),\n            exp.CurrentVersion: rename_func(\"VERSION\"),\n            exp.DateAdd: _datetime_delta_sql(\"DATE_ADD\"),\n            exp.DateDiff: _datetime_delta_sql(\"DATE_DIFF\"),\n            exp.DateStrToDate: rename_func(\"toDate\"),\n            exp.DateSub: _datetime_delta_sql(\"DATE_SUB\"),\n            exp.Explode: rename_func(\"arrayJoin\"),\n            exp.FarmFingerprint: rename_func(\"farmFingerprint64\"),\n            exp.Final: lambda self, e: f\"{self.sql(e, 'this')} FINAL\",\n            exp.IsNan: rename_func(\"isNaN\"),\n            exp.JarowinklerSimilarity: jarowinkler_similarity(\"jaroWinklerSimilarity\"),\n            exp.JSONCast: _json_cast_sql,\n            exp.JSONExtract: json_extract_segments(\"JSONExtractString\", quoted_index=False),\n            exp.JSONExtractScalar: json_extract_segments(\"JSONExtractString\", quoted_index=False),\n            exp.JSONPathKey: json_path_key_only_name,\n            exp.JSONPathRoot: lambda *_: \"\",\n            exp.Length: length_or_char_length_sql,\n            exp.Map: _map_sql,\n            exp.Median: rename_func(\"median\"),\n            exp.Nullif: rename_func(\"nullIf\"),\n            exp.PartitionedByProperty: lambda self, e: f\"PARTITION BY {self.sql(e, 'this')}\",\n            exp.Pivot: no_pivot_sql,\n            exp.Quantile: _quantile_sql,\n            exp.RegexpLike: lambda self, e: self.func(\"match\", e.this, e.expression),\n            exp.Rand: rename_func(\"randCanonical\"),\n            exp.StartsWith: rename_func(\"startsWith\"),\n            exp.Struct: rename_func(\"tuple\"),\n            exp.Trunc: rename_func(\"trunc\"),\n            exp.EndsWith: rename_func(\"endsWith\"),\n            exp.EuclideanDistance: rename_func(\"L2Distance\"),\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self,\n                e,\n                func_name=\"POSITION\",\n                supports_position=True,\n                use_ansi_position=False,\n            ),\n            exp.TimeToStr: lambda self, e: self.func(\n                \"formatDateTime\",\n                e.this.this if isinstance(e.this, exp.TsOrDsToTimestamp) else e.this,\n                self.format_time(e),\n                e.args.get(\"zone\"),\n            ),\n            exp.TimeStrToTime: _timestrtotime_sql,\n            exp.TimestampAdd: _datetime_delta_sql(\"TIMESTAMP_ADD\"),\n            exp.TimestampSub: _datetime_delta_sql(\"TIMESTAMP_SUB\"),\n            exp.Typeof: rename_func(\"toTypeName\"),\n            exp.VarMap: _map_sql,\n            exp.Xor: lambda self, e: self.func(\"xor\", e.this, e.expression, *e.expressions),\n            exp.MD5Digest: rename_func(\"MD5\"),\n            exp.MD5: lambda self, e: self.func(\"LOWER\", self.func(\"HEX\", self.func(\"MD5\", e.this))),\n            exp.SHA: rename_func(\"SHA1\"),\n            exp.SHA1Digest: rename_func(\"SHA1\"),\n            exp.SHA2: sha256_sql,\n            exp.SHA2Digest: sha2_digest_sql,\n            exp.Split: lambda self, e: self.func(\n                \"splitByString\", e.args.get(\"expression\"), e.this, e.args.get(\"limit\")\n            ),\n            exp.RegexpSplit: lambda self, e: self.func(\n                \"splitByRegexp\", e.args.get(\"expression\"), e.this, e.args.get(\"limit\")\n            ),\n            exp.UnixToTime: _unix_to_time_sql,\n            exp.Trim: lambda self, e: trim_sql(self, e, default_trim_type=\"BOTH\"),\n            exp.Variance: rename_func(\"varSamp\"),\n            exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),\n            exp.Stddev: rename_func(\"stddevSamp\"),\n            exp.Chr: rename_func(\"CHAR\"),\n            exp.Lag: lambda self, e: self.func(\n                \"lagInFrame\", e.this, e.args.get(\"offset\"), e.args.get(\"default\")\n            ),\n            exp.Lead: lambda self, e: self.func(\n                \"leadInFrame\", e.this, e.args.get(\"offset\"), e.args.get(\"default\")\n            ),\n            exp.Levenshtein: unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\", \"max_dist\")(\n                rename_func(\"editDistance\")\n            ),\n            exp.ParseDatetime: rename_func(\"parseDateTime\"),\n        }\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.DefinerProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.OnCluster: exp.Properties.Location.POST_NAME,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.ToTableProperty: exp.Properties.Location.POST_NAME,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        # There's no list in docs, but it can be found in Clickhouse code\n        # see `ClickHouse/src/Parsers/ParserCreate*.cpp`\n        ON_CLUSTER_TARGETS = {\n            \"SCHEMA\",  # Transpiled CREATE SCHEMA may have OnCluster property set\n            \"DATABASE\",\n            \"TABLE\",\n            \"VIEW\",\n            \"DICTIONARY\",\n            \"INDEX\",\n            \"FUNCTION\",\n            \"NAMED COLLECTION\",\n        }\n\n        # https://clickhouse.com/docs/en/sql-reference/data-types/nullable\n        NON_NULLABLE_TYPES = {\n            exp.DType.ARRAY,\n            exp.DType.MAP,\n            exp.DType.STRUCT,\n            exp.DType.POINT,\n            exp.DType.RING,\n            exp.DType.LINESTRING,\n            exp.DType.MULTILINESTRING,\n            exp.DType.POLYGON,\n            exp.DType.MULTIPOLYGON,\n        }\n\n        def offset_sql(self, expression: exp.Offset) -> str:\n            offset = super().offset_sql(expression)\n\n            # OFFSET ... FETCH syntax requires a \"ROW\" or \"ROWS\" keyword\n            # https://clickhouse.com/docs/sql-reference/statements/select/offset\n            parent = expression.parent\n            if isinstance(parent, exp.Select) and isinstance(parent.args.get(\"limit\"), exp.Fetch):\n                offset = f\"{offset} ROWS\"\n\n            return offset\n\n        def strtodate_sql(self, expression: exp.StrToDate) -> str:\n            strtodate_sql = self.function_fallback_sql(expression)\n\n            if not isinstance(expression.parent, exp.Cast):\n                # StrToDate returns DATEs in other dialects (eg. postgres), so\n                # this branch aims to improve the transpilation to clickhouse\n                return self.cast_sql(exp.cast(expression, \"DATE\"))\n\n            return strtodate_sql\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            this = expression.this\n\n            if isinstance(this, exp.StrToDate) and expression.to == exp.DataType.build(\"datetime\"):\n                return self.sql(this)\n\n            return super().cast_sql(expression, safe_prefix=safe_prefix)\n\n        def trycast_sql(self, expression: exp.TryCast) -> str:\n            dtype = expression.to\n            if not dtype.is_type(*self.NON_NULLABLE_TYPES, check_nullable=True):\n                # Casting x into Nullable(T) appears to behave similarly to TRY_CAST(x AS T)\n                dtype.set(\"nullable\", True)\n\n            return super().cast_sql(expression)\n\n        def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:\n            this = self.json_path_part(expression.this)\n            return str(int(this) + 1) if is_int(this) else this\n\n        def likeproperty_sql(self, expression: exp.LikeProperty) -> str:\n            return f\"AS {self.sql(expression, 'this')}\"\n\n        def _any_to_has(\n            self,\n            expression: exp.EQ | exp.NEQ,\n            default: t.Callable[[t.Any], str],\n            prefix: str = \"\",\n        ) -> str:\n            if isinstance(expression.left, exp.Any):\n                arr = expression.left\n                this = expression.right\n            elif isinstance(expression.right, exp.Any):\n                arr = expression.right\n                this = expression.left\n            else:\n                return default(expression)\n\n            return prefix + self.func(\"has\", arr.this.unnest(), this)\n\n        def eq_sql(self, expression: exp.EQ) -> str:\n            return self._any_to_has(expression, super().eq_sql)\n\n        def neq_sql(self, expression: exp.NEQ) -> str:\n            return self._any_to_has(expression, super().neq_sql, \"NOT \")\n\n        def regexpilike_sql(self, expression: exp.RegexpILike) -> str:\n            # Manually add a flag to make the search case-insensitive\n            regex = self.func(\"CONCAT\", \"'(?i)'\", expression.expression)\n            return self.func(\"match\", expression.this, regex)\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            # String is the standard ClickHouse type, every other variant is just an alias.\n            # Additionally, any supplied length parameter will be ignored.\n            #\n            # https://clickhouse.com/docs/en/sql-reference/data-types/string\n            if expression.this in self.STRING_TYPE_MAPPING:\n                dtype = \"String\"\n            else:\n                dtype = super().datatype_sql(expression)\n\n            # This section changes the type to `Nullable(...)` if the following conditions hold:\n            # - It's marked as nullable - this ensures we won't wrap ClickHouse types with `Nullable`\n            #   and change their semantics\n            # - It's not the key type of a `Map`. This is because ClickHouse enforces the following\n            #   constraint: \"Type of Map key must be a type, that can be represented by integer or\n            #   String or FixedString (possibly LowCardinality) or UUID or IPv6\"\n            # - It's not a composite type, e.g. `Nullable(Array(...))` is not a valid type\n            parent = expression.parent\n            nullable = expression.args.get(\"nullable\")\n            if nullable is True or (\n                nullable is None\n                and not (\n                    isinstance(parent, exp.DataType)\n                    and parent.is_type(exp.DType.MAP, check_nullable=True)\n                    and expression.index in (None, 0)\n                )\n                and not expression.is_type(*self.NON_NULLABLE_TYPES, check_nullable=True)\n            ):\n                dtype = f\"Nullable({dtype})\"\n\n            return dtype\n\n        def cte_sql(self, expression: exp.CTE) -> str:\n            if expression.args.get(\"scalar\"):\n                this = self.sql(expression, \"this\")\n                alias = self.sql(expression, \"alias\")\n                return f\"{this} AS {alias}\"\n\n            return super().cte_sql(expression)\n\n        def after_limit_modifiers(self, expression: exp.Expr) -> t.List[str]:\n            return super().after_limit_modifiers(expression) + [\n                (\n                    self.seg(\"SETTINGS \") + self.expressions(expression, key=\"settings\", flat=True)\n                    if expression.args.get(\"settings\")\n                    else \"\"\n                ),\n                (\n                    self.seg(\"FORMAT \") + self.sql(expression, \"format\")\n                    if expression.args.get(\"format\")\n                    else \"\"\n                ),\n            ]\n\n        def placeholder_sql(self, expression: exp.Placeholder) -> str:\n            return f\"{{{expression.name}: {self.sql(expression, 'kind')}}}\"\n\n        def oncluster_sql(self, expression: exp.OnCluster) -> str:\n            return f\"ON CLUSTER {self.sql(expression, 'this')}\"\n\n        def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:\n            if expression.kind in self.ON_CLUSTER_TARGETS and locations.get(\n                exp.Properties.Location.POST_NAME\n            ):\n                this_name = self.sql(\n                    expression.this if isinstance(expression.this, exp.Schema) else expression,\n                    \"this\",\n                )\n                this_properties = \" \".join(\n                    [self.sql(prop) for prop in locations[exp.Properties.Location.POST_NAME]]\n                )\n                this_schema = self.schema_columns_sql(expression.this)\n                this_schema = f\"{self.sep()}{this_schema}\" if this_schema else \"\"\n\n                return f\"{this_name}{self.sep()}{this_properties}{this_schema}\"\n\n            return super().createable_sql(expression, locations)\n\n        def create_sql(self, expression: exp.Create) -> str:\n            # The comment property comes last in CTAS statements, i.e. after the query\n            query = expression.expression\n            if isinstance(query, exp.Query):\n                comment_prop = expression.find(exp.SchemaCommentProperty)\n                if comment_prop:\n                    comment_prop.pop()\n                    query.replace(exp.paren(query))\n            else:\n                comment_prop = None\n\n            create_sql = super().create_sql(expression)\n\n            comment_sql = self.sql(comment_prop)\n            comment_sql = f\" {comment_sql}\" if comment_sql else \"\"\n\n            return f\"{create_sql}{comment_sql}\"\n\n        def prewhere_sql(self, expression: exp.PreWhere) -> str:\n            this = self.indent(self.sql(expression, \"this\"))\n            return f\"{self.seg('PREWHERE')}{self.sep()}{this}\"\n\n        def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:\n            this = self.sql(expression, \"this\")\n            this = f\" {this}\" if this else \"\"\n            expr = self.sql(expression, \"expression\")\n            expr = f\" {expr}\" if expr else \"\"\n            index_type = self.sql(expression, \"index_type\")\n            index_type = f\" TYPE {index_type}\" if index_type else \"\"\n            granularity = self.sql(expression, \"granularity\")\n            granularity = f\" GRANULARITY {granularity}\" if granularity else \"\"\n\n            return f\"INDEX{this}{expr}{index_type}{granularity}\"\n\n        def partition_sql(self, expression: exp.Partition) -> str:\n            return f\"PARTITION {self.expressions(expression, flat=True)}\"\n\n        def partitionid_sql(self, expression: exp.PartitionId) -> str:\n            return f\"ID {self.sql(expression.this)}\"\n\n        def replacepartition_sql(self, expression: exp.ReplacePartition) -> str:\n            return (\n                f\"REPLACE {self.sql(expression.expression)} FROM {self.sql(expression, 'source')}\"\n            )\n\n        def projectiondef_sql(self, expression: exp.ProjectionDef) -> str:\n            return f\"PROJECTION {self.sql(expression.this)} {self.wrap(expression.expression)}\"\n\n        def nestedjsonselect_sql(self, expression: exp.NestedJSONSelect) -> str:\n            return f\"{self.sql(expression, 'this')}.^{self.sql(expression, 'expression')}\"\n\n        def is_sql(self, expression: exp.Is) -> str:\n            is_sql = super().is_sql(expression)\n\n            if isinstance(expression.parent, exp.Not):\n                # value IS NOT NULL -> NOT (value IS NULL)\n                is_sql = self.wrap(is_sql)\n\n            return is_sql\n\n        def in_sql(self, expression: exp.In) -> str:\n            in_sql = super().in_sql(expression)\n\n            if isinstance(expression.parent, exp.Not) and expression.args.get(\"is_global\"):\n                in_sql = in_sql.replace(\"GLOBAL IN\", \"GLOBAL NOT IN\", 1)\n\n            return in_sql\n\n        def not_sql(self, expression: exp.Not) -> str:\n            if isinstance(expression.this, exp.In):\n                if expression.this.args.get(\"is_global\"):\n                    # let `GLOBAL IN` child interpose `NOT`\n                    return self.sql(expression, \"this\")\n\n                expression.set(\"this\", exp.paren(expression.this, copy=False))\n\n            return super().not_sql(expression)\n\n        def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:\n            # If the VALUES clause contains tuples of expressions, we need to treat it\n            # as a table since Clickhouse will automatically alias it as such.\n            alias = expression.args.get(\"alias\")\n\n            if alias and alias.args.get(\"columns\") and expression.expressions:\n                values = expression.expressions[0].expressions\n                values_as_table = any(isinstance(value, exp.Tuple) for value in values)\n            else:\n                values_as_table = True\n\n            return super().values_sql(expression, values_as_table=values_as_table)\n\n        def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str:\n            unit = unit_to_str(expression)\n            # https://clickhouse.com/docs/whats-new/changelog/2023#improvement\n            if self.dialect.version < (23, 12) and unit and unit.is_string:\n                unit = exp.Literal.string(unit.name.lower())\n            return self.func(\"dateTrunc\", unit, expression.this, expression.args.get(\"zone\"))\n"
  },
  {
    "path": "sqlglot/dialects/databricks.py",
    "content": "from __future__ import annotations\n\nfrom copy import deepcopy\nfrom collections import defaultdict\n\nfrom sqlglot import exp, transforms\nfrom sqlglot.dialects.dialect import (\n    date_delta_sql,\n    timestamptrunc_sql,\n    groupconcat_sql,\n)\nfrom sqlglot.dialects.spark import Spark\nfrom sqlglot.parsers.databricks import DatabricksParser\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.optimizer.annotate_types import TypeAnnotator\n\n\ndef _jsonextract_sql(\n    self: Databricks.Generator, expression: exp.JSONExtract | exp.JSONExtractScalar\n) -> str:\n    this = self.sql(expression, \"this\")\n    expr = self.sql(expression, \"expression\")\n    return f\"{this}:{expr}\"\n\n\nclass Databricks(Spark):\n    SAFE_DIVISION = False\n    COPY_PARAMS_ARE_CSV = False\n\n    COERCES_TO = defaultdict(set, deepcopy(TypeAnnotator.COERCES_TO))\n    for text_type in exp.DataType.TEXT_TYPES:\n        COERCES_TO[text_type] |= {\n            *exp.DataType.NUMERIC_TYPES,\n            *exp.DataType.TEMPORAL_TYPES,\n            exp.DType.BINARY,\n            exp.DType.BOOLEAN,\n            exp.DType.INTERVAL,\n        }\n\n    class JSONPathTokenizer(Spark.JSONPathTokenizer):\n        IDENTIFIERS = [\"`\", '\"']\n\n    class Tokenizer(Spark.Tokenizer):\n        KEYWORDS = {\n            **Spark.Tokenizer.KEYWORDS,\n            \"STREAM\": TokenType.STREAM,\n            \"VOID\": TokenType.VOID,\n        }\n\n    Parser = DatabricksParser\n\n    class Generator(Spark.Generator):\n        TABLESAMPLE_SEED_KEYWORD = \"REPEATABLE\"\n        COPY_PARAMS_ARE_WRAPPED = False\n        COPY_PARAMS_EQ_REQUIRED = True\n        JSON_PATH_SINGLE_QUOTE_ESCAPE = False\n        SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE\n        QUOTE_JSON_PATH = False\n        PARSE_JSON_NAME = \"PARSE_JSON\"\n\n        TRANSFORMS = {\n            **Spark.Generator.TRANSFORMS,\n            exp.CurrentVersion: lambda *_: \"CURRENT_VERSION()\",\n            exp.DateAdd: date_delta_sql(\"DATEADD\"),\n            exp.DateDiff: date_delta_sql(\"DATEDIFF\"),\n            exp.DatetimeAdd: lambda self, e: self.func(\n                \"TIMESTAMPADD\", e.unit, e.expression, e.this\n            ),\n            exp.DatetimeSub: lambda self, e: self.func(\n                \"TIMESTAMPADD\",\n                e.unit,\n                exp.Mul(this=e.expression, expression=exp.Literal.number(-1)),\n                e.this,\n            ),\n            exp.DatetimeTrunc: timestamptrunc_sql(),\n            exp.GroupConcat: groupconcat_sql,\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_distinct_on,\n                    transforms.unnest_to_explode,\n                    transforms.any_to_exists,\n                ]\n            ),\n            exp.JSONExtract: _jsonextract_sql,\n            exp.JSONExtractScalar: _jsonextract_sql,\n            exp.JSONPathRoot: lambda *_: \"\",\n            exp.ToChar: lambda self, e: (\n                self.cast_sql(exp.Cast(this=e.this, to=exp.DataType(this=\"STRING\")))\n                if e.args.get(\"is_numeric\")\n                else self.function_fallback_sql(e)\n            ),\n            exp.CurrentCatalog: lambda *_: \"CURRENT_CATALOG()\",\n        }\n\n        TRANSFORMS.pop(exp.RegexpLike)\n        TRANSFORMS.pop(exp.TryCast)\n\n        TYPE_MAPPING = {\n            **Spark.Generator.TYPE_MAPPING,\n            exp.DType.NULL: \"VOID\",\n        }\n\n        def columndef_sql(self, expression: exp.ColumnDef, sep: str = \" \") -> str:\n            constraint = expression.find(exp.GeneratedAsIdentityColumnConstraint)\n            kind = expression.kind\n            if (\n                constraint\n                and isinstance(kind, exp.DataType)\n                and kind.this in exp.DataType.INTEGER_TYPES\n            ):\n                # only BIGINT generated identity constraints are supported\n                expression.set(\"kind\", exp.DataType.build(\"bigint\"))\n\n            return super().columndef_sql(expression, sep)\n\n        def jsonpath_sql(self, expression: exp.JSONPath) -> str:\n            expression.set(\"escape\", None)\n            return super().jsonpath_sql(expression)\n\n        def uniform_sql(self, expression: exp.Uniform) -> str:\n            gen = expression.args.get(\"gen\")\n            seed = expression.args.get(\"seed\")\n\n            # From Snowflake UNIFORM(min, max, gen) as RANDOM(), RANDOM(seed), or constant value -> Extract seed\n            if gen:\n                seed = gen.this\n\n            return self.func(\"UNIFORM\", expression.this, expression.expression, seed)\n"
  },
  {
    "path": "sqlglot/dialects/dialect.py",
    "content": "from __future__ import annotations\n\nimport importlib\nimport logging\nimport typing as t\nimport sys\nfrom collections.abc import Sequence\nfrom enum import Enum, auto\nfrom functools import reduce\n\nfrom sqlglot import exp\nfrom sqlglot.dialects import DIALECT_MODULE_NAMES\nfrom sqlglot.errors import ParseError\nfrom sqlglot.generator import Generator, unsupported_args\nfrom sqlglot.expressions import apply_index_offset\nfrom sqlglot.helper import (\n    AutoName,\n    flatten,\n    is_int,\n    seq_get,\n    suggest_closest_match_and_fail,\n    to_bool,\n    ensure_list,\n)\nfrom sqlglot.jsonpath import JSONPathTokenizer, parse as parse_json_path\nfrom sqlglot.parser import Parser\nfrom sqlglot.parsers.base import BaseParser\nfrom sqlglot.time import TIMEZONES, format_time, subsecond_precision\nfrom sqlglot.tokens import Token, Tokenizer, TokenType\nfrom sqlglot.trie import new_trie\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nfrom importlib.metadata import entry_points\n\nDATE_ADD_OR_DIFF = t.Union[\n    exp.DateAdd,\n    exp.DateDiff,\n    exp.DateSub,\n    exp.TsOrDsAdd,\n    exp.TsOrDsDiff,\n]\nDATE_ADD_OR_SUB = t.Union[exp.DateAdd, exp.TsOrDsAdd, exp.DateSub]\nJSON_EXTRACT_TYPE = t.Union[\n    exp.JSONExtract, exp.JSONExtractScalar, exp.JSONBExtract, exp.JSONBExtractScalar\n]\nDATETIME_DELTA = t.Union[\n    exp.DateAdd,\n    exp.DatetimeAdd,\n    exp.DatetimeSub,\n    exp.TimeAdd,\n    exp.TimeSub,\n    exp.TimestampAdd,\n    exp.TimestampSub,\n    exp.TsOrDsAdd,\n]\nDATETIME_ADD = (exp.DateAdd, exp.TimeAdd, exp.DatetimeAdd, exp.TsOrDsAdd, exp.TimestampAdd)\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import B, E, F\n\nlogger = logging.getLogger(\"sqlglot\")\n\nUNESCAPED_SEQUENCES = {\n    \"\\\\a\": \"\\a\",\n    \"\\\\b\": \"\\b\",\n    \"\\\\f\": \"\\f\",\n    \"\\\\n\": \"\\n\",\n    \"\\\\r\": \"\\r\",\n    \"\\\\t\": \"\\t\",\n    \"\\\\v\": \"\\v\",\n    \"\\\\\\\\\": \"\\\\\",\n}\n\nPLUGIN_GROUP_NAME = \"sqlglot.dialects\"\n\n\nclass Dialects(str, Enum):\n    \"\"\"Dialects supported by SQLGLot.\"\"\"\n\n    DIALECT = \"\"\n\n    ATHENA = \"athena\"\n    BIGQUERY = \"bigquery\"\n    CLICKHOUSE = \"clickhouse\"\n    DATABRICKS = \"databricks\"\n    DORIS = \"doris\"\n    DREMIO = \"dremio\"\n    DRILL = \"drill\"\n    DRUID = \"druid\"\n    DUCKDB = \"duckdb\"\n    DUNE = \"dune\"\n    FABRIC = \"fabric\"\n    HIVE = \"hive\"\n    MATERIALIZE = \"materialize\"\n    MYSQL = \"mysql\"\n    ORACLE = \"oracle\"\n    POSTGRES = \"postgres\"\n    PRESTO = \"presto\"\n    PRQL = \"prql\"\n    REDSHIFT = \"redshift\"\n    RISINGWAVE = \"risingwave\"\n    SNOWFLAKE = \"snowflake\"\n    SOLR = \"solr\"\n    SPARK = \"spark\"\n    SPARK2 = \"spark2\"\n    SQLITE = \"sqlite\"\n    STARROCKS = \"starrocks\"\n    TABLEAU = \"tableau\"\n    TERADATA = \"teradata\"\n    TRINO = \"trino\"\n    TSQL = \"tsql\"\n    EXASOL = \"exasol\"\n\n\nclass NormalizationStrategy(str, AutoName):\n    \"\"\"Specifies the strategy according to which identifiers should be normalized.\"\"\"\n\n    LOWERCASE = auto()\n    \"\"\"Unquoted identifiers are lowercased.\"\"\"\n\n    UPPERCASE = auto()\n    \"\"\"Unquoted identifiers are uppercased.\"\"\"\n\n    CASE_SENSITIVE = auto()\n    \"\"\"Always case-sensitive, regardless of quotes.\"\"\"\n\n    CASE_INSENSITIVE = auto()\n    \"\"\"Always case-insensitive (lowercase), regardless of quotes.\"\"\"\n\n    CASE_INSENSITIVE_UPPERCASE = auto()\n    \"\"\"Always case-insensitive (uppercase), regardless of quotes.\"\"\"\n\n\nclass _Dialect(type):\n    _classes: t.Dict[str, t.Type[Dialect]] = {}\n\n    def __eq__(cls, other: t.Any) -> bool:\n        if cls is other:\n            return True\n        if isinstance(other, str):\n            return cls is cls.get(other)\n        if isinstance(other, Dialect):\n            return cls is type(other)\n\n        return False\n\n    def __hash__(cls) -> int:\n        return hash(cls.__name__.lower())\n\n    @property\n    def classes(cls):\n        if len(DIALECT_MODULE_NAMES) != len(cls._classes):\n            for key in DIALECT_MODULE_NAMES:\n                cls._try_load(key)\n\n        return cls._classes\n\n    @classmethod\n    def _try_load(cls, key: str | Dialects) -> None:\n        if isinstance(key, Dialects):\n            key = key.value\n\n        # 1. Try standard sqlglot modules first\n        if key in DIALECT_MODULE_NAMES:\n            module = importlib.import_module(f\"sqlglot.dialects.{key}\")\n            # If module was already imported, the class may not be in _classes\n            # Find and register the dialect class from the module\n            if key not in cls._classes:\n                for attr_name in dir(module):\n                    attr = getattr(module, attr_name, None)\n                    if (\n                        isinstance(attr, type)\n                        and issubclass(attr, Dialect)\n                        and attr.__name__.lower() == key\n                    ):\n                        cls._classes[key] = attr\n                        break\n            return\n\n        # 2. Try entry points (for plugins)\n        try:\n            all_eps = entry_points()\n            # Python 3.10+ has select() method, older versions use dict-like access\n            if hasattr(all_eps, \"select\"):\n                eps = all_eps.select(group=PLUGIN_GROUP_NAME, name=key)\n            else:\n                # For older Python versions, entry_points() returns a dict-like object\n                group_eps = all_eps.get(PLUGIN_GROUP_NAME, [])  # type: ignore\n                eps = [ep for ep in group_eps if ep.name == key]  # type: ignore\n\n            for entry_point in eps:\n                dialect_class = entry_point.load()\n                # Verify it's a Dialect subclass\n                # issubclass() returns False if not a subclass, TypeError only if not a class at all\n                if isinstance(dialect_class, type) and issubclass(dialect_class, Dialect):\n                    # Register the dialect using the entry point name (key)\n                    # The metaclass may have registered it by class name, but we need it by entry point name\n                    if key not in cls._classes:\n                        cls._classes[key] = dialect_class\n                    return\n        except ImportError:\n            # entry_point.load() failed (bad plugin - module/class doesn't exist)\n            pass\n\n        # 3. Try direct import (for backward compatibility)\n        # This allows namespace packages or explicit imports to work\n        try:\n            importlib.import_module(f\"sqlglot.dialects.{key}\")\n        except ImportError:\n            pass\n\n    @classmethod\n    def __getitem__(cls, key: str) -> t.Type[Dialect]:\n        if key not in cls._classes:\n            cls._try_load(key)\n\n        return cls._classes[key]\n\n    @classmethod\n    def get(\n        cls, key: str, default: t.Optional[t.Type[Dialect]] = None\n    ) -> t.Optional[t.Type[Dialect]]:\n        if key not in cls._classes:\n            cls._try_load(key)\n\n        return cls._classes.get(key, default)\n\n    def __new__(cls, clsname, bases, attrs):\n        klass = super().__new__(cls, clsname, bases, attrs)\n        enum = Dialects.__members__.get(clsname.upper())\n        cls._classes[enum.value if enum is not None else clsname.lower()] = klass\n\n        klass.TIME_TRIE = new_trie(klass.TIME_MAPPING)\n        klass.FORMAT_TRIE = (\n            new_trie(klass.FORMAT_MAPPING) if klass.FORMAT_MAPPING else klass.TIME_TRIE\n        )\n        # Merge class-defined INVERSE_TIME_MAPPING with auto-generated mappings\n        # This allows dialects to define custom inverse mappings for roundtrip correctness\n        klass.INVERSE_TIME_MAPPING = {v: k for k, v in klass.TIME_MAPPING.items()} | (\n            klass.__dict__.get(\"INVERSE_TIME_MAPPING\") or {}\n        )\n        klass.INVERSE_TIME_TRIE = new_trie(klass.INVERSE_TIME_MAPPING)\n        klass.INVERSE_FORMAT_MAPPING = {v: k for k, v in klass.FORMAT_MAPPING.items()}\n        klass.INVERSE_FORMAT_TRIE = new_trie(klass.INVERSE_FORMAT_MAPPING)\n\n        klass.INVERSE_CREATABLE_KIND_MAPPING = {\n            v: k for k, v in klass.CREATABLE_KIND_MAPPING.items()\n        }\n\n        base = seq_get(bases, 0)\n        base_tokenizer = (getattr(base, \"tokenizer_class\", Tokenizer),)\n        base_jsonpath_tokenizer = (getattr(base, \"jsonpath_tokenizer_class\", JSONPathTokenizer),)\n        base_parser = (getattr(base, \"parser_class\", Parser),)\n        base_generator = (getattr(base, \"generator_class\", Generator),)\n\n        klass.tokenizer_class = klass.__dict__.get(\n            \"Tokenizer\", type(\"Tokenizer\", base_tokenizer, {})\n        )\n        klass.jsonpath_tokenizer_class = klass.__dict__.get(\n            \"JSONPathTokenizer\", type(\"JSONPathTokenizer\", base_jsonpath_tokenizer, {})\n        )\n        klass.parser_class = klass.__dict__.get(\n            \"Parser\", klass.__dict__.get(\"parser_class\", base_parser[0])\n        )\n        klass.generator_class = klass.__dict__.get(\n            \"Generator\", type(\"Generator\", base_generator, {})\n        )\n\n        klass.QUOTE_START, klass.QUOTE_END = list(klass.tokenizer_class._QUOTES.items())[0]\n        klass.IDENTIFIER_START, klass.IDENTIFIER_END = list(\n            klass.tokenizer_class._IDENTIFIERS.items()\n        )[0]\n\n        def get_start_end(token_type: TokenType) -> t.Tuple[t.Optional[str], t.Optional[str]]:\n            return next(\n                (\n                    (s, e)\n                    for s, (e, t) in klass.tokenizer_class._FORMAT_STRINGS.items()\n                    if t == token_type\n                ),\n                (None, None),\n            )\n\n        klass.BIT_START, klass.BIT_END = get_start_end(TokenType.BIT_STRING)\n        klass.HEX_START, klass.HEX_END = get_start_end(TokenType.HEX_STRING)\n        klass.BYTE_START, klass.BYTE_END = get_start_end(TokenType.BYTE_STRING)\n        klass.UNICODE_START, klass.UNICODE_END = get_start_end(TokenType.UNICODE_STRING)\n\n        klass.STRINGS_SUPPORT_ESCAPED_SEQUENCES = \"\\\\\" in klass.tokenizer_class.STRING_ESCAPES\n        klass.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES = (\n            \"\\\\\" in klass.tokenizer_class.BYTE_STRING_ESCAPES\n        )\n\n        if klass.STRINGS_SUPPORT_ESCAPED_SEQUENCES or klass.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES:\n            klass.UNESCAPED_SEQUENCES = {\n                **UNESCAPED_SEQUENCES,\n                **klass.UNESCAPED_SEQUENCES,\n            }\n\n        klass.ESCAPED_SEQUENCES = {v: k for k, v in klass.UNESCAPED_SEQUENCES.items()}\n\n        klass.SUPPORTS_COLUMN_JOIN_MARKS = \"(+)\" in klass.tokenizer_class.KEYWORDS\n\n        if enum not in (\"\", \"bigquery\", \"snowflake\"):\n            klass.INITCAP_SUPPORTS_CUSTOM_DELIMITERS = False\n\n        if enum not in (\"\", \"bigquery\"):\n            klass.generator_class.SELECT_KINDS = ()\n\n        if enum not in (\"\", \"athena\", \"presto\", \"trino\", \"duckdb\"):\n            klass.generator_class.TRY_SUPPORTED = False\n            klass.generator_class.SUPPORTS_UESCAPE = False\n\n        if enum not in (\"\", \"databricks\", \"hive\", \"spark\", \"spark2\"):\n            modifier_transforms = klass.generator_class.AFTER_HAVING_MODIFIER_TRANSFORMS.copy()\n            for modifier in (\"cluster\", \"distribute\", \"sort\"):\n                modifier_transforms.pop(modifier, None)\n\n            klass.generator_class.AFTER_HAVING_MODIFIER_TRANSFORMS = modifier_transforms\n\n        if enum not in (\"\", \"databricks\", \"oracle\", \"redshift\", \"snowflake\", \"spark\"):\n            klass.generator_class.SUPPORTS_DECODE_CASE = False\n\n        klass.VALID_INTERVAL_UNITS = {\n            *klass.VALID_INTERVAL_UNITS,\n            *klass.DATE_PART_MAPPING.keys(),\n            *klass.DATE_PART_MAPPING.values(),\n        }\n\n        return klass\n\n\nclass Dialect(metaclass=_Dialect):\n    INDEX_OFFSET = 0\n    \"\"\"The base index offset for arrays.\"\"\"\n\n    WEEK_OFFSET = 0\n    \"\"\"First day of the week in DATE_TRUNC(week). Defaults to 0 (Monday). -1 would be Sunday.\"\"\"\n\n    UNNEST_COLUMN_ONLY = False\n    \"\"\"Whether `UNNEST` table aliases are treated as column aliases.\"\"\"\n\n    ALIAS_POST_TABLESAMPLE = False\n    \"\"\"Whether the table alias comes after tablesample.\"\"\"\n\n    TABLESAMPLE_SIZE_IS_PERCENT = False\n    \"\"\"Whether a size in the table sample clause represents percentage.\"\"\"\n\n    NORMALIZATION_STRATEGY = NormalizationStrategy.LOWERCASE\n    \"\"\"Specifies the strategy according to which identifiers should be normalized.\"\"\"\n\n    IDENTIFIERS_CAN_START_WITH_DIGIT = False\n    \"\"\"Whether an unquoted identifier can start with a digit.\"\"\"\n\n    DPIPE_IS_STRING_CONCAT = True\n    \"\"\"Whether the DPIPE token (`||`) is a string concatenation operator.\"\"\"\n\n    STRICT_STRING_CONCAT = False\n    \"\"\"Whether `CONCAT`'s arguments must be strings.\"\"\"\n\n    SUPPORTS_USER_DEFINED_TYPES = True\n    \"\"\"Whether user-defined data types are supported.\"\"\"\n\n    SUPPORTS_COLUMN_JOIN_MARKS = False\n    \"\"\"Whether the old-style outer join (+) syntax is supported.\"\"\"\n\n    COPY_PARAMS_ARE_CSV = True\n    \"\"\"Separator of COPY statement parameters.\"\"\"\n\n    NORMALIZE_FUNCTIONS: bool | str = \"upper\"\n    \"\"\"\n    Determines how function names are going to be normalized.\n    Possible values:\n        \"upper\" or True: Convert names to uppercase.\n        \"lower\": Convert names to lowercase.\n        False: Disables function name normalization.\n    \"\"\"\n\n    PRESERVE_ORIGINAL_NAMES: bool = False\n    \"\"\"\n    Whether the name of the function should be preserved inside the node's metadata,\n    can be useful for roundtripping deprecated vs new functions that share an AST node\n    e.g JSON_VALUE vs JSON_EXTRACT_SCALAR in BigQuery\n    \"\"\"\n\n    LOG_BASE_FIRST: t.Optional[bool] = True\n    \"\"\"\n    Whether the base comes first in the `LOG` function.\n    Possible values: `True`, `False`, `None` (two arguments are not supported by `LOG`)\n    \"\"\"\n\n    NULL_ORDERING = \"nulls_are_small\"\n    \"\"\"\n    Default `NULL` ordering method to use if not explicitly set.\n    Possible values: `\"nulls_are_small\"`, `\"nulls_are_large\"`, `\"nulls_are_last\"`\n    \"\"\"\n\n    TYPED_DIVISION = False\n    \"\"\"\n    Whether the behavior of `a / b` depends on the types of `a` and `b`.\n    False means `a / b` is always float division.\n    True means `a / b` is integer division if both `a` and `b` are integers.\n    \"\"\"\n\n    SAFE_DIVISION = False\n    \"\"\"Whether division by zero throws an error (`False`) or returns NULL (`True`).\"\"\"\n\n    CONCAT_COALESCE = False\n    \"\"\"A `NULL` arg in `CONCAT` yields `NULL` by default, but in some dialects it yields an empty string.\"\"\"\n\n    HEX_LOWERCASE = False\n    \"\"\"Whether the `HEX` function returns a lowercase hexadecimal string.\"\"\"\n\n    DATE_FORMAT = \"'%Y-%m-%d'\"\n    DATEINT_FORMAT = \"'%Y%m%d'\"\n    TIME_FORMAT = \"'%Y-%m-%d %H:%M:%S'\"\n\n    TIME_MAPPING: t.Dict[str, str] = {}\n    \"\"\"Associates this dialect's time formats with their equivalent Python `strftime` formats.\"\"\"\n\n    # https://cloud.google.com/bigquery/docs/reference/standard-sql/format-elements#format_model_rules_date_time\n    # https://docs.teradata.com/r/Teradata-Database-SQL-Functions-Operators-Exprs-and-Predicates/March-2017/Data-Type-Conversions/Character-to-DATE-Conversion/Forcing-a-FORMAT-on-CAST-for-Converting-Character-to-DATE\n    FORMAT_MAPPING: t.Dict[str, str] = {}\n    \"\"\"\n    Helper which is used for parsing the special syntax `CAST(x AS DATE FORMAT 'yyyy')`.\n    If empty, the corresponding trie will be constructed off of `TIME_MAPPING`.\n    \"\"\"\n\n    UNESCAPED_SEQUENCES: t.Dict[str, str] = {}\n    \"\"\"Mapping of an escaped sequence (`\\\\n`) to its unescaped version (`\\n`).\"\"\"\n\n    PSEUDOCOLUMNS: t.Set[str] = set()\n    \"\"\"\n    Columns that are auto-generated by the engine corresponding to this dialect.\n    For example, such columns may be excluded from `SELECT *` queries.\n    \"\"\"\n\n    PREFER_CTE_ALIAS_COLUMN = False\n    \"\"\"\n    Some dialects, such as Snowflake, allow you to reference a CTE column alias in the\n    HAVING clause of the CTE. This flag will cause the CTE alias columns to override\n    any projection aliases in the subquery.\n\n    For example,\n        WITH y(c) AS (\n            SELECT SUM(a) FROM (SELECT 1 a) AS x HAVING c > 0\n        ) SELECT c FROM y;\n\n        will be rewritten as\n\n        WITH y(c) AS (\n            SELECT SUM(a) AS c FROM (SELECT 1 AS a) AS x HAVING c > 0\n        ) SELECT c FROM y;\n    \"\"\"\n\n    COPY_PARAMS_ARE_CSV = True\n    \"\"\"\n    Whether COPY statement parameters are separated by comma or whitespace\n    \"\"\"\n\n    FORCE_EARLY_ALIAS_REF_EXPANSION = False\n    \"\"\"\n    Whether alias reference expansion (_expand_alias_refs()) should run before column qualification (_qualify_columns()).\n\n    For example:\n        WITH data AS (\n        SELECT\n            1 AS id,\n            2 AS my_id\n        )\n        SELECT\n            id AS my_id\n        FROM\n            data\n        WHERE\n            my_id = 1\n        GROUP BY\n            my_id,\n        HAVING\n            my_id = 1\n\n    In most dialects, \"my_id\" would refer to \"data.my_id\" across the query, except:\n        - BigQuery, which will forward the alias to GROUP BY + HAVING clauses i.e\n          it resolves to \"WHERE my_id = 1 GROUP BY id HAVING id = 1\"\n        - Clickhouse, which will forward the alias across the query i.e it resolves\n        to \"WHERE id = 1 GROUP BY id HAVING id = 1\"\n    \"\"\"\n\n    EXPAND_ONLY_GROUP_ALIAS_REF = False\n    \"\"\"Whether alias reference expansion before qualification should only happen for the GROUP BY clause.\"\"\"\n\n    ANNOTATE_ALL_SCOPES = False\n    \"\"\"Whether to annotate all scopes during optimization. Used by BigQuery for UNNEST support.\"\"\"\n\n    DISABLES_ALIAS_REF_EXPANSION = False\n    \"\"\"\n    Whether alias reference expansion is disabled for this dialect.\n\n    Some dialects like Oracle do NOT support referencing aliases in projections or WHERE clauses.\n    The original expression must be repeated instead.\n\n    For example, in Oracle:\n        SELECT y.foo AS bar, bar * 2 AS baz FROM y  -- INVALID\n        SELECT y.foo AS bar, y.foo * 2 AS baz FROM y  -- VALID\n    \"\"\"\n\n    SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS = False\n    \"\"\"\n    Whether alias references are allowed in JOIN ... ON clauses.\n\n    Most dialects do not support this, but Snowflake allows alias expansion in the JOIN ... ON\n    clause (and almost everywhere else)\n\n    For example, in Snowflake:\n        SELECT a.id AS user_id FROM a JOIN b ON user_id = b.id  -- VALID\n\n    Reference: https://docs.snowflake.com/en/sql-reference/sql/select#usage-notes\n    \"\"\"\n\n    SUPPORTS_ORDER_BY_ALL = False\n    \"\"\"\n    Whether ORDER BY ALL is supported (expands to all the selected columns) as in DuckDB, Spark3/Databricks\n    \"\"\"\n\n    PROJECTION_ALIASES_SHADOW_SOURCE_NAMES = False\n    \"\"\"\n    Whether projection alias names can shadow table/source names in GROUP BY and HAVING clauses.\n\n    In BigQuery, when a projection alias has the same name as a source table, the alias takes\n    precedence in GROUP BY and HAVING clauses, and the table becomes inaccessible by that name.\n\n    For example, in BigQuery:\n        SELECT id, ARRAY_AGG(col) AS custom_fields\n        FROM custom_fields\n        GROUP BY id\n        HAVING id >= 1\n\n    The \"custom_fields\" source is shadowed by the projection alias, so we cannot qualify \"id\"\n    with \"custom_fields\" in GROUP BY/HAVING.\n    \"\"\"\n\n    TABLES_REFERENCEABLE_AS_COLUMNS = False\n    \"\"\"\n    Whether table names can be referenced as columns (treated as structs).\n\n    BigQuery allows tables to be referenced as columns in queries, automatically treating\n    them as struct values containing all the table's columns.\n\n    For example, in BigQuery:\n        SELECT t FROM my_table AS t  -- Returns entire row as a struct\n    \"\"\"\n\n    SUPPORTS_STRUCT_STAR_EXPANSION = False\n    \"\"\"\n    Whether the dialect supports expanding struct fields using star notation (e.g., struct_col.*).\n\n    BigQuery allows struct fields to be expanded with the star operator:\n        SELECT t.struct_col.* FROM table t\n    RisingWave also allows struct field expansion with the star operator using parentheses:\n        SELECT (t.struct_col).* FROM table t\n\n    This expands to all fields within the struct.\n    \"\"\"\n\n    EXCLUDES_PSEUDOCOLUMNS_FROM_STAR = False\n    \"\"\"\n    Whether pseudocolumns should be excluded from star expansion (SELECT *).\n\n    Pseudocolumns are special dialect-specific columns (e.g., Oracle's ROWNUM, ROWID, LEVEL,\n    or BigQuery's _PARTITIONTIME, _PARTITIONDATE) that are implicitly available but not part\n    of the table schema. When this is True, SELECT * will not include these pseudocolumns;\n    they must be explicitly selected.\n    \"\"\"\n\n    QUERY_RESULTS_ARE_STRUCTS = False\n    \"\"\"\n    Whether query results are typed as structs in metadata for type inference.\n\n    In BigQuery, subqueries store their column types as a STRUCT in metadata,\n    enabling special type inference for ARRAY(SELECT ...) expressions:\n        ARRAY(SELECT x, y FROM t) → ARRAY<STRUCT<...>>\n\n    For single column subqueries, BigQuery unwraps the struct:\n        ARRAY(SELECT x FROM t) → ARRAY<type_of_x>\n\n    This is metadata-only for type inference.\n    \"\"\"\n\n    REQUIRES_PARENTHESIZED_STRUCT_ACCESS = False\n    \"\"\"\n    Whether struct field access requires parentheses around the expression.\n\n    RisingWave requires parentheses for struct field access in certain contexts:\n        SELECT (col.field).subfield FROM table  -- Parentheses required\n\n    Without parentheses, the parser may not correctly interpret nested struct access.\n\n    Reference: https://docs.risingwave.com/sql/data-types/struct#retrieve-data-in-a-struct\n    \"\"\"\n\n    SUPPORTS_NULL_TYPE = False\n    \"\"\"\n    Whether NULL/VOID is supported as a valid data type (not just a value).\n\n    Databricks and Spark v3+ support NULL as an actual type, allowing expressions like:\n        SELECT NULL AS col  -- Has type NULL, not just value NULL\n        CAST(x AS VOID)     -- Valid type cast\n    \"\"\"\n\n    COALESCE_COMPARISON_NON_STANDARD = False\n    \"\"\"\n    Whether COALESCE in comparisons has non-standard NULL semantics.\n\n    We can't convert `COALESCE(x, 1) = 2` into `NOT x IS NULL AND x = 2` for redshift,\n    because they are not always equivalent. For example,  if `x` is `NULL` and it comes\n    from a table, then the result is `NULL`, despite `FALSE AND NULL` evaluating to `FALSE`.\n\n    In standard SQL and most dialects, these expressions are equivalent, but Redshift treats\n    table NULLs differently in this context.\n    \"\"\"\n\n    HAS_DISTINCT_ARRAY_CONSTRUCTORS = False\n    \"\"\"\n    Whether the ARRAY constructor is context-sensitive, i.e in Redshift ARRAY[1, 2, 3] != ARRAY(1, 2, 3)\n    as the former is of type INT[] vs the latter which is SUPER\n    \"\"\"\n\n    SUPPORTS_FIXED_SIZE_ARRAYS = False\n    \"\"\"\n    Whether expressions such as x::INT[5] should be parsed as fixed-size array defs/casts e.g.\n    in DuckDB. In dialects which don't support fixed size arrays such as Snowflake, this should\n    be interpreted as a subscript/index operator.\n    \"\"\"\n\n    STRICT_JSON_PATH_SYNTAX = True\n    \"\"\"Whether failing to parse a JSON path expression using the JSONPath dialect will log a warning.\"\"\"\n\n    ON_CONDITION_EMPTY_BEFORE_ERROR = True\n    \"\"\"Whether \"X ON EMPTY\" should come before \"X ON ERROR\" (for dialects like T-SQL, MySQL, Oracle).\"\"\"\n\n    ARRAY_AGG_INCLUDES_NULLS: t.Optional[bool] = True\n    \"\"\"Whether ArrayAgg needs to filter NULL values.\"\"\"\n\n    ARRAY_FUNCS_PROPAGATES_NULLS = False\n    \"\"\"Whether Array update functions return NULL when the input array is NULL.\"\"\"\n\n    PROMOTE_TO_INFERRED_DATETIME_TYPE = False\n    \"\"\"\n    This flag is used in the optimizer's canonicalize rule and determines whether x will be promoted\n    to the literal's type in x::DATE < '2020-01-01 12:05:03' (i.e., DATETIME). When false, the literal\n    is cast to x's type to match it instead.\n    \"\"\"\n\n    SUPPORTS_VALUES_DEFAULT = True\n    \"\"\"Whether the DEFAULT keyword is supported in the VALUES clause.\"\"\"\n\n    NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = False\n    \"\"\"Whether number literals can include underscores for better readability\"\"\"\n\n    HEX_STRING_IS_INTEGER_TYPE: bool = False\n    \"\"\"Whether hex strings such as x'CC' evaluate to integer or binary/blob type\"\"\"\n\n    REGEXP_EXTRACT_DEFAULT_GROUP = 0\n    \"\"\"The default value for the capturing group.\"\"\"\n\n    REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL = True\n    \"\"\"Whether REGEXP_EXTRACT returns NULL when the position arg exceeds the string length.\"\"\"\n\n    SET_OP_DISTINCT_BY_DEFAULT: t.Dict[t.Type[exp.Expr], t.Optional[bool]] = {\n        exp.Except: True,\n        exp.Intersect: True,\n        exp.Union: True,\n    }\n    \"\"\"\n    Whether a set operation uses DISTINCT by default. This is `None` when either `DISTINCT` or `ALL`\n    must be explicitly specified.\n    \"\"\"\n\n    CREATABLE_KIND_MAPPING: dict[str, str] = {}\n    \"\"\"\n    Helper for dialects that use a different name for the same creatable kind. For example, the Clickhouse\n    equivalent of CREATE SCHEMA is CREATE DATABASE.\n    \"\"\"\n\n    ALTER_TABLE_SUPPORTS_CASCADE = False\n    \"\"\"\n    Hive by default does not update the schema of existing partitions when a column is changed.\n    the CASCADE clause is used to indicate that the change should be propagated to all existing partitions.\n    the Spark dialect, while derived from Hive, does not support the CASCADE clause.\n    \"\"\"\n\n    # Whether ADD is present for each column added by ALTER TABLE\n    ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN = True\n\n    # Whether the value/LHS of the TRY_CAST(<value> AS <type>) should strictly be a\n    # STRING type (Snowflake's case) or can be of any type\n    TRY_CAST_REQUIRES_STRING: t.Optional[bool] = None\n\n    # Whether the double negation can be applied\n    # Not safe with MySQL and SQLite due to type coercion (may not return boolean)\n    SAFE_TO_ELIMINATE_DOUBLE_NEGATION = True\n\n    # Whether the INITCAP function supports custom delimiter characters as the second argument\n    # Default delimiter characters for INITCAP function: whitespace and non-alphanumeric characters\n    INITCAP_SUPPORTS_CUSTOM_DELIMITERS = True\n    INITCAP_DEFAULT_DELIMITER_CHARS = \" \\t\\n\\r\\f\\v!\\\"#$%&'()*+,\\\\-./:;<=>?@\\\\[\\\\]^_`{|}~\"\n\n    BYTE_STRING_IS_BYTES_TYPE: bool = False\n    \"\"\"\n    Whether byte string literals (ex: BigQuery's b'...') are typed as BYTES/BINARY\n    \"\"\"\n\n    UUID_IS_STRING_TYPE: bool = False\n    \"\"\"\n    Whether a UUID is considered a string or a UUID type.\n    \"\"\"\n\n    JSON_EXTRACT_SCALAR_SCALAR_ONLY = False\n    \"\"\"\n    Whether JSON_EXTRACT_SCALAR returns null if a non-scalar value is selected.\n    \"\"\"\n\n    DEFAULT_FUNCTIONS_COLUMN_NAMES: t.Dict[t.Type[exp.Func], t.Union[str, t.Tuple[str, ...]]] = {}\n    \"\"\"\n    Maps function expressions to their default output column name(s).\n\n    For example, in Postgres, generate_series function outputs a column named \"generate_series\" by default,\n    so we map the ExplodingGenerateSeries expression to \"generate_series\" string.\n    \"\"\"\n\n    DEFAULT_NULL_TYPE = exp.DType.UNKNOWN\n    \"\"\"\n    The default type of NULL for producing the correct projection type.\n\n    For example, in BigQuery the default type of the NULL value is INT64.\n    \"\"\"\n\n    LEAST_GREATEST_IGNORES_NULLS = True\n    \"\"\"\n    Whether LEAST/GREATEST functions ignore NULL values, e.g:\n    - BigQuery, Snowflake, MySQL, Presto/Trino: LEAST(1, NULL, 2) -> NULL\n    - Spark, Postgres, DuckDB, TSQL: LEAST(1, NULL, 2) -> 1\n    \"\"\"\n\n    PRIORITIZE_NON_LITERAL_TYPES = False\n    \"\"\"\n    Whether to prioritize non-literal types over literals during type annotation.\n    \"\"\"\n\n    ALIAS_POST_VERSION = True\n    \"\"\"Whether the table alias comes after version (timestamp or iceberg snapshot).\"\"\"\n\n    # --- Autofilled ---\n\n    tokenizer_class = Tokenizer\n    jsonpath_tokenizer_class = JSONPathTokenizer\n    parser_class = BaseParser\n    generator_class = Generator\n\n    # A trie of the time_mapping keys\n    TIME_TRIE: t.Dict = {}\n    FORMAT_TRIE: t.Dict = {}\n\n    INVERSE_TIME_MAPPING: t.Dict[str, str] = {}\n    INVERSE_TIME_TRIE: t.Dict = {}\n    INVERSE_FORMAT_MAPPING: t.Dict[str, str] = {}\n    INVERSE_FORMAT_TRIE: t.Dict = {}\n\n    INVERSE_CREATABLE_KIND_MAPPING: dict[str, str] = {}\n\n    ESCAPED_SEQUENCES: t.Dict[str, str] = {}\n\n    # Delimiters for string literals and identifiers\n    QUOTE_START = \"'\"\n    QUOTE_END = \"'\"\n    IDENTIFIER_START = '\"'\n    IDENTIFIER_END = '\"'\n\n    VALID_INTERVAL_UNITS: t.Set[str] = set()\n\n    # Delimiters for bit, hex, byte and unicode literals\n    BIT_START: t.Optional[str] = None\n    BIT_END: t.Optional[str] = None\n    HEX_START: t.Optional[str] = None\n    HEX_END: t.Optional[str] = None\n    BYTE_START: t.Optional[str] = None\n    BYTE_END: t.Optional[str] = None\n    UNICODE_START: t.Optional[str] = None\n    UNICODE_END: t.Optional[str] = None\n\n    DATE_PART_MAPPING = {\n        \"Y\": \"YEAR\",\n        \"YY\": \"YEAR\",\n        \"YYY\": \"YEAR\",\n        \"YYYY\": \"YEAR\",\n        \"YR\": \"YEAR\",\n        \"YEARS\": \"YEAR\",\n        \"YRS\": \"YEAR\",\n        \"MM\": \"MONTH\",\n        \"MON\": \"MONTH\",\n        \"MONS\": \"MONTH\",\n        \"MONTHS\": \"MONTH\",\n        \"D\": \"DAY\",\n        \"DD\": \"DAY\",\n        \"DAYS\": \"DAY\",\n        \"DAYOFMONTH\": \"DAY\",\n        \"DAY OF WEEK\": \"DAYOFWEEK\",\n        \"WEEKDAY\": \"DAYOFWEEK\",\n        \"DOW\": \"DAYOFWEEK\",\n        \"DW\": \"DAYOFWEEK\",\n        \"WEEKDAY_ISO\": \"DAYOFWEEKISO\",\n        \"DOW_ISO\": \"DAYOFWEEKISO\",\n        \"DW_ISO\": \"DAYOFWEEKISO\",\n        \"DAYOFWEEK_ISO\": \"DAYOFWEEKISO\",\n        \"DAY OF YEAR\": \"DAYOFYEAR\",\n        \"DOY\": \"DAYOFYEAR\",\n        \"DY\": \"DAYOFYEAR\",\n        \"W\": \"WEEK\",\n        \"WK\": \"WEEK\",\n        \"WEEKOFYEAR\": \"WEEK\",\n        \"WOY\": \"WEEK\",\n        \"WY\": \"WEEK\",\n        \"WEEK_ISO\": \"WEEKISO\",\n        \"WEEKOFYEARISO\": \"WEEKISO\",\n        \"WEEKOFYEAR_ISO\": \"WEEKISO\",\n        \"Q\": \"QUARTER\",\n        \"QTR\": \"QUARTER\",\n        \"QTRS\": \"QUARTER\",\n        \"QUARTERS\": \"QUARTER\",\n        \"H\": \"HOUR\",\n        \"HH\": \"HOUR\",\n        \"HR\": \"HOUR\",\n        \"HOURS\": \"HOUR\",\n        \"HRS\": \"HOUR\",\n        \"M\": \"MINUTE\",\n        \"MI\": \"MINUTE\",\n        \"MIN\": \"MINUTE\",\n        \"MINUTES\": \"MINUTE\",\n        \"MINS\": \"MINUTE\",\n        \"S\": \"SECOND\",\n        \"SEC\": \"SECOND\",\n        \"SECONDS\": \"SECOND\",\n        \"SECS\": \"SECOND\",\n        \"MS\": \"MILLISECOND\",\n        \"MSEC\": \"MILLISECOND\",\n        \"MSECS\": \"MILLISECOND\",\n        \"MSECOND\": \"MILLISECOND\",\n        \"MSECONDS\": \"MILLISECOND\",\n        \"MILLISEC\": \"MILLISECOND\",\n        \"MILLISECS\": \"MILLISECOND\",\n        \"MILLISECON\": \"MILLISECOND\",\n        \"MILLISECONDS\": \"MILLISECOND\",\n        \"US\": \"MICROSECOND\",\n        \"USEC\": \"MICROSECOND\",\n        \"USECS\": \"MICROSECOND\",\n        \"MICROSEC\": \"MICROSECOND\",\n        \"MICROSECS\": \"MICROSECOND\",\n        \"USECOND\": \"MICROSECOND\",\n        \"USECONDS\": \"MICROSECOND\",\n        \"MICROSECONDS\": \"MICROSECOND\",\n        \"NS\": \"NANOSECOND\",\n        \"NSEC\": \"NANOSECOND\",\n        \"NANOSEC\": \"NANOSECOND\",\n        \"NSECOND\": \"NANOSECOND\",\n        \"NSECONDS\": \"NANOSECOND\",\n        \"NANOSECS\": \"NANOSECOND\",\n        \"EPOCH_SECOND\": \"EPOCH\",\n        \"EPOCH_SECONDS\": \"EPOCH\",\n        \"EPOCH_MILLISECONDS\": \"EPOCH_MILLISECOND\",\n        \"EPOCH_MICROSECONDS\": \"EPOCH_MICROSECOND\",\n        \"EPOCH_NANOSECONDS\": \"EPOCH_NANOSECOND\",\n        \"TZH\": \"TIMEZONE_HOUR\",\n        \"TZM\": \"TIMEZONE_MINUTE\",\n        \"DEC\": \"DECADE\",\n        \"DECS\": \"DECADE\",\n        \"DECADES\": \"DECADE\",\n        \"MIL\": \"MILLENNIUM\",\n        \"MILS\": \"MILLENNIUM\",\n        \"MILLENIA\": \"MILLENNIUM\",\n        \"C\": \"CENTURY\",\n        \"CENT\": \"CENTURY\",\n        \"CENTS\": \"CENTURY\",\n        \"CENTURIES\": \"CENTURY\",\n    }\n\n    # Specifies what types a given type can be coerced into\n    COERCES_TO: t.Dict[exp.DType, t.Set[exp.DType]] = {}\n\n    # Specifies type inference & validation rules for expressions\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    # Determines the supported Dialect instance settings\n    SUPPORTED_SETTINGS = {\n        \"normalization_strategy\",\n        \"version\",\n    }\n\n    @classmethod\n    def get_or_raise(cls, dialect: DialectType) -> Dialect:\n        \"\"\"\n        Look up a dialect in the global dialect registry and return it if it exists.\n\n        Args:\n            dialect: The target dialect. If this is a string, it can be optionally followed by\n                additional key-value pairs that are separated by commas and are used to specify\n                dialect settings, such as whether the dialect's identifiers are case-sensitive.\n\n        Example:\n            >>> from sqlglot.dialects.dialect import Dialect\n            >>> dialect = Dialect.get_or_raise(\"duckdb\")\n            >>> dialect = Dialect.get_or_raise(\"mysql, normalization_strategy = case_sensitive\")\n\n        Returns:\n            The corresponding Dialect instance.\n        \"\"\"\n\n        if not dialect:\n            return cls()\n        if isinstance(dialect, _Dialect):\n            return dialect()\n        if isinstance(dialect, Dialect):\n            return dialect\n        if isinstance(dialect, str):\n            try:\n                dialect_name, *kv_strings = dialect.split(\",\")\n                kv_pairs = (kv.split(\"=\") for kv in kv_strings)\n                kwargs = {}\n                for pair in kv_pairs:\n                    key = pair[0].strip()\n                    value: t.Union[bool | str | None] = None\n\n                    if len(pair) == 1:\n                        # Default initialize standalone settings to True\n                        value = True\n                    elif len(pair) == 2:\n                        value = pair[1].strip()\n\n                    kwargs[key] = to_bool(value)\n\n            except ValueError:\n                raise ValueError(\n                    f\"Invalid dialect format: '{dialect}'. \"\n                    \"Please use the correct format: 'dialect [, k1 = v2 [, ...]]'.\"\n                )\n\n            result = cls.get(dialect_name.strip())\n            if not result:\n                # Include both built-in dialects and any loaded dialects for better error messages\n                all_dialects = set(DIALECT_MODULE_NAMES) | set(cls._classes.keys())\n                suggest_closest_match_and_fail(\"dialect\", dialect_name, all_dialects)\n\n            assert result is not None\n            return result(**kwargs)\n\n        raise ValueError(f\"Invalid dialect type for '{dialect}': '{type(dialect)}'.\")\n\n    @classmethod\n    def format_time(cls, expression: t.Optional[str | exp.Expr]) -> t.Optional[exp.Expr]:\n        \"\"\"Converts a time format in this dialect to its equivalent Python `strftime` format.\"\"\"\n        if isinstance(expression, str):\n            return exp.Literal.string(\n                # the time formats are quoted\n                format_time(expression[1:-1], cls.TIME_MAPPING, cls.TIME_TRIE)\n            )\n\n        if expression and expression.is_string:\n            return exp.Literal.string(format_time(expression.this, cls.TIME_MAPPING, cls.TIME_TRIE))\n\n        return expression\n\n    def __init__(self, **kwargs) -> None:\n        parts = str(kwargs.pop(\"version\", sys.maxsize)).split(\".\")\n        parts.extend([\"0\"] * (3 - len(parts)))\n        self.version = tuple(int(p) for p in parts[:3])\n\n        normalization_strategy = kwargs.pop(\"normalization_strategy\", None)\n        if normalization_strategy is None:\n            self.normalization_strategy = self.NORMALIZATION_STRATEGY\n        else:\n            self.normalization_strategy = NormalizationStrategy(normalization_strategy.upper())\n\n        self.settings = kwargs\n\n        for unsupported_setting in kwargs.keys() - self.SUPPORTED_SETTINGS:\n            suggest_closest_match_and_fail(\"setting\", unsupported_setting, self.SUPPORTED_SETTINGS)\n\n    def __eq__(self, other: t.Any) -> bool:\n        # Does not currently take dialect state into account\n        return type(self) == other\n\n    def __hash__(self) -> int:\n        # Does not currently take dialect state into account\n        return hash(type(self))\n\n    def normalize_identifier(self, expression: E) -> E:\n        \"\"\"\n        Transforms an identifier in a way that resembles how it'd be resolved by this dialect.\n\n        For example, an identifier like `FoO` would be resolved as `foo` in Postgres, because it\n        lowercases all unquoted identifiers. On the other hand, Snowflake uppercases them, so\n        it would resolve it as `FOO`. If it was quoted, it'd need to be treated as case-sensitive,\n        and so any normalization would be prohibited in order to avoid \"breaking\" the identifier.\n\n        There are also dialects like Spark, which are case-insensitive even when quotes are\n        present, and dialects like MySQL, whose resolution rules match those employed by the\n        underlying operating system, for example they may always be case-sensitive in Linux.\n\n        Finally, the normalization behavior of some engines can even be controlled through flags,\n        like in Redshift's case, where users can explicitly set enable_case_sensitive_identifier.\n\n        SQLGlot aims to understand and handle all of these different behaviors gracefully, so\n        that it can analyze queries in the optimizer and successfully capture their semantics.\n        \"\"\"\n        if (\n            isinstance(expression, exp.Identifier)\n            and self.normalization_strategy is not NormalizationStrategy.CASE_SENSITIVE\n            and (\n                not expression.quoted\n                or self.normalization_strategy\n                in (\n                    NormalizationStrategy.CASE_INSENSITIVE,\n                    NormalizationStrategy.CASE_INSENSITIVE_UPPERCASE,\n                )\n            )\n        ):\n            normalized = (\n                expression.this.upper()\n                if self.normalization_strategy\n                in (\n                    NormalizationStrategy.UPPERCASE,\n                    NormalizationStrategy.CASE_INSENSITIVE_UPPERCASE,\n                )\n                else expression.this.lower()\n            )\n            expression.set(\"this\", normalized)\n\n        return expression\n\n    def case_sensitive(self, text: str) -> bool:\n        \"\"\"Checks if text contains any case sensitive characters, based on the dialect's rules.\"\"\"\n        if self.normalization_strategy is NormalizationStrategy.CASE_INSENSITIVE:\n            return False\n\n        unsafe = (\n            str.islower\n            if self.normalization_strategy is NormalizationStrategy.UPPERCASE\n            else str.isupper\n        )\n        return any(unsafe(char) for char in text)\n\n    def can_quote(self, identifier: exp.Identifier, identify: str | bool = \"safe\") -> bool:\n        \"\"\"Checks if an identifier can be quoted\n\n        Args:\n            identifier: The identifier to check.\n            identify:\n                `True`: Always returns `True` except for certain cases.\n                `\"safe\"`: Only returns `True` if the identifier is case-insensitive.\n                `\"unsafe\"`: Only returns `True` if the identifier is case-sensitive.\n\n        Returns:\n            Whether the given text can be identified.\n        \"\"\"\n        if identifier.quoted:\n            return True\n        if not identify:\n            return False\n        if isinstance(identifier.parent, exp.Func):\n            return False\n        if identify is True:\n            return True\n\n        is_safe = not self.case_sensitive(identifier.this) and bool(\n            exp.SAFE_IDENTIFIER_RE.match(identifier.this)\n        )\n\n        if identify == \"safe\":\n            return is_safe\n        if identify == \"unsafe\":\n            return not is_safe\n\n        raise ValueError(f\"Unexpected argument for identify: '{identify}'\")\n\n    def quote_identifier(self, expression: E, identify: bool = True) -> E:\n        \"\"\"\n        Adds quotes to a given expression if it is an identifier.\n\n        Args:\n            expression: The expression of interest. If it's not an `Identifier`, this method is a no-op.\n            identify: If set to `False`, the quotes will only be added if the identifier is deemed\n                \"unsafe\", with respect to its characters and this dialect's normalization strategy.\n        \"\"\"\n        if isinstance(expression, exp.Identifier):\n            expression.set(\"quoted\", self.can_quote(expression, identify or \"unsafe\"))\n        return expression\n\n    def to_json_path(self, path: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        if isinstance(path, exp.Literal):\n            path_text = path.name\n            if path.is_number:\n                path_text = f\"[{path_text}]\"\n            try:\n                return parse_json_path(path_text, self)\n            except ParseError as e:\n                if self.STRICT_JSON_PATH_SYNTAX and not path_text.lstrip().startswith(\n                    (\"lax\", \"strict\")\n                ):\n                    logger.warning(f\"Invalid JSON path syntax. {str(e)}\")\n\n        return path\n\n    def parse(self, sql: str, **opts) -> t.List[t.Optional[exp.Expr]]:\n        return self.parser(**opts).parse(self.tokenize(sql), sql)\n\n    def parse_into(\n        self, expression_type: exp.IntoType, sql: str, **opts\n    ) -> t.List[t.Optional[exp.Expr]]:\n        return self.parser(**opts).parse_into(expression_type, self.tokenize(sql), sql)\n\n    def generate(self, expression: exp.Expr, copy: bool = True, **opts) -> str:\n        return self.generator(**opts).generate(expression, copy=copy)\n\n    def transpile(self, sql: str, **opts) -> t.List[str]:\n        return [\n            self.generate(expression, copy=False, **opts) if expression else \"\"\n            for expression in self.parse(sql)\n        ]\n\n    def tokenize(self, sql: str, **opts) -> t.List[Token]:\n        return self.tokenizer(**opts).tokenize(sql)\n\n    def tokenizer(self, **opts) -> Tokenizer:\n        return self.tokenizer_class(**{\"dialect\": self, **opts})\n\n    def jsonpath_tokenizer(self, **opts) -> JSONPathTokenizer:\n        return self.jsonpath_tokenizer_class(**{\"dialect\": self, **opts})\n\n    def parser(self, **opts) -> Parser:\n        return self.parser_class(**{\"dialect\": self, **opts})\n\n    def generator(self, **opts) -> Generator:\n        return self.generator_class(**{\"dialect\": self, **opts})\n\n    def generate_values_aliases(self, expression: exp.Values) -> t.List[exp.Identifier]:\n        return [\n            exp.to_identifier(f\"_col_{i}\")\n            for i, _ in enumerate(expression.expressions[0].expressions)\n        ]\n\n\nDialectType = t.Union[str, Dialect, t.Type[Dialect], None]\n\n\ndef rename_func(name: str) -> t.Callable[[Generator, exp.Expr], str]:\n    return lambda self, expression: self.func(name, *flatten(expression.args.values()))\n\n\ndef bracket_to_element_at_sql(self: Generator, expression: exp.Bracket) -> str:\n    index = seq_get(\n        apply_index_offset(\n            expression.this,\n            expression.expressions,\n            1 - expression.args.get(\"offset\", 0),\n            dialect=self.dialect,\n        ),\n        0,\n    )\n    return self.func(\"ELEMENT_AT\", expression.this, index)\n\n\n@unsupported_args(\"accuracy\")\ndef approx_count_distinct_sql(self: Generator, expression: exp.ApproxDistinct) -> str:\n    return self.func(\"APPROX_COUNT_DISTINCT\", expression.this)\n\n\ndef if_sql(\n    name: str = \"IF\", false_value: t.Optional[exp.Expr | str] = None\n) -> t.Callable[[Generator, exp.If], str]:\n    def _if_sql(self: Generator, expression: exp.If) -> str:\n        return self.func(\n            name,\n            expression.this,\n            expression.args.get(\"true\"),\n            expression.args.get(\"false\") or false_value,\n        )\n\n    return _if_sql\n\n\ndef arrow_json_extract_sql(self: Generator, expression: JSON_EXTRACT_TYPE) -> str:\n    this = expression.this\n    if self.JSON_TYPE_REQUIRED_FOR_EXTRACTION and isinstance(this, exp.Literal) and this.is_string:\n        this.replace(exp.cast(this, exp.DType.JSON))\n\n    return self.binary(expression, \"->\" if isinstance(expression, exp.JSONExtract) else \"->>\")\n\n\ndef inline_array_sql(self: Generator, expression: exp.Expr) -> str:\n    return f\"[{self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)}]\"\n\n\ndef inline_array_unless_query(self: Generator, expression: exp.Expr) -> str:\n    elem = seq_get(expression.expressions, 0)\n    if isinstance(elem, exp.Expr) and elem.find(exp.Query):\n        return self.func(\"ARRAY\", elem)\n    return inline_array_sql(self, expression)\n\n\ndef no_ilike_sql(self: Generator, expression: exp.ILike) -> str:\n    return self.like_sql(\n        exp.Like(\n            this=exp.Lower(this=expression.this), expression=exp.Lower(this=expression.expression)\n        )\n    )\n\n\ndef no_paren_current_date_sql(self: Generator, expression: exp.CurrentDate) -> str:\n    zone = self.sql(expression, \"this\")\n    return f\"CURRENT_DATE AT TIME ZONE {zone}\" if zone else \"CURRENT_DATE\"\n\n\ndef no_recursive_cte_sql(self: Generator, expression: exp.With) -> str:\n    if expression.args.get(\"recursive\"):\n        self.unsupported(\"Recursive CTEs are unsupported\")\n        expression.set(\"recursive\", False)\n    return self.with_sql(expression)\n\n\ndef no_tablesample_sql(self: Generator, expression: exp.TableSample) -> str:\n    self.unsupported(\"TABLESAMPLE unsupported\")\n    return self.sql(expression.this)\n\n\ndef no_pivot_sql(self: Generator, expression: exp.Pivot) -> str:\n    self.unsupported(\"PIVOT unsupported\")\n    return \"\"\n\n\ndef no_trycast_sql(self: Generator, expression: exp.TryCast) -> str:\n    return self.cast_sql(expression)\n\n\ndef no_comment_column_constraint_sql(\n    self: Generator, expression: exp.CommentColumnConstraint\n) -> str:\n    self.unsupported(\"CommentColumnConstraint unsupported\")\n    return \"\"\n\n\ndef no_map_from_entries_sql(self: Generator, expression: exp.MapFromEntries) -> str:\n    self.unsupported(\"MAP_FROM_ENTRIES unsupported\")\n    return \"\"\n\n\ndef property_sql(self: Generator, expression: exp.Property) -> str:\n    return f\"{self.property_name(expression, string_key=True)}={self.sql(expression, 'value')}\"\n\n\ndef strposition_sql(\n    self: Generator,\n    expression: exp.StrPosition,\n    func_name: str = \"STRPOS\",\n    supports_position: bool = False,\n    supports_occurrence: bool = False,\n    use_ansi_position: bool = True,\n) -> str:\n    string = expression.this\n    substr = expression.args.get(\"substr\")\n    position = expression.args.get(\"position\")\n    occurrence = expression.args.get(\"occurrence\")\n    zero = exp.Literal.number(0)\n    one = exp.Literal.number(1)\n\n    if supports_occurrence and occurrence and supports_position and not position:\n        position = one\n\n    transpile_position = position and not supports_position\n    if transpile_position:\n        string = exp.Substring(this=string, start=position)\n\n    if func_name == \"POSITION\" and use_ansi_position:\n        func = exp.Anonymous(this=func_name, expressions=[exp.In(this=substr, field=string)])\n    else:\n        args = [substr, string] if func_name in (\"LOCATE\", \"CHARINDEX\") else [string, substr]\n        if supports_position:\n            args.append(position)\n        if occurrence:\n            if supports_occurrence:\n                args.append(occurrence)\n            else:\n                self.unsupported(f\"{func_name} does not support the occurrence parameter.\")\n        func = exp.Anonymous(this=func_name, expressions=args)\n\n    if transpile_position:\n        func_with_offset = exp.Sub(this=func + position, expression=one)\n        func_wrapped = exp.If(this=func.eq(zero), true=zero, false=func_with_offset)\n        return self.sql(func_wrapped)\n\n    return self.sql(func)\n\n\ndef struct_extract_sql(self: Generator, expression: exp.StructExtract) -> str:\n    return (\n        f\"{self.sql(expression, 'this')}.{self.sql(exp.to_identifier(expression.expression.name))}\"\n    )\n\n\ndef array_append_sql(\n    name: str, swap_params: bool = False\n) -> t.Callable[[Generator, exp.ArrayAppend | exp.ArrayPrepend], str]:\n    \"\"\"\n    Transpile ARRAY_APPEND/ARRAY_PREPEND between dialects with different NULL propagation semantics.\n\n    Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.\n    Others (DuckDB, Postgres) create a new single-element array instead.\n\n    Args:\n        name: Target dialect's function name (e.g., \"ARRAY_APPEND\", \"ARRAY_PREPEND\")\n        swap_params: If True, generate (element, array) order instead of (array, element).\n                     DuckDB LIST_PREPEND and Postgres ARRAY_PREPEND use (element, array).\n\n    Returns:\n        A callable that generates SQL with appropriate NULL handling for the target dialect.\n        Dialects that propagate NULLs need to set `ARRAY_FUNCS_PROPAGATES_NULLS` to True.\n    \"\"\"\n\n    def _array_append_sql(self: Generator, expression: exp.ArrayAppend | exp.ArrayPrepend) -> str:\n        this = expression.this\n        element = expression.expression\n        args = [element, this] if swap_params else [this, element]\n        func_sql = self.func(name, *args)\n\n        source_null_propagation = bool(expression.args.get(\"null_propagation\"))\n        target_null_propagation = self.dialect.ARRAY_FUNCS_PROPAGATES_NULLS\n\n        # No transpilation needed when source and target have matching NULL semantics\n        if source_null_propagation == target_null_propagation:\n            return func_sql\n\n        # Source propagates NULLs, target doesn't: wrap in conditional to return NULL explicitly\n        if source_null_propagation:\n            return self.sql(\n                exp.If(\n                    this=exp.Is(this=this, expression=exp.Null()),\n                    true=exp.Null(),\n                    false=func_sql,\n                )\n            )\n\n        # Source doesn't propagate NULLs, target does: use COALESCE to convert NULL to empty array\n        this = exp.Coalesce(expressions=[this, exp.Array(expressions=[])])\n        args = [element, this] if swap_params else [this, element]\n        return self.func(name, *args)\n\n    return _array_append_sql\n\n\ndef generate_series_sql(\n    func_name: str, exclusive_func_name: t.Optional[str] = None\n) -> t.Callable[[Generator, exp.GenerateSeries], str]:\n    def _generate_series_sql(self: Generator, expression: exp.GenerateSeries) -> str:\n        start = expression.args.get(\"start\")\n        end = expression.args.get(\"end\")\n        step = expression.args.get(\"step\")\n\n        if expression.args.get(\"is_end_exclusive\"):\n            if exclusive_func_name:\n                return self.func(exclusive_func_name, start, end, step)\n            adjusted_end = exp.Sub(this=end, expression=exp.Literal.number(1))\n            return self.func(func_name, start, adjusted_end, step)\n\n        return self.func(func_name, start, end, step)\n\n    return _generate_series_sql\n\n\ndef array_concat_sql(\n    name: str,\n) -> t.Callable[[Generator, exp.ArrayConcat], str]:\n    \"\"\"\n    Transpile ARRAY_CONCAT/ARRAY_CAT between dialects with different NULL propagation semantics.\n\n    Some dialects (Redshift, Snowflake, Spark) return NULL when ANY input array is NULL.\n    Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.\n\n    Args:\n        name: Target dialect's function name (e.g., \"ARRAY_CAT\", \"ARRAY_CONCAT\", \"LIST_CONCAT\")\n\n    Returns:\n        A callable that generates SQL with appropriate NULL handling for the target dialect.\n        Dialects that propagate NULLs need to set `ARRAY_FUNCS_PROPAGATES_NULLS` to True.\n    \"\"\"\n\n    def _build_func_call(self: Generator, func_name: str, args: Sequence[exp.Expr]) -> str:\n        \"\"\"Build ARRAY_CONCAT call from a list of arguments, handling variadic vs binary nesting.\"\"\"\n        if self.ARRAY_CONCAT_IS_VAR_LEN:\n            return self.func(func_name, *args)\n        elif len(args) == 1:\n            # Single arg gets empty array to preserve semantics\n            return self.func(func_name, args[0], exp.Array(expressions=[]))\n        else:\n            # Snowflake/PostgreSQL/Redshift require binary nesting: ARRAY_CAT(a, ARRAY_CAT(b, c))\n            # Build right-deep tree recursively to avoid creating new ArrayConcat expressions\n            result = self.func(func_name, args[-2], args[-1])\n            for arg in reversed(args[:-2]):\n                result = f\"{func_name}({self.sql(arg)}, {result})\"\n            return result\n\n    def _array_concat_sql(self: Generator, expression: exp.ArrayConcat) -> str:\n        this = expression.this\n        exprs = expression.expressions\n        all_args = [this] + exprs\n\n        source_null_propagation = bool(expression.args.get(\"null_propagation\"))\n        target_null_propagation = self.dialect.ARRAY_FUNCS_PROPAGATES_NULLS\n\n        # Skip wrapper when source and target have matching NULL semantics,\n        # or when the first argument is an array literal (which can never be NULL),\n        # or when it's a single-argument call (empty array is added, preserving NULL semantics)\n        if (\n            source_null_propagation == target_null_propagation\n            or isinstance(this, exp.Array)\n            or len(exprs) == 0\n        ):\n            return _build_func_call(self, name, all_args)\n\n        # Case 1: Source propagates NULLs, target doesn't (Snowflake → DuckDB)\n        # Check if ANY argument is NULL and return NULL explicitly\n        if source_null_propagation:\n            # Build OR-chain: a IS NULL OR b IS NULL OR c IS NULL\n            null_checks: t.List[exp.Expr] = [\n                exp.Is(this=arg.copy(), expression=exp.Null()) for arg in all_args\n            ]\n            combined_check: exp.Expr = reduce(\n                lambda a, b: exp.Or(this=a, expression=b), null_checks\n            )\n\n            func_sql = _build_func_call(self, name, all_args)\n\n            return self.sql(\n                exp.If(\n                    this=combined_check,\n                    true=exp.Null(),\n                    false=func_sql,\n                )\n            )\n\n        # Case 2: Source doesn't propagate NULLs, target does (DuckDB → Snowflake)\n        # Wrap ALL arguments in COALESCE to convert NULL → empty array\n        wrapped_args = [\n            exp.Coalesce(expressions=[arg.copy(), exp.Array(expressions=[])]) for arg in all_args\n        ]\n\n        return _build_func_call(self, name, wrapped_args)\n\n    return _array_concat_sql\n\n\ndef var_map_sql(\n    self: Generator, expression: exp.Map | exp.VarMap, map_func_name: str = \"MAP\"\n) -> str:\n    keys = expression.args.get(\"keys\")\n    values = expression.args.get(\"values\")\n\n    if not isinstance(keys, exp.Array) or not isinstance(values, exp.Array):\n        self.unsupported(\"Cannot convert array columns into map.\")\n        return self.func(map_func_name, keys, values)\n\n    args = []\n    for key, value in zip(keys.expressions, values.expressions):\n        args.append(self.sql(key))\n        args.append(self.sql(value))\n\n    return self.func(map_func_name, *args)\n\n\ndef months_between_sql(self: Generator, expression: exp.MonthsBetween) -> str:\n    \"\"\"\n    Transpile MONTHS_BETWEEN to dialects that don't have native support.\n\n    Snowflake's MONTHS_BETWEEN returns whole months + fractional part where:\n    - Fractional part = (DAY(date1) - DAY(date2)) / 31\n    - Special case: If both dates are last day of month, fractional part = 0\n\n    Formula: DATEDIFF('month', date2, date1) + (DAY(date1) - DAY(date2)) / 31.0\n    \"\"\"\n    date1 = expression.this\n    date2 = expression.expression\n\n    # Cast to DATE to ensure consistent behavior\n    date1_cast = exp.cast(date1, exp.DType.DATE, copy=False)\n    date2_cast = exp.cast(date2, exp.DType.DATE, copy=False)\n\n    # Whole months: DATEDIFF('month', date2, date1)\n    whole_months = exp.DateDiff(this=date1_cast, expression=date2_cast, unit=exp.var(\"month\"))\n\n    # Day components\n    day1 = exp.Day(this=date1_cast.copy())\n    day2 = exp.Day(this=date2_cast.copy())\n\n    # Last day of month components\n    last_day_of_month1 = exp.LastDay(this=date1_cast.copy())\n    last_day_of_month2 = exp.LastDay(this=date2_cast.copy())\n\n    day_of_last_day1 = exp.Day(this=last_day_of_month1)\n    day_of_last_day2 = exp.Day(this=last_day_of_month2)\n\n    # Check if both are last day of month\n    last_day1 = exp.EQ(this=day1.copy(), expression=day_of_last_day1)\n    last_day2 = exp.EQ(this=day2.copy(), expression=day_of_last_day2)\n    both_last_day = exp.And(this=last_day1, expression=last_day2)\n\n    # Fractional part: (DAY(date1) - DAY(date2)) / 31.0\n    fractional = exp.Div(\n        this=exp.Paren(this=exp.Sub(this=day1.copy(), expression=day2.copy())),\n        expression=exp.Literal.number(\"31.0\"),\n    )\n\n    # If both are last day of month, fractional = 0, else calculate fractional\n    fractional_with_check = exp.If(\n        this=both_last_day, true=exp.Literal.number(\"0\"), false=fractional\n    )\n\n    # Final result: whole_months + fractional\n    result = exp.Add(this=whole_months, expression=fractional_with_check)\n\n    return self.sql(result)\n\n\ndef build_formatted_time(\n    exp_class: t.Type[E], dialect: str, default: t.Optional[bool | str] = None\n) -> t.Callable[[t.List], E]:\n    \"\"\"Helper used for time expressions.\n\n    Args:\n        exp_class: the expression class to instantiate.\n        dialect: target sql dialect.\n        default: the default format, True being time.\n\n    Returns:\n        A callable that can be used to return the appropriately formatted time expression.\n    \"\"\"\n\n    def _builder(args: t.List):\n        return exp_class(\n            this=seq_get(args, 0),\n            format=Dialect[dialect].format_time(\n                seq_get(args, 1)\n                or (Dialect[dialect].TIME_FORMAT if default is True else default or None)\n            ),\n        )\n\n    return _builder\n\n\ndef time_format(\n    dialect: DialectType = None,\n) -> t.Callable[[Generator, exp.UnixToStr | exp.StrToUnix], t.Optional[str]]:\n    def _time_format(self: Generator, expression: exp.UnixToStr | exp.StrToUnix) -> t.Optional[str]:\n        \"\"\"\n        Returns the time format for a given expression, unless it's equivalent\n        to the default time format of the dialect of interest.\n        \"\"\"\n        time_format = self.format_time(expression)\n        return time_format if time_format != Dialect.get_or_raise(dialect).TIME_FORMAT else None\n\n    return _time_format\n\n\ndef build_date_delta(\n    exp_class: t.Type[E],\n    unit_mapping: t.Optional[t.Dict[str, str]] = None,\n    default_unit: t.Optional[str] = \"DAY\",\n    supports_timezone: bool = False,\n) -> t.Callable[[t.List], E]:\n    def _builder(args: t.List) -> E:\n        unit_based = len(args) >= 3\n        has_timezone = len(args) == 4\n        this = args[2] if unit_based else seq_get(args, 0)\n        unit = None\n        if unit_based or default_unit:\n            unit = args[0] if unit_based else exp.Literal.string(default_unit)\n            unit = exp.var(unit_mapping.get(unit.name.lower(), unit.name)) if unit_mapping else unit\n        expression = exp_class(this=this, expression=seq_get(args, 1), unit=unit)\n        if supports_timezone and has_timezone:\n            expression.set(\"zone\", args[-1])\n        return expression\n\n    return _builder\n\n\ndef build_date_delta_with_interval(\n    expression_class: t.Type[E],\n) -> t.Callable[[t.List], t.Optional[E]]:\n    def _builder(args: t.List) -> t.Optional[E]:\n        if len(args) < 2:\n            return None\n\n        interval = args[1]\n\n        if not isinstance(interval, exp.Interval):\n            raise ParseError(f\"INTERVAL expression expected but got '{interval}'\")\n\n        return expression_class(this=args[0], expression=interval.this, unit=unit_to_str(interval))\n\n    return _builder\n\n\ndef date_trunc_to_time(args: t.List) -> exp.DateTrunc | exp.TimestampTrunc:\n    unit = seq_get(args, 0)\n    this = seq_get(args, 1)\n\n    if isinstance(this, exp.Cast) and this.is_type(\"date\"):\n        return exp.DateTrunc(unit=unit, this=this)\n    return exp.TimestampTrunc(this=this, unit=unit)\n\n\ndef date_add_interval_sql(data_type: str, kind: str) -> t.Callable[[Generator, exp.Expr], str]:\n    def func(self: Generator, expression: exp.Expr) -> str:\n        this = self.sql(expression, \"this\")\n        interval = exp.Interval(this=expression.expression, unit=unit_to_var(expression))\n        return f\"{data_type}_{kind}({this}, {self.sql(interval)})\"\n\n    return func\n\n\ndef timestamptrunc_sql(\n    func: str = \"DATE_TRUNC\", zone: bool = False\n) -> t.Callable[[Generator, exp.TimestampTrunc], str]:\n    def _timestamptrunc_sql(self: Generator, expression: exp.TimestampTrunc) -> str:\n        args = [unit_to_str(expression), expression.this]\n        if zone:\n            args.append(expression.args.get(\"zone\"))\n        return self.func(func, *args)\n\n    return _timestamptrunc_sql\n\n\ndef no_timestamp_sql(self: Generator, expression: exp.Timestamp) -> str:\n    zone = expression.args.get(\"zone\")\n    if not zone:\n        from sqlglot.optimizer.annotate_types import annotate_types\n\n        target_type = annotate_types(expression, dialect=self.dialect).type or exp.DType.TIMESTAMP\n        return self.sql(exp.cast(expression.this, target_type))\n    if zone.name.lower() in TIMEZONES:\n        return self.sql(\n            exp.AtTimeZone(\n                this=exp.cast(expression.this, exp.DType.TIMESTAMP),\n                zone=zone,\n            )\n        )\n    return self.func(\"TIMESTAMP\", expression.this, zone)\n\n\ndef no_time_sql(self: Generator, expression: exp.Time) -> str:\n    # Transpile BQ's TIME(timestamp, zone) to CAST(TIMESTAMPTZ <timestamp> AT TIME ZONE <zone> AS TIME)\n    this = exp.cast(expression.this, exp.DType.TIMESTAMPTZ)\n    expr = exp.cast(exp.AtTimeZone(this=this, zone=expression.args.get(\"zone\")), exp.DType.TIME)\n    return self.sql(expr)\n\n\ndef no_datetime_sql(self: Generator, expression: exp.Datetime) -> str:\n    this = expression.this\n    expr = expression.expression\n\n    if expr.name.lower() in TIMEZONES:\n        # Transpile BQ's DATETIME(timestamp, zone) to CAST(TIMESTAMPTZ <timestamp> AT TIME ZONE <zone> AS TIMESTAMP)\n        this = exp.cast(this, exp.DType.TIMESTAMPTZ)\n        this = exp.cast(exp.AtTimeZone(this=this, zone=expr), exp.DType.TIMESTAMP)\n        return self.sql(this)\n\n    this = exp.cast(this, exp.DType.DATE)\n    expr = exp.cast(expr, exp.DType.TIME)\n\n    return self.sql(exp.cast(exp.Add(this=this, expression=expr), exp.DType.TIMESTAMP))\n\n\ndef left_to_substring_sql(self: Generator, expression: exp.Left) -> str:\n    return self.sql(\n        exp.Substring(\n            this=expression.this, start=exp.Literal.number(1), length=expression.expression\n        )\n    )\n\n\ndef right_to_substring_sql(self: Generator, expression: exp.Left) -> str:\n    return self.sql(\n        exp.Substring(\n            this=expression.this,\n            start=exp.Length(this=expression.this) - exp.paren(expression.expression - 1),\n        )\n    )\n\n\ndef timestrtotime_sql(\n    self: Generator,\n    expression: exp.TimeStrToTime,\n    include_precision: bool = False,\n) -> str:\n    datatype = exp.DataType.build(\n        exp.DType.TIMESTAMPTZ if expression.args.get(\"zone\") else exp.DType.TIMESTAMP\n    )\n\n    if isinstance(expression.this, exp.Literal) and include_precision:\n        precision = subsecond_precision(expression.this.name)\n        if precision > 0:\n            datatype = exp.DataType.build(\n                datatype.this, expressions=[exp.DataTypeParam(this=exp.Literal.number(precision))]\n            )\n\n    return self.sql(exp.cast(expression.this, datatype, dialect=self.dialect))\n\n\ndef datestrtodate_sql(self: Generator, expression: exp.DateStrToDate) -> str:\n    return self.sql(exp.cast(expression.this, exp.DType.DATE))\n\n\n# Used for Presto and Duckdb which use functions that don't support charset, and assume utf-8\ndef encode_decode_sql(\n    self: Generator, expression: exp.Expr, name: str, replace: bool = True\n) -> str:\n    charset = expression.args.get(\"charset\")\n    if charset and charset.name.lower() not in (\"utf-8\", \"utf8\"):\n        self.unsupported(f\"Expected utf-8 character set, got {charset}.\")\n\n    return self.func(name, expression.this, expression.args.get(\"replace\") if replace else None)\n\n\ndef min_or_least(self: Generator, expression: exp.Min) -> str:\n    name = \"LEAST\" if expression.expressions else \"MIN\"\n    return rename_func(name)(self, expression)\n\n\ndef max_or_greatest(self: Generator, expression: exp.Max) -> str:\n    name = \"GREATEST\" if expression.expressions else \"MAX\"\n    return rename_func(name)(self, expression)\n\n\ndef count_if_to_sum(self: Generator, expression: exp.CountIf) -> str:\n    cond = expression.this\n\n    if isinstance(expression.this, exp.Distinct):\n        cond = expression.this.expressions[0]\n        self.unsupported(\"DISTINCT is not supported when converting COUNT_IF to SUM\")\n\n    return self.func(\"sum\", exp.func(\"if\", cond, 1, 0))\n\n\ndef trim_sql(self: Generator, expression: exp.Trim, default_trim_type: str = \"\") -> str:\n    target = self.sql(expression, \"this\")\n    trim_type = self.sql(expression, \"position\") or default_trim_type\n    remove_chars = self.sql(expression, \"expression\")\n    collation = self.sql(expression, \"collation\")\n\n    # Use TRIM/LTRIM/RTRIM syntax if the expression isn't database-specific\n    if not remove_chars:\n        return self.trim_sql(expression)\n\n    trim_type = f\"{trim_type} \" if trim_type else \"\"\n    remove_chars = f\"{remove_chars} \" if remove_chars else \"\"\n    from_part = \"FROM \" if trim_type or remove_chars else \"\"\n    collation = f\" COLLATE {collation}\" if collation else \"\"\n    return f\"TRIM({trim_type}{remove_chars}{from_part}{target}{collation})\"\n\n\ndef str_to_time_sql(self: Generator, expression: exp.Expr) -> str:\n    return self.func(\"STRPTIME\", expression.this, self.format_time(expression))\n\n\ndef concat_to_dpipe_sql(self: Generator, expression: exp.Concat) -> str:\n    return self.sql(reduce(lambda x, y: exp.DPipe(this=x, expression=y), expression.expressions))\n\n\ndef concat_ws_to_dpipe_sql(self: Generator, expression: exp.ConcatWs) -> str:\n    delim, *rest_args = expression.expressions\n    return self.sql(\n        reduce(\n            lambda x, y: exp.DPipe(this=x, expression=exp.DPipe(this=delim, expression=y)),\n            rest_args,\n        )\n    )\n\n\n@unsupported_args(\"position\", \"occurrence\", \"parameters\")\ndef regexp_extract_sql(\n    self: Generator, expression: exp.RegexpExtract | exp.RegexpExtractAll\n) -> str:\n    group = expression.args.get(\"group\")\n\n    # Do not render group if it's the default value for this dialect\n    if group and group.name == str(self.dialect.REGEXP_EXTRACT_DEFAULT_GROUP):\n        group = None\n\n    return self.func(expression.sql_name(), expression.this, expression.expression, group)\n\n\n@unsupported_args(\"position\", \"occurrence\", \"modifiers\")\ndef regexp_replace_sql(self: Generator, expression: exp.RegexpReplace) -> str:\n    return self.func(\n        \"REGEXP_REPLACE\", expression.this, expression.expression, expression.args[\"replacement\"]\n    )\n\n\ndef pivot_column_names(aggregations: t.List[exp.Expr], dialect: DialectType) -> t.List[str]:\n    names = []\n    for agg in aggregations:\n        if isinstance(agg, exp.Alias):\n            names.append(agg.alias)\n        else:\n            \"\"\"\n            This case corresponds to aggregations without aliases being used as suffixes\n            (e.g. col_avg(foo)). We need to unquote identifiers because they're going to\n            be quoted in the base parser's `_parse_pivot` method, due to `to_identifier`.\n            Otherwise, we'd end up with `col_avg(`foo`)` (notice the double quotes).\n            \"\"\"\n            agg_all_unquoted = agg.transform(\n                lambda node: (\n                    exp.Identifier(this=node.name, quoted=False)\n                    if isinstance(node, exp.Identifier)\n                    else node\n                )\n            )\n            names.append(agg_all_unquoted.sql(dialect=dialect, normalize_functions=\"lower\"))\n\n    return names\n\n\ndef binary_from_function(expr_type: t.Type[B]) -> t.Callable[[t.List], B]:\n    return lambda args: expr_type(this=seq_get(args, 0), expression=seq_get(args, 1))\n\n\n# Used to represent DATE_TRUNC in Doris, Postgres and Starrocks dialects\ndef build_timestamp_trunc(args: t.List) -> exp.TimestampTrunc:\n    return exp.TimestampTrunc(this=seq_get(args, 1), unit=seq_get(args, 0))\n\n\ndef build_trunc(\n    args: t.List,\n    dialect: DialectType,\n    date_trunc_unabbreviate: bool = True,\n    default_date_trunc_unit: t.Optional[str] = None,\n    date_trunc_requires_part: bool = True,\n) -> exp.DateTrunc | exp.Trunc | exp.Anonymous:\n    \"\"\"\n    Builder for dialects with overloaded TRUNC (Oracle, Snowflake, etc).\n    Uses type annotation to distinguish date vs numeric truncation.\n    Returns Anonymous if type cannot be determined.\n    \"\"\"\n    from sqlglot.optimizer.annotate_types import annotate_types\n\n    this = seq_get(args, 0)\n    second = seq_get(args, 1)\n\n    if this and not this.type:\n        this = annotate_types(this, dialect=dialect)\n    if second and not second.type:\n        second = annotate_types(second, dialect=dialect)\n\n    # Date truncation\n    if (\n        this and this.is_type(*exp.DataType.TEMPORAL_TYPES) and (second or default_date_trunc_unit)\n    ) or (second and second.is_type(*exp.DataType.TEXT_TYPES)):\n        unit = second or exp.Literal.string(default_date_trunc_unit)\n        return exp.DateTrunc(this=this, unit=unit, unabbreviate=date_trunc_unabbreviate)\n\n    # Numeric truncation\n    if (\n        (this and this.is_type(*exp.DataType.NUMERIC_TYPES))\n        or (second and second.is_type(*exp.DataType.NUMERIC_TYPES))\n        or (not date_trunc_requires_part and not second)\n    ):\n        return exp.Trunc(this=this, decimals=second)\n\n    return exp.Anonymous(this=\"TRUNC\", expressions=args)\n\n\ndef any_value_to_max_sql(self: Generator, expression: exp.AnyValue) -> str:\n    return self.func(\"MAX\", expression.this)\n\n\ndef bool_xor_sql(self: Generator, expression: exp.Xor) -> str:\n    a = self.sql(expression.left)\n    b = self.sql(expression.right)\n    return f\"({a} AND (NOT {b})) OR ((NOT {a}) AND {b})\"\n\n\ndef is_parse_json(expression: exp.Expr) -> bool:\n    return isinstance(expression, exp.ParseJSON) or (\n        isinstance(expression, exp.Cast) and expression.is_type(\"json\")\n    )\n\n\ndef isnull_to_is_null(args: t.List) -> exp.Expr:\n    return exp.Paren(this=exp.Is(this=seq_get(args, 0), expression=exp.null()))\n\n\ndef generatedasidentitycolumnconstraint_sql(\n    self: Generator, expression: exp.GeneratedAsIdentityColumnConstraint\n) -> str:\n    start = self.sql(expression, \"start\") or \"1\"\n    increment = self.sql(expression, \"increment\") or \"1\"\n    return f\"IDENTITY({start}, {increment})\"\n\n\ndef arg_max_or_min_no_count(name: str) -> t.Callable[[Generator, exp.ArgMax | exp.ArgMin], str]:\n    @unsupported_args(\"count\")\n    def _arg_max_or_min_sql(self: Generator, expression: exp.ArgMax | exp.ArgMin) -> str:\n        return self.func(name, expression.this, expression.expression)\n\n    return _arg_max_or_min_sql\n\n\ndef ts_or_ds_add_cast(expression: exp.TsOrDsAdd) -> exp.TsOrDsAdd:\n    this = expression.this.copy()\n\n    return_type = expression.return_type\n    if return_type.is_type(exp.DType.DATE):\n        # If we need to cast to a DATE, we cast to TIMESTAMP first to make sure we\n        # can truncate timestamp strings, because some dialects can't cast them to DATE\n        this = exp.cast(this, exp.DType.TIMESTAMP)\n\n    expression.this.replace(exp.cast(this, return_type))\n    return expression\n\n\ndef date_delta_sql(name: str, cast: bool = False) -> t.Callable[[Generator, DATE_ADD_OR_DIFF], str]:\n    def _delta_sql(self: Generator, expression: DATE_ADD_OR_DIFF) -> str:\n        if cast and isinstance(expression, exp.TsOrDsAdd):\n            expression = ts_or_ds_add_cast(expression)\n\n        return self.func(\n            name,\n            unit_to_var(expression),\n            expression.expression,\n            expression.this,\n        )\n\n    return _delta_sql\n\n\ndef date_delta_to_binary_interval_op(\n    cast: bool = True,\n) -> t.Callable[[Generator, DATETIME_DELTA], str]:\n    def date_delta_to_binary_interval_op_sql(self: Generator, expression: DATETIME_DELTA) -> str:\n        this = expression.this\n        unit = unit_to_var(expression)\n        op = \"+\" if isinstance(expression, DATETIME_ADD) else \"-\"\n\n        to_type: t.Optional[exp.DATA_TYPE] = None\n        if cast:\n            if isinstance(expression, exp.TsOrDsAdd):\n                to_type = expression.return_type\n            elif this.is_string:\n                # Cast string literals (i.e function parameters) to the appropriate type for +/- interval to work\n                to_type = (\n                    exp.DType.DATETIME\n                    if isinstance(expression, (exp.DatetimeAdd, exp.DatetimeSub))\n                    else exp.DType.DATE\n                )\n\n        this = exp.cast(this, to_type) if to_type else this\n\n        expr = expression.expression\n        interval = expr if isinstance(expr, exp.Interval) else exp.Interval(this=expr, unit=unit)\n\n        return f\"{self.sql(this)} {op} {self.sql(interval)}\"\n\n    return date_delta_to_binary_interval_op_sql\n\n\ndef unit_to_str(expression: exp.Expr, default: str = \"DAY\") -> t.Optional[exp.Expr]:\n    unit = expression.args.get(\"unit\")\n    if not unit:\n        return exp.Literal.string(default) if default else None\n\n    if isinstance(unit, exp.Placeholder) or type(unit) not in (exp.Var, exp.Literal):\n        return unit\n\n    return exp.Literal.string(unit.name)\n\n\ndef unit_to_var(expression: exp.Expr, default: str = \"DAY\") -> t.Optional[exp.Expr]:\n    unit = expression.args.get(\"unit\")\n\n    if isinstance(unit, (exp.Var, exp.Placeholder, exp.WeekStart, exp.Column)):\n        return unit\n\n    value = unit.name if unit else default\n    return exp.Var(this=value) if value else None\n\n\n@t.overload\ndef map_date_part(part: exp.Expr, dialect: DialectType = Dialect) -> exp.Expr:\n    pass\n\n\n@t.overload\ndef map_date_part(\n    part: t.Optional[exp.Expr], dialect: DialectType = Dialect\n) -> t.Optional[exp.Expr]:\n    pass\n\n\ndef map_date_part(part, dialect: DialectType = Dialect):\n    mapped = (\n        Dialect.get_or_raise(dialect).DATE_PART_MAPPING.get(part.name.upper())\n        if part and not (isinstance(part, exp.Column) and len(part.parts) != 1)\n        else None\n    )\n    if mapped:\n        return exp.Literal.string(mapped) if part.is_string else exp.var(mapped)\n\n    return part\n\n\ndef no_last_day_sql(self: Generator, expression: exp.LastDay) -> str:\n    trunc_curr_date = exp.func(\"date_trunc\", \"month\", expression.this)\n    plus_one_month = exp.func(\"date_add\", trunc_curr_date, 1, \"month\")\n    minus_one_day = exp.func(\"date_sub\", plus_one_month, 1, \"day\")\n\n    return self.sql(exp.cast(minus_one_day, exp.DType.DATE))\n\n\ndef merge_without_target_sql(self: Generator, expression: exp.Merge) -> str:\n    \"\"\"Remove table refs from columns in when statements.\"\"\"\n    alias = expression.this.args.get(\"alias\")\n\n    def normalize(identifier: t.Optional[exp.Identifier]) -> t.Optional[str]:\n        return self.dialect.normalize_identifier(identifier).name if identifier else None\n\n    targets = {normalize(expression.this.this)}\n\n    if alias:\n        targets.add(normalize(alias.this))\n\n    for when in expression.args[\"whens\"].expressions:\n        # only remove the target table names from certain parts of WHEN MATCHED / WHEN NOT MATCHED\n        # they are still valid in the <condition>, the right hand side of each UPDATE and the VALUES part\n        # (not the column list) of the INSERT\n        then: exp.Insert | exp.Update | None = when.args.get(\"then\")\n        if then:\n            if isinstance(then, exp.Update):\n                for equals in then.find_all(exp.EQ):\n                    equal_lhs = equals.this\n                    if (\n                        isinstance(equal_lhs, exp.Column)\n                        and normalize(equal_lhs.args.get(\"table\")) in targets\n                    ):\n                        equal_lhs.replace(exp.column(equal_lhs.this))\n            if isinstance(then, exp.Insert):\n                column_list = then.this\n                if isinstance(column_list, exp.Tuple):\n                    for column in column_list.expressions:\n                        if normalize(column.args.get(\"table\")) in targets:\n                            column.replace(exp.column(column.this))\n\n    return self.merge_sql(expression)\n\n\ndef build_json_extract_path(\n    expr_type: t.Type[F],\n    zero_based_indexing: bool = True,\n    arrow_req_json_type: bool = False,\n    json_type: t.Optional[str] = None,\n) -> t.Callable[[t.List], F]:\n    def _builder(args: t.List) -> F:\n        segments: t.List[exp.JSONPathPart] = [exp.JSONPathRoot()]\n        for arg in args[1:]:\n            if not isinstance(arg, exp.Literal):\n                # We use the fallback parser because we can't really transpile non-literals safely\n                return expr_type.from_arg_list(args)\n\n            text = arg.name\n            if is_int(text) and (not arrow_req_json_type or not arg.is_string):\n                index = int(text)\n                segments.append(\n                    exp.JSONPathSubscript(this=index if zero_based_indexing else index - 1)\n                )\n            else:\n                segments.append(exp.JSONPathKey(this=text))\n\n        # This is done to avoid failing in the expression validator due to the arg count\n        del args[2:]\n        kwargs = {\n            \"this\": seq_get(args, 0),\n            \"expression\": exp.JSONPath(expressions=segments),\n        }\n\n        is_jsonb = issubclass(expr_type, (exp.JSONBExtract, exp.JSONBExtractScalar))\n        if not is_jsonb:\n            kwargs[\"only_json_types\"] = arrow_req_json_type\n\n        if json_type is not None:\n            kwargs[\"json_type\"] = json_type\n\n        return expr_type(**kwargs)\n\n    return _builder\n\n\ndef json_extract_segments(\n    name: str, quoted_index: bool = True, op: t.Optional[str] = None\n) -> t.Callable[[Generator, JSON_EXTRACT_TYPE], str]:\n    def _json_extract_segments(self: Generator, expression: JSON_EXTRACT_TYPE) -> str:\n        path = expression.expression\n        if not isinstance(path, exp.JSONPath):\n            return rename_func(name)(self, expression)\n\n        escape = path.args.get(\"escape\")\n\n        segments = []\n        for segment in path.expressions:\n            path = self.sql(segment)\n            if path:\n                if isinstance(segment, exp.JSONPathPart) and (\n                    quoted_index or not isinstance(segment, exp.JSONPathSubscript)\n                ):\n                    if escape:\n                        path = self.escape_str(path)\n\n                    path = f\"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}\"\n\n                segments.append(path)\n\n        if op:\n            return f\" {op} \".join([self.sql(expression.this), *segments])\n        return self.func(name, expression.this, *segments)\n\n    return _json_extract_segments\n\n\ndef json_path_key_only_name(self: Generator, expression: exp.JSONPathKey) -> str:\n    if isinstance(expression.this, exp.JSONPathWildcard):\n        self.unsupported(\"Unsupported wildcard in JSONPathKey expression\")\n\n    return expression.name\n\n\ndef filter_array_using_unnest(\n    self: Generator, expression: exp.ArrayFilter | exp.ArrayRemove\n) -> str:\n    cond = expression.expression\n    if isinstance(cond, exp.Lambda) and len(cond.expressions) == 1:\n        alias = cond.expressions[0]\n        cond = cond.this\n    elif isinstance(cond, exp.Predicate):\n        alias = \"_u\"\n    elif isinstance(expression, exp.ArrayRemove):\n        alias = \"_u\"\n        cond = exp.NEQ(this=alias, expression=expression.expression)\n    else:\n        self.unsupported(\"Unsupported filter condition\")\n        return \"\"\n\n    unnest = exp.Unnest(expressions=[expression.this])\n    filtered = exp.select(alias).from_(exp.alias_(unnest, None, table=[alias])).where(cond)\n    return self.sql(exp.Array(expressions=[filtered]))\n\n\ndef array_compact_sql(self: Generator, expression: exp.ArrayCompact) -> str:\n    lambda_id = exp.to_identifier(\"_u\")\n    cond = exp.Is(this=lambda_id, expression=exp.null()).not_()\n    return self.sql(\n        exp.ArrayFilter(\n            this=expression.this,\n            expression=exp.Lambda(this=cond, expressions=[lambda_id]),\n        )\n    )\n\n\ndef remove_from_array_using_filter(self: Generator, expression: exp.ArrayRemove) -> str:\n    lambda_id = exp.to_identifier(\"_u\")\n    cond = exp.NEQ(this=lambda_id, expression=expression.expression)\n\n    filter_sql = self.sql(\n        exp.ArrayFilter(\n            this=expression.this,\n            expression=exp.Lambda(this=cond, expressions=[lambda_id]),\n        )\n    )\n\n    # Handle NULL propagation for ArrayRemove\n    source_null_propagation = bool(expression.args.get(\"null_propagation\"))\n    target_null_propagation = self.dialect.ARRAY_FUNCS_PROPAGATES_NULLS\n\n    # Source propagates NULLs (Snowflake), target doesn't (DuckDB):\n    # When removal value is NULL, return NULL instead of applying filter\n    if source_null_propagation and not target_null_propagation:\n        removal_value = expression.expression\n\n        # Optimization: skip wrapper if removal value is a non-NULL literal\n        # (e.g., 5, 'a', TRUE) or an array literal (e.g., [1, 2])\n        if (\n            isinstance(removal_value, exp.Literal) and not isinstance(removal_value, exp.Null)\n        ) or isinstance(removal_value, exp.Array):\n            return filter_sql\n\n        return self.sql(\n            exp.If(\n                this=exp.Is(this=removal_value, expression=exp.Null()),\n                true=exp.Null(),\n                false=filter_sql,\n            )\n        )\n\n    return filter_sql\n\n\ndef to_number_with_nls_param(self: Generator, expression: exp.ToNumber) -> str:\n    return self.func(\n        \"TO_NUMBER\",\n        expression.this,\n        expression.args.get(\"format\"),\n        expression.args.get(\"nlsparam\"),\n    )\n\n\ndef build_default_decimal_type(\n    precision: t.Optional[int] = None, scale: t.Optional[int] = None\n) -> t.Callable[[exp.DataType], exp.DataType]:\n    def _builder(dtype: exp.DataType) -> exp.DataType:\n        if dtype.expressions or precision is None:\n            return dtype\n\n        params = f\"{precision}{f', {scale}' if scale is not None else ''}\"\n        return exp.DataType.build(f\"DECIMAL({params})\")\n\n    return _builder\n\n\ndef build_timestamp_from_parts(args: t.List) -> exp.Func:\n    if len(args) == 2:\n        # Other dialects don't have the TIMESTAMP_FROM_PARTS(date, time) concept,\n        # so we parse this into Anonymous for now instead of introducing complexity\n        return exp.Anonymous(this=\"TIMESTAMP_FROM_PARTS\", expressions=args)\n\n    return exp.TimestampFromParts.from_arg_list(args)\n\n\ndef sha256_sql(self: Generator, expression: exp.SHA2) -> str:\n    return self.func(f\"SHA{expression.text('length') or '256'}\", expression.this)\n\n\ndef sha2_digest_sql(self: Generator, expression: exp.SHA2Digest) -> str:\n    return self.func(f\"SHA{expression.text('length') or '256'}\", expression.this)\n\n\ndef sequence_sql(self: Generator, expression: exp.GenerateSeries | exp.GenerateDateArray) -> str:\n    start = expression.args.get(\"start\")\n    end = expression.args.get(\"end\")\n    step = expression.args.get(\"step\")\n\n    if isinstance(start, exp.Cast):\n        target_type = start.to\n    elif isinstance(end, exp.Cast):\n        target_type = end.to\n    else:\n        target_type = None\n\n    if start and end:\n        if target_type and target_type.is_type(\"date\", \"timestamp\"):\n            if isinstance(start, exp.Cast) and target_type is start.to:\n                end = exp.cast(end, target_type)\n            else:\n                start = exp.cast(start, target_type)\n\n        if expression.args.get(\"is_end_exclusive\"):\n            step_value = step or exp.Literal.number(1)\n            end = exp.paren(exp.Sub(this=end, expression=step_value), copy=False)\n\n            sequence_call = exp.Anonymous(\n                this=\"SEQUENCE\", expressions=[e for e in (start, end, step) if e]\n            )\n            zero = exp.Literal.number(0)\n            should_return_empty = exp.or_(\n                exp.EQ(this=step_value.copy(), expression=zero.copy()),\n                exp.and_(\n                    exp.GT(this=step_value.copy(), expression=zero.copy()),\n                    exp.GT(this=start.copy(), expression=end.copy()),\n                ),\n                exp.and_(\n                    exp.LT(this=step_value.copy(), expression=zero.copy()),\n                    exp.LT(this=start.copy(), expression=end.copy()),\n                ),\n            )\n            empty_array_or_sequence = exp.If(\n                this=should_return_empty,\n                true=exp.Array(expressions=[]),\n                false=sequence_call,\n            )\n            return self.sql(self._simplify_unless_literal(empty_array_or_sequence))\n\n    return self.func(\"SEQUENCE\", start, end, step)\n\n\ndef build_like(expr_type: t.Type[E], not_like: bool = False) -> t.Callable[[t.List], exp.Expr]:\n    def _builder(args: t.List) -> exp.Expr:\n        like_expr: exp.Expr = expr_type(this=seq_get(args, 0), expression=seq_get(args, 1))\n\n        if escape := seq_get(args, 2):\n            like_expr = exp.Escape(this=like_expr, expression=escape)\n\n        if not_like:\n            like_expr = exp.Not(this=like_expr)\n\n        return like_expr\n\n    return _builder\n\n\ndef build_regexp_extract(expr_type: t.Type[E]) -> t.Callable[[t.List, Dialect], E]:\n    def _builder(args: t.List, dialect: Dialect) -> E:\n        # The \"position\" argument specifies the index of the string character to start matching from.\n        # `null_if_pos_overflow` reflects the dialect's behavior when position is greater than the string\n        # length. If true, returns NULL. If false, returns an empty string. `null_if_pos_overflow` is\n        # only needed for exp.RegexpExtract - exp.RegexpExtractAll always returns an empty array if\n        # position overflows.\n        return expr_type(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            group=seq_get(args, 2) or exp.Literal.number(dialect.REGEXP_EXTRACT_DEFAULT_GROUP),\n            parameters=seq_get(args, 3),\n            **(\n                {\"null_if_pos_overflow\": dialect.REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL}\n                if expr_type is exp.RegexpExtract\n                else {}\n            ),\n        )\n\n    return _builder\n\n\ndef explode_to_unnest_sql(self: Generator, expression: exp.Lateral) -> str:\n    this = expression.this\n    alias = expression.args.get(\"alias\")\n\n    cross_join_expr: t.Optional[exp.Expr] = None\n    if isinstance(this, exp.Posexplode) and alias:\n        # Spark's `FROM x LATERAL VIEW POSEXPLODE(y) t AS pos, col` has the following semantics:\n        # - The first column is the position and the rest (1 for array, 2 for maps) are the exploded values\n        # - The position is 0-based whereas WITH ORDINALITY is 1-based\n        # For that matter, we must (1) subtract 1 from the ORDINALITY position and (2) rearrange the columns accordingly, returning:\n        # `FROM x CROSS JOIN LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))\n        pos, cols = alias.columns[0], alias.columns[1:]\n\n        cols = ensure_list(cols)\n        lateral_subquery = exp.select(\n            exp.alias_(pos - 1, pos),\n            *cols,\n        ).from_(\n            exp.Unnest(\n                expressions=[this.this],\n                offset=True,\n                alias=exp.TableAlias(this=alias.this, columns=[*cols, pos]),\n            ),\n        )\n\n        cross_join_expr = exp.Lateral(this=lateral_subquery.subquery())\n    elif isinstance(this, exp.Explode):\n        cross_join_expr = exp.Unnest(\n            expressions=[this.this],\n            alias=alias,\n        )\n\n    if cross_join_expr:\n        return self.sql(exp.Join(this=cross_join_expr, kind=\"cross\"))\n\n    return self.lateral_sql(expression)\n\n\ndef timestampdiff_sql(self: Generator, expression: exp.DatetimeDiff | exp.TimestampDiff) -> str:\n    return self.func(\"TIMESTAMPDIFF\", expression.unit, expression.expression, expression.this)\n\n\ndef no_make_interval_sql(self: Generator, expression: exp.MakeInterval, sep: str = \", \") -> str:\n    args = []\n    for unit, value in expression.args.items():\n        if isinstance(value, exp.Kwarg):\n            value = value.expression\n\n        args.append(f\"{value} {unit}\")\n\n    return f\"INTERVAL '{self.format_args(*args, sep=sep)}'\"\n\n\ndef length_or_char_length_sql(self: Generator, expression: exp.Length) -> str:\n    length_func = \"LENGTH\" if expression.args.get(\"binary\") else \"CHAR_LENGTH\"\n    return self.func(length_func, expression.this)\n\n\ndef groupconcat_sql(\n    self: Generator,\n    expression: exp.GroupConcat,\n    func_name=\"LISTAGG\",\n    sep: t.Optional[str] = \",\",\n    within_group: bool = True,\n    on_overflow: bool = False,\n) -> str:\n    this = expression.this\n    separator = self.sql(\n        expression.args.get(\"separator\") or (exp.Literal.string(sep) if sep else None)\n    )\n\n    on_overflow_sql = self.sql(expression, \"on_overflow\")\n    on_overflow_sql = f\" ON OVERFLOW {on_overflow_sql}\" if (on_overflow and on_overflow_sql) else \"\"\n\n    if isinstance(this, exp.Limit) and this.this:\n        limit = this\n        this = limit.this.pop()\n    else:\n        limit = None\n\n    order = this.find(exp.Order)\n\n    if order and order.this:\n        this = order.this.pop()\n\n    args = self.format_args(\n        this, f\"{separator}{on_overflow_sql}\" if separator or on_overflow_sql else None\n    )\n\n    listagg: exp.Expr = exp.Anonymous(this=func_name, expressions=[args])\n\n    modifiers = self.sql(limit)\n\n    if order:\n        if within_group:\n            listagg = exp.WithinGroup(this=listagg, expression=order)\n        else:\n            modifiers = f\"{self.sql(order)}{modifiers}\"\n\n    if modifiers:\n        listagg.set(\"expressions\", [f\"{args}{modifiers}\"])\n\n    return self.sql(listagg)\n\n\ndef build_timetostr_or_tochar(args: t.List, dialect: DialectType) -> exp.TimeToStr | exp.ToChar:\n    if len(args) == 2:\n        this = args[0]\n        if not this.type:\n            from sqlglot.optimizer.annotate_types import annotate_types\n\n            annotate_types(this, dialect=dialect)\n\n        if this.is_type(*exp.DataType.TEMPORAL_TYPES):\n            dialect_name = dialect.__class__.__name__.lower()\n            return build_formatted_time(exp.TimeToStr, dialect_name, default=True)(args)\n\n    return exp.ToChar.from_arg_list(args)\n\n\ndef build_replace_with_optional_replacement(args: t.List) -> exp.Replace:\n    return exp.Replace(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        replacement=seq_get(args, 2) or exp.Literal.string(\"\"),\n    )\n\n\ndef regexp_replace_global_modifier(expression: exp.RegexpReplace) -> exp.Expr | None:\n    modifiers = expression.args.get(\"modifiers\")\n    single_replace = expression.args.get(\"single_replace\")\n    occurrence = expression.args.get(\"occurrence\")\n\n    if not single_replace and (not occurrence or (occurrence.is_int and occurrence.to_py() == 0)):\n        if not modifiers or modifiers.is_string:\n            # Append 'g' to the modifiers if they are not provided since\n            # the semantics of REGEXP_REPLACE from the input dialect\n            # is to replace all occurrences of the pattern.\n            value = \"\" if not modifiers else modifiers.name\n            modifiers = exp.Literal.string(value + \"g\")\n\n    return modifiers\n\n\ndef getbit_sql(self: Generator, expression: exp.Getbit) -> str:\n    \"\"\"\n    Generates SQL for Getbit according to DuckDB and Postgres, transpiling it if either:\n\n    1. The zero index corresponds to the least-significant bit\n    2. The input type is an integer value\n    \"\"\"\n    value = expression.this\n    position = expression.expression\n\n    if not expression.args.get(\"zero_is_msb\") and expression.is_type(\n        *exp.DataType.SIGNED_INTEGER_TYPES, *exp.DataType.UNSIGNED_INTEGER_TYPES\n    ):\n        # Use bitwise operations: (value >> position) & 1\n        shifted = exp.BitwiseRightShift(this=value, expression=position)\n        masked = exp.BitwiseAnd(this=shifted, expression=exp.Literal.number(1))\n        return self.sql(masked)\n\n    return self.func(\"GET_BIT\", value, position)\n\n\ndef jarowinkler_similarity(func: str) -> t.Callable[[Generator, exp.JarowinklerSimilarity], str]:\n    def jarowinklersimilarity_sql(self: Generator, expression: exp.JarowinklerSimilarity) -> str:\n        this = expression.this\n        expr = expression.expression\n        if expression.args.get(\"case_insensitive\"):\n            this = exp.Upper(this=this)\n            expr = exp.Upper(this=expr)\n\n        return self.func(func, this, expr)\n\n    return jarowinklersimilarity_sql\n"
  },
  {
    "path": "sqlglot/dialects/doris.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import (\n    approx_count_distinct_sql,\n    property_sql,\n    rename_func,\n    time_format,\n    unit_to_str,\n)\nfrom sqlglot.dialects.mysql import MySQL\nfrom sqlglot.parsers.doris import DorisParser\n\n\ndef _lag_lead_sql(self, expression: exp.Lag | exp.Lead) -> str:\n    return self.func(\n        \"LAG\" if isinstance(expression, exp.Lag) else \"LEAD\",\n        expression.this,\n        expression.args.get(\"offset\") or exp.Literal.number(1),\n        expression.args.get(\"default\") or exp.null(),\n    )\n\n\nclass Doris(MySQL):\n    DATE_FORMAT = \"'yyyy-MM-dd'\"\n    DATEINT_FORMAT = \"'yyyyMMdd'\"\n    TIME_FORMAT = \"'yyyy-MM-dd HH:mm:ss'\"\n\n    Parser = DorisParser\n\n    class Generator(MySQL.Generator):\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        VARCHAR_REQUIRES_SIZE = False\n        WITH_PROPERTIES_PREFIX = \"PROPERTIES\"\n        RENAME_TABLE_WITH_DB = False\n        UPDATE_STATEMENT_SUPPORTS_FROM = True\n\n        TYPE_MAPPING = {\n            **MySQL.Generator.TYPE_MAPPING,\n            exp.DType.TEXT: \"STRING\",\n            exp.DType.TIMESTAMP: \"DATETIME\",\n            exp.DType.TIMESTAMPTZ: \"DATETIME\",\n        }\n\n        PROPERTIES_LOCATION = {\n            **MySQL.Generator.PROPERTIES_LOCATION,\n            exp.UniqueKeyProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.BuildProperty: exp.Properties.Location.POST_SCHEMA,\n        }\n\n        CAST_MAPPING = {}\n        TIMESTAMP_FUNC_TYPES = set()\n\n        TRANSFORMS = {\n            **MySQL.Generator.TRANSFORMS,\n            exp.AddMonths: rename_func(\"MONTHS_ADD\"),\n            exp.ApproxDistinct: approx_count_distinct_sql,\n            exp.ArgMax: rename_func(\"MAX_BY\"),\n            exp.ArgMin: rename_func(\"MIN_BY\"),\n            exp.ArrayAgg: rename_func(\"COLLECT_LIST\"),\n            exp.ArrayToString: rename_func(\"ARRAY_JOIN\"),\n            exp.ArrayUniqueAgg: rename_func(\"COLLECT_SET\"),\n            exp.CurrentDate: lambda self, _: self.func(\"CURRENT_DATE\"),\n            exp.CurrentTimestamp: lambda self, _: self.func(\"NOW\"),\n            exp.DateTrunc: lambda self, e: self.func(\"DATE_TRUNC\", e.this, unit_to_str(e)),\n            exp.EuclideanDistance: rename_func(\"L2_DISTANCE\"),\n            exp.GroupConcat: lambda self, e: self.func(\n                \"GROUP_CONCAT\", e.this, e.args.get(\"separator\") or exp.Literal.string(\",\")\n            ),\n            exp.JSONExtractScalar: lambda self, e: self.func(\"JSON_EXTRACT\", e.this, e.expression),\n            exp.Lag: _lag_lead_sql,\n            exp.Lead: _lag_lead_sql,\n            exp.Map: rename_func(\"ARRAY_MAP\"),\n            exp.Property: property_sql,\n            exp.RegexpLike: rename_func(\"REGEXP\"),\n            exp.RegexpSplit: rename_func(\"SPLIT_BY_STRING\"),\n            exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),\n            exp.Split: rename_func(\"SPLIT_BY_STRING\"),\n            exp.StringToArray: rename_func(\"SPLIT_BY_STRING\"),\n            exp.StrToUnix: lambda self, e: self.func(\"UNIX_TIMESTAMP\", e.this, self.format_time(e)),\n            exp.TimeStrToDate: rename_func(\"TO_DATE\"),\n            exp.TsOrDsAdd: lambda self, e: self.func(\"DATE_ADD\", e.this, e.expression),\n            exp.TsOrDsToDate: lambda self, e: self.func(\"TO_DATE\", e.this),\n            exp.TimeToUnix: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.TimestampTrunc: lambda self, e: self.func(\"DATE_TRUNC\", e.this, unit_to_str(e)),\n            exp.UnixToStr: lambda self, e: self.func(\n                \"FROM_UNIXTIME\", e.this, time_format(\"doris\")(self, e)\n            ),\n            exp.UnixToTime: rename_func(\"FROM_UNIXTIME\"),\n        }\n\n        # https://github.com/apache/doris/blob/e4f41dbf1ec03f5937fdeba2ee1454a20254015b/fe/fe-core/src/main/antlr4/org/apache/doris/nereids/DorisLexer.g4#L93\n        RESERVED_KEYWORDS = {\n            \"account_lock\",\n            \"account_unlock\",\n            \"add\",\n            \"adddate\",\n            \"admin\",\n            \"after\",\n            \"agg_state\",\n            \"aggregate\",\n            \"alias\",\n            \"all\",\n            \"alter\",\n            \"analyze\",\n            \"analyzed\",\n            \"and\",\n            \"anti\",\n            \"append\",\n            \"array\",\n            \"array_range\",\n            \"as\",\n            \"asc\",\n            \"at\",\n            \"authors\",\n            \"auto\",\n            \"auto_increment\",\n            \"backend\",\n            \"backends\",\n            \"backup\",\n            \"begin\",\n            \"belong\",\n            \"between\",\n            \"bigint\",\n            \"bin\",\n            \"binary\",\n            \"binlog\",\n            \"bitand\",\n            \"bitmap\",\n            \"bitmap_union\",\n            \"bitor\",\n            \"bitxor\",\n            \"blob\",\n            \"boolean\",\n            \"brief\",\n            \"broker\",\n            \"buckets\",\n            \"build\",\n            \"builtin\",\n            \"bulk\",\n            \"by\",\n            \"cached\",\n            \"call\",\n            \"cancel\",\n            \"case\",\n            \"cast\",\n            \"catalog\",\n            \"catalogs\",\n            \"chain\",\n            \"char\",\n            \"character\",\n            \"charset\",\n            \"check\",\n            \"clean\",\n            \"cluster\",\n            \"clusters\",\n            \"collate\",\n            \"collation\",\n            \"collect\",\n            \"column\",\n            \"columns\",\n            \"comment\",\n            \"commit\",\n            \"committed\",\n            \"compact\",\n            \"complete\",\n            \"config\",\n            \"connection\",\n            \"connection_id\",\n            \"consistent\",\n            \"constraint\",\n            \"constraints\",\n            \"convert\",\n            \"copy\",\n            \"count\",\n            \"create\",\n            \"creation\",\n            \"cron\",\n            \"cross\",\n            \"cube\",\n            \"current\",\n            \"current_catalog\",\n            \"current_date\",\n            \"current_time\",\n            \"current_timestamp\",\n            \"current_user\",\n            \"data\",\n            \"database\",\n            \"databases\",\n            \"date\",\n            \"date_add\",\n            \"date_ceil\",\n            \"date_diff\",\n            \"date_floor\",\n            \"date_sub\",\n            \"dateadd\",\n            \"datediff\",\n            \"datetime\",\n            \"datetimev2\",\n            \"datev2\",\n            \"datetimev1\",\n            \"datev1\",\n            \"day\",\n            \"days_add\",\n            \"days_sub\",\n            \"decimal\",\n            \"decimalv2\",\n            \"decimalv3\",\n            \"decommission\",\n            \"default\",\n            \"deferred\",\n            \"delete\",\n            \"demand\",\n            \"desc\",\n            \"describe\",\n            \"diagnose\",\n            \"disk\",\n            \"distinct\",\n            \"distinctpc\",\n            \"distinctpcsa\",\n            \"distributed\",\n            \"distribution\",\n            \"div\",\n            \"do\",\n            \"doris_internal_table_id\",\n            \"double\",\n            \"drop\",\n            \"dropp\",\n            \"dual\",\n            \"duplicate\",\n            \"dynamic\",\n            \"else\",\n            \"enable\",\n            \"encryptkey\",\n            \"encryptkeys\",\n            \"end\",\n            \"ends\",\n            \"engine\",\n            \"engines\",\n            \"enter\",\n            \"errors\",\n            \"events\",\n            \"every\",\n            \"except\",\n            \"exclude\",\n            \"execute\",\n            \"exists\",\n            \"expired\",\n            \"explain\",\n            \"export\",\n            \"extended\",\n            \"external\",\n            \"extract\",\n            \"failed_login_attempts\",\n            \"false\",\n            \"fast\",\n            \"feature\",\n            \"fields\",\n            \"file\",\n            \"filter\",\n            \"first\",\n            \"float\",\n            \"follower\",\n            \"following\",\n            \"for\",\n            \"foreign\",\n            \"force\",\n            \"format\",\n            \"free\",\n            \"from\",\n            \"frontend\",\n            \"frontends\",\n            \"full\",\n            \"function\",\n            \"functions\",\n            \"generic\",\n            \"global\",\n            \"grant\",\n            \"grants\",\n            \"graph\",\n            \"group\",\n            \"grouping\",\n            \"groups\",\n            \"hash\",\n            \"having\",\n            \"hdfs\",\n            \"help\",\n            \"histogram\",\n            \"hll\",\n            \"hll_union\",\n            \"hostname\",\n            \"hour\",\n            \"hub\",\n            \"identified\",\n            \"if\",\n            \"ignore\",\n            \"immediate\",\n            \"in\",\n            \"incremental\",\n            \"index\",\n            \"indexes\",\n            \"infile\",\n            \"inner\",\n            \"insert\",\n            \"install\",\n            \"int\",\n            \"integer\",\n            \"intermediate\",\n            \"intersect\",\n            \"interval\",\n            \"into\",\n            \"inverted\",\n            \"ipv4\",\n            \"ipv6\",\n            \"is\",\n            \"is_not_null_pred\",\n            \"is_null_pred\",\n            \"isnull\",\n            \"isolation\",\n            \"job\",\n            \"jobs\",\n            \"join\",\n            \"json\",\n            \"jsonb\",\n            \"key\",\n            \"keys\",\n            \"kill\",\n            \"label\",\n            \"largeint\",\n            \"last\",\n            \"lateral\",\n            \"ldap\",\n            \"ldap_admin_password\",\n            \"left\",\n            \"less\",\n            \"level\",\n            \"like\",\n            \"limit\",\n            \"lines\",\n            \"link\",\n            \"list\",\n            \"load\",\n            \"local\",\n            \"localtime\",\n            \"localtimestamp\",\n            \"location\",\n            \"lock\",\n            \"logical\",\n            \"low_priority\",\n            \"manual\",\n            \"map\",\n            \"match\",\n            \"match_all\",\n            \"match_any\",\n            \"match_phrase\",\n            \"match_phrase_edge\",\n            \"match_phrase_prefix\",\n            \"match_regexp\",\n            \"materialized\",\n            \"max\",\n            \"maxvalue\",\n            \"memo\",\n            \"merge\",\n            \"migrate\",\n            \"migrations\",\n            \"min\",\n            \"minus\",\n            \"minute\",\n            \"modify\",\n            \"month\",\n            \"mtmv\",\n            \"name\",\n            \"names\",\n            \"natural\",\n            \"negative\",\n            \"never\",\n            \"next\",\n            \"ngram_bf\",\n            \"no\",\n            \"non_nullable\",\n            \"not\",\n            \"null\",\n            \"nulls\",\n            \"observer\",\n            \"of\",\n            \"offset\",\n            \"on\",\n            \"only\",\n            \"open\",\n            \"optimized\",\n            \"or\",\n            \"order\",\n            \"outer\",\n            \"outfile\",\n            \"over\",\n            \"overwrite\",\n            \"parameter\",\n            \"parsed\",\n            \"partition\",\n            \"partitions\",\n            \"password\",\n            \"password_expire\",\n            \"password_history\",\n            \"password_lock_time\",\n            \"password_reuse\",\n            \"path\",\n            \"pause\",\n            \"percent\",\n            \"period\",\n            \"permissive\",\n            \"physical\",\n            \"plan\",\n            \"process\",\n            \"plugin\",\n            \"plugins\",\n            \"policy\",\n            \"preceding\",\n            \"prepare\",\n            \"primary\",\n            \"proc\",\n            \"procedure\",\n            \"processlist\",\n            \"profile\",\n            \"properties\",\n            \"property\",\n            \"quantile_state\",\n            \"quantile_union\",\n            \"query\",\n            \"quota\",\n            \"random\",\n            \"range\",\n            \"read\",\n            \"real\",\n            \"rebalance\",\n            \"recover\",\n            \"recycle\",\n            \"refresh\",\n            \"references\",\n            \"regexp\",\n            \"release\",\n            \"rename\",\n            \"repair\",\n            \"repeatable\",\n            \"replace\",\n            \"replace_if_not_null\",\n            \"replica\",\n            \"repositories\",\n            \"repository\",\n            \"resource\",\n            \"resources\",\n            \"restore\",\n            \"restrictive\",\n            \"resume\",\n            \"returns\",\n            \"revoke\",\n            \"rewritten\",\n            \"right\",\n            \"rlike\",\n            \"role\",\n            \"roles\",\n            \"rollback\",\n            \"rollup\",\n            \"routine\",\n            \"row\",\n            \"rows\",\n            \"s3\",\n            \"sample\",\n            \"schedule\",\n            \"scheduler\",\n            \"schema\",\n            \"schemas\",\n            \"second\",\n            \"select\",\n            \"semi\",\n            \"sequence\",\n            \"serializable\",\n            \"session\",\n            \"set\",\n            \"sets\",\n            \"shape\",\n            \"show\",\n            \"signed\",\n            \"skew\",\n            \"smallint\",\n            \"snapshot\",\n            \"soname\",\n            \"split\",\n            \"sql_block_rule\",\n            \"start\",\n            \"starts\",\n            \"stats\",\n            \"status\",\n            \"stop\",\n            \"storage\",\n            \"stream\",\n            \"streaming\",\n            \"string\",\n            \"struct\",\n            \"subdate\",\n            \"sum\",\n            \"superuser\",\n            \"switch\",\n            \"sync\",\n            \"system\",\n            \"table\",\n            \"tables\",\n            \"tablesample\",\n            \"tablet\",\n            \"tablets\",\n            \"task\",\n            \"tasks\",\n            \"temporary\",\n            \"terminated\",\n            \"text\",\n            \"than\",\n            \"then\",\n            \"time\",\n            \"timestamp\",\n            \"timestampadd\",\n            \"timestampdiff\",\n            \"tinyint\",\n            \"to\",\n            \"transaction\",\n            \"trash\",\n            \"tree\",\n            \"triggers\",\n            \"trim\",\n            \"true\",\n            \"truncate\",\n            \"type\",\n            \"type_cast\",\n            \"types\",\n            \"unbounded\",\n            \"uncommitted\",\n            \"uninstall\",\n            \"union\",\n            \"unique\",\n            \"unlock\",\n            \"unsigned\",\n            \"update\",\n            \"use\",\n            \"user\",\n            \"using\",\n            \"value\",\n            \"values\",\n            \"varchar\",\n            \"variables\",\n            \"variant\",\n            \"vault\",\n            \"verbose\",\n            \"version\",\n            \"view\",\n            \"warnings\",\n            \"week\",\n            \"when\",\n            \"where\",\n            \"whitelist\",\n            \"with\",\n            \"work\",\n            \"workload\",\n            \"write\",\n            \"xor\",\n            \"year\",\n        }\n\n        def uniquekeyproperty_sql(\n            self, expression: exp.UniqueKeyProperty, prefix: str = \"UNIQUE KEY\"\n        ) -> str:\n            create_stmt = expression.find_ancestor(exp.Create)\n            if create_stmt and create_stmt.args[\"properties\"].find(exp.MaterializedProperty):\n                return super().uniquekeyproperty_sql(expression, prefix=\"KEY\")\n\n            return super().uniquekeyproperty_sql(expression)\n\n        def partitionrange_sql(self, expression: exp.PartitionRange) -> str:\n            name = self.sql(expression, \"this\")\n            values = expression.expressions\n\n            if len(values) != 1:\n                # Multiple values: use VALUES [ ... )\n                if values and isinstance(values[0], list):\n                    values_sql = \", \".join(\n                        f\"({', '.join(self.sql(v) for v in inner)})\" for inner in values\n                    )\n                else:\n                    values_sql = \", \".join(f\"({self.sql(v)})\" for v in values)\n\n                return f\"PARTITION {name} VALUES [{values_sql})\"\n\n            return f\"PARTITION {name} VALUES LESS THAN ({self.sql(values[0])})\"\n\n        def partitionbyrangepropertydynamic_sql(\n            self, expression: exp.PartitionByRangePropertyDynamic\n        ) -> str:\n            # Generates: FROM (\"start\") TO (\"end\") INTERVAL N UNIT\n            start = self.sql(expression, \"start\")\n            end = self.sql(expression, \"end\")\n            every = expression.args.get(\"every\")\n\n            if every:\n                number = self.sql(every, \"this\")\n                interval = f\"INTERVAL {number} {self.sql(every, 'unit')}\"\n            else:\n                interval = \"\"\n\n            return f\"FROM ({start}) TO ({end}) {interval}\"\n\n        def partitionedbyproperty_sql(self, expression: exp.PartitionedByProperty) -> str:\n            this = expression.this\n            if isinstance(this, exp.Schema):\n                return f\"PARTITION BY ({self.expressions(this, flat=True)})\"\n            return f\"PARTITION BY ({self.sql(this)})\"\n\n        def table_sql(self, expression: exp.Table, sep: str = \" AS \") -> str:\n            \"\"\"Override table_sql to avoid AS keyword in UPDATE and DELETE statements.\"\"\"\n            ancestor = expression.find_ancestor(exp.Update, exp.Delete, exp.Select)\n            if not isinstance(ancestor, exp.Select):\n                sep = \" \"\n            return super().table_sql(expression, sep=sep)\n"
  },
  {
    "path": "sqlglot/dialects/dremio.py",
    "content": "from __future__ import annotations\n\nimport typing as t\nfrom sqlglot import expressions as exp\nfrom sqlglot import generator, tokens\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    rename_func,\n    no_trycast_sql,\n)\nfrom sqlglot.parsers.dremio import DremioParser\n\nDATE_DELTA = t.Union[exp.DateAdd, exp.DateSub]\n\n\ndef _date_delta_sql(name: str) -> t.Callable[[Dremio.Generator, DATE_DELTA], str]:\n    def _delta_sql(self: Dremio.Generator, expression: DATE_DELTA) -> str:\n        unit = expression.text(\"unit\").upper()\n\n        # Fallback to default behavior if unit is missing or 'DAY'\n        if not unit or unit == \"DAY\":\n            return self.func(name, expression.this, expression.expression)\n\n        this_sql = self.sql(expression, \"this\")\n        expr_sql = self.sql(expression, \"expression\")\n\n        interval_sql = f\"CAST({expr_sql} AS INTERVAL {unit})\"\n        return f\"{name}({this_sql}, {interval_sql})\"\n\n    return _delta_sql\n\n\nclass Dremio(Dialect):\n    SUPPORTS_USER_DEFINED_TYPES = False\n    CONCAT_COALESCE = True\n    TYPED_DIVISION = True\n    NULL_ORDERING = \"nulls_are_last\"\n    SUPPORTS_VALUES_DEFAULT = False\n\n    TIME_MAPPING = {\n        # year\n        \"YYYY\": \"%Y\",\n        \"yyyy\": \"%Y\",\n        \"YY\": \"%y\",\n        \"yy\": \"%y\",\n        # month / day\n        \"MM\": \"%m\",\n        \"mm\": \"%m\",\n        \"MON\": \"%b\",\n        \"mon\": \"%b\",\n        \"MONTH\": \"%B\",\n        \"month\": \"%B\",\n        \"DDD\": \"%j\",\n        \"ddd\": \"%j\",\n        \"DD\": \"%d\",\n        \"dd\": \"%d\",\n        \"DY\": \"%a\",\n        \"dy\": \"%a\",\n        \"DAY\": \"%A\",\n        \"day\": \"%A\",\n        # hours / minutes / seconds\n        \"HH24\": \"%H\",\n        \"hh24\": \"%H\",\n        \"HH12\": \"%I\",\n        \"hh12\": \"%I\",\n        \"HH\": \"%I\",\n        \"hh\": \"%I\",  # 24- / 12-hour\n        \"MI\": \"%M\",\n        \"mi\": \"%M\",\n        \"SS\": \"%S\",\n        \"ss\": \"%S\",\n        \"FFF\": \"%f\",\n        \"fff\": \"%f\",\n        \"AMPM\": \"%p\",\n        \"ampm\": \"%p\",\n        # ISO week / century etc.\n        \"WW\": \"%W\",\n        \"ww\": \"%W\",\n        \"D\": \"%w\",\n        \"d\": \"%w\",\n        \"CC\": \"%C\",\n        \"cc\": \"%C\",\n        # timezone\n        \"TZD\": \"%Z\",\n        \"tzd\": \"%Z\",  # abbreviation (UTC, PST, ...)\n        \"TZO\": \"%z\",\n        \"tzo\": \"%z\",  # numeric offset (+0200)\n    }\n\n    class Tokenizer(tokens.Tokenizer):\n        COMMENTS = [\"--\", \"//\", (\"/*\", \"*/\")]\n\n    Parser = DremioParser\n\n    class Generator(generator.Generator):\n        NVL2_SUPPORTED = False\n        SUPPORTS_CONVERT_TIMEZONE = True\n        INTERVAL_ALLOWS_PLURAL_FORM = False\n        JOIN_HINTS = False\n        LIMIT_ONLY_LITERALS = True\n        MULTI_ARG_DISTINCT = False\n        SUPPORTS_BETWEEN_FLAGS = True\n\n        # https://docs.dremio.com/current/reference/sql/data-types/\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.SMALLINT: \"INT\",\n            exp.DType.TINYINT: \"INT\",\n            exp.DType.BINARY: \"VARBINARY\",\n            exp.DType.TEXT: \"VARCHAR\",\n            exp.DType.NCHAR: \"VARCHAR\",\n            exp.DType.CHAR: \"VARCHAR\",\n            exp.DType.TIMESTAMPNTZ: \"TIMESTAMP\",\n            exp.DType.DATETIME: \"TIMESTAMP\",\n            exp.DType.ARRAY: \"LIST\",\n            exp.DType.BIT: \"BOOLEAN\",\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.BitwiseAndAgg: rename_func(\"BIT_AND\"),\n            exp.BitwiseOrAgg: rename_func(\"BIT_OR\"),\n            exp.ToChar: rename_func(\"TO_CHAR\"),\n            exp.TimeToStr: lambda self, e: self.func(\"TO_CHAR\", e.this, self.format_time(e)),\n            exp.TryCast: no_trycast_sql,\n            exp.DateAdd: _date_delta_sql(\"DATE_ADD\"),\n            exp.DateSub: _date_delta_sql(\"DATE_SUB\"),\n            exp.GenerateSeries: rename_func(\"ARRAY_GENERATE_RANGE\"),\n        }\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            \"\"\"\n            Reject time-zone–aware TIMESTAMPs, which Dremio does not accept\n            \"\"\"\n            if expression.is_type(\n                exp.DType.TIMESTAMPTZ,\n                exp.DType.TIMESTAMPLTZ,\n            ):\n                self.unsupported(\"Dremio does not support time-zone-aware TIMESTAMP\")\n\n            return super().datatype_sql(expression)\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:\n            # Match: CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE)\n            if expression.is_type(exp.DType.DATE):\n                at_time_zone = expression.this\n\n                if (\n                    isinstance(at_time_zone, exp.AtTimeZone)\n                    and isinstance(at_time_zone.this, exp.CurrentTimestamp)\n                    and isinstance(at_time_zone.args[\"zone\"], exp.Literal)\n                    and at_time_zone.text(\"zone\").upper() == \"UTC\"\n                ):\n                    return \"CURRENT_DATE_UTC\"\n\n            return super().cast_sql(expression, safe_prefix)\n"
  },
  {
    "path": "sqlglot/dialects/drill.py",
    "content": "from __future__ import annotations\n\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    datestrtodate_sql,\n    no_trycast_sql,\n    rename_func,\n    strposition_sql,\n    timestrtotime_sql,\n)\nfrom sqlglot.dialects.mysql import date_add_sql\nfrom sqlglot.transforms import preprocess, move_schema_columns_to_partitioned_by\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.parsers.drill import DrillParser\n\n\ndef _str_to_date(self: Drill.Generator, expression: exp.StrToDate) -> str:\n    this = self.sql(expression, \"this\")\n    time_format = self.format_time(expression)\n    if time_format == Drill.DATE_FORMAT:\n        return self.sql(exp.cast(this, exp.DType.DATE))\n    return self.func(\"TO_DATE\", this, time_format)\n\n\nclass Drill(Dialect):\n    NORMALIZE_FUNCTIONS: bool | str = False\n    PRESERVE_ORIGINAL_NAMES = True\n    NULL_ORDERING = \"nulls_are_last\"\n    DATE_FORMAT = \"'yyyy-MM-dd'\"\n    DATEINT_FORMAT = \"'yyyyMMdd'\"\n    TIME_FORMAT = \"'yyyy-MM-dd HH:mm:ss'\"\n    SUPPORTS_USER_DEFINED_TYPES = False\n    TYPED_DIVISION = True\n    CONCAT_COALESCE = True\n\n    TIME_MAPPING = {\n        \"y\": \"%Y\",\n        \"Y\": \"%Y\",\n        \"YYYY\": \"%Y\",\n        \"yyyy\": \"%Y\",\n        \"YY\": \"%y\",\n        \"yy\": \"%y\",\n        \"MMMM\": \"%B\",\n        \"MMM\": \"%b\",\n        \"MM\": \"%m\",\n        \"M\": \"%-m\",\n        \"dd\": \"%d\",\n        \"d\": \"%-d\",\n        \"HH\": \"%H\",\n        \"H\": \"%-H\",\n        \"hh\": \"%I\",\n        \"h\": \"%-I\",\n        \"mm\": \"%M\",\n        \"m\": \"%-M\",\n        \"ss\": \"%S\",\n        \"s\": \"%-S\",\n        \"SSSSSS\": \"%f\",\n        \"a\": \"%p\",\n        \"DD\": \"%j\",\n        \"D\": \"%-j\",\n        \"E\": \"%a\",\n        \"EE\": \"%a\",\n        \"EEE\": \"%a\",\n        \"EEEE\": \"%A\",\n        \"''T''\": \"T\",\n    }\n\n    class Tokenizer(tokens.Tokenizer):\n        IDENTIFIERS = [\"`\"]\n        STRING_ESCAPES = [\"\\\\\"]\n\n        KEYWORDS = tokens.Tokenizer.KEYWORDS.copy()\n        KEYWORDS.pop(\"/*+\")\n\n    Parser = DrillParser\n\n    class Generator(generator.Generator):\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n        NVL2_SUPPORTED = False\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        SUPPORTS_CREATE_TABLE_LIKE = False\n        ARRAY_SIZE_NAME = \"REPEATED_COUNT\"\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.INT: \"INTEGER\",\n            exp.DType.SMALLINT: \"INTEGER\",\n            exp.DType.TINYINT: \"INTEGER\",\n            exp.DType.BINARY: \"VARBINARY\",\n            exp.DType.TEXT: \"VARCHAR\",\n            exp.DType.NCHAR: \"VARCHAR\",\n            exp.DType.TIMESTAMPLTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n            exp.DType.DATETIME: \"TIMESTAMP\",\n        }\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.CurrentTimestamp: lambda *_: \"CURRENT_TIMESTAMP\",\n            exp.ArrayContains: rename_func(\"REPEATED_CONTAINS\"),\n            exp.Create: preprocess([move_schema_columns_to_partitioned_by]),\n            exp.DateAdd: date_add_sql(\"ADD\"),\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.DateSub: date_add_sql(\"SUB\"),\n            exp.DateToDi: lambda self, e: (\n                f\"CAST(TO_DATE({self.sql(e, 'this')}, {Drill.DATEINT_FORMAT}) AS INT)\"\n            ),\n            exp.DiToDate: lambda self, e: (\n                f\"TO_DATE(CAST({self.sql(e, 'this')} AS VARCHAR), {Drill.DATEINT_FORMAT})\"\n            ),\n            exp.If: lambda self, e: (\n                f\"`IF`({self.format_args(e.this, e.args.get('true'), e.args.get('false'))})\"\n            ),\n            exp.ILike: lambda self, e: self.binary(e, \"`ILIKE`\"),\n            exp.Levenshtein: unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\", \"max_dist\")(\n                rename_func(\"LEVENSHTEIN_DISTANCE\")\n            ),\n            exp.PartitionedByProperty: lambda self, e: f\"PARTITION BY {self.sql(e, 'this')}\",\n            exp.RegexpLike: rename_func(\"REGEXP_MATCHES\"),\n            exp.StrToDate: _str_to_date,\n            exp.Pow: rename_func(\"POW\"),\n            exp.Select: transforms.preprocess(\n                [transforms.eliminate_distinct_on, transforms.eliminate_semi_and_anti_joins]\n            ),\n            exp.StrPosition: strposition_sql,\n            exp.StrToTime: lambda self, e: self.func(\"TO_TIMESTAMP\", e.this, self.format_time(e)),\n            exp.TimeStrToDate: lambda self, e: self.sql(exp.cast(e.this, exp.DType.DATE)),\n            exp.TimeStrToTime: timestrtotime_sql,\n            exp.TimeStrToUnix: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.TimeToStr: lambda self, e: self.func(\"TO_CHAR\", e.this, self.format_time(e)),\n            exp.TimeToUnix: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.ToChar: lambda self, e: self.function_fallback_sql(e),\n            exp.TryCast: no_trycast_sql,\n            exp.TsOrDsAdd: lambda self, e: (\n                f\"DATE_ADD(CAST({self.sql(e, 'this')} AS DATE), {self.sql(exp.Interval(this=e.expression, unit=exp.var('DAY')))})\"\n            ),\n            exp.TsOrDiToDi: lambda self, e: (\n                f\"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS VARCHAR), '-', ''), 1, 8) AS INT)\"\n            ),\n        }\n"
  },
  {
    "path": "sqlglot/dialects/druid.py",
    "content": "from sqlglot import exp, generator\nfrom sqlglot.dialects.dialect import rename_func, Dialect\nfrom sqlglot.parsers.druid import DruidParser\n\n\nclass Druid(Dialect):\n    Parser = DruidParser\n\n    class Generator(generator.Generator):\n        # https://druid.apache.org/docs/latest/querying/sql-data-types/\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.NCHAR: \"STRING\",\n            exp.DType.NVARCHAR: \"STRING\",\n            exp.DType.TEXT: \"STRING\",\n            exp.DType.UUID: \"STRING\",\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.CurrentTimestamp: lambda *_: \"CURRENT_TIMESTAMP\",\n            exp.Mod: rename_func(\"MOD\"),\n            exp.Array: lambda self, e: f\"ARRAY[{self.expressions(e)}]\",\n        }\n"
  },
  {
    "path": "sqlglot/dialects/duckdb.py",
    "content": "from __future__ import annotations\n\nfrom decimal import Decimal\nfrom itertools import groupby\nimport re\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens, transforms\n\nfrom sqlglot.dialects.dialect import (\n    DATETIME_DELTA,\n    Dialect,\n    JSON_EXTRACT_TYPE,\n    NormalizationStrategy,\n    approx_count_distinct_sql,\n    array_append_sql,\n    array_compact_sql,\n    array_concat_sql,\n    arrow_json_extract_sql,\n    count_if_to_sum,\n    date_delta_to_binary_interval_op,\n    datestrtodate_sql,\n    encode_decode_sql,\n    explode_to_unnest_sql,\n    generate_series_sql,\n    getbit_sql,\n    groupconcat_sql,\n    inline_array_unless_query,\n    jarowinkler_similarity,\n    months_between_sql,\n    no_datetime_sql,\n    no_comment_column_constraint_sql,\n    no_make_interval_sql,\n    no_time_sql,\n    no_timestamp_sql,\n    rename_func,\n    remove_from_array_using_filter,\n    strposition_sql,\n    str_to_time_sql,\n    timestrtotime_sql,\n    unit_to_str,\n)\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.helper import is_date_unit, seq_get\nfrom sqlglot.parsers.duckdb import DuckDBParser\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.typing.duckdb import EXPRESSION_METADATA\nfrom builtins import type as Type\n\n# Regex to detect time zones in timestamps of the form [+|-]TT[:tt]\n# The pattern matches timezone offsets that appear after the time portion\nTIMEZONE_PATTERN = re.compile(r\":\\d{2}.*?[+\\-]\\d{2}(?::\\d{2})?\")\n\n# Characters that must be escaped when building regex expressions in INITCAP\nREGEX_ESCAPE_REPLACEMENTS = {\n    \"\\\\\": \"\\\\\\\\\",\n    \"-\": r\"\\-\",\n    \"^\": r\"\\^\",\n    \"[\": r\"\\[\",\n    \"]\": r\"\\]\",\n}\n\n# Used to in RANDSTR transpilation\nRANDSTR_CHAR_POOL = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"\nRANDSTR_SEED = 123456\n\n# Whitespace control characters that DuckDB must process with `CHR({val})` calls\nWS_CONTROL_CHARS_TO_DUCK = {\n    \"\\u000b\": 11,\n    \"\\u001c\": 28,\n    \"\\u001d\": 29,\n    \"\\u001e\": 30,\n    \"\\u001f\": 31,\n}\n\n# Days of week to ISO 8601 day-of-week numbers\n# ISO 8601 standard: Monday=1, Tuesday=2, Wednesday=3, Thursday=4, Friday=5, Saturday=6, Sunday=7\nWEEK_START_DAY_TO_DOW = {\n    \"MONDAY\": 1,\n    \"TUESDAY\": 2,\n    \"WEDNESDAY\": 3,\n    \"THURSDAY\": 4,\n    \"FRIDAY\": 5,\n    \"SATURDAY\": 6,\n    \"SUNDAY\": 7,\n}\n\nMAX_BIT_POSITION = exp.Literal.number(32768)\n\n# SEQ function constants\n_SEQ_BASE: exp.Expr = exp.maybe_parse(\"(ROW_NUMBER() OVER (ORDER BY 1) - 1)\")\n_SEQ_RESTRICTED = (exp.Where, exp.Having, exp.AggFunc, exp.Order, exp.Select)\n# Maps SEQ expression types to their byte width (suffix indicates bytes: SEQ1=1, SEQ2=2, etc.)\n_SEQ_BYTE_WIDTH = {exp.Seq1: 1, exp.Seq2: 2, exp.Seq4: 4, exp.Seq8: 8}\n\n\ndef _apply_base64_alphabet_replacements(\n    result: exp.Expr,\n    alphabet: t.Optional[exp.Expr],\n    reverse: bool = False,\n) -> exp.Expr:\n    \"\"\"\n    Apply base64 alphabet character replacements.\n\n    Base64 alphabet can be 1-3 chars: 1st = index 62 ('+'), 2nd = index 63 ('/'), 3rd = padding ('=').\n    zip truncates to the shorter string, so 1-char alphabet only replaces '+', 2-char replaces '+/', etc.\n\n    Args:\n        result: The expression to apply replacements to\n        alphabet: Custom alphabet literal (expected chars for +/=)\n        reverse: If False, replace default with custom (encode)\n                 If True, replace custom with default (decode)\n    \"\"\"\n    if isinstance(alphabet, exp.Literal) and alphabet.is_string:\n        for default_char, new_char in zip(\"+/=\", alphabet.this):\n            if new_char != default_char:\n                find, replace = (new_char, default_char) if reverse else (default_char, new_char)\n                result = exp.Replace(\n                    this=result,\n                    expression=exp.Literal.string(find),\n                    replacement=exp.Literal.string(replace),\n                )\n    return result\n\n\ndef _base64_decode_sql(self: DuckDB.Generator, expression: exp.Expr, to_string: bool) -> str:\n    \"\"\"\n    Transpile Snowflake BASE64_DECODE_STRING/BINARY to DuckDB.\n\n    DuckDB uses FROM_BASE64() which returns BLOB. For string output, wrap with DECODE().\n    Custom alphabets require REPLACE() calls to convert to standard base64.\n    \"\"\"\n    input_expr = expression.this\n    alphabet = expression.args.get(\"alphabet\")\n\n    # Handle custom alphabet by replacing non-standard chars with standard ones\n    input_expr = _apply_base64_alphabet_replacements(input_expr, alphabet, reverse=True)\n\n    # FROM_BASE64 returns BLOB\n    input_expr = exp.FromBase64(this=input_expr)\n\n    if to_string:\n        input_expr = exp.Decode(this=input_expr)\n\n    return self.sql(input_expr)\n\n\ndef _last_day_sql(self: DuckDB.Generator, expression: exp.LastDay) -> str:\n    \"\"\"\n    DuckDB's LAST_DAY only supports finding the last day of a month.\n    For other date parts (year, quarter, week), we need to implement equivalent logic.\n    \"\"\"\n    date_expr = expression.this\n    unit = expression.text(\"unit\")\n\n    if not unit or unit.upper() == \"MONTH\":\n        # Default behavior - use DuckDB's native LAST_DAY\n        return self.func(\"LAST_DAY\", date_expr)\n\n    if unit.upper() == \"YEAR\":\n        # Last day of year: December 31st of the same year\n        year_expr = exp.func(\"EXTRACT\", \"YEAR\", date_expr)\n        make_date_expr = exp.func(\n            \"MAKE_DATE\", year_expr, exp.Literal.number(12), exp.Literal.number(31)\n        )\n        return self.sql(make_date_expr)\n\n    if unit.upper() == \"QUARTER\":\n        # Last day of quarter\n        year_expr = exp.func(\"EXTRACT\", \"YEAR\", date_expr)\n        quarter_expr = exp.func(\"EXTRACT\", \"QUARTER\", date_expr)\n\n        # Calculate last month of quarter: quarter * 3. Quarter can be 1 to 4\n        last_month_expr = exp.Mul(this=quarter_expr, expression=exp.Literal.number(3))\n        first_day_last_month_expr = exp.func(\n            \"MAKE_DATE\", year_expr, last_month_expr, exp.Literal.number(1)\n        )\n\n        # Last day of the last month of the quarter\n        last_day_expr = exp.func(\"LAST_DAY\", first_day_last_month_expr)\n        return self.sql(last_day_expr)\n\n    if unit.upper() == \"WEEK\":\n        # DuckDB DAYOFWEEK: Sunday=0, Monday=1, ..., Saturday=6\n        dow = exp.func(\"EXTRACT\", \"DAYOFWEEK\", date_expr)\n        # Days to the last day of week: (7 - dayofweek) % 7, assuming the last day of week is Sunday (Snowflake)\n        # Wrap in parentheses to ensure correct precedence\n        days_to_sunday_expr = exp.Mod(\n            this=exp.Paren(this=exp.Sub(this=exp.Literal.number(7), expression=dow)),\n            expression=exp.Literal.number(7),\n        )\n        interval_expr = exp.Interval(this=days_to_sunday_expr, unit=exp.var(\"DAY\"))\n        add_expr = exp.Add(this=date_expr, expression=interval_expr)\n        cast_expr = exp.cast(add_expr, exp.DType.DATE)\n        return self.sql(cast_expr)\n\n    self.unsupported(f\"Unsupported date part '{unit}' in LAST_DAY function\")\n    return self.function_fallback_sql(expression)\n\n\ndef _is_nanosecond_unit(unit: t.Optional[exp.Expr]) -> bool:\n    return isinstance(unit, (exp.Var, exp.Literal)) and unit.name.upper() == \"NANOSECOND\"\n\n\ndef _handle_nanosecond_diff(\n    self: DuckDB.Generator,\n    end_time: exp.Expr,\n    start_time: exp.Expr,\n) -> str:\n    \"\"\"Generate NANOSECOND diff using EPOCH_NS since DATE_DIFF doesn't support it.\"\"\"\n    end_ns = exp.cast(end_time, exp.DType.TIMESTAMP_NS)\n    start_ns = exp.cast(start_time, exp.DType.TIMESTAMP_NS)\n\n    # Build expression tree: EPOCH_NS(end) - EPOCH_NS(start)\n    return self.sql(\n        exp.Sub(this=exp.func(\"EPOCH_NS\", end_ns), expression=exp.func(\"EPOCH_NS\", start_ns))\n    )\n\n\ndef _to_boolean_sql(self: DuckDB.Generator, expression: exp.ToBoolean) -> str:\n    \"\"\"\n    Transpile TO_BOOLEAN and TRY_TO_BOOLEAN functions from Snowflake to DuckDB equivalent.\n\n    DuckDB's CAST to BOOLEAN supports most of Snowflake's TO_BOOLEAN strings except 'on'/'off'.\n    We need to handle the 'on'/'off' cases explicitly.\n\n    For TO_BOOLEAN (safe=False): NaN and INF values cause errors. We use DuckDB's native ERROR()\n    function to replicate this behavior with a clear error message.\n\n    For TRY_TO_BOOLEAN (safe=True): Use DuckDB's TRY_CAST for conversion, which returns NULL\n    for invalid inputs instead of throwing errors.\n    \"\"\"\n    arg = expression.this\n    is_safe = expression.args.get(\"safe\", False)\n\n    base_case_expr = (\n        exp.case()\n        .when(\n            # Handle 'on' -> TRUE (case insensitive)\n            exp.Upper(this=exp.cast(arg, exp.DType.VARCHAR)).eq(exp.Literal.string(\"ON\")),\n            exp.true(),\n        )\n        .when(\n            # Handle 'off' -> FALSE (case insensitive)\n            exp.Upper(this=exp.cast(arg, exp.DType.VARCHAR)).eq(exp.Literal.string(\"OFF\")),\n            exp.false(),\n        )\n    )\n\n    if is_safe:\n        # TRY_TO_BOOLEAN: handle 'on'/'off' and use TRY_CAST for everything else\n        case_expr = base_case_expr.else_(exp.func(\"TRY_CAST\", arg, exp.DataType.build(\"BOOLEAN\")))\n    else:\n        # TO_BOOLEAN: handle NaN/INF errors, 'on'/'off', and use regular CAST\n        cast_to_real = exp.func(\"TRY_CAST\", arg, exp.DataType.build(\"REAL\"))\n\n        # Check for NaN and INF values\n        nan_inf_check = exp.Or(\n            this=exp.func(\"ISNAN\", cast_to_real), expression=exp.func(\"ISINF\", cast_to_real)\n        )\n\n        case_expr = base_case_expr.when(\n            nan_inf_check,\n            exp.func(\n                \"ERROR\",\n                exp.Literal.string(\"TO_BOOLEAN: Non-numeric values NaN and INF are not supported\"),\n            ),\n        ).else_(exp.cast(arg, exp.DType.BOOLEAN))\n\n    return self.sql(case_expr)\n\n\n# BigQuery -> DuckDB conversion for the DATE function\ndef _date_sql(self: DuckDB.Generator, expression: exp.Date) -> str:\n    this = expression.this\n    zone = self.sql(expression, \"zone\")\n\n    if zone:\n        # BigQuery considers \"this\" at UTC, converts it to the specified\n        # time zone and then keeps only the DATE part\n        # To micmic that, we:\n        #   (1) Cast to TIMESTAMP to remove DuckDB's local tz\n        #   (2) Apply consecutive AtTimeZone calls for UTC -> zone conversion\n        this = exp.cast(this, exp.DType.TIMESTAMP)\n        at_utc = exp.AtTimeZone(this=this, zone=exp.Literal.string(\"UTC\"))\n        this = exp.AtTimeZone(this=at_utc, zone=zone)\n\n    return self.sql(exp.cast(expression=this, to=exp.DType.DATE))\n\n\n# BigQuery -> DuckDB conversion for the TIME_DIFF function\ndef _timediff_sql(self: DuckDB.Generator, expression: exp.TimeDiff) -> str:\n    unit = expression.unit\n\n    if _is_nanosecond_unit(unit):\n        return _handle_nanosecond_diff(self, expression.expression, expression.this)\n\n    this = exp.cast(expression.this, exp.DType.TIME)\n    expr = exp.cast(expression.expression, exp.DType.TIME)\n\n    # Although the 2 dialects share similar signatures, BQ seems to inverse\n    # the sign of the result so the start/end time operands are flipped\n    return self.func(\"DATE_DIFF\", unit_to_str(expression), expr, this)\n\n\ndef _date_delta_to_binary_interval_op(\n    cast: bool = True,\n) -> t.Callable[[DuckDB.Generator, DATETIME_DELTA], str]:\n    \"\"\"\n    DuckDB override to handle:\n    1. NANOSECOND operations (DuckDB doesn't support INTERVAL ... NANOSECOND)\n    2. Float/decimal interval values (DuckDB INTERVAL requires integers)\n    \"\"\"\n    base_impl = date_delta_to_binary_interval_op(cast=cast)\n\n    def _duckdb_date_delta_sql(self: DuckDB.Generator, expression: DATETIME_DELTA) -> str:\n        unit = expression.unit\n        interval_value = expression.expression\n\n        # Handle NANOSECOND unit (DuckDB doesn't support INTERVAL ... NANOSECOND)\n        if _is_nanosecond_unit(unit):\n            if isinstance(interval_value, exp.Interval):\n                interval_value = interval_value.this\n\n            timestamp_ns = exp.cast(expression.this, exp.DType.TIMESTAMP_NS)\n\n            return self.sql(\n                exp.func(\n                    \"MAKE_TIMESTAMP_NS\",\n                    exp.Add(this=exp.func(\"EPOCH_NS\", timestamp_ns), expression=interval_value),\n                )\n            )\n\n        # Handle float/decimal interval values as duckDB INTERVAL requires integer expressions\n        if not interval_value or isinstance(interval_value, exp.Interval):\n            return base_impl(self, expression)\n\n        if interval_value.is_type(*exp.DataType.REAL_TYPES):\n            expression.set(\"expression\", exp.cast(exp.func(\"ROUND\", interval_value), \"INT\"))\n\n        return base_impl(self, expression)\n\n    return _duckdb_date_delta_sql\n\n\ndef _array_insert_sql(self: DuckDB.Generator, expression: exp.ArrayInsert) -> str:\n    \"\"\"\n    Transpile ARRAY_INSERT to DuckDB using LIST_CONCAT and slicing.\n\n    Handles:\n    - 0-based and 1-based indexing (normalizes to 0-based for calculations)\n    - Negative position conversion (requires array length)\n    - NULL propagation (source dialects return NULL, DuckDB creates single-element array)\n    - Assumes position is within bounds per user constraint\n\n    Note: All dialects that support ARRAY_INSERT (Snowflake, Spark, Databricks) have\n    ARRAY_FUNCS_PROPAGATES_NULLS=True, so we always assume source propagates NULLs.\n\n    Args:\n        expression: The ArrayInsert expression to transpile.\n\n    Returns:\n        SQL string implementing ARRAY_INSERT behavior.\n    \"\"\"\n    this = expression.this\n    position = expression.args.get(\"position\")\n    element = expression.expression\n    element_array = exp.Array(expressions=[element])\n    index_offset = expression.args.get(\"offset\", 0)\n\n    if not position or not position.is_int:\n        self.unsupported(\"ARRAY_INSERT can only be transpiled with a literal position\")\n        return self.func(\"ARRAY_INSERT\", this, position, element)\n\n    pos_value = position.to_py()\n\n    # Normalize one-based indexing to zero-based for slice calculations\n    # Spark (1-based) → Snowflake (0-based):\n    #   Positive: pos=1 → pos=0 (subtract 1)\n    #   Negative: pos=-2 → pos=-1 (add 1)\n    # Example: Spark array_insert([a,b,c], -2, d) → [a,b,d,c] is same as Snowflake pos=-1\n    if pos_value > 0:\n        pos_value = pos_value - index_offset\n    elif pos_value < 0:\n        pos_value = pos_value + index_offset\n\n    # Build the appropriate list_concat expression based on position\n    if pos_value == 0:\n        # insert at beginning\n        concat_exprs = [element_array, this]\n    elif pos_value > 0:\n        # Positive position: LIST_CONCAT(arr[1:pos], [elem], arr[pos+1:])\n        # 0-based -> DuckDB 1-based slicing\n\n        # left slice: arr[1:pos]\n        slice_start = exp.Bracket(\n            this=this,\n            expressions=[\n                exp.Slice(this=exp.Literal.number(1), expression=exp.Literal.number(pos_value))\n            ],\n        )\n\n        # right slice: arr[pos+1:]\n        slice_end = exp.Bracket(\n            this=this, expressions=[exp.Slice(this=exp.Literal.number(pos_value + 1))]\n        )\n\n        concat_exprs = [slice_start, element_array, slice_end]\n    else:\n        # Negative position: arr[1:LEN(arr)+pos], [elem], arr[LEN(arr)+pos+1:]\n        # pos=-1 means insert before last element\n        arr_len = exp.Length(this=this)\n\n        # Calculate slice position: LEN(arr) + pos (e.g., LEN(arr) + (-1) = LEN(arr) - 1)\n        slice_end_pos = arr_len + exp.Literal.number(pos_value)\n        slice_start_pos = slice_end_pos + exp.Literal.number(1)\n\n        # left slice: arr[1:LEN(arr)+pos]\n        slice_start = exp.Bracket(\n            this=this,\n            expressions=[exp.Slice(this=exp.Literal.number(1), expression=slice_end_pos)],\n        )\n\n        # right slice: arr[LEN(arr)+pos+1:]\n        slice_end = exp.Bracket(this=this, expressions=[exp.Slice(this=slice_start_pos)])\n\n        concat_exprs = [slice_start, element_array, slice_end]\n\n    # All dialects that support ARRAY_INSERT propagate NULLs (Snowflake/Spark/Databricks)\n    # Wrap in CASE WHEN array IS NULL THEN NULL ELSE func_expr END\n    return self.sql(\n        exp.If(\n            this=exp.Is(this=this, expression=exp.Null()),\n            true=exp.Null(),\n            false=self.func(\"LIST_CONCAT\", *concat_exprs),\n        )\n    )\n\n\ndef _array_remove_at_sql(self: DuckDB.Generator, expression: exp.ArrayRemoveAt) -> str:\n    \"\"\"\n    Transpile ARRAY_REMOVE_AT to DuckDB using LIST_CONCAT and slicing.\n\n    Handles:\n    - Positive positions (0-based indexing)\n    - Negative positions (from end of array)\n    - NULL propagation (Snowflake returns NULL for NULL array, DuckDB doesn't auto-propagate)\n    - Only supports literal integer positions (non-literals remain untranspiled)\n\n    Transpilation patterns:\n    - pos=0 (first): arr[2:]\n    - pos>0 (middle): LIST_CONCAT(arr[1:p], arr[p+2:])\n    - pos=-1 (last): arr[1:LEN(arr)-1]\n    - pos<-1: LIST_CONCAT(arr[1:LEN(arr)+p], arr[LEN(arr)+p+2:])\n\n    All wrapped in: CASE WHEN arr IS NULL THEN NULL ELSE ... END\n\n    Args:\n        expression: The ArrayRemoveAt expression to transpile.\n\n    Returns:\n        SQL string implementing ARRAY_REMOVE_AT behavior.\n    \"\"\"\n    this = expression.this\n    position = expression.args.get(\"position\")\n\n    if not position or not position.is_int:\n        self.unsupported(\"ARRAY_REMOVE_AT can only be transpiled with a literal position\")\n        return self.func(\"ARRAY_REMOVE_AT\", this, position)\n\n    pos_value = position.to_py()\n\n    # Build the appropriate expression based on position\n    if pos_value == 0:\n        # Remove first element: arr[2:]\n        result_expr: exp.Expr | str = exp.Bracket(\n            this=this,\n            expressions=[exp.Slice(this=exp.Literal.number(2))],\n        )\n    elif pos_value > 0:\n        # Remove at positive position: LIST_CONCAT(arr[1:pos], arr[pos+2:])\n        # DuckDB uses 1-based slicing\n        left_slice = exp.Bracket(\n            this=this,\n            expressions=[\n                exp.Slice(this=exp.Literal.number(1), expression=exp.Literal.number(pos_value))\n            ],\n        )\n        right_slice = exp.Bracket(\n            this=this,\n            expressions=[exp.Slice(this=exp.Literal.number(pos_value + 2))],\n        )\n        result_expr = self.func(\"LIST_CONCAT\", left_slice, right_slice)\n    elif pos_value == -1:\n        # Remove last element: arr[1:LEN(arr)-1]\n        # Optimization: simpler than general negative case\n        arr_len = exp.Length(this=this)\n        slice_end = arr_len + exp.Literal.number(-1)\n        result_expr = exp.Bracket(\n            this=this,\n            expressions=[exp.Slice(this=exp.Literal.number(1), expression=slice_end)],\n        )\n    else:\n        # Remove at negative position: LIST_CONCAT(arr[1:LEN(arr)+pos], arr[LEN(arr)+pos+2:])\n        arr_len = exp.Length(this=this)\n        slice_end_pos = arr_len + exp.Literal.number(pos_value)\n        slice_start_pos = slice_end_pos + exp.Literal.number(2)\n\n        left_slice = exp.Bracket(\n            this=this,\n            expressions=[exp.Slice(this=exp.Literal.number(1), expression=slice_end_pos)],\n        )\n        right_slice = exp.Bracket(\n            this=this,\n            expressions=[exp.Slice(this=slice_start_pos)],\n        )\n        result_expr = self.func(\"LIST_CONCAT\", left_slice, right_slice)\n\n    # Snowflake ARRAY_FUNCS_PROPAGATES_NULLS=True, so wrap in NULL check\n    # CASE WHEN array IS NULL THEN NULL ELSE result_expr END\n    return self.sql(\n        exp.If(\n            this=exp.Is(this=this, expression=exp.Null()),\n            true=exp.Null(),\n            false=result_expr,\n        )\n    )\n\n\n@unsupported_args((\"expression\", \"DuckDB's ARRAY_SORT does not support a comparator.\"))\ndef _array_sort_sql(self: DuckDB.Generator, expression: exp.ArraySort) -> str:\n    return self.func(\"ARRAY_SORT\", expression.this)\n\n\ndef _array_contains_sql(self: DuckDB.Generator, expression: exp.ArrayContains) -> str:\n    this = expression.this\n    expr = expression.expression\n\n    func = self.func(\"ARRAY_CONTAINS\", this, expr)\n\n    if expression.args.get(\"check_null\"):\n        check_null_in_array = exp.Nullif(\n            this=exp.NEQ(this=exp.ArraySize(this=this), expression=exp.func(\"LIST_COUNT\", this)),\n            expression=exp.false(),\n        )\n        return self.sql(exp.If(this=expr.is_(exp.Null()), true=check_null_in_array, false=func))\n\n    return func\n\n\ndef _array_overlaps_sql(self: DuckDB.Generator, expression: exp.ArrayOverlaps) -> str:\n    \"\"\"\n    Translates Snowflake's NULL-safe ARRAYS_OVERLAP to DuckDB.\n\n    DuckDB's native && operator is not NULL-safe: [1,NULL,3] && [NULL,4,5] returns FALSE.\n    Snowflake returns TRUE when both arrays contain NULL (NULLs are treated as known values).\n\n    Generated SQL: (arr1 && arr2) OR (ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1) AND ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2))\n\n    ARRAY_LENGTH counts all elements (including NULLs); LIST_COUNT counts only non-NULLs.\n    When they differ, the array contains at least one NULL, matching Snowflake's NULL-safe semantics.\n    \"\"\"\n    if not expression.args.get(\"null_safe\"):\n        return self.binary(expression, \"&&\")\n\n    arr1 = expression.this\n    arr2 = expression.expression\n\n    check_nulls = exp.and_(\n        exp.NEQ(\n            this=exp.ArraySize(this=arr1.copy()),\n            expression=exp.func(\"LIST_COUNT\", arr1.copy()),\n        ),\n        exp.NEQ(\n            this=exp.ArraySize(this=arr2.copy()),\n            expression=exp.func(\"LIST_COUNT\", arr2.copy()),\n        ),\n        copy=False,\n    )\n\n    overlap = exp.ArrayOverlaps(this=arr1.copy(), expression=arr2.copy())\n\n    return self.sql(\n        exp.or_(\n            exp.paren(overlap, copy=False),\n            exp.paren(check_nulls, copy=False),\n            copy=False,\n            wrap=False,\n        )\n    )\n\n\ndef _struct_sql(self: DuckDB.Generator, expression: exp.Struct) -> str:\n    ancestor_cast = expression.find_ancestor(exp.Cast, exp.Select)\n    ancestor_cast = None if isinstance(ancestor_cast, exp.Select) else ancestor_cast\n\n    # Empty struct cast works with MAP() since DuckDB can't parse {}\n    if not expression.expressions:\n        if isinstance(ancestor_cast, exp.Cast) and ancestor_cast.to.is_type(exp.DType.MAP):\n            return \"MAP()\"\n\n    args: t.List[str] = []\n\n    # BigQuery allows inline construction such as \"STRUCT<a STRING, b INTEGER>('str', 1)\" which is\n    # canonicalized to \"ROW('str', 1) AS STRUCT(a TEXT, b INT)\" in DuckDB\n    # The transformation to ROW will take place if:\n    #  1. The STRUCT itself does not have proper fields (key := value) as a \"proper\" STRUCT would\n    #  2. A cast to STRUCT / ARRAY of STRUCTs is found\n    is_bq_inline_struct = (\n        (expression.find(exp.PropertyEQ) is None)\n        and ancestor_cast\n        and any(\n            casted_type.is_type(exp.DType.STRUCT)\n            for casted_type in ancestor_cast.find_all(exp.DataType)\n        )\n    )\n\n    for i, expr in enumerate(expression.expressions):\n        is_property_eq = isinstance(expr, exp.PropertyEQ)\n        this = expr.this\n        value = expr.expression if is_property_eq else expr\n\n        if is_bq_inline_struct:\n            args.append(self.sql(value))\n        else:\n            if isinstance(this, exp.Identifier):\n                key = self.sql(exp.Literal.string(expr.name))\n            elif is_property_eq:\n                key = self.sql(this)\n            else:\n                key = self.sql(exp.Literal.string(f\"_{i}\"))\n\n            args.append(f\"{key}: {self.sql(value)}\")\n\n    csv_args = \", \".join(args)\n\n    return f\"ROW({csv_args})\" if is_bq_inline_struct else f\"{{{csv_args}}}\"\n\n\ndef _datatype_sql(self: DuckDB.Generator, expression: exp.DataType) -> str:\n    if expression.is_type(\"array\"):\n        return f\"{self.expressions(expression, flat=True)}[{self.expressions(expression, key='values', flat=True)}]\"\n\n    # Modifiers are not supported for TIME, [TIME | TIMESTAMP] WITH TIME ZONE\n    if expression.is_type(exp.DType.TIME, exp.DType.TIMETZ, exp.DType.TIMESTAMPTZ):\n        return expression.this.value\n\n    return self.datatype_sql(expression)\n\n\ndef _json_format_sql(self: DuckDB.Generator, expression: exp.JSONFormat) -> str:\n    sql = self.func(\"TO_JSON\", expression.this, expression.args.get(\"options\"))\n    return f\"CAST({sql} AS TEXT)\"\n\n\ndef _build_seq_expression(base: exp.Expr, byte_width: int, signed: bool) -> exp.Expr:\n    \"\"\"Build a SEQ expression with the given base, byte width, and signedness.\"\"\"\n    bits = byte_width * 8\n    max_val = exp.Literal.number(2**bits)\n\n    if signed:\n        half = exp.Literal.number(2 ** (bits - 1))\n        return exp.replace_placeholders(\n            DuckDB.Generator.SEQ_SIGNED.copy(), base=base, max_val=max_val, half=half\n        )\n    return exp.replace_placeholders(\n        DuckDB.Generator.SEQ_UNSIGNED.copy(), base=base, max_val=max_val\n    )\n\n\ndef _seq_to_range_in_generator(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Transform SEQ functions to `range` column references when inside a GENERATOR context.\n\n    When GENERATOR(ROWCOUNT => N) becomes RANGE(N) in DuckDB, it produces a column\n    named `range` with values 0, 1, ..., N-1. SEQ functions produce the same sequence,\n    so we replace them with `range % max_val` to avoid nested window function issues.\n    \"\"\"\n    if not isinstance(expression, exp.Select):\n        return expression\n\n    from_ = expression.args.get(\"from_\")\n    if not (\n        from_\n        and isinstance(from_.this, exp.TableFromRows)\n        and isinstance(from_.this.this, exp.Generator)\n    ):\n        return expression\n\n    def replace_seq(node: exp.Expr) -> exp.Expr:\n        if isinstance(node, (exp.Seq1, exp.Seq2, exp.Seq4, exp.Seq8)):\n            byte_width = _SEQ_BYTE_WIDTH[type(node)]\n            return _build_seq_expression(exp.column(\"range\"), byte_width, signed=node.name == \"1\")\n        return node\n\n    return expression.transform(replace_seq, copy=False)\n\n\ndef _seq_sql(self: DuckDB.Generator, expression: exp.Func, byte_width: int) -> str:\n    \"\"\"\n    Transpile Snowflake SEQ1/SEQ2/SEQ4/SEQ8 to DuckDB.\n\n    Generates monotonically increasing integers starting from 0.\n    The signed parameter (0 or 1) affects wrap-around behavior:\n    - Unsigned (0): wraps at 2^(bits) - 1\n    - Signed (1): wraps at 2^(bits-1) - 1, then goes negative\n    \"\"\"\n    # Warn if SEQ is in a restricted context (Select stops search at current scope)\n    ancestor = expression.find_ancestor(*_SEQ_RESTRICTED)\n    if ancestor and (\n        (not isinstance(ancestor, (exp.Order, exp.Select)))\n        or (isinstance(ancestor, exp.Order) and isinstance(ancestor.parent, exp.Window))\n    ):\n        self.unsupported(\"SEQ in restricted context is not supported - use CTE or subquery\")\n\n    result = _build_seq_expression(_SEQ_BASE.copy(), byte_width, signed=expression.name == \"1\")\n    return self.sql(result)\n\n\ndef _unix_to_time_sql(self: DuckDB.Generator, expression: exp.UnixToTime) -> str:\n    scale = expression.args.get(\"scale\")\n    timestamp = expression.this\n    target_type = expression.args.get(\"target_type\")\n\n    # Check if we need NTZ (naive timestamp in UTC)\n    is_ntz = target_type and target_type.this in (\n        exp.DType.TIMESTAMP,\n        exp.DType.TIMESTAMPNTZ,\n    )\n\n    if scale == exp.UnixToTime.MILLIS:\n        # EPOCH_MS already returns TIMESTAMP (naive, UTC)\n        return self.func(\"EPOCH_MS\", timestamp)\n    if scale == exp.UnixToTime.MICROS:\n        # MAKE_TIMESTAMP already returns TIMESTAMP (naive, UTC)\n        return self.func(\"MAKE_TIMESTAMP\", timestamp)\n\n    # Other scales: divide and use TO_TIMESTAMP\n    if scale not in (None, exp.UnixToTime.SECONDS):\n        timestamp = exp.Div(this=timestamp, expression=exp.func(\"POW\", 10, scale))\n\n    to_timestamp: exp.Expr = exp.Anonymous(this=\"TO_TIMESTAMP\", expressions=[timestamp])\n\n    if is_ntz:\n        to_timestamp = exp.AtTimeZone(this=to_timestamp, zone=exp.Literal.string(\"UTC\"))\n\n    return self.sql(to_timestamp)\n\n\nWRAPPED_JSON_EXTRACT_EXPRESSIONS = (exp.Binary, exp.Bracket, exp.In, exp.Not)\n\n\ndef _arrow_json_extract_sql(self: DuckDB.Generator, expression: JSON_EXTRACT_TYPE) -> str:\n    arrow_sql = arrow_json_extract_sql(self, expression)\n    if not expression.same_parent and isinstance(\n        expression.parent, WRAPPED_JSON_EXTRACT_EXPRESSIONS\n    ):\n        arrow_sql = self.wrap(arrow_sql)\n    return arrow_sql\n\n\ndef _implicit_datetime_cast(\n    arg: t.Optional[exp.Expr], type: exp.DType = exp.DType.DATE\n) -> t.Optional[exp.Expr]:\n    if isinstance(arg, exp.Literal) and arg.is_string:\n        ts = arg.name\n        if type == exp.DType.DATE and \":\" in ts:\n            type = exp.DType.TIMESTAMPTZ if TIMEZONE_PATTERN.search(ts) else exp.DType.TIMESTAMP\n\n        arg = exp.cast(arg, type)\n\n    return arg\n\n\ndef _week_unit_to_dow(unit: t.Optional[exp.Expr]) -> t.Optional[int]:\n    \"\"\"\n    Compute the Monday-based day shift to align DATE_DIFF('WEEK', ...) coming\n    from other dialects, e.g BigQuery's WEEK(<day>) or ISOWEEK unit parts.\n\n    Args:\n        unit: The unit expression (Var for ISOWEEK or WeekStart)\n\n    Returns:\n        The ISO 8601 day number (Monday=1, Sunday=7 etc) or None if not a week unit or if day is dynamic (not a constant).\n\n        Examples:\n            \"WEEK(SUNDAY)\" -> 7\n            \"WEEK(MONDAY)\" -> 1\n            \"ISOWEEK\" -> 1\n    \"\"\"\n    # Handle plain Var expressions for ISOWEEK only\n    if isinstance(unit, exp.Var) and unit.name.upper() in \"ISOWEEK\":\n        return 1\n\n    # Handle WeekStart expressions with explicit day\n    if isinstance(unit, exp.WeekStart):\n        return WEEK_START_DAY_TO_DOW.get(unit.name.upper())\n\n    return None\n\n\ndef _build_week_trunc_expression(\n    date_expr: exp.Expr,\n    start_dow: int,\n    preserve_start_day: bool = False,\n) -> exp.Expr:\n    \"\"\"\n    Build DATE_TRUNC expression for week boundaries with custom start day.\n\n    DuckDB's DATE_TRUNC('WEEK', ...) always returns Monday. To align to a different\n    start day, we shift the date before truncating.\n\n    Args:\n        date_expr: The date expression to truncate.\n        start_dow: ISO 8601 day-of-week number (Monday=1, ..., Sunday=7).\n        preserve_start_day: If True, reverse the shift after truncating so the result lands on the\n            correct week start day. Needed for DATE_TRUNC (absolute result matters) but\n            not for DATE_DIFF (only relative alignment matters).\n\n    Shift formula: Sunday (7) gets +1, others get (1 - start_dow).\n    \"\"\"\n    shift_days = 1 if start_dow == 7 else 1 - start_dow\n    truncated = exp.func(\"DATE_TRUNC\", unit=exp.var(\"WEEK\"), this=date_expr)\n\n    if shift_days == 0:\n        return truncated\n\n    shift = exp.Interval(this=exp.Literal.string(str(shift_days)), unit=exp.var(\"DAY\"))\n    shifted_date = exp.DateAdd(this=date_expr, expression=shift)\n    truncated.set(\"this\", shifted_date)\n\n    if preserve_start_day:\n        interval = exp.Interval(this=exp.Literal.string(str(-shift_days)), unit=exp.var(\"DAY\"))\n        return exp.cast(\n            exp.DateAdd(this=truncated, expression=interval), to=exp.DType.DATE, copy=False\n        )\n\n    return truncated\n\n\ndef _date_diff_sql(self: DuckDB.Generator, expression: exp.DateDiff) -> str:\n    unit = expression.unit\n\n    if _is_nanosecond_unit(unit):\n        return _handle_nanosecond_diff(self, expression.this, expression.expression)\n\n    this = _implicit_datetime_cast(expression.this)\n    expr = _implicit_datetime_cast(expression.expression)\n\n    # DuckDB's WEEK diff does not respect Monday crossing (week boundaries), it checks (end_day - start_day) / 7:\n    #  SELECT DATE_DIFF('WEEK', CAST('2024-12-13' AS DATE), CAST('2024-12-17' AS DATE)) --> 0 (Monday crossed)\n    #  SELECT DATE_DIFF('WEEK', CAST('2024-12-13' AS DATE), CAST('2024-12-20' AS DATE)) --> 1 (7 days difference)\n    # Whereas for other units such as MONTH it does respect month boundaries:\n    #  SELECT DATE_DIFF('MONTH', CAST('2024-11-30' AS DATE), CAST('2024-12-01' AS DATE)) --> 1 (Month crossed)\n    date_part_boundary = expression.args.get(\"date_part_boundary\")\n\n    # Extract week start day; returns None if day is dynamic (column/placeholder)\n    week_start = _week_unit_to_dow(unit)\n    if date_part_boundary and week_start and this and expr:\n        expression.set(\"unit\", exp.Literal.string(\"WEEK\"))\n\n        # Truncate both dates to week boundaries to respect input dialect semantics\n        this = _build_week_trunc_expression(this, week_start)\n        expr = _build_week_trunc_expression(expr, week_start)\n\n    return self.func(\"DATE_DIFF\", unit_to_str(expression), expr, this)\n\n\ndef _generate_datetime_array_sql(\n    self: DuckDB.Generator, expression: t.Union[exp.GenerateDateArray, exp.GenerateTimestampArray]\n) -> str:\n    is_generate_date_array = isinstance(expression, exp.GenerateDateArray)\n\n    type = exp.DType.DATE if is_generate_date_array else exp.DType.TIMESTAMP\n    start = _implicit_datetime_cast(expression.args.get(\"start\"), type=type)\n    end = _implicit_datetime_cast(expression.args.get(\"end\"), type=type)\n\n    # BQ's GENERATE_DATE_ARRAY & GENERATE_TIMESTAMP_ARRAY are transformed to DuckDB'S GENERATE_SERIES\n    gen_series: t.Union[exp.GenerateSeries, exp.Cast] = exp.GenerateSeries(\n        start=start, end=end, step=expression.args.get(\"step\")\n    )\n\n    if is_generate_date_array:\n        # The GENERATE_SERIES result type is TIMESTAMP array, so to match BQ's semantics for\n        # GENERATE_DATE_ARRAY we must cast it back to DATE array\n        gen_series = exp.cast(gen_series, exp.DataType.build(\"ARRAY<DATE>\"))\n\n    return self.sql(gen_series)\n\n\ndef _json_extract_value_array_sql(\n    self: DuckDB.Generator, expression: exp.JSONValueArray | exp.JSONExtractArray\n) -> str:\n    json_extract = exp.JSONExtract(this=expression.this, expression=expression.expression)\n    data_type = \"ARRAY<STRING>\" if isinstance(expression, exp.JSONValueArray) else \"ARRAY<JSON>\"\n    return self.sql(exp.cast(json_extract, to=exp.DataType.build(data_type)))\n\n\ndef _cast_to_varchar(arg: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n    if arg and arg.type and not arg.is_type(*exp.DataType.TEXT_TYPES, exp.DType.UNKNOWN):\n        return exp.cast(arg, exp.DType.VARCHAR)\n    return arg\n\n\ndef _cast_to_boolean(arg: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n    if arg and not arg.is_type(exp.DType.BOOLEAN):\n        return exp.cast(arg, exp.DType.BOOLEAN)\n    return arg\n\n\ndef _is_binary(arg: exp.Expr) -> bool:\n    return arg.is_type(\n        exp.DType.BINARY,\n        exp.DType.VARBINARY,\n        exp.DType.BLOB,\n    )\n\n\ndef _gen_with_cast_to_blob(self: DuckDB.Generator, expression: exp.Expr, result_sql: str) -> str:\n    if _is_binary(expression):\n        blob = exp.DataType.build(\"BLOB\", dialect=\"duckdb\")\n        result_sql = self.sql(exp.Cast(this=result_sql, to=blob))\n    return result_sql\n\n\ndef _cast_to_bit(arg: exp.Expr) -> exp.Expr:\n    if not _is_binary(arg):\n        return arg\n\n    if isinstance(arg, exp.HexString):\n        arg = exp.Unhex(this=exp.Literal.string(arg.this))\n\n    return exp.cast(arg, exp.DType.BIT)\n\n\ndef _prepare_binary_bitwise_args(expression: exp.Binary) -> None:\n    if _is_binary(expression.this):\n        expression.set(\"this\", _cast_to_bit(expression.this))\n    if _is_binary(expression.expression):\n        expression.set(\"expression\", _cast_to_bit(expression.expression))\n\n\ndef _day_navigation_sql(\n    self: DuckDB.Generator, expression: t.Union[exp.NextDay, exp.PreviousDay]\n) -> str:\n    \"\"\"\n    Transpile Snowflake's NEXT_DAY / PREVIOUS_DAY to DuckDB using date arithmetic.\n\n    Returns the DATE of the next/previous occurrence of the specified weekday.\n\n    Formulas:\n    - NEXT_DAY: (target_dow - current_dow + 6) % 7 + 1\n    - PREVIOUS_DAY: (current_dow - target_dow + 6) % 7 + 1\n\n    Supports both literal and non-literal day names:\n    - Literal: Direct lookup (e.g., 'Monday' → 1)\n    - Non-literal: CASE statement for runtime evaluation\n\n    Examples:\n        NEXT_DAY('2024-01-01' (Monday), 'Monday')\n          → (1 - 1 + 6) % 7 + 1 = 6 % 7 + 1 = 7 days → 2024-01-08\n\n        PREVIOUS_DAY('2024-01-15' (Monday), 'Friday')\n          → (1 - 5 + 6) % 7 + 1 = 2 % 7 + 1 = 3 days → 2024-01-12\n    \"\"\"\n    date_expr = expression.this\n    day_name_expr = expression.expression\n\n    # Build ISODOW call for current day of week\n    isodow_call = exp.func(\"ISODOW\", date_expr)\n\n    # Determine target day of week\n    if isinstance(day_name_expr, exp.Literal):\n        # Literal day name: lookup target_dow directly\n        day_name_str = day_name_expr.name.upper()\n        matching_day = next(\n            (day for day in WEEK_START_DAY_TO_DOW if day.startswith(day_name_str)), None\n        )\n        if matching_day:\n            target_dow: exp.Expr = exp.Literal.number(WEEK_START_DAY_TO_DOW[matching_day])\n        else:\n            # Unrecognized day name, use fallback\n            return self.function_fallback_sql(expression)\n    else:\n        # Non-literal day name: build CASE statement for runtime mapping\n        upper_day_name = exp.Upper(this=day_name_expr)\n        target_dow = exp.Case(\n            ifs=[\n                exp.If(\n                    this=exp.func(\n                        \"STARTS_WITH\", upper_day_name.copy(), exp.Literal.string(day[:2])\n                    ),\n                    true=exp.Literal.number(dow_num),\n                )\n                for day, dow_num in WEEK_START_DAY_TO_DOW.items()\n            ]\n        )\n\n    # Calculate days offset and apply interval based on direction\n    if isinstance(expression, exp.NextDay):\n        # NEXT_DAY: (target_dow - current_dow + 6) % 7 + 1\n        days_offset = exp.paren(target_dow - isodow_call + 6, copy=False) % 7 + 1\n        date_with_offset = date_expr + exp.Interval(this=days_offset, unit=exp.var(\"DAY\"))\n    else:  # exp.PreviousDay\n        # PREVIOUS_DAY: (current_dow - target_dow + 6) % 7 + 1\n        days_offset = exp.paren(isodow_call - target_dow + 6, copy=False) % 7 + 1\n        date_with_offset = date_expr - exp.Interval(this=days_offset, unit=exp.var(\"DAY\"))\n\n    # Build final: CAST(date_with_offset AS DATE)\n    return self.sql(exp.cast(date_with_offset, exp.DType.DATE))\n\n\ndef _anyvalue_sql(self: DuckDB.Generator, expression: exp.AnyValue) -> str:\n    # Transform ANY_VALUE(expr HAVING MAX/MIN having_expr) to ARG_MAX_NULL/ARG_MIN_NULL\n    having = expression.this\n    if isinstance(having, exp.HavingMax):\n        func_name = \"ARG_MAX_NULL\" if having.args.get(\"max\") else \"ARG_MIN_NULL\"\n        return self.func(func_name, having.this, having.expression)\n    return self.function_fallback_sql(expression)\n\n\ndef _bitwise_agg_sql(\n    self: DuckDB.Generator,\n    expression: t.Union[exp.BitwiseOrAgg, exp.BitwiseAndAgg, exp.BitwiseXorAgg],\n) -> str:\n    \"\"\"\n    DuckDB's bitwise aggregate functions only accept integer types. For other types:\n    - DECIMAL/STRING: Use CAST(arg AS INT) to convert directly, will round to nearest int\n    - FLOAT/DOUBLE: Use ROUND(arg)::INT to round to nearest integer, required due to float precision loss\n    \"\"\"\n    if isinstance(expression, exp.BitwiseOrAgg):\n        func_name = \"BIT_OR\"\n    elif isinstance(expression, exp.BitwiseAndAgg):\n        func_name = \"BIT_AND\"\n    else:  # exp.BitwiseXorAgg\n        func_name = \"BIT_XOR\"\n\n    arg = expression.this\n\n    if not arg.type:\n        from sqlglot.optimizer.annotate_types import annotate_types\n\n        arg = annotate_types(arg, dialect=self.dialect)\n\n    if arg.is_type(*exp.DataType.REAL_TYPES, *exp.DataType.TEXT_TYPES):\n        if arg.is_type(*exp.DataType.FLOAT_TYPES):\n            # float types need to be rounded first due to precision loss\n            arg = exp.func(\"ROUND\", arg)\n\n        arg = exp.cast(arg, exp.DType.INT)\n\n    return self.func(func_name, arg)\n\n\ndef _literal_sql_with_ws_chr(self: DuckDB.Generator, literal: str) -> str:\n    # DuckDB does not support \\uXXXX escapes, so we must use CHR() instead of replacing them directly\n    if not any(ch in WS_CONTROL_CHARS_TO_DUCK for ch in literal):\n        return self.sql(exp.Literal.string(literal))\n\n    sql_segments: t.List[str] = []\n    for is_ws_control, group in groupby(literal, key=lambda ch: ch in WS_CONTROL_CHARS_TO_DUCK):\n        if is_ws_control:\n            for ch in group:\n                duckdb_char_code = WS_CONTROL_CHARS_TO_DUCK[ch]\n                sql_segments.append(self.func(\"CHR\", exp.Literal.number(str(duckdb_char_code))))\n        else:\n            sql_segments.append(self.sql(exp.Literal.string(\"\".join(group))))\n\n    sql = \" || \".join(sql_segments)\n    return sql if len(sql_segments) == 1 else f\"({sql})\"\n\n\ndef _escape_regex_metachars(\n    self: DuckDB.Generator, delimiters: t.Optional[exp.Expr], delimiters_sql: str\n) -> str:\n    r\"\"\"\n    Escapes regex metacharacters \\ - ^ [ ] for use in character classes regex expressions.\n\n    Literal strings are escaped at transpile time, expressions handled with REPLACE() calls.\n    \"\"\"\n    if not delimiters:\n        return delimiters_sql\n\n    if delimiters.is_string:\n        literal_value = delimiters.this\n        escaped_literal = \"\".join(REGEX_ESCAPE_REPLACEMENTS.get(ch, ch) for ch in literal_value)\n        return _literal_sql_with_ws_chr(self, escaped_literal)\n\n    escaped_sql = delimiters_sql\n    for raw, escaped in REGEX_ESCAPE_REPLACEMENTS.items():\n        escaped_sql = self.func(\n            \"REPLACE\",\n            escaped_sql,\n            self.sql(exp.Literal.string(raw)),\n            self.sql(exp.Literal.string(escaped)),\n        )\n\n    return escaped_sql\n\n\ndef _build_capitalization_sql(\n    self: DuckDB.Generator,\n    value_to_split: str,\n    delimiters_sql: str,\n) -> str:\n    # empty string delimiter --> treat value as one word, no need to split\n    if delimiters_sql == \"''\":\n        return f\"UPPER(LEFT({value_to_split}, 1)) || LOWER(SUBSTRING({value_to_split}, 2))\"\n\n    delim_regex_sql = f\"CONCAT('[', {delimiters_sql}, ']')\"\n    split_regex_sql = f\"CONCAT('([', {delimiters_sql}, ']+|[^', {delimiters_sql}, ']+)')\"\n\n    # REGEXP_EXTRACT_ALL produces a list of string segments, alternating between delimiter and non-delimiter segments.\n    # We do not know whether the first segment is a delimiter or not, so we check the first character of the string\n    # with REGEXP_MATCHES. If the first char is a delimiter, we capitalize even list indexes, otherwise capitalize odd.\n    return self.func(\n        \"ARRAY_TO_STRING\",\n        exp.case()\n        .when(\n            f\"REGEXP_MATCHES(LEFT({value_to_split}, 1), {delim_regex_sql})\",\n            self.func(\n                \"LIST_TRANSFORM\",\n                self.func(\"REGEXP_EXTRACT_ALL\", value_to_split, split_regex_sql),\n                \"(seg, idx) -> CASE WHEN idx % 2 = 0 THEN UPPER(LEFT(seg, 1)) || LOWER(SUBSTRING(seg, 2)) ELSE seg END\",\n            ),\n        )\n        .else_(\n            self.func(\n                \"LIST_TRANSFORM\",\n                self.func(\"REGEXP_EXTRACT_ALL\", value_to_split, split_regex_sql),\n                \"(seg, idx) -> CASE WHEN idx % 2 = 1 THEN UPPER(LEFT(seg, 1)) || LOWER(SUBSTRING(seg, 2)) ELSE seg END\",\n            ),\n        ),\n        \"''\",\n    )\n\n\ndef _initcap_sql(self: DuckDB.Generator, expression: exp.Initcap) -> str:\n    this_sql = self.sql(expression, \"this\")\n    delimiters = expression.args.get(\"expression\")\n    if delimiters is None:\n        # fallback for manually created exp.Initcap w/o delimiters arg\n        delimiters = exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS)\n    delimiters_sql = self.sql(delimiters)\n\n    escaped_delimiters_sql = _escape_regex_metachars(self, delimiters, delimiters_sql)\n\n    return _build_capitalization_sql(self, this_sql, escaped_delimiters_sql)\n\n\ndef _boolxor_agg_sql(self: DuckDB.Generator, expression: exp.BoolxorAgg) -> str:\n    \"\"\"\n    Snowflake's `BOOLXOR_AGG(col)` returns TRUE if exactly one input in `col` is TRUE, FALSE otherwise;\n    Since DuckDB does not have a mapping function, we mimic the behavior by generating `COUNT_IF(col) = 1`.\n\n    DuckDB's COUNT_IF strictly requires boolean inputs, so cast if not already boolean.\n    \"\"\"\n    return self.sql(\n        exp.EQ(\n            this=exp.CountIf(this=_cast_to_boolean(expression.this)),\n            expression=exp.Literal.number(1),\n        )\n    )\n\n\ndef _bitshift_sql(\n    self: DuckDB.Generator, expression: exp.BitwiseLeftShift | exp.BitwiseRightShift\n) -> str:\n    \"\"\"\n    Transform bitshift expressions for DuckDB by injecting BIT/INT128 casts.\n\n    DuckDB's bitwise shift operators don't work with BLOB/BINARY types, so we cast\n    them to BIT for the operation, then cast the result back to the original type.\n\n    Note: Assumes type annotation has been applied with the source dialect.\n    \"\"\"\n    operator = \"<<\" if isinstance(expression, exp.BitwiseLeftShift) else \">>\"\n    result_is_blob = False\n    this = expression.this\n\n    if _is_binary(this):\n        result_is_blob = True\n        expression.set(\"this\", exp.cast(this, exp.DType.BIT))\n    elif expression.args.get(\"requires_int128\"):\n        this.replace(exp.cast(this, exp.DType.INT128))\n\n    result_sql = self.binary(expression, operator)\n\n    # Wrap in parentheses if parent is a bitwise operator to \"fix\" DuckDB precedence issue\n    # DuckDB parses: a << b | c << d  as  (a << b | c) << d\n    if isinstance(expression.parent, exp.Binary):\n        result_sql = self.sql(exp.Paren(this=result_sql))\n\n    if result_is_blob:\n        result_sql = self.sql(\n            exp.Cast(this=result_sql, to=exp.DataType.build(\"BLOB\", dialect=\"duckdb\"))\n        )\n\n    return result_sql\n\n\ndef _scale_rounding_sql(\n    self: DuckDB.Generator,\n    expression: exp.Expr,\n    rounding_func: Type[exp.Expr],\n) -> str | None:\n    \"\"\"\n    Handle scale parameter transformation for rounding functions.\n\n    DuckDB doesn't support the scale parameter for certain functions (e.g., FLOOR, CEIL),\n    so we transform: FUNC(x, n) to ROUND(FUNC(x * 10^n) / 10^n, n)\n\n    Args:\n        self: The DuckDB generator instance\n        expression: The expression to transform (must have 'this', 'decimals', and 'to' args)\n        rounding_func: The rounding function class to use in the transformation\n\n    Returns:\n        The transformed SQL string if decimals parameter exists, None otherwise\n    \"\"\"\n    decimals = expression.args.get(\"decimals\")\n\n    if decimals is None or expression.args.get(\"to\") is not None:\n        return None\n\n    this = expression.this\n    if isinstance(this, exp.Binary):\n        this = exp.Paren(this=this)\n\n    n_int = decimals\n    if not (decimals.is_int or decimals.is_type(*exp.DataType.INTEGER_TYPES)):\n        n_int = exp.cast(decimals, exp.DType.INT)\n\n    pow_ = exp.Pow(this=exp.Literal.number(\"10\"), expression=n_int)\n    rounded = rounding_func(this=exp.Mul(this=this, expression=pow_))\n    result = exp.Div(this=rounded, expression=pow_.copy())\n\n    return self.round_sql(\n        exp.Round(this=result, decimals=decimals, casts_non_integer_decimals=True)\n    )\n\n\ndef _ceil_floor(self: DuckDB.Generator, expression: exp.Floor | exp.Ceil) -> str:\n    scaled_sql = _scale_rounding_sql(self, expression, type(expression))\n    if scaled_sql is not None:\n        return scaled_sql\n    return self.ceil_floor(expression)\n\n\ndef _regr_val_sql(\n    self: DuckDB.Generator,\n    expression: exp.RegrValx | exp.RegrValy,\n) -> str:\n    \"\"\"\n    Transpile Snowflake's REGR_VALX/REGR_VALY to DuckDB equivalent.\n\n    REGR_VALX(y, x) returns NULL if y is NULL; otherwise returns x.\n    REGR_VALY(y, x) returns NULL if x is NULL; otherwise returns y.\n    \"\"\"\n    from sqlglot.optimizer.annotate_types import annotate_types\n\n    y = expression.this\n    x = expression.expression\n\n    # Determine which argument to check for NULL and which to return based on expression type\n    if isinstance(expression, exp.RegrValx):\n        # REGR_VALX: check y for NULL, return x\n        check_for_null = y\n        return_value = x\n        return_value_attr = \"expression\"\n    else:\n        # REGR_VALY: check x for NULL, return y\n        check_for_null = x\n        return_value = y\n        return_value_attr = \"this\"\n\n    # Get the type from the return argument\n    result_type = return_value.type\n\n    # If no type info, annotate the expression to infer types\n    if not result_type or result_type.this == exp.DType.UNKNOWN:\n        try:\n            annotated = annotate_types(expression.copy(), dialect=self.dialect)\n            result_type = getattr(annotated, return_value_attr).type\n        except Exception:\n            pass\n\n    # Default to DOUBLE for regression functions if type still unknown\n    if not result_type or result_type.this == exp.DType.UNKNOWN:\n        result_type = exp.DataType.build(\"DOUBLE\")\n\n    # Cast NULL to the same type as return_value to avoid DuckDB type inference issues\n    typed_null = exp.Cast(this=exp.Null(), to=result_type)\n\n    return self.sql(\n        exp.If(\n            this=exp.Is(this=check_for_null.copy(), expression=exp.Null()),\n            true=typed_null,\n            false=return_value.copy(),\n        )\n    )\n\n\ndef _maybe_corr_null_to_false(\n    expression: t.Union[exp.Filter, exp.Window, exp.Corr],\n) -> t.Optional[t.Union[exp.Filter, exp.Window, exp.Corr]]:\n    corr = expression\n    while isinstance(corr, (exp.Window, exp.Filter)):\n        corr = corr.this\n\n    if not isinstance(corr, exp.Corr) or not corr.args.get(\"null_on_zero_variance\"):\n        return None\n\n    corr.set(\"null_on_zero_variance\", False)\n    return expression\n\n\ndef _date_from_parts_sql(self, expression: exp.DateFromParts) -> str:\n    \"\"\"\n    Snowflake's DATE_FROM_PARTS allows out-of-range values for the month and day input.\n    E.g., larger values (month=13, day=100), zero-values (month=0, day=0), negative values (month=-13, day=-100).\n\n    DuckDB's MAKE_DATE does not support out-of-range values, but DuckDB's INTERVAL type does.\n\n    We convert to date arithmetic:\n    DATE_FROM_PARTS(year, month, day)\n    - MAKE_DATE(year, 1, 1) + INTERVAL (month-1) MONTH + INTERVAL (day-1) DAY\n    \"\"\"\n    year_expr = expression.args.get(\"year\")\n    month_expr = expression.args.get(\"month\")\n    day_expr = expression.args.get(\"day\")\n\n    if expression.args.get(\"allow_overflow\"):\n        base_date: exp.Expr = exp.func(\n            \"MAKE_DATE\", year_expr, exp.Literal.number(1), exp.Literal.number(1)\n        )\n\n        if month_expr:\n            base_date = base_date + exp.Interval(this=month_expr - 1, unit=exp.var(\"MONTH\"))\n\n        if day_expr:\n            base_date = base_date + exp.Interval(this=day_expr - 1, unit=exp.var(\"DAY\"))\n\n        return self.sql(exp.cast(expression=base_date, to=exp.DType.DATE))\n\n    return self.func(\"MAKE_DATE\", year_expr, month_expr, day_expr)\n\n\ndef _round_arg(arg: exp.Expr, round_input: t.Optional[bool] = None) -> exp.Expr:\n    if round_input:\n        return exp.func(\"ROUND\", arg, exp.Literal.number(0))\n    return arg\n\n\ndef _boolnot_sql(self: DuckDB.Generator, expression: exp.Boolnot) -> str:\n    arg = _round_arg(expression.this, expression.args.get(\"round_input\"))\n    return self.sql(exp.not_(exp.paren(arg)))\n\n\ndef _booland_sql(self: DuckDB.Generator, expression: exp.Booland) -> str:\n    round_input = expression.args.get(\"round_input\")\n    left = _round_arg(expression.this, round_input)\n    right = _round_arg(expression.expression, round_input)\n    return self.sql(exp.paren(exp.and_(exp.paren(left), exp.paren(right), wrap=False)))\n\n\ndef _boolor_sql(self: DuckDB.Generator, expression: exp.Boolor) -> str:\n    round_input = expression.args.get(\"round_input\")\n    left = _round_arg(expression.this, round_input)\n    right = _round_arg(expression.expression, round_input)\n    return self.sql(exp.paren(exp.or_(exp.paren(left), exp.paren(right), wrap=False)))\n\n\ndef _xor_sql(self: DuckDB.Generator, expression: exp.Xor) -> str:\n    round_input = expression.args.get(\"round_input\")\n    left = _round_arg(expression.this, round_input)\n    right = _round_arg(expression.expression, round_input)\n    return self.sql(\n        exp.or_(\n            exp.paren(exp.and_(left.copy(), exp.paren(right.not_()), wrap=False)),\n            exp.paren(exp.and_(exp.paren(left.not_()), right.copy(), wrap=False)),\n            wrap=False,\n        )\n    )\n\n\ndef _explode_to_unnest_sql(self: DuckDB.Generator, expression: exp.Lateral) -> str:\n    \"\"\"Handle LATERAL VIEW EXPLODE/INLINE conversion to UNNEST for DuckDB.\"\"\"\n    explode = expression.this\n\n    if isinstance(explode, exp.Inline):\n        # For INLINE, create CROSS JOIN LATERAL (SELECT UNNEST(..., max_depth => 2))\n        # Build the UNNEST call with DuckDB-style named parameter\n        unnest_expr = exp.Unnest(\n            expressions=[\n                explode.this,\n                exp.Kwarg(this=exp.var(\"max_depth\"), expression=exp.Literal.number(2)),\n            ]\n        )\n        select_expr = exp.Select(expressions=[unnest_expr]).subquery()\n\n        alias_expr = expression.args.get(\"alias\")\n        if alias_expr and not alias_expr.this:\n            # we need to provide a table name if not present\n            alias_expr.set(\"this\", exp.to_identifier(f\"_u_{expression.index}\"))\n\n        transformed_lateral_expr = exp.Lateral(this=select_expr, alias=alias_expr)\n        cross_join_lateral_expr = exp.Join(this=transformed_lateral_expr, kind=\"CROSS\")\n\n        return self.sql(cross_join_lateral_expr)\n\n    # For other cases, use the standard conversion\n    return explode_to_unnest_sql(self, expression)\n\n\ndef _sha_sql(\n    self: DuckDB.Generator,\n    expression: exp.Expr,\n    hash_func: str,\n    is_binary: bool = False,\n) -> str:\n    arg = expression.this\n\n    # For SHA2 variants, check digest length (DuckDB only supports SHA256)\n    if hash_func == \"SHA256\":\n        length = expression.text(\"length\") or \"256\"\n        if length != \"256\":\n            self.unsupported(\"DuckDB only supports SHA256 hashing algorithm.\")\n\n    # Cast if type is incompatible with DuckDB\n    if (\n        arg.type\n        and arg.type.this != exp.DType.UNKNOWN\n        and not arg.is_type(*exp.DataType.TEXT_TYPES)\n        and not _is_binary(arg)\n    ):\n        arg = exp.cast(arg, exp.DType.VARCHAR)\n\n    result = self.func(hash_func, arg)\n    return self.func(\"UNHEX\", result) if is_binary else result\n\n\nclass DuckDB(Dialect):\n    NULL_ORDERING = \"nulls_are_last\"\n    SUPPORTS_USER_DEFINED_TYPES = True\n    SAFE_DIVISION = True\n    INDEX_OFFSET = 1\n    CONCAT_COALESCE = True\n    SUPPORTS_ORDER_BY_ALL = True\n    SUPPORTS_FIXED_SIZE_ARRAYS = True\n    STRICT_JSON_PATH_SYNTAX = False\n    NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = True\n\n    # https://duckdb.org/docs/sql/introduction.html#creating-a-new-table\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n\n    DATE_PART_MAPPING = {\n        **Dialect.DATE_PART_MAPPING,\n        \"DAYOFWEEKISO\": \"ISODOW\",\n    }\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    DATE_PART_MAPPING.pop(\"WEEKDAY\")\n\n    INVERSE_TIME_MAPPING = {\n        \"%e\": \"%-d\",  # BigQuery's space-padded day (%e) -> DuckDB's no-padding day (%-d)\n        \"%:z\": \"%z\",  # In DuckDB %z\tcan represent ±HH:MM, ±HHMM, or ±HH.\n        \"%-z\": \"%z\",\n        \"%f_zero\": \"%n\",\n        \"%f_one\": \"%n\",\n        \"%f_two\": \"%n\",\n        \"%f_three\": \"%g\",\n        \"%f_four\": \"%n\",\n        \"%f_five\": \"%n\",\n        \"%f_seven\": \"%n\",\n        \"%f_eight\": \"%n\",\n        \"%f_nine\": \"%n\",\n    }\n\n    def to_json_path(self, path: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        if isinstance(path, exp.Literal):\n            # DuckDB also supports the JSON pointer syntax, where every path starts with a `/`.\n            # Additionally, it allows accessing the back of lists using the `[#-i]` syntax.\n            # This check ensures we'll avoid trying to parse these as JSON paths, which can\n            # either result in a noisy warning or in an invalid representation of the path.\n            path_text = path.name\n            if path_text.startswith(\"/\") or \"[#\" in path_text:\n                return path\n\n        return super().to_json_path(path)\n\n    class Tokenizer(tokens.Tokenizer):\n        BYTE_STRINGS = [(\"e'\", \"'\"), (\"E'\", \"'\")]\n        BYTE_STRING_ESCAPES = [\"'\", \"\\\\\"]\n        HEREDOC_STRINGS = [\"$\"]\n\n        HEREDOC_TAG_IS_IDENTIFIER = True\n        HEREDOC_STRING_ALTERNATIVE = TokenType.PARAMETER\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"//\": TokenType.DIV,\n            \"**\": TokenType.DSTAR,\n            \"^@\": TokenType.CARET_AT,\n            \"@>\": TokenType.AT_GT,\n            \"<@\": TokenType.LT_AT,\n            \"ATTACH\": TokenType.ATTACH,\n            \"BINARY\": TokenType.VARBINARY,\n            \"BITSTRING\": TokenType.BIT,\n            \"BPCHAR\": TokenType.TEXT,\n            \"CHAR\": TokenType.TEXT,\n            \"DATETIME\": TokenType.TIMESTAMPNTZ,\n            \"DETACH\": TokenType.DETACH,\n            \"FORCE\": TokenType.FORCE,\n            \"INSTALL\": TokenType.INSTALL,\n            \"INT8\": TokenType.BIGINT,\n            \"LOGICAL\": TokenType.BOOLEAN,\n            \"MACRO\": TokenType.FUNCTION,\n            \"ONLY\": TokenType.ONLY,\n            \"PIVOT_WIDER\": TokenType.PIVOT,\n            \"POSITIONAL\": TokenType.POSITIONAL,\n            \"RESET\": TokenType.COMMAND,\n            \"ROW\": TokenType.STRUCT,\n            \"SIGNED\": TokenType.INT,\n            \"STRING\": TokenType.TEXT,\n            \"SUMMARIZE\": TokenType.SUMMARIZE,\n            \"TIMESTAMP\": TokenType.TIMESTAMPNTZ,\n            \"TIMESTAMP_S\": TokenType.TIMESTAMP_S,\n            \"TIMESTAMP_MS\": TokenType.TIMESTAMP_MS,\n            \"TIMESTAMP_NS\": TokenType.TIMESTAMP_NS,\n            \"TIMESTAMP_US\": TokenType.TIMESTAMP,\n            \"UBIGINT\": TokenType.UBIGINT,\n            \"UINTEGER\": TokenType.UINT,\n            \"USMALLINT\": TokenType.USMALLINT,\n            \"UTINYINT\": TokenType.UTINYINT,\n            \"VARCHAR\": TokenType.TEXT,\n        }\n        KEYWORDS.pop(\"/*+\")\n\n        SINGLE_TOKENS = {\n            **tokens.Tokenizer.SINGLE_TOKENS,\n            \"$\": TokenType.PARAMETER,\n        }\n\n        COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW}\n\n    Parser = DuckDBParser\n\n    class Generator(generator.Generator):\n        PARAMETER_TOKEN = \"$\"\n        NAMED_PLACEHOLDER_TOKEN = \"$\"\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n        LIMIT_FETCH = \"LIMIT\"\n        STRUCT_DELIMITER = (\"(\", \")\")\n        RENAME_TABLE_WITH_DB = False\n        NVL2_SUPPORTED = False\n        SEMI_ANTI_JOIN_WITH_SIDE = False\n        TABLESAMPLE_KEYWORDS = \"USING SAMPLE\"\n        TABLESAMPLE_SEED_KEYWORD = \"REPEATABLE\"\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        JSON_KEY_VALUE_PAIR_SEP = \",\"\n        IGNORE_NULLS_IN_FUNC = True\n        IGNORE_NULLS_BEFORE_ORDER = False\n        JSON_PATH_BRACKETED_KEY_SUPPORTED = False\n        SUPPORTS_CREATE_TABLE_LIKE = False\n        MULTI_ARG_DISTINCT = False\n        CAN_IMPLEMENT_ARRAY_ANY = True\n        SUPPORTS_TO_NUMBER = False\n        SUPPORTS_WINDOW_EXCLUDE = True\n        COPY_HAS_INTO_KEYWORD = False\n        STAR_EXCEPT = \"EXCLUDE\"\n        PAD_FILL_PATTERN_IS_REQUIRED = True\n        ARRAY_SIZE_DIM_REQUIRED = False\n        NORMALIZE_EXTRACT_DATE_PARTS = True\n        SUPPORTS_LIKE_QUANTIFIERS = False\n        SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = True\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.AnyValue: _anyvalue_sql,\n            exp.ApproxDistinct: approx_count_distinct_sql,\n            exp.Boolnot: _boolnot_sql,\n            exp.Booland: _booland_sql,\n            exp.Boolor: _boolor_sql,\n            exp.Array: transforms.preprocess(\n                [transforms.inherit_struct_field_names],\n                generator=inline_array_unless_query,\n            ),\n            exp.ArrayAppend: array_append_sql(\"LIST_APPEND\"),\n            exp.ArrayCompact: array_compact_sql,\n            exp.ArrayConstructCompact: lambda self, e: self.sql(\n                exp.ArrayCompact(this=exp.Array(expressions=e.expressions))\n            ),\n            exp.ArrayConcat: array_concat_sql(\"LIST_CONCAT\"),\n            exp.ArrayContains: _array_contains_sql,\n            exp.ArrayOverlaps: _array_overlaps_sql,\n            exp.ArrayFilter: rename_func(\"LIST_FILTER\"),\n            exp.ArrayInsert: _array_insert_sql,\n            exp.ArrayPosition: lambda self, e: (\n                self.sql(\n                    exp.Sub(\n                        this=exp.ArrayPosition(this=e.this, expression=e.expression),\n                        expression=exp.Literal.number(1),\n                    )\n                )\n                if e.args.get(\"zero_based\")\n                else self.func(\"ARRAY_POSITION\", e.this, e.expression)\n            ),\n            exp.ArrayRemoveAt: _array_remove_at_sql,\n            exp.ArrayRemove: remove_from_array_using_filter,\n            exp.ArraySort: _array_sort_sql,\n            exp.ArrayPrepend: array_append_sql(\"LIST_PREPEND\", swap_params=True),\n            exp.ArraySum: rename_func(\"LIST_SUM\"),\n            exp.ArrayMax: rename_func(\"LIST_MAX\"),\n            exp.ArrayMin: rename_func(\"LIST_MIN\"),\n            exp.ArrayUniqueAgg: lambda self, e: self.func(\n                \"LIST\", exp.Distinct(expressions=[e.this])\n            ),\n            exp.Base64DecodeBinary: lambda self, e: _base64_decode_sql(self, e, to_string=False),\n            exp.Base64DecodeString: lambda self, e: _base64_decode_sql(self, e, to_string=True),\n            exp.BitwiseAnd: lambda self, e: self._bitwise_op(e, \"&\"),\n            exp.BitwiseAndAgg: _bitwise_agg_sql,\n            exp.BitwiseCount: rename_func(\"BIT_COUNT\"),\n            exp.BitwiseLeftShift: _bitshift_sql,\n            exp.BitwiseOr: lambda self, e: self._bitwise_op(e, \"|\"),\n            exp.BitwiseOrAgg: _bitwise_agg_sql,\n            exp.BitwiseRightShift: _bitshift_sql,\n            exp.BitwiseXorAgg: _bitwise_agg_sql,\n            exp.ByteLength: lambda self, e: self.func(\"OCTET_LENGTH\", e.this),\n            exp.CommentColumnConstraint: no_comment_column_constraint_sql,\n            exp.Corr: lambda self, e: self._corr_sql(e),\n            exp.CosineDistance: rename_func(\"LIST_COSINE_DISTANCE\"),\n            exp.CurrentTime: lambda *_: \"CURRENT_TIME\",\n            exp.CurrentSchemas: lambda self, e: self.func(\n                \"current_schemas\", e.this if e.this else exp.true()\n            ),\n            exp.CurrentTimestamp: lambda self, e: (\n                self.sql(\n                    exp.AtTimeZone(\n                        this=exp.var(\"CURRENT_TIMESTAMP\"), zone=exp.Literal.string(\"UTC\")\n                    )\n                )\n                if e.args.get(\"sysdate\")\n                else \"CURRENT_TIMESTAMP\"\n            ),\n            exp.CurrentVersion: rename_func(\"version\"),\n            exp.Localtime: unsupported_args(\"this\")(lambda *_: \"LOCALTIME\"),\n            exp.DayOfMonth: rename_func(\"DAYOFMONTH\"),\n            exp.DayOfWeek: rename_func(\"DAYOFWEEK\"),\n            exp.DayOfWeekIso: rename_func(\"ISODOW\"),\n            exp.DayOfYear: rename_func(\"DAYOFYEAR\"),\n            exp.Dayname: lambda self, e: (\n                self.func(\"STRFTIME\", e.this, exp.Literal.string(\"%a\"))\n                if e.args.get(\"abbreviated\")\n                else self.func(\"DAYNAME\", e.this)\n            ),\n            exp.Monthname: lambda self, e: (\n                self.func(\"STRFTIME\", e.this, exp.Literal.string(\"%b\"))\n                if e.args.get(\"abbreviated\")\n                else self.func(\"MONTHNAME\", e.this)\n            ),\n            exp.DataType: _datatype_sql,\n            exp.Date: _date_sql,\n            exp.DateAdd: _date_delta_to_binary_interval_op(),\n            exp.DateFromParts: _date_from_parts_sql,\n            exp.DateSub: _date_delta_to_binary_interval_op(),\n            exp.DateDiff: _date_diff_sql,\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.Datetime: no_datetime_sql,\n            exp.DatetimeDiff: _date_diff_sql,\n            exp.DatetimeSub: _date_delta_to_binary_interval_op(),\n            exp.DatetimeAdd: _date_delta_to_binary_interval_op(),\n            exp.DateToDi: lambda self, e: (\n                f\"CAST(STRFTIME({self.sql(e, 'this')}, {DuckDB.DATEINT_FORMAT}) AS INT)\"\n            ),\n            exp.Decode: lambda self, e: encode_decode_sql(self, e, \"DECODE\", replace=False),\n            exp.DiToDate: lambda self, e: (\n                f\"CAST(STRPTIME(CAST({self.sql(e, 'this')} AS TEXT), {DuckDB.DATEINT_FORMAT}) AS DATE)\"\n            ),\n            exp.Encode: lambda self, e: encode_decode_sql(self, e, \"ENCODE\", replace=False),\n            exp.EqualNull: lambda self, e: self.sql(\n                exp.NullSafeEQ(this=e.this, expression=e.expression)\n            ),\n            exp.EuclideanDistance: rename_func(\"LIST_DISTANCE\"),\n            exp.GenerateDateArray: _generate_datetime_array_sql,\n            exp.GenerateSeries: generate_series_sql(\"GENERATE_SERIES\", \"RANGE\"),\n            exp.GenerateTimestampArray: _generate_datetime_array_sql,\n            exp.Getbit: getbit_sql,\n            exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, within_group=False),\n            exp.Explode: rename_func(\"UNNEST\"),\n            exp.IntDiv: lambda self, e: self.binary(e, \"//\"),\n            exp.IsInf: rename_func(\"ISINF\"),\n            exp.IsNan: rename_func(\"ISNAN\"),\n            exp.IsNullValue: lambda self, e: self.sql(\n                exp.func(\"JSON_TYPE\", e.this).eq(exp.Literal.string(\"NULL\"))\n            ),\n            exp.IsArray: lambda self, e: self.sql(\n                exp.func(\"JSON_TYPE\", e.this).eq(exp.Literal.string(\"ARRAY\"))\n            ),\n            exp.Ceil: _ceil_floor,\n            exp.Floor: _ceil_floor,\n            exp.JarowinklerSimilarity: jarowinkler_similarity(\"JARO_WINKLER_SIMILARITY\"),\n            exp.JSONBExists: rename_func(\"JSON_EXISTS\"),\n            exp.JSONExtract: _arrow_json_extract_sql,\n            exp.JSONExtractArray: _json_extract_value_array_sql,\n            exp.JSONFormat: _json_format_sql,\n            exp.JSONValueArray: _json_extract_value_array_sql,\n            exp.Lateral: _explode_to_unnest_sql,\n            exp.LogicalOr: lambda self, e: self.func(\"BOOL_OR\", _cast_to_boolean(e.this)),\n            exp.LogicalAnd: lambda self, e: self.func(\"BOOL_AND\", _cast_to_boolean(e.this)),\n            exp.Select: transforms.preprocess([_seq_to_range_in_generator]),\n            exp.Seq1: lambda self, e: _seq_sql(self, e, 1),\n            exp.Seq2: lambda self, e: _seq_sql(self, e, 2),\n            exp.Seq4: lambda self, e: _seq_sql(self, e, 4),\n            exp.Seq8: lambda self, e: _seq_sql(self, e, 8),\n            exp.BoolxorAgg: _boolxor_agg_sql,\n            exp.MakeInterval: lambda self, e: no_make_interval_sql(self, e, sep=\" \"),\n            exp.Initcap: _initcap_sql,\n            exp.MD5Digest: lambda self, e: self.func(\"UNHEX\", self.func(\"MD5\", e.this)),\n            exp.SHA: lambda self, e: _sha_sql(self, e, \"SHA1\"),\n            exp.SHA1Digest: lambda self, e: _sha_sql(self, e, \"SHA1\", is_binary=True),\n            exp.SHA2: lambda self, e: _sha_sql(self, e, \"SHA256\"),\n            exp.SHA2Digest: lambda self, e: _sha_sql(self, e, \"SHA256\", is_binary=True),\n            exp.MonthsBetween: months_between_sql,\n            exp.NextDay: _day_navigation_sql,\n            exp.PercentileCont: rename_func(\"QUANTILE_CONT\"),\n            exp.PercentileDisc: rename_func(\"QUANTILE_DISC\"),\n            # DuckDB doesn't allow qualified columns inside of PIVOT expressions.\n            # See: https://github.com/duckdb/duckdb/blob/671faf92411182f81dce42ac43de8bfb05d9909e/src/planner/binder/tableref/bind_pivot.cpp#L61-L62\n            exp.Pivot: transforms.preprocess([transforms.unqualify_columns]),\n            exp.PreviousDay: _day_navigation_sql,\n            exp.RegexpILike: lambda self, e: self.func(\n                \"REGEXP_MATCHES\", e.this, e.expression, exp.Literal.string(\"i\")\n            ),\n            exp.RegexpSplit: rename_func(\"STR_SPLIT_REGEX\"),\n            exp.RegrValx: _regr_val_sql,\n            exp.RegrValy: _regr_val_sql,\n            exp.Return: lambda self, e: self.sql(e, \"this\"),\n            exp.ReturnsProperty: lambda self, e: \"TABLE\" if isinstance(e.this, exp.Schema) else \"\",\n            exp.StrToUnix: lambda self, e: self.func(\n                \"EPOCH\", self.func(\"STRPTIME\", e.this, self.format_time(e))\n            ),\n            exp.Struct: _struct_sql,\n            exp.Transform: rename_func(\"LIST_TRANSFORM\"),\n            exp.TimeAdd: _date_delta_to_binary_interval_op(),\n            exp.TimeSub: _date_delta_to_binary_interval_op(),\n            exp.Time: no_time_sql,\n            exp.TimeDiff: _timediff_sql,\n            exp.Timestamp: no_timestamp_sql,\n            exp.TimestampAdd: _date_delta_to_binary_interval_op(),\n            exp.TimestampDiff: lambda self, e: self.func(\n                \"DATE_DIFF\", exp.Literal.string(e.unit), e.expression, e.this\n            ),\n            exp.TimestampSub: _date_delta_to_binary_interval_op(),\n            exp.TimeStrToDate: lambda self, e: self.sql(exp.cast(e.this, exp.DType.DATE)),\n            exp.TimeStrToTime: timestrtotime_sql,\n            exp.TimeStrToUnix: lambda self, e: self.func(\n                \"EPOCH\", exp.cast(e.this, exp.DType.TIMESTAMP)\n            ),\n            exp.TimeToStr: lambda self, e: self.func(\"STRFTIME\", e.this, self.format_time(e)),\n            exp.ToBoolean: _to_boolean_sql,\n            exp.ToVariant: lambda self, e: self.sql(\n                exp.cast(e.this, exp.DataType.build(\"VARIANT\", dialect=\"duckdb\"))\n            ),\n            exp.TimeToUnix: rename_func(\"EPOCH\"),\n            exp.TsOrDiToDi: lambda self, e: (\n                f\"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS TEXT), '-', ''), 1, 8) AS INT)\"\n            ),\n            exp.TsOrDsAdd: _date_delta_to_binary_interval_op(),\n            exp.TsOrDsDiff: lambda self, e: self.func(\n                \"DATE_DIFF\",\n                f\"'{e.args.get('unit') or 'DAY'}'\",\n                exp.cast(e.expression, exp.DType.TIMESTAMP),\n                exp.cast(e.this, exp.DType.TIMESTAMP),\n            ),\n            exp.UnixMicros: lambda self, e: self.func(\"EPOCH_US\", _implicit_datetime_cast(e.this)),\n            exp.UnixMillis: lambda self, e: self.func(\"EPOCH_MS\", _implicit_datetime_cast(e.this)),\n            exp.UnixSeconds: lambda self, e: self.sql(\n                exp.cast(self.func(\"EPOCH\", _implicit_datetime_cast(e.this)), exp.DType.BIGINT)\n            ),\n            exp.UnixToStr: lambda self, e: self.func(\n                \"STRFTIME\", self.func(\"TO_TIMESTAMP\", e.this), self.format_time(e)\n            ),\n            exp.DatetimeTrunc: lambda self, e: self.func(\n                \"DATE_TRUNC\", unit_to_str(e), exp.cast(e.this, exp.DType.DATETIME)\n            ),\n            exp.UnixToTime: _unix_to_time_sql,\n            exp.UnixToTimeStr: lambda self, e: f\"CAST(TO_TIMESTAMP({self.sql(e, 'this')}) AS TEXT)\",\n            exp.VariancePop: rename_func(\"VAR_POP\"),\n            exp.WeekOfYear: rename_func(\"WEEKOFYEAR\"),\n            exp.YearOfWeek: lambda self, e: self.sql(\n                exp.Extract(\n                    this=exp.Var(this=\"ISOYEAR\"),\n                    expression=e.this,\n                )\n            ),\n            exp.YearOfWeekIso: lambda self, e: self.sql(\n                exp.Extract(\n                    this=exp.Var(this=\"ISOYEAR\"),\n                    expression=e.this,\n                )\n            ),\n            exp.Xor: _xor_sql,\n            exp.JSONObjectAgg: rename_func(\"JSON_GROUP_OBJECT\"),\n            exp.JSONBObjectAgg: rename_func(\"JSON_GROUP_OBJECT\"),\n            exp.DateBin: rename_func(\"TIME_BUCKET\"),\n            exp.LastDay: _last_day_sql,\n        }\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n            exp.JSONPathWildcard,\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.BINARY: \"BLOB\",\n            exp.DType.BPCHAR: \"TEXT\",\n            exp.DType.CHAR: \"TEXT\",\n            exp.DType.DATETIME: \"TIMESTAMP\",\n            exp.DType.DECFLOAT: \"DECIMAL(38, 5)\",\n            exp.DType.FLOAT: \"REAL\",\n            exp.DType.JSONB: \"JSON\",\n            exp.DType.NCHAR: \"TEXT\",\n            exp.DType.NVARCHAR: \"TEXT\",\n            exp.DType.UINT: \"UINTEGER\",\n            exp.DType.VARBINARY: \"BLOB\",\n            exp.DType.ROWVERSION: \"BLOB\",\n            exp.DType.VARCHAR: \"TEXT\",\n            exp.DType.TIMESTAMPLTZ: \"TIMESTAMPTZ\",\n            exp.DType.TIMESTAMPNTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMP_S: \"TIMESTAMP_S\",\n            exp.DType.TIMESTAMP_MS: \"TIMESTAMP_MS\",\n            exp.DType.TIMESTAMP_NS: \"TIMESTAMP_NS\",\n            exp.DType.BIGDECIMAL: \"DECIMAL(38, 5)\",\n        }\n\n        # https://github.com/duckdb/duckdb/blob/ff7f24fd8e3128d94371827523dae85ebaf58713/third_party/libpg_query/grammar/keywords/reserved_keywords.list#L1-L77\n        RESERVED_KEYWORDS = {\n            \"array\",\n            \"analyse\",\n            \"union\",\n            \"all\",\n            \"when\",\n            \"in_p\",\n            \"default\",\n            \"create_p\",\n            \"window\",\n            \"asymmetric\",\n            \"to\",\n            \"else\",\n            \"localtime\",\n            \"from\",\n            \"end_p\",\n            \"select\",\n            \"current_date\",\n            \"foreign\",\n            \"with\",\n            \"grant\",\n            \"session_user\",\n            \"or\",\n            \"except\",\n            \"references\",\n            \"fetch\",\n            \"limit\",\n            \"group_p\",\n            \"leading\",\n            \"into\",\n            \"collate\",\n            \"offset\",\n            \"do\",\n            \"then\",\n            \"localtimestamp\",\n            \"check_p\",\n            \"lateral_p\",\n            \"current_role\",\n            \"where\",\n            \"asc_p\",\n            \"placing\",\n            \"desc_p\",\n            \"user\",\n            \"unique\",\n            \"initially\",\n            \"column\",\n            \"both\",\n            \"some\",\n            \"as\",\n            \"any\",\n            \"only\",\n            \"deferrable\",\n            \"null_p\",\n            \"current_time\",\n            \"true_p\",\n            \"table\",\n            \"case\",\n            \"trailing\",\n            \"variadic\",\n            \"for\",\n            \"on\",\n            \"distinct\",\n            \"false_p\",\n            \"not\",\n            \"constraint\",\n            \"current_timestamp\",\n            \"returning\",\n            \"primary\",\n            \"intersect\",\n            \"having\",\n            \"analyze\",\n            \"current_user\",\n            \"and\",\n            \"cast\",\n            \"symmetric\",\n            \"using\",\n            \"order\",\n            \"current_catalog\",\n        }\n\n        UNWRAPPED_INTERVAL_VALUES = (exp.Literal, exp.Paren)\n\n        # DuckDB doesn't generally support CREATE TABLE .. properties\n        # https://duckdb.org/docs/sql/statements/create_table.html\n        PROPERTIES_LOCATION = {\n            prop: exp.Properties.Location.UNSUPPORTED\n            for prop in generator.Generator.PROPERTIES_LOCATION\n        }\n\n        # There are a few exceptions (e.g. temporary tables) which are supported or\n        # can be transpiled to DuckDB, so we explicitly override them accordingly\n        PROPERTIES_LOCATION[exp.LikeProperty] = exp.Properties.Location.POST_SCHEMA\n        PROPERTIES_LOCATION[exp.TemporaryProperty] = exp.Properties.Location.POST_CREATE\n        PROPERTIES_LOCATION[exp.ReturnsProperty] = exp.Properties.Location.POST_ALIAS\n        PROPERTIES_LOCATION[exp.SequenceProperties] = exp.Properties.Location.POST_EXPRESSION\n\n        IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS = (\n            exp.FirstValue,\n            exp.Lag,\n            exp.LastValue,\n            exp.Lead,\n            exp.NthValue,\n        )\n\n        # Template for ZIPF transpilation - placeholders get replaced with actual parameters\n        ZIPF_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            WITH rand AS (SELECT :random_expr AS r),\n            weights AS (\n                SELECT i, 1.0 / POWER(i, :s) AS w\n                FROM RANGE(1, :n + 1) AS t(i)\n            ),\n            cdf AS (\n                SELECT i, SUM(w) OVER (ORDER BY i) / SUM(w) OVER () AS p\n                FROM weights\n            )\n            SELECT MIN(i)\n            FROM cdf\n            WHERE p >= (SELECT r FROM rand)\n            \"\"\"\n        )\n\n        # Template for NORMAL transpilation using Box-Muller transform\n        # mean + (stddev * sqrt(-2 * ln(u1)) * cos(2 * pi * u2))\n        NORMAL_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \":mean + (:stddev * SQRT(-2 * LN(GREATEST(:u1, 1e-10))) * COS(2 * PI() * :u2))\"\n        )\n\n        # Template for generating a seeded pseudo-random value in [0, 1) from a hash\n        SEEDED_RANDOM_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"(ABS(HASH(:seed)) % 1000000) / 1000000.0\"\n        )\n\n        # Template for generating signed and unsigned SEQ values within a specified range\n        SEQ_UNSIGNED: exp.Expr = exp.maybe_parse(\":base % :max_val\")\n        SEQ_SIGNED: exp.Expr = exp.maybe_parse(\n            \"(CASE WHEN :base % :max_val >= :half \"\n            \"THEN :base % :max_val - :max_val \"\n            \"ELSE :base % :max_val END)\"\n        )\n\n        # Template for MAP_CAT transpilation - Snowflake semantics:\n        # 1. Returns NULL if either input is NULL\n        # 2. For duplicate keys, prefers non-NULL value (COALESCE(m2[k], m1[k]))\n        # 3. Filters out entries with NULL values from the result\n        MAPCAT_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            CASE\n                WHEN :map1 IS NULL OR :map2 IS NULL THEN NULL\n                ELSE MAP_FROM_ENTRIES(LIST_FILTER(LIST_TRANSFORM(\n                    LIST_DISTINCT(LIST_CONCAT(MAP_KEYS(:map1), MAP_KEYS(:map2))),\n                    __k -> STRUCT_PACK(key := __k, value := COALESCE(:map2[__k], :map1[__k]))\n                ), __x -> __x.value IS NOT NULL))\n            END\n            \"\"\"\n        )\n\n        # Mappings for EXTRACT/DATE_PART transpilation\n        # Maps Snowflake specifiers unsupported in DuckDB to strftime format codes\n        EXTRACT_STRFTIME_MAPPINGS: t.Dict[str, t.Tuple[str, str]] = {\n            \"WEEKISO\": (\"%V\", \"INTEGER\"),\n            \"YEAROFWEEK\": (\"%G\", \"INTEGER\"),\n            \"YEAROFWEEKISO\": (\"%G\", \"INTEGER\"),\n            \"NANOSECOND\": (\"%n\", \"BIGINT\"),\n        }\n\n        # Maps epoch-based specifiers to DuckDB epoch functions\n        EXTRACT_EPOCH_MAPPINGS: t.Dict[str, str] = {\n            \"EPOCH_SECOND\": \"EPOCH\",\n            \"EPOCH_MILLISECOND\": \"EPOCH_MS\",\n            \"EPOCH_MICROSECOND\": \"EPOCH_US\",\n            \"EPOCH_NANOSECOND\": \"EPOCH_NS\",\n        }\n\n        # Template for BITMAP_CONSTRUCT_AGG transpilation\n        #\n        # BACKGROUND:\n        # Snowflake's BITMAP_CONSTRUCT_AGG aggregates integers into a compact binary bitmap.\n        # Supports values in range 0-32767, this version returns NULL if any value is out of range\n        # See: https://docs.snowflake.com/en/sql-reference/functions/bitmap_construct_agg\n        # See: https://docs.snowflake.com/en/user-guide/querying-bitmaps-for-distinct-counts\n        #\n        # Snowflake uses two different formats based on the number of unique values:\n        #\n        # Format 1 - Small bitmap (< 5 unique values): Length of 10 bytes\n        #   Bytes 0-1: Count of values as 2-byte big-endian integer (e.g., 3 values = 0x0003)\n        #   Bytes 2-9: Up to 4 values, each as 2-byte little-endian integers, zero-padded to 8 bytes\n        #   Example: Values [1, 2, 3] -> 0x0003 0100 0200 0300 0000 (hex)\n        #                                count  v1   v2   v3   pad\n        #\n        # Format 2 - Large bitmap (>= 5 unique values): Length of 10 + (2 * count) bytes\n        #   Bytes 0-9: Fixed header 0x08 followed by 9 zero bytes\n        #   Bytes 10+: Each value as 2-byte little-endian integer (no padding)\n        #   Example: Values [1,2,3,4,5] -> 0x08 00000000 00000000 00 0100 0200 0300 0400 0500\n        #                                  hdr  ----9 zero bytes----  v1   v2   v3   v4   v5\n        #\n        # TEMPLATE STRUCTURE\n        #\n        # Phase 1 - Innermost subquery: Data preparation\n        #   SELECT LIST_SORT(...) AS l\n        #   - Aggregates all input values into a list, remove NULLs, duplicates and sorts\n        #   Result: Clean, sorted list of unique non-null integers stored as 'l'\n        #\n        # Phase 2 - Middle subquery: Hex string construction\n        #   LIST_TRANSFORM(...)\n        #   - Converts each integer to 2-byte little-endian hex representation\n        #   - & 255 extracts low byte, >> 8 extracts high byte\n        #   - LIST_REDUCE: Concatenates all hex pairs into single string 'h'\n        #   Result: Hex string of all values\n        #\n        # Phase 3 - Outer SELECT: Final bitmap assembly\n        #   LENGTH(l) < 5:\n        #   - Small format: 2-byte count (big-endian via %04X) + values + zero padding\n        #   LENGTH(l) >= 5:\n        #   - Large format: Fixed 10-byte header + values (no padding needed)\n        #   Result: Complete binary bitmap as BLOB\n        #\n        BITMAP_CONSTRUCT_AGG_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            SELECT CASE\n                WHEN l IS NULL OR LENGTH(l) = 0 THEN NULL\n                WHEN LENGTH(l) != LENGTH(LIST_FILTER(l, __v -> __v BETWEEN 0 AND 32767)) THEN NULL\n                WHEN LENGTH(l) < 5 THEN UNHEX(PRINTF('%04X', LENGTH(l)) || h || REPEAT('00', GREATEST(0, 4 - LENGTH(l)) * 2))\n                ELSE UNHEX('08000000000000000000' || h)\n            END\n            FROM (\n                SELECT l, COALESCE(LIST_REDUCE(\n                    LIST_TRANSFORM(l, __x -> PRINTF('%02X%02X', CAST(__x AS INT) & 255, (CAST(__x AS INT) >> 8) & 255)),\n                    (__a, __b) -> __a || __b, ''\n                ), '') AS h\n                FROM (SELECT LIST_SORT(LIST_DISTINCT(LIST(:arg) FILTER(NOT :arg IS NULL))) AS l)\n            )\n            \"\"\"\n        )\n\n        # Template for RANDSTR transpilation - placeholders get replaced with actual parameters\n        RANDSTR_TEMPLATE: exp.Expr = exp.maybe_parse(\n            f\"\"\"\n            SELECT LISTAGG(\n                SUBSTRING(\n                    '{RANDSTR_CHAR_POOL}',\n                    1 + CAST(FLOOR(random_value * 62) AS INT),\n                    1\n                ),\n                ''\n            )\n            FROM (\n                SELECT (ABS(HASH(i + :seed)) % 1000) / 1000.0 AS random_value\n                FROM RANGE(:length) AS t(i)\n            )\n            \"\"\",\n        )\n\n        # Template for MINHASH transpilation\n        # Computes k minimum hash values across aggregated data using DuckDB list functions\n        # Returns JSON matching Snowflake format: {\"state\": [...], \"type\": \"minhash\", \"version\": 1}\n        MINHASH_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            SELECT JSON_OBJECT('state', LIST(min_h ORDER BY seed), 'type', 'minhash', 'version', 1)\n            FROM (\n                SELECT seed, LIST_MIN(LIST_TRANSFORM(vals, __v -> HASH(CAST(__v AS VARCHAR) || CAST(seed AS VARCHAR)))) AS min_h\n                FROM (SELECT LIST(:expr) AS vals), RANGE(0, :k) AS t(seed)\n            )\n            \"\"\",\n        )\n\n        # Template for MINHASH_COMBINE transpilation\n        # Combines multiple minhash signatures by taking element-wise minimum\n        MINHASH_COMBINE_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            SELECT JSON_OBJECT('state', LIST(min_h ORDER BY idx), 'type', 'minhash', 'version', 1)\n            FROM (\n                SELECT\n                    pos AS idx,\n                    MIN(val) AS min_h\n                FROM\n                    UNNEST(LIST(:expr)) AS _(sig),\n                    UNNEST(CAST(sig -> 'state' AS UBIGINT[])) WITH ORDINALITY AS t(val, pos)\n                GROUP BY pos\n            )\n            \"\"\",\n        )\n\n        # Template for APPROXIMATE_SIMILARITY transpilation\n        # Computes multi-way Jaccard similarity: fraction of positions where ALL signatures agree\n        APPROXIMATE_SIMILARITY_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            SELECT CAST(SUM(CASE WHEN num_distinct = 1 THEN 1 ELSE 0 END) AS DOUBLE) / COUNT(*)\n            FROM (\n                SELECT pos, COUNT(DISTINCT h) AS num_distinct\n                FROM (\n                    SELECT h, pos\n                    FROM UNNEST(LIST(:expr)) AS _(sig),\n                         UNNEST(CAST(sig -> 'state' AS UBIGINT[])) WITH ORDINALITY AS s(h, pos)\n                )\n                GROUP BY pos\n            )\n            \"\"\",\n        )\n\n        # Template for ARRAYS_ZIP transpilation\n        # Snowflake pads to longest array; DuckDB LIST_ZIP truncates to shortest\n        # Uses RANGE + indexing to match Snowflake behavior\n        ARRAYS_ZIP_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            CASE WHEN :null_check THEN NULL\n            WHEN :all_empty_check THEN [:empty_struct]\n            ELSE LIST_TRANSFORM(RANGE(0, :max_len), __i -> :transform_struct)\n            END\n            \"\"\",\n        )\n\n        # Shared bag semantics outer frame for ARRAY_EXCEPT and ARRAY_INTERSECTION.\n        # Each element is paired with its 1-based position via LIST_ZIP, then filtered\n        # by a comparison operator (supplied via :cond) that determines the operation:\n        #   EXCEPT (>):        keep the N-th occurrence only if N > count in arr2\n        #                      e.g. [2,2,2] EXCEPT [2,2] -> [2]\n        #   INTERSECTION (<=): keep the N-th occurrence only if N <= count in arr2\n        #                      e.g. [2,2,2] INTERSECT [2,2] -> [2,2]\n        # IS NOT DISTINCT FROM is used for NULL-safe element comparison.\n        ARRAY_BAG_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            CASE\n                WHEN :arr1 IS NULL OR :arr2 IS NULL THEN NULL\n                ELSE LIST_TRANSFORM(\n                    LIST_FILTER(\n                        LIST_ZIP(:arr1, GENERATE_SERIES(1, LEN(:arr1))),\n                        pair -> :cond\n                    ),\n                    pair -> pair[0]\n                )\n            END\n            \"\"\"\n        )\n\n        ARRAY_EXCEPT_CONDITION: exp.Expr = exp.maybe_parse(\n            \"LEN(LIST_FILTER(:arr1[1:pair[1]], e -> e IS NOT DISTINCT FROM pair[0]))\"\n            \" > LEN(LIST_FILTER(:arr2, e -> e IS NOT DISTINCT FROM pair[0]))\"\n        )\n\n        ARRAY_INTERSECTION_CONDITION: exp.Expr = exp.maybe_parse(\n            \"LEN(LIST_FILTER(:arr1[1:pair[1]], e -> e IS NOT DISTINCT FROM pair[0]))\"\n            \" <= LEN(LIST_FILTER(:arr2, e -> e IS NOT DISTINCT FROM pair[0]))\"\n        )\n\n        # Set semantics for ARRAY_EXCEPT. Deduplicates arr1 via LIST_DISTINCT, then\n        # filters out any element that appears at least once in arr2.\n        #   e.g. [1,1,2,3] EXCEPT [1] -> [2,3]\n        # IS NOT DISTINCT FROM is used for NULL-safe element comparison.\n        ARRAY_EXCEPT_SET_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            CASE\n                WHEN :arr1 IS NULL OR :arr2 IS NULL THEN NULL\n                ELSE LIST_FILTER(\n                    LIST_DISTINCT(:arr1),\n                    e -> LEN(LIST_FILTER(:arr2, x -> x IS NOT DISTINCT FROM e)) = 0\n                )\n            END\n            \"\"\"\n        )\n\n        # Template for STRTOK function transpilation\n        #\n        # DuckDB itself doesn't have a strtok function. This handles the transpilation from Snowflake to DuckDB.\n        # We may need to adjust this if we want to support transpilation from other dialects\n        #\n        # CASE\n        #     -- Snowflake: empty delimiter + empty input string → NULL\n        #     WHEN delimiter = '' AND input_str = '' THEN NULL\n        #\n        #     -- Snowflake: empty delimiter + non-empty input string → treats whole input as 1 token → return input string if index is 1\n        #     WHEN delimiter = '' AND index = 1 THEN input_str\n        #\n        #     -- Snowflake: empty delimiter + non-empty input string → treats whole input as 1 token → return NULL if index is not 1\n        #     WHEN delimiter = '' THEN NULL\n        #\n        #     -- Snowflake: negative indices return NULL\n        #     WHEN index < 0 THEN NULL\n        #\n        #     -- Snowflake: return NULL if any argument is NULL\n        #     WHEN input_str IS NULL OR delimiter IS NULL OR index IS NULL THEN NULL\n        #\n        #\n        #     ELSE LIST_FILTER(\n        #         REGEXP_SPLIT_TO_ARRAY(\n        #             input_str,\n        #             CASE\n        #                 -- if delimiter is '', we don't want to surround it with '[' and ']' as '[]' is invalid for DuckDB\n        #                 WHEN delimiter = '' THEN ''\n        #\n        #                 -- handle problematic regex characters in delimiter with REGEXP_REPLACE\n        #                 -- turn delimiter into a regex char set, otherwise DuckDB will match in order, which we don't want\n        #                 ELSE '[' || REGEXP_REPLACE(delimiter, problematic_char_set, '\\\\\\1', 'g') || ']'\n        #             END\n        #         ),\n        #\n        #         -- Snowflake: don't return empty strings\n        #         x -> NOT x = ''\n        #     )[index]\n        # END\n        STRTOK_TEMPLATE: exp.Expr = exp.maybe_parse(\n            \"\"\"\n            CASE\n                WHEN :delimiter = '' AND :string = '' THEN NULL\n                WHEN :delimiter = '' AND :part_index = 1 THEN :string\n                WHEN :delimiter = '' THEN NULL\n                WHEN :part_index < 0 THEN NULL\n                WHEN :string IS NULL OR :delimiter IS NULL OR :part_index IS NULL THEN NULL\n                ELSE :base_func\n            END\n            \"\"\"\n        )\n\n        def _array_bag_sql(self, condition: exp.Expr, arr1: exp.Expr, arr2: exp.Expr) -> str:\n            cond = exp.Paren(this=exp.replace_placeholders(condition, arr1=arr1, arr2=arr2))\n            return self.sql(\n                exp.replace_placeholders(self.ARRAY_BAG_TEMPLATE, arr1=arr1, arr2=arr2, cond=cond)\n            )\n\n        def timeslice_sql(self: DuckDB.Generator, expression: exp.TimeSlice) -> str:\n            \"\"\"\n            Transform Snowflake's TIME_SLICE to DuckDB's time_bucket.\n\n            Snowflake: TIME_SLICE(date_expr, slice_length, 'UNIT' [, 'START'|'END'])\n            DuckDB:    time_bucket(INTERVAL 'slice_length' UNIT, date_expr)\n\n            For 'END' kind, add the interval to get the end of the slice.\n            For DATE type with 'END', cast result back to DATE to preserve type.\n            \"\"\"\n            date_expr = expression.this\n            slice_length = expression.expression\n            unit = expression.unit\n            kind = expression.text(\"kind\").upper()\n\n            # Create INTERVAL expression: INTERVAL 'N' UNIT\n            interval_expr = exp.Interval(this=slice_length, unit=unit)\n\n            # Create base time_bucket expression\n            time_bucket_expr = exp.func(\"time_bucket\", interval_expr, date_expr)\n\n            # Check if we need the end of the slice (default is start)\n            if not kind == \"END\":\n                # For 'START', return time_bucket directly\n                return self.sql(time_bucket_expr)\n\n            # For 'END', add the interval to get end of slice\n            add_expr = exp.Add(this=time_bucket_expr, expression=interval_expr.copy())\n\n            # If input is DATE type, cast result back to DATE to preserve type\n            # DuckDB converts DATE to TIMESTAMP when adding intervals\n            if date_expr.is_type(exp.DType.DATE):\n                return self.sql(exp.cast(add_expr, exp.DType.DATE))\n\n            return self.sql(add_expr)\n\n        def bitmapbucketnumber_sql(\n            self: DuckDB.Generator, expression: exp.BitmapBucketNumber\n        ) -> str:\n            \"\"\"\n            Transpile BITMAP_BUCKET_NUMBER function from Snowflake to DuckDB equivalent.\n\n            Snowflake's BITMAP_BUCKET_NUMBER returns a 1-based bucket identifier where:\n            - Each bucket covers 32,768 values\n            - Bucket numbering starts at 1\n            - Formula: ((value - 1) // 32768) + 1 for positive values\n\n            For non-positive values (0 and negative), we use value // 32768 to avoid\n            producing bucket 0 or positive bucket IDs for negative inputs.\n            \"\"\"\n            value = expression.this\n\n            positive_formula = ((value - 1) // 32768) + 1\n            non_positive_formula = value // 32768\n\n            # CASE WHEN value > 0 THEN ((value - 1) // 32768) + 1 ELSE value // 32768 END\n            case_expr = (\n                exp.case()\n                .when(exp.GT(this=value, expression=exp.Literal.number(0)), positive_formula)\n                .else_(non_positive_formula)\n            )\n            return self.sql(case_expr)\n\n        def bitmapbitposition_sql(self: DuckDB.Generator, expression: exp.BitmapBitPosition) -> str:\n            \"\"\"\n            Transpile Snowflake's BITMAP_BIT_POSITION to DuckDB CASE expression.\n\n            Snowflake's BITMAP_BIT_POSITION behavior:\n            - For n <= 0: returns ABS(n) % 32768\n            - For n > 0: returns (n - 1) % 32768 (maximum return value is 32767)\n            \"\"\"\n            this = expression.this\n\n            return self.sql(\n                exp.Mod(\n                    this=exp.Paren(\n                        this=exp.If(\n                            this=exp.GT(this=this, expression=exp.Literal.number(0)),\n                            true=this - exp.Literal.number(1),\n                            false=exp.Abs(this=this),\n                        )\n                    ),\n                    expression=MAX_BIT_POSITION,\n                )\n            )\n\n        def bitmapconstructagg_sql(\n            self: DuckDB.Generator, expression: exp.BitmapConstructAgg\n        ) -> str:\n            \"\"\"\n            Transpile Snowflake's BITMAP_CONSTRUCT_AGG to DuckDB equivalent.\n            Uses a pre-parsed template with placeholders replaced by expression nodes.\n\n            Snowflake bitmap format:\n            - Small (< 5 unique values): 2-byte count (big-endian) + values (little-endian) + padding to 10 bytes\n            - Large (>= 5 unique values): 10-byte header (0x08 + 9 zeros) + values (little-endian)\n            \"\"\"\n            arg = expression.this\n            return f\"({self.sql(exp.replace_placeholders(self.BITMAP_CONSTRUCT_AGG_TEMPLATE, arg=arg))})\"\n\n        def nthvalue_sql(self: DuckDB.Generator, expression: exp.NthValue) -> str:\n            from_first = expression.args.get(\"from_first\", True)\n            if not from_first:\n                self.unsupported(\"DuckDB's NTH_VALUE doesn't support starting from the end \")\n\n            return self.function_fallback_sql(expression)\n\n        def randstr_sql(self: DuckDB.Generator, expression: exp.Randstr) -> str:\n            \"\"\"\n            Transpile Snowflake's RANDSTR to DuckDB equivalent using deterministic hash-based random.\n            Uses a pre-parsed template with placeholders replaced by expression nodes.\n\n            RANDSTR(length, generator) generates a random string of specified length.\n            - With numeric seed: Use HASH(i + seed) for deterministic output (same seed = same result)\n            - With RANDOM(): Use RANDOM() in the hash for non-deterministic output\n            - No generator: Use default seed value\n            \"\"\"\n            length = expression.this\n            generator = expression.args.get(\"generator\")\n\n            if generator:\n                if isinstance(generator, exp.Rand):\n                    # If it's RANDOM(), use its seed if available, otherwise use RANDOM() itself\n                    seed_value = generator.this or generator\n                else:\n                    # Const/int or other expression - use as seed directly\n                    seed_value = generator\n            else:\n                # No generator specified, use default seed (arbitrary but deterministic)\n                seed_value = exp.Literal.number(RANDSTR_SEED)\n\n            replacements = {\"seed\": seed_value, \"length\": length}\n            return f\"({self.sql(exp.replace_placeholders(self.RANDSTR_TEMPLATE, **replacements))})\"\n\n        def zipf_sql(self: DuckDB.Generator, expression: exp.Zipf) -> str:\n            \"\"\"\n            Transpile Snowflake's ZIPF to DuckDB using CDF-based inverse sampling.\n            Uses a pre-parsed template with placeholders replaced by expression nodes.\n            \"\"\"\n            s = expression.this\n            n = expression.args[\"elementcount\"]\n            gen = expression.args[\"gen\"]\n\n            if not isinstance(gen, exp.Rand):\n                # (ABS(HASH(seed)) % 1000000) / 1000000.0\n                random_expr: exp.Expr = exp.Div(\n                    this=exp.Paren(\n                        this=exp.Mod(\n                            this=exp.Abs(this=exp.Anonymous(this=\"HASH\", expressions=[gen.copy()])),\n                            expression=exp.Literal.number(1000000),\n                        )\n                    ),\n                    expression=exp.Literal.number(1000000.0),\n                )\n            else:\n                # Use RANDOM() for non-deterministic output\n                random_expr = exp.Rand()\n\n            replacements = {\"s\": s, \"n\": n, \"random_expr\": random_expr}\n            return f\"({self.sql(exp.replace_placeholders(self.ZIPF_TEMPLATE, **replacements))})\"\n\n        def tobinary_sql(self: DuckDB.Generator, expression: exp.ToBinary) -> str:\n            \"\"\"\n            TO_BINARY and TRY_TO_BINARY transpilation:\n            - 'HEX': TO_BINARY('48454C50', 'HEX') → UNHEX('48454C50')\n            - 'UTF-8': TO_BINARY('TEST', 'UTF-8') → ENCODE('TEST')\n            - 'BASE64': TO_BINARY('SEVMUA==', 'BASE64') → FROM_BASE64('SEVMUA==')\n\n            For TRY_TO_BINARY (safe=True), wrap with TRY():\n            - 'HEX': TRY_TO_BINARY('invalid', 'HEX') → TRY(UNHEX('invalid'))\n            \"\"\"\n            value = expression.this\n            format_arg = expression.args.get(\"format\")\n            is_safe = expression.args.get(\"safe\")\n            is_binary = _is_binary(expression)\n\n            if not format_arg and not is_binary:\n                func_name = \"TRY_TO_BINARY\" if is_safe else \"TO_BINARY\"\n                return self.func(func_name, value)\n\n            # Snowflake defaults to HEX encoding when no format is specified\n            fmt = format_arg.name.upper() if format_arg else \"HEX\"\n\n            if fmt in (\"UTF-8\", \"UTF8\"):\n                # DuckDB ENCODE always uses UTF-8, no charset parameter needed\n                result = self.func(\"ENCODE\", value)\n            elif fmt == \"BASE64\":\n                result = self.func(\"FROM_BASE64\", value)\n            elif fmt == \"HEX\":\n                result = self.func(\"UNHEX\", value)\n            else:\n                if is_safe:\n                    return self.sql(exp.null())\n                else:\n                    self.unsupported(f\"format {fmt} is not supported\")\n                    result = self.func(\"TO_BINARY\", value)\n            return f\"TRY({result})\" if is_safe else result\n\n        def _greatest_least_sql(\n            self: DuckDB.Generator, expression: exp.Greatest | exp.Least\n        ) -> str:\n            \"\"\"\n            Handle GREATEST/LEAST functions with dialect-aware NULL behavior.\n\n            - If ignore_nulls=False (BigQuery-style): return NULL if any argument is NULL\n            - If ignore_nulls=True (DuckDB/PostgreSQL-style): ignore NULLs, return greatest/least non-NULL value\n            \"\"\"\n            # Get all arguments\n            all_args = [expression.this, *expression.expressions]\n            fallback_sql = self.function_fallback_sql(expression)\n\n            if expression.args.get(\"ignore_nulls\"):\n                # DuckDB/PostgreSQL behavior: use native GREATEST/LEAST (ignores NULLs)\n                return self.sql(fallback_sql)\n\n            # return NULL if any argument is NULL\n            case_expr = exp.case().when(\n                exp.or_(*[arg.is_(exp.null()) for arg in all_args], copy=False),\n                exp.null(),\n                copy=False,\n            )\n            case_expr.set(\"default\", fallback_sql)\n            return self.sql(case_expr)\n\n        def generator_sql(self, expression: exp.Generator) -> str:\n            # Transpile Snowflake GENERATOR to DuckDB range()\n            rowcount = expression.args.get(\"rowcount\")\n            time_limit = expression.args.get(\"time_limit\")\n\n            if time_limit:\n                self.unsupported(\"GENERATOR TIMELIMIT parameter is not supported in DuckDB\")\n\n            if not rowcount:\n                self.unsupported(\"GENERATOR without ROWCOUNT is not supported in DuckDB\")\n                return self.func(\"range\", exp.Literal.number(0))\n\n            return self.func(\"range\", rowcount)\n\n        def greatest_sql(self: DuckDB.Generator, expression: exp.Greatest) -> str:\n            return self._greatest_least_sql(expression)\n\n        def least_sql(self: DuckDB.Generator, expression: exp.Least) -> str:\n            return self._greatest_least_sql(expression)\n\n        def lambda_sql(\n            self, expression: exp.Lambda, arrow_sep: str = \"->\", wrap: bool = True\n        ) -> str:\n            if expression.args.get(\"colon\"):\n                prefix = \"LAMBDA \"\n                arrow_sep = \":\"\n                wrap = False\n            else:\n                prefix = \"\"\n\n            lambda_sql = super().lambda_sql(expression, arrow_sep=arrow_sep, wrap=wrap)\n            return f\"{prefix}{lambda_sql}\"\n\n        def show_sql(self, expression: exp.Show) -> str:\n            return f\"SHOW {expression.name}\"\n\n        def sortarray_sql(self, expression: exp.SortArray) -> str:\n            arr = expression.this\n            asc = expression.args.get(\"asc\")\n            nulls_first = expression.args.get(\"nulls_first\")\n\n            if not isinstance(asc, exp.Boolean) and not isinstance(nulls_first, exp.Boolean):\n                return self.func(\"LIST_SORT\", arr, asc, nulls_first)\n\n            nulls_are_first = nulls_first == exp.true()\n            nulls_first_sql = exp.Literal.string(\"NULLS FIRST\") if nulls_are_first else None\n\n            if not isinstance(asc, exp.Boolean):\n                return self.func(\"LIST_SORT\", arr, asc, nulls_first_sql)\n\n            descending = asc == exp.false()\n\n            if not descending and not nulls_are_first:\n                return self.func(\"LIST_SORT\", arr)\n            if not nulls_are_first:\n                return self.func(\"ARRAY_REVERSE_SORT\", arr)\n            return self.func(\n                \"LIST_SORT\",\n                arr,\n                exp.Literal.string(\"DESC\" if descending else \"ASC\"),\n                exp.Literal.string(\"NULLS FIRST\"),\n            )\n\n        def install_sql(self, expression: exp.Install) -> str:\n            force = \"FORCE \" if expression.args.get(\"force\") else \"\"\n            this = self.sql(expression, \"this\")\n            from_clause = expression.args.get(\"from_\")\n            from_clause = f\" FROM {from_clause}\" if from_clause else \"\"\n            return f\"{force}INSTALL {this}{from_clause}\"\n\n        def approxtopk_sql(self, expression: exp.ApproxTopK) -> str:\n            self.unsupported(\n                \"APPROX_TOP_K cannot be transpiled to DuckDB due to incompatible return types. \"\n            )\n            return self.function_fallback_sql(expression)\n\n        def fromiso8601timestamp_sql(self, expression: exp.FromISO8601Timestamp) -> str:\n            return self.sql(exp.cast(expression.this, exp.DType.TIMESTAMPTZ))\n\n        def strposition_sql(self, expression: exp.StrPosition) -> str:\n            position = expression.args.get(\"position\")\n            if expression.args.get(\"clamp_position\") and position:\n                expression = expression.copy()\n                expression.set(\n                    \"position\",\n                    exp.If(\n                        this=exp.LTE(this=position, expression=exp.Literal.number(0)),\n                        true=exp.Literal.number(1),\n                        false=position.copy(),\n                    ),\n                )\n\n            return strposition_sql(self, expression)\n\n        def strtotime_sql(self, expression: exp.StrToTime) -> str:\n            # Check if target_type requires TIMESTAMPTZ (for LTZ/TZ variants)\n            target_type = expression.args.get(\"target_type\")\n            needs_tz = target_type and target_type.this in (\n                exp.DType.TIMESTAMPLTZ,\n                exp.DType.TIMESTAMPTZ,\n            )\n\n            if expression.args.get(\"safe\"):\n                formatted_time = self.format_time(expression)\n                cast_type = exp.DType.TIMESTAMPTZ if needs_tz else exp.DType.TIMESTAMP\n                return self.sql(\n                    exp.cast(self.func(\"TRY_STRPTIME\", expression.this, formatted_time), cast_type)\n                )\n\n            base_sql = str_to_time_sql(self, expression)\n            if needs_tz:\n                return self.sql(\n                    exp.cast(\n                        base_sql,\n                        exp.DataType(this=exp.DType.TIMESTAMPTZ),\n                    )\n                )\n            return base_sql\n\n        def strtodate_sql(self, expression: exp.StrToDate) -> str:\n            formatted_time = self.format_time(expression)\n            function_name = \"STRPTIME\" if not expression.args.get(\"safe\") else \"TRY_STRPTIME\"\n            return self.sql(\n                exp.cast(\n                    self.func(function_name, expression.this, formatted_time),\n                    exp.DataType(this=exp.DType.DATE),\n                )\n            )\n\n        def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:\n            this = expression.this\n            time_format = self.format_time(expression)\n            safe = expression.args.get(\"safe\")\n            time_type = exp.DataType.build(\"TIME\", dialect=\"duckdb\")\n            cast_expr = exp.TryCast if safe else exp.Cast\n\n            if time_format:\n                func_name = \"TRY_STRPTIME\" if safe else \"STRPTIME\"\n                strptime = exp.Anonymous(this=func_name, expressions=[this, time_format])\n                return self.sql(cast_expr(this=strptime, to=time_type))\n\n            if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):\n                return self.sql(this)\n\n            return self.sql(cast_expr(this=this, to=time_type))\n\n        def currentdate_sql(self, expression: exp.CurrentDate) -> str:\n            if not expression.this:\n                return \"CURRENT_DATE\"\n\n            expr = exp.Cast(\n                this=exp.AtTimeZone(this=exp.CurrentTimestamp(), zone=expression.this),\n                to=exp.DataType(this=exp.DType.DATE),\n            )\n            return self.sql(expr)\n\n        def parsejson_sql(self, expression: exp.ParseJSON) -> str:\n            arg = expression.this\n            if expression.args.get(\"safe\"):\n                return self.sql(exp.case().when(exp.func(\"json_valid\", arg), arg).else_(exp.null()))\n            return self.func(\"JSON\", arg)\n\n        @unsupported_args(\"decimals\")\n        def trunc_sql(self, expression: exp.Trunc) -> str:\n            return self.func(\"TRUNC\", expression.this)\n\n        def normal_sql(self, expression: exp.Normal) -> str:\n            \"\"\"\n            Transpile Snowflake's NORMAL(mean, stddev, gen) to DuckDB.\n\n            Uses the Box-Muller transform via NORMAL_TEMPLATE.\n            \"\"\"\n            mean = expression.this\n            stddev = expression.args[\"stddev\"]\n            gen: exp.Expr = expression.args[\"gen\"]\n\n            # Build two uniform random values [0, 1) for Box-Muller transform\n            if isinstance(gen, exp.Rand) and gen.this is None:\n                u1: exp.Expr = exp.Rand()\n                u2: exp.Expr = exp.Rand()\n            else:\n                # Seeded: derive two values using HASH with different inputs\n                seed = gen.this if isinstance(gen, exp.Rand) else gen\n                u1 = exp.replace_placeholders(self.SEEDED_RANDOM_TEMPLATE, seed=seed)\n                u2 = exp.replace_placeholders(\n                    self.SEEDED_RANDOM_TEMPLATE,\n                    seed=exp.Add(this=seed.copy(), expression=exp.Literal.number(1)),\n                )\n\n            replacements = {\"mean\": mean, \"stddev\": stddev, \"u1\": u1, \"u2\": u2}\n            return self.sql(exp.replace_placeholders(self.NORMAL_TEMPLATE, **replacements))\n\n        def uniform_sql(self, expression: exp.Uniform) -> str:\n            \"\"\"\n            Transpile Snowflake's UNIFORM(min, max, gen) to DuckDB.\n\n            UNIFORM returns a random value in [min, max]:\n            - Integer result if both min and max are integers\n            - Float result if either min or max is a float\n            \"\"\"\n            min_val = expression.this\n            max_val = expression.expression\n            gen = expression.args.get(\"gen\")\n\n            # Determine if result should be integer (both bounds are integers).\n            # We do this to emulate Snowflake's behavior, INT -> INT, FLOAT -> FLOAT\n            is_int_result = min_val.is_int and max_val.is_int\n\n            # Build the random value expression [0, 1)\n            if not isinstance(gen, exp.Rand):\n                # Seed value: (ABS(HASH(seed)) % 1000000) / 1000000.0\n                random_expr: exp.Expr = exp.Div(\n                    this=exp.Paren(\n                        this=exp.Mod(\n                            this=exp.Abs(this=exp.Anonymous(this=\"HASH\", expressions=[gen])),\n                            expression=exp.Literal.number(1000000),\n                        )\n                    ),\n                    expression=exp.Literal.number(1000000.0),\n                )\n            else:\n                random_expr = exp.Rand()\n\n            # Build: min + random * (max - min [+ 1 for int])\n            range_expr: exp.Expr = exp.Sub(this=max_val, expression=min_val)\n            if is_int_result:\n                range_expr = exp.Add(this=range_expr, expression=exp.Literal.number(1))\n\n            result: exp.Expr = exp.Add(\n                this=min_val,\n                expression=exp.Mul(this=random_expr, expression=exp.Paren(this=range_expr)),\n            )\n\n            if is_int_result:\n                result = exp.Cast(\n                    this=exp.Floor(this=result),\n                    to=exp.DataType.build(\"BIGINT\"),\n                )\n\n            return self.sql(result)\n\n        def timefromparts_sql(self, expression: exp.TimeFromParts) -> str:\n            nano = expression.args.get(\"nano\")\n            overflow = expression.args.get(\"overflow\")\n\n            # Snowflake's TIME_FROM_PARTS supports overflow\n            if overflow:\n                hour = expression.args[\"hour\"]\n                minute = expression.args[\"min\"]\n                sec = expression.args[\"sec\"]\n\n                # Check if values are within normal ranges - use MAKE_TIME for efficiency\n                if not nano and all(arg.is_int for arg in [hour, minute, sec]):\n                    try:\n                        h_val = hour.to_py()\n                        m_val = minute.to_py()\n                        s_val = sec.to_py()\n                        if 0 <= h_val <= 23 and 0 <= m_val <= 59 and 0 <= s_val <= 59:\n                            return rename_func(\"MAKE_TIME\")(self, expression)\n                    except ValueError:\n                        pass\n\n                # Overflow or nanoseconds detected - use INTERVAL arithmetic\n                if nano:\n                    sec = sec + nano.pop() / exp.Literal.number(1000000000.0)\n\n                total_seconds = (\n                    hour * exp.Literal.number(3600) + minute * exp.Literal.number(60) + sec\n                )\n\n                return self.sql(\n                    exp.Add(\n                        this=exp.Cast(\n                            this=exp.Literal.string(\"00:00:00\"), to=exp.DataType.build(\"TIME\")\n                        ),\n                        expression=exp.Interval(this=total_seconds, unit=exp.var(\"SECOND\")),\n                    )\n                )\n\n            # Default: MAKE_TIME\n            if nano:\n                expression.set(\n                    \"sec\", expression.args[\"sec\"] + nano.pop() / exp.Literal.number(1000000000.0)\n                )\n\n            return rename_func(\"MAKE_TIME\")(self, expression)\n\n        def extract_sql(self, expression: exp.Extract) -> str:\n            \"\"\"\n            Transpile EXTRACT/DATE_PART for DuckDB, handling specifiers not natively supported.\n\n            DuckDB doesn't support: WEEKISO, YEAROFWEEK, YEAROFWEEKISO, NANOSECOND,\n            EPOCH_SECOND (as integer), EPOCH_MILLISECOND, EPOCH_MICROSECOND, EPOCH_NANOSECOND\n            \"\"\"\n            this = expression.this\n            datetime_expr = expression.expression\n\n            # TIMESTAMPTZ extractions may produce different results between Snowflake and DuckDB\n            # because Snowflake applies server timezone while DuckDB uses local timezone\n            if datetime_expr.is_type(exp.DType.TIMESTAMPTZ, exp.DType.TIMESTAMPLTZ):\n                self.unsupported(\n                    \"EXTRACT from TIMESTAMPTZ / TIMESTAMPLTZ may produce different results due to timezone handling differences\"\n                )\n\n            part_name = this.name.upper()\n\n            if part_name in self.EXTRACT_STRFTIME_MAPPINGS:\n                fmt, cast_type = self.EXTRACT_STRFTIME_MAPPINGS[part_name]\n\n                # Problem: strftime doesn't accept TIME and there's no NANOSECOND function\n                # So, for NANOSECOND with TIME, fallback to MICROSECOND * 1000\n                is_nano_time = part_name == \"NANOSECOND\" and datetime_expr.is_type(\n                    exp.DType.TIME, exp.DType.TIMETZ\n                )\n\n                if is_nano_time:\n                    self.unsupported(\n                        \"Parameter NANOSECOND is not supported with TIME type in DuckDB\"\n                    )\n                    return self.sql(\n                        exp.cast(\n                            exp.Mul(\n                                this=exp.Extract(\n                                    this=exp.var(\"MICROSECOND\"), expression=datetime_expr\n                                ),\n                                expression=exp.Literal.number(1000),\n                            ),\n                            exp.DataType.build(cast_type, dialect=\"duckdb\"),\n                        )\n                    )\n\n                # For NANOSECOND, cast to TIMESTAMP_NS to preserve nanosecond precision\n                strftime_input = datetime_expr\n                if part_name == \"NANOSECOND\":\n                    strftime_input = exp.cast(datetime_expr, exp.DType.TIMESTAMP_NS)\n\n                return self.sql(\n                    exp.cast(\n                        exp.Anonymous(\n                            this=\"STRFTIME\",\n                            expressions=[strftime_input, exp.Literal.string(fmt)],\n                        ),\n                        exp.DataType.build(cast_type, dialect=\"duckdb\"),\n                    )\n                )\n\n            if part_name in self.EXTRACT_EPOCH_MAPPINGS:\n                func_name = self.EXTRACT_EPOCH_MAPPINGS[part_name]\n                result: exp.Expr = exp.Anonymous(this=func_name, expressions=[datetime_expr])\n                # EPOCH returns float, cast to BIGINT for integer result\n                if part_name == \"EPOCH_SECOND\":\n                    result = exp.cast(result, exp.DataType.build(\"BIGINT\", dialect=\"duckdb\"))\n                return self.sql(result)\n\n            return super().extract_sql(expression)\n\n        def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:\n            # Check if this is the date/time expression form: TIMESTAMP_FROM_PARTS(date_expr, time_expr)\n            date_expr = expression.this\n            time_expr = expression.expression\n\n            if date_expr is not None and time_expr is not None:\n                # In DuckDB, DATE + TIME produces TIMESTAMP\n                return self.sql(exp.Add(this=date_expr, expression=time_expr))\n\n            # Component-based form: TIMESTAMP_FROM_PARTS(year, month, day, hour, minute, second, ...)\n            sec = expression.args.get(\"sec\")\n            if sec is None:\n                # This shouldn't happen with valid input, but handle gracefully\n                return rename_func(\"MAKE_TIMESTAMP\")(self, expression)\n\n            milli = expression.args.get(\"milli\")\n            if milli is not None:\n                sec += milli.pop() / exp.Literal.number(1000.0)\n\n            nano = expression.args.get(\"nano\")\n            if nano is not None:\n                sec += nano.pop() / exp.Literal.number(1000000000.0)\n\n            if milli or nano:\n                expression.set(\"sec\", sec)\n\n            return rename_func(\"MAKE_TIMESTAMP\")(self, expression)\n\n        @unsupported_args(\"nano\")\n        def timestampltzfromparts_sql(self, expression: exp.TimestampLtzFromParts) -> str:\n            # Pop nano so rename_func only passes args that MAKE_TIMESTAMP accepts\n            if nano := expression.args.get(\"nano\"):\n                nano.pop()\n\n            timestamp = rename_func(\"MAKE_TIMESTAMP\")(self, expression)\n            return f\"CAST({timestamp} AS TIMESTAMPTZ)\"\n\n        @unsupported_args(\"nano\")\n        def timestamptzfromparts_sql(self, expression: exp.TimestampTzFromParts) -> str:\n            # Extract zone before popping\n            zone = expression.args.get(\"zone\")\n            # Pop zone and nano so rename_func only passes args that MAKE_TIMESTAMP accepts\n            if zone:\n                zone = zone.pop()\n\n            if nano := expression.args.get(\"nano\"):\n                nano.pop()\n\n            timestamp = rename_func(\"MAKE_TIMESTAMP\")(self, expression)\n\n            if zone:\n                # Use AT TIME ZONE to apply the explicit timezone\n                return f\"{timestamp} AT TIME ZONE {self.sql(zone)}\"\n\n            return timestamp\n\n        def tablesample_sql(\n            self,\n            expression: exp.TableSample,\n            tablesample_keyword: t.Optional[str] = None,\n        ) -> str:\n            if not isinstance(expression.parent, exp.Select):\n                # This sample clause only applies to a single source, not the entire resulting relation\n                tablesample_keyword = \"TABLESAMPLE\"\n\n            if expression.args.get(\"size\"):\n                method = expression.args.get(\"method\")\n                if method and method.name.upper() != \"RESERVOIR\":\n                    self.unsupported(\n                        f\"Sampling method {method} is not supported with a discrete sample count, \"\n                        \"defaulting to reservoir sampling\"\n                    )\n                    expression.set(\"method\", exp.var(\"RESERVOIR\"))\n\n            return super().tablesample_sql(expression, tablesample_keyword=tablesample_keyword)\n\n        def columndef_sql(self, expression: exp.ColumnDef, sep: str = \" \") -> str:\n            if isinstance(expression.parent, exp.UserDefinedFunction):\n                return self.sql(expression, \"this\")\n            return super().columndef_sql(expression, sep)\n\n        def join_sql(self, expression: exp.Join) -> str:\n            if (\n                not expression.args.get(\"using\")\n                and not expression.args.get(\"on\")\n                and not expression.method\n                and (expression.kind in (\"\", \"INNER\", \"OUTER\"))\n            ):\n                # Some dialects support `LEFT/INNER JOIN UNNEST(...)` without an explicit ON clause\n                # DuckDB doesn't, but we can just add a dummy ON clause that is always true\n                if isinstance(expression.this, exp.Unnest):\n                    return super().join_sql(expression.on(exp.true()))\n\n                expression.set(\"side\", None)\n                expression.set(\"kind\", None)\n\n            return super().join_sql(expression)\n\n        def countif_sql(self, expression: exp.CountIf) -> str:\n            if self.dialect.version >= (1, 2):\n                return self.function_fallback_sql(expression)\n\n            # https://github.com/tobymao/sqlglot/pull/4749\n            return count_if_to_sum(self, expression)\n\n        def bracket_sql(self, expression: exp.Bracket) -> str:\n            if self.dialect.version >= (1, 2):\n                return super().bracket_sql(expression)\n\n            # https://duckdb.org/2025/02/05/announcing-duckdb-120.html#breaking-changes\n            this = expression.this\n            if isinstance(this, exp.Array):\n                this.replace(exp.paren(this))\n\n            bracket = super().bracket_sql(expression)\n\n            if not expression.args.get(\"returns_list_for_maps\"):\n                if not this.type:\n                    from sqlglot.optimizer.annotate_types import annotate_types\n\n                    this = annotate_types(this, dialect=self.dialect)\n\n                if this.is_type(exp.DType.MAP):\n                    bracket = f\"({bracket})[1]\"\n\n            return bracket\n\n        def withingroup_sql(self, expression: exp.WithinGroup) -> str:\n            func = expression.this\n\n            # For ARRAY_AGG, DuckDB requires ORDER BY inside the function, not in WITHIN GROUP\n            # Transform: ARRAY_AGG(x) WITHIN GROUP (ORDER BY y) -> ARRAY_AGG(x ORDER BY y)\n            if isinstance(func, exp.ArrayAgg):\n                if not isinstance(order := expression.expression, exp.Order):\n                    return self.sql(func)\n\n                # Save the original column for FILTER clause (before wrapping with Order)\n                original_this = func.this\n\n                # Move ORDER BY inside ARRAY_AGG by wrapping its argument with Order\n                # ArrayAgg.this should become Order(this=ArrayAgg.this, expressions=order.expressions)\n                func.set(\n                    \"this\",\n                    exp.Order(\n                        this=func.this.copy(),\n                        expressions=order.expressions,\n                    ),\n                )\n\n                # Generate the ARRAY_AGG function with ORDER BY and add FILTER clause if needed\n                # Use original_this (not the Order-wrapped version) for the FILTER condition\n                array_agg_sql = self.function_fallback_sql(func)\n                return self._add_arrayagg_null_filter(array_agg_sql, func, original_this)\n\n            # For other functions (like PERCENTILES), use existing logic\n            expression_sql = self.sql(expression, \"expression\")\n\n            if isinstance(func, exp.PERCENTILES):\n                # Make the order key the first arg and slide the fraction to the right\n                # https://duckdb.org/docs/sql/aggregates#ordered-set-aggregate-functions\n                order_col = expression.find(exp.Ordered)\n                if order_col:\n                    func.set(\"expression\", func.this)\n                    func.set(\"this\", order_col.this)\n\n            this = self.sql(expression, \"this\").rstrip(\")\")\n\n            return f\"{this}{expression_sql})\"\n\n        def length_sql(self, expression: exp.Length) -> str:\n            arg = expression.this\n\n            # Dialects like BQ and Snowflake also accept binary values as args, so\n            # DDB will attempt to infer the type or resort to case/when resolution\n            if not expression.args.get(\"binary\") or arg.is_string:\n                return self.func(\"LENGTH\", arg)\n\n            if not arg.type:\n                from sqlglot.optimizer.annotate_types import annotate_types\n\n                arg = annotate_types(arg, dialect=self.dialect)\n\n            if arg.is_type(*exp.DataType.TEXT_TYPES):\n                return self.func(\"LENGTH\", arg)\n\n            # We need these casts to make duckdb's static type checker happy\n            blob = exp.cast(arg, exp.DType.VARBINARY)\n            varchar = exp.cast(arg, exp.DType.VARCHAR)\n\n            case = (\n                exp.case(exp.Anonymous(this=\"TYPEOF\", expressions=[arg]))\n                .when(exp.Literal.string(\"BLOB\"), exp.ByteLength(this=blob))\n                .else_(exp.Anonymous(this=\"LENGTH\", expressions=[varchar]))\n            )\n            return self.sql(case)\n\n        def _validate_regexp_flags(\n            self, flags: t.Optional[exp.Expr], supported_flags: str\n        ) -> t.Optional[str]:\n            \"\"\"\n            Validate and filter regexp flags for DuckDB compatibility.\n\n            Args:\n                flags: The flags expression to validate\n                supported_flags: String of supported flags (e.g., \"ims\", \"cims\").\n                                Only these flags will be returned.\n\n            Returns:\n                Validated/filtered flag string, or None if no valid flags remain\n            \"\"\"\n            if not isinstance(flags, exp.Expr):\n                return None\n\n            if not flags.is_string:\n                self.unsupported(\"Non-literal regexp flags are not fully supported in DuckDB\")\n                return None\n\n            flag_str = flags.this\n            unsupported = set(flag_str) - set(supported_flags)\n\n            if unsupported:\n                self.unsupported(\n                    f\"Regexp flags {sorted(unsupported)} are not supported in this context\"\n                )\n\n            flag_str = \"\".join(f for f in flag_str if f in supported_flags)\n            return flag_str if flag_str else None\n\n        def regexpcount_sql(self, expression: exp.RegexpCount) -> str:\n            this = expression.this\n            pattern = expression.expression\n            position = expression.args.get(\"position\")\n            parameters = expression.args.get(\"parameters\")\n\n            # Validate flags - only \"ims\" flags are supported for embedded patterns\n            validated_flags = self._validate_regexp_flags(parameters, supported_flags=\"ims\")\n\n            if position:\n                this = exp.Substring(this=this, start=position)\n\n            # Embed flags in pattern (REGEXP_EXTRACT_ALL doesn't support flags argument)\n            if validated_flags:\n                pattern = exp.Concat(\n                    expressions=[exp.Literal.string(f\"(?{validated_flags})\"), pattern]\n                )\n\n            # Handle empty pattern: Snowflake returns 0, DuckDB would match between every character\n            result = (\n                exp.case()\n                .when(\n                    exp.EQ(this=pattern, expression=exp.Literal.string(\"\")),\n                    exp.Literal.number(0),\n                )\n                .else_(\n                    exp.Length(\n                        this=exp.Anonymous(this=\"REGEXP_EXTRACT_ALL\", expressions=[this, pattern])\n                    )\n                )\n            )\n\n            return self.sql(result)\n\n        def regexpreplace_sql(self, expression: exp.RegexpReplace) -> str:\n            subject = expression.this\n            pattern = expression.expression\n            replacement = expression.args.get(\"replacement\") or exp.Literal.string(\"\")\n            position = expression.args.get(\"position\")\n            occurrence = expression.args.get(\"occurrence\")\n            modifiers = expression.args.get(\"modifiers\")\n\n            validated_flags = self._validate_regexp_flags(modifiers, supported_flags=\"cimsg\") or \"\"\n\n            # Handle occurrence (only literals supported)\n            if occurrence and not occurrence.is_int:\n                self.unsupported(\"REGEXP_REPLACE with non-literal occurrence\")\n            else:\n                occurrence = occurrence.to_py() if occurrence and occurrence.is_int else 0\n                if occurrence > 1:\n                    self.unsupported(f\"REGEXP_REPLACE occurrence={occurrence} not supported\")\n                # flag duckdb to do either all or none, single_replace check is for duckdb round trip\n                elif (\n                    occurrence == 0\n                    and \"g\" not in validated_flags\n                    and not expression.args.get(\"single_replace\")\n                ):\n                    validated_flags += \"g\"\n\n            # Handle position (only literals supported)\n            prefix = None\n            if position and not position.is_int:\n                self.unsupported(\"REGEXP_REPLACE with non-literal position\")\n            elif position and position.is_int and position.to_py() > 1:\n                pos = position.to_py()\n                prefix = exp.Substring(\n                    this=subject, start=exp.Literal.number(1), length=exp.Literal.number(pos - 1)\n                )\n                subject = exp.Substring(this=subject, start=exp.Literal.number(pos))\n\n            result: exp.Expr = exp.Anonymous(\n                this=\"REGEXP_REPLACE\",\n                expressions=[\n                    subject,\n                    pattern,\n                    replacement,\n                    exp.Literal.string(validated_flags) if validated_flags else None,\n                ],\n            )\n\n            if prefix:\n                result = exp.Concat(expressions=[prefix, result])\n\n            return self.sql(result)\n\n        def regexplike_sql(self, expression: exp.RegexpLike) -> str:\n            this = expression.this\n            pattern = expression.expression\n            flag = expression.args.get(\"flag\")\n\n            if not expression.args.get(\"full_match\"):\n                return self.func(\"REGEXP_MATCHES\", this, pattern, flag)\n\n            # DuckDB REGEXP_MATCHES supports: c, i, m, s (but not 'e')\n            validated_flags = self._validate_regexp_flags(flag, supported_flags=\"cims\")\n\n            anchored_pattern = exp.Concat(\n                expressions=[\n                    exp.Literal.string(\"^(\"),\n                    exp.Paren(this=pattern),\n                    exp.Literal.string(\")$\"),\n                ]\n            )\n\n            if validated_flags:\n                flag = exp.Literal.string(validated_flags)\n\n            return self.func(\"REGEXP_MATCHES\", this, anchored_pattern, flag)\n\n        @unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\")\n        def levenshtein_sql(self, expression: exp.Levenshtein) -> str:\n            this = expression.this\n            expr = expression.expression\n            max_dist = expression.args.get(\"max_dist\")\n\n            if max_dist is None:\n                return self.func(\"LEVENSHTEIN\", this, expr)\n\n            # Emulate Snowflake semantics: if distance > max_dist, return max_dist\n            levenshtein = exp.Levenshtein(this=this, expression=expr)\n            return self.sql(exp.Least(this=levenshtein, expressions=[max_dist]))\n\n        def pad_sql(self, expression: exp.Pad) -> str:\n            \"\"\"\n            Handle RPAD/LPAD for VARCHAR and BINARY types.\n\n            For VARCHAR: Delegate to parent class\n            For BINARY: Lower to: input || REPEAT(pad, GREATEST(0, target_len - OCTET_LENGTH(input)))\n            \"\"\"\n            string_arg = expression.this\n            fill_arg = expression.args.get(\"fill_pattern\") or exp.Literal.string(\" \")\n\n            if _is_binary(string_arg) or _is_binary(fill_arg):\n                length_arg = expression.expression\n                is_left = expression.args.get(\"is_left\")\n\n                input_len = exp.ByteLength(this=string_arg)\n                chars_needed = length_arg - input_len\n                pad_count = exp.Greatest(\n                    this=exp.Literal.number(0), expressions=[chars_needed], ignore_nulls=True\n                )\n                repeat_expr = exp.Repeat(this=fill_arg, times=pad_count)\n\n                left, right = string_arg, repeat_expr\n                if is_left:\n                    left, right = right, left\n\n                result = exp.DPipe(this=left, expression=right)\n                return self.sql(result)\n\n            # For VARCHAR: Delegate to parent class (handles PAD_FILL_PATTERN_IS_REQUIRED)\n            return super().pad_sql(expression)\n\n        def minhash_sql(self, expression: exp.Minhash) -> str:\n            k = expression.this\n            exprs = expression.expressions\n\n            if len(exprs) != 1 or isinstance(exprs[0], exp.Star):\n                self.unsupported(\n                    \"MINHASH with multiple expressions or * requires manual query restructuring\"\n                )\n                return self.func(\"MINHASH\", k, *exprs)\n\n            expr = exprs[0]\n            result = exp.replace_placeholders(self.MINHASH_TEMPLATE.copy(), expr=expr, k=k)\n            return f\"({self.sql(result)})\"\n\n        def minhashcombine_sql(self, expression: exp.MinhashCombine) -> str:\n            expr = expression.this\n            result = exp.replace_placeholders(self.MINHASH_COMBINE_TEMPLATE.copy(), expr=expr)\n            return f\"({self.sql(result)})\"\n\n        def approximatesimilarity_sql(self, expression: exp.ApproximateSimilarity) -> str:\n            expr = expression.this\n            result = exp.replace_placeholders(\n                self.APPROXIMATE_SIMILARITY_TEMPLATE.copy(), expr=expr\n            )\n            return f\"({self.sql(result)})\"\n\n        def arraydistinct_sql(self, expression: exp.ArrayDistinct) -> str:\n            arr = expression.this\n            func = self.func(\"LIST_DISTINCT\", arr)\n\n            if expression.args.get(\"check_null\"):\n                add_null_to_array = exp.func(\n                    \"LIST_APPEND\", exp.func(\"LIST_DISTINCT\", exp.ArrayCompact(this=arr)), exp.Null()\n                )\n                return self.sql(\n                    exp.If(\n                        this=exp.NEQ(\n                            this=exp.ArraySize(this=arr), expression=exp.func(\"LIST_COUNT\", arr)\n                        ),\n                        true=add_null_to_array,\n                        false=func,\n                    )\n                )\n\n            return func\n\n        def arrayintersect_sql(self, expression: exp.ArrayIntersect) -> str:\n            if expression.args.get(\"is_multiset\") and len(expression.expressions) == 2:\n                return self._array_bag_sql(\n                    self.ARRAY_INTERSECTION_CONDITION,\n                    expression.expressions[0],\n                    expression.expressions[1],\n                )\n            return self.function_fallback_sql(expression)\n\n        def arrayexcept_sql(self, expression: exp.ArrayExcept) -> str:\n            arr1, arr2 = expression.this, expression.expression\n            if expression.args.get(\"is_multiset\"):\n                return self._array_bag_sql(self.ARRAY_EXCEPT_CONDITION, arr1, arr2)\n            return self.sql(\n                exp.replace_placeholders(self.ARRAY_EXCEPT_SET_TEMPLATE, arr1=arr1, arr2=arr2)\n            )\n\n        def arrayslice_sql(self, expression: exp.ArraySlice) -> str:\n            \"\"\"\n            Transpiles Snowflake's ARRAY_SLICE (0-indexed, exclusive end) to DuckDB's\n            ARRAY_SLICE (1-indexed, inclusive end) by wrapping start and end in CASE\n            expressions that adjust the index at query time:\n              - start: CASE WHEN start >= 0 THEN start + 1 ELSE start END\n              - end:   CASE WHEN end < 0 THEN end - 1 ELSE end END\n            \"\"\"\n            start, end = expression.args.get(\"start\"), expression.args.get(\"end\")\n\n            if expression.args.get(\"zero_based\"):\n                if start is not None:\n                    start = (\n                        exp.case()\n                        .when(\n                            exp.GTE(this=start.copy(), expression=exp.Literal.number(0)),\n                            exp.Add(this=start.copy(), expression=exp.Literal.number(1)),\n                        )\n                        .else_(start)\n                    )\n                if end is not None:\n                    end = (\n                        exp.case()\n                        .when(\n                            exp.LT(this=end.copy(), expression=exp.Literal.number(0)),\n                            exp.Sub(this=end.copy(), expression=exp.Literal.number(1)),\n                        )\n                        .else_(end)\n                    )\n\n            return self.func(\n                \"ARRAY_SLICE\", expression.this, start, end, expression.args.get(\"step\")\n            )\n\n        def arrayszip_sql(self, expression: exp.ArraysZip) -> str:\n            args = expression.expressions\n\n            if not args:\n                # Return [{}] - using MAP([], []) since DuckDB can't represent empty structs\n                return self.sql(exp.array(exp.Map(keys=exp.array(), values=exp.array())))\n\n            # Build placeholder values for template\n            lengths = [exp.Length(this=arg) for arg in args]\n            max_len = (\n                lengths[0]\n                if len(lengths) == 1\n                else exp.Greatest(this=lengths[0], expressions=lengths[1:])\n            )\n\n            # Empty struct with same schema: {'$1': NULL, '$2': NULL, ...}\n            empty_struct = exp.func(\n                \"STRUCT\",\n                *[\n                    exp.PropertyEQ(this=exp.Literal.string(f\"${i + 1}\"), expression=exp.Null())\n                    for i in range(len(args))\n                ],\n            )\n\n            # Struct for transform: {'$1': COALESCE(arr1, [])[__i + 1], ...}\n            # COALESCE wrapping handles NULL arrays - prevents invalid NULL[i] syntax\n            index = exp.column(\"__i\") + 1\n            transform_struct = exp.func(\n                \"STRUCT\",\n                *[\n                    exp.PropertyEQ(\n                        this=exp.Literal.string(f\"${i + 1}\"),\n                        expression=exp.func(\"COALESCE\", arg, exp.array())[index],\n                    )\n                    for i, arg in enumerate(args)\n                ],\n            )\n\n            result = exp.replace_placeholders(\n                self.ARRAYS_ZIP_TEMPLATE.copy(),\n                null_check=exp.or_(*[arg.is_(exp.Null()) for arg in args]),\n                all_empty_check=exp.and_(\n                    *[\n                        exp.EQ(this=exp.Length(this=arg), expression=exp.Literal.number(0))\n                        for arg in args\n                    ]\n                ),\n                empty_struct=empty_struct,\n                max_len=max_len,\n                transform_struct=transform_struct,\n            )\n            return self.sql(result)\n\n        def lower_sql(self, expression: exp.Lower) -> str:\n            result_sql = self.func(\"LOWER\", _cast_to_varchar(expression.this))\n            return _gen_with_cast_to_blob(self, expression, result_sql)\n\n        def upper_sql(self, expression: exp.Upper) -> str:\n            result_sql = self.func(\"UPPER\", _cast_to_varchar(expression.this))\n            return _gen_with_cast_to_blob(self, expression, result_sql)\n\n        def reverse_sql(self, expression: exp.Reverse) -> str:\n            result_sql = self.func(\"REVERSE\", _cast_to_varchar(expression.this))\n            return _gen_with_cast_to_blob(self, expression, result_sql)\n\n        def right_sql(self, expression: exp.Right) -> str:\n            arg = expression.this\n            length = expression.expression\n\n            # For BINARY/BLOB: DuckDB doesn't support RIGHT on BLOB\n            # Convert to HEX string, use RIGHT, then convert back to BLOB\n            if _is_binary(arg):\n                # RIGHT(blob, n) becomes UNHEX(RIGHT(HEX(blob), n * 2))\n                # Each byte becomes 2 hex chars, so multiply length by 2\n                hex_arg = exp.Hex(this=arg)\n                hex_length = exp.Mul(this=length, expression=exp.Literal.number(2))\n                # since this exp.Right is not annotated, it won't enter this _is_binary branch during the recursive call\n                hex_right = self.func(\"RIGHT\", hex_arg, hex_length)\n                result = exp.Unhex(this=hex_right)\n                return self.sql(result)\n\n            # For VARCHAR: Use native RIGHT function\n            return self.func(\"RIGHT\", arg, length)\n\n        def rand_sql(self, expression: exp.Rand) -> str:\n            seed = expression.this\n            if seed is not None:\n                self.unsupported(\"RANDOM with seed is not supported in DuckDB\")\n\n            lower = expression.args.get(\"lower\")\n            upper = expression.args.get(\"upper\")\n\n            if lower and upper:\n                # scale DuckDB's [0,1) to the specified range\n                range_size = exp.paren(upper - lower)\n                scaled = exp.Add(this=lower, expression=exp.func(\"random\") * range_size)\n\n                # For now we assume that if bounds are set, return type is BIGINT. Snowflake/Teradata\n                result = exp.cast(scaled, exp.DType.BIGINT)\n                return self.sql(result)\n\n            # Default DuckDB behavior - just return RANDOM() as float\n            return \"RANDOM()\"\n\n        def base64encode_sql(self, expression: exp.Base64Encode) -> str:\n            # DuckDB TO_BASE64 requires BLOB input\n            # Snowflake BASE64_ENCODE accepts both VARCHAR and BINARY - for VARCHAR it implicitly\n            # encodes UTF-8 bytes. We add ENCODE unless the input is a binary type.\n            result = expression.this\n\n            # Check if input is a string type - ENCODE only accepts VARCHAR\n            if result.is_type(*exp.DataType.TEXT_TYPES):\n                result = exp.Encode(this=result)\n\n            result = exp.ToBase64(this=result)\n\n            max_line_length = expression.args.get(\"max_line_length\")\n            alphabet = expression.args.get(\"alphabet\")\n\n            # Handle custom alphabet by replacing standard chars with custom ones\n            result = _apply_base64_alphabet_replacements(result, alphabet)\n\n            # Handle max_line_length by inserting newlines every N characters\n            line_length = (\n                t.cast(int, max_line_length.to_py())\n                if isinstance(max_line_length, exp.Literal) and max_line_length.is_number\n                else 0\n            )\n            if line_length > 0:\n                newline = exp.Chr(expressions=[exp.Literal.number(10)])\n                result = exp.Trim(\n                    this=exp.RegexpReplace(\n                        this=result,\n                        expression=exp.Literal.string(f\"(.{{{line_length}}})\"),\n                        replacement=exp.Concat(\n                            expressions=[exp.Literal.string(\"\\\\1\"), newline.copy()]\n                        ),\n                    ),\n                    expression=newline,\n                    position=\"TRAILING\",\n                )\n\n            return self.sql(result)\n\n        def replace_sql(self, expression: exp.Replace) -> str:\n            result_sql = self.func(\n                \"REPLACE\",\n                _cast_to_varchar(expression.this),\n                _cast_to_varchar(expression.expression),\n                _cast_to_varchar(expression.args.get(\"replacement\")),\n            )\n            return _gen_with_cast_to_blob(self, expression, result_sql)\n\n        def _bitwise_op(self, expression: exp.Binary, op: str) -> str:\n            _prepare_binary_bitwise_args(expression)\n            result_sql = self.binary(expression, op)\n            return _gen_with_cast_to_blob(self, expression, result_sql)\n\n        def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:\n            _prepare_binary_bitwise_args(expression)\n            result_sql = self.func(\"XOR\", expression.this, expression.expression)\n            return _gen_with_cast_to_blob(self, expression, result_sql)\n\n        def objectinsert_sql(self, expression: exp.ObjectInsert) -> str:\n            this = expression.this\n            key = expression.args.get(\"key\")\n            key_sql = key.name if isinstance(key, exp.Expr) else \"\"\n            value_sql = self.sql(expression, \"value\")\n\n            kv_sql = f\"{key_sql} := {value_sql}\"\n\n            # If the input struct is empty e.g. transpiling OBJECT_INSERT(OBJECT_CONSTRUCT(), key, value) from Snowflake\n            # then we can generate STRUCT_PACK which will build it since STRUCT_INSERT({}, key := value) is not valid DuckDB\n            if isinstance(this, exp.Struct) and not this.expressions:\n                return self.func(\"STRUCT_PACK\", kv_sql)\n\n            return self.func(\"STRUCT_INSERT\", this, kv_sql)\n\n        def mapcat_sql(self, expression: exp.MapCat) -> str:\n            result = exp.replace_placeholders(\n                self.MAPCAT_TEMPLATE.copy(),\n                map1=expression.this,\n                map2=expression.expression,\n            )\n            return self.sql(result)\n\n        def mapcontainskey_sql(self, expression: exp.MapContainsKey) -> str:\n            return self.func(\n                \"ARRAY_CONTAINS\", exp.func(\"MAP_KEYS\", expression.args[\"key\"]), expression.this\n            )\n\n        def mapdelete_sql(self, expression: exp.MapDelete) -> str:\n            map_arg = expression.this\n            keys_to_delete = expression.expressions\n\n            x_dot_key = exp.Dot(this=exp.to_identifier(\"x\"), expression=exp.to_identifier(\"key\"))\n\n            lambda_expr = exp.Lambda(\n                this=exp.In(this=x_dot_key, expressions=keys_to_delete).not_(),\n                expressions=[exp.to_identifier(\"x\")],\n            )\n            result = exp.func(\n                \"MAP_FROM_ENTRIES\",\n                exp.ArrayFilter(this=exp.func(\"MAP_ENTRIES\", map_arg), expression=lambda_expr),\n            )\n            return self.sql(result)\n\n        def mappick_sql(self, expression: exp.MapPick) -> str:\n            map_arg = expression.this\n            keys_to_pick = expression.expressions\n\n            x_dot_key = exp.Dot(this=exp.to_identifier(\"x\"), expression=exp.to_identifier(\"key\"))\n\n            if len(keys_to_pick) == 1 and keys_to_pick[0].is_type(exp.DType.ARRAY):\n                lambda_expr = exp.Lambda(\n                    this=exp.func(\"ARRAY_CONTAINS\", keys_to_pick[0], x_dot_key),\n                    expressions=[exp.to_identifier(\"x\")],\n                )\n            else:\n                lambda_expr = exp.Lambda(\n                    this=exp.In(this=x_dot_key, expressions=keys_to_pick),\n                    expressions=[exp.to_identifier(\"x\")],\n                )\n\n            result = exp.func(\n                \"MAP_FROM_ENTRIES\",\n                exp.func(\"LIST_FILTER\", exp.func(\"MAP_ENTRIES\", map_arg), lambda_expr),\n            )\n            return self.sql(result)\n\n        def mapsize_sql(self, expression: exp.MapSize) -> str:\n            return self.func(\"CARDINALITY\", expression.this)\n\n        @unsupported_args(\"update_flag\")\n        def mapinsert_sql(self, expression: exp.MapInsert) -> str:\n            map_arg = expression.this\n            key = expression.args.get(\"key\")\n            value = expression.args.get(\"value\")\n\n            map_type = map_arg.type\n\n            if value is not None:\n                if map_type and map_type.expressions and len(map_type.expressions) > 1:\n                    # Extract the value type from MAP(key_type, value_type)\n                    value_type = map_type.expressions[1]\n                    # Cast value to match the map's value type to avoid type conflicts\n                    value = exp.cast(value, value_type)\n                # else: polymorphic MAP case - no type parameters available, use value as-is\n\n            # Create a single-entry map for the new key-value pair\n            new_entry_struct = exp.Struct(expressions=[exp.PropertyEQ(this=key, expression=value)])\n            new_entry: exp.Expression = exp.ToMap(this=new_entry_struct)\n\n            # Use MAP_CONCAT to merge the original map with the new entry\n            # This automatically handles both insert and update cases\n            result = exp.func(\"MAP_CONCAT\", map_arg, new_entry)\n\n            return self.sql(result)\n\n        def startswith_sql(self, expression: exp.StartsWith) -> str:\n            return self.func(\n                \"STARTS_WITH\",\n                _cast_to_varchar(expression.this),\n                _cast_to_varchar(expression.expression),\n            )\n\n        def space_sql(self, expression: exp.Space) -> str:\n            # DuckDB's REPEAT requires BIGINT for the count parameter\n            return self.sql(\n                exp.Repeat(\n                    this=exp.Literal.string(\" \"),\n                    times=exp.cast(expression.this, exp.DType.BIGINT),\n                )\n            )\n\n        def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:\n            # For GENERATOR, unwrap TABLE() - just emit the Generator (becomes RANGE)\n            if isinstance(expression.this, exp.Generator):\n                # Preserve alias, joins, and other table-level args\n                table = exp.Table(\n                    this=expression.this,\n                    alias=expression.args.get(\"alias\"),\n                    joins=expression.args.get(\"joins\"),\n                )\n                return self.sql(table)\n\n            return super().tablefromrows_sql(expression)\n\n        def unnest_sql(self, expression: exp.Unnest) -> str:\n            explode_array = expression.args.get(\"explode_array\")\n            if explode_array:\n                # In BigQuery, UNNESTing a nested array leads to explosion of the top-level array & struct\n                # This is transpiled to DDB by transforming \"FROM UNNEST(...)\" to \"FROM (SELECT UNNEST(..., max_depth => 2))\"\n                expression.expressions.append(\n                    exp.Kwarg(this=exp.var(\"max_depth\"), expression=exp.Literal.number(2))\n                )\n\n                # If BQ's UNNEST is aliased, we transform it from a column alias to a table alias in DDB\n                alias = expression.args.get(\"alias\")\n                if isinstance(alias, exp.TableAlias):\n                    expression.set(\"alias\", None)\n                    if alias.columns:\n                        alias = exp.TableAlias(this=seq_get(alias.columns, 0))\n\n                unnest_sql = super().unnest_sql(expression)\n                select = exp.Select(expressions=[unnest_sql]).subquery(alias)\n                return self.sql(select)\n\n            return super().unnest_sql(expression)\n\n        def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:\n            this = expression.this\n\n            if isinstance(this, self.IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS):\n                # DuckDB should render IGNORE NULLS only for the general-purpose\n                # window functions that accept it e.g. FIRST_VALUE(... IGNORE NULLS) OVER (...)\n                return super().ignorenulls_sql(expression)\n\n            if isinstance(this, exp.First):\n                this = exp.AnyValue(this=this.this)\n\n            if not isinstance(this, (exp.AnyValue, exp.ApproxQuantiles)):\n                self.unsupported(\"IGNORE NULLS is not supported for non-window functions.\")\n\n            return self.sql(this)\n\n        def split_sql(self, expression: exp.Split) -> str:\n            base_func = exp.func(\"STR_SPLIT\", expression.this, expression.expression)\n\n            case_expr = exp.case().else_(base_func)\n            needs_case = False\n\n            if expression.args.get(\"null_returns_null\"):\n                case_expr = case_expr.when(expression.expression.is_(exp.null()), exp.null())\n                needs_case = True\n\n            if expression.args.get(\"empty_delimiter_returns_whole\"):\n                # When delimiter is empty string, return input string as single array element\n                array_with_input = exp.array(expression.this)\n                case_expr = case_expr.when(\n                    expression.expression.eq(exp.Literal.string(\"\")), array_with_input\n                )\n                needs_case = True\n\n            return self.sql(case_expr if needs_case else base_func)\n\n        def splitpart_sql(self, expression: exp.SplitPart) -> str:\n            string_arg = expression.this\n            delimiter_arg = expression.args.get(\"delimiter\")\n            part_index_arg = expression.args.get(\"part_index\")\n\n            if delimiter_arg and part_index_arg:\n                # Handle Snowflake's \"index 0 and 1 both return first element\" behavior\n                if expression.args.get(\"part_index_zero_as_one\"):\n                    # Convert 0 to 1 for compatibility\n\n                    part_index_arg = exp.Paren(\n                        this=exp.case()\n                        .when(part_index_arg.eq(exp.Literal.number(\"0\")), exp.Literal.number(\"1\"))\n                        .else_(part_index_arg)\n                    )\n\n                # Use Anonymous to avoid recursion\n                base_func_expr: exp.Expr = exp.Anonymous(\n                    this=\"SPLIT_PART\", expressions=[string_arg, delimiter_arg, part_index_arg]\n                )\n                needs_case_transform = False\n                case_expr = exp.case().else_(base_func_expr)\n\n                if expression.args.get(\"empty_delimiter_returns_whole\"):\n                    # When delimiter is empty string:\n                    # - Return whole string if part_index is 1 or -1\n                    # - Return empty string otherwise\n                    empty_case = exp.Paren(\n                        this=exp.case()\n                        .when(\n                            exp.or_(\n                                part_index_arg.eq(exp.Literal.number(\"1\")),\n                                part_index_arg.eq(exp.Literal.number(\"-1\")),\n                            ),\n                            string_arg,\n                        )\n                        .else_(exp.Literal.string(\"\"))\n                    )\n\n                    case_expr = case_expr.when(delimiter_arg.eq(exp.Literal.string(\"\")), empty_case)\n                    needs_case_transform = True\n\n                \"\"\"\n                Output looks something like this:\n\n                CASE\n                WHEN delimiter is '' THEN\n                    (\n                        CASE\n                        WHEN adjusted_part_index = 1 OR adjusted_part_index = -1 THEN input\n                        ELSE '' END\n                    )\n                ELSE SPLIT_PART(input, delimiter, adjusted_part_index)\n                END\n\n                \"\"\"\n                return self.sql(case_expr if needs_case_transform else base_func_expr)\n\n            return self.function_fallback_sql(expression)\n\n        def respectnulls_sql(self, expression: exp.RespectNulls) -> str:\n            if isinstance(expression.this, self.IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS):\n                # DuckDB should render RESPECT NULLS only for the general-purpose\n                # window functions that accept it e.g. FIRST_VALUE(... RESPECT NULLS) OVER (...)\n                return super().respectnulls_sql(expression)\n\n            self.unsupported(\"RESPECT NULLS is not supported for non-window functions.\")\n            return self.sql(expression, \"this\")\n\n        def arraytostring_sql(self, expression: exp.ArrayToString) -> str:\n            null = expression.args.get(\"null\")\n\n            if expression.args.get(\"null_is_empty\"):\n                x = exp.to_identifier(\"x\")\n                list_transform = exp.Transform(\n                    this=expression.this.copy(),\n                    expression=exp.Lambda(\n                        this=exp.Coalesce(\n                            this=exp.cast(x, \"TEXT\"), expressions=[exp.Literal.string(\"\")]\n                        ),\n                        expressions=[x],\n                    ),\n                )\n                array_to_string = exp.ArrayToString(\n                    this=list_transform, expression=expression.expression\n                )\n                if expression.args.get(\"null_delim_is_null\"):\n                    return self.sql(\n                        exp.case()\n                        .when(expression.expression.copy().is_(exp.null()), exp.null())\n                        .else_(array_to_string)\n                    )\n                return self.sql(array_to_string)\n\n            if null:\n                x = exp.to_identifier(\"x\")\n                return self.sql(\n                    exp.ArrayToString(\n                        this=exp.Transform(\n                            this=expression.this,\n                            expression=exp.Lambda(\n                                this=exp.Coalesce(this=x, expressions=[null]),\n                                expressions=[x],\n                            ),\n                        ),\n                        expression=expression.expression,\n                    )\n                )\n\n            return self.func(\"ARRAY_TO_STRING\", expression.this, expression.expression)\n\n        def _regexp_extract_sql(self, expression: exp.RegexpExtract | exp.RegexpExtractAll) -> str:\n            this = expression.this\n            group = expression.args.get(\"group\")\n            params = expression.args.get(\"parameters\")\n            position = expression.args.get(\"position\")\n            occurrence = expression.args.get(\"occurrence\")\n            null_if_pos_overflow = expression.args.get(\"null_if_pos_overflow\")\n\n            # Handle Snowflake's 'e' flag: it enables capture group extraction\n            # In DuckDB, this is controlled by the group parameter directly\n            if params and params.is_string and \"e\" in params.name:\n                params = exp.Literal.string(params.name.replace(\"e\", \"\"))\n\n            validated_flags = self._validate_regexp_flags(params, supported_flags=\"cims\")\n\n            # Strip default group when no following params (DuckDB default is same as group=0)\n            if (\n                not validated_flags\n                and group\n                and group.name == str(self.dialect.REGEXP_EXTRACT_DEFAULT_GROUP)\n            ):\n                group = None\n            flags_expr = exp.Literal.string(validated_flags) if validated_flags else None\n\n            # use substring to handle position argument\n            if position and (not position.is_int or position.to_py() > 1):\n                this = exp.Substring(this=this, start=position)\n\n                if null_if_pos_overflow:\n                    this = exp.Nullif(this=this, expression=exp.Literal.string(\"\"))\n\n            is_extract_all = isinstance(expression, exp.RegexpExtractAll)\n            non_single_occurrence = occurrence and (not occurrence.is_int or occurrence.to_py() > 1)\n\n            if is_extract_all or non_single_occurrence:\n                name = \"REGEXP_EXTRACT_ALL\"\n            else:\n                name = \"REGEXP_EXTRACT\"\n\n            result: exp.Expr = exp.Anonymous(\n                this=name, expressions=[this, expression.expression, group, flags_expr]\n            )\n\n            # Array slicing for REGEXP_EXTRACT_ALL with occurrence\n            if is_extract_all and non_single_occurrence:\n                result = exp.Bracket(this=result, expressions=[exp.Slice(this=occurrence)])\n            # ARRAY_EXTRACT for REGEXP_EXTRACT with occurrence > 1\n            elif non_single_occurrence:\n                result = exp.Anonymous(this=\"ARRAY_EXTRACT\", expressions=[result, occurrence])\n\n            return self.sql(result)\n\n        def regexpextract_sql(self, expression: exp.RegexpExtract) -> str:\n            return self._regexp_extract_sql(expression)\n\n        def regexpextractall_sql(self, expression: exp.RegexpExtractAll) -> str:\n            return self._regexp_extract_sql(expression)\n\n        def regexpinstr_sql(self, expression: exp.RegexpInstr) -> str:\n            this = expression.this\n            pattern = expression.expression\n            position = expression.args.get(\"position\")\n            orig_occ = expression.args.get(\"occurrence\")\n            occurrence = orig_occ or exp.Literal.number(1)\n            option = expression.args.get(\"option\")\n            parameters = expression.args.get(\"parameters\")\n\n            validated_flags = self._validate_regexp_flags(parameters, supported_flags=\"ims\")\n            if validated_flags:\n                pattern = exp.Concat(\n                    expressions=[exp.Literal.string(f\"(?{validated_flags})\"), pattern]\n                )\n\n            # Handle starting position offset\n            pos_offset: exp.Expr = exp.Literal.number(0)\n            if position and (not position.is_int or position.to_py() > 1):\n                this = exp.Substring(this=this, start=position)\n                pos_offset = position - exp.Literal.number(1)\n\n            # Helper: LIST_SUM(LIST_TRANSFORM(list[1:end], x -> LENGTH(x)))\n            def sum_lengths(func_name: str, end: exp.Expr) -> exp.Expr:\n                lst = exp.Bracket(\n                    this=exp.Anonymous(this=func_name, expressions=[this, pattern]),\n                    expressions=[exp.Slice(this=exp.Literal.number(1), expression=end)],\n                    offset=1,\n                )\n                transform = exp.Anonymous(\n                    this=\"LIST_TRANSFORM\",\n                    expressions=[\n                        lst,\n                        exp.Lambda(\n                            this=exp.Length(this=exp.to_identifier(\"x\")),\n                            expressions=[exp.to_identifier(\"x\")],\n                        ),\n                    ],\n                )\n                return exp.Coalesce(\n                    this=exp.Anonymous(this=\"LIST_SUM\", expressions=[transform]),\n                    expressions=[exp.Literal.number(0)],\n                )\n\n            # Position = 1 + sum(split_lengths[1:occ]) + sum(match_lengths[1:occ-1]) + offset\n            base_pos: exp.Expr = (\n                exp.Literal.number(1)\n                + sum_lengths(\"STRING_SPLIT_REGEX\", occurrence)\n                + sum_lengths(\"REGEXP_EXTRACT_ALL\", occurrence - exp.Literal.number(1))\n                + pos_offset\n            )\n\n            # option=1: add match length for end position\n            if option and option.is_int and option.to_py() == 1:\n                match_at_occ = exp.Bracket(\n                    this=exp.Anonymous(this=\"REGEXP_EXTRACT_ALL\", expressions=[this, pattern]),\n                    expressions=[occurrence],\n                    offset=1,\n                )\n                base_pos = base_pos + exp.Coalesce(\n                    this=exp.Length(this=match_at_occ), expressions=[exp.Literal.number(0)]\n                )\n\n            # NULL checks for all provided arguments\n            # .copy() is used strictly because .is_() alters the node's parent pointer, mutating the parsed AST\n            null_args = [\n                expression.this,\n                expression.expression,\n                position,\n                orig_occ,\n                option,\n                parameters,\n            ]\n            null_checks = [arg.copy().is_(exp.Null()) for arg in null_args if arg]\n\n            matches = exp.Anonymous(this=\"REGEXP_EXTRACT_ALL\", expressions=[this, pattern])\n\n            return self.sql(\n                exp.case()\n                .when(exp.or_(*null_checks), exp.Null())\n                .when(pattern.copy().eq(exp.Literal.string(\"\")), exp.Literal.number(0))\n                .when(exp.Length(this=matches) < occurrence, exp.Literal.number(0))\n                .else_(base_pos)\n            )\n\n        @unsupported_args(\"culture\")\n        def numbertostr_sql(self, expression: exp.NumberToStr) -> str:\n            fmt = expression.args.get(\"format\")\n            if fmt and fmt.is_int:\n                return self.func(\"FORMAT\", f\"'{{:,.{fmt.name}f}}'\", expression.this)\n\n            self.unsupported(\"Only integer formats are supported by NumberToStr\")\n            return self.function_fallback_sql(expression)\n\n        def autoincrementcolumnconstraint_sql(self, _) -> str:\n            self.unsupported(\"The AUTOINCREMENT column constraint is not supported by DuckDB\")\n            return \"\"\n\n        def aliases_sql(self, expression: exp.Aliases) -> str:\n            this = expression.this\n            if isinstance(this, exp.Posexplode):\n                return self.posexplode_sql(this)\n\n            return super().aliases_sql(expression)\n\n        def posexplode_sql(self, expression: exp.Posexplode) -> str:\n            this = expression.this\n            parent = expression.parent\n\n            # The default Spark aliases are \"pos\" and \"col\", unless specified otherwise\n            pos, col = exp.to_identifier(\"pos\"), exp.to_identifier(\"col\")\n\n            if isinstance(parent, exp.Aliases):\n                # Column case: SELECT POSEXPLODE(col) [AS (a, b)]\n                pos, col = parent.expressions\n            elif isinstance(parent, exp.Table):\n                # Table case: SELECT * FROM POSEXPLODE(col) [AS (a, b)]\n                alias = parent.args.get(\"alias\")\n                if alias:\n                    pos, col = alias.columns or [pos, col]\n                    alias.pop()\n\n            # Translate POSEXPLODE to UNNEST + GENERATE_SUBSCRIPTS\n            # Note: In Spark pos is 0-indexed, but in DuckDB it's 1-indexed, so we subtract 1 from GENERATE_SUBSCRIPTS\n            unnest_sql = self.sql(exp.Unnest(expressions=[this], alias=col))\n            gen_subscripts = self.sql(\n                exp.Alias(\n                    this=exp.Anonymous(\n                        this=\"GENERATE_SUBSCRIPTS\", expressions=[this, exp.Literal.number(1)]\n                    )\n                    - exp.Literal.number(1),\n                    alias=pos,\n                )\n            )\n\n            posexplode_sql = self.format_args(gen_subscripts, unnest_sql)\n\n            if isinstance(parent, exp.From) or (parent and isinstance(parent.parent, exp.From)):\n                # SELECT * FROM POSEXPLODE(col) -> SELECT * FROM (SELECT GENERATE_SUBSCRIPTS(...), UNNEST(...))\n                return self.sql(exp.Subquery(this=exp.Select(expressions=[posexplode_sql])))\n\n            return posexplode_sql\n\n        def addmonths_sql(self, expression: exp.AddMonths) -> str:\n            \"\"\"\n            Handles three key issues:\n            1. Float/decimal months: e.g., Snowflake rounds, whereas DuckDB INTERVAL requires integers\n            2. End-of-month preservation: If input is last day of month, result is last day of result month\n            3. Type preservation: Maintains DATE/TIMESTAMPTZ types (DuckDB defaults to TIMESTAMP)\n            \"\"\"\n            from sqlglot.optimizer.annotate_types import annotate_types\n\n            this = expression.this\n            if not this.type:\n                this = annotate_types(this, dialect=self.dialect)\n\n            if this.is_type(*exp.DataType.TEXT_TYPES):\n                this = exp.Cast(this=this, to=exp.DataType(this=exp.DType.TIMESTAMP))\n\n            # Detect float/decimal months to apply rounding (Snowflake behavior)\n            # DuckDB INTERVAL syntax doesn't support non-integer expressions, so use TO_MONTHS\n            months_expr = expression.expression\n            if not months_expr.type:\n                months_expr = annotate_types(months_expr, dialect=self.dialect)\n\n            # Build interval or to_months expression based on type\n            # Float/decimal case: Round and use TO_MONTHS(CAST(ROUND(value) AS INT))\n            interval_or_to_months = (\n                exp.func(\"TO_MONTHS\", exp.cast(exp.func(\"ROUND\", months_expr), \"INT\"))\n                if months_expr.is_type(\n                    exp.DType.FLOAT,\n                    exp.DType.DOUBLE,\n                    exp.DType.DECIMAL,\n                )\n                # Integer case: standard INTERVAL N MONTH syntax\n                else exp.Interval(this=months_expr, unit=exp.var(\"MONTH\"))\n            )\n\n            date_add_expr = exp.Add(this=this, expression=interval_or_to_months)\n\n            # Apply end-of-month preservation if Snowflake flag is set\n            # CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(result) ELSE result END\n            preserve_eom = expression.args.get(\"preserve_end_of_month\")\n            result_expr = (\n                exp.case()\n                .when(\n                    exp.EQ(this=exp.func(\"LAST_DAY\", this), expression=this),\n                    exp.func(\"LAST_DAY\", date_add_expr),\n                )\n                .else_(date_add_expr)\n                if preserve_eom\n                else date_add_expr\n            )\n\n            # DuckDB's DATE_ADD function returns TIMESTAMP/DATETIME by default, even when the input is DATE\n            # To match for example Snowflake's ADD_MONTHS behavior (which preserves the input type)\n            # We need to cast the result back to the original type when the input is DATE or TIMESTAMPTZ\n            # Example: ADD_MONTHS('2023-01-31'::date, 1) should return DATE, not TIMESTAMP\n            if this.is_type(exp.DType.DATE, exp.DType.TIMESTAMPTZ):\n                return self.sql(exp.Cast(this=result_expr, to=this.type))\n            return self.sql(result_expr)\n\n        def format_sql(self, expression: exp.Format) -> str:\n            if expression.name.lower() == \"%s\" and len(expression.expressions) == 1:\n                return self.func(\"FORMAT\", \"'{}'\", expression.expressions[0])\n\n            return self.function_fallback_sql(expression)\n\n        def hexstring_sql(\n            self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None\n        ) -> str:\n            # UNHEX('FF') correctly produces blob \\xFF in DuckDB\n            return super().hexstring_sql(expression, binary_function_repr=\"UNHEX\")\n\n        def datetrunc_sql(self, expression: exp.DateTrunc) -> str:\n            unit = expression.args.get(\"unit\")\n            date = expression.this\n\n            week_start = _week_unit_to_dow(unit)\n            unit = unit_to_str(expression)\n\n            if week_start:\n                result = self.sql(\n                    _build_week_trunc_expression(date, week_start, preserve_start_day=True)\n                )\n            else:\n                result = self.func(\"DATE_TRUNC\", unit, date)\n\n            if (\n                expression.args.get(\"input_type_preserved\")\n                and date.is_type(*exp.DataType.TEMPORAL_TYPES)\n                and not (is_date_unit(unit) and date.is_type(exp.DType.DATE))\n            ):\n                return self.sql(exp.Cast(this=result, to=date.type))\n\n            return result\n\n        def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str:\n            unit = unit_to_str(expression)\n            zone = expression.args.get(\"zone\")\n            timestamp = expression.this\n            date_unit = is_date_unit(unit)\n\n            if date_unit and zone:\n                # BigQuery's TIMESTAMP_TRUNC with timezone truncates in the target timezone and returns as UTC.\n                # Double AT TIME ZONE needed for BigQuery compatibility:\n                # 1. First AT TIME ZONE: ensures truncation happens in the target timezone\n                # 2. Second AT TIME ZONE: converts the DATE result back to TIMESTAMPTZ (preserving time component)\n                timestamp = exp.AtTimeZone(this=timestamp, zone=zone)\n                result_sql = self.func(\"DATE_TRUNC\", unit, timestamp)\n                return self.sql(exp.AtTimeZone(this=result_sql, zone=zone))\n\n            result = self.func(\"DATE_TRUNC\", unit, timestamp)\n            if expression.args.get(\"input_type_preserved\"):\n                if timestamp.type and timestamp.is_type(exp.DType.TIME, exp.DType.TIMETZ):\n                    dummy_date = exp.Cast(\n                        this=exp.Literal.string(\"1970-01-01\"),\n                        to=exp.DataType(this=exp.DType.DATE),\n                    )\n                    date_time = exp.Add(this=dummy_date, expression=timestamp)\n                    result = self.func(\"DATE_TRUNC\", unit, date_time)\n                    return self.sql(exp.Cast(this=result, to=timestamp.type))\n\n                if timestamp.is_type(*exp.DataType.TEMPORAL_TYPES) and not (\n                    date_unit and timestamp.is_type(exp.DType.DATE)\n                ):\n                    return self.sql(exp.Cast(this=result, to=timestamp.type))\n\n            return result\n\n        def trim_sql(self, expression: exp.Trim) -> str:\n            expression.this.replace(_cast_to_varchar(expression.this))\n            if expression.expression:\n                expression.expression.replace(_cast_to_varchar(expression.expression))\n\n            result_sql = super().trim_sql(expression)\n            return _gen_with_cast_to_blob(self, expression, result_sql)\n\n        def round_sql(self, expression: exp.Round) -> str:\n            this = expression.this\n            decimals = expression.args.get(\"decimals\")\n            truncate = expression.args.get(\"truncate\")\n\n            # DuckDB requires the scale (decimals) argument to be an INT\n            # Some dialects (e.g., Snowflake) allow non-integer scales and cast to an integer internally\n            if decimals is not None and expression.args.get(\"casts_non_integer_decimals\"):\n                if not (decimals.is_int or decimals.is_type(*exp.DataType.INTEGER_TYPES)):\n                    decimals = exp.cast(decimals, exp.DType.INT)\n\n            func = \"ROUND\"\n            if truncate:\n                # BigQuery uses ROUND_HALF_EVEN; Snowflake uses HALF_TO_EVEN\n                if truncate.this in (\"ROUND_HALF_EVEN\", \"HALF_TO_EVEN\"):\n                    func = \"ROUND_EVEN\"\n                    truncate = None\n                # BigQuery uses ROUND_HALF_AWAY_FROM_ZERO; Snowflake uses HALF_AWAY_FROM_ZERO\n                elif truncate.this in (\"ROUND_HALF_AWAY_FROM_ZERO\", \"HALF_AWAY_FROM_ZERO\"):\n                    truncate = None\n\n            return self.func(func, this, decimals, truncate)\n\n        def strtok_sql(self, expression: exp.Strtok) -> str:\n            string_arg = expression.this\n            delimiter_arg = expression.args.get(\"delimiter\")\n            part_index_arg = expression.args.get(\"part_index\")\n\n            if delimiter_arg and part_index_arg:\n                # Escape regex chars and build character class at runtime using REGEXP_REPLACE\n                escaped_delimiter = exp.Anonymous(\n                    this=\"REGEXP_REPLACE\",\n                    expressions=[\n                        delimiter_arg,\n                        exp.Literal.string(\n                            r\"([\\[\\]^.\\-*+?(){}|$\\\\])\"\n                        ),  # Escape problematic regex chars\n                        exp.Literal.string(\n                            r\"\\\\\\1\"\n                        ),  # Replace with escaped version using $1 backreference\n                        exp.Literal.string(\"g\"),  # Global flag\n                    ],\n                )\n                # CASE WHEN delimiter = '' THEN '' ELSE CONCAT('[', escaped_delimiter, ']') END\n                regex_pattern = (\n                    exp.case()\n                    .when(delimiter_arg.eq(exp.Literal.string(\"\")), exp.Literal.string(\"\"))\n                    .else_(\n                        exp.func(\n                            \"CONCAT\",\n                            exp.Literal.string(\"[\"),\n                            escaped_delimiter,\n                            exp.Literal.string(\"]\"),\n                        )\n                    )\n                )\n\n                # STRTOK skips empty strings, so we need to filter them out\n                # LIST_FILTER(REGEXP_SPLIT_TO_ARRAY(string, pattern), x -> x != '')[index]\n                split_array = exp.func(\"REGEXP_SPLIT_TO_ARRAY\", string_arg, regex_pattern)\n                x = exp.to_identifier(\"x\")\n                is_empty = x.eq(exp.Literal.string(\"\"))\n                filtered_array = exp.func(\n                    \"LIST_FILTER\",\n                    split_array,\n                    exp.Lambda(this=exp.not_(is_empty.copy()), expressions=[x.copy()]),\n                )\n                base_func = exp.Bracket(\n                    this=filtered_array,\n                    expressions=[part_index_arg],\n                    offset=1,\n                )\n\n                # Use template with the built regex pattern\n                result = exp.replace_placeholders(\n                    self.STRTOK_TEMPLATE.copy(),\n                    string=string_arg,\n                    delimiter=delimiter_arg,\n                    part_index=part_index_arg,\n                    base_func=base_func,\n                )\n\n                return self.sql(result)\n\n            return self.function_fallback_sql(expression)\n\n        def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:\n            result = self.func(\"APPROX_QUANTILE\", expression.this, expression.args.get(\"quantile\"))\n\n            # DuckDB returns integers for APPROX_QUANTILE, cast to DOUBLE if the expected type is a real type\n            if expression.is_type(*exp.DataType.REAL_TYPES):\n                result = f\"CAST({result} AS DOUBLE)\"\n\n            return result\n\n        def approxquantiles_sql(self, expression: exp.ApproxQuantiles) -> str:\n            \"\"\"\n            BigQuery's APPROX_QUANTILES(expr, n) returns an array of n+1 approximate quantile values\n            dividing the input distribution into n equal-sized buckets.\n\n            Both BigQuery and DuckDB use approximate algorithms for quantile estimation, but BigQuery\n            does not document the specific algorithm used so results may differ. DuckDB does not\n            support RESPECT NULLS.\n            \"\"\"\n            this = expression.this\n            if isinstance(this, exp.Distinct):\n                # APPROX_QUANTILES requires 2 args and DISTINCT node grabs both\n                if len(this.expressions) < 2:\n                    self.unsupported(\"APPROX_QUANTILES requires a bucket count argument\")\n                    return self.function_fallback_sql(expression)\n                num_quantiles_expr = this.expressions[1].pop()\n            else:\n                num_quantiles_expr = expression.expression\n\n            if not isinstance(num_quantiles_expr, exp.Literal) or not num_quantiles_expr.is_int:\n                self.unsupported(\"APPROX_QUANTILES bucket count must be a positive integer\")\n                return self.function_fallback_sql(expression)\n\n            num_quantiles = t.cast(int, num_quantiles_expr.to_py())\n            if num_quantiles <= 0:\n                self.unsupported(\"APPROX_QUANTILES bucket count must be a positive integer\")\n                return self.function_fallback_sql(expression)\n\n            quantiles = [\n                exp.Literal.number(Decimal(i) / Decimal(num_quantiles))\n                for i in range(num_quantiles + 1)\n            ]\n\n            return self.sql(\n                exp.ApproxQuantile(this=this, quantile=exp.Array(expressions=quantiles))\n            )\n\n        def jsonextractscalar_sql(self, expression: exp.JSONExtractScalar) -> str:\n            if expression.args.get(\"scalar_only\"):\n                expression = exp.JSONExtractScalar(\n                    this=rename_func(\"JSON_VALUE\")(self, expression), expression=\"'$'\"\n                )\n            return _arrow_json_extract_sql(self, expression)\n\n        def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:\n            this = expression.this\n\n            if _is_binary(this):\n                expression.type = exp.DataType.build(\"BINARY\")\n\n            arg = _cast_to_bit(this)\n\n            if isinstance(this, exp.Neg):\n                arg = exp.Paren(this=arg)\n\n            expression.set(\"this\", arg)\n\n            result_sql = f\"~{self.sql(expression, 'this')}\"\n\n            return _gen_with_cast_to_blob(self, expression, result_sql)\n\n        def window_sql(self, expression: exp.Window) -> str:\n            this = expression.this\n            if isinstance(this, exp.Corr) or (\n                isinstance(this, exp.Filter) and isinstance(this.this, exp.Corr)\n            ):\n                return self._corr_sql(expression)\n\n            return super().window_sql(expression)\n\n        def filter_sql(self, expression: exp.Filter) -> str:\n            if isinstance(expression.this, exp.Corr):\n                return self._corr_sql(expression)\n\n            return super().filter_sql(expression)\n\n        def _corr_sql(\n            self,\n            expression: t.Union[exp.Filter, exp.Window, exp.Corr],\n        ) -> str:\n            if isinstance(expression, exp.Corr) and not expression.args.get(\n                \"null_on_zero_variance\"\n            ):\n                return self.func(\"CORR\", expression.this, expression.expression)\n\n            corr_expr = _maybe_corr_null_to_false(expression)\n            if corr_expr is None:\n                if isinstance(expression, exp.Window):\n                    return super().window_sql(expression)\n                if isinstance(expression, exp.Filter):\n                    return super().filter_sql(expression)\n                corr_expr = expression  # make mypy happy\n\n            return self.sql(exp.case().when(exp.IsNan(this=corr_expr), exp.null()).else_(corr_expr))\n"
  },
  {
    "path": "sqlglot/dialects/dune.py",
    "content": "from __future__ import annotations\n\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.trino import Trino\nfrom sqlglot.parsers.dune import DuneParser\n\n\nclass Dune(Trino):\n    Parser = DuneParser\n\n    class Tokenizer(Trino.Tokenizer):\n        HEX_STRINGS = [\"0x\", (\"X'\", \"'\")]\n\n    class Generator(Trino.Generator):\n        TRANSFORMS = {\n            **Trino.Generator.TRANSFORMS,\n            exp.HexString: lambda self, e: f\"0x{e.this}\",\n        }\n"
  },
  {
    "path": "sqlglot/dialects/exasol.py",
    "content": "from __future__ import annotations\n\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.errors import UnsupportedError\nfrom sqlglot.dialects.dialect import (\n    DATE_ADD_OR_SUB,\n    Dialect,\n    NormalizationStrategy,\n    groupconcat_sql,\n    no_last_day_sql,\n    rename_func,\n    strposition_sql,\n    timestrtotime_sql,\n    timestamptrunc_sql,\n)\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.optimizer.scope import build_scope\nfrom sqlglot.parsers.exasol import DATE_UNITS, ExasolParser\nfrom sqlglot.tokens import TokenType\n\n\ndef _sha2_sql(self: Exasol.Generator, expression: exp.SHA2) -> str:\n    length = expression.text(\"length\")\n    func_name = \"HASH_SHA256\" if length == \"256\" else \"HASH_SHA512\"\n    return self.func(func_name, expression.this)\n\n\ndef _date_diff_sql(self: Exasol.Generator, expression: exp.DateDiff | exp.TsOrDsDiff) -> str:\n    unit = expression.text(\"unit\").upper() or \"DAY\"\n\n    if unit not in DATE_UNITS:\n        self.unsupported(f\"'{unit}' is not supported in Exasol.\")\n        return self.function_fallback_sql(expression)\n\n    return self.func(f\"{unit}S_BETWEEN\", expression.this, expression.expression)\n\n\n# https://docs.exasol.com/db/latest/sql/select.htm#:~:text=If%20you%20have,local.x%3E10\ndef _add_local_prefix_for_aliases(expression: exp.Expr) -> exp.Expr:\n    if isinstance(expression, exp.Select):\n        aliases: dict[str, bool] = {\n            alias.name: bool(alias.args.get(\"quoted\"))\n            for sel in expression.selects\n            if isinstance(sel, exp.Alias) and (alias := sel.args.get(\"alias\"))\n        }\n\n        table = expression.find(exp.Table)\n        table_ident = table.this if table else None\n\n        if (\n            table_ident\n            and table_ident.name.upper() == \"LOCAL\"\n            and not bool(table_ident.args.get(\"quoted\"))\n        ):\n            table_ident.replace(exp.to_identifier(table_ident.name.upper(), quoted=True))\n\n        def prefix_local(node, visible_aliases: dict[str, bool]) -> exp.Expr:\n            if isinstance(node, exp.Column) and not node.table:\n                if node.name in visible_aliases:\n                    return exp.Column(\n                        this=exp.to_identifier(node.name, quoted=visible_aliases[node.name]),\n                        table=exp.to_identifier(\"LOCAL\", quoted=False),\n                    )\n            return node\n\n        for key in (\"where\", \"group\", \"having\"):\n            if arg := expression.args.get(key):\n                expression.set(key, arg.transform(lambda node: prefix_local(node, aliases)))\n\n        seen_aliases: dict[str, bool] = {}\n        new_selects: list[exp.Expr] = []\n        for sel in expression.selects:\n            if isinstance(sel, exp.Alias):\n                inner = sel.this.transform(lambda node: prefix_local(node, seen_aliases))\n                sel.set(\"this\", inner)\n\n                alias_node = sel.args.get(\"alias\")\n\n                seen_aliases[sel.alias] = bool(alias_node and getattr(alias_node, \"quoted\", False))\n                new_selects.append(sel)\n            else:\n                new_selects.append(sel.transform(lambda node: prefix_local(node, seen_aliases)))\n        expression.set(\"expressions\", new_selects)\n\n    return expression\n\n\ndef _trunc_sql(self: Exasol.Generator, kind: str, expression: exp.DateTrunc) -> str:\n    unit = expression.text(\"unit\")\n    node = expression.this.this if isinstance(expression.this, exp.Cast) else expression.this\n    expr_sql = self.sql(node)\n    if isinstance(node, exp.Literal) and node.is_string:\n        expr_sql = (\n            f\"{kind} '{node.this.replace('T', ' ')}'\"\n            if kind == \"TIMESTAMP\"\n            else f\"DATE '{node.this}'\"\n        )\n    return f\"DATE_TRUNC('{unit}', {expr_sql})\"\n\n\ndef _date_trunc_sql(self: Exasol.Generator, expression: exp.DateTrunc) -> str:\n    return _trunc_sql(self, \"DATE\", expression)\n\n\ndef _timestamp_trunc_sql(self: Exasol.Generator, expression: exp.DateTrunc) -> str:\n    return _trunc_sql(self, \"TIMESTAMP\", expression)\n\n\ndef is_case_insensitive(node: exp.Expr) -> bool:\n    return isinstance(node, exp.Collate) and node.text(\"expression\").upper() == \"UTF8_LCASE\"\n\n\ndef _substring_index_sql(self: Exasol.Generator, expression: exp.SubstringIndex) -> str:\n    this = expression.this\n    delimiter = expression.args[\"delimiter\"]\n    count_node = expression.args[\"count\"]\n    count_sql = self.sql(expression, \"count\")\n    num = count_node.to_py() if count_node.is_number else 0\n\n    haystack_sql = self.sql(this)\n    if num == 0:\n        return self.func(\"SUBSTR\", haystack_sql, \"1\", \"0\")\n\n    from_right = num < 0\n    direction = \"-1\" if from_right else \"1\"\n    occur = self.func(\"ABS\", count_sql) if from_right else count_sql\n\n    delimiter_sql = self.sql(delimiter)\n\n    position = self.func(\n        \"INSTR\",\n        self.func(\"LOWER\", haystack_sql) if is_case_insensitive(this) else haystack_sql,\n        self.func(\"LOWER\", delimiter_sql) if is_case_insensitive(delimiter) else delimiter_sql,\n        direction,\n        occur,\n    )\n    nullable_pos = self.func(\"NULLIF\", position, \"0\")\n\n    if from_right:\n        start = self.func(\n            \"NVL\", f\"{nullable_pos} + {self.func('LENGTH', delimiter_sql)}\", direction\n        )\n        return self.func(\"SUBSTR\", haystack_sql, start)\n\n    length = self.func(\"NVL\", f\"{nullable_pos} - 1\", self.func(\"LENGTH\", haystack_sql))\n    return self.func(\"SUBSTR\", haystack_sql, direction, length)\n\n\n# https://docs.exasol.com/db/latest/sql/select.htm#:~:text=The%20select_list%20defines%20the%20columns%20of%20the%20result%20table.%20If%20*%20is%20used%2C%20all%20columns%20are%20listed.%20You%20can%20use%20an%20expression%20like%20t.*%20to%20list%20all%20columns%20of%20the%20table%20t%2C%20the%20view%20t%2C%20or%20the%20object%20with%20the%20table%20alias%20t.\ndef _qualify_unscoped_star(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Exasol doesn't support a bare * alongside other select items, so we rewrite it\n    Rewrite: SELECT *, <other> FROM <Table>\n    Into: SELECT T.*, <other> FROM <Table> AS T\n    \"\"\"\n\n    if not isinstance(expression, exp.Select):\n        return expression\n\n    select_expressions = expression.expressions or []\n\n    def is_bare_star(expr: exp.Expr) -> bool:\n        return isinstance(expr, exp.Star) and expr.this is None\n\n    has_other_expression = False\n    bare_star_expr: exp.Expr | None = None\n    for expr in select_expressions:\n        has_bare_star = is_bare_star(expr)\n        if has_bare_star and bare_star_expr is None:\n            bare_star_expr = expr\n        elif not has_bare_star:\n            has_other_expression = True\n        if bare_star_expr and has_other_expression:\n            break\n\n    if not (bare_star_expr and has_other_expression):\n        return expression\n\n    scope = build_scope(expression)\n\n    if not scope or not scope.selected_sources:\n        return expression\n\n    table_identifiers: list[exp.Identifier] = []\n\n    for source_name, (source_expr, _) in scope.selected_sources.items():\n        ident = (\n            source_expr.this.copy()\n            if isinstance(source_expr, exp.Table) and isinstance(source_expr.this, exp.Identifier)\n            else exp.to_identifier(source_name)\n        )\n        table_identifiers.append(ident)\n\n    qualified_star_columns = [\n        exp.Column(this=bare_star_expr.copy(), table=ident) for ident in table_identifiers\n    ]\n\n    new_select_expressions: list[exp.Expr] = []\n\n    for select_expr in select_expressions:\n        new_select_expressions.extend(qualified_star_columns) if is_bare_star(\n            select_expr\n        ) else new_select_expressions.append(select_expr)\n\n    expression.set(\"expressions\", new_select_expressions)\n    return expression\n\n\ndef _add_date_sql(self: Exasol.Generator, expression: DATE_ADD_OR_SUB) -> str:\n    interval = expression.expression if isinstance(expression.expression, exp.Interval) else None\n\n    unit = (\n        (interval.text(\"unit\") or \"DAY\").upper()\n        if interval is not None\n        else (expression.text(\"unit\") or \"DAY\").upper()\n    )\n\n    if unit not in DATE_UNITS:\n        self.unsupported(f\"'{unit}' is not supported in Exasol.\")\n        return self.function_fallback_sql(expression)\n\n    offset_expr: exp.Expr = expression.expression\n    if interval is not None:\n        offset_expr = interval.this\n\n    if isinstance(expression, exp.DateSub):\n        offset_expr = exp.Neg(this=offset_expr)\n\n    return self.func(f\"ADD_{unit}S\", expression.this, offset_expr)\n\n\ndef _group_by_all(expression: exp.Expr) -> exp.Expr:\n    if not isinstance(expression, exp.Select):\n        return expression\n\n    group = expression.args.get(\"group\")\n    if not group or not group.args.get(\"all\"):\n        return expression\n\n    if expression.is_star:\n        if any(proj.find(exp.AggFunc) for proj in expression.expressions):\n            raise UnsupportedError(\n                \"GROUP BY ALL with star projection and aggregates is not supported by Exasol\"\n            )\n        expression.set(\"distinct\", exp.Distinct())\n        expression.set(\"group\", None)\n        return expression\n\n    group_positions = [\n        exp.Literal.number(i)\n        for i, proj in enumerate(expression.expressions, start=1)\n        if not proj.find(exp.AggFunc)\n    ]\n\n    if not group_positions:\n        expression.set(\"group\", None)\n        return expression\n\n    group.set(\"expressions\", group_positions)\n    group.set(\"all\", None)\n\n    return expression\n\n\nclass Exasol(Dialect):\n    # https://docs.exasol.com/db/latest/sql_references/basiclanguageelements.htm#SQLidentifier\n    NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE\n    # https://docs.exasol.com/db/latest/sql_references/data_types/datatypesoverview.htm\n    SUPPORTS_USER_DEFINED_TYPES = False\n    # https://docs.exasol.com/db/latest/sql/select.htm\n    SUPPORTS_COLUMN_JOIN_MARKS = True\n    NULL_ORDERING = \"nulls_are_last\"\n    # https://docs.exasol.com/db/latest/sql_references/literals.htm#StringLiterals\n    CONCAT_COALESCE = True\n\n    TIME_MAPPING = {\n        \"yyyy\": \"%Y\",\n        \"YYYY\": \"%Y\",\n        \"yy\": \"%y\",\n        \"YY\": \"%y\",\n        \"mm\": \"%m\",\n        \"MM\": \"%m\",\n        \"MONTH\": \"%B\",\n        \"MON\": \"%b\",\n        \"dd\": \"%d\",\n        \"DD\": \"%d\",\n        \"DAY\": \"%A\",\n        \"DY\": \"%a\",\n        \"H12\": \"%I\",\n        \"H24\": \"%H\",\n        \"HH\": \"%H\",\n        \"ID\": \"%u\",\n        \"vW\": \"%V\",\n        \"IW\": \"%V\",\n        \"vYYY\": \"%G\",\n        \"IYYY\": \"%G\",\n        \"MI\": \"%M\",\n        \"SS\": \"%S\",\n        \"uW\": \"%W\",\n        \"UW\": \"%U\",\n        \"Z\": \"%z\",\n    }\n\n    class Tokenizer(tokens.Tokenizer):\n        IDENTIFIERS = ['\"', (\"[\", \"]\")]\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"USER\": TokenType.CURRENT_USER,\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/if.htm\n            \"ENDIF\": TokenType.END,\n            \"LONG VARCHAR\": TokenType.TEXT,\n            \"REGEXP_LIKE\": TokenType.RLIKE,\n            \"SEPARATOR\": TokenType.SEPARATOR,\n            \"SYSTIMESTAMP\": TokenType.SYSTIMESTAMP,\n        }\n        KEYWORDS.pop(\"DIV\")\n\n    Parser = ExasolParser\n\n    class Generator(generator.Generator):\n        # https://docs.exasol.com/db/latest/sql_references/data_types/datatypedetails.htm#StringDataType\n        STRING_TYPE_MAPPING = {\n            exp.DType.BLOB: \"VARCHAR\",\n            exp.DType.LONGBLOB: \"VARCHAR\",\n            exp.DType.LONGTEXT: \"VARCHAR\",\n            exp.DType.MEDIUMBLOB: \"VARCHAR\",\n            exp.DType.MEDIUMTEXT: \"VARCHAR\",\n            exp.DType.TINYBLOB: \"VARCHAR\",\n            exp.DType.TINYTEXT: \"VARCHAR\",\n            # https://docs.exasol.com/db/latest/sql_references/data_types/datatypealiases.htm\n            exp.DType.TEXT: \"LONG VARCHAR\",\n            exp.DType.VARBINARY: \"VARCHAR\",\n        }\n\n        # https://docs.exasol.com/db/latest/sql_references/data_types/datatypealiases.htm\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            **STRING_TYPE_MAPPING,\n            exp.DType.TINYINT: \"SMALLINT\",\n            exp.DType.MEDIUMINT: \"INT\",\n            exp.DType.DECIMAL32: \"DECIMAL\",\n            exp.DType.DECIMAL64: \"DECIMAL\",\n            exp.DType.DECIMAL128: \"DECIMAL\",\n            exp.DType.DECIMAL256: \"DECIMAL\",\n            exp.DType.DATETIME: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPLTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPNTZ: \"TIMESTAMP\",\n        }\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            # Exasol supports a fixed default precision of 3 for TIMESTAMP WITH LOCAL TIME ZONE\n            # and does not allow specifying a different custom precision\n            if expression.is_type(exp.DType.TIMESTAMPLTZ):\n                return \"TIMESTAMP WITH LOCAL TIME ZONE\"\n\n            return super().datatype_sql(expression)\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/every.htm\n            exp.All: rename_func(\"EVERY\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_and.htm\n            exp.BitwiseAnd: rename_func(\"BIT_AND\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_or.htm\n            exp.BitwiseOr: rename_func(\"BIT_OR\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_not.htm\n            exp.BitwiseNot: rename_func(\"BIT_NOT\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_lshift.htm\n            exp.BitwiseLeftShift: rename_func(\"BIT_LSHIFT\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_rshift.htm\n            exp.BitwiseRightShift: rename_func(\"BIT_RSHIFT\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/bit_xor.htm\n            exp.BitwiseXor: rename_func(\"BIT_XOR\"),\n            exp.CurrentSchema: lambda *_: \"CURRENT_SCHEMA\",\n            exp.DateDiff: _date_diff_sql,\n            exp.DateAdd: _add_date_sql,\n            exp.TsOrDsAdd: _add_date_sql,\n            exp.DateSub: _add_date_sql,\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/div.htm#DIV\n            exp.IntDiv: rename_func(\"DIV\"),\n            exp.TsOrDsDiff: _date_diff_sql,\n            exp.DateTrunc: _date_trunc_sql,\n            exp.DayOfWeek: lambda self, e: f\"CAST(TO_CHAR({self.sql(e, 'this')}, 'D') AS INTEGER)\",\n            exp.DatetimeTrunc: timestamptrunc_sql(),\n            exp.GroupConcat: lambda self, e: groupconcat_sql(\n                self, e, func_name=\"LISTAGG\", within_group=True\n            ),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/edit_distance.htm#EDIT_DISTANCE\n            exp.Levenshtein: unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\", \"max_dist\")(\n                rename_func(\"EDIT_DISTANCE\")\n            ),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/mod.htm\n            exp.Mod: rename_func(\"MOD\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/from_posix_time.htm\n            exp.UnixToTime: lambda self, e: self.func(\"FROM_POSIX_TIME\", e.this),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/rank.htm\n            exp.Rank: unsupported_args(\"expressions\")(lambda *_: \"RANK()\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/dense_rank.htm\n            exp.DenseRank: unsupported_args(\"expressions\")(lambda *_: \"DENSE_RANK()\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/regexp_substr.htm\n            exp.RegexpExtract: unsupported_args(\"parameters\", \"group\")(\n                rename_func(\"REGEXP_SUBSTR\")\n            ),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/regexp_replace.htm\n            exp.RegexpReplace: unsupported_args(\"modifiers\")(rename_func(\"REGEXP_REPLACE\")),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/var_pop.htm\n            exp.VariancePop: rename_func(\"VAR_POP\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/approximate_count_distinct.htm\n            exp.ApproxDistinct: unsupported_args(\"accuracy\")(\n                rename_func(\"APPROXIMATE_COUNT_DISTINCT\")\n            ),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/to_char%20(datetime).htm\n            exp.TimeToStr: lambda self, e: self.func(\"TO_CHAR\", e.this, self.format_time(e)),\n            exp.ToChar: lambda self, e: self.func(\"TO_CHAR\", e.this, self.format_time(e)),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/to_date.htm\n            exp.TsOrDsToDate: lambda self, e: self.func(\"TO_DATE\", e.this, self.format_time(e)),\n            exp.TimeStrToTime: timestrtotime_sql,\n            exp.TimestampTrunc: _timestamp_trunc_sql,\n            exp.StrToTime: lambda self, e: self.func(\"TO_DATE\", e.this, self.format_time(e)),\n            exp.CurrentUser: lambda *_: \"CURRENT_USER\",\n            exp.AtTimeZone: lambda self, e: self.func(\n                \"CONVERT_TZ\",\n                e.this,\n                \"'UTC'\",\n                e.args.get(\"zone\"),\n            ),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/instr.htm\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self, e, func_name=\"INSTR\", supports_position=True, supports_occurrence=True\n            ),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/hash_sha%5B1%5D.htm#HASH_SHA%5B1%5D\n            exp.SHA: rename_func(\"HASH_SHA\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/hash_sha256.htm\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/hash_sha512.htm\n            exp.SHA2: _sha2_sql,\n            exp.MD5: rename_func(\"HASH_MD5\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/hashtype_md5.htm\n            exp.MD5Digest: rename_func(\"HASHTYPE_MD5\"),\n            # https://docs.exasol.com/db/latest/sql/create_view.htm\n            exp.CommentColumnConstraint: lambda self, e: f\"COMMENT IS {self.sql(e, 'this')}\",\n            exp.Select: transforms.preprocess(\n                [\n                    _qualify_unscoped_star,\n                    _add_local_prefix_for_aliases,\n                    _group_by_all,\n                ]\n            ),\n            exp.SubstringIndex: _substring_index_sql,\n            exp.WeekOfYear: rename_func(\"WEEK\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/to_date.htm\n            exp.Date: rename_func(\"TO_DATE\"),\n            # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/to_timestamp.htm\n            exp.Timestamp: rename_func(\"TO_TIMESTAMP\"),\n            exp.Quarter: lambda self, e: f\"CEIL(MONTH(TO_DATE({self.sql(e, 'this')}))/3)\",\n            exp.LastDay: no_last_day_sql,\n        }\n\n        # https://docs.exasol.com/db/7.1/sql_references/system_tables/metadata/exa_sql_keywords.htm\n        RESERVED_KEYWORDS = {\n            \"absolute\",\n            \"action\",\n            \"add\",\n            \"after\",\n            \"all\",\n            \"allocate\",\n            \"alter\",\n            \"and\",\n            \"any\",\n            \"append\",\n            \"are\",\n            \"array\",\n            \"as\",\n            \"asc\",\n            \"asensitive\",\n            \"assertion\",\n            \"at\",\n            \"attribute\",\n            \"authid\",\n            \"authorization\",\n            \"before\",\n            \"begin\",\n            \"between\",\n            \"bigint\",\n            \"binary\",\n            \"bit\",\n            \"blob\",\n            \"blocked\",\n            \"bool\",\n            \"boolean\",\n            \"both\",\n            \"by\",\n            \"byte\",\n            \"call\",\n            \"called\",\n            \"cardinality\",\n            \"cascade\",\n            \"cascaded\",\n            \"case\",\n            \"casespecific\",\n            \"cast\",\n            \"catalog\",\n            \"chain\",\n            \"char\",\n            \"character\",\n            \"character_set_catalog\",\n            \"character_set_name\",\n            \"character_set_schema\",\n            \"characteristics\",\n            \"check\",\n            \"checked\",\n            \"clob\",\n            \"close\",\n            \"coalesce\",\n            \"collate\",\n            \"collation\",\n            \"collation_catalog\",\n            \"collation_name\",\n            \"collation_schema\",\n            \"column\",\n            \"commit\",\n            \"condition\",\n            \"connect_by_iscycle\",\n            \"connect_by_isleaf\",\n            \"connect_by_root\",\n            \"connection\",\n            \"constant\",\n            \"constraint\",\n            \"constraint_state_default\",\n            \"constraints\",\n            \"constructor\",\n            \"contains\",\n            \"continue\",\n            \"control\",\n            \"convert\",\n            \"corresponding\",\n            \"create\",\n            \"cs\",\n            \"csv\",\n            \"cube\",\n            \"current\",\n            \"current_cluster\",\n            \"current_cluster_uid\",\n            \"current_date\",\n            \"current_path\",\n            \"current_role\",\n            \"current_schema\",\n            \"current_session\",\n            \"current_statement\",\n            \"current_time\",\n            \"current_timestamp\",\n            \"current_user\",\n            \"cursor\",\n            \"cycle\",\n            \"data\",\n            \"datalink\",\n            \"datetime_interval_code\",\n            \"datetime_interval_precision\",\n            \"day\",\n            \"dbtimezone\",\n            \"deallocate\",\n            \"dec\",\n            \"decimal\",\n            \"declare\",\n            \"default\",\n            \"default_like_escape_character\",\n            \"deferrable\",\n            \"deferred\",\n            \"defined\",\n            \"definer\",\n            \"delete\",\n            \"deref\",\n            \"derived\",\n            \"desc\",\n            \"describe\",\n            \"descriptor\",\n            \"deterministic\",\n            \"disable\",\n            \"disabled\",\n            \"disconnect\",\n            \"dispatch\",\n            \"distinct\",\n            \"dlurlcomplete\",\n            \"dlurlpath\",\n            \"dlurlpathonly\",\n            \"dlurlscheme\",\n            \"dlurlserver\",\n            \"dlvalue\",\n            \"do\",\n            \"domain\",\n            \"double\",\n            \"drop\",\n            \"dynamic\",\n            \"dynamic_function\",\n            \"dynamic_function_code\",\n            \"each\",\n            \"else\",\n            \"elseif\",\n            \"elsif\",\n            \"emits\",\n            \"enable\",\n            \"enabled\",\n            \"end\",\n            \"end-exec\",\n            \"endif\",\n            \"enforce\",\n            \"equals\",\n            \"errors\",\n            \"escape\",\n            \"except\",\n            \"exception\",\n            \"exec\",\n            \"execute\",\n            \"exists\",\n            \"exit\",\n            \"export\",\n            \"external\",\n            \"extract\",\n            \"false\",\n            \"fbv\",\n            \"fetch\",\n            \"file\",\n            \"final\",\n            \"first\",\n            \"float\",\n            \"following\",\n            \"for\",\n            \"forall\",\n            \"force\",\n            \"format\",\n            \"found\",\n            \"free\",\n            \"from\",\n            \"fs\",\n            \"full\",\n            \"function\",\n            \"general\",\n            \"generated\",\n            \"geometry\",\n            \"get\",\n            \"global\",\n            \"go\",\n            \"goto\",\n            \"grant\",\n            \"granted\",\n            \"group\",\n            \"group_concat\",\n            \"grouping\",\n            \"groups\",\n            \"hashtype\",\n            \"hashtype_format\",\n            \"having\",\n            \"high\",\n            \"hold\",\n            \"hour\",\n            \"identity\",\n            \"if\",\n            \"ifnull\",\n            \"immediate\",\n            \"impersonate\",\n            \"implementation\",\n            \"import\",\n            \"in\",\n            \"index\",\n            \"indicator\",\n            \"inner\",\n            \"inout\",\n            \"input\",\n            \"insensitive\",\n            \"insert\",\n            \"instance\",\n            \"instantiable\",\n            \"int\",\n            \"integer\",\n            \"integrity\",\n            \"intersect\",\n            \"interval\",\n            \"into\",\n            \"inverse\",\n            \"invoker\",\n            \"is\",\n            \"iterate\",\n            \"join\",\n            \"key_member\",\n            \"key_type\",\n            \"large\",\n            \"last\",\n            \"lateral\",\n            \"ldap\",\n            \"leading\",\n            \"leave\",\n            \"left\",\n            \"level\",\n            \"like\",\n            \"limit\",\n            \"listagg\",\n            \"localtime\",\n            \"localtimestamp\",\n            \"locator\",\n            \"log\",\n            \"longvarchar\",\n            \"loop\",\n            \"low\",\n            \"map\",\n            \"match\",\n            \"matched\",\n            \"merge\",\n            \"method\",\n            \"minus\",\n            \"minute\",\n            \"mod\",\n            \"modifies\",\n            \"modify\",\n            \"module\",\n            \"month\",\n            \"names\",\n            \"national\",\n            \"natural\",\n            \"nchar\",\n            \"nclob\",\n            \"new\",\n            \"next\",\n            \"nls_date_format\",\n            \"nls_date_language\",\n            \"nls_first_day_of_week\",\n            \"nls_numeric_characters\",\n            \"nls_timestamp_format\",\n            \"no\",\n            \"nocycle\",\n            \"nologging\",\n            \"none\",\n            \"not\",\n            \"null\",\n            \"nullif\",\n            \"number\",\n            \"numeric\",\n            \"nvarchar\",\n            \"nvarchar2\",\n            \"object\",\n            \"of\",\n            \"off\",\n            \"old\",\n            \"on\",\n            \"only\",\n            \"open\",\n            \"option\",\n            \"options\",\n            \"or\",\n            \"order\",\n            \"ordering\",\n            \"ordinality\",\n            \"others\",\n            \"out\",\n            \"outer\",\n            \"output\",\n            \"over\",\n            \"overlaps\",\n            \"overlay\",\n            \"overriding\",\n            \"pad\",\n            \"parallel_enable\",\n            \"parameter\",\n            \"parameter_specific_catalog\",\n            \"parameter_specific_name\",\n            \"parameter_specific_schema\",\n            \"parquet\",\n            \"partial\",\n            \"path\",\n            \"permission\",\n            \"placing\",\n            \"plus\",\n            \"preceding\",\n            \"preferring\",\n            \"prepare\",\n            \"preserve\",\n            \"prior\",\n            \"privileges\",\n            \"procedure\",\n            \"profile\",\n            \"qualify\",\n            \"random\",\n            \"range\",\n            \"read\",\n            \"reads\",\n            \"real\",\n            \"recovery\",\n            \"recursive\",\n            \"ref\",\n            \"references\",\n            \"referencing\",\n            \"refresh\",\n            \"regexp_like\",\n            \"relative\",\n            \"release\",\n            \"rename\",\n            \"repeat\",\n            \"replace\",\n            \"restore\",\n            \"restrict\",\n            \"result\",\n            \"return\",\n            \"returned_length\",\n            \"returned_octet_length\",\n            \"returns\",\n            \"revoke\",\n            \"right\",\n            \"rollback\",\n            \"rollup\",\n            \"routine\",\n            \"row\",\n            \"rows\",\n            \"rowtype\",\n            \"savepoint\",\n            \"schema\",\n            \"scope\",\n            \"scope_user\",\n            \"script\",\n            \"scroll\",\n            \"search\",\n            \"second\",\n            \"section\",\n            \"security\",\n            \"select\",\n            \"selective\",\n            \"self\",\n            \"sensitive\",\n            \"separator\",\n            \"sequence\",\n            \"session\",\n            \"session_user\",\n            \"sessiontimezone\",\n            \"set\",\n            \"sets\",\n            \"shortint\",\n            \"similar\",\n            \"smallint\",\n            \"some\",\n            \"source\",\n            \"space\",\n            \"specific\",\n            \"specifictype\",\n            \"sql\",\n            \"sql_bigint\",\n            \"sql_bit\",\n            \"sql_char\",\n            \"sql_date\",\n            \"sql_decimal\",\n            \"sql_double\",\n            \"sql_float\",\n            \"sql_integer\",\n            \"sql_longvarchar\",\n            \"sql_numeric\",\n            \"sql_preprocessor_script\",\n            \"sql_real\",\n            \"sql_smallint\",\n            \"sql_timestamp\",\n            \"sql_tinyint\",\n            \"sql_type_date\",\n            \"sql_type_timestamp\",\n            \"sql_varchar\",\n            \"sqlexception\",\n            \"sqlstate\",\n            \"sqlwarning\",\n            \"start\",\n            \"state\",\n            \"statement\",\n            \"static\",\n            \"structure\",\n            \"style\",\n            \"substring\",\n            \"subtype\",\n            \"sysdate\",\n            \"system\",\n            \"system_user\",\n            \"systimestamp\",\n            \"table\",\n            \"temporary\",\n            \"text\",\n            \"then\",\n            \"time\",\n            \"timestamp\",\n            \"timezone_hour\",\n            \"timezone_minute\",\n            \"tinyint\",\n            \"to\",\n            \"trailing\",\n            \"transaction\",\n            \"transform\",\n            \"transforms\",\n            \"translation\",\n            \"treat\",\n            \"trigger\",\n            \"trim\",\n            \"true\",\n            \"truncate\",\n            \"under\",\n            \"union\",\n            \"unique\",\n            \"unknown\",\n            \"unlink\",\n            \"unnest\",\n            \"until\",\n            \"update\",\n            \"usage\",\n            \"user\",\n            \"using\",\n            \"value\",\n            \"values\",\n            \"varchar\",\n            \"varchar2\",\n            \"varray\",\n            \"verify\",\n            \"view\",\n            \"when\",\n            \"whenever\",\n            \"where\",\n            \"while\",\n            \"window\",\n            \"with\",\n            \"within\",\n            \"without\",\n            \"work\",\n            \"year\",\n            \"yes\",\n            \"zone\",\n        }\n\n        def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:\n            from_tz = expression.args.get(\"source_tz\")\n            to_tz = expression.args.get(\"target_tz\")\n            datetime = expression.args.get(\"timestamp\")\n            options = expression.args.get(\"options\")\n\n            return self.func(\"CONVERT_TZ\", datetime, from_tz, to_tz, options)\n\n        def if_sql(self, expression: exp.If) -> str:\n            this = self.sql(expression, \"this\")\n            true = self.sql(expression, \"true\")\n            false = self.sql(expression, \"false\")\n            return f\"IF {this} THEN {true} ELSE {false} ENDIF\"\n\n        def collate_sql(self, expression: exp.Collate) -> str:\n            return self.sql(expression.this)\n\n        def jsonextract_sql(self, expression: exp.JSONExtract) -> str:\n            sql = self.func(\n                \"JSON_EXTRACT\", expression.this, expression.expression, *expression.expressions\n            )\n\n            emits = self.sql(expression, \"emits\")\n            if emits:\n                sql = f\"{sql} EMITS {emits}\"\n\n            return sql\n\n        @unsupported_args(\"flag\")\n        def regexplike_sql(self, expression: exp.RegexpLike) -> str:\n            if not expression.args.get(\"full_match\"):\n                pattern = expression.expression\n                if pattern.is_string:\n                    expression.set(\"expression\", exp.Literal.string(f\".*{pattern.name}.*\"))\n                else:\n                    expression.set(\n                        \"expression\",\n                        exp.Paren(\n                            this=exp.Concat(\n                                expressions=[\n                                    exp.Literal.string(\".*\"),\n                                    pattern,\n                                    exp.Literal.string(\".*\"),\n                                ]\n                            )\n                        ),\n                    )\n            return self.binary(expression, \"REGEXP_LIKE\")\n"
  },
  {
    "path": "sqlglot/dialects/fabric.py",
    "content": "from __future__ import annotations\n\n\nfrom sqlglot import exp, transforms\nfrom sqlglot.dialects.dialect import NormalizationStrategy\nfrom sqlglot.dialects.tsql import TSQL\nfrom sqlglot.parsers.fabric import FabricParser\nfrom sqlglot.tokens import TokenType\n\n\ndef _cap_data_type_precision(expression: exp.DataType, max_precision: int = 6) -> exp.DataType:\n    \"\"\"\n    Cap the precision of to a maximum of `max_precision` digits.\n    If no precision is specified, default to `max_precision`.\n    \"\"\"\n\n    precision_param = expression.find(exp.DataTypeParam)\n\n    if precision_param and precision_param.this.is_int:\n        current_precision = precision_param.this.to_py()\n        target_precision = min(current_precision, max_precision)\n    else:\n        target_precision = max_precision\n\n    return exp.DataType(\n        this=expression.this,\n        expressions=[exp.DataTypeParam(this=exp.Literal.number(target_precision))],\n    )\n\n\ndef _add_default_precision_to_varchar(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Transform function to add VARCHAR(MAX) or CHAR(MAX) for cross-dialect conversion.\"\"\"\n    if (\n        isinstance(expression, exp.Create)\n        and expression.kind == \"TABLE\"\n        and isinstance(expression.this, exp.Schema)\n    ):\n        for column in expression.this.expressions:\n            if isinstance(column, exp.ColumnDef):\n                column_type = column.kind\n                if (\n                    isinstance(column_type, exp.DataType)\n                    and column_type.this in (exp.DType.VARCHAR, exp.DType.CHAR)\n                    and not column_type.expressions\n                ):\n                    # For transpilation, VARCHAR/CHAR without precision becomes VARCHAR(MAX)/CHAR(MAX)\n                    column_type.set(\"expressions\", [exp.var(\"MAX\")])\n\n    return expression\n\n\nclass Fabric(TSQL):\n    \"\"\"\n    Microsoft Fabric Data Warehouse dialect that inherits from T-SQL.\n\n    Microsoft Fabric is a cloud-based analytics platform that provides a unified\n    data warehouse experience. While it shares much of T-SQL's syntax, it has\n    specific differences and limitations that this dialect addresses.\n\n    Key differences from T-SQL:\n    - Case-sensitive identifiers (unlike T-SQL which is case-insensitive)\n    - Limited data type support with mappings to supported alternatives\n    - Temporal types (DATETIME2, DATETIMEOFFSET, TIME) limited to 6 digits precision\n    - Certain legacy types (MONEY, SMALLMONEY, etc.) are not supported\n    - Unicode types (NCHAR, NVARCHAR) are mapped to non-unicode equivalents\n\n    References:\n    - Data Types: https://learn.microsoft.com/en-us/fabric/data-warehouse/data-types\n    - T-SQL Surface Area: https://learn.microsoft.com/en-us/fabric/data-warehouse/tsql-surface-area\n    \"\"\"\n\n    # Fabric is case-sensitive unlike T-SQL which is case-insensitive\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE\n\n    class Tokenizer(TSQL.Tokenizer):\n        # Override T-SQL tokenizer to handle TIMESTAMP differently\n        # In T-SQL, TIMESTAMP is a synonym for ROWVERSION, but in Fabric we want it to be a datetime type\n        # Also add UTINYINT keyword mapping since T-SQL doesn't have it\n        KEYWORDS = {\n            **TSQL.Tokenizer.KEYWORDS,\n            \"TIMESTAMP\": TokenType.TIMESTAMP,\n            \"UTINYINT\": TokenType.UTINYINT,\n        }\n\n    Parser = FabricParser\n\n    class Generator(TSQL.Generator):\n        # Fabric-specific type mappings - override T-SQL types that aren't supported\n        # Reference: https://learn.microsoft.com/en-us/fabric/data-warehouse/data-types\n        TYPE_MAPPING = {\n            **TSQL.Generator.TYPE_MAPPING,\n            exp.DType.DATETIME: \"DATETIME2\",\n            exp.DType.DECIMAL: \"DECIMAL\",\n            exp.DType.IMAGE: \"VARBINARY\",\n            exp.DType.INT: \"INT\",\n            exp.DType.JSON: \"VARCHAR\",\n            exp.DType.MONEY: \"DECIMAL\",\n            exp.DType.NCHAR: \"CHAR\",\n            exp.DType.NVARCHAR: \"VARCHAR\",\n            exp.DType.ROWVERSION: \"ROWVERSION\",\n            exp.DType.SMALLDATETIME: \"DATETIME2\",\n            exp.DType.SMALLMONEY: \"DECIMAL\",\n            exp.DType.TIMESTAMP: \"DATETIME2\",\n            exp.DType.TIMESTAMPNTZ: \"DATETIME2\",\n            exp.DType.TIMESTAMPTZ: \"DATETIME2\",\n            exp.DType.TINYINT: \"SMALLINT\",\n            exp.DType.UTINYINT: \"SMALLINT\",\n            exp.DType.UUID: \"UNIQUEIDENTIFIER\",\n            exp.DType.XML: \"VARCHAR\",\n        }\n\n        TRANSFORMS = {\n            **TSQL.Generator.TRANSFORMS,\n            exp.Create: transforms.preprocess([_add_default_precision_to_varchar]),\n        }\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            # Check if this is a temporal type that needs precision handling. Fabric limits temporal\n            # types to max 6 digits precision. When no precision is specified, we default to 6 digits.\n            if (\n                expression.is_type(*exp.DataType.TEMPORAL_TYPES)\n                and expression.this != exp.DType.DATE\n            ):\n                # Create a new expression with the capped precision\n                expression = _cap_data_type_precision(expression)\n\n            return super().datatype_sql(expression)\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: str | None = None) -> str:\n            # Cast to DATETIMEOFFSET if inside an AT TIME ZONE expression\n            # https://learn.microsoft.com/en-us/sql/t-sql/data-types/datetimeoffset-transact-sql#microsoft-fabric-support\n            if expression.is_type(exp.DType.TIMESTAMPTZ):\n                at_time_zone = expression.find_ancestor(exp.AtTimeZone, exp.Select)\n\n                # Return normal cast, if the expression is not in an AT TIME ZONE context\n                if not isinstance(at_time_zone, exp.AtTimeZone):\n                    return super().cast_sql(expression, safe_prefix)\n\n                # Get the precision from the original TIMESTAMPTZ cast and cap it to 6\n                capped_data_type = _cap_data_type_precision(expression.to, max_precision=6)\n                precision = capped_data_type.find(exp.DataTypeParam)\n                precision_value = (\n                    precision.this.to_py() if precision and precision.this.is_int else 6\n                )\n\n                # Do the cast explicitly to bypass sqlglot's default handling\n                datetimeoffset = f\"CAST({expression.this} AS DATETIMEOFFSET({precision_value}))\"\n\n                return self.sql(datetimeoffset)\n\n            return super().cast_sql(expression, safe_prefix)\n\n        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:\n            # Wrap the AT TIME ZONE expression in a cast to DATETIME2 if it contains a TIMESTAMPTZ\n            ## https://learn.microsoft.com/en-us/sql/t-sql/data-types/datetimeoffset-transact-sql#microsoft-fabric-support\n            timestamptz_cast = expression.find(exp.Cast)\n            if timestamptz_cast and timestamptz_cast.to.is_type(exp.DType.TIMESTAMPTZ):\n                # Get the precision from the original TIMESTAMPTZ cast and cap it to 6\n                data_type = timestamptz_cast.to\n                capped_data_type = _cap_data_type_precision(data_type, max_precision=6)\n                precision_param = capped_data_type.find(exp.DataTypeParam)\n                precision = precision_param.this.to_py() if precision_param else 6\n\n                # Generate the AT TIME ZONE expression (which will handle the inner cast conversion)\n                at_time_zone_sql = super().attimezone_sql(expression)\n\n                # Wrap it in an outer cast to DATETIME2\n                return f\"CAST({at_time_zone_sql} AS DATETIME2({precision}))\"\n\n            return super().attimezone_sql(expression)\n\n        def unixtotime_sql(self, expression: exp.UnixToTime) -> str:\n            scale = expression.args.get(\"scale\")\n            timestamp = expression.this\n\n            if scale not in (None, exp.UnixToTime.SECONDS):\n                self.unsupported(f\"UnixToTime scale {scale} is not supported by Fabric\")\n                return \"\"\n\n            # Convert unix timestamp (seconds) to microseconds and round to avoid decimals\n            microseconds = timestamp * exp.Literal.number(\"1e6\")\n            rounded = exp.func(\"round\", microseconds, 0)\n            rounded_ms_as_bigint = exp.cast(rounded, exp.DType.BIGINT)\n\n            # Create the base datetime as '1970-01-01' cast to DATETIME2(6)\n            epoch_start = exp.cast(\"'1970-01-01'\", \"datetime2(6)\", dialect=\"fabric\")\n\n            dateadd = exp.DateAdd(\n                this=epoch_start,\n                expression=rounded_ms_as_bigint,\n                unit=exp.Literal.string(\"MICROSECONDS\"),\n            )\n            return self.sql(dateadd)\n"
  },
  {
    "path": "sqlglot/dialects/hive.py",
    "content": "from __future__ import annotations\n\nimport re\nimport typing as t\nfrom copy import deepcopy\nfrom functools import partial\nfrom collections import defaultdict\n\nfrom sqlglot import exp, generator, jsonpath, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    DATE_ADD_OR_SUB,\n    Dialect,\n    NormalizationStrategy,\n    approx_count_distinct_sql,\n    arg_max_or_min_no_count,\n    datestrtodate_sql,\n    if_sql,\n    is_parse_json,\n    left_to_substring_sql,\n    max_or_greatest,\n    min_or_least,\n    no_ilike_sql,\n    no_recursive_cte_sql,\n    no_trycast_sql,\n    regexp_extract_sql,\n    regexp_replace_sql,\n    rename_func,\n    right_to_substring_sql,\n    strposition_sql,\n    struct_extract_sql,\n    time_format,\n    timestrtotime_sql,\n    trim_sql,\n    unit_to_str,\n    var_map_sql,\n    sequence_sql,\n    property_sql,\n)\nfrom sqlglot.transforms import (\n    remove_unique_constraints,\n    ctas_with_tmp_tables_to_create_tmp_view,\n    preprocess,\n    move_schema_columns_to_partitioned_by,\n)\nfrom sqlglot.parsers.hive import HiveParser\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.optimizer.annotate_types import TypeAnnotator\nfrom sqlglot.typing.hive import EXPRESSION_METADATA\n\n# (FuncType, Multiplier)\nDATE_DELTA_INTERVAL = {\n    \"YEAR\": (\"ADD_MONTHS\", 12),\n    \"MONTH\": (\"ADD_MONTHS\", 1),\n    \"QUARTER\": (\"ADD_MONTHS\", 3),\n    \"WEEK\": (\"DATE_ADD\", 7),\n    \"DAY\": (\"DATE_ADD\", 1),\n}\n\nTIME_DIFF_FACTOR = {\n    \"MILLISECOND\": \" * 1000\",\n    \"SECOND\": \"\",\n    \"MINUTE\": \" / 60\",\n    \"HOUR\": \" / 3600\",\n}\n\nDIFF_MONTH_SWITCH = (\"YEAR\", \"QUARTER\", \"MONTH\")\n\n\ndef _add_date_sql(self: Hive.Generator, expression: DATE_ADD_OR_SUB) -> str:\n    if isinstance(expression, exp.TsOrDsAdd) and not expression.unit:\n        return self.func(\"DATE_ADD\", expression.this, expression.expression)\n\n    unit = expression.text(\"unit\").upper()\n    func, multiplier = DATE_DELTA_INTERVAL.get(unit, (\"DATE_ADD\", 1))\n\n    if isinstance(expression, exp.DateSub):\n        multiplier *= -1\n\n    increment = expression.expression\n    if isinstance(increment, exp.Literal):\n        value = increment.to_py() if increment.is_number else int(increment.name)\n        increment = exp.Literal.number(value * multiplier)\n    elif multiplier != 1:\n        increment *= exp.Literal.number(multiplier)\n\n    return self.func(func, expression.this, increment)\n\n\ndef _date_diff_sql(self: Hive.Generator, expression: exp.DateDiff | exp.TsOrDsDiff) -> str:\n    unit = expression.text(\"unit\").upper()\n\n    factor = TIME_DIFF_FACTOR.get(unit)\n    if factor is not None:\n        left = self.sql(expression, \"this\")\n        right = self.sql(expression, \"expression\")\n        sec_diff = f\"UNIX_TIMESTAMP({left}) - UNIX_TIMESTAMP({right})\"\n        return f\"({sec_diff}){factor}\" if factor else sec_diff\n\n    months_between = unit in DIFF_MONTH_SWITCH\n    sql_func = \"MONTHS_BETWEEN\" if months_between else \"DATEDIFF\"\n    _, multiplier = DATE_DELTA_INTERVAL.get(unit, (\"\", 1))\n    multiplier_sql = f\" / {multiplier}\" if multiplier > 1 else \"\"\n    diff_sql = f\"{sql_func}({self.format_args(expression.this, expression.expression)})\"\n\n    if months_between or multiplier_sql:\n        # MONTHS_BETWEEN returns a float, so we need to truncate the fractional part.\n        # For the same reason, we want to truncate if there's a divisor present.\n        diff_sql = f\"CAST({diff_sql}{multiplier_sql} AS INT)\"\n\n    return diff_sql\n\n\ndef _json_format_sql(self: Hive.Generator, expression: exp.JSONFormat) -> str:\n    this = expression.this\n\n    if is_parse_json(this):\n        if this.this.is_string:\n            # Since FROM_JSON requires a nested type, we always wrap the json string with\n            # an array to ensure that \"naked\" strings like \"'a'\" will be handled correctly\n            wrapped_json = exp.Literal.string(f\"[{this.this.name}]\")\n\n            from_json = self.func(\n                \"FROM_JSON\", wrapped_json, self.func(\"SCHEMA_OF_JSON\", wrapped_json)\n            )\n            to_json = self.func(\"TO_JSON\", from_json)\n\n            # This strips the [, ] delimiters of the dummy array printed by TO_JSON\n            return self.func(\"REGEXP_EXTRACT\", to_json, \"'^.(.*).$'\", \"1\")\n        return self.sql(this)\n\n    return self.func(\"TO_JSON\", this, expression.args.get(\"options\"))\n\n\n@generator.unsupported_args((\"expression\", \"Hive's SORT_ARRAY does not support a comparator.\"))\ndef _array_sort_sql(self: Hive.Generator, expression: exp.ArraySort) -> str:\n    return self.func(\"SORT_ARRAY\", expression.this)\n\n\ndef _str_to_unix_sql(self: Hive.Generator, expression: exp.StrToUnix) -> str:\n    return self.func(\"UNIX_TIMESTAMP\", expression.this, time_format(\"hive\")(self, expression))\n\n\ndef _unix_to_time_sql(self: Hive.Generator, expression: exp.UnixToTime) -> str:\n    timestamp = self.sql(expression, \"this\")\n    scale = expression.args.get(\"scale\")\n    if scale in (None, exp.UnixToTime.SECONDS):\n        return rename_func(\"FROM_UNIXTIME\")(self, expression)\n\n    return f\"FROM_UNIXTIME({timestamp} / POW(10, {scale}))\"\n\n\ndef _str_to_date_sql(self: Hive.Generator, expression: exp.StrToDate) -> str:\n    this = self.sql(expression, \"this\")\n    time_format = self.format_time(expression)\n    if time_format not in (Hive.TIME_FORMAT, Hive.DATE_FORMAT):\n        this = f\"FROM_UNIXTIME(UNIX_TIMESTAMP({this}, {time_format}))\"\n    return f\"CAST({this} AS DATE)\"\n\n\ndef _str_to_time_sql(self: Hive.Generator, expression: exp.StrToTime) -> str:\n    this = self.sql(expression, \"this\")\n    time_format = self.format_time(expression)\n    if time_format not in (Hive.TIME_FORMAT, Hive.DATE_FORMAT):\n        this = f\"FROM_UNIXTIME(UNIX_TIMESTAMP({this}, {time_format}))\"\n    return f\"CAST({this} AS TIMESTAMP)\"\n\n\ndef _to_date_sql(self: Hive.Generator, expression: exp.TsOrDsToDate) -> str:\n    time_format = self.format_time(expression)\n    if time_format and time_format not in (Hive.TIME_FORMAT, Hive.DATE_FORMAT):\n        return self.func(\"TO_DATE\", expression.this, time_format)\n\n    if isinstance(expression.parent, self.TS_OR_DS_EXPRESSIONS):\n        return self.sql(expression, \"this\")\n\n    return self.func(\"TO_DATE\", expression.this)\n\n\nclass Hive(Dialect):\n    ALIAS_POST_TABLESAMPLE = True\n    IDENTIFIERS_CAN_START_WITH_DIGIT = True\n    SUPPORTS_USER_DEFINED_TYPES = False\n    SAFE_DIVISION = True\n    ARRAY_AGG_INCLUDES_NULLS = None\n    REGEXP_EXTRACT_DEFAULT_GROUP = 1\n    ALTER_TABLE_SUPPORTS_CASCADE = True\n\n    # https://spark.apache.org/docs/latest/sql-ref-identifier.html#description\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    # https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27362046#LanguageManualUDF-StringFunctions\n    # https://github.com/apache/hive/blob/master/ql/src/java/org/apache/hadoop/hive/ql/exec/Utilities.java#L266-L269\n    INITCAP_DEFAULT_DELIMITER_CHARS = \" \\t\\n\\r\\f\\u000b\\u001c\\u001d\\u001e\\u001f\"\n\n    # Support only the non-ANSI mode (default for Hive, Spark2, Spark)\n    COERCES_TO = defaultdict(set, deepcopy(TypeAnnotator.COERCES_TO))\n    for target_type in {\n        *exp.DataType.NUMERIC_TYPES,\n        *exp.DataType.TEMPORAL_TYPES,\n        exp.DType.INTERVAL,\n    }:\n        COERCES_TO[target_type] |= exp.DataType.TEXT_TYPES\n\n    TIME_MAPPING = {\n        \"y\": \"%Y\",\n        \"Y\": \"%Y\",\n        \"YYYY\": \"%Y\",\n        \"yyyy\": \"%Y\",\n        \"YY\": \"%y\",\n        \"yy\": \"%y\",\n        \"MMMM\": \"%B\",\n        \"MMM\": \"%b\",\n        \"MM\": \"%m\",\n        \"M\": \"%-m\",\n        \"dd\": \"%d\",\n        \"d\": \"%-d\",\n        \"HH\": \"%H\",\n        \"H\": \"%-H\",\n        \"hh\": \"%I\",\n        \"h\": \"%-I\",\n        \"mm\": \"%M\",\n        \"m\": \"%-M\",\n        \"ss\": \"%S\",\n        \"s\": \"%-S\",\n        \"SSSSSS\": \"%f\",\n        \"a\": \"%p\",\n        \"DD\": \"%j\",\n        \"D\": \"%-j\",\n        \"E\": \"%a\",\n        \"EE\": \"%a\",\n        \"EEE\": \"%a\",\n        \"EEEE\": \"%A\",\n        \"z\": \"%Z\",\n        \"Z\": \"%z\",\n    }\n\n    DATE_FORMAT = \"'yyyy-MM-dd'\"\n    DATEINT_FORMAT = \"'yyyyMMdd'\"\n    TIME_FORMAT = \"'yyyy-MM-dd HH:mm:ss'\"\n\n    class JSONPathTokenizer(jsonpath.JSONPathTokenizer):\n        VAR_TOKENS = {\n            *jsonpath.JSONPathTokenizer.VAR_TOKENS,\n            TokenType.DASH,\n        }\n\n    class Tokenizer(tokens.Tokenizer):\n        QUOTES = [\"'\", '\"']\n        IDENTIFIERS = [\"`\"]\n        STRING_ESCAPES = [\"\\\\\"]\n\n        SINGLE_TOKENS = {\n            **tokens.Tokenizer.SINGLE_TOKENS,\n            \"$\": TokenType.PARAMETER,\n        }\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"ADD ARCHIVE\": TokenType.COMMAND,\n            \"ADD ARCHIVES\": TokenType.COMMAND,\n            \"ADD FILE\": TokenType.COMMAND,\n            \"ADD FILES\": TokenType.COMMAND,\n            \"ADD JAR\": TokenType.COMMAND,\n            \"ADD JARS\": TokenType.COMMAND,\n            \"MINUS\": TokenType.EXCEPT,\n            \"MSCK REPAIR\": TokenType.COMMAND,\n            \"REFRESH\": TokenType.REFRESH,\n            \"TIMESTAMP AS OF\": TokenType.TIMESTAMP_SNAPSHOT,\n            \"VERSION AS OF\": TokenType.VERSION_SNAPSHOT,\n            \"SERDEPROPERTIES\": TokenType.SERDE_PROPERTIES,\n        }\n\n        NUMERIC_LITERALS = {\n            \"L\": \"BIGINT\",\n            \"S\": \"SMALLINT\",\n            \"Y\": \"TINYINT\",\n            \"D\": \"DOUBLE\",\n            \"F\": \"FLOAT\",\n            \"BD\": \"DECIMAL\",\n        }\n\n    Parser = HiveParser\n\n    class Generator(generator.Generator):\n        LIMIT_FETCH = \"LIMIT\"\n        TABLESAMPLE_WITH_METHOD = False\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n        INDEX_ON = \"ON TABLE\"\n        EXTRACT_ALLOWS_QUOTES = False\n        NVL2_SUPPORTED = False\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        JSON_PATH_SINGLE_QUOTE_ESCAPE = True\n        SAFE_JSON_PATH_KEY_RE = re.compile(r\"^[_\\-a-zA-Z][\\-\\w]*$\")\n        SUPPORTS_TO_NUMBER = False\n        WITH_PROPERTIES_PREFIX = \"TBLPROPERTIES\"\n        PARSE_JSON_NAME: t.Optional[str] = None\n        PAD_FILL_PATTERN_IS_REQUIRED = True\n        SUPPORTS_MEDIAN = False\n        ARRAY_SIZE_NAME = \"SIZE\"\n        ALTER_SET_TYPE = \"\"\n\n        EXPRESSIONS_WITHOUT_NESTED_CTES = {\n            exp.Insert,\n            exp.Select,\n            exp.Subquery,\n            exp.SetOperation,\n        }\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n            exp.JSONPathWildcard,\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.BIT: \"BOOLEAN\",\n            exp.DType.BLOB: \"BINARY\",\n            exp.DType.DATETIME: \"TIMESTAMP\",\n            exp.DType.ROWVERSION: \"BINARY\",\n            exp.DType.TEXT: \"STRING\",\n            exp.DType.TIME: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPNTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n            exp.DType.UTINYINT: \"SMALLINT\",\n            exp.DType.VARBINARY: \"BINARY\",\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.Property: property_sql,\n            exp.AnyValue: rename_func(\"FIRST\"),\n            exp.ApproxDistinct: approx_count_distinct_sql,\n            exp.ArgMax: arg_max_or_min_no_count(\"MAX_BY\"),\n            exp.ArgMin: arg_max_or_min_no_count(\"MIN_BY\"),\n            exp.Array: transforms.preprocess([transforms.inherit_struct_field_names]),\n            exp.ArrayConcat: rename_func(\"CONCAT\"),\n            exp.ArrayToString: lambda self, e: self.func(\"CONCAT_WS\", e.expression, e.this),\n            exp.ArraySort: _array_sort_sql,\n            exp.With: no_recursive_cte_sql,\n            exp.DateAdd: _add_date_sql,\n            exp.DateDiff: _date_diff_sql,\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.DateSub: _add_date_sql,\n            exp.DateToDi: lambda self, e: (\n                f\"CAST(DATE_FORMAT({self.sql(e, 'this')}, {Hive.DATEINT_FORMAT}) AS INT)\"\n            ),\n            exp.DiToDate: lambda self, e: (\n                f\"TO_DATE(CAST({self.sql(e, 'this')} AS STRING), {Hive.DATEINT_FORMAT})\"\n            ),\n            exp.StorageHandlerProperty: lambda self, e: f\"STORED BY {self.sql(e, 'this')}\",\n            exp.FromBase64: rename_func(\"UNBASE64\"),\n            exp.GenerateSeries: sequence_sql,\n            exp.GenerateDateArray: sequence_sql,\n            exp.If: if_sql(),\n            exp.ILike: no_ilike_sql,\n            exp.IntDiv: lambda self, e: self.binary(e, \"DIV\"),\n            exp.IsNan: rename_func(\"ISNAN\"),\n            exp.JSONExtract: lambda self, e: self.func(\"GET_JSON_OBJECT\", e.this, e.expression),\n            exp.JSONExtractScalar: lambda self, e: self.func(\n                \"GET_JSON_OBJECT\", e.this, e.expression\n            ),\n            exp.JSONFormat: _json_format_sql,\n            exp.Left: left_to_substring_sql,\n            exp.Map: var_map_sql,\n            exp.Max: max_or_greatest,\n            exp.MD5Digest: lambda self, e: self.func(\"UNHEX\", self.func(\"MD5\", e.this)),\n            exp.Min: min_or_least,\n            exp.MonthsBetween: lambda self, e: self.func(\"MONTHS_BETWEEN\", e.this, e.expression),\n            exp.NotNullColumnConstraint: lambda _, e: (\n                \"\" if e.args.get(\"allow_null\") else \"NOT NULL\"\n            ),\n            exp.VarMap: var_map_sql,\n            exp.Create: preprocess(\n                [\n                    remove_unique_constraints,\n                    ctas_with_tmp_tables_to_create_tmp_view,\n                    move_schema_columns_to_partitioned_by,\n                ]\n            ),\n            exp.Quantile: rename_func(\"PERCENTILE\"),\n            exp.ApproxQuantile: rename_func(\"PERCENTILE_APPROX\"),\n            exp.RegexpExtract: regexp_extract_sql,\n            exp.RegexpExtractAll: regexp_extract_sql,\n            exp.RegexpReplace: regexp_replace_sql,\n            exp.RegexpLike: lambda self, e: self.binary(e, \"RLIKE\"),\n            exp.RegexpSplit: rename_func(\"SPLIT\"),\n            exp.Right: right_to_substring_sql,\n            exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),\n            exp.ArrayUniqueAgg: rename_func(\"COLLECT_SET\"),\n            exp.Split: lambda self, e: self.func(\n                \"SPLIT\", e.this, self.func(\"CONCAT\", \"'\\\\\\\\Q'\", e.expression, \"'\\\\\\\\E'\")\n            ),\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_qualify,\n                    transforms.eliminate_distinct_on,\n                    partial(transforms.unnest_to_explode, unnest_using_arrays_zip=False),\n                    transforms.any_to_exists,\n                ]\n            ),\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self, e, func_name=\"LOCATE\", supports_position=True\n            ),\n            exp.StrToDate: _str_to_date_sql,\n            exp.StrToTime: _str_to_time_sql,\n            exp.StrToUnix: _str_to_unix_sql,\n            exp.StructExtract: struct_extract_sql,\n            exp.StarMap: rename_func(\"MAP\"),\n            exp.Table: transforms.preprocess([transforms.unnest_generate_series]),\n            exp.TimeStrToDate: rename_func(\"TO_DATE\"),\n            exp.TimeStrToTime: timestrtotime_sql,\n            exp.TimeStrToUnix: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.TimestampTrunc: lambda self, e: self.func(\"TRUNC\", e.this, unit_to_str(e)),\n            exp.TimeToUnix: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.ToBase64: rename_func(\"BASE64\"),\n            exp.TsOrDiToDi: lambda self, e: (\n                f\"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS STRING), '-', ''), 1, 8) AS INT)\"\n            ),\n            exp.TsOrDsAdd: _add_date_sql,\n            exp.TsOrDsDiff: _date_diff_sql,\n            exp.TsOrDsToDate: _to_date_sql,\n            exp.TryCast: no_trycast_sql,\n            exp.Trim: trim_sql,\n            exp.Unicode: rename_func(\"ASCII\"),\n            exp.UnixToStr: lambda self, e: self.func(\n                \"FROM_UNIXTIME\", e.this, time_format(\"hive\")(self, e)\n            ),\n            exp.UnixToTime: _unix_to_time_sql,\n            exp.UnixToTimeStr: rename_func(\"FROM_UNIXTIME\"),\n            exp.Unnest: rename_func(\"EXPLODE\"),\n            exp.PartitionedByProperty: lambda self, e: f\"PARTITIONED BY {self.sql(e, 'this')}\",\n            exp.NumberToStr: rename_func(\"FORMAT_NUMBER\"),\n            exp.National: lambda self, e: self.national_sql(e, prefix=\"\"),\n            exp.ClusteredColumnConstraint: lambda self, e: (\n                f\"({self.expressions(e, 'this', indent=False)})\"\n            ),\n            exp.NonClusteredColumnConstraint: lambda self, e: (\n                f\"({self.expressions(e, 'this', indent=False)})\"\n            ),\n            exp.NotForReplicationColumnConstraint: lambda *_: \"\",\n            exp.OnProperty: lambda *_: \"\",\n            exp.PartitionedByBucket: lambda self, e: self.func(\"BUCKET\", e.expression, e.this),\n            exp.PartitionByTruncate: lambda self, e: self.func(\"TRUNCATE\", e.expression, e.this),\n            exp.PrimaryKeyColumnConstraint: lambda *_: \"PRIMARY KEY\",\n            exp.WeekOfYear: rename_func(\"WEEKOFYEAR\"),\n            exp.DayOfMonth: rename_func(\"DAYOFMONTH\"),\n            exp.DayOfWeek: rename_func(\"DAYOFWEEK\"),\n            exp.Levenshtein: unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\", \"max_dist\")(\n                rename_func(\"LEVENSHTEIN\")\n            ),\n        }\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.FileFormatProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.WithDataProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        TS_OR_DS_EXPRESSIONS: t.Tuple[t.Type[exp.Expr], ...] = (\n            exp.DateDiff,\n            exp.Day,\n            exp.Month,\n            exp.Year,\n        )\n\n        IGNORE_NULLS_FUNCS = (exp.First, exp.Last, exp.FirstValue, exp.LastValue)\n\n        def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:\n            this = expression.this\n            if isinstance(this, self.IGNORE_NULLS_FUNCS):\n                return self.func(this.sql_name(), this.this, exp.true())\n\n            return super().ignorenulls_sql(expression)\n\n        def unnest_sql(self, expression: exp.Unnest) -> str:\n            return rename_func(\"EXPLODE\")(self, expression)\n\n        def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:\n            if isinstance(expression.this, exp.JSONPathWildcard):\n                self.unsupported(\"Unsupported wildcard in JSONPathKey expression\")\n                return \"\"\n\n            return super()._jsonpathkey_sql(expression)\n\n        def parameter_sql(self, expression: exp.Parameter) -> str:\n            this = self.sql(expression, \"this\")\n            expression_sql = self.sql(expression, \"expression\")\n\n            parent = expression.parent\n            this = f\"{this}:{expression_sql}\" if expression_sql else this\n\n            if isinstance(parent, exp.EQ) and isinstance(parent.parent, exp.SetItem):\n                # We need to produce SET key = value instead of SET ${key} = value\n                return this\n\n            return f\"${{{this}}}\"\n\n        def schema_sql(self, expression: exp.Schema) -> str:\n            for ordered in expression.find_all(exp.Ordered):\n                if ordered.args.get(\"desc\") is False:\n                    ordered.set(\"desc\", None)\n\n            return super().schema_sql(expression)\n\n        def constraint_sql(self, expression: exp.Constraint) -> str:\n            for prop in list(expression.find_all(exp.Properties)):\n                prop.pop()\n\n            this = self.sql(expression, \"this\")\n            expressions = self.expressions(expression, sep=\" \", flat=True)\n            return f\"CONSTRAINT {this} {expressions}\"\n\n        def rowformatserdeproperty_sql(self, expression: exp.RowFormatSerdeProperty) -> str:\n            serde_props = self.sql(expression, \"serde_properties\")\n            serde_props = f\" {serde_props}\" if serde_props else \"\"\n            return f\"ROW FORMAT SERDE {self.sql(expression, 'this')}{serde_props}\"\n\n        def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:\n            return self.func(\n                \"COLLECT_LIST\",\n                expression.this.this if isinstance(expression.this, exp.Order) else expression.this,\n            )\n\n        # Hive/Spark lack native numeric TRUNC. CAST to BIGINT truncates toward zero (not rounds).\n        # Potential enhancement: a TRUNC_TEMPLATE using FLOOR/CEIL with scale (Spark 3.3+)\n        # could preserve decimals: CASE WHEN x >= 0 THEN FLOOR(x, d) ELSE CEIL(x, d) END\n        @unsupported_args(\"decimals\")\n        def trunc_sql(self, expression: exp.Trunc) -> str:\n            return self.sql(exp.cast(expression.this, exp.DType.BIGINT))\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            if expression.this in self.PARAMETERIZABLE_TEXT_TYPES and (\n                not expression.expressions or expression.expressions[0].name == \"MAX\"\n            ):\n                expression = exp.DataType.build(\"text\")\n            elif expression.is_type(exp.DType.TEXT) and expression.expressions:\n                expression.set(\"this\", exp.DType.VARCHAR)\n            elif expression.this in exp.DataType.TEMPORAL_TYPES:\n                expression = exp.DataType.build(expression.this)\n            elif expression.is_type(\"float\"):\n                size_expression = expression.find(exp.DataTypeParam)\n                if size_expression:\n                    size = int(size_expression.name)\n                    expression = (\n                        exp.DataType.build(\"float\") if size <= 32 else exp.DataType.build(\"double\")\n                    )\n\n            return super().datatype_sql(expression)\n\n        def version_sql(self, expression: exp.Version) -> str:\n            sql = super().version_sql(expression)\n            return sql.replace(\"FOR \", \"\", 1)\n\n        def struct_sql(self, expression: exp.Struct) -> str:\n            values = []\n\n            for i, e in enumerate(expression.expressions):\n                if isinstance(e, exp.PropertyEQ):\n                    self.unsupported(\"Hive does not support named structs.\")\n                    values.append(e.expression)\n                else:\n                    values.append(e)\n\n            return self.func(\"STRUCT\", *values)\n\n        def columndef_sql(self, expression: exp.ColumnDef, sep: str = \" \") -> str:\n            return super().columndef_sql(\n                expression,\n                sep=(\n                    \": \"\n                    if isinstance(expression.parent, exp.DataType)\n                    and expression.parent.is_type(\"struct\")\n                    else sep\n                ),\n            )\n\n        def altercolumn_sql(self, expression: exp.AlterColumn) -> str:\n            this = self.sql(expression, \"this\")\n            new_name = self.sql(expression, \"rename_to\") or this\n            dtype = self.sql(expression, \"dtype\")\n            comment = (\n                f\" COMMENT {self.sql(expression, 'comment')}\"\n                if self.sql(expression, \"comment\")\n                else \"\"\n            )\n            default = self.sql(expression, \"default\")\n            visible = expression.args.get(\"visible\")\n            allow_null = expression.args.get(\"allow_null\")\n            drop = expression.args.get(\"drop\")\n\n            if any([default, drop, visible, allow_null, drop]):\n                self.unsupported(\"Unsupported CHANGE COLUMN syntax\")\n\n            if not dtype:\n                self.unsupported(\"CHANGE COLUMN without a type is not supported\")\n\n            return f\"CHANGE COLUMN {this} {new_name} {dtype}{comment}\"\n\n        def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:\n            self.unsupported(\"Cannot rename columns without data type defined in Hive\")\n            return \"\"\n\n        def alterset_sql(self, expression: exp.AlterSet) -> str:\n            exprs = self.expressions(expression, flat=True)\n            exprs = f\" {exprs}\" if exprs else \"\"\n            location = self.sql(expression, \"location\")\n            location = f\" LOCATION {location}\" if location else \"\"\n            file_format = self.expressions(expression, key=\"file_format\", flat=True, sep=\" \")\n            file_format = f\" FILEFORMAT {file_format}\" if file_format else \"\"\n            serde = self.sql(expression, \"serde\")\n            serde = f\" SERDE {serde}\" if serde else \"\"\n            tags = self.expressions(expression, key=\"tag\", flat=True, sep=\"\")\n            tags = f\" TAGS {tags}\" if tags else \"\"\n\n            return f\"SET{serde}{exprs}{location}{file_format}{tags}\"\n\n        def serdeproperties_sql(self, expression: exp.SerdeProperties) -> str:\n            prefix = \"WITH \" if expression.args.get(\"with_\") else \"\"\n            exprs = self.expressions(expression, flat=True)\n\n            return f\"{prefix}SERDEPROPERTIES ({exprs})\"\n\n        def exists_sql(self, expression: exp.Exists) -> str:\n            if expression.expression:\n                return self.function_fallback_sql(expression)\n\n            return super().exists_sql(expression)\n\n        def timetostr_sql(self, expression: exp.TimeToStr) -> str:\n            this = expression.this\n            if isinstance(this, exp.TimeStrToTime):\n                this = this.this\n\n            return self.func(\"DATE_FORMAT\", this, self.format_time(expression))\n\n        def fileformatproperty_sql(self, expression: exp.FileFormatProperty) -> str:\n            if isinstance(expression.this, exp.InputOutputFormat):\n                this = self.sql(expression, \"this\")\n            else:\n                this = expression.name.upper()\n\n            return f\"STORED AS {this}\"\n"
  },
  {
    "path": "sqlglot/dialects/materialize.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.helper import seq_get\nfrom sqlglot.dialects.postgres import Postgres\nfrom sqlglot.parsers.materialize import MaterializeParser\n\nfrom sqlglot.transforms import (\n    remove_unique_constraints,\n    ctas_with_tmp_tables_to_create_tmp_view,\n    preprocess,\n)\n\n\nclass Materialize(Postgres):\n    Parser = MaterializeParser\n\n    class Generator(Postgres.Generator):\n        SUPPORTS_CREATE_TABLE_LIKE = False\n        SUPPORTS_BETWEEN_FLAGS = False\n\n        TRANSFORMS = {\n            **Postgres.Generator.TRANSFORMS,\n            exp.AutoIncrementColumnConstraint: lambda self, e: \"\",\n            exp.Create: preprocess(\n                [\n                    remove_unique_constraints,\n                    ctas_with_tmp_tables_to_create_tmp_view,\n                ]\n            ),\n            exp.GeneratedAsIdentityColumnConstraint: lambda self, e: \"\",\n            exp.OnConflict: lambda self, e: \"\",\n            exp.PrimaryKeyColumnConstraint: lambda self, e: \"\",\n        }\n        TRANSFORMS.pop(exp.ToMap)\n\n        def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:\n            return self.binary(expression, \"=>\")\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            if expression.is_type(exp.DType.LIST):\n                if expression.expressions:\n                    return f\"{self.expressions(expression, flat=True)} LIST\"\n                return \"LIST\"\n\n            if expression.is_type(exp.DType.MAP) and len(expression.expressions) == 2:\n                key, value = expression.expressions\n                return f\"MAP[{self.sql(key)} => {self.sql(value)}]\"\n\n            return super().datatype_sql(expression)\n\n        def list_sql(self, expression: exp.List) -> str:\n            if isinstance(seq_get(expression.expressions, 0), exp.Select):\n                return self.func(\"LIST\", seq_get(expression.expressions, 0))\n\n            return f\"{self.normalize_func('LIST')}[{self.expressions(expression, flat=True)}]\"\n\n        def tomap_sql(self, expression: exp.ToMap) -> str:\n            if isinstance(expression.this, exp.Select):\n                return self.func(\"MAP\", expression.this)\n            return f\"{self.normalize_func('MAP')}[{self.expressions(expression.this)}]\"\n"
  },
  {
    "path": "sqlglot/dialects/mysql.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    NormalizationStrategy,\n    arrow_json_extract_sql,\n    build_date_delta,\n    build_date_delta_with_interval,\n    date_add_interval_sql,\n    datestrtodate_sql,\n    length_or_char_length_sql,\n    max_or_greatest,\n    min_or_least,\n    no_ilike_sql,\n    no_paren_current_date_sql,\n    no_pivot_sql,\n    no_tablesample_sql,\n    no_trycast_sql,\n    rename_func,\n    strposition_sql,\n    unit_to_var,\n    trim_sql,\n    timestrtotime_sql,\n)\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.parsers.mysql import MySQLParser\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.typing.mysql import EXPRESSION_METADATA\n\n\ndef _date_trunc_sql(self: MySQL.Generator, expression: exp.DateTrunc) -> str:\n    expr = self.sql(expression, \"this\")\n    unit = expression.text(\"unit\").upper()\n\n    if unit == \"WEEK\":\n        concat = f\"CONCAT(YEAR({expr}), ' ', WEEK({expr}, 1), ' 1')\"\n        date_format = \"%Y %u %w\"\n    elif unit == \"MONTH\":\n        concat = f\"CONCAT(YEAR({expr}), ' ', MONTH({expr}), ' 1')\"\n        date_format = \"%Y %c %e\"\n    elif unit == \"QUARTER\":\n        concat = f\"CONCAT(YEAR({expr}), ' ', QUARTER({expr}) * 3 - 2, ' 1')\"\n        date_format = \"%Y %c %e\"\n    elif unit == \"YEAR\":\n        concat = f\"CONCAT(YEAR({expr}), ' 1 1')\"\n        date_format = \"%Y %c %e\"\n    else:\n        if unit != \"DAY\":\n            self.unsupported(f\"Unexpected interval unit: {unit}\")\n        return self.func(\"DATE\", expr)\n\n    return self.func(\"STR_TO_DATE\", concat, f\"'{date_format}'\")\n\n\ndef _str_to_date_sql(\n    self: MySQL.Generator, expression: exp.StrToDate | exp.StrToTime | exp.TsOrDsToDate\n) -> str:\n    return self.func(\"STR_TO_DATE\", expression.this, self.format_time(expression))\n\n\ndef _unix_to_time_sql(self: MySQL.Generator, expression: exp.UnixToTime) -> str:\n    scale = expression.args.get(\"scale\")\n    timestamp = expression.this\n\n    if scale in (None, exp.UnixToTime.SECONDS):\n        return self.func(\"FROM_UNIXTIME\", timestamp, self.format_time(expression))\n\n    return self.func(\n        \"FROM_UNIXTIME\",\n        exp.Div(this=timestamp, expression=exp.func(\"POW\", 10, scale)),\n        self.format_time(expression),\n    )\n\n\ndef date_add_sql(\n    kind: str,\n) -> t.Callable[[generator.Generator, exp.Expr], str]:\n    def func(self: generator.Generator, expression: exp.Expr) -> str:\n        return self.func(\n            f\"DATE_{kind}\",\n            expression.this,\n            exp.Interval(this=expression.expression, unit=unit_to_var(expression)),\n        )\n\n    return func\n\n\ndef _ts_or_ds_to_date_sql(self: MySQL.Generator, expression: exp.TsOrDsToDate) -> str:\n    time_format = expression.args.get(\"format\")\n    return _str_to_date_sql(self, expression) if time_format else self.func(\"DATE\", expression.this)\n\n\ndef _remove_ts_or_ds_to_date(\n    to_sql: t.Optional[t.Callable[[MySQL.Generator, exp.Expr], str]] = None,\n    args: t.Tuple[str, ...] = (\"this\",),\n) -> t.Callable[[MySQL.Generator, exp.Func], str]:\n    def func(self: MySQL.Generator, expression: exp.Func) -> str:\n        for arg_key in args:\n            arg = expression.args.get(arg_key)\n            if isinstance(arg, (exp.TsOrDsToDate, exp.TsOrDsToTimestamp)) and not arg.args.get(\n                \"format\"\n            ):\n                expression.set(arg_key, arg.this)\n\n        return to_sql(self, expression) if to_sql else self.function_fallback_sql(expression)\n\n    return func\n\n\nclass MySQL(Dialect):\n    PROMOTE_TO_INFERRED_DATETIME_TYPE = True\n\n    # https://dev.mysql.com/doc/refman/8.0/en/identifiers.html\n    IDENTIFIERS_CAN_START_WITH_DIGIT = True\n\n    # We default to treating all identifiers as case-sensitive, since it matches MySQL's\n    # behavior on Linux systems. For MacOS and Windows systems, one can override this\n    # setting by specifying `dialect=\"mysql, normalization_strategy = lowercase\"`.\n    #\n    # See also https://dev.mysql.com/doc/refman/8.2/en/identifier-case-sensitivity.html\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_SENSITIVE\n\n    TIME_FORMAT = \"'%Y-%m-%d %T'\"\n    DPIPE_IS_STRING_CONCAT = False\n    SUPPORTS_USER_DEFINED_TYPES = False\n    SAFE_DIVISION = True\n    SAFE_TO_ELIMINATE_DOUBLE_NEGATION = False\n    LEAST_GREATEST_IGNORES_NULLS = False\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    # https://prestodb.io/docs/current/functions/datetime.html#mysql-date-functions\n    TIME_MAPPING = {\n        \"%M\": \"%B\",\n        \"%c\": \"%-m\",\n        \"%e\": \"%-d\",\n        \"%h\": \"%I\",\n        \"%i\": \"%M\",\n        \"%s\": \"%S\",\n        \"%u\": \"%W\",\n        \"%k\": \"%-H\",\n        \"%l\": \"%-I\",\n        \"%T\": \"%H:%M:%S\",\n        \"%W\": \"%A\",\n    }\n\n    VALID_INTERVAL_UNITS = {\n        *Dialect.VALID_INTERVAL_UNITS,\n        \"SECOND_MICROSECOND\",\n        \"MINUTE_MICROSECOND\",\n        \"MINUTE_SECOND\",\n        \"HOUR_MICROSECOND\",\n        \"HOUR_SECOND\",\n        \"HOUR_MINUTE\",\n        \"DAY_MICROSECOND\",\n        \"DAY_SECOND\",\n        \"DAY_MINUTE\",\n        \"DAY_HOUR\",\n        \"YEAR_MONTH\",\n    }\n\n    class Tokenizer(tokens.Tokenizer):\n        QUOTES = [\"'\", '\"']\n        COMMENTS = [\"--\", \"#\", (\"/*\", \"*/\")]\n        IDENTIFIERS = [\"`\"]\n        STRING_ESCAPES = [\"'\", '\"', \"\\\\\"]\n        BIT_STRINGS = [(\"b'\", \"'\"), (\"B'\", \"'\"), (\"0b\", \"\")]\n        HEX_STRINGS = [(\"x'\", \"'\"), (\"X'\", \"'\"), (\"0x\", \"\")]\n        # https://dev.mysql.com/doc/refman/8.4/en/string-literals.html\n        ESCAPE_FOLLOW_CHARS = [\"0\", \"b\", \"n\", \"r\", \"t\", \"Z\", \"%\", \"_\"]\n\n        NESTED_COMMENTS = False\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"BLOB\": TokenType.BLOB,\n            \"CHARSET\": TokenType.CHARACTER_SET,\n            \"DISTINCTROW\": TokenType.DISTINCT,\n            \"EXPLAIN\": TokenType.DESCRIBE,\n            \"FORCE\": TokenType.FORCE,\n            \"IGNORE\": TokenType.IGNORE,\n            \"KEY\": TokenType.KEY,\n            \"LOCK TABLES\": TokenType.COMMAND,\n            \"LONGBLOB\": TokenType.LONGBLOB,\n            \"LONGTEXT\": TokenType.LONGTEXT,\n            \"MEDIUMBLOB\": TokenType.MEDIUMBLOB,\n            \"MEDIUMINT\": TokenType.MEDIUMINT,\n            \"MEDIUMTEXT\": TokenType.MEDIUMTEXT,\n            \"MEMBER OF\": TokenType.MEMBER_OF,\n            \"MOD\": TokenType.MOD,\n            \"SEPARATOR\": TokenType.SEPARATOR,\n            \"SERIAL\": TokenType.SERIAL,\n            \"SIGNED\": TokenType.BIGINT,\n            \"SIGNED INTEGER\": TokenType.BIGINT,\n            \"SOUNDS LIKE\": TokenType.SOUNDS_LIKE,\n            \"START\": TokenType.BEGIN,\n            \"TIMESTAMP\": TokenType.TIMESTAMPTZ,\n            \"TINYBLOB\": TokenType.TINYBLOB,\n            \"TINYTEXT\": TokenType.TINYTEXT,\n            \"UNLOCK TABLES\": TokenType.COMMAND,\n            \"UNSIGNED\": TokenType.UBIGINT,\n            \"UNSIGNED INTEGER\": TokenType.UBIGINT,\n            \"YEAR\": TokenType.YEAR,\n            \"_ARMSCII8\": TokenType.INTRODUCER,\n            \"_ASCII\": TokenType.INTRODUCER,\n            \"_BIG5\": TokenType.INTRODUCER,\n            \"_BINARY\": TokenType.INTRODUCER,\n            \"_CP1250\": TokenType.INTRODUCER,\n            \"_CP1251\": TokenType.INTRODUCER,\n            \"_CP1256\": TokenType.INTRODUCER,\n            \"_CP1257\": TokenType.INTRODUCER,\n            \"_CP850\": TokenType.INTRODUCER,\n            \"_CP852\": TokenType.INTRODUCER,\n            \"_CP866\": TokenType.INTRODUCER,\n            \"_CP932\": TokenType.INTRODUCER,\n            \"_DEC8\": TokenType.INTRODUCER,\n            \"_EUCJPMS\": TokenType.INTRODUCER,\n            \"_EUCKR\": TokenType.INTRODUCER,\n            \"_GB18030\": TokenType.INTRODUCER,\n            \"_GB2312\": TokenType.INTRODUCER,\n            \"_GBK\": TokenType.INTRODUCER,\n            \"_GEOSTD8\": TokenType.INTRODUCER,\n            \"_GREEK\": TokenType.INTRODUCER,\n            \"_HEBREW\": TokenType.INTRODUCER,\n            \"_HP8\": TokenType.INTRODUCER,\n            \"_KEYBCS2\": TokenType.INTRODUCER,\n            \"_KOI8R\": TokenType.INTRODUCER,\n            \"_KOI8U\": TokenType.INTRODUCER,\n            \"_LATIN1\": TokenType.INTRODUCER,\n            \"_LATIN2\": TokenType.INTRODUCER,\n            \"_LATIN5\": TokenType.INTRODUCER,\n            \"_LATIN7\": TokenType.INTRODUCER,\n            \"_MACCE\": TokenType.INTRODUCER,\n            \"_MACROMAN\": TokenType.INTRODUCER,\n            \"_SJIS\": TokenType.INTRODUCER,\n            \"_SWE7\": TokenType.INTRODUCER,\n            \"_TIS620\": TokenType.INTRODUCER,\n            \"_UCS2\": TokenType.INTRODUCER,\n            \"_UJIS\": TokenType.INTRODUCER,\n            # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html\n            \"_UTF8\": TokenType.INTRODUCER,\n            \"_UTF16\": TokenType.INTRODUCER,\n            \"_UTF16LE\": TokenType.INTRODUCER,\n            \"_UTF32\": TokenType.INTRODUCER,\n            \"_UTF8MB3\": TokenType.INTRODUCER,\n            \"_UTF8MB4\": TokenType.INTRODUCER,\n            \"@@\": TokenType.SESSION_PARAMETER,\n        }\n\n        COMMANDS = {*tokens.Tokenizer.COMMANDS, TokenType.REPLACE} - {TokenType.SHOW}\n\n    Parser = MySQLParser\n\n    class Generator(generator.Generator):\n        INTERVAL_ALLOWS_PLURAL_FORM = False\n        LOCKING_READS_SUPPORTED = True\n        NULL_ORDERING_SUPPORTED: t.Optional[bool] = None\n        JOIN_HINTS = False\n        TABLE_HINTS = True\n        DUPLICATE_KEY_UPDATE_WITH_SET = False\n        QUERY_HINT_SEP = \" \"\n        VALUES_AS_TABLE = False\n        NVL2_SUPPORTED = False\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        JSON_TYPE_REQUIRED_FOR_EXTRACTION = True\n        JSON_PATH_BRACKETED_KEY_SUPPORTED = False\n        JSON_KEY_VALUE_PAIR_SEP = \",\"\n        SUPPORTS_TO_NUMBER = False\n        PARSE_JSON_NAME: t.Optional[str] = None\n        PAD_FILL_PATTERN_IS_REQUIRED = True\n        WRAP_DERIVED_VALUES = False\n        VARCHAR_REQUIRES_SIZE = True\n        SUPPORTS_MEDIAN = False\n        UPDATE_STATEMENT_SUPPORTS_FROM = False\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.ArrayAgg: rename_func(\"GROUP_CONCAT\"),\n            exp.BitwiseAndAgg: rename_func(\"BIT_AND\"),\n            exp.BitwiseOrAgg: rename_func(\"BIT_OR\"),\n            exp.BitwiseXorAgg: rename_func(\"BIT_XOR\"),\n            exp.BitwiseCount: rename_func(\"BIT_COUNT\"),\n            exp.Chr: lambda self, e: self.chr_sql(e, \"CHAR\"),\n            exp.CurrentDate: no_paren_current_date_sql,\n            exp.CurrentVersion: rename_func(\"VERSION\"),\n            exp.DateDiff: _remove_ts_or_ds_to_date(\n                lambda self, e: self.func(\"DATEDIFF\", e.this, e.expression), (\"this\", \"expression\")\n            ),\n            exp.DateAdd: _remove_ts_or_ds_to_date(date_add_sql(\"ADD\")),\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.DateSub: _remove_ts_or_ds_to_date(date_add_sql(\"SUB\")),\n            exp.DateTrunc: _date_trunc_sql,\n            exp.Day: _remove_ts_or_ds_to_date(),\n            exp.DayOfMonth: _remove_ts_or_ds_to_date(rename_func(\"DAYOFMONTH\")),\n            exp.DayOfWeek: _remove_ts_or_ds_to_date(rename_func(\"DAYOFWEEK\")),\n            exp.DayOfYear: _remove_ts_or_ds_to_date(rename_func(\"DAYOFYEAR\")),\n            exp.GroupConcat: lambda self, e: (\n                f\"\"\"GROUP_CONCAT({self.sql(e, \"this\")} SEPARATOR {self.sql(e, \"separator\") or \"','\"})\"\"\"\n            ),\n            exp.ILike: no_ilike_sql,\n            exp.JSONExtractScalar: arrow_json_extract_sql,\n            exp.Length: length_or_char_length_sql,\n            exp.LogicalOr: rename_func(\"MAX\"),\n            exp.LogicalAnd: rename_func(\"MIN\"),\n            exp.Max: max_or_greatest,\n            exp.Min: min_or_least,\n            exp.Month: _remove_ts_or_ds_to_date(),\n            exp.NullSafeEQ: lambda self, e: self.binary(e, \"<=>\"),\n            exp.NullSafeNEQ: lambda self, e: f\"NOT {self.binary(e, '<=>')}\",\n            exp.NumberToStr: rename_func(\"FORMAT\"),\n            exp.Pivot: no_pivot_sql,\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_distinct_on,\n                    transforms.eliminate_semi_and_anti_joins,\n                    transforms.eliminate_qualify,\n                    transforms.eliminate_full_outer_join,\n                    transforms.unnest_generate_date_array_using_recursive_cte,\n                ]\n            ),\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self, e, func_name=\"LOCATE\", supports_position=True\n            ),\n            exp.StrToDate: _str_to_date_sql,\n            exp.StrToTime: _str_to_date_sql,\n            exp.Stuff: rename_func(\"INSERT\"),\n            exp.SessionUser: lambda *_: \"SESSION_USER()\",\n            exp.TableSample: no_tablesample_sql,\n            exp.TimeFromParts: rename_func(\"MAKETIME\"),\n            exp.TimestampAdd: date_add_interval_sql(\"DATE\", \"ADD\"),\n            exp.TimestampDiff: lambda self, e: self.func(\n                \"TIMESTAMPDIFF\", unit_to_var(e), e.expression, e.this\n            ),\n            exp.TimestampSub: date_add_interval_sql(\"DATE\", \"SUB\"),\n            exp.TimeStrToUnix: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.TimeStrToTime: lambda self, e: timestrtotime_sql(\n                self,\n                e,\n                include_precision=not e.args.get(\"zone\"),\n            ),\n            exp.TimeToStr: _remove_ts_or_ds_to_date(\n                lambda self, e: self.func(\"DATE_FORMAT\", e.this, self.format_time(e))\n            ),\n            exp.Trim: trim_sql,\n            exp.Trunc: rename_func(\"TRUNCATE\"),\n            exp.TryCast: no_trycast_sql,\n            exp.TsOrDsAdd: date_add_sql(\"ADD\"),\n            exp.TsOrDsDiff: lambda self, e: self.func(\"DATEDIFF\", e.this, e.expression),\n            exp.TsOrDsToDate: _ts_or_ds_to_date_sql,\n            exp.Unicode: lambda self, e: f\"ORD(CONVERT({self.sql(e.this)} USING utf32))\",\n            exp.UnixToTime: _unix_to_time_sql,\n            exp.Week: _remove_ts_or_ds_to_date(),\n            exp.WeekOfYear: _remove_ts_or_ds_to_date(rename_func(\"WEEKOFYEAR\")),\n            exp.Year: _remove_ts_or_ds_to_date(),\n            exp.UtcTimestamp: rename_func(\"UTC_TIMESTAMP\"),\n            exp.UtcTime: rename_func(\"UTC_TIME\"),\n        }\n\n        UNSIGNED_TYPE_MAPPING = {\n            exp.DType.UBIGINT: \"BIGINT\",\n            exp.DType.UINT: \"INT\",\n            exp.DType.UMEDIUMINT: \"MEDIUMINT\",\n            exp.DType.USMALLINT: \"SMALLINT\",\n            exp.DType.UTINYINT: \"TINYINT\",\n            exp.DType.UDECIMAL: \"DECIMAL\",\n            exp.DType.UDOUBLE: \"DOUBLE\",\n        }\n\n        TIMESTAMP_TYPE_MAPPING = {\n            exp.DType.DATETIME2: \"DATETIME\",\n            exp.DType.SMALLDATETIME: \"DATETIME\",\n            exp.DType.TIMESTAMP: \"DATETIME\",\n            exp.DType.TIMESTAMPNTZ: \"DATETIME\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPLTZ: \"TIMESTAMP\",\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            **UNSIGNED_TYPE_MAPPING,\n            **TIMESTAMP_TYPE_MAPPING,\n        }\n\n        TYPE_MAPPING.pop(exp.DType.MEDIUMTEXT)\n        TYPE_MAPPING.pop(exp.DType.LONGTEXT)\n        TYPE_MAPPING.pop(exp.DType.TINYTEXT)\n        TYPE_MAPPING.pop(exp.DType.BLOB)\n        TYPE_MAPPING.pop(exp.DType.MEDIUMBLOB)\n        TYPE_MAPPING.pop(exp.DType.LONGBLOB)\n        TYPE_MAPPING.pop(exp.DType.TINYBLOB)\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.TransientProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.PartitionedByProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.PartitionByRangeProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.PartitionByListProperty: exp.Properties.Location.POST_SCHEMA,\n        }\n\n        LIMIT_FETCH = \"LIMIT\"\n\n        LIMIT_ONLY_LITERALS = True\n\n        CHAR_CAST_MAPPING = dict.fromkeys(\n            (\n                exp.DType.LONGTEXT,\n                exp.DType.LONGBLOB,\n                exp.DType.MEDIUMBLOB,\n                exp.DType.MEDIUMTEXT,\n                exp.DType.TEXT,\n                exp.DType.TINYBLOB,\n                exp.DType.TINYTEXT,\n                exp.DType.VARCHAR,\n            ),\n            \"CHAR\",\n        )\n        SIGNED_CAST_MAPPING = dict.fromkeys(\n            (\n                exp.DType.BIGINT,\n                exp.DType.BOOLEAN,\n                exp.DType.INT,\n                exp.DType.SMALLINT,\n                exp.DType.TINYINT,\n                exp.DType.MEDIUMINT,\n            ),\n            \"SIGNED\",\n        )\n\n        # MySQL doesn't support many datatypes in cast.\n        # https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_cast\n        CAST_MAPPING = {\n            **CHAR_CAST_MAPPING,\n            **SIGNED_CAST_MAPPING,\n            exp.DType.UBIGINT: \"UNSIGNED\",\n        }\n\n        TIMESTAMP_FUNC_TYPES = {\n            exp.DType.TIMESTAMPTZ,\n            exp.DType.TIMESTAMPLTZ,\n        }\n\n        # https://dev.mysql.com/doc/refman/8.0/en/keywords.html\n        RESERVED_KEYWORDS = {\n            \"accessible\",\n            \"add\",\n            \"all\",\n            \"alter\",\n            \"analyze\",\n            \"and\",\n            \"as\",\n            \"asc\",\n            \"asensitive\",\n            \"before\",\n            \"between\",\n            \"bigint\",\n            \"binary\",\n            \"blob\",\n            \"both\",\n            \"by\",\n            \"call\",\n            \"cascade\",\n            \"case\",\n            \"change\",\n            \"char\",\n            \"character\",\n            \"check\",\n            \"collate\",\n            \"column\",\n            \"condition\",\n            \"constraint\",\n            \"continue\",\n            \"convert\",\n            \"create\",\n            \"cross\",\n            \"cube\",\n            \"cume_dist\",\n            \"current_date\",\n            \"current_time\",\n            \"current_timestamp\",\n            \"current_user\",\n            \"cursor\",\n            \"database\",\n            \"databases\",\n            \"day_hour\",\n            \"day_microsecond\",\n            \"day_minute\",\n            \"day_second\",\n            \"dec\",\n            \"decimal\",\n            \"declare\",\n            \"default\",\n            \"delayed\",\n            \"delete\",\n            \"dense_rank\",\n            \"desc\",\n            \"describe\",\n            \"deterministic\",\n            \"distinct\",\n            \"distinctrow\",\n            \"div\",\n            \"double\",\n            \"drop\",\n            \"dual\",\n            \"each\",\n            \"else\",\n            \"elseif\",\n            \"empty\",\n            \"enclosed\",\n            \"escaped\",\n            \"except\",\n            \"exists\",\n            \"exit\",\n            \"explain\",\n            \"false\",\n            \"fetch\",\n            \"first_value\",\n            \"float\",\n            \"float4\",\n            \"float8\",\n            \"for\",\n            \"force\",\n            \"foreign\",\n            \"from\",\n            \"fulltext\",\n            \"function\",\n            \"generated\",\n            \"get\",\n            \"grant\",\n            \"group\",\n            \"grouping\",\n            \"groups\",\n            \"having\",\n            \"high_priority\",\n            \"hour_microsecond\",\n            \"hour_minute\",\n            \"hour_second\",\n            \"if\",\n            \"ignore\",\n            \"in\",\n            \"index\",\n            \"infile\",\n            \"inner\",\n            \"inout\",\n            \"insensitive\",\n            \"insert\",\n            \"int\",\n            \"int1\",\n            \"int2\",\n            \"int3\",\n            \"int4\",\n            \"int8\",\n            \"integer\",\n            \"intersect\",\n            \"interval\",\n            \"into\",\n            \"io_after_gtids\",\n            \"io_before_gtids\",\n            \"is\",\n            \"iterate\",\n            \"join\",\n            \"json_table\",\n            \"key\",\n            \"keys\",\n            \"kill\",\n            \"lag\",\n            \"last_value\",\n            \"lateral\",\n            \"lead\",\n            \"leading\",\n            \"leave\",\n            \"left\",\n            \"like\",\n            \"limit\",\n            \"linear\",\n            \"lines\",\n            \"load\",\n            \"localtime\",\n            \"localtimestamp\",\n            \"lock\",\n            \"long\",\n            \"longblob\",\n            \"longtext\",\n            \"loop\",\n            \"low_priority\",\n            \"master_bind\",\n            \"master_ssl_verify_server_cert\",\n            \"match\",\n            \"maxvalue\",\n            \"mediumblob\",\n            \"mediumint\",\n            \"mediumtext\",\n            \"middleint\",\n            \"minute_microsecond\",\n            \"minute_second\",\n            \"mod\",\n            \"modifies\",\n            \"natural\",\n            \"not\",\n            \"no_write_to_binlog\",\n            \"nth_value\",\n            \"ntile\",\n            \"null\",\n            \"numeric\",\n            \"of\",\n            \"on\",\n            \"optimize\",\n            \"optimizer_costs\",\n            \"option\",\n            \"optionally\",\n            \"or\",\n            \"order\",\n            \"out\",\n            \"outer\",\n            \"outfile\",\n            \"over\",\n            \"partition\",\n            \"percent_rank\",\n            \"precision\",\n            \"primary\",\n            \"procedure\",\n            \"purge\",\n            \"range\",\n            \"rank\",\n            \"read\",\n            \"reads\",\n            \"read_write\",\n            \"real\",\n            \"recursive\",\n            \"references\",\n            \"regexp\",\n            \"release\",\n            \"rename\",\n            \"repeat\",\n            \"replace\",\n            \"require\",\n            \"resignal\",\n            \"restrict\",\n            \"return\",\n            \"revoke\",\n            \"right\",\n            \"rlike\",\n            \"row\",\n            \"rows\",\n            \"row_number\",\n            \"schema\",\n            \"schemas\",\n            \"second_microsecond\",\n            \"select\",\n            \"sensitive\",\n            \"separator\",\n            \"set\",\n            \"show\",\n            \"signal\",\n            \"smallint\",\n            \"spatial\",\n            \"specific\",\n            \"sql\",\n            \"sqlexception\",\n            \"sqlstate\",\n            \"sqlwarning\",\n            \"sql_big_result\",\n            \"sql_calc_found_rows\",\n            \"sql_small_result\",\n            \"ssl\",\n            \"starting\",\n            \"stored\",\n            \"straight_join\",\n            \"system\",\n            \"table\",\n            \"terminated\",\n            \"then\",\n            \"tinyblob\",\n            \"tinyint\",\n            \"tinytext\",\n            \"to\",\n            \"trailing\",\n            \"trigger\",\n            \"true\",\n            \"undo\",\n            \"union\",\n            \"unique\",\n            \"unlock\",\n            \"unsigned\",\n            \"update\",\n            \"usage\",\n            \"use\",\n            \"using\",\n            \"utc_date\",\n            \"utc_time\",\n            \"utc_timestamp\",\n            \"values\",\n            \"varbinary\",\n            \"varchar\",\n            \"varcharacter\",\n            \"varying\",\n            \"virtual\",\n            \"when\",\n            \"where\",\n            \"while\",\n            \"window\",\n            \"with\",\n            \"write\",\n            \"xor\",\n            \"year_month\",\n            \"zerofill\",\n        }\n\n        SQL_SECURITY_VIEW_LOCATION = exp.Properties.Location.POST_CREATE\n\n        def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:\n            locations = super().locate_properties(properties)\n\n            # MySQL puts SQL SECURITY before VIEW but after the schema for functions/procedures\n            if isinstance(create := properties.parent, exp.Create) and create.kind == \"VIEW\":\n                post_schema = locations[exp.Properties.Location.POST_SCHEMA]\n                for i, p in enumerate(post_schema):\n                    if isinstance(p, exp.SqlSecurityProperty):\n                        post_schema.pop(i)\n                        locations[self.SQL_SECURITY_VIEW_LOCATION].append(p)\n                        break\n\n            return locations\n\n        def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:\n            persisted = \"STORED\" if expression.args.get(\"persisted\") else \"VIRTUAL\"\n            return f\"GENERATED ALWAYS AS ({self.sql(expression.this.unnest())}) {persisted}\"\n\n        def array_sql(self, expression: exp.Array) -> str:\n            self.unsupported(\"Arrays are not supported by MySQL\")\n            return self.function_fallback_sql(expression)\n\n        def arraycontainsall_sql(self, expression: exp.ArrayContainsAll) -> str:\n            self.unsupported(\"Array operations are not supported by MySQL\")\n            return self.function_fallback_sql(expression)\n\n        def dpipe_sql(self, expression: exp.DPipe) -> str:\n            return self.func(\"CONCAT\", *expression.flatten())\n\n        def extract_sql(self, expression: exp.Extract) -> str:\n            unit = expression.name\n            if unit and unit.lower() == \"epoch\":\n                return self.func(\"UNIX_TIMESTAMP\", expression.expression)\n\n            return super().extract_sql(expression)\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            if (\n                self.VARCHAR_REQUIRES_SIZE\n                and expression.is_type(exp.DType.VARCHAR)\n                and not expression.expressions\n            ):\n                # `VARCHAR` must always have a size - if it doesn't, we always generate `TEXT`\n                return \"TEXT\"\n\n            # https://dev.mysql.com/doc/refman/8.0/en/numeric-type-syntax.html\n            result = super().datatype_sql(expression)\n            if expression.this in self.UNSIGNED_TYPE_MAPPING:\n                result = f\"{result} UNSIGNED\"\n\n            return result\n\n        def jsonarraycontains_sql(self, expression: exp.JSONArrayContains) -> str:\n            return f\"{self.sql(expression, 'this')} MEMBER OF({self.sql(expression, 'expression')})\"\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            if expression.to.this in self.TIMESTAMP_FUNC_TYPES:\n                return self.func(\"TIMESTAMP\", expression.this)\n\n            to = self.CAST_MAPPING.get(expression.to.this)\n\n            if to:\n                expression.to.set(\"this\", to)\n            return super().cast_sql(expression)\n\n        def show_sql(self, expression: exp.Show) -> str:\n            this = f\" {expression.name}\"\n            full = \" FULL\" if expression.args.get(\"full\") else \"\"\n            global_ = \" GLOBAL\" if expression.args.get(\"global_\") else \"\"\n\n            target = self.sql(expression, \"target\")\n            target = f\" {target}\" if target else \"\"\n            if expression.name in (\"COLUMNS\", \"INDEX\"):\n                target = f\" FROM{target}\"\n            elif expression.name == \"GRANTS\":\n                target = f\" FOR{target}\"\n            elif expression.name in (\"LINKS\", \"PARTITIONS\"):\n                target = f\" ON{target}\" if target else \"\"\n            elif expression.name == \"PROJECTIONS\":\n                target = f\" ON TABLE{target}\" if target else \"\"\n\n            db = self._prefixed_sql(\"FROM\", expression, \"db\")\n\n            like = self._prefixed_sql(\"LIKE\", expression, \"like\")\n            where = self.sql(expression, \"where\")\n\n            types = self.expressions(expression, key=\"types\")\n            types = f\" {types}\" if types else types\n            query = self._prefixed_sql(\"FOR QUERY\", expression, \"query\")\n\n            if expression.name == \"PROFILE\":\n                offset = self._prefixed_sql(\"OFFSET\", expression, \"offset\")\n                limit = self._prefixed_sql(\"LIMIT\", expression, \"limit\")\n            else:\n                offset = \"\"\n                limit = self._oldstyle_limit_sql(expression)\n\n            log = self._prefixed_sql(\"IN\", expression, \"log\")\n            position = self._prefixed_sql(\"FROM\", expression, \"position\")\n\n            channel = self._prefixed_sql(\"FOR CHANNEL\", expression, \"channel\")\n\n            if expression.name == \"ENGINE\":\n                mutex_or_status = \" MUTEX\" if expression.args.get(\"mutex\") else \" STATUS\"\n            else:\n                mutex_or_status = \"\"\n\n            for_table = self._prefixed_sql(\"FOR TABLE\", expression, \"for_table\")\n            for_group = self._prefixed_sql(\"FOR GROUP\", expression, \"for_group\")\n            for_user = self._prefixed_sql(\"FOR USER\", expression, \"for_user\")\n            for_role = self._prefixed_sql(\"FOR ROLE\", expression, \"for_role\")\n            into_outfile = self._prefixed_sql(\"INTO OUTFILE\", expression, \"into_outfile\")\n            json = \" JSON\" if expression.args.get(\"json\") else \"\"\n\n            return f\"SHOW{full}{global_}{this}{json}{target}{for_table}{types}{db}{query}{log}{position}{channel}{mutex_or_status}{like}{where}{offset}{limit}{for_group}{for_user}{for_role}{into_outfile}\"\n\n        def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:\n            \"\"\"To avoid TO keyword in ALTER ... RENAME statements.\n            It's moved from Doris, because it's the same for all MySQL, Doris, and StarRocks.\n            \"\"\"\n            return super().alterrename_sql(expression, include_to=False)\n\n        def altercolumn_sql(self, expression: exp.AlterColumn) -> str:\n            dtype = self.sql(expression, \"dtype\")\n            if not dtype:\n                return super().altercolumn_sql(expression)\n\n            this = self.sql(expression, \"this\")\n            return f\"MODIFY COLUMN {this} {dtype}\"\n\n        def _prefixed_sql(self, prefix: str, expression: exp.Expr, arg: str) -> str:\n            sql = self.sql(expression, arg)\n            return f\" {prefix} {sql}\" if sql else \"\"\n\n        def _oldstyle_limit_sql(self, expression: exp.Show) -> str:\n            limit = self.sql(expression, \"limit\")\n            offset = self.sql(expression, \"offset\")\n            if limit:\n                limit_offset = f\"{offset}, {limit}\" if offset else limit\n                return f\" LIMIT {limit_offset}\"\n            return \"\"\n\n        def timestamptrunc_sql(self, expression: exp.TimestampTrunc) -> str:\n            unit = expression.args.get(\"unit\")\n\n            # Pick an old-enough date to avoid negative timestamp diffs\n            start_ts = \"'0000-01-01 00:00:00'\"\n\n            # Source: https://stackoverflow.com/a/32955740\n            timestamp_diff = build_date_delta(exp.TimestampDiff)([unit, start_ts, expression.this])\n            interval = exp.Interval(this=timestamp_diff, unit=unit)\n            dateadd = build_date_delta_with_interval(exp.DateAdd)([start_ts, interval])\n\n            return self.sql(dateadd)\n\n        def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:\n            from_tz = expression.args.get(\"source_tz\")\n            to_tz = expression.args.get(\"target_tz\")\n            dt = expression.args.get(\"timestamp\")\n\n            return self.func(\"CONVERT_TZ\", dt, from_tz, to_tz)\n\n        def attimezone_sql(self, expression: exp.AtTimeZone) -> str:\n            self.unsupported(\"AT TIME ZONE is not supported by MySQL\")\n            return self.sql(expression.this)\n\n        def isascii_sql(self, expression: exp.IsAscii) -> str:\n            return f\"REGEXP_LIKE({self.sql(expression.this)}, '^[[:ascii:]]*$')\"\n\n        def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:\n            # https://dev.mysql.com/doc/refman/8.4/en/window-function-descriptions.html\n            self.unsupported(\"MySQL does not support IGNORE NULLS.\")\n            return self.sql(expression.this)\n\n        @unsupported_args(\"this\")\n        def currentschema_sql(self, expression: exp.CurrentSchema) -> str:\n            return self.func(\"SCHEMA\")\n\n        def partition_sql(self, expression: exp.Partition) -> str:\n            parent = expression.parent\n            if isinstance(parent, (exp.PartitionByRangeProperty, exp.PartitionByListProperty)):\n                return self.expressions(expression, flat=True)\n            return super().partition_sql(expression)\n\n        def _partition_by_sql(\n            self, expression: exp.PartitionByRangeProperty | exp.PartitionByListProperty, kind: str\n        ) -> str:\n            partitions = self.expressions(expression, key=\"partition_expressions\", flat=True)\n            create = self.expressions(expression, key=\"create_expressions\", flat=True)\n            return f\"PARTITION BY {kind} ({partitions}) ({create})\"\n\n        def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:\n            return self._partition_by_sql(expression, \"RANGE\")\n\n        def partitionbylistproperty_sql(self, expression: exp.PartitionByListProperty) -> str:\n            return self._partition_by_sql(expression, \"LIST\")\n\n        def partitionlist_sql(self, expression: exp.PartitionList) -> str:\n            name = self.sql(expression, \"this\")\n            values = self.expressions(expression, flat=True)\n            return f\"PARTITION {name} VALUES IN ({values})\"\n\n        def partitionrange_sql(self, expression: exp.PartitionRange) -> str:\n            name = self.sql(expression, \"this\")\n            values = self.expressions(expression, flat=True)\n            return f\"PARTITION {name} VALUES LESS THAN ({values})\"\n"
  },
  {
    "path": "sqlglot/dialects/oracle.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    NormalizationStrategy,\n    no_ilike_sql,\n    rename_func,\n    strposition_sql,\n    to_number_with_nls_param,\n    trim_sql,\n)\nfrom sqlglot.parsers.oracle import OracleParser\nfrom sqlglot.tokens import TokenType\n\n\ndef _trim_sql(self: Oracle.Generator, expression: exp.Trim) -> str:\n    position = expression.args.get(\"position\")\n\n    if position and position.upper() in (\"LEADING\", \"TRAILING\"):\n        return self.trim_sql(expression)\n\n    return trim_sql(self, expression)\n\n\nclass Oracle(Dialect):\n    ALIAS_POST_TABLESAMPLE = True\n    LOCKING_READS_SUPPORTED = True\n    TABLESAMPLE_SIZE_IS_PERCENT = True\n    NULL_ORDERING = \"nulls_are_large\"\n    ON_CONDITION_EMPTY_BEFORE_ERROR = False\n    ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN = False\n    DISABLES_ALIAS_REF_EXPANSION = True\n\n    # See section 8: https://docs.oracle.com/cd/A97630_01/server.920/a96540/sql_elements9a.htm\n    NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE\n\n    # https://docs.oracle.com/database/121/SQLRF/sql_elements004.htm#SQLRF00212\n    # https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes\n    TIME_MAPPING = {\n        \"D\": \"%u\",  # Day of week (1-7)\n        \"DAY\": \"%A\",  # name of day\n        \"DD\": \"%d\",  # day of month (1-31)\n        \"DDD\": \"%j\",  # day of year (1-366)\n        \"DY\": \"%a\",  # abbreviated name of day\n        \"HH\": \"%I\",  # Hour of day (1-12)\n        \"HH12\": \"%I\",  # alias for HH\n        \"HH24\": \"%H\",  # Hour of day (0-23)\n        \"IW\": \"%V\",  # Calendar week of year (1-52 or 1-53), as defined by the ISO 8601 standard\n        \"MI\": \"%M\",  # Minute (0-59)\n        \"MM\": \"%m\",  # Month (01-12; January = 01)\n        \"MON\": \"%b\",  # Abbreviated name of month\n        \"MONTH\": \"%B\",  # Name of month\n        \"SS\": \"%S\",  # Second (0-59)\n        \"WW\": \"%W\",  # Week of year (1-53)\n        \"YY\": \"%y\",  # 15\n        \"YYYY\": \"%Y\",  # 2015\n        \"FF6\": \"%f\",  # only 6 digits are supported in python formats\n    }\n\n    PSEUDOCOLUMNS = {\"ROWNUM\", \"ROWID\", \"OBJECT_ID\", \"OBJECT_VALUE\", \"LEVEL\"}\n\n    def can_quote(self, identifier: exp.Identifier, identify: str | bool = \"safe\") -> bool:\n        # Disable quoting for pseudocolumns as it may break queries e.g\n        # `WHERE \"ROWNUM\" = ...` does not work but `WHERE ROWNUM = ...` does\n        return (\n            identifier.quoted or not isinstance(identifier.parent, exp.Pseudocolumn)\n        ) and super().can_quote(identifier, identify=identify)\n\n    class Tokenizer(tokens.Tokenizer):\n        VAR_SINGLE_TOKENS = {\"@\", \"$\", \"#\"}\n\n        UNICODE_STRINGS = [\n            (prefix + q, q)\n            for q in t.cast(t.List[str], tokens.Tokenizer.QUOTES)\n            for prefix in (\"U\", \"u\")\n        ]\n\n        NESTED_COMMENTS = False\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"(+)\": TokenType.JOIN_MARKER,\n            \"BINARY_DOUBLE\": TokenType.DOUBLE,\n            \"BINARY_FLOAT\": TokenType.FLOAT,\n            \"BULK COLLECT INTO\": TokenType.BULK_COLLECT_INTO,\n            \"COLUMNS\": TokenType.COLUMN,\n            \"MATCH_RECOGNIZE\": TokenType.MATCH_RECOGNIZE,\n            \"MINUS\": TokenType.EXCEPT,\n            \"NVARCHAR2\": TokenType.NVARCHAR,\n            \"ORDER SIBLINGS BY\": TokenType.ORDER_SIBLINGS_BY,\n            \"SAMPLE\": TokenType.TABLE_SAMPLE,\n            \"START\": TokenType.BEGIN,\n            \"TOP\": TokenType.TOP,\n            \"VARCHAR2\": TokenType.VARCHAR,\n            \"SYSTIMESTAMP\": TokenType.SYSTIMESTAMP,\n        }\n\n    Parser = OracleParser\n\n    class Generator(generator.Generator):\n        LOCKING_READS_SUPPORTED = True\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        DATA_TYPE_SPECIFIERS_ALLOWED = True\n        ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = False\n        LIMIT_FETCH = \"FETCH\"\n        TABLESAMPLE_KEYWORDS = \"SAMPLE\"\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        SUPPORTS_SELECT_INTO = True\n        TZ_TO_WITH_TIME_ZONE = True\n        SUPPORTS_WINDOW_EXCLUDE = True\n        QUERY_HINT_SEP = \" \"\n        SUPPORTS_DECODE_CASE = True\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.TINYINT: \"SMALLINT\",\n            exp.DType.SMALLINT: \"SMALLINT\",\n            exp.DType.INT: \"INT\",\n            exp.DType.BIGINT: \"INT\",\n            exp.DType.DECIMAL: \"NUMBER\",\n            exp.DType.DOUBLE: \"DOUBLE PRECISION\",\n            exp.DType.VARCHAR: \"VARCHAR2\",\n            exp.DType.NVARCHAR: \"NVARCHAR2\",\n            exp.DType.NCHAR: \"NCHAR\",\n            exp.DType.TEXT: \"CLOB\",\n            exp.DType.TIMETZ: \"TIME\",\n            exp.DType.TIMESTAMPNTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n            exp.DType.BINARY: \"BLOB\",\n            exp.DType.VARBINARY: \"BLOB\",\n            exp.DType.ROWVERSION: \"BLOB\",\n        }\n        TYPE_MAPPING.pop(exp.DType.BLOB)\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.DateStrToDate: lambda self, e: self.func(\n                \"TO_DATE\", e.this, exp.Literal.string(\"YYYY-MM-DD\")\n            ),\n            exp.DateTrunc: lambda self, e: self.func(\"TRUNC\", e.this, e.unit),\n            exp.EuclideanDistance: rename_func(\"L2_DISTANCE\"),\n            exp.ILike: no_ilike_sql,\n            exp.LogicalOr: rename_func(\"MAX\"),\n            exp.LogicalAnd: rename_func(\"MIN\"),\n            exp.Mod: rename_func(\"MOD\"),\n            exp.Rand: rename_func(\"DBMS_RANDOM.VALUE\"),\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_distinct_on,\n                    transforms.eliminate_qualify,\n                ]\n            ),\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self, e, func_name=\"INSTR\", supports_position=True, supports_occurrence=True\n            ),\n            exp.StrToTime: lambda self, e: self.func(\"TO_TIMESTAMP\", e.this, self.format_time(e)),\n            exp.StrToDate: lambda self, e: self.func(\"TO_DATE\", e.this, self.format_time(e)),\n            exp.Subquery: lambda self, e: self.subquery_sql(e, sep=\" \"),\n            exp.Substring: rename_func(\"SUBSTR\"),\n            exp.Table: lambda self, e: self.table_sql(e, sep=\" \"),\n            exp.TableSample: lambda self, e: self.tablesample_sql(e),\n            exp.TemporaryProperty: lambda _, e: f\"{e.name or 'GLOBAL'} TEMPORARY\",\n            exp.TimeToStr: lambda self, e: self.func(\"TO_CHAR\", e.this, self.format_time(e)),\n            exp.ToChar: lambda self, e: self.function_fallback_sql(e),\n            exp.ToNumber: to_number_with_nls_param,\n            exp.Trim: _trim_sql,\n            exp.Unicode: lambda self, e: f\"ASCII(UNISTR({self.sql(e.this)}))\",\n            exp.UnixToTime: lambda self, e: (\n                f\"TO_DATE('1970-01-01', 'YYYY-MM-DD') + ({self.sql(e, 'this')} / 86400)\"\n            ),\n            exp.UtcTimestamp: rename_func(\"UTC_TIMESTAMP\"),\n            exp.UtcTime: rename_func(\"UTC_TIME\"),\n            exp.Systimestamp: lambda self, e: \"SYSTIMESTAMP\",\n        }\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        def currenttimestamp_sql(self, expression: exp.CurrentTimestamp) -> str:\n            if expression.args.get(\"sysdate\"):\n                return \"SYSDATE\"\n\n            this = expression.this\n            return self.func(\"CURRENT_TIMESTAMP\", this) if this else \"CURRENT_TIMESTAMP\"\n\n        def offset_sql(self, expression: exp.Offset) -> str:\n            return f\"{super().offset_sql(expression)} ROWS\"\n\n        def add_column_sql(self, expression: exp.Expr) -> str:\n            return f\"ADD {self.sql(expression)}\"\n\n        def queryoption_sql(self, expression: exp.QueryOption) -> str:\n            option = self.sql(expression, \"this\")\n            value = self.sql(expression, \"expression\")\n            value = f\" CONSTRAINT {value}\" if value else \"\"\n\n            return f\"{option}{value}\"\n\n        def coalesce_sql(self, expression: exp.Coalesce) -> str:\n            func_name = \"NVL\" if expression.args.get(\"is_nvl\") else \"COALESCE\"\n            return rename_func(func_name)(self, expression)\n\n        def into_sql(self, expression: exp.Into) -> str:\n            into = \"INTO\" if not expression.args.get(\"bulk_collect\") else \"BULK COLLECT INTO\"\n            if expression.this:\n                return f\"{self.seg(into)} {self.sql(expression, 'this')}\"\n\n            return f\"{self.seg(into)} {self.expressions(expression)}\"\n\n        def hint_sql(self, expression: exp.Hint) -> str:\n            expressions = []\n\n            for expression in expression.expressions:\n                if isinstance(expression, exp.Anonymous):\n                    formatted_args = self.format_args(*expression.expressions, sep=\" \")\n                    expressions.append(f\"{self.sql(expression, 'this')}({formatted_args})\")\n                else:\n                    expressions.append(self.sql(expression))\n\n            return f\" /*+ {self.expressions(sqls=expressions, sep=self.QUERY_HINT_SEP).strip()} */\"\n\n        def isascii_sql(self, expression: exp.IsAscii) -> str:\n            return f\"NVL(REGEXP_LIKE({self.sql(expression.this)}, '^[' || CHR(1) || '-' || CHR(127) || ']*$'), TRUE)\"\n\n        def interval_sql(self, expression: exp.Interval) -> str:\n            return f\"{'INTERVAL ' if isinstance(expression.this, exp.Literal) else ''}{self.sql(expression, 'this')} {self.sql(expression, 'unit')}\"\n\n        def columndef_sql(self, expression: exp.ColumnDef, sep: str = \" \") -> str:\n            param_constraint = expression.find(exp.InOutColumnConstraint)\n            if param_constraint:\n                sep = f\" {self.sql(param_constraint)} \"\n                param_constraint.pop()\n            return super().columndef_sql(expression, sep)\n"
  },
  {
    "path": "sqlglot/dialects/postgres.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    DATE_ADD_OR_SUB,\n    Dialect,\n    JSON_EXTRACT_TYPE,\n    any_value_to_max_sql,\n    array_append_sql,\n    array_concat_sql,\n    bool_xor_sql,\n    datestrtodate_sql,\n    filter_array_using_unnest,\n    generate_series_sql,\n    getbit_sql,\n    inline_array_sql,\n    json_extract_segments,\n    json_path_key_only_name,\n    max_or_greatest,\n    merge_without_target_sql,\n    min_or_least,\n    no_last_day_sql,\n    no_map_from_entries_sql,\n    no_paren_current_date_sql,\n    no_pivot_sql,\n    no_trycast_sql,\n    rename_func,\n    sha256_sql,\n    struct_extract_sql,\n    timestamptrunc_sql,\n    timestrtotime_sql,\n    trim_sql,\n    ts_or_ds_add_cast,\n    strposition_sql,\n    count_if_to_sum,\n    groupconcat_sql,\n    regexp_replace_global_modifier,\n    sha2_digest_sql,\n)\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.postgres import PostgresParser\nfrom sqlglot.tokens import TokenType\n\n\nDATE_DIFF_FACTOR = {\n    \"MICROSECOND\": \" * 1000000\",\n    \"MILLISECOND\": \" * 1000\",\n    \"SECOND\": \"\",\n    \"MINUTE\": \" / 60\",\n    \"HOUR\": \" / 3600\",\n    \"DAY\": \" / 86400\",\n}\n\n\ndef _date_add_sql(kind: str) -> t.Callable[[Postgres.Generator, DATE_ADD_OR_SUB], str]:\n    def func(self: Postgres.Generator, expression: DATE_ADD_OR_SUB) -> str:\n        if isinstance(expression, exp.TsOrDsAdd):\n            expression = ts_or_ds_add_cast(expression)\n\n        this = self.sql(expression, \"this\")\n        unit = expression.args.get(\"unit\")\n\n        e = self._simplify_unless_literal(expression.expression)\n        if isinstance(e, exp.Literal):\n            e.set(\"is_string\", True)\n        elif e.is_number:\n            e = exp.Literal.string(e.to_py())\n        else:\n            self.unsupported(\"Cannot add non literal\")\n\n        return f\"{this} {kind} {self.sql(exp.Interval(this=e, unit=unit))}\"\n\n    return func\n\n\ndef _date_diff_sql(self: Postgres.Generator, expression: exp.DateDiff) -> str:\n    unit = expression.text(\"unit\").upper()\n    factor = DATE_DIFF_FACTOR.get(unit)\n\n    end = f\"CAST({self.sql(expression, 'this')} AS TIMESTAMP)\"\n    start = f\"CAST({self.sql(expression, 'expression')} AS TIMESTAMP)\"\n\n    if factor is not None:\n        return f\"CAST(EXTRACT(epoch FROM {end} - {start}){factor} AS BIGINT)\"\n\n    age = f\"AGE({end}, {start})\"\n\n    if unit == \"WEEK\":\n        unit = f\"EXTRACT(days FROM ({end} - {start})) / 7\"\n    elif unit == \"MONTH\":\n        unit = f\"EXTRACT(year FROM {age}) * 12 + EXTRACT(month FROM {age})\"\n    elif unit == \"QUARTER\":\n        unit = f\"EXTRACT(year FROM {age}) * 4 + EXTRACT(month FROM {age}) / 3\"\n    elif unit == \"YEAR\":\n        unit = f\"EXTRACT(year FROM {age})\"\n    else:\n        unit = age\n\n    return f\"CAST({unit} AS BIGINT)\"\n\n\ndef _substring_sql(self: Postgres.Generator, expression: exp.Substring) -> str:\n    this = self.sql(expression, \"this\")\n    start = self.sql(expression, \"start\")\n    length = self.sql(expression, \"length\")\n\n    from_part = f\" FROM {start}\" if start else \"\"\n    for_part = f\" FOR {length}\" if length else \"\"\n\n    return f\"SUBSTRING({this}{from_part}{for_part})\"\n\n\ndef _auto_increment_to_serial(expression: exp.Expr) -> exp.Expr:\n    auto = expression.find(exp.AutoIncrementColumnConstraint)\n\n    if auto:\n        expression.args[\"constraints\"].remove(auto.parent)\n        kind = expression.args[\"kind\"]\n\n        if kind.this == exp.DType.INT:\n            kind.replace(exp.DataType(this=exp.DType.SERIAL))\n        elif kind.this == exp.DType.SMALLINT:\n            kind.replace(exp.DataType(this=exp.DType.SMALLSERIAL))\n        elif kind.this == exp.DType.BIGINT:\n            kind.replace(exp.DataType(this=exp.DType.BIGSERIAL))\n\n    return expression\n\n\ndef _serial_to_generated(expression: exp.Expr) -> exp.Expr:\n    if not isinstance(expression, exp.ColumnDef):\n        return expression\n    kind = expression.kind\n    if not kind:\n        return expression\n\n    if kind.this == exp.DType.SERIAL:\n        data_type = exp.DataType(this=exp.DType.INT)\n    elif kind.this == exp.DType.SMALLSERIAL:\n        data_type = exp.DataType(this=exp.DType.SMALLINT)\n    elif kind.this == exp.DType.BIGSERIAL:\n        data_type = exp.DataType(this=exp.DType.BIGINT)\n    else:\n        data_type = None\n\n    if data_type:\n        expression.args[\"kind\"].replace(data_type)\n        constraints = expression.args[\"constraints\"]\n        generated = exp.ColumnConstraint(kind=exp.GeneratedAsIdentityColumnConstraint(this=False))\n        notnull = exp.ColumnConstraint(kind=exp.NotNullColumnConstraint())\n\n        if notnull not in constraints:\n            constraints.insert(0, notnull)\n        if generated not in constraints:\n            constraints.insert(0, generated)\n\n    return expression\n\n\ndef _json_extract_sql(\n    name: str, op: str\n) -> t.Callable[[Postgres.Generator, JSON_EXTRACT_TYPE], str]:\n    def _generate(self: Postgres.Generator, expression: JSON_EXTRACT_TYPE) -> str:\n        if expression.args.get(\"only_json_types\"):\n            return json_extract_segments(name, quoted_index=False, op=op)(self, expression)\n        return json_extract_segments(name)(self, expression)\n\n    return _generate\n\n\ndef _unix_to_time_sql(self: Postgres.Generator, expression: exp.UnixToTime) -> str:\n    scale = expression.args.get(\"scale\")\n    timestamp = expression.this\n\n    if scale in (None, exp.UnixToTime.SECONDS):\n        return self.func(\"TO_TIMESTAMP\", timestamp, self.format_time(expression))\n\n    return self.func(\n        \"TO_TIMESTAMP\",\n        exp.Div(this=timestamp, expression=exp.func(\"POW\", 10, scale)),\n        self.format_time(expression),\n    )\n\n\ndef _levenshtein_sql(self: Postgres.Generator, expression: exp.Levenshtein) -> str:\n    name = \"LEVENSHTEIN_LESS_EQUAL\" if expression.args.get(\"max_dist\") else \"LEVENSHTEIN\"\n\n    return rename_func(name)(self, expression)\n\n\ndef _versioned_anyvalue_sql(self: Postgres.Generator, expression: exp.AnyValue) -> str:\n    # https://www.postgresql.org/docs/16/functions-aggregate.html\n    # https://www.postgresql.org/about/featurematrix/\n    if self.dialect.version < (16,):\n        return any_value_to_max_sql(self, expression)\n\n    return rename_func(\"ANY_VALUE\")(self, expression)\n\n\ndef _round_sql(self: Postgres.Generator, expression: exp.Round) -> str:\n    this = self.sql(expression, \"this\")\n    decimals = self.sql(expression, \"decimals\")\n\n    if not decimals:\n        return self.func(\"ROUND\", this)\n\n    if not expression.type:\n        from sqlglot.optimizer.annotate_types import annotate_types\n\n        expression = annotate_types(expression, dialect=self.dialect)\n\n    # ROUND(double precision, integer) is not permitted in Postgres\n    # so it's necessary to cast to decimal before rounding.\n    if expression.this.is_type(exp.DType.DOUBLE):\n        decimal_type = exp.DataType.build(exp.DType.DECIMAL, expressions=expression.expressions)\n        this = self.sql(exp.Cast(this=this, to=decimal_type))\n\n    return self.func(\"ROUND\", this, decimals)\n\n\nclass Postgres(Dialect):\n    INDEX_OFFSET = 1\n    TYPED_DIVISION = True\n    CONCAT_COALESCE = True\n    NULL_ORDERING = \"nulls_are_large\"\n    TIME_FORMAT = \"'YYYY-MM-DD HH24:MI:SS'\"\n    TABLESAMPLE_SIZE_IS_PERCENT = True\n    TABLES_REFERENCEABLE_AS_COLUMNS = True\n\n    DEFAULT_FUNCTIONS_COLUMN_NAMES = {\n        exp.ExplodingGenerateSeries: \"generate_series\",\n    }\n\n    TIME_MAPPING = {\n        \"d\": \"%u\",  # 1-based day of week\n        \"D\": \"%u\",  # 1-based day of week\n        \"dd\": \"%d\",  # day of month\n        \"DD\": \"%d\",  # day of month\n        \"ddd\": \"%j\",  # zero padded day of year\n        \"DDD\": \"%j\",  # zero padded day of year\n        \"FMDD\": \"%-d\",  # - is no leading zero for Python; same for FM in postgres\n        \"FMDDD\": \"%-j\",  # day of year\n        \"FMHH12\": \"%-I\",  # 9\n        \"FMHH24\": \"%-H\",  # 9\n        \"FMMI\": \"%-M\",  # Minute\n        \"FMMM\": \"%-m\",  # 1\n        \"FMSS\": \"%-S\",  # Second\n        \"HH12\": \"%I\",  # 09\n        \"HH24\": \"%H\",  # 09\n        \"mi\": \"%M\",  # zero padded minute\n        \"MI\": \"%M\",  # zero padded minute\n        \"mm\": \"%m\",  # 01\n        \"MM\": \"%m\",  # 01\n        \"OF\": \"%z\",  # utc offset\n        \"ss\": \"%S\",  # zero padded second\n        \"SS\": \"%S\",  # zero padded second\n        \"TMDay\": \"%A\",  # TM is locale dependent\n        \"TMDy\": \"%a\",\n        \"TMMon\": \"%b\",  # Sep\n        \"TMMonth\": \"%B\",  # September\n        \"TZ\": \"%Z\",  # uppercase timezone name\n        \"US\": \"%f\",  # zero padded microsecond\n        \"ww\": \"%U\",  # 1-based week of year\n        \"WW\": \"%U\",  # 1-based week of year\n        \"yy\": \"%y\",  # 15\n        \"YY\": \"%y\",  # 15\n        \"yyyy\": \"%Y\",  # 2015\n        \"YYYY\": \"%Y\",  # 2015\n    }\n\n    class Tokenizer(tokens.Tokenizer):\n        BIT_STRINGS = [(\"b'\", \"'\"), (\"B'\", \"'\")]\n        HEX_STRINGS = [(\"x'\", \"'\"), (\"X'\", \"'\")]\n        BYTE_STRINGS = [(\"e'\", \"'\"), (\"E'\", \"'\")]\n        BYTE_STRING_ESCAPES = [\"'\", \"\\\\\"]\n        HEREDOC_STRINGS = [\"$\"]\n\n        HEREDOC_TAG_IS_IDENTIFIER = True\n        HEREDOC_STRING_ALTERNATIVE = TokenType.PARAMETER\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"~\": TokenType.RLIKE,\n            \"@@\": TokenType.DAT,\n            \"@>\": TokenType.AT_GT,\n            \"<@\": TokenType.LT_AT,\n            \"?&\": TokenType.QMARK_AMP,\n            \"?|\": TokenType.QMARK_PIPE,\n            \"#-\": TokenType.HASH_DASH,\n            \"|/\": TokenType.PIPE_SLASH,\n            \"||/\": TokenType.DPIPE_SLASH,\n            \"BEGIN\": TokenType.BEGIN,\n            \"BIGSERIAL\": TokenType.BIGSERIAL,\n            \"CSTRING\": TokenType.PSEUDO_TYPE,\n            \"DECLARE\": TokenType.COMMAND,\n            \"DO\": TokenType.COMMAND,\n            \"EXEC\": TokenType.COMMAND,\n            \"HSTORE\": TokenType.HSTORE,\n            \"INT8\": TokenType.BIGINT,\n            \"MONEY\": TokenType.MONEY,\n            \"NAME\": TokenType.NAME,\n            \"OID\": TokenType.OBJECT_IDENTIFIER,\n            \"ONLY\": TokenType.ONLY,\n            \"POINT\": TokenType.POINT,\n            \"REFRESH\": TokenType.COMMAND,\n            \"REINDEX\": TokenType.COMMAND,\n            \"RESET\": TokenType.COMMAND,\n            \"SERIAL\": TokenType.SERIAL,\n            \"SMALLSERIAL\": TokenType.SMALLSERIAL,\n            \"TEMP\": TokenType.TEMPORARY,\n            \"REGCLASS\": TokenType.OBJECT_IDENTIFIER,\n            \"REGCOLLATION\": TokenType.OBJECT_IDENTIFIER,\n            \"REGCONFIG\": TokenType.OBJECT_IDENTIFIER,\n            \"REGDICTIONARY\": TokenType.OBJECT_IDENTIFIER,\n            \"REGNAMESPACE\": TokenType.OBJECT_IDENTIFIER,\n            \"REGOPER\": TokenType.OBJECT_IDENTIFIER,\n            \"REGOPERATOR\": TokenType.OBJECT_IDENTIFIER,\n            \"REGPROC\": TokenType.OBJECT_IDENTIFIER,\n            \"REGPROCEDURE\": TokenType.OBJECT_IDENTIFIER,\n            \"REGROLE\": TokenType.OBJECT_IDENTIFIER,\n            \"REGTYPE\": TokenType.OBJECT_IDENTIFIER,\n            \"FLOAT\": TokenType.DOUBLE,\n            \"XML\": TokenType.XML,\n            \"VARIADIC\": TokenType.VARIADIC,\n            \"INOUT\": TokenType.INOUT,\n        }\n        KEYWORDS.pop(\"/*+\")\n        KEYWORDS.pop(\"DIV\")\n\n        SINGLE_TOKENS = {\n            **tokens.Tokenizer.SINGLE_TOKENS,\n            \"$\": TokenType.HEREDOC_STRING,\n        }\n\n        VAR_SINGLE_TOKENS = {\"$\"}\n\n    Parser = PostgresParser\n\n    class Generator(generator.Generator):\n        SINGLE_STRING_INTERVAL = True\n        RENAME_TABLE_WITH_DB = False\n        LOCKING_READS_SUPPORTED = True\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n        NVL2_SUPPORTED = False\n        PARAMETER_TOKEN = \"$\"\n        NAMED_PLACEHOLDER_TOKEN = \"%\"\n        TABLESAMPLE_SIZE_IS_ROWS = False\n        TABLESAMPLE_SEED_KEYWORD = \"REPEATABLE\"\n        SUPPORTS_SELECT_INTO = True\n        JSON_TYPE_REQUIRED_FOR_EXTRACTION = True\n        SUPPORTS_UNLOGGED_TABLES = True\n        LIKE_PROPERTY_INSIDE_SCHEMA = True\n        MULTI_ARG_DISTINCT = False\n        CAN_IMPLEMENT_ARRAY_ANY = True\n        SUPPORTS_WINDOW_EXCLUDE = True\n        COPY_HAS_INTO_KEYWORD = False\n        ARRAY_CONCAT_IS_VAR_LEN = False\n        SUPPORTS_MEDIAN = False\n        ARRAY_SIZE_DIM_REQUIRED = True\n        SUPPORTS_BETWEEN_FLAGS = True\n        INOUT_SEPARATOR = \"\"  # PostgreSQL uses \"INOUT\" (no space)\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n        }\n\n        def lateral_sql(self, expression: exp.Lateral) -> str:\n            sql = super().lateral_sql(expression)\n\n            if expression.args.get(\"cross_apply\") is not None:\n                sql = f\"{sql} ON TRUE\"\n\n            return sql\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.TINYINT: \"SMALLINT\",\n            exp.DType.FLOAT: \"REAL\",\n            exp.DType.DOUBLE: \"DOUBLE PRECISION\",\n            exp.DType.BINARY: \"BYTEA\",\n            exp.DType.VARBINARY: \"BYTEA\",\n            exp.DType.ROWVERSION: \"BYTEA\",\n            exp.DType.DATETIME: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPNTZ: \"TIMESTAMP\",\n            exp.DType.BLOB: \"BYTEA\",\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.AnyValue: _versioned_anyvalue_sql,\n            exp.ArrayConcat: array_concat_sql(\"ARRAY_CAT\"),\n            exp.ArrayFilter: filter_array_using_unnest,\n            exp.ArrayAppend: array_append_sql(\"ARRAY_APPEND\"),\n            exp.ArrayPrepend: array_append_sql(\"ARRAY_PREPEND\", swap_params=True),\n            exp.BitwiseAndAgg: rename_func(\"BIT_AND\"),\n            exp.BitwiseOrAgg: rename_func(\"BIT_OR\"),\n            exp.BitwiseXor: lambda self, e: self.binary(e, \"#\"),\n            exp.BitwiseXorAgg: rename_func(\"BIT_XOR\"),\n            exp.ColumnDef: transforms.preprocess([_auto_increment_to_serial, _serial_to_generated]),\n            exp.CurrentDate: no_paren_current_date_sql,\n            exp.CurrentTimestamp: lambda *_: \"CURRENT_TIMESTAMP\",\n            exp.CurrentUser: lambda *_: \"CURRENT_USER\",\n            exp.CurrentVersion: rename_func(\"VERSION\"),\n            exp.DateAdd: _date_add_sql(\"+\"),\n            exp.DateDiff: _date_diff_sql,\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.DateSub: _date_add_sql(\"-\"),\n            exp.Explode: rename_func(\"UNNEST\"),\n            exp.ExplodingGenerateSeries: rename_func(\"GENERATE_SERIES\"),\n            exp.GenerateSeries: generate_series_sql(\"GENERATE_SERIES\"),\n            exp.Getbit: getbit_sql,\n            exp.GroupConcat: lambda self, e: groupconcat_sql(\n                self, e, func_name=\"STRING_AGG\", within_group=False\n            ),\n            exp.IntDiv: rename_func(\"DIV\"),\n            exp.JSONArrayAgg: lambda self, e: self.func(\n                \"JSON_AGG\",\n                self.sql(e, \"this\"),\n                suffix=f\"{self.sql(e, 'order')})\",\n            ),\n            exp.JSONExtract: _json_extract_sql(\"JSON_EXTRACT_PATH\", \"->\"),\n            exp.JSONExtractScalar: _json_extract_sql(\"JSON_EXTRACT_PATH_TEXT\", \"->>\"),\n            exp.JSONBExtract: lambda self, e: self.binary(e, \"#>\"),\n            exp.JSONBExtractScalar: lambda self, e: self.binary(e, \"#>>\"),\n            exp.JSONBContains: lambda self, e: self.binary(e, \"?\"),\n            exp.ParseJSON: lambda self, e: self.sql(exp.cast(e.this, exp.DType.JSON)),\n            exp.JSONPathKey: json_path_key_only_name,\n            exp.JSONPathRoot: lambda *_: \"\",\n            exp.JSONPathSubscript: lambda self, e: self.json_path_part(e.this),\n            exp.LastDay: no_last_day_sql,\n            exp.LogicalOr: rename_func(\"BOOL_OR\"),\n            exp.LogicalAnd: rename_func(\"BOOL_AND\"),\n            exp.Max: max_or_greatest,\n            exp.MapFromEntries: no_map_from_entries_sql,\n            exp.Min: min_or_least,\n            exp.Merge: merge_without_target_sql,\n            exp.PartitionedByProperty: lambda self, e: f\"PARTITION BY {self.sql(e, 'this')}\",\n            exp.PercentileCont: transforms.preprocess(\n                [transforms.add_within_group_for_percentiles]\n            ),\n            exp.PercentileDisc: transforms.preprocess(\n                [transforms.add_within_group_for_percentiles]\n            ),\n            exp.Pivot: no_pivot_sql,\n            exp.Rand: rename_func(\"RANDOM\"),\n            exp.RegexpLike: lambda self, e: self.binary(e, \"~\"),\n            exp.RegexpILike: lambda self, e: self.binary(e, \"~*\"),\n            exp.RegexpReplace: lambda self, e: self.func(\n                \"REGEXP_REPLACE\",\n                e.this,\n                e.expression,\n                e.args.get(\"replacement\"),\n                e.args.get(\"position\"),\n                e.args.get(\"occurrence\"),\n                regexp_replace_global_modifier(e),\n            ),\n            exp.Round: _round_sql,\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_semi_and_anti_joins,\n                    transforms.eliminate_qualify,\n                ]\n            ),\n            exp.SHA2: sha256_sql,\n            exp.SHA2Digest: sha2_digest_sql,\n            exp.StrPosition: lambda self, e: strposition_sql(self, e, func_name=\"POSITION\"),\n            exp.StrToDate: lambda self, e: self.func(\"TO_DATE\", e.this, self.format_time(e)),\n            exp.StrToTime: lambda self, e: self.func(\"TO_TIMESTAMP\", e.this, self.format_time(e)),\n            exp.StructExtract: struct_extract_sql,\n            exp.Substring: _substring_sql,\n            exp.TimeFromParts: rename_func(\"MAKE_TIME\"),\n            exp.TimestampFromParts: rename_func(\"MAKE_TIMESTAMP\"),\n            exp.TimestampTrunc: timestamptrunc_sql(zone=True),\n            exp.TimeStrToTime: timestrtotime_sql,\n            exp.TimeToStr: lambda self, e: self.func(\"TO_CHAR\", e.this, self.format_time(e)),\n            exp.ToChar: lambda self, e: (\n                self.function_fallback_sql(e) if e.args.get(\"format\") else self.tochar_sql(e)\n            ),\n            exp.Trim: trim_sql,\n            exp.TryCast: no_trycast_sql,\n            exp.TsOrDsAdd: _date_add_sql(\"+\"),\n            exp.TsOrDsDiff: _date_diff_sql,\n            exp.UnixToTime: lambda self, e: self.func(\"TO_TIMESTAMP\", e.this),\n            exp.Uuid: lambda *_: \"GEN_RANDOM_UUID()\",\n            exp.TimeToUnix: lambda self, e: self.func(\n                \"DATE_PART\", exp.Literal.string(\"epoch\"), e.this\n            ),\n            exp.VariancePop: rename_func(\"VAR_POP\"),\n            exp.Variance: rename_func(\"VAR_SAMP\"),\n            exp.Xor: bool_xor_sql,\n            exp.Unicode: rename_func(\"ASCII\"),\n            exp.UnixToTime: _unix_to_time_sql,\n            exp.Levenshtein: _levenshtein_sql,\n            exp.JSONObjectAgg: rename_func(\"JSON_OBJECT_AGG\"),\n            exp.JSONBObjectAgg: rename_func(\"JSONB_OBJECT_AGG\"),\n            exp.CountIf: count_if_to_sum,\n        }\n\n        TRANSFORMS.pop(exp.CommentColumnConstraint)\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.TransientProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        def schemacommentproperty_sql(self, expression: exp.SchemaCommentProperty) -> str:\n            self.unsupported(\"Table comments are not supported in the CREATE statement\")\n            return \"\"\n\n        def commentcolumnconstraint_sql(self, expression: exp.CommentColumnConstraint) -> str:\n            self.unsupported(\"Column comments are not supported in the CREATE statement\")\n            return \"\"\n\n        def columndef_sql(self, expression: exp.ColumnDef, sep: str = \" \") -> str:\n            # PostgreSQL places parameter modes BEFORE parameter name\n            param_constraint = expression.find(exp.InOutColumnConstraint)\n\n            if param_constraint:\n                mode_sql = self.sql(param_constraint)\n                param_constraint.pop()  # Remove to prevent double-rendering\n                base_sql = super().columndef_sql(expression, sep)\n                return f\"{mode_sql} {base_sql}\"\n\n            return super().columndef_sql(expression, sep)\n\n        def unnest_sql(self, expression: exp.Unnest) -> str:\n            if len(expression.expressions) == 1:\n                arg = expression.expressions[0]\n                if isinstance(arg, exp.GenerateDateArray):\n                    generate_series: exp.Expr = exp.GenerateSeries(**arg.args)\n                    if isinstance(expression.parent, (exp.From, exp.Join)):\n                        generate_series = (\n                            exp.select(\"value::date\")\n                            .from_(exp.Table(this=generate_series).as_(\"_t\", table=[\"value\"]))\n                            .subquery(expression.args.get(\"alias\") or \"_unnested_generate_series\")\n                        )\n                    return self.sql(generate_series)\n\n                from sqlglot.optimizer.annotate_types import annotate_types\n\n                this = annotate_types(arg, dialect=self.dialect)\n                if this.is_type(\"array<json>\"):\n                    while isinstance(this, exp.Cast):\n                        this = this.this\n\n                    arg_as_json = self.sql(exp.cast(this, exp.DType.JSON))\n                    alias = self.sql(expression, \"alias\")\n                    alias = f\" AS {alias}\" if alias else \"\"\n\n                    if expression.args.get(\"offset\"):\n                        self.unsupported(\"Unsupported JSON_ARRAY_ELEMENTS with offset\")\n\n                    return f\"JSON_ARRAY_ELEMENTS({arg_as_json}){alias}\"\n\n            return super().unnest_sql(expression)\n\n        def bracket_sql(self, expression: exp.Bracket) -> str:\n            \"\"\"Forms like ARRAY[1, 2, 3][3] aren't allowed; we need to wrap the ARRAY.\"\"\"\n            if isinstance(expression.this, exp.Array):\n                expression.set(\"this\", exp.paren(expression.this, copy=False))\n\n            return super().bracket_sql(expression)\n\n        def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:\n            this = self.sql(expression, \"this\")\n            expressions = [f\"{self.sql(e)} @@ {this}\" for e in expression.expressions]\n            sql = \" OR \".join(expressions)\n            return f\"({sql})\" if len(expressions) > 1 else sql\n\n        def alterset_sql(self, expression: exp.AlterSet) -> str:\n            exprs = self.expressions(expression, flat=True)\n            exprs = f\"({exprs})\" if exprs else \"\"\n\n            access_method = self.sql(expression, \"access_method\")\n            access_method = f\"ACCESS METHOD {access_method}\" if access_method else \"\"\n            tablespace = self.sql(expression, \"tablespace\")\n            tablespace = f\"TABLESPACE {tablespace}\" if tablespace else \"\"\n            option = self.sql(expression, \"option\")\n\n            return f\"SET {exprs}{access_method}{tablespace}{option}\"\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            if expression.is_type(exp.DType.ARRAY):\n                if expression.expressions:\n                    values = self.expressions(expression, key=\"values\", flat=True)\n                    return f\"{self.expressions(expression, flat=True)}[{values}]\"\n                return \"ARRAY\"\n\n            if expression.is_type(exp.DType.DOUBLE, exp.DType.FLOAT) and expression.expressions:\n                # Postgres doesn't support precision for REAL and DOUBLE PRECISION types\n                return f\"FLOAT({self.expressions(expression, flat=True)})\"\n\n            return super().datatype_sql(expression)\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            this = expression.this\n\n            # Postgres casts DIV() to decimal for transpilation but when roundtripping it's superfluous\n            if isinstance(this, exp.IntDiv) and expression.to == exp.DataType.build(\"decimal\"):\n                return self.sql(this)\n\n            return super().cast_sql(expression, safe_prefix=safe_prefix)\n\n        def array_sql(self, expression: exp.Array) -> str:\n            exprs = expression.expressions\n            func_name = self.normalize_func(\"ARRAY\")\n\n            if isinstance(seq_get(exprs, 0), exp.Select):\n                return f\"{func_name}({self.sql(exprs[0])})\"\n\n            return f\"{func_name}{inline_array_sql(self, expression)}\"\n\n        def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:\n            return f\"GENERATED ALWAYS AS ({self.sql(expression, 'this')}) STORED\"\n\n        def isascii_sql(self, expression: exp.IsAscii) -> str:\n            return f\"({self.sql(expression.this)} ~ '^[[:ascii:]]*$')\"\n\n        def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:\n            # https://www.postgresql.org/docs/current/functions-window.html\n            self.unsupported(\"PostgreSQL does not support IGNORE NULLS.\")\n            return self.sql(expression.this)\n\n        def respectnulls_sql(self, expression: exp.RespectNulls) -> str:\n            # https://www.postgresql.org/docs/current/functions-window.html\n            self.unsupported(\"PostgreSQL does not support RESPECT NULLS.\")\n            return self.sql(expression.this)\n\n        @unsupported_args(\"this\")\n        def currentschema_sql(self, expression: exp.CurrentSchema) -> str:\n            return \"CURRENT_SCHEMA\"\n\n        def interval_sql(self, expression: exp.Interval) -> str:\n            unit = expression.text(\"unit\").lower()\n\n            this = expression.this\n            if unit.startswith(\"quarter\") and isinstance(this, exp.Literal):\n                this.replace(exp.Literal.string(int(this.to_py()) * 3))\n                expression.args[\"unit\"].replace(exp.var(\"MONTH\"))\n\n            return super().interval_sql(expression)\n\n        def placeholder_sql(self, expression: exp.Placeholder) -> str:\n            if expression.args.get(\"jdbc\"):\n                return \"?\"\n\n            this = f\"({expression.name})\" if expression.this else \"\"\n            return f\"{self.NAMED_PLACEHOLDER_TOKEN}{this}s\"\n\n        def arraycontains_sql(self, expression: exp.ArrayContains) -> str:\n            # Convert DuckDB's LIST_CONTAINS(array, value) to PostgreSQL\n            # DuckDB behavior:\n            #   - LIST_CONTAINS([1,2,3], 2) -> true\n            #   - LIST_CONTAINS([1,2,3], 4) -> false\n            #   - LIST_CONTAINS([1,2,NULL], 4) -> false (not NULL)\n            #   - LIST_CONTAINS([1,2,3], NULL) -> NULL\n            #\n            # PostgreSQL equivalent: CASE WHEN value IS NULL THEN NULL\n            #                            ELSE COALESCE(value = ANY(array), FALSE) END\n            value = expression.expression\n            array = expression.this\n\n            coalesce_expr = exp.Coalesce(\n                this=value.eq(exp.Any(this=exp.paren(expression=array, copy=False))),\n                expressions=[exp.false()],\n            )\n\n            case_expr = (\n                exp.Case()\n                .when(exp.Is(this=value, expression=exp.null()), exp.null(), copy=False)\n                .else_(coalesce_expr, copy=False)\n            )\n\n            return self.sql(case_expr)\n"
  },
  {
    "path": "sqlglot/dialects/presto.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    NormalizationStrategy,\n    bool_xor_sql,\n    bracket_to_element_at_sql,\n    datestrtodate_sql,\n    encode_decode_sql,\n    if_sql,\n    left_to_substring_sql,\n    no_ilike_sql,\n    no_pivot_sql,\n    no_timestamp_sql,\n    regexp_extract_sql,\n    rename_func,\n    right_to_substring_sql,\n    sha256_sql,\n    strposition_sql,\n    struct_extract_sql,\n    timestamptrunc_sql,\n    timestrtotime_sql,\n    ts_or_ds_add_cast,\n    unit_to_str,\n    sequence_sql,\n    explode_to_unnest_sql,\n    sha2_digest_sql,\n)\nfrom sqlglot.dialects.hive import Hive\nfrom sqlglot.dialects.mysql import MySQL\nfrom sqlglot.optimizer.scope import find_all_in_scope\nfrom sqlglot.parsers.presto import PrestoParser\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.transforms import unqualify_columns\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.typing.presto import EXPRESSION_METADATA\n\nDATE_ADD_OR_SUB = t.Union[exp.DateAdd, exp.TimestampAdd, exp.DateSub]\n\n\ndef _initcap_sql(self: Presto.Generator, expression: exp.Initcap) -> str:\n    delimiters = expression.expression\n    if delimiters and not (\n        delimiters.is_string and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS\n    ):\n        self.unsupported(\"INITCAP does not support custom delimiters\")\n\n    regex = r\"(\\w)(\\w*)\"\n    return f\"REGEXP_REPLACE({self.sql(expression, 'this')}, '{regex}', x -> UPPER(x[1]) || LOWER(x[2]))\"\n\n\ndef _no_sort_array(self: Presto.Generator, expression: exp.SortArray) -> str:\n    if expression.args.get(\"asc\") == exp.false():\n        comparator = \"(a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END\"\n    else:\n        comparator = None\n    return self.func(\"ARRAY_SORT\", expression.this, comparator)\n\n\ndef _schema_sql(self: Presto.Generator, expression: exp.Schema) -> str:\n    if isinstance(expression.parent, exp.PartitionedByProperty):\n        # Any columns in the ARRAY[] string literals should not be quoted\n        expression.transform(lambda n: n.name if isinstance(n, exp.Identifier) else n, copy=False)\n\n        partition_exprs = [\n            self.sql(c) if isinstance(c, (exp.Func, exp.Property)) else self.sql(c, \"this\")\n            for c in expression.expressions\n        ]\n        return self.sql(exp.Array(expressions=[exp.Literal.string(c) for c in partition_exprs]))\n\n    if expression.parent:\n        for schema in expression.parent.find_all(exp.Schema):\n            if schema is expression:\n                continue\n\n            column_defs = schema.find_all(exp.ColumnDef)\n            if column_defs and isinstance(schema.parent, exp.Property):\n                expression.expressions.extend(column_defs)\n\n    return self.schema_sql(expression)\n\n\ndef _quantile_sql(self: Presto.Generator, expression: exp.Quantile) -> str:\n    self.unsupported(\"Presto does not support exact quantiles\")\n    return self.func(\"APPROX_PERCENTILE\", expression.this, expression.args.get(\"quantile\"))\n\n\ndef _str_to_time_sql(\n    self: Presto.Generator, expression: exp.StrToDate | exp.StrToTime | exp.TsOrDsToDate\n) -> str:\n    return self.func(\"DATE_PARSE\", expression.this, self.format_time(expression))\n\n\ndef _ts_or_ds_to_date_sql(self: Presto.Generator, expression: exp.TsOrDsToDate) -> str:\n    time_format = self.format_time(expression)\n    if time_format and time_format not in (Presto.TIME_FORMAT, Presto.DATE_FORMAT):\n        return self.sql(exp.cast(_str_to_time_sql(self, expression), exp.DType.DATE))\n    return self.sql(exp.cast(exp.cast(expression.this, exp.DType.TIMESTAMP), exp.DType.DATE))\n\n\ndef _ts_or_ds_add_sql(self: Presto.Generator, expression: exp.TsOrDsAdd) -> str:\n    expression = ts_or_ds_add_cast(expression)\n    unit = unit_to_str(expression)\n    return self.func(\"DATE_ADD\", unit, expression.expression, expression.this)\n\n\ndef _ts_or_ds_diff_sql(self: Presto.Generator, expression: exp.TsOrDsDiff) -> str:\n    this = exp.cast(expression.this, exp.DType.TIMESTAMP)\n    expr = exp.cast(expression.expression, exp.DType.TIMESTAMP)\n    unit = unit_to_str(expression)\n    return self.func(\"DATE_DIFF\", unit, expr, this)\n\n\ndef _first_last_sql(self: Presto.Generator, expression: exp.Func) -> str:\n    \"\"\"\n    Trino doesn't support FIRST / LAST as functions, but they're valid in the context\n    of MATCH_RECOGNIZE, so we need to preserve them in that case. In all other cases\n    they're converted into an ARBITRARY call.\n\n    Reference: https://trino.io/docs/current/sql/match-recognize.html#logical-navigation-functions\n    \"\"\"\n    if isinstance(expression.find_ancestor(exp.MatchRecognize, exp.Select), exp.MatchRecognize):\n        return self.function_fallback_sql(expression)\n\n    return rename_func(\"ARBITRARY\")(self, expression)\n\n\ndef _unix_to_time_sql(self: Presto.Generator, expression: exp.UnixToTime) -> str:\n    scale = expression.args.get(\"scale\")\n    timestamp = self.sql(expression, \"this\")\n    if scale in (None, exp.UnixToTime.SECONDS):\n        return rename_func(\"FROM_UNIXTIME\")(self, expression)\n\n    return f\"FROM_UNIXTIME(CAST({timestamp} AS DOUBLE) / POW(10, {scale}))\"\n\n\ndef _to_int(self: Presto.Generator, expression: exp.Expr) -> exp.Expr:\n    if not expression.type:\n        from sqlglot.optimizer.annotate_types import annotate_types\n\n        annotate_types(expression, dialect=self.dialect)\n    if expression.type and expression.type.this not in exp.DataType.INTEGER_TYPES:\n        return exp.cast(expression, to=exp.DType.BIGINT)\n    return expression\n\n\ndef _date_delta_sql(\n    name: str, negate_interval: bool = False\n) -> t.Callable[[Presto.Generator, DATE_ADD_OR_SUB], str]:\n    def _delta_sql(self: Presto.Generator, expression: DATE_ADD_OR_SUB) -> str:\n        interval = _to_int(self, expression.expression)\n        return self.func(\n            name,\n            unit_to_str(expression),\n            interval * (-1) if negate_interval else interval,\n            expression.this,\n        )\n\n    return _delta_sql\n\n\ndef _explode_to_unnest_sql(self: Presto.Generator, expression: exp.Lateral) -> str:\n    explode = expression.this\n    if isinstance(explode, exp.Explode):\n        exploded_type = explode.this.type\n        alias = expression.args.get(\"alias\")\n\n        # This attempts a best-effort transpilation of LATERAL VIEW EXPLODE on a struct array\n        if (\n            isinstance(alias, exp.TableAlias)\n            and isinstance(exploded_type, exp.DataType)\n            and exploded_type.is_type(exp.DType.ARRAY)\n            and exploded_type.expressions\n            and exploded_type.expressions[0].is_type(exp.DType.STRUCT)\n        ):\n            # When unnesting a ROW in Presto, it produces N columns, so we need to fix the alias\n            alias.set(\"columns\", [c.this.copy() for c in exploded_type.expressions[0].expressions])\n    elif isinstance(explode, exp.Inline):\n        explode.replace(exp.Explode(this=explode.this.copy()))\n\n    return explode_to_unnest_sql(self, expression)\n\n\ndef amend_exploded_column_table(expression: exp.Expr) -> exp.Expr:\n    # We check for expression.type because the columns can be amended only if types were inferred\n    if isinstance(expression, exp.Select) and expression.type:\n        for lateral in expression.args.get(\"laterals\") or []:\n            alias = lateral.args.get(\"alias\")\n            if (\n                not isinstance(lateral.this, exp.Explode)\n                or not isinstance(alias, exp.TableAlias)\n                or len(alias.columns) != 1\n            ):\n                continue\n\n            new_table = alias.this\n            old_table = alias.columns[0].name.lower()\n\n            # When transpiling a LATERAL VIEW EXPLODE Spark query, the exploded fields may be qualified\n            # with the struct column, resulting in invalid Presto references that need to be amended\n            for column in find_all_in_scope(expression, exp.Column):\n                if column.db.lower() == old_table:\n                    column.set(\"table\", column.args[\"db\"].pop())\n                elif column.table.lower() == old_table:\n                    column.set(\"table\", new_table.copy())\n                elif column.name.lower() == old_table and isinstance(column.parent, exp.Dot):\n                    column.parent.replace(exp.column(column.parent.expression, table=new_table))\n\n    return expression\n\n\nclass Presto(Dialect):\n    INDEX_OFFSET = 1\n    NULL_ORDERING = \"nulls_are_last\"\n    TIME_FORMAT = MySQL.TIME_FORMAT\n    STRICT_STRING_CONCAT = True\n    TYPED_DIVISION = True\n    TABLESAMPLE_SIZE_IS_PERCENT = True\n    LOG_BASE_FIRST: t.Optional[bool] = None\n    SUPPORTS_VALUES_DEFAULT = False\n    LEAST_GREATEST_IGNORES_NULLS = False\n\n    TIME_MAPPING = MySQL.TIME_MAPPING\n\n    # https://github.com/trinodb/trino/issues/17\n    # https://github.com/trinodb/trino/issues/12289\n    # https://github.com/prestodb/presto/issues/2863\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    SUPPORTED_SETTINGS = {\n        *Dialect.SUPPORTED_SETTINGS,\n        \"variant_extract_is_json_extract\",\n    }\n\n    class Tokenizer(tokens.Tokenizer):\n        HEX_STRINGS = [(\"x'\", \"'\"), (\"X'\", \"'\")]\n        UNICODE_STRINGS = [\n            (prefix + q, q)\n            for q in t.cast(t.List[str], tokens.Tokenizer.QUOTES)\n            for prefix in (\"U&\", \"u&\")\n        ]\n\n        NESTED_COMMENTS = False\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"DEALLOCATE PREPARE\": TokenType.COMMAND,\n            \"DESCRIBE INPUT\": TokenType.COMMAND,\n            \"DESCRIBE OUTPUT\": TokenType.COMMAND,\n            \"RESET SESSION\": TokenType.COMMAND,\n            \"START\": TokenType.BEGIN,\n            \"MATCH_RECOGNIZE\": TokenType.MATCH_RECOGNIZE,\n            \"ROW\": TokenType.STRUCT,\n            \"IPADDRESS\": TokenType.IPADDRESS,\n            \"IPPREFIX\": TokenType.IPPREFIX,\n            \"TDIGEST\": TokenType.TDIGEST,\n            \"HYPERLOGLOG\": TokenType.HLLSKETCH,\n        }\n        KEYWORDS.pop(\"/*+\")\n        KEYWORDS.pop(\"QUALIFY\")\n\n    Parser = PrestoParser\n\n    class Generator(generator.Generator):\n        INTERVAL_ALLOWS_PLURAL_FORM = False\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n        IS_BOOL_ALLOWED = False\n        TZ_TO_WITH_TIME_ZONE = True\n        NVL2_SUPPORTED = False\n        STRUCT_DELIMITER = (\"(\", \")\")\n        LIMIT_ONLY_LITERALS = True\n        SUPPORTS_SINGLE_ARG_CONCAT = False\n        LIKE_PROPERTY_INSIDE_SCHEMA = True\n        MULTI_ARG_DISTINCT = False\n        SUPPORTS_TO_NUMBER = False\n        HEX_FUNC = \"TO_HEX\"\n        PARSE_JSON_NAME = \"JSON_PARSE\"\n        PAD_FILL_PATTERN_IS_REQUIRED = True\n        EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False\n        SUPPORTS_MEDIAN = False\n        ARRAY_SIZE_NAME = \"CARDINALITY\"\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.LocationProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.BINARY: \"VARBINARY\",\n            exp.DType.BIT: \"BOOLEAN\",\n            exp.DType.DATETIME: \"TIMESTAMP\",\n            exp.DType.DATETIME64: \"TIMESTAMP\",\n            exp.DType.FLOAT: \"REAL\",\n            exp.DType.HLLSKETCH: \"HYPERLOGLOG\",\n            exp.DType.INT: \"INTEGER\",\n            exp.DType.STRUCT: \"ROW\",\n            exp.DType.TEXT: \"VARCHAR\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n            exp.DType.TIMESTAMPNTZ: \"TIMESTAMP\",\n            exp.DType.TIMETZ: \"TIME\",\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.AnyValue: rename_func(\"ARBITRARY\"),\n            exp.ApproxQuantile: lambda self, e: self.func(\n                \"APPROX_PERCENTILE\",\n                e.this,\n                e.args.get(\"weight\"),\n                e.args.get(\"quantile\"),\n                e.args.get(\"accuracy\"),\n            ),\n            exp.ArgMax: rename_func(\"MAX_BY\"),\n            exp.ArgMin: rename_func(\"MIN_BY\"),\n            exp.Array: transforms.preprocess(\n                [transforms.inherit_struct_field_names],\n                generator=lambda self, e: f\"ARRAY[{self.expressions(e, flat=True)}]\",\n            ),\n            exp.ArrayAny: rename_func(\"ANY_MATCH\"),\n            exp.ArrayConcat: rename_func(\"CONCAT\"),\n            exp.ArrayContains: rename_func(\"CONTAINS\"),\n            exp.ArrayToString: rename_func(\"ARRAY_JOIN\"),\n            exp.ArrayUniqueAgg: rename_func(\"SET_AGG\"),\n            exp.ArraySlice: rename_func(\"SLICE\"),\n            exp.AtTimeZone: rename_func(\"AT_TIMEZONE\"),\n            exp.BitwiseAnd: lambda self, e: self.func(\"BITWISE_AND\", e.this, e.expression),\n            exp.BitwiseLeftShift: lambda self, e: self.func(\n                \"BITWISE_ARITHMETIC_SHIFT_LEFT\", e.this, e.expression\n            ),\n            exp.BitwiseNot: lambda self, e: self.func(\"BITWISE_NOT\", e.this),\n            exp.BitwiseOr: lambda self, e: self.func(\"BITWISE_OR\", e.this, e.expression),\n            exp.BitwiseRightShift: lambda self, e: self.func(\n                \"BITWISE_ARITHMETIC_SHIFT_RIGHT\", e.this, e.expression\n            ),\n            exp.BitwiseXor: lambda self, e: self.func(\"BITWISE_XOR\", e.this, e.expression),\n            exp.Cast: transforms.preprocess([transforms.epoch_cast_to_ts]),\n            exp.CurrentTime: lambda *_: \"CURRENT_TIME\",\n            exp.CurrentTimestamp: lambda *_: \"CURRENT_TIMESTAMP\",\n            exp.CurrentUser: lambda *_: \"CURRENT_USER\",\n            exp.DateAdd: _date_delta_sql(\"DATE_ADD\"),\n            exp.DateDiff: lambda self, e: self.func(\n                \"DATE_DIFF\", unit_to_str(e), e.expression, e.this\n            ),\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.DateToDi: lambda self, e: (\n                f\"CAST(DATE_FORMAT({self.sql(e, 'this')}, {Presto.DATEINT_FORMAT}) AS INT)\"\n            ),\n            exp.DateSub: _date_delta_sql(\"DATE_ADD\", negate_interval=True),\n            exp.DayOfWeek: lambda self, e: f\"(({self.func('DAY_OF_WEEK', e.this)} % 7) + 1)\",\n            exp.DayOfWeekIso: rename_func(\"DAY_OF_WEEK\"),\n            exp.Decode: lambda self, e: encode_decode_sql(self, e, \"FROM_UTF8\"),\n            exp.DiToDate: lambda self, e: (\n                f\"CAST(DATE_PARSE(CAST({self.sql(e, 'this')} AS VARCHAR), {Presto.DATEINT_FORMAT}) AS DATE)\"\n            ),\n            exp.Encode: lambda self, e: encode_decode_sql(self, e, \"TO_UTF8\"),\n            exp.FileFormatProperty: lambda self, e: (\n                f\"format={self.sql(exp.Literal.string(e.name))}\"\n            ),\n            exp.First: _first_last_sql,\n            exp.FromTimeZone: lambda self, e: (\n                f\"WITH_TIMEZONE({self.sql(e, 'this')}, {self.sql(e, 'zone')}) AT TIME ZONE 'UTC'\"\n            ),\n            exp.GenerateSeries: sequence_sql,\n            exp.GenerateDateArray: sequence_sql,\n            exp.If: if_sql(),\n            exp.ILike: no_ilike_sql,\n            exp.Initcap: _initcap_sql,\n            exp.Last: _first_last_sql,\n            exp.LastDay: lambda self, e: self.func(\"LAST_DAY_OF_MONTH\", e.this),\n            exp.Lateral: _explode_to_unnest_sql,\n            exp.Left: left_to_substring_sql,\n            exp.Levenshtein: unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\", \"max_dist\")(\n                rename_func(\"LEVENSHTEIN_DISTANCE\")\n            ),\n            exp.LogicalAnd: rename_func(\"BOOL_AND\"),\n            exp.LogicalOr: rename_func(\"BOOL_OR\"),\n            exp.Pivot: no_pivot_sql,\n            exp.Quantile: _quantile_sql,\n            exp.RegexpExtract: regexp_extract_sql,\n            exp.RegexpExtractAll: regexp_extract_sql,\n            exp.Right: right_to_substring_sql,\n            exp.Schema: _schema_sql,\n            exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_window_clause,\n                    transforms.eliminate_qualify,\n                    transforms.eliminate_distinct_on,\n                    transforms.explode_projection_to_unnest(1),\n                    transforms.eliminate_semi_and_anti_joins,\n                    amend_exploded_column_table,\n                ]\n            ),\n            exp.SortArray: _no_sort_array,\n            exp.SqlSecurityProperty: lambda self, e: f\"SECURITY {self.sql(e.this)}\",\n            exp.StrPosition: lambda self, e: strposition_sql(self, e, supports_occurrence=True),\n            exp.StrToDate: lambda self, e: f\"CAST({_str_to_time_sql(self, e)} AS DATE)\",\n            exp.StrToMap: rename_func(\"SPLIT_TO_MAP\"),\n            exp.StrToTime: _str_to_time_sql,\n            exp.StructExtract: struct_extract_sql,\n            exp.Table: transforms.preprocess([transforms.unnest_generate_series]),\n            exp.Timestamp: no_timestamp_sql,\n            exp.TimestampAdd: _date_delta_sql(\"DATE_ADD\"),\n            exp.TimestampTrunc: timestamptrunc_sql(),\n            exp.TimeStrToDate: timestrtotime_sql,\n            exp.TimeStrToTime: timestrtotime_sql,\n            exp.TimeStrToUnix: lambda self, e: self.func(\n                \"TO_UNIXTIME\", self.func(\"DATE_PARSE\", e.this, Presto.TIME_FORMAT)\n            ),\n            exp.TimeToStr: lambda self, e: self.func(\"DATE_FORMAT\", e.this, self.format_time(e)),\n            exp.TimeToUnix: rename_func(\"TO_UNIXTIME\"),\n            exp.ToChar: lambda self, e: self.func(\"DATE_FORMAT\", e.this, self.format_time(e)),\n            exp.TryCast: transforms.preprocess([transforms.epoch_cast_to_ts]),\n            exp.TsOrDiToDi: lambda self, e: (\n                f\"CAST(SUBSTR(REPLACE(CAST({self.sql(e, 'this')} AS VARCHAR), '-', ''), 1, 8) AS INT)\"\n            ),\n            exp.TsOrDsAdd: _ts_or_ds_add_sql,\n            exp.TsOrDsDiff: _ts_or_ds_diff_sql,\n            exp.TsOrDsToDate: _ts_or_ds_to_date_sql,\n            exp.Unhex: rename_func(\"FROM_HEX\"),\n            exp.UnixToStr: lambda self, e: (\n                f\"DATE_FORMAT(FROM_UNIXTIME({self.sql(e, 'this')}), {self.format_time(e)})\"\n            ),\n            exp.UnixToTime: _unix_to_time_sql,\n            exp.UnixToTimeStr: lambda self, e: (\n                f\"CAST(FROM_UNIXTIME({self.sql(e, 'this')}) AS VARCHAR)\"\n            ),\n            exp.VariancePop: rename_func(\"VAR_POP\"),\n            exp.With: transforms.preprocess([transforms.add_recursive_cte_column_names]),\n            exp.WithinGroup: transforms.preprocess(\n                [transforms.remove_within_group_for_percentiles]\n            ),\n            # Note: Presto's TRUNCATE always returns DOUBLE, even with decimals=0, whereas\n            # most dialects return INT (SQLite also returns REAL, see sqlite.py). This creates\n            # a bidirectional transpilation gap: Presto→Other may change float division to int\n            # division, and vice versa. Modeling precisely would require exp.FloatTrunc or\n            # similar, deemed overengineering for this subtle semantic difference.\n            exp.Trunc: rename_func(\"TRUNCATE\"),\n            exp.Xor: bool_xor_sql,\n            exp.MD5Digest: rename_func(\"MD5\"),\n            exp.SHA: rename_func(\"SHA1\"),\n            exp.SHA1Digest: rename_func(\"SHA1\"),\n            exp.SHA2: sha256_sql,\n            exp.SHA2Digest: sha2_digest_sql,\n        }\n\n        RESERVED_KEYWORDS = {\n            \"alter\",\n            \"and\",\n            \"as\",\n            \"between\",\n            \"by\",\n            \"case\",\n            \"cast\",\n            \"constraint\",\n            \"create\",\n            \"cross\",\n            \"current_time\",\n            \"current_timestamp\",\n            \"deallocate\",\n            \"delete\",\n            \"describe\",\n            \"distinct\",\n            \"drop\",\n            \"else\",\n            \"end\",\n            \"escape\",\n            \"except\",\n            \"execute\",\n            \"exists\",\n            \"extract\",\n            \"false\",\n            \"for\",\n            \"from\",\n            \"full\",\n            \"group\",\n            \"having\",\n            \"in\",\n            \"inner\",\n            \"insert\",\n            \"intersect\",\n            \"into\",\n            \"is\",\n            \"join\",\n            \"left\",\n            \"like\",\n            \"natural\",\n            \"not\",\n            \"null\",\n            \"on\",\n            \"or\",\n            \"order\",\n            \"outer\",\n            \"prepare\",\n            \"right\",\n            \"select\",\n            \"table\",\n            \"then\",\n            \"true\",\n            \"union\",\n            \"using\",\n            \"values\",\n            \"when\",\n            \"where\",\n            \"with\",\n        }\n\n        def extract_sql(self, expression: exp.Extract) -> str:\n            date_part = expression.name\n\n            if not date_part.startswith(\"EPOCH\"):\n                return super().extract_sql(expression)\n\n            if date_part == \"EPOCH_MILLISECOND\":\n                scale = 10**3\n            elif date_part == \"EPOCH_MICROSECOND\":\n                scale = 10**6\n            elif date_part == \"EPOCH_NANOSECOND\":\n                scale = 10**9\n            else:\n                scale = None\n\n            value = expression.expression\n\n            ts = exp.cast(value, to=exp.DataType.build(\"TIMESTAMP\"))\n            to_unix: exp.Expr = exp.TimeToUnix(this=ts)\n\n            if scale:\n                to_unix = exp.Mul(this=to_unix, expression=exp.Literal.number(scale))\n\n            return self.sql(to_unix)\n\n        def jsonformat_sql(self, expression: exp.JSONFormat) -> str:\n            this = expression.this\n            is_json = expression.args.get(\"is_json\")\n\n            if this and not (is_json or this.type):\n                from sqlglot.optimizer.annotate_types import annotate_types\n\n                this = annotate_types(this, dialect=self.dialect)\n\n            if not (is_json or this.is_type(exp.DType.JSON)):\n                this.replace(exp.cast(this, exp.DType.JSON))\n\n            return self.function_fallback_sql(expression)\n\n        def md5_sql(self, expression: exp.MD5) -> str:\n            this = expression.this\n\n            if not this.type:\n                from sqlglot.optimizer.annotate_types import annotate_types\n\n                this = annotate_types(this, dialect=self.dialect)\n\n            if this.is_type(*exp.DataType.TEXT_TYPES):\n                this = exp.Encode(this=this, charset=exp.Literal.string(\"utf-8\"))\n\n            return self.func(\"LOWER\", self.func(\"TO_HEX\", self.func(\"MD5\", self.sql(this))))\n\n        def strtounix_sql(self, expression: exp.StrToUnix) -> str:\n            # Since `TO_UNIXTIME` requires a `TIMESTAMP`, we need to parse the argument into one.\n            # To do this, we first try to `DATE_PARSE` it, but since this can fail when there's a\n            # timezone involved, we wrap it in a `TRY` call and use `PARSE_DATETIME` as a fallback,\n            # which seems to be using the same time mapping as Hive, as per:\n            # https://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html\n            this = expression.this\n            value_as_text = exp.cast(this, exp.DType.TEXT)\n            value_as_timestamp = exp.cast(this, exp.DType.TIMESTAMP) if this.is_string else this\n\n            parse_without_tz = self.func(\"DATE_PARSE\", value_as_text, self.format_time(expression))\n\n            formatted_value = self.func(\n                \"DATE_FORMAT\", value_as_timestamp, self.format_time(expression)\n            )\n            parse_with_tz = self.func(\n                \"PARSE_DATETIME\",\n                formatted_value,\n                self.format_time(expression, Hive.INVERSE_TIME_MAPPING, Hive.INVERSE_TIME_TRIE),\n            )\n            coalesced = self.func(\"COALESCE\", self.func(\"TRY\", parse_without_tz), parse_with_tz)\n            return self.func(\"TO_UNIXTIME\", coalesced)\n\n        def bracket_sql(self, expression: exp.Bracket) -> str:\n            if expression.args.get(\"safe\"):\n                return bracket_to_element_at_sql(self, expression)\n            return super().bracket_sql(expression)\n\n        def struct_sql(self, expression: exp.Struct) -> str:\n            if not expression.type:\n                from sqlglot.optimizer.annotate_types import annotate_types\n\n                annotate_types(expression, dialect=self.dialect)\n\n            values: t.List[str] = []\n            schema: t.List[str] = []\n            unknown_type = False\n\n            for e in expression.expressions:\n                if isinstance(e, exp.PropertyEQ):\n                    if e.type and e.type.is_type(exp.DType.UNKNOWN):\n                        unknown_type = True\n                    else:\n                        schema.append(f\"{self.sql(e, 'this')} {self.sql(e.type)}\")\n                    values.append(self.sql(e, \"expression\"))\n                else:\n                    values.append(self.sql(e))\n\n            size = len(expression.expressions)\n\n            if not size or len(schema) != size:\n                if unknown_type:\n                    self.unsupported(\n                        \"Cannot convert untyped key-value definitions (try annotate_types).\"\n                    )\n                return self.func(\"ROW\", *values)\n            return f\"CAST(ROW({', '.join(values)}) AS ROW({', '.join(schema)}))\"\n\n        def interval_sql(self, expression: exp.Interval) -> str:\n            if expression.this and expression.text(\"unit\").upper().startswith(\"WEEK\"):\n                return f\"({expression.this.name} * INTERVAL '7' DAY)\"\n            return super().interval_sql(expression)\n\n        def transaction_sql(self, expression: exp.Transaction) -> str:\n            modes = expression.args.get(\"modes\")\n            modes = f\" {', '.join(modes)}\" if modes else \"\"\n            return f\"START TRANSACTION{modes}\"\n\n        def offset_limit_modifiers(\n            self, expression: exp.Expr, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]\n        ) -> t.List[str]:\n            return [\n                self.sql(expression, \"offset\"),\n                self.sql(limit),\n            ]\n\n        def create_sql(self, expression: exp.Create) -> str:\n            \"\"\"\n            Presto doesn't support CREATE VIEW with expressions (ex: `CREATE VIEW x (cola)` then `(cola)` is the expression),\n            so we need to remove them\n            \"\"\"\n            kind = expression.args[\"kind\"]\n            schema = expression.this\n            if kind == \"VIEW\" and schema.expressions:\n                expression.this.set(\"expressions\", None)\n            return super().create_sql(expression)\n\n        def delete_sql(self, expression: exp.Delete) -> str:\n            \"\"\"\n            Presto only supports DELETE FROM for a single table without an alias, so we need\n            to remove the unnecessary parts. If the original DELETE statement contains more\n            than one table to be deleted, we can't safely map it 1-1 to a Presto statement.\n            \"\"\"\n            tables = expression.args.get(\"tables\") or [expression.this]\n            if len(tables) > 1:\n                return super().delete_sql(expression)\n\n            table = tables[0]\n            expression.set(\"this\", table)\n            expression.set(\"tables\", None)\n\n            if isinstance(table, exp.Table):\n                table_alias = table.args.get(\"alias\")\n                if table_alias:\n                    table_alias.pop()\n                    expression = t.cast(exp.Delete, expression.transform(unqualify_columns))\n\n            return super().delete_sql(expression)\n\n        def jsonextract_sql(self, expression: exp.JSONExtract) -> str:\n            is_json_extract = self.dialect.settings.get(\"variant_extract_is_json_extract\", True)\n\n            # Generate JSON_EXTRACT unless the user has configured that a Snowflake / Databricks\n            # VARIANT extract (e.g. col:x.y) should map to dot notation (i.e ROW access) in Presto/Trino\n            if not expression.args.get(\"variant_extract\") or is_json_extract:\n                return self.func(\n                    \"JSON_EXTRACT\", expression.this, expression.expression, *expression.expressions\n                )\n\n            this = self.sql(expression, \"this\")\n\n            # Convert the JSONPath extraction `JSON_EXTRACT(col, '$.x.y) to a ROW access col.x.y\n            segments = []\n            for path_key in expression.expression.expressions[1:]:\n                if not isinstance(path_key, exp.JSONPathKey):\n                    # Cannot transpile subscripts, wildcards etc to dot notation\n                    self.unsupported(\n                        f\"Cannot transpile JSONPath segment '{path_key}' to ROW access\"\n                    )\n                    continue\n                key = path_key.this\n                if not exp.SAFE_IDENTIFIER_RE.match(key):\n                    key = f'\"{key}\"'\n                segments.append(f\".{key}\")\n\n            expr = \"\".join(segments)\n\n            return f\"{this}{expr}\"\n\n        def groupconcat_sql(self, expression: exp.GroupConcat) -> str:\n            return self.func(\n                \"ARRAY_JOIN\",\n                self.func(\"ARRAY_AGG\", expression.this),\n                expression.args.get(\"separator\"),\n            )\n"
  },
  {
    "path": "sqlglot/dialects/prql.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import tokens\nfrom sqlglot.dialects.dialect import Dialect\nfrom sqlglot.parsers.prql import PRQLParser\nfrom sqlglot.tokens import TokenType\n\n\nclass PRQL(Dialect):\n    DPIPE_IS_STRING_CONCAT = False\n\n    class Tokenizer(tokens.Tokenizer):\n        IDENTIFIERS = [\"`\"]\n        QUOTES = [\"'\", '\"']\n\n        SINGLE_TOKENS = {\n            **tokens.Tokenizer.SINGLE_TOKENS,\n            \"=\": TokenType.ALIAS,\n            \"'\": TokenType.QUOTE,\n            '\"': TokenType.QUOTE,\n            \"`\": TokenType.IDENTIFIER,\n            \"#\": TokenType.COMMENT,\n        }\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n        }\n\n    Parser = PRQLParser\n"
  },
  {
    "path": "sqlglot/dialects/redshift.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, transforms\nfrom sqlglot.typing.redshift import EXPRESSION_METADATA\nfrom sqlglot.dialects.dialect import (\n    NormalizationStrategy,\n    array_concat_sql,\n    concat_to_dpipe_sql,\n    concat_ws_to_dpipe_sql,\n    date_delta_sql,\n    generatedasidentitycolumnconstraint_sql,\n    json_extract_segments,\n    no_tablesample_sql,\n    rename_func,\n)\nfrom sqlglot.dialects.postgres import Postgres\nfrom sqlglot.generator import Generator\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.redshift import RedshiftParser\nfrom sqlglot.tokens import TokenType\n\n\nclass Redshift(Postgres):\n    # https://docs.aws.amazon.com/redshift/latest/dg/r_names.html\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n    SUPPORTS_USER_DEFINED_TYPES = False\n    INDEX_OFFSET = 0\n    COPY_PARAMS_ARE_CSV = False\n    HEX_LOWERCASE = True\n    HAS_DISTINCT_ARRAY_CONSTRUCTORS = True\n    COALESCE_COMPARISON_NON_STANDARD = True\n    REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL = False\n    ARRAY_FUNCS_PROPAGATES_NULLS = True\n\n    # ref: https://docs.aws.amazon.com/redshift/latest/dg/r_FORMAT_strings.html\n    TIME_FORMAT = \"'YYYY-MM-DD HH24:MI:SS'\"\n    TIME_MAPPING = {**Postgres.TIME_MAPPING, \"MON\": \"%b\", \"HH24\": \"%H\", \"HH\": \"%I\"}\n\n    Parser = RedshiftParser\n\n    class Tokenizer(Postgres.Tokenizer):\n        BIT_STRINGS = []\n        HEX_STRINGS = []\n        STRING_ESCAPES = [\"\\\\\", \"'\"]\n\n        KEYWORDS = {\n            **Postgres.Tokenizer.KEYWORDS,\n            \"(+)\": TokenType.JOIN_MARKER,\n            \"HLLSKETCH\": TokenType.HLLSKETCH,\n            \"MINUS\": TokenType.EXCEPT,\n            \"SUPER\": TokenType.SUPER,\n            \"TOP\": TokenType.TOP,\n            \"UNLOAD\": TokenType.COMMAND,\n            \"VARBYTE\": TokenType.VARBINARY,\n            \"BINARY VARYING\": TokenType.VARBINARY,\n        }\n        KEYWORDS.pop(\"VALUES\")\n\n        # Redshift allows # to appear as a table identifier prefix\n        SINGLE_TOKENS = Postgres.Tokenizer.SINGLE_TOKENS.copy()\n        SINGLE_TOKENS.pop(\"#\")\n\n    class Generator(Postgres.Generator):\n        LOCKING_READS_SUPPORTED = False\n        QUERY_HINTS = False\n        VALUES_AS_TABLE = False\n        TZ_TO_WITH_TIME_ZONE = True\n        NVL2_SUPPORTED = True\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        CAN_IMPLEMENT_ARRAY_ANY = False\n        MULTI_ARG_DISTINCT = True\n        COPY_PARAMS_ARE_WRAPPED = False\n        HEX_FUNC = \"TO_HEX\"\n        PARSE_JSON_NAME = \"JSON_PARSE\"\n        ARRAY_CONCAT_IS_VAR_LEN = False\n        SUPPORTS_CONVERT_TIMEZONE = True\n        EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False\n        SUPPORTS_MEDIAN = True\n        ALTER_SET_TYPE = \"TYPE\"\n        SUPPORTS_DECODE_CASE = True\n        SUPPORTS_BETWEEN_FLAGS = False\n        LIMIT_FETCH = \"LIMIT\"\n        STAR_EXCEPT = \"EXCLUDE\"\n        STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = False\n\n        # Redshift doesn't have `WITH` as part of their with_properties so we remove it\n        WITH_PROPERTIES_PREFIX = \" \"\n\n        TYPE_MAPPING = {\n            **Postgres.Generator.TYPE_MAPPING,\n            exp.DType.BINARY: \"VARBYTE\",\n            exp.DType.BLOB: \"VARBYTE\",\n            exp.DType.INT: \"INTEGER\",\n            exp.DType.TIMETZ: \"TIME\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n            exp.DType.VARBINARY: \"VARBYTE\",\n            exp.DType.ROWVERSION: \"VARBYTE\",\n        }\n\n        TRANSFORMS = {\n            **Postgres.Generator.TRANSFORMS,\n            exp.ArrayConcat: array_concat_sql(\"ARRAY_CONCAT\"),\n            exp.Concat: concat_to_dpipe_sql,\n            exp.ConcatWs: concat_ws_to_dpipe_sql,\n            exp.ApproxDistinct: lambda self, e: (\n                f\"APPROXIMATE COUNT(DISTINCT {self.sql(e, 'this')})\"\n            ),\n            exp.CurrentTimestamp: lambda self, e: (\n                \"SYSDATE\" if e.args.get(\"sysdate\") else \"GETDATE()\"\n            ),\n            exp.DateAdd: date_delta_sql(\"DATEADD\"),\n            exp.DateDiff: date_delta_sql(\"DATEDIFF\"),\n            exp.DistKeyProperty: lambda self, e: self.func(\"DISTKEY\", e.this),\n            exp.DistStyleProperty: lambda self, e: self.naked_property(e),\n            exp.Explode: lambda self, e: self.explode_sql(e),\n            exp.FarmFingerprint: rename_func(\"FARMFINGERPRINT64\"),\n            exp.FromBase: rename_func(\"STRTOL\"),\n            exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql,\n            exp.JSONExtract: json_extract_segments(\"JSON_EXTRACT_PATH_TEXT\"),\n            exp.JSONExtractScalar: json_extract_segments(\"JSON_EXTRACT_PATH_TEXT\"),\n            exp.GroupConcat: rename_func(\"LISTAGG\"),\n            exp.Hex: lambda self, e: self.func(\"UPPER\", self.func(\"TO_HEX\", self.sql(e, \"this\"))),\n            exp.RegexpExtract: rename_func(\"REGEXP_SUBSTR\"),\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_window_clause,\n                    transforms.eliminate_distinct_on,\n                    transforms.eliminate_semi_and_anti_joins,\n                    transforms.unqualify_unnest,\n                    transforms.unnest_generate_date_array_using_recursive_cte,\n                ]\n            ),\n            exp.SortKeyProperty: lambda self, e: (\n                f\"{'COMPOUND ' if e.args['compound'] else ''}SORTKEY({self.format_args(*e.this)})\"\n            ),\n            exp.StartsWith: lambda self, e: (\n                f\"{self.sql(e.this)} LIKE {self.sql(e.expression)} || '%'\"\n            ),\n            exp.StringToArray: rename_func(\"SPLIT_TO_ARRAY\"),\n            exp.TableSample: no_tablesample_sql,\n            exp.TsOrDsAdd: date_delta_sql(\"DATEADD\"),\n            exp.TsOrDsDiff: date_delta_sql(\"DATEDIFF\"),\n            exp.UnixToTime: lambda self, e: self._unix_to_time_sql(e),\n            exp.SHA2Digest: lambda self, e: self.func(\n                \"SHA2\", e.this, e.args.get(\"length\") or exp.Literal.number(256)\n            ),\n        }\n\n        # Postgres maps exp.Pivot to no_pivot_sql, but Redshift support pivots\n        TRANSFORMS.pop(exp.Pivot)\n\n        # Postgres doesn't support JSON_PARSE, but Redshift does\n        TRANSFORMS.pop(exp.ParseJSON)\n\n        # Redshift supports these functions\n        TRANSFORMS.pop(exp.AnyValue)\n        TRANSFORMS.pop(exp.LastDay)\n        TRANSFORMS.pop(exp.SHA2)\n\n        # Postgres and Redshift have different semantics for Getbit\n        TRANSFORMS.pop(exp.Getbit)\n\n        # Postgres does not permit a double precision argument in ROUND; Redshift does\n        TRANSFORMS.pop(exp.Round)\n\n        RESERVED_KEYWORDS = {\n            \"aes128\",\n            \"aes256\",\n            \"all\",\n            \"allowoverwrite\",\n            \"analyse\",\n            \"analyze\",\n            \"and\",\n            \"any\",\n            \"array\",\n            \"as\",\n            \"asc\",\n            \"authorization\",\n            \"az64\",\n            \"backup\",\n            \"between\",\n            \"binary\",\n            \"blanksasnull\",\n            \"both\",\n            \"bytedict\",\n            \"bzip2\",\n            \"case\",\n            \"cast\",\n            \"check\",\n            \"collate\",\n            \"column\",\n            \"constraint\",\n            \"create\",\n            \"credentials\",\n            \"cross\",\n            \"current_date\",\n            \"current_time\",\n            \"current_timestamp\",\n            \"current_user\",\n            \"current_user_id\",\n            \"default\",\n            \"deferrable\",\n            \"deflate\",\n            \"defrag\",\n            \"delta\",\n            \"delta32k\",\n            \"desc\",\n            \"disable\",\n            \"distinct\",\n            \"do\",\n            \"else\",\n            \"emptyasnull\",\n            \"enable\",\n            \"encode\",\n            \"encrypt     \",\n            \"encryption\",\n            \"end\",\n            \"except\",\n            \"explicit\",\n            \"false\",\n            \"for\",\n            \"foreign\",\n            \"freeze\",\n            \"from\",\n            \"full\",\n            \"globaldict256\",\n            \"globaldict64k\",\n            \"grant\",\n            \"group\",\n            \"gzip\",\n            \"having\",\n            \"identity\",\n            \"ignore\",\n            \"ilike\",\n            \"in\",\n            \"initially\",\n            \"inner\",\n            \"intersect\",\n            \"interval\",\n            \"into\",\n            \"is\",\n            \"isnull\",\n            \"join\",\n            \"leading\",\n            \"left\",\n            \"like\",\n            \"limit\",\n            \"localtime\",\n            \"localtimestamp\",\n            \"lun\",\n            \"luns\",\n            \"lzo\",\n            \"lzop\",\n            \"minus\",\n            \"mostly16\",\n            \"mostly32\",\n            \"mostly8\",\n            \"natural\",\n            \"new\",\n            \"not\",\n            \"notnull\",\n            \"null\",\n            \"nulls\",\n            \"off\",\n            \"offline\",\n            \"offset\",\n            \"oid\",\n            \"old\",\n            \"on\",\n            \"only\",\n            \"open\",\n            \"or\",\n            \"order\",\n            \"outer\",\n            \"overlaps\",\n            \"parallel\",\n            \"partition\",\n            \"percent\",\n            \"permissions\",\n            \"pivot\",\n            \"placing\",\n            \"primary\",\n            \"raw\",\n            \"readratio\",\n            \"recover\",\n            \"references\",\n            \"rejectlog\",\n            \"resort\",\n            \"respect\",\n            \"restore\",\n            \"right\",\n            \"select\",\n            \"session_user\",\n            \"similar\",\n            \"snapshot\",\n            \"some\",\n            \"sysdate\",\n            \"system\",\n            \"table\",\n            \"tag\",\n            \"tdes\",\n            \"text255\",\n            \"text32k\",\n            \"then\",\n            \"timestamp\",\n            \"to\",\n            \"top\",\n            \"trailing\",\n            \"true\",\n            \"truncatecolumns\",\n            \"type\",\n            \"union\",\n            \"unique\",\n            \"unnest\",\n            \"unpivot\",\n            \"user\",\n            \"using\",\n            \"verbose\",\n            \"wallet\",\n            \"when\",\n            \"where\",\n            \"with\",\n            \"without\",\n        }\n\n        def unnest_sql(self, expression: exp.Unnest) -> str:\n            args = expression.expressions\n            num_args = len(args)\n\n            if num_args != 1:\n                self.unsupported(f\"Unsupported number of arguments in UNNEST: {num_args}\")\n                return \"\"\n\n            if isinstance(expression.find_ancestor(exp.From, exp.Join, exp.Select), exp.Select):\n                self.unsupported(\"Unsupported UNNEST when not used in FROM/JOIN clauses\")\n                return \"\"\n\n            arg = self.sql(seq_get(args, 0))\n\n            alias = self.expressions(expression.args.get(\"alias\"), key=\"columns\", flat=True)\n            return f\"{arg} AS {alias}\" if alias else arg\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            if expression.is_type(exp.DType.JSON):\n                # Redshift doesn't support a JSON type, so casting to it is treated as a noop\n                return self.sql(expression, \"this\")\n\n            return super().cast_sql(expression, safe_prefix=safe_prefix)\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            \"\"\"\n            Redshift converts the `TEXT` data type to `VARCHAR(255)` by default when people more generally mean\n            VARCHAR of max length which is `VARCHAR(max)` in Redshift. Therefore if we get a `TEXT` data type\n            without precision we convert it to `VARCHAR(max)` and if it does have precision then we just convert\n            `TEXT` to `VARCHAR`.\n            \"\"\"\n            if expression.is_type(\"text\"):\n                expression.set(\"this\", exp.DType.VARCHAR)\n                precision = expression.args.get(\"expressions\")\n\n                if not precision:\n                    expression.append(\"expressions\", exp.var(\"MAX\"))\n\n            return super().datatype_sql(expression)\n\n        def alterset_sql(self, expression: exp.AlterSet) -> str:\n            exprs = self.expressions(expression, flat=True)\n            exprs = f\" TABLE PROPERTIES ({exprs})\" if exprs else \"\"\n            location = self.sql(expression, \"location\")\n            location = f\" LOCATION {location}\" if location else \"\"\n            file_format = self.expressions(expression, key=\"file_format\", flat=True, sep=\" \")\n            file_format = f\" FILE FORMAT {file_format}\" if file_format else \"\"\n\n            return f\"SET{exprs}{location}{file_format}\"\n\n        def array_sql(self, expression: exp.Array) -> str:\n            if expression.args.get(\"bracket_notation\"):\n                return super().array_sql(expression)\n\n            return rename_func(\"ARRAY\")(self, expression)\n\n        def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:\n            return Generator.ignorenulls_sql(self, expression)\n\n        def respectnulls_sql(self, expression: exp.RespectNulls) -> str:\n            return Generator.respectnulls_sql(self, expression)\n\n        def explode_sql(self, expression: exp.Explode) -> str:\n            self.unsupported(\"Unsupported EXPLODE() function\")\n            return \"\"\n\n        def _unix_to_time_sql(self, expression: exp.UnixToTime) -> str:\n            scale = expression.args.get(\"scale\")\n            this = self.sql(expression.this)\n\n            if scale is not None and scale != exp.UnixToTime.SECONDS and scale.is_int:\n                this = f\"({this} / POWER(10, {scale.to_py()}))\"\n\n            return f\"(TIMESTAMP 'epoch' + {this} * INTERVAL '1 SECOND')\"\n"
  },
  {
    "path": "sqlglot/dialects/risingwave.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.postgres import Postgres\nfrom sqlglot.generator import Generator\nfrom sqlglot.parsers.risingwave import RisingWaveParser\nfrom sqlglot.tokens import TokenType\n\n\nclass RisingWave(Postgres):\n    REQUIRES_PARENTHESIZED_STRUCT_ACCESS = True\n    SUPPORTS_STRUCT_STAR_EXPANSION = True\n\n    class Tokenizer(Postgres.Tokenizer):\n        KEYWORDS = {\n            **Postgres.Tokenizer.KEYWORDS,\n            \"SINK\": TokenType.SINK,\n            \"SOURCE\": TokenType.SOURCE,\n        }\n\n    Parser = RisingWaveParser\n\n    class Generator(Postgres.Generator):\n        LOCKING_READS_SUPPORTED = False\n        SUPPORTS_BETWEEN_FLAGS = False\n\n        TRANSFORMS = {\n            **Postgres.Generator.TRANSFORMS,\n            exp.FileFormatProperty: lambda self, e: f\"FORMAT {self.sql(e, 'this')}\",\n        }\n\n        PROPERTIES_LOCATION = {\n            **Postgres.Generator.PROPERTIES_LOCATION,\n            exp.FileFormatProperty: exp.Properties.Location.POST_EXPRESSION,\n        }\n\n        EXPRESSION_PRECEDES_PROPERTIES_CREATABLES = {\"SINK\"}\n\n        def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:\n            return Generator.computedcolumnconstraint_sql(self, expression)\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            if expression.is_type(exp.DType.MAP) and len(expression.expressions) == 2:\n                key_type, value_type = expression.expressions\n                return f\"MAP({self.sql(key_type)}, {self.sql(value_type)})\"\n\n            return super().datatype_sql(expression)\n"
  },
  {
    "path": "sqlglot/dialects/singlestore.py",
    "content": "import re\n\nfrom sqlglot import TokenType\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import (\n    json_extract_segments,\n    json_path_key_only_name,\n    rename_func,\n    bool_xor_sql,\n    count_if_to_sum,\n    timestamptrunc_sql,\n    date_add_interval_sql,\n    timestampdiff_sql,\n)\nfrom sqlglot.dialects.mysql import MySQL, _remove_ts_or_ds_to_date, date_add_sql\nfrom sqlglot.expressions import DataType\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.parsers.singlestore import SingleStoreParser, cast_to_time6\n\n\nclass SingleStore(MySQL):\n    SUPPORTS_ORDER_BY_ALL = True\n\n    TIME_MAPPING: t.Dict[str, str] = {\n        \"D\": \"%u\",  # Day of week (1-7)\n        \"DD\": \"%d\",  # day of month (01-31)\n        \"DY\": \"%a\",  # abbreviated name of day\n        \"HH\": \"%I\",  # Hour of day (01-12)\n        \"HH12\": \"%I\",  # alias for HH\n        \"HH24\": \"%H\",  # Hour of day (00-23)\n        \"MI\": \"%M\",  # Minute (00-59)\n        \"MM\": \"%m\",  # Month (01-12; January = 01)\n        \"MON\": \"%b\",  # Abbreviated name of month\n        \"MONTH\": \"%B\",  # Name of month\n        \"SS\": \"%S\",  # Second (00-59)\n        \"RR\": \"%y\",  # 15\n        \"YY\": \"%y\",  # 15\n        \"YYYY\": \"%Y\",  # 2015\n        \"FF6\": \"%f\",  # only 6 digits are supported in python formats\n    }\n\n    VECTOR_TYPE_ALIASES = {\n        \"I8\": \"TINYINT\",\n        \"I16\": \"SMALLINT\",\n        \"I32\": \"INT\",\n        \"I64\": \"BIGINT\",\n        \"F32\": \"FLOAT\",\n        \"F64\": \"DOUBLE\",\n    }\n\n    INVERSE_VECTOR_TYPE_ALIASES = {v: k for k, v in VECTOR_TYPE_ALIASES.items()}\n\n    class Tokenizer(MySQL.Tokenizer):\n        BYTE_STRINGS = [(\"e'\", \"'\"), (\"E'\", \"'\")]\n\n        KEYWORDS = {\n            **MySQL.Tokenizer.KEYWORDS,\n            \"BSON\": TokenType.JSONB,\n            \"GEOGRAPHYPOINT\": TokenType.GEOGRAPHYPOINT,\n            \"TIMESTAMP\": TokenType.TIMESTAMP,\n            \"UTC_DATE\": TokenType.UTC_DATE,\n            \"UTC_TIME\": TokenType.UTC_TIME,\n            \"UTC_TIMESTAMP\": TokenType.UTC_TIMESTAMP,\n            \":>\": TokenType.COLON_GT,\n            \"!:>\": TokenType.NCOLON_GT,\n            \"::$\": TokenType.DCOLONDOLLAR,\n            \"::%\": TokenType.DCOLONPERCENT,\n            \"::?\": TokenType.DCOLONQMARK,\n            \"RECORD\": TokenType.STRUCT,\n        }\n\n    Parser = SingleStoreParser\n\n    class Generator(MySQL.Generator):\n        SUPPORTS_UESCAPE = False\n        NULL_ORDERING_SUPPORTED = True\n        MATCH_AGAINST_TABLE_PREFIX = \"TABLE \"\n        STRUCT_DELIMITER = (\"(\", \")\")\n\n        @staticmethod\n        def _unicode_substitute(m: re.Match[str]) -> str:\n            # Interpret the number as hex and convert it to the Unicode string\n            return chr(int(m.group(1), 16))\n\n        UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = _unicode_substitute\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n        }\n\n        TRANSFORMS = {\n            **MySQL.Generator.TRANSFORMS,\n            exp.TsOrDsToDate: lambda self, e: (\n                self.func(\"TO_DATE\", e.this, self.format_time(e))\n                if e.args.get(\"format\")\n                else self.func(\"DATE\", e.this)\n            ),\n            exp.StrToTime: lambda self, e: self.func(\"TO_TIMESTAMP\", e.this, self.format_time(e)),\n            exp.ToChar: lambda self, e: self.func(\"TO_CHAR\", e.this, self.format_time(e)),\n            exp.StrToDate: lambda self, e: self.func(\n                \"STR_TO_DATE\",\n                e.this,\n                self.format_time(\n                    e,\n                    inverse_time_mapping=MySQL.INVERSE_TIME_MAPPING,\n                    inverse_time_trie=MySQL.INVERSE_TIME_TRIE,\n                ),\n            ),\n            exp.TimeToStr: lambda self, e: self.func(\n                \"DATE_FORMAT\",\n                e.this,\n                self.format_time(\n                    e,\n                    inverse_time_mapping=MySQL.INVERSE_TIME_MAPPING,\n                    inverse_time_trie=MySQL.INVERSE_TIME_TRIE,\n                ),\n            ),\n            exp.Date: unsupported_args(\"zone\", \"expressions\")(rename_func(\"DATE\")),\n            exp.Cast: unsupported_args(\"format\", \"action\", \"default\")(\n                lambda self, e: f\"{self.sql(e, 'this')} :> {self.sql(e, 'to')}\"\n            ),\n            exp.TryCast: unsupported_args(\"format\", \"action\", \"default\")(\n                lambda self, e: f\"{self.sql(e, 'this')} !:> {self.sql(e, 'to')}\"\n            ),\n            exp.CastToStrType: lambda self, e: self.sql(\n                exp.cast(e.this, DataType.build(e.args[\"to\"].name))\n            ),\n            exp.StrToUnix: unsupported_args(\"format\")(rename_func(\"UNIX_TIMESTAMP\")),\n            exp.TimeToUnix: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.TimeStrToUnix: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.UnixSeconds: rename_func(\"UNIX_TIMESTAMP\"),\n            exp.UnixToStr: lambda self, e: self.func(\n                \"FROM_UNIXTIME\",\n                e.this,\n                self.format_time(\n                    e,\n                    inverse_time_mapping=MySQL.INVERSE_TIME_MAPPING,\n                    inverse_time_trie=MySQL.INVERSE_TIME_TRIE,\n                ),\n            ),\n            exp.UnixToTime: unsupported_args(\"scale\", \"zone\", \"hours\", \"minutes\")(\n                lambda self, e: self.func(\n                    \"FROM_UNIXTIME\",\n                    e.this,\n                    self.format_time(\n                        e,\n                        inverse_time_mapping=MySQL.INVERSE_TIME_MAPPING,\n                        inverse_time_trie=MySQL.INVERSE_TIME_TRIE,\n                    ),\n                ),\n            ),\n            exp.UnixToTimeStr: lambda self, e: f\"FROM_UNIXTIME({self.sql(e, 'this')}) :> TEXT\",\n            exp.DateBin: unsupported_args(\"unit\", \"zone\")(\n                lambda self, e: self.func(\"TIME_BUCKET\", e.this, e.expression, e.args.get(\"origin\"))\n            ),\n            exp.TimeStrToDate: lambda self, e: self.sql(exp.cast(e.this, exp.DType.DATE)),\n            exp.FromTimeZone: lambda self, e: self.func(\n                \"CONVERT_TZ\", e.this, e.args.get(\"zone\"), \"'UTC'\"\n            ),\n            exp.DiToDate: lambda self, e: (\n                f\"STR_TO_DATE({self.sql(e, 'this')}, {SingleStore.DATEINT_FORMAT})\"\n            ),\n            exp.DateToDi: lambda self, e: (\n                f\"(DATE_FORMAT({self.sql(e, 'this')}, {SingleStore.DATEINT_FORMAT}) :> INT)\"\n            ),\n            exp.TsOrDiToDi: lambda self, e: (\n                f\"(DATE_FORMAT({self.sql(e, 'this')}, {SingleStore.DATEINT_FORMAT}) :> INT)\"\n            ),\n            exp.Time: unsupported_args(\"zone\")(lambda self, e: f\"{self.sql(e, 'this')} :> TIME\"),\n            exp.DatetimeAdd: _remove_ts_or_ds_to_date(date_add_sql(\"ADD\")),\n            exp.DatetimeTrunc: unsupported_args(\"zone\")(timestamptrunc_sql()),\n            exp.DatetimeSub: date_add_interval_sql(\"DATE\", \"SUB\"),\n            exp.DatetimeDiff: timestampdiff_sql,\n            exp.DateTrunc: unsupported_args(\"zone\")(timestamptrunc_sql()),\n            exp.DateDiff: unsupported_args(\"zone\")(\n                lambda self, e: (\n                    timestampdiff_sql(self, e)\n                    if e.unit is not None\n                    else self.func(\"DATEDIFF\", e.this, e.expression)\n                )\n            ),\n            exp.TsOrDsDiff: lambda self, e: (\n                timestampdiff_sql(self, e)\n                if e.unit is not None\n                else self.func(\"DATEDIFF\", e.this, e.expression)\n            ),\n            exp.TimestampTrunc: unsupported_args(\"zone\")(timestamptrunc_sql()),\n            exp.CurrentDatetime: lambda self, e: self.sql(\n                cast_to_time6(exp.CurrentTimestamp(this=exp.Literal.number(6)), exp.DType.DATETIME)\n            ),\n            exp.JSONExtract: unsupported_args(\n                \"only_json_types\",\n                \"expressions\",\n                \"variant_extract\",\n                \"json_query\",\n                \"option\",\n                \"quote\",\n                \"on_condition\",\n                \"requires_json\",\n            )(json_extract_segments(\"JSON_EXTRACT_JSON\")),\n            exp.JSONBExtract: json_extract_segments(\"BSON_EXTRACT_BSON\"),\n            exp.JSONPathKey: json_path_key_only_name,\n            exp.JSONPathSubscript: lambda self, e: self.json_path_part(e.this),\n            exp.JSONPathRoot: lambda *_: \"\",\n            exp.JSONFormat: unsupported_args(\"options\", \"is_json\")(rename_func(\"JSON_PRETTY\")),\n            exp.JSONArrayAgg: unsupported_args(\"null_handling\", \"return_type\", \"strict\")(\n                lambda self, e: self.func(\"JSON_AGG\", e.this, suffix=f\"{self.sql(e, 'order')})\")\n            ),\n            exp.JSONArray: unsupported_args(\"null_handling\", \"return_type\", \"strict\")(\n                rename_func(\"JSON_BUILD_ARRAY\")\n            ),\n            exp.JSONBExists: lambda self, e: self.func(\n                \"BSON_MATCH_ANY_EXISTS\", e.this, e.args.get(\"path\")\n            ),\n            exp.JSONExists: lambda self, e: (\n                f\"{self.sql(e.this)}::?{self.sql(e.args.get('path'))}\"\n                if e.args.get(\"from_dcolonqmark\")\n                else self.func(\"JSON_MATCH_ANY_EXISTS\", e.this, e.args.get(\"path\"))\n            ),\n            exp.JSONObject: unsupported_args(\n                \"null_handling\", \"unique_keys\", \"return_type\", \"encoding\"\n            )(rename_func(\"JSON_BUILD_OBJECT\")),\n            exp.DayOfWeekIso: lambda self, e: f\"(({self.func('DAYOFWEEK', e.this)} % 7) + 1)\",\n            exp.DayOfMonth: rename_func(\"DAY\"),\n            exp.Hll: rename_func(\"APPROX_COUNT_DISTINCT\"),\n            exp.ApproxDistinct: rename_func(\"APPROX_COUNT_DISTINCT\"),\n            exp.CountIf: count_if_to_sum,\n            exp.LogicalOr: lambda self, e: f\"MAX(ABS({self.sql(e, 'this')}))\",\n            exp.LogicalAnd: lambda self, e: f\"MIN(ABS({self.sql(e, 'this')}))\",\n            exp.ApproxQuantile: unsupported_args(\"accuracy\", \"weight\")(\n                lambda self, e: self.func(\n                    \"APPROX_PERCENTILE\",\n                    e.this,\n                    e.args.get(\"quantile\"),\n                    e.args.get(\"error_tolerance\"),\n                )\n            ),\n            exp.Variance: rename_func(\"VAR_SAMP\"),\n            exp.VariancePop: rename_func(\"VAR_POP\"),\n            exp.Xor: bool_xor_sql,\n            exp.Cbrt: lambda self, e: self.sql(\n                exp.Pow(this=e.this, expression=exp.Literal.number(1) / exp.Literal.number(3))\n            ),\n            exp.RegexpLike: lambda self, e: self.binary(e, \"RLIKE\"),\n            exp.Repeat: lambda self, e: self.func(\n                \"LPAD\",\n                exp.Literal.string(\"\"),\n                exp.Mul(this=self.func(\"LENGTH\", e.this), expression=e.args.get(\"times\")),\n                e.this,\n            ),\n            exp.IsAscii: lambda self, e: f\"({self.sql(e, 'this')} RLIKE '^[\\x00-\\x7f]*$')\",\n            exp.MD5Digest: lambda self, e: self.func(\"UNHEX\", self.func(\"MD5\", e.this)),\n            exp.Contains: rename_func(\"INSTR\"),\n            exp.RegexpExtractAll: unsupported_args(\"position\", \"occurrence\", \"group\")(\n                lambda self, e: self.func(\n                    \"REGEXP_MATCH\",\n                    e.this,\n                    e.expression,\n                    e.args.get(\"parameters\"),\n                )\n            ),\n            exp.RegexpExtract: unsupported_args(\"group\")(\n                lambda self, e: self.func(\n                    \"REGEXP_SUBSTR\",\n                    e.this,\n                    e.expression,\n                    e.args.get(\"position\"),\n                    e.args.get(\"occurrence\"),\n                    e.args.get(\"parameters\"),\n                )\n            ),\n            exp.StartsWith: lambda self, e: self.func(\n                \"REGEXP_INSTR\", e.this, self.func(\"CONCAT\", exp.Literal.string(\"^\"), e.expression)\n            ),\n            exp.FromBase: lambda self, e: self.func(\n                \"CONV\", e.this, e.expression, exp.Literal.number(10)\n            ),\n            exp.RegexpILike: lambda self, e: self.binary(\n                exp.RegexpLike(\n                    this=exp.Lower(this=e.this),\n                    expression=exp.Lower(this=e.expression),\n                ),\n                \"RLIKE\",\n            ),\n            exp.Stuff: lambda self, e: self.func(\n                \"CONCAT\",\n                self.func(\"SUBSTRING\", e.this, exp.Literal.number(1), e.args.get(\"start\") - 1),\n                e.expression,\n                self.func(\"SUBSTRING\", e.this, e.args.get(\"start\") + e.args.get(\"length\")),\n            ),\n            exp.National: lambda self, e: self.national_sql(e, prefix=\"\"),\n            exp.Reduce: unsupported_args(\"finish\")(\n                lambda self, e: self.func(\n                    \"REDUCE\", e.args.get(\"initial\"), e.this, e.args.get(\"merge\")\n                )\n            ),\n            exp.MatchAgainst: unsupported_args(\"modifier\")(\n                lambda self, e: super().matchagainst_sql(e)\n            ),\n            exp.Show: unsupported_args(\n                \"history\",\n                \"terse\",\n                \"offset\",\n                \"starts_with\",\n                \"limit\",\n                \"from_\",\n                \"scope\",\n                \"scope_kind\",\n                \"mutex\",\n                \"query\",\n                \"channel\",\n                \"log\",\n                \"types\",\n                \"privileges\",\n            )(lambda self, e: super().show_sql(e)),\n            exp.Describe: unsupported_args(\n                \"style\",\n                \"kind\",\n                \"expressions\",\n                \"partition\",\n                \"format\",\n            )(lambda self, e: super().describe_sql(e)),\n        }\n        TRANSFORMS.pop(exp.JSONExtractScalar)\n        TRANSFORMS.pop(exp.CurrentDate)\n\n        UNSUPPORTED_TYPES = {\n            exp.DType.ARRAY,\n            exp.DType.AGGREGATEFUNCTION,\n            exp.DType.SIMPLEAGGREGATEFUNCTION,\n            exp.DType.BIGSERIAL,\n            exp.DType.BPCHAR,\n            exp.DType.DATEMULTIRANGE,\n            exp.DType.DATERANGE,\n            exp.DType.DYNAMIC,\n            exp.DType.HLLSKETCH,\n            exp.DType.HSTORE,\n            exp.DType.IMAGE,\n            exp.DType.INET,\n            exp.DType.INT128,\n            exp.DType.INT256,\n            exp.DType.INT4MULTIRANGE,\n            exp.DType.INT4RANGE,\n            exp.DType.INT8MULTIRANGE,\n            exp.DType.INT8RANGE,\n            exp.DType.INTERVAL,\n            exp.DType.IPADDRESS,\n            exp.DType.IPPREFIX,\n            exp.DType.IPV4,\n            exp.DType.IPV6,\n            exp.DType.LIST,\n            exp.DType.MAP,\n            exp.DType.LOWCARDINALITY,\n            exp.DType.MONEY,\n            exp.DType.MULTILINESTRING,\n            exp.DType.NAME,\n            exp.DType.NESTED,\n            exp.DType.NOTHING,\n            exp.DType.NULL,\n            exp.DType.NUMMULTIRANGE,\n            exp.DType.NUMRANGE,\n            exp.DType.OBJECT,\n            exp.DType.RANGE,\n            exp.DType.ROWVERSION,\n            exp.DType.SERIAL,\n            exp.DType.SMALLSERIAL,\n            exp.DType.SMALLMONEY,\n            exp.DType.SUPER,\n            exp.DType.TIMETZ,\n            exp.DType.TIMESTAMPNTZ,\n            exp.DType.TIMESTAMPLTZ,\n            exp.DType.TIMESTAMPTZ,\n            exp.DType.TIMESTAMP_NS,\n            exp.DType.TSMULTIRANGE,\n            exp.DType.TSRANGE,\n            exp.DType.TSTZMULTIRANGE,\n            exp.DType.TSTZRANGE,\n            exp.DType.UINT128,\n            exp.DType.UINT256,\n            exp.DType.UNION,\n            exp.DType.UNKNOWN,\n            exp.DType.USERDEFINED,\n            exp.DType.UUID,\n            exp.DType.VARIANT,\n            exp.DType.XML,\n            exp.DType.TDIGEST,\n        }\n\n        TYPE_MAPPING = {\n            **MySQL.Generator.TYPE_MAPPING,\n            exp.DType.BIGDECIMAL: \"DECIMAL\",\n            exp.DType.BIT: \"BOOLEAN\",\n            exp.DType.DATE32: \"DATE\",\n            exp.DType.DATETIME64: \"DATETIME\",\n            exp.DType.DECIMAL32: \"DECIMAL\",\n            exp.DType.DECIMAL64: \"DECIMAL\",\n            exp.DType.DECIMAL128: \"DECIMAL\",\n            exp.DType.DECIMAL256: \"DECIMAL\",\n            exp.DType.ENUM8: \"ENUM\",\n            exp.DType.ENUM16: \"ENUM\",\n            exp.DType.FIXEDSTRING: \"TEXT\",\n            exp.DType.GEOMETRY: \"GEOGRAPHY\",\n            exp.DType.POINT: \"GEOGRAPHYPOINT\",\n            exp.DType.RING: \"GEOGRAPHY\",\n            exp.DType.LINESTRING: \"GEOGRAPHY\",\n            exp.DType.POLYGON: \"GEOGRAPHY\",\n            exp.DType.MULTIPOLYGON: \"GEOGRAPHY\",\n            exp.DType.STRUCT: \"RECORD\",\n            exp.DType.JSONB: \"BSON\",\n            exp.DType.TIMESTAMP: \"TIMESTAMP\",\n            exp.DType.TIMESTAMP_S: \"TIMESTAMP\",\n            exp.DType.TIMESTAMP_MS: \"TIMESTAMP(6)\",\n        }\n\n        # https://docs.singlestore.com/cloud/reference/sql-reference/restricted-keywords/list-of-restricted-keywords/\n        RESERVED_KEYWORDS = {\n            \"abs\",\n            \"absolute\",\n            \"access\",\n            \"account\",\n            \"acos\",\n            \"action\",\n            \"add\",\n            \"adddate\",\n            \"addtime\",\n            \"admin\",\n            \"aes_decrypt\",\n            \"aes_encrypt\",\n            \"after\",\n            \"against\",\n            \"aggregate\",\n            \"aggregates\",\n            \"aggregator\",\n            \"aggregator_id\",\n            \"aggregator_plan_hash\",\n            \"aggregators\",\n            \"algorithm\",\n            \"all\",\n            \"also\",\n            \"alter\",\n            \"always\",\n            \"analyse\",\n            \"analyze\",\n            \"and\",\n            \"anti_join\",\n            \"any\",\n            \"any_value\",\n            \"approx_count_distinct\",\n            \"approx_count_distinct_accumulate\",\n            \"approx_count_distinct_combine\",\n            \"approx_count_distinct_estimate\",\n            \"approx_geography_intersects\",\n            \"approx_percentile\",\n            \"arghistory\",\n            \"arrange\",\n            \"arrangement\",\n            \"array\",\n            \"as\",\n            \"asc\",\n            \"ascii\",\n            \"asensitive\",\n            \"asin\",\n            \"asm\",\n            \"assertion\",\n            \"assignment\",\n            \"ast\",\n            \"asymmetric\",\n            \"async\",\n            \"at\",\n            \"atan\",\n            \"atan2\",\n            \"attach\",\n            \"attribute\",\n            \"authorization\",\n            \"auto\",\n            \"auto_increment\",\n            \"auto_reprovision\",\n            \"autostats\",\n            \"autostats_cardinality_mode\",\n            \"autostats_enabled\",\n            \"autostats_histogram_mode\",\n            \"autostats_sampling\",\n            \"availability\",\n            \"avg\",\n            \"avg_row_length\",\n            \"avro\",\n            \"azure\",\n            \"background\",\n            \"_background_threads_for_cleanup\",\n            \"backup\",\n            \"backup_history\",\n            \"backup_id\",\n            \"backward\",\n            \"batch\",\n            \"batches\",\n            \"batch_interval\",\n            \"_batch_size_limit\",\n            \"before\",\n            \"begin\",\n            \"between\",\n            \"bigint\",\n            \"bin\",\n            \"binary\",\n            \"_binary\",\n            \"bit\",\n            \"bit_and\",\n            \"bit_count\",\n            \"bit_or\",\n            \"bit_xor\",\n            \"blob\",\n            \"bool\",\n            \"boolean\",\n            \"bootstrap\",\n            \"both\",\n            \"_bt\",\n            \"btree\",\n            \"bucket_count\",\n            \"by\",\n            \"byte\",\n            \"byte_length\",\n            \"cache\",\n            \"call\",\n            \"call_for_pipeline\",\n            \"called\",\n            \"capture\",\n            \"cascade\",\n            \"cascaded\",\n            \"case\",\n            \"cast\",\n            \"catalog\",\n            \"ceil\",\n            \"ceiling\",\n            \"chain\",\n            \"change\",\n            \"char\",\n            \"character\",\n            \"characteristics\",\n            \"character_length\",\n            \"char_length\",\n            \"charset\",\n            \"check\",\n            \"checkpoint\",\n            \"_check_can_connect\",\n            \"_check_consistency\",\n            \"checksum\",\n            \"_checksum\",\n            \"class\",\n            \"clear\",\n            \"client\",\n            \"client_found_rows\",\n            \"close\",\n            \"cluster\",\n            \"clustered\",\n            \"cnf\",\n            \"coalesce\",\n            \"coercibility\",\n            \"collate\",\n            \"collation\",\n            \"collect\",\n            \"column\",\n            \"columnar\",\n            \"columns\",\n            \"columnstore\",\n            \"columnstore_segment_rows\",\n            \"comment\",\n            \"comments\",\n            \"commit\",\n            \"committed\",\n            \"_commit_log_tail\",\n            \"committed\",\n            \"compact\",\n            \"compile\",\n            \"compressed\",\n            \"compression\",\n            \"concat\",\n            \"concat_ws\",\n            \"concurrent\",\n            \"concurrently\",\n            \"condition\",\n            \"configuration\",\n            \"connection\",\n            \"connection_id\",\n            \"connections\",\n            \"config\",\n            \"constraint\",\n            \"constraints\",\n            \"content\",\n            \"continue\",\n            \"_continue_replay\",\n            \"conv\",\n            \"conversion\",\n            \"convert\",\n            \"convert_tz\",\n            \"copy\",\n            \"_core\",\n            \"cos\",\n            \"cost\",\n            \"cot\",\n            \"count\",\n            \"create\",\n            \"credentials\",\n            \"cross\",\n            \"cube\",\n            \"csv\",\n            \"cume_dist\",\n            \"curdate\",\n            \"current\",\n            \"current_catalog\",\n            \"current_date\",\n            \"current_role\",\n            \"current_schema\",\n            \"current_security_groups\",\n            \"current_security_roles\",\n            \"current_time\",\n            \"current_timestamp\",\n            \"current_user\",\n            \"cursor\",\n            \"curtime\",\n            \"cycle\",\n            \"data\",\n            \"database\",\n            \"databases\",\n            \"date\",\n            \"date_add\",\n            \"datediff\",\n            \"date_format\",\n            \"date_sub\",\n            \"date_trunc\",\n            \"datetime\",\n            \"day\",\n            \"day_hour\",\n            \"day_microsecond\",\n            \"day_minute\",\n            \"dayname\",\n            \"dayofmonth\",\n            \"dayofweek\",\n            \"dayofyear\",\n            \"day_second\",\n            \"deallocate\",\n            \"dec\",\n            \"decimal\",\n            \"declare\",\n            \"decode\",\n            \"default\",\n            \"defaults\",\n            \"deferrable\",\n            \"deferred\",\n            \"defined\",\n            \"definer\",\n            \"degrees\",\n            \"delayed\",\n            \"delay_key_write\",\n            \"delete\",\n            \"delimiter\",\n            \"delimiters\",\n            \"dense_rank\",\n            \"desc\",\n            \"describe\",\n            \"detach\",\n            \"deterministic\",\n            \"dictionary\",\n            \"differential\",\n            \"directory\",\n            \"disable\",\n            \"discard\",\n            \"_disconnect\",\n            \"disk\",\n            \"distinct\",\n            \"distinctrow\",\n            \"distributed_joins\",\n            \"div\",\n            \"do\",\n            \"document\",\n            \"domain\",\n            \"dot_product\",\n            \"double\",\n            \"drop\",\n            \"_drop_profile\",\n            \"dual\",\n            \"dump\",\n            \"duplicate\",\n            \"dynamic\",\n            \"earliest\",\n            \"each\",\n            \"echo\",\n            \"election\",\n            \"else\",\n            \"elseif\",\n            \"elt\",\n            \"enable\",\n            \"enclosed\",\n            \"encoding\",\n            \"encrypted\",\n            \"end\",\n            \"engine\",\n            \"engines\",\n            \"enum\",\n            \"errors\",\n            \"escape\",\n            \"escaped\",\n            \"estimate\",\n            \"euclidean_distance\",\n            \"event\",\n            \"events\",\n            \"except\",\n            \"exclude\",\n            \"excluding\",\n            \"exclusive\",\n            \"execute\",\n            \"exists\",\n            \"exit\",\n            \"exp\",\n            \"explain\",\n            \"extended\",\n            \"extension\",\n            \"external\",\n            \"external_host\",\n            \"external_port\",\n            \"extract\",\n            \"extractor\",\n            \"extractors\",\n            \"extra_join\",\n            \"_failover\",\n            \"failed_login_attempts\",\n            \"failure\",\n            \"false\",\n            \"family\",\n            \"fault\",\n            \"fetch\",\n            \"field\",\n            \"fields\",\n            \"file\",\n            \"files\",\n            \"fill\",\n            \"first\",\n            \"first_value\",\n            \"fix_alter\",\n            \"fixed\",\n            \"float\",\n            \"float4\",\n            \"float8\",\n            \"floor\",\n            \"flush\",\n            \"following\",\n            \"for\",\n            \"force\",\n            \"force_compiled_mode\",\n            \"force_interpreter_mode\",\n            \"foreground\",\n            \"foreign\",\n            \"format\",\n            \"forward\",\n            \"found_rows\",\n            \"freeze\",\n            \"from\",\n            \"from_base64\",\n            \"from_days\",\n            \"from_unixtime\",\n            \"fs\",\n            \"_fsync\",\n            \"full\",\n            \"fulltext\",\n            \"function\",\n            \"functions\",\n            \"gc\",\n            \"gcs\",\n            \"get_format\",\n            \"_gc\",\n            \"_gcx\",\n            \"generate\",\n            \"geography\",\n            \"geography_area\",\n            \"geography_contains\",\n            \"geography_distance\",\n            \"geography_intersects\",\n            \"geography_latitude\",\n            \"geography_length\",\n            \"geography_longitude\",\n            \"geographypoint\",\n            \"geography_point\",\n            \"geography_within_distance\",\n            \"geometry\",\n            \"geometry_area\",\n            \"geometry_contains\",\n            \"geometry_distance\",\n            \"geometry_filter\",\n            \"geometry_intersects\",\n            \"geometry_length\",\n            \"geometrypoint\",\n            \"geometry_point\",\n            \"geometry_within_distance\",\n            \"geometry_x\",\n            \"geometry_y\",\n            \"global\",\n            \"_global_version_timestamp\",\n            \"grant\",\n            \"granted\",\n            \"grants\",\n            \"greatest\",\n            \"group\",\n            \"grouping\",\n            \"groups\",\n            \"group_concat\",\n            \"gzip\",\n            \"handle\",\n            \"handler\",\n            \"hard_cpu_limit_percentage\",\n            \"hash\",\n            \"has_temp_tables\",\n            \"having\",\n            \"hdfs\",\n            \"header\",\n            \"heartbeat_no_logging\",\n            \"hex\",\n            \"highlight\",\n            \"high_priority\",\n            \"hold\",\n            \"holding\",\n            \"host\",\n            \"hosts\",\n            \"hour\",\n            \"hour_microsecond\",\n            \"hour_minute\",\n            \"hour_second\",\n            \"identified\",\n            \"identity\",\n            \"if\",\n            \"ifnull\",\n            \"ignore\",\n            \"ilike\",\n            \"immediate\",\n            \"immutable\",\n            \"implicit\",\n            \"import\",\n            \"in\",\n            \"including\",\n            \"increment\",\n            \"incremental\",\n            \"index\",\n            \"indexes\",\n            \"inet_aton\",\n            \"inet_ntoa\",\n            \"inet6_aton\",\n            \"inet6_ntoa\",\n            \"infile\",\n            \"inherit\",\n            \"inherits\",\n            \"_init_profile\",\n            \"init\",\n            \"initcap\",\n            \"initialize\",\n            \"initially\",\n            \"inject\",\n            \"inline\",\n            \"inner\",\n            \"inout\",\n            \"input\",\n            \"insensitive\",\n            \"insert\",\n            \"insert_method\",\n            \"instance\",\n            \"instead\",\n            \"instr\",\n            \"int\",\n            \"int1\",\n            \"int2\",\n            \"int3\",\n            \"int4\",\n            \"int8\",\n            \"integer\",\n            \"_internal_dynamic_typecast\",\n            \"interpreter_mode\",\n            \"intersect\",\n            \"interval\",\n            \"into\",\n            \"invoker\",\n            \"is\",\n            \"isnull\",\n            \"isolation\",\n            \"iterate\",\n            \"join\",\n            \"json\",\n            \"json_agg\",\n            \"json_array_contains_double\",\n            \"json_array_contains_json\",\n            \"json_array_contains_string\",\n            \"json_array_push_double\",\n            \"json_array_push_json\",\n            \"json_array_push_string\",\n            \"json_delete_key\",\n            \"json_extract_double\",\n            \"json_extract_json\",\n            \"json_extract_string\",\n            \"json_extract_bigint\",\n            \"json_get_type\",\n            \"json_length\",\n            \"json_set_double\",\n            \"json_set_json\",\n            \"json_set_string\",\n            \"json_splice_double\",\n            \"json_splice_json\",\n            \"json_splice_string\",\n            \"kafka\",\n            \"key\",\n            \"key_block_size\",\n            \"keys\",\n            \"kill\",\n            \"killall\",\n            \"label\",\n            \"lag\",\n            \"language\",\n            \"large\",\n            \"last\",\n            \"last_day\",\n            \"last_insert_id\",\n            \"last_value\",\n            \"lateral\",\n            \"latest\",\n            \"lc_collate\",\n            \"lc_ctype\",\n            \"lcase\",\n            \"lead\",\n            \"leading\",\n            \"leaf\",\n            \"leakproof\",\n            \"least\",\n            \"leave\",\n            \"leaves\",\n            \"left\",\n            \"length\",\n            \"level\",\n            \"license\",\n            \"like\",\n            \"limit\",\n            \"lines\",\n            \"listen\",\n            \"llvm\",\n            \"ln\",\n            \"load\",\n            \"loaddata_where\",\n            \"_load\",\n            \"local\",\n            \"localtime\",\n            \"localtimestamp\",\n            \"locate\",\n            \"location\",\n            \"lock\",\n            \"log\",\n            \"log10\",\n            \"log2\",\n            \"long\",\n            \"longblob\",\n            \"longtext\",\n            \"loop\",\n            \"lower\",\n            \"low_priority\",\n            \"lpad\",\n            \"_ls\",\n            \"ltrim\",\n            \"lz4\",\n            \"management\",\n            \"_management_thread\",\n            \"mapping\",\n            \"master\",\n            \"match\",\n            \"materialized\",\n            \"max\",\n            \"maxvalue\",\n            \"max_concurrency\",\n            \"max_errors\",\n            \"max_partitions_per_batch\",\n            \"max_queue_depth\",\n            \"max_retries_per_batch_partition\",\n            \"max_rows\",\n            \"mbc\",\n            \"md5\",\n            \"mpl\",\n            \"median\",\n            \"mediumblob\",\n            \"mediumint\",\n            \"mediumtext\",\n            \"member\",\n            \"memory\",\n            \"memory_percentage\",\n            \"_memsql_table_id_lookup\",\n            \"memsql\",\n            \"memsql_deserialize\",\n            \"memsql_imitating_kafka\",\n            \"memsql_serialize\",\n            \"merge\",\n            \"metadata\",\n            \"microsecond\",\n            \"middleint\",\n            \"min\",\n            \"min_rows\",\n            \"minus\",\n            \"minute\",\n            \"minute_microsecond\",\n            \"minute_second\",\n            \"minvalue\",\n            \"mod\",\n            \"mode\",\n            \"model\",\n            \"modifies\",\n            \"modify\",\n            \"month\",\n            \"monthname\",\n            \"months_between\",\n            \"move\",\n            \"mpl\",\n            \"names\",\n            \"named\",\n            \"namespace\",\n            \"national\",\n            \"natural\",\n            \"nchar\",\n            \"next\",\n            \"no\",\n            \"node\",\n            \"none\",\n            \"no_query_rewrite\",\n            \"noparam\",\n            \"not\",\n            \"nothing\",\n            \"notify\",\n            \"now\",\n            \"nowait\",\n            \"no_write_to_binlog\",\n            \"no_query_rewrite\",\n            \"norely\",\n            \"nth_value\",\n            \"ntile\",\n            \"null\",\n            \"nullcols\",\n            \"nullif\",\n            \"nulls\",\n            \"numeric\",\n            \"nvarchar\",\n            \"object\",\n            \"octet_length\",\n            \"of\",\n            \"off\",\n            \"offline\",\n            \"offset\",\n            \"offsets\",\n            \"oids\",\n            \"on\",\n            \"online\",\n            \"only\",\n            \"open\",\n            \"operator\",\n            \"optimization\",\n            \"optimize\",\n            \"optimizer\",\n            \"optimizer_state\",\n            \"option\",\n            \"options\",\n            \"optionally\",\n            \"or\",\n            \"order\",\n            \"ordered_serialize\",\n            \"orphan\",\n            \"out\",\n            \"out_of_order\",\n            \"outer\",\n            \"outfile\",\n            \"over\",\n            \"overlaps\",\n            \"overlay\",\n            \"owned\",\n            \"owner\",\n            \"pack_keys\",\n            \"paired\",\n            \"parser\",\n            \"parquet\",\n            \"partial\",\n            \"partition\",\n            \"partition_id\",\n            \"partitioning\",\n            \"partitions\",\n            \"passing\",\n            \"password\",\n            \"password_lock_time\",\n            \"parser\",\n            \"pause\",\n            \"_pause_replay\",\n            \"percent_rank\",\n            \"percentile_cont\",\n            \"percentile_disc\",\n            \"periodic\",\n            \"persisted\",\n            \"pi\",\n            \"pipeline\",\n            \"pipelines\",\n            \"pivot\",\n            \"placing\",\n            \"plan\",\n            \"plans\",\n            \"plancache\",\n            \"plugins\",\n            \"pool\",\n            \"pools\",\n            \"port\",\n            \"position\",\n            \"pow\",\n            \"power\",\n            \"preceding\",\n            \"precision\",\n            \"prepare\",\n            \"prepared\",\n            \"preserve\",\n            \"primary\",\n            \"prior\",\n            \"privileges\",\n            \"procedural\",\n            \"procedure\",\n            \"procedures\",\n            \"process\",\n            \"processlist\",\n            \"profile\",\n            \"profiles\",\n            \"program\",\n            \"promote\",\n            \"proxy\",\n            \"purge\",\n            \"quarter\",\n            \"queries\",\n            \"query\",\n            \"query_timeout\",\n            \"queue\",\n            \"quote\",\n            \"radians\",\n            \"rand\",\n            \"range\",\n            \"rank\",\n            \"read\",\n            \"_read\",\n            \"reads\",\n            \"real\",\n            \"reassign\",\n            \"rebalance\",\n            \"recheck\",\n            \"record\",\n            \"recursive\",\n            \"redundancy\",\n            \"redundant\",\n            \"ref\",\n            \"reference\",\n            \"references\",\n            \"refresh\",\n            \"regexp\",\n            \"reindex\",\n            \"relative\",\n            \"release\",\n            \"reload\",\n            \"rely\",\n            \"remote\",\n            \"remove\",\n            \"rename\",\n            \"repair\",\n            \"_repair_table\",\n            \"repeat\",\n            \"repeatable\",\n            \"_repl\",\n            \"_reprovisioning\",\n            \"replace\",\n            \"replica\",\n            \"replicate\",\n            \"replicating\",\n            \"replication\",\n            \"durability\",\n            \"require\",\n            \"resource\",\n            \"resource_pool\",\n            \"reset\",\n            \"restart\",\n            \"restore\",\n            \"restrict\",\n            \"result\",\n            \"_resurrect\",\n            \"retry\",\n            \"return\",\n            \"returning\",\n            \"returns\",\n            \"reverse\",\n            \"revoke\",\n            \"rg_pool\",\n            \"right\",\n            \"right_anti_join\",\n            \"right_semi_join\",\n            \"right_straight_join\",\n            \"rlike\",\n            \"role\",\n            \"roles\",\n            \"rollback\",\n            \"rollup\",\n            \"round\",\n            \"routine\",\n            \"row\",\n            \"row_count\",\n            \"row_format\",\n            \"row_number\",\n            \"rows\",\n            \"rowstore\",\n            \"rule\",\n            \"rpad\",\n            \"_rpc\",\n            \"rtrim\",\n            \"running\",\n            \"s3\",\n            \"safe\",\n            \"save\",\n            \"savepoint\",\n            \"scalar\",\n            \"schema\",\n            \"schemas\",\n            \"schema_binding\",\n            \"scroll\",\n            \"search\",\n            \"second\",\n            \"second_microsecond\",\n            \"sec_to_time\",\n            \"security\",\n            \"select\",\n            \"semi_join\",\n            \"_send_threads\",\n            \"sensitive\",\n            \"separator\",\n            \"sequence\",\n            \"sequences\",\n            \"serial\",\n            \"serializable\",\n            \"series\",\n            \"service_user\",\n            \"server\",\n            \"session\",\n            \"session_user\",\n            \"set\",\n            \"setof\",\n            \"security_lists_intersect\",\n            \"sha\",\n            \"sha1\",\n            \"sha2\",\n            \"shard\",\n            \"sharded\",\n            \"sharded_id\",\n            \"share\",\n            \"show\",\n            \"shutdown\",\n            \"sigmoid\",\n            \"sign\",\n            \"signal\",\n            \"similar\",\n            \"simple\",\n            \"site\",\n            \"signed\",\n            \"sin\",\n            \"skip\",\n            \"skipped_batches\",\n            \"sleep\",\n            \"_sleep\",\n            \"smallint\",\n            \"snapshot\",\n            \"_snapshot\",\n            \"_snapshots\",\n            \"soft_cpu_limit_percentage\",\n            \"some\",\n            \"soname\",\n            \"sparse\",\n            \"spatial\",\n            \"spatial_check_index\",\n            \"specific\",\n            \"split\",\n            \"sql\",\n            \"sql_big_result\",\n            \"sql_buffer_result\",\n            \"sql_cache\",\n            \"sql_calc_found_rows\",\n            \"sqlexception\",\n            \"sql_mode\",\n            \"sql_no_cache\",\n            \"sql_no_logging\",\n            \"sql_small_result\",\n            \"sqlstate\",\n            \"sqlwarning\",\n            \"sqrt\",\n            \"ssl\",\n            \"stable\",\n            \"standalone\",\n            \"start\",\n            \"starting\",\n            \"state\",\n            \"statement\",\n            \"statistics\",\n            \"stats\",\n            \"status\",\n            \"std\",\n            \"stddev\",\n            \"stddev_pop\",\n            \"stddev_samp\",\n            \"stdin\",\n            \"stdout\",\n            \"stop\",\n            \"storage\",\n            \"str_to_date\",\n            \"straight_join\",\n            \"strict\",\n            \"string\",\n            \"strip\",\n            \"subdate\",\n            \"substr\",\n            \"substring\",\n            \"substring_index\",\n            \"success\",\n            \"sum\",\n            \"super\",\n            \"symmetric\",\n            \"sync_snapshot\",\n            \"sync\",\n            \"_sync\",\n            \"_sync2\",\n            \"_sync_partitions\",\n            \"_sync_snapshot\",\n            \"synchronize\",\n            \"sysid\",\n            \"system\",\n            \"table\",\n            \"table_checksum\",\n            \"tables\",\n            \"tablespace\",\n            \"tags\",\n            \"tan\",\n            \"target_size\",\n            \"task\",\n            \"temp\",\n            \"template\",\n            \"temporary\",\n            \"temptable\",\n            \"_term_bump\",\n            \"terminate\",\n            \"terminated\",\n            \"test\",\n            \"text\",\n            \"then\",\n            \"time\",\n            \"timediff\",\n            \"time_bucket\",\n            \"time_format\",\n            \"timeout\",\n            \"timestamp\",\n            \"timestampadd\",\n            \"timestampdiff\",\n            \"timezone\",\n            \"time_to_sec\",\n            \"tinyblob\",\n            \"tinyint\",\n            \"tinytext\",\n            \"to\",\n            \"to_base64\",\n            \"to_char\",\n            \"to_date\",\n            \"to_days\",\n            \"to_json\",\n            \"to_number\",\n            \"to_seconds\",\n            \"to_timestamp\",\n            \"tracelogs\",\n            \"traditional\",\n            \"trailing\",\n            \"transform\",\n            \"transaction\",\n            \"_transactions_experimental\",\n            \"treat\",\n            \"trigger\",\n            \"triggers\",\n            \"trim\",\n            \"true\",\n            \"trunc\",\n            \"truncate\",\n            \"trusted\",\n            \"two_phase\",\n            \"_twopcid\",\n            \"type\",\n            \"types\",\n            \"ucase\",\n            \"unbounded\",\n            \"uncommitted\",\n            \"undefined\",\n            \"undo\",\n            \"unencrypted\",\n            \"unenforced\",\n            \"unhex\",\n            \"unhold\",\n            \"unicode\",\n            \"union\",\n            \"unique\",\n            \"_unittest\",\n            \"unix_timestamp\",\n            \"unknown\",\n            \"unlisten\",\n            \"_unload\",\n            \"unlock\",\n            \"unlogged\",\n            \"unpivot\",\n            \"unsigned\",\n            \"until\",\n            \"update\",\n            \"upgrade\",\n            \"upper\",\n            \"usage\",\n            \"use\",\n            \"user\",\n            \"users\",\n            \"using\",\n            \"utc_date\",\n            \"utc_time\",\n            \"utc_timestamp\",\n            \"_utf8\",\n            \"vacuum\",\n            \"valid\",\n            \"validate\",\n            \"validator\",\n            \"value\",\n            \"values\",\n            \"varbinary\",\n            \"varchar\",\n            \"varcharacter\",\n            \"variables\",\n            \"variadic\",\n            \"variance\",\n            \"var_pop\",\n            \"var_samp\",\n            \"varying\",\n            \"vector_sub\",\n            \"verbose\",\n            \"version\",\n            \"view\",\n            \"void\",\n            \"volatile\",\n            \"voting\",\n            \"wait\",\n            \"_wake\",\n            \"warnings\",\n            \"week\",\n            \"weekday\",\n            \"weekofyear\",\n            \"when\",\n            \"where\",\n            \"while\",\n            \"whitespace\",\n            \"window\",\n            \"with\",\n            \"without\",\n            \"within\",\n            \"_wm_heartbeat\",\n            \"work\",\n            \"workload\",\n            \"wrapper\",\n            \"write\",\n            \"xact_id\",\n            \"xor\",\n            \"year\",\n            \"year_month\",\n            \"yes\",\n            \"zerofill\",\n            \"zone\",\n        }\n\n        def jsonextractscalar_sql(self, expression: exp.JSONExtractScalar) -> str:\n            json_type = expression.args.get(\"json_type\")\n            func_name = \"JSON_EXTRACT_JSON\" if json_type is None else f\"JSON_EXTRACT_{json_type}\"\n            return json_extract_segments(func_name)(self, expression)\n\n        def jsonbextractscalar_sql(self, expression: exp.JSONBExtractScalar) -> str:\n            json_type = expression.args.get(\"json_type\")\n            func_name = \"BSON_EXTRACT_BSON\" if json_type is None else f\"BSON_EXTRACT_{json_type}\"\n            return json_extract_segments(func_name)(self, expression)\n\n        def jsonextractarray_sql(self, expression: exp.JSONExtractArray) -> str:\n            self.unsupported(\"Arrays are not supported in SingleStore\")\n            return self.function_fallback_sql(expression)\n\n        @unsupported_args(\"on_condition\")\n        def jsonvalue_sql(self, expression: exp.JSONValue) -> str:\n            res: exp.Expr = exp.JSONExtractScalar(\n                this=expression.this,\n                expression=expression.args.get(\"path\"),\n                json_type=\"STRING\",\n            )\n\n            returning = expression.args.get(\"returning\")\n            if returning is not None:\n                res = exp.Cast(this=res, to=returning)\n\n            return self.sql(res)\n\n        def all_sql(self, expression: exp.All) -> str:\n            self.unsupported(\"ALL subquery predicate is not supported in SingleStore\")\n            return super().all_sql(expression)\n\n        def jsonarraycontains_sql(self, expression: exp.JSONArrayContains) -> str:\n            json_type = expression.text(\"json_type\").upper()\n\n            if json_type:\n                return self.func(\n                    f\"JSON_ARRAY_CONTAINS_{json_type}\", expression.expression, expression.this\n                )\n\n            return self.func(\n                \"JSON_ARRAY_CONTAINS_JSON\",\n                expression.expression,\n                self.func(\"TO_JSON\", expression.this),\n            )\n\n        @unsupported_args(\"kind\", \"values\")\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            if expression.args.get(\"nested\") and not expression.is_type(exp.DType.STRUCT):\n                self.unsupported(\n                    f\"Argument 'nested' is not supported for representation of '{expression.this.value}' in SingleStore\"\n                )\n\n            if expression.is_type(exp.DType.VARBINARY) and not expression.expressions:\n                # `VARBINARY` must always have a size - if it doesn't, we always generate `BLOB`\n                return \"BLOB\"\n            if expression.is_type(\n                exp.DType.DECIMAL32,\n                exp.DType.DECIMAL64,\n                exp.DType.DECIMAL128,\n                exp.DType.DECIMAL256,\n            ):\n                scale = self.expressions(expression, flat=True)\n\n                if expression.is_type(exp.DType.DECIMAL32):\n                    precision = \"9\"\n                elif expression.is_type(exp.DType.DECIMAL64):\n                    precision = \"18\"\n                elif expression.is_type(exp.DType.DECIMAL128):\n                    precision = \"38\"\n                else:\n                    # 65 is a maximum precision supported in SingleStore\n                    precision = \"65\"\n                if scale is not None:\n                    return f\"DECIMAL({precision}, {scale[0]})\"\n                else:\n                    return f\"DECIMAL({precision})\"\n            if expression.is_type(exp.DType.VECTOR):\n                expressions = expression.expressions\n                if len(expressions) == 2:\n                    type_name = self.sql(expressions[0])\n                    if type_name in self.dialect.INVERSE_VECTOR_TYPE_ALIASES:\n                        type_name = self.dialect.INVERSE_VECTOR_TYPE_ALIASES[type_name]\n\n                    return f\"VECTOR({self.sql(expressions[1])}, {type_name})\"\n\n            return super().datatype_sql(expression)\n\n        def collate_sql(self, expression: exp.Collate) -> str:\n            # SingleStore does not support setting a collation for column in the SELECT query,\n            # so we cast column to a LONGTEXT type with specific collation\n            return self.binary(expression, \":> LONGTEXT COLLATE\")\n\n        def currentdate_sql(self, expression: exp.CurrentDate) -> str:\n            timezone = expression.this\n            if timezone:\n                if isinstance(timezone, exp.Literal) and timezone.name.lower() == \"utc\":\n                    return self.func(\"UTC_DATE\")\n                self.unsupported(\"CurrentDate with timezone is not supported in SingleStore\")\n\n            return self.func(\"CURRENT_DATE\")\n\n        def currenttime_sql(self, expression: exp.CurrentTime) -> str:\n            arg = expression.this\n            if arg:\n                if isinstance(arg, exp.Literal) and arg.name.lower() == \"utc\":\n                    return self.func(\"UTC_TIME\")\n                if isinstance(arg, exp.Literal) and arg.is_number:\n                    return self.func(\"CURRENT_TIME\", arg)\n                self.unsupported(\"CurrentTime with timezone is not supported in SingleStore\")\n\n            return self.func(\"CURRENT_TIME\")\n\n        def currenttimestamp_sql(self, expression: exp.CurrentTimestamp) -> str:\n            arg = expression.this\n            if arg:\n                if isinstance(arg, exp.Literal) and arg.name.lower() == \"utc\":\n                    return self.func(\"UTC_TIMESTAMP\")\n                if isinstance(arg, exp.Literal) and arg.is_number:\n                    return self.func(\"CURRENT_TIMESTAMP\", arg)\n                self.unsupported(\"CurrentTimestamp with timezone is not supported in SingleStore\")\n\n            return self.func(\"CURRENT_TIMESTAMP\")\n\n        def standardhash_sql(self, expression: exp.StandardHash) -> str:\n            hash_function = expression.expression\n            if hash_function is None:\n                return self.func(\"SHA\", expression.this)\n            if isinstance(hash_function, exp.Literal):\n                if hash_function.name.lower() == \"sha\":\n                    return self.func(\"SHA\", expression.this)\n                if hash_function.name.lower() == \"md5\":\n                    return self.func(\"MD5\", expression.this)\n\n                self.unsupported(\n                    f\"{hash_function.this} hash method is not supported in SingleStore\"\n                )\n                return self.func(\"SHA\", expression.this)\n\n            self.unsupported(\"STANDARD_HASH function is not supported in SingleStore\")\n            return self.func(\"SHA\", expression.this)\n\n        @unsupported_args(\"is_database\", \"exists\", \"cluster\", \"identity\", \"option\", \"partition\")\n        def truncatetable_sql(self, expression: exp.TruncateTable) -> str:\n            statements = []\n            for expression in expression.expressions:\n                statements.append(f\"TRUNCATE {self.sql(expression)}\")\n\n            return \"; \".join(statements)\n\n        @unsupported_args(\"exists\")\n        def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:\n            old_column = self.sql(expression, \"this\")\n            new_column = self.sql(expression, \"to\")\n            return f\"CHANGE {old_column} {new_column}\"\n\n        @unsupported_args(\"drop\", \"comment\", \"allow_null\", \"visible\", \"using\")\n        def altercolumn_sql(self, expression: exp.AlterColumn) -> str:\n            alter = super().altercolumn_sql(expression)\n\n            collate = self.sql(expression, \"collate\")\n            collate = f\" COLLATE {collate}\" if collate else \"\"\n            return f\"{alter}{collate}\"\n\n        def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:\n            this = self.sql(expression, \"this\")\n            not_null = \" NOT NULL\" if expression.args.get(\"not_null\") else \"\"\n            type = self.sql(expression, \"data_type\") or \"AUTO\"\n            return f\"AS {this} PERSISTED {type}{not_null}\"\n"
  },
  {
    "path": "sqlglot/dialects/snowflake.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, generator, jsonpath, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    NormalizationStrategy,\n    array_append_sql,\n    array_concat_sql,\n    date_delta_sql,\n    datestrtodate_sql,\n    groupconcat_sql,\n    if_sql,\n    inline_array_sql,\n    map_date_part,\n    max_or_greatest,\n    min_or_least,\n    no_make_interval_sql,\n    no_timestamp_sql,\n    rename_func,\n    strposition_sql,\n    timestampdiff_sql,\n    timestamptrunc_sql,\n    timestrtotime_sql,\n    unit_to_str,\n    var_map_sql,\n)\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.helper import find_new_name, flatten, seq_get\nfrom sqlglot.optimizer.scope import build_scope, find_all_in_scope\nfrom sqlglot.parsers.snowflake import (\n    RANKING_WINDOW_FUNCTIONS_WITH_FRAME,\n    TIMESTAMP_TYPES,\n    SnowflakeParser,\n    build_object_construct,\n)\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.typing.snowflake import EXPRESSION_METADATA\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n\n\ndef _build_datediff(args: t.List) -> exp.DateDiff:\n    return exp.DateDiff(\n        this=seq_get(args, 2),\n        expression=seq_get(args, 1),\n        unit=map_date_part(seq_get(args, 0)),\n        date_part_boundary=True,\n    )\n\n\ndef _build_date_time_add(expr_type: t.Type[E]) -> t.Callable[[t.List], E]:\n    def _builder(args: t.List) -> E:\n        return expr_type(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=map_date_part(seq_get(args, 0)),\n        )\n\n    return _builder\n\n\ndef _regexpilike_sql(self: Snowflake.Generator, expression: exp.RegexpILike) -> str:\n    flag = expression.text(\"flag\")\n\n    if \"i\" not in flag:\n        flag += \"i\"\n\n    return self.func(\n        \"REGEXP_LIKE\", expression.this, expression.expression, exp.Literal.string(flag)\n    )\n\n\ndef _unqualify_pivot_columns(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Snowflake doesn't allow columns referenced in UNPIVOT to be qualified,\n    so we need to unqualify them. Same goes for ANY ORDER BY <column>.\n\n    Example:\n        >>> from sqlglot import parse_one\n        >>> expr = parse_one(\"SELECT * FROM m_sales UNPIVOT(sales FOR month IN (m_sales.jan, feb, mar, april))\")\n        >>> print(_unqualify_pivot_columns(expr).sql(dialect=\"snowflake\"))\n        SELECT * FROM m_sales UNPIVOT(sales FOR month IN (jan, feb, mar, april))\n    \"\"\"\n    if isinstance(expression, exp.Pivot):\n        if expression.unpivot:\n            expression = transforms.unqualify_columns(expression)\n        else:\n            for field in expression.fields:\n                field_expr = seq_get(field.expressions if field else [], 0)\n\n                if isinstance(field_expr, exp.PivotAny):\n                    unqualified_field_expr = transforms.unqualify_columns(field_expr)\n                    t.cast(exp.Expr, field).set(\"expressions\", unqualified_field_expr, 0)\n\n    return expression\n\n\ndef _flatten_structured_types_unless_iceberg(expression: exp.Expr) -> exp.Expr:\n    assert isinstance(expression, exp.Create)\n\n    def _flatten_structured_type(expression: exp.DataType) -> exp.DataType:\n        if expression.this in exp.DataType.NESTED_TYPES:\n            expression.set(\"expressions\", None)\n        return expression\n\n    props = expression.args.get(\"properties\")\n    if isinstance(expression.this, exp.Schema) and not (props and props.find(exp.IcebergProperty)):\n        for schema_expression in expression.this.expressions:\n            if isinstance(schema_expression, exp.ColumnDef):\n                column_type = schema_expression.kind\n                if isinstance(column_type, exp.DataType):\n                    column_type.transform(_flatten_structured_type, copy=False)\n\n    return expression\n\n\ndef _unnest_generate_date_array(unnest: exp.Unnest) -> None:\n    generate_date_array = unnest.expressions[0]\n    start = generate_date_array.args.get(\"start\")\n    end = generate_date_array.args.get(\"end\")\n    step = generate_date_array.args.get(\"step\")\n\n    if not start or not end or not isinstance(step, exp.Interval) or step.name != \"1\":\n        return\n\n    unit = step.args.get(\"unit\")\n\n    unnest_alias = unnest.args.get(\"alias\")\n    if unnest_alias:\n        unnest_alias = unnest_alias.copy()\n        sequence_value_name = seq_get(unnest_alias.columns, 0) or \"value\"\n    else:\n        sequence_value_name = \"value\"\n\n    # We'll add the next sequence value to the starting date and project the result\n    date_add = _build_date_time_add(exp.DateAdd)(\n        [unit, exp.cast(sequence_value_name, \"int\"), exp.cast(start, \"date\")]\n    )\n\n    # We use DATEDIFF to compute the number of sequence values needed\n    number_sequence = Snowflake.Parser.FUNCTIONS[\"ARRAY_GENERATE_RANGE\"](\n        [exp.Literal.number(0), _build_datediff([unit, start, end]) + 1]\n    )\n\n    unnest.set(\"expressions\", [number_sequence])\n\n    unnest_parent = unnest.parent\n    if isinstance(unnest_parent, exp.Join):\n        select = unnest_parent.parent\n        if isinstance(select, exp.Select):\n            replace_column_name = (\n                sequence_value_name\n                if isinstance(sequence_value_name, str)\n                else sequence_value_name.name\n            )\n\n            scope = build_scope(select)\n            if scope:\n                for column in scope.columns:\n                    if column.name.lower() == replace_column_name.lower():\n                        column.replace(\n                            date_add.as_(replace_column_name)\n                            if isinstance(column.parent, exp.Select)\n                            else date_add\n                        )\n\n            lateral = exp.Lateral(this=unnest_parent.this.pop())\n            unnest_parent.replace(exp.Join(this=lateral))\n    else:\n        unnest.replace(\n            exp.select(date_add.as_(sequence_value_name))\n            .from_(unnest.copy())\n            .subquery(unnest_alias)\n        )\n\n\ndef _transform_generate_date_array(expression: exp.Expr) -> exp.Expr:\n    if isinstance(expression, exp.Select):\n        for generate_date_array in expression.find_all(exp.GenerateDateArray):\n            parent = generate_date_array.parent\n\n            # If GENERATE_DATE_ARRAY is used directly as an array (e.g passed into ARRAY_LENGTH), the transformed Snowflake\n            # query is the following (it'll be unnested properly on the next iteration due to copy):\n            # SELECT ref(GENERATE_DATE_ARRAY(...)) -> SELECT ref((SELECT ARRAY_AGG(*) FROM UNNEST(GENERATE_DATE_ARRAY(...))))\n            if not isinstance(parent, exp.Unnest):\n                unnest = exp.Unnest(expressions=[generate_date_array.copy()])\n                generate_date_array.replace(\n                    exp.select(exp.ArrayAgg(this=exp.Star())).from_(unnest).subquery()\n                )\n\n            if (\n                isinstance(parent, exp.Unnest)\n                and isinstance(parent.parent, (exp.From, exp.Join))\n                and len(parent.expressions) == 1\n            ):\n                _unnest_generate_date_array(parent)\n\n    return expression\n\n\ndef _regexpextract_sql(self, expression: exp.RegexpExtract | exp.RegexpExtractAll) -> str:\n    # Other dialects don't support all of the following parameters, so we need to\n    # generate default values as necessary to ensure the transpilation is correct\n    group = expression.args.get(\"group\")\n\n    # To avoid generating all these default values, we set group to None if\n    # it's 0 (also default value) which doesn't trigger the following chain\n    if group and group.name == \"0\":\n        group = None\n\n    parameters = expression.args.get(\"parameters\") or (group and exp.Literal.string(\"c\"))\n    occurrence = expression.args.get(\"occurrence\") or (parameters and exp.Literal.number(1))\n    position = expression.args.get(\"position\") or (occurrence and exp.Literal.number(1))\n\n    return self.func(\n        \"REGEXP_SUBSTR\" if isinstance(expression, exp.RegexpExtract) else \"REGEXP_SUBSTR_ALL\",\n        expression.this,\n        expression.expression,\n        position,\n        occurrence,\n        parameters,\n        group,\n    )\n\n\ndef _json_extract_value_array_sql(\n    self: Snowflake.Generator, expression: exp.JSONValueArray | exp.JSONExtractArray\n) -> str:\n    json_extract = exp.JSONExtract(this=expression.this, expression=expression.expression)\n    ident = exp.to_identifier(\"x\")\n\n    if isinstance(expression, exp.JSONValueArray):\n        this: exp.Expr = exp.cast(ident, to=exp.DType.VARCHAR)\n    else:\n        this = exp.ParseJSON(this=f\"TO_JSON({ident})\")\n\n    transform_lambda = exp.Lambda(expressions=[ident], this=this)\n\n    return self.func(\"TRANSFORM\", json_extract, transform_lambda)\n\n\ndef _qualify_unnested_columns(expression: exp.Expr) -> exp.Expr:\n    if isinstance(expression, exp.Select):\n        scope = build_scope(expression)\n        if not scope:\n            return expression\n\n        unnests = list(scope.find_all(exp.Unnest))\n\n        if not unnests:\n            return expression\n\n        taken_source_names = set(scope.sources)\n        column_source: t.Dict[str, exp.Identifier] = {}\n        unnest_to_identifier: t.Dict[exp.Unnest, exp.Identifier] = {}\n\n        unnest_identifier: t.Optional[exp.Identifier] = None\n        orig_expression = expression.copy()\n\n        for unnest in unnests:\n            if not isinstance(unnest.parent, (exp.From, exp.Join)):\n                continue\n\n            # Try to infer column names produced by an unnest operator. This is only possible\n            # when we can peek into the (statically known) contents of the unnested value.\n            unnest_columns: t.Set[str] = set()\n            for unnest_expr in unnest.expressions:\n                if not isinstance(unnest_expr, exp.Array):\n                    continue\n\n                for array_expr in unnest_expr.expressions:\n                    if not (\n                        isinstance(array_expr, exp.Struct)\n                        and array_expr.expressions\n                        and all(\n                            isinstance(struct_expr, exp.PropertyEQ)\n                            for struct_expr in array_expr.expressions\n                        )\n                    ):\n                        continue\n\n                    unnest_columns.update(\n                        struct_expr.this.name.lower() for struct_expr in array_expr.expressions\n                    )\n                    break\n\n                if unnest_columns:\n                    break\n\n            unnest_alias = unnest.args.get(\"alias\")\n            if not unnest_alias:\n                alias_name = find_new_name(taken_source_names, \"value\")\n                taken_source_names.add(alias_name)\n\n                # Produce a `TableAlias` AST similar to what is produced for BigQuery. This\n                # will be corrected later, when we generate SQL for the `Unnest` AST node.\n                aliased_unnest = exp.alias_(unnest, None, table=[alias_name])\n                scope.replace(unnest, aliased_unnest)\n\n                unnest_identifier = aliased_unnest.args[\"alias\"].columns[0]\n            else:\n                alias_columns = getattr(unnest_alias, \"columns\", [])\n                unnest_identifier = unnest_alias.this or seq_get(alias_columns, 0)\n\n            if not isinstance(unnest_identifier, exp.Identifier):\n                return orig_expression\n\n            unnest_to_identifier[unnest] = unnest_identifier\n            column_source.update({c.lower(): unnest_identifier for c in unnest_columns})\n\n        for column in scope.columns:\n            if column.table:\n                continue\n\n            table = column_source.get(column.name.lower())\n            if (\n                unnest_identifier\n                and not table\n                and len(scope.sources) == 1\n                and column.name.lower() != unnest_identifier.name.lower()\n            ):\n                unnest_ancestor = column.find_ancestor(exp.Unnest, exp.Select)\n                if isinstance(unnest_ancestor, exp.Unnest):\n                    ancestor_identifier = unnest_to_identifier.get(unnest_ancestor)\n                    if (\n                        ancestor_identifier\n                        and ancestor_identifier.name.lower() == unnest_identifier.name.lower()\n                    ):\n                        continue\n\n                table = unnest_identifier\n\n            column.set(\"table\", table and table.copy())\n\n    return expression\n\n\ndef _eliminate_dot_variant_lookup(expression: exp.Expr) -> exp.Expr:\n    if isinstance(expression, exp.Select):\n        # This transformation is used to facilitate transpilation of BigQuery `UNNEST` operations\n        # to Snowflake. It should not affect roundtrip because `Unnest` nodes cannot be produced\n        # by Snowflake's parser.\n        #\n        # Additionally, at the time of writing this, BigQuery is the only dialect that produces a\n        # `TableAlias` node that only fills `columns` and not `this`, due to `UNNEST_COLUMN_ONLY`.\n        unnest_aliases = set()\n        for unnest in find_all_in_scope(expression, exp.Unnest):\n            unnest_alias = unnest.args.get(\"alias\")\n            if (\n                isinstance(unnest_alias, exp.TableAlias)\n                and not unnest_alias.this\n                and len(unnest_alias.columns) == 1\n            ):\n                unnest_aliases.add(unnest_alias.columns[0].name)\n\n        if unnest_aliases:\n            for c in find_all_in_scope(expression, exp.Column):\n                if c.table in unnest_aliases:\n                    bracket_lhs = c.args[\"table\"]\n                    bracket_rhs = exp.Literal.string(c.name)\n                    bracket = exp.Bracket(this=bracket_lhs, expressions=[bracket_rhs])\n\n                    if c.parent is expression:\n                        # Retain column projection names by using aliases\n                        c.replace(exp.alias_(bracket, c.this.copy()))\n                    else:\n                        c.replace(bracket)\n\n    return expression\n\n\nclass Snowflake(Dialect):\n    # https://docs.snowflake.com/en/sql-reference/identifiers-syntax\n    NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE\n    NULL_ORDERING = \"nulls_are_large\"\n    TIME_FORMAT = \"'YYYY-MM-DD HH24:MI:SS'\"\n    SUPPORTS_USER_DEFINED_TYPES = False\n    PREFER_CTE_ALIAS_COLUMN = True\n    TABLESAMPLE_SIZE_IS_PERCENT = True\n    COPY_PARAMS_ARE_CSV = False\n    ARRAY_AGG_INCLUDES_NULLS = None\n    ARRAY_FUNCS_PROPAGATES_NULLS = True\n    ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN = False\n    TRY_CAST_REQUIRES_STRING = True\n    SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS = True\n    LEAST_GREATEST_IGNORES_NULLS = False\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    # https://docs.snowflake.com/en/en/sql-reference/functions/initcap\n    INITCAP_DEFAULT_DELIMITER_CHARS = ' \\t\\n\\r\\f\\v!?@\"^#$&~_,.:;+\\\\-*%/|\\\\[\\\\](){}<>'\n\n    INVERSE_TIME_MAPPING = {\n        \"T\": \"T\",  # in TIME_MAPPING we map '\"T\"' with the double quotes to 'T', and we want to prevent 'T' from being mapped back to '\"T\"' so that 'AUTO' doesn't become 'AU\"T\"O'\n    }\n\n    TIME_MAPPING = {\n        \"YYYY\": \"%Y\",\n        \"yyyy\": \"%Y\",\n        \"YY\": \"%y\",\n        \"yy\": \"%y\",\n        \"MMMM\": \"%B\",\n        \"mmmm\": \"%B\",\n        \"MON\": \"%b\",\n        \"mon\": \"%b\",\n        \"MM\": \"%m\",\n        \"mm\": \"%m\",\n        \"DD\": \"%d\",\n        \"dd\": \"%-d\",\n        \"DY\": \"%a\",\n        \"dy\": \"%w\",\n        \"HH24\": \"%H\",\n        \"hh24\": \"%H\",\n        \"HH12\": \"%I\",\n        \"hh12\": \"%I\",\n        \"MI\": \"%M\",\n        \"mi\": \"%M\",\n        \"SS\": \"%S\",\n        \"ss\": \"%S\",\n        \"FF\": \"%f_nine\",  # %f_ internal representation with precision specified\n        \"ff\": \"%f_nine\",\n        \"FF0\": \"%f_zero\",\n        \"ff0\": \"%f_zero\",\n        \"FF1\": \"%f_one\",\n        \"ff1\": \"%f_one\",\n        \"FF2\": \"%f_two\",\n        \"ff2\": \"%f_two\",\n        \"FF3\": \"%f_three\",\n        \"ff3\": \"%f_three\",\n        \"FF4\": \"%f_four\",\n        \"ff4\": \"%f_four\",\n        \"FF5\": \"%f_five\",\n        \"ff5\": \"%f_five\",\n        \"FF6\": \"%f\",\n        \"ff6\": \"%f\",\n        \"FF7\": \"%f_seven\",\n        \"ff7\": \"%f_seven\",\n        \"FF8\": \"%f_eight\",\n        \"ff8\": \"%f_eight\",\n        \"FF9\": \"%f_nine\",\n        \"ff9\": \"%f_nine\",\n        \"TZHTZM\": \"%z\",\n        \"tzhtzm\": \"%z\",\n        \"TZH:TZM\": \"%:z\",  # internal representation for ±HH:MM\n        \"tzh:tzm\": \"%:z\",\n        \"TZH\": \"%-z\",  # internal representation ±HH\n        \"tzh\": \"%-z\",\n        '\"T\"': \"T\",  # remove the optional double quotes around the separator between the date and time\n        # Seems like Snowflake treats AM/PM in the format string as equivalent,\n        # only the time (stamp) value's AM/PM affects the output\n        \"AM\": \"%p\",\n        \"am\": \"%p\",\n        \"PM\": \"%p\",\n        \"pm\": \"%p\",\n    }\n\n    DATE_PART_MAPPING = {\n        **Dialect.DATE_PART_MAPPING,\n        \"ISOWEEK\": \"WEEKISO\",\n        # The base Dialect maps EPOCH_SECOND -> EPOCH, but we need to preserve\n        # EPOCH_SECOND as a distinct value for two reasons:\n        # 1. Type annotation: EPOCH_SECOND returns BIGINT, while EPOCH returns DOUBLE\n        # 2. Transpilation: DuckDB's EPOCH() returns float, so we cast EPOCH_SECOND\n        #    to BIGINT to match Snowflake's integer behavior\n        # Without this override, EXTRACT(EPOCH_SECOND FROM ts) would be normalized\n        # to EXTRACT(EPOCH FROM ts) and lose the integer semantics.\n        \"EPOCH_SECOND\": \"EPOCH_SECOND\",\n        \"EPOCH_SECONDS\": \"EPOCH_SECOND\",\n    }\n\n    PSEUDOCOLUMNS = {\"LEVEL\"}\n\n    def can_quote(self, identifier: exp.Identifier, identify: str | bool = \"safe\") -> bool:\n        # This disables quoting DUAL in SELECT ... FROM DUAL, because Snowflake treats an\n        # unquoted DUAL keyword in a special way and does not map it to a user-defined table\n        return super().can_quote(identifier, identify) and not (\n            isinstance(identifier.parent, exp.Table)\n            and not identifier.quoted\n            and identifier.name.lower() == \"dual\"\n        )\n\n    class JSONPathTokenizer(jsonpath.JSONPathTokenizer):\n        SINGLE_TOKENS = jsonpath.JSONPathTokenizer.SINGLE_TOKENS.copy()\n        SINGLE_TOKENS.pop(\"$\")\n\n    Parser = SnowflakeParser\n\n    class Tokenizer(tokens.Tokenizer):\n        STRING_ESCAPES = [\"\\\\\", \"'\"]\n        HEX_STRINGS = [(\"x'\", \"'\"), (\"X'\", \"'\")]\n        RAW_STRINGS = [\"$$\"]\n        COMMENTS = [\"--\", \"//\", (\"/*\", \"*/\")]\n        NESTED_COMMENTS = False\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"BYTEINT\": TokenType.INT,\n            \"FILE://\": TokenType.URI_START,\n            \"FILE FORMAT\": TokenType.FILE_FORMAT,\n            \"GET\": TokenType.GET,\n            \"INTEGRATION\": TokenType.INTEGRATION,\n            \"MATCH_CONDITION\": TokenType.MATCH_CONDITION,\n            \"MATCH_RECOGNIZE\": TokenType.MATCH_RECOGNIZE,\n            \"MINUS\": TokenType.EXCEPT,\n            \"NCHAR VARYING\": TokenType.VARCHAR,\n            \"PACKAGE\": TokenType.PACKAGE,\n            \"POLICY\": TokenType.POLICY,\n            \"POOL\": TokenType.POOL,\n            \"PUT\": TokenType.PUT,\n            \"REMOVE\": TokenType.COMMAND,\n            \"RM\": TokenType.COMMAND,\n            \"ROLE\": TokenType.ROLE,\n            \"RULE\": TokenType.RULE,\n            \"SAMPLE\": TokenType.TABLE_SAMPLE,\n            \"SEMANTIC VIEW\": TokenType.SEMANTIC_VIEW,\n            \"SQL_DOUBLE\": TokenType.DOUBLE,\n            \"SQL_VARCHAR\": TokenType.VARCHAR,\n            \"STAGE\": TokenType.STAGE,\n            \"STORAGE INTEGRATION\": TokenType.STORAGE_INTEGRATION,\n            \"STREAMLIT\": TokenType.STREAMLIT,\n            \"TAG\": TokenType.TAG,\n            \"TIMESTAMP_TZ\": TokenType.TIMESTAMPTZ,\n            \"TOP\": TokenType.TOP,\n            \"VOLUME\": TokenType.VOLUME,\n            \"WAREHOUSE\": TokenType.WAREHOUSE,\n            # https://docs.snowflake.com/en/sql-reference/data-types-numeric#float\n            # FLOAT is a synonym for DOUBLE in Snowflake\n            \"FLOAT\": TokenType.DOUBLE,\n        }\n        KEYWORDS.pop(\"/*+\")\n\n        SINGLE_TOKENS = {\n            **tokens.Tokenizer.SINGLE_TOKENS,\n            \"$\": TokenType.PARAMETER,\n            \"!\": TokenType.EXCLAMATION,\n        }\n\n        VAR_SINGLE_TOKENS = {\"$\"}\n\n        COMMANDS = tokens.Tokenizer.COMMANDS - {TokenType.SHOW}\n\n    class Generator(generator.Generator):\n        PARAMETER_TOKEN = \"$\"\n        MATCHED_BY_SOURCE = False\n        SINGLE_STRING_INTERVAL = True\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n        AGGREGATE_FILTER_SUPPORTED = False\n        SUPPORTS_TABLE_COPY = False\n        COLLATE_IS_FUNC = True\n        LIMIT_ONLY_LITERALS = True\n        JSON_KEY_VALUE_PAIR_SEP = \",\"\n        INSERT_OVERWRITE = \" OVERWRITE INTO\"\n        STRUCT_DELIMITER = (\"(\", \")\")\n        COPY_PARAMS_ARE_WRAPPED = False\n        COPY_PARAMS_EQ_REQUIRED = True\n        STAR_EXCEPT = \"EXCLUDE\"\n        SUPPORTS_EXPLODING_PROJECTIONS = False\n        ARRAY_CONCAT_IS_VAR_LEN = False\n        SUPPORTS_CONVERT_TIMEZONE = True\n        EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False\n        SUPPORTS_MEDIAN = True\n        ARRAY_SIZE_NAME = \"ARRAY_SIZE\"\n        SUPPORTS_DECODE_CASE = True\n        IS_BOOL_ALLOWED = False\n        DIRECTED_JOINS = True\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.ApproxDistinct: rename_func(\"APPROX_COUNT_DISTINCT\"),\n            exp.ArgMax: rename_func(\"MAX_BY\"),\n            exp.ArgMin: rename_func(\"MIN_BY\"),\n            exp.Array: transforms.preprocess([transforms.inherit_struct_field_names]),\n            exp.ArrayConcat: array_concat_sql(\"ARRAY_CAT\"),\n            exp.ArrayAppend: array_append_sql(\"ARRAY_APPEND\"),\n            exp.ArrayPrepend: array_append_sql(\"ARRAY_PREPEND\"),\n            exp.ArrayContains: lambda self, e: self.func(\n                \"ARRAY_CONTAINS\",\n                e.expression\n                if e.args.get(\"ensure_variant\") is False\n                else exp.cast(e.expression, exp.DType.VARIANT, copy=False),\n                e.this,\n            ),\n            exp.ArrayPosition: lambda self, e: self.func(\n                \"ARRAY_POSITION\",\n                e.expression,\n                e.this,\n            ),\n            exp.ArrayIntersect: rename_func(\"ARRAY_INTERSECTION\"),\n            exp.ArrayOverlaps: rename_func(\"ARRAYS_OVERLAP\"),\n            exp.AtTimeZone: lambda self, e: self.func(\n                \"CONVERT_TIMEZONE\", e.args.get(\"zone\"), e.this\n            ),\n            exp.BitwiseOr: rename_func(\"BITOR\"),\n            exp.BitwiseXor: rename_func(\"BITXOR\"),\n            exp.BitwiseAnd: rename_func(\"BITAND\"),\n            exp.BitwiseAndAgg: rename_func(\"BITANDAGG\"),\n            exp.BitwiseOrAgg: rename_func(\"BITORAGG\"),\n            exp.BitwiseXorAgg: rename_func(\"BITXORAGG\"),\n            exp.BitwiseNot: rename_func(\"BITNOT\"),\n            exp.BitwiseLeftShift: rename_func(\"BITSHIFTLEFT\"),\n            exp.BitwiseRightShift: rename_func(\"BITSHIFTRIGHT\"),\n            exp.Create: transforms.preprocess([_flatten_structured_types_unless_iceberg]),\n            exp.CurrentTimestamp: lambda self, e: (\n                self.func(\"SYSDATE\") if e.args.get(\"sysdate\") else self.function_fallback_sql(e)\n            ),\n            exp.CurrentSchemas: lambda self, e: self.func(\"CURRENT_SCHEMAS\"),\n            exp.Localtime: lambda self, e: (\n                self.func(\"CURRENT_TIME\", e.this) if e.this else \"CURRENT_TIME\"\n            ),\n            exp.Localtimestamp: lambda self, e: (\n                self.func(\"CURRENT_TIMESTAMP\", e.this) if e.this else \"CURRENT_TIMESTAMP\"\n            ),\n            exp.DateAdd: date_delta_sql(\"DATEADD\"),\n            exp.DateDiff: date_delta_sql(\"DATEDIFF\"),\n            exp.DatetimeAdd: date_delta_sql(\"TIMESTAMPADD\"),\n            exp.DatetimeDiff: timestampdiff_sql,\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.Decrypt: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}DECRYPT\",\n                e.this,\n                e.args.get(\"passphrase\"),\n                e.args.get(\"aad\"),\n                e.args.get(\"encryption_method\"),\n            ),\n            exp.DecryptRaw: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}DECRYPT_RAW\",\n                e.this,\n                e.args.get(\"key\"),\n                e.args.get(\"iv\"),\n                e.args.get(\"aad\"),\n                e.args.get(\"encryption_method\"),\n                e.args.get(\"aead\"),\n            ),\n            exp.DayOfMonth: rename_func(\"DAYOFMONTH\"),\n            exp.DayOfWeek: rename_func(\"DAYOFWEEK\"),\n            exp.DayOfWeekIso: rename_func(\"DAYOFWEEKISO\"),\n            exp.DayOfYear: rename_func(\"DAYOFYEAR\"),\n            exp.DotProduct: rename_func(\"VECTOR_INNER_PRODUCT\"),\n            exp.Explode: rename_func(\"FLATTEN\"),\n            exp.Extract: lambda self, e: self.func(\n                \"DATE_PART\", map_date_part(e.this, self.dialect), e.expression\n            ),\n            exp.CosineDistance: rename_func(\"VECTOR_COSINE_SIMILARITY\"),\n            exp.EuclideanDistance: rename_func(\"VECTOR_L2_DISTANCE\"),\n            exp.FileFormatProperty: lambda self, e: (\n                f\"FILE_FORMAT=({self.expressions(e, 'expressions', sep=' ')})\"\n            ),\n            exp.FromTimeZone: lambda self, e: self.func(\n                \"CONVERT_TIMEZONE\", e.args.get(\"zone\"), \"'UTC'\", e.this\n            ),\n            exp.GenerateSeries: lambda self, e: self.func(\n                \"ARRAY_GENERATE_RANGE\",\n                e.args[\"start\"],\n                e.args[\"end\"] if e.args.get(\"is_end_exclusive\") else e.args[\"end\"] + 1,\n                e.args.get(\"step\"),\n            ),\n            exp.GetExtract: rename_func(\"GET\"),\n            exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, sep=\"\"),\n            exp.If: if_sql(name=\"IFF\", false_value=\"NULL\"),\n            exp.JSONExtractArray: _json_extract_value_array_sql,\n            exp.JSONExtractScalar: lambda self, e: self.func(\n                \"JSON_EXTRACT_PATH_TEXT\", e.this, e.expression\n            ),\n            exp.JSONKeys: rename_func(\"OBJECT_KEYS\"),\n            exp.JSONObject: lambda self, e: self.func(\"OBJECT_CONSTRUCT_KEEP_NULL\", *e.expressions),\n            exp.JSONPathRoot: lambda *_: \"\",\n            exp.JSONValueArray: _json_extract_value_array_sql,\n            exp.Levenshtein: unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\")(\n                rename_func(\"EDITDISTANCE\")\n            ),\n            exp.LocationProperty: lambda self, e: f\"LOCATION={self.sql(e, 'this')}\",\n            exp.LogicalAnd: rename_func(\"BOOLAND_AGG\"),\n            exp.LogicalOr: rename_func(\"BOOLOR_AGG\"),\n            exp.Map: lambda self, e: var_map_sql(self, e, \"OBJECT_CONSTRUCT\"),\n            exp.ManhattanDistance: rename_func(\"VECTOR_L1_DISTANCE\"),\n            exp.MakeInterval: no_make_interval_sql,\n            exp.Max: max_or_greatest,\n            exp.Min: min_or_least,\n            exp.ParseJSON: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}PARSE_JSON\", e.this\n            ),\n            exp.ToBinary: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}TO_BINARY\", e.this, e.args.get(\"format\")\n            ),\n            exp.ToBoolean: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}TO_BOOLEAN\", e.this\n            ),\n            exp.ToDouble: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}TO_DOUBLE\", e.this, e.args.get(\"format\")\n            ),\n            exp.ToFile: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}TO_FILE\", e.this, e.args.get(\"path\")\n            ),\n            exp.ToNumber: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}TO_NUMBER\",\n                e.this,\n                e.args.get(\"format\"),\n                e.args.get(\"precision\"),\n                e.args.get(\"scale\"),\n            ),\n            exp.JSONFormat: rename_func(\"TO_JSON\"),\n            exp.PartitionedByProperty: lambda self, e: f\"PARTITION BY {self.sql(e, 'this')}\",\n            exp.PercentileCont: transforms.preprocess(\n                [transforms.add_within_group_for_percentiles]\n            ),\n            exp.PercentileDisc: transforms.preprocess(\n                [transforms.add_within_group_for_percentiles]\n            ),\n            exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]),\n            exp.RegexpExtract: _regexpextract_sql,\n            exp.RegexpExtractAll: _regexpextract_sql,\n            exp.RegexpILike: _regexpilike_sql,\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_window_clause,\n                    transforms.eliminate_distinct_on,\n                    transforms.explode_projection_to_unnest(),\n                    transforms.eliminate_semi_and_anti_joins,\n                    _transform_generate_date_array,\n                    _qualify_unnested_columns,\n                    _eliminate_dot_variant_lookup,\n                ]\n            ),\n            exp.SHA: rename_func(\"SHA1\"),\n            exp.SHA1Digest: rename_func(\"SHA1_BINARY\"),\n            exp.MD5Digest: rename_func(\"MD5_BINARY\"),\n            exp.MD5NumberLower64: rename_func(\"MD5_NUMBER_LOWER64\"),\n            exp.MD5NumberUpper64: rename_func(\"MD5_NUMBER_UPPER64\"),\n            exp.LowerHex: rename_func(\"TO_CHAR\"),\n            exp.Skewness: rename_func(\"SKEW\"),\n            exp.StarMap: rename_func(\"OBJECT_CONSTRUCT\"),\n            exp.StartsWith: rename_func(\"STARTSWITH\"),\n            exp.EndsWith: rename_func(\"ENDSWITH\"),\n            exp.Rand: lambda self, e: self.func(\"RANDOM\", e.this),\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self, e, func_name=\"CHARINDEX\", supports_position=True\n            ),\n            exp.StrToDate: lambda self, e: self.func(\"DATE\", e.this, self.format_time(e)),\n            exp.StringToArray: rename_func(\"STRTOK_TO_ARRAY\"),\n            exp.Stuff: rename_func(\"INSERT\"),\n            exp.StPoint: rename_func(\"ST_MAKEPOINT\"),\n            exp.TimeAdd: date_delta_sql(\"TIMEADD\"),\n            exp.TimeSlice: lambda self, e: self.func(\n                \"TIME_SLICE\",\n                e.this,\n                e.expression,\n                unit_to_str(e),\n                e.args.get(\"kind\"),\n            ),\n            exp.Timestamp: no_timestamp_sql,\n            exp.TimestampAdd: date_delta_sql(\"TIMESTAMPADD\"),\n            exp.TimestampDiff: lambda self, e: self.func(\n                \"TIMESTAMPDIFF\", e.unit, e.expression, e.this\n            ),\n            exp.TimestampTrunc: timestamptrunc_sql(),\n            exp.TimeStrToTime: timestrtotime_sql,\n            exp.TimeToUnix: lambda self, e: f\"EXTRACT(epoch_second FROM {self.sql(e, 'this')})\",\n            exp.ToArray: rename_func(\"TO_ARRAY\"),\n            exp.ToChar: lambda self, e: self.function_fallback_sql(e),\n            exp.TsOrDsAdd: date_delta_sql(\"DATEADD\", cast=True),\n            exp.TsOrDsDiff: date_delta_sql(\"DATEDIFF\"),\n            exp.TsOrDsToDate: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}TO_DATE\", e.this, self.format_time(e)\n            ),\n            exp.TsOrDsToTime: lambda self, e: self.func(\n                f\"{'TRY_' if e.args.get('safe') else ''}TO_TIME\", e.this, self.format_time(e)\n            ),\n            exp.Unhex: rename_func(\"HEX_DECODE_BINARY\"),\n            exp.UnixToTime: lambda self, e: self.func(\"TO_TIMESTAMP\", e.this, e.args.get(\"scale\")),\n            exp.Uuid: rename_func(\"UUID_STRING\"),\n            exp.VarMap: lambda self, e: var_map_sql(self, e, \"OBJECT_CONSTRUCT\"),\n            exp.Booland: rename_func(\"BOOLAND\"),\n            exp.Boolor: rename_func(\"BOOLOR\"),\n            exp.WeekOfYear: rename_func(\"WEEKISO\"),\n            exp.YearOfWeek: rename_func(\"YEAROFWEEK\"),\n            exp.YearOfWeekIso: rename_func(\"YEAROFWEEKISO\"),\n            exp.Xor: rename_func(\"BOOLXOR\"),\n            exp.ByteLength: rename_func(\"OCTET_LENGTH\"),\n            exp.Flatten: rename_func(\"ARRAY_FLATTEN\"),\n            exp.ArrayConcatAgg: lambda self, e: self.func(\n                \"ARRAY_FLATTEN\", exp.ArrayAgg(this=e.this)\n            ),\n            exp.SHA2Digest: lambda self, e: self.func(\n                \"SHA2_BINARY\", e.this, e.args.get(\"length\") or exp.Literal.number(256)\n            ),\n        }\n\n        def sortarray_sql(self, expression: exp.SortArray) -> str:\n            asc = expression.args.get(\"asc\")\n            nulls_first = expression.args.get(\"nulls_first\")\n            if asc == exp.false() and nulls_first == exp.true():\n                nulls_first = None\n            return self.func(\"ARRAY_SORT\", expression.this, asc, nulls_first)\n\n        def nthvalue_sql(self, expression: exp.NthValue) -> str:\n            result = self.func(\"NTH_VALUE\", expression.this, expression.args.get(\"offset\"))\n\n            from_first = expression.args.get(\"from_first\")\n\n            if from_first is not None:\n                if from_first:\n                    result = result + \" FROM FIRST\"\n                else:\n                    result = result + \" FROM LAST\"\n\n            return result\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.BIGDECIMAL: \"DOUBLE\",\n            exp.DType.NESTED: \"OBJECT\",\n            exp.DType.STRUCT: \"OBJECT\",\n            exp.DType.TEXT: \"VARCHAR\",\n        }\n\n        TOKEN_MAPPING = {\n            TokenType.AUTO_INCREMENT: \"AUTOINCREMENT\",\n        }\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.CredentialsProperty: exp.Properties.Location.POST_WITH,\n            exp.LocationProperty: exp.Properties.Location.POST_WITH,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.SetProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        UNSUPPORTED_VALUES_EXPRESSIONS = {\n            exp.Map,\n            exp.StarMap,\n            exp.Struct,\n            exp.VarMap,\n        }\n\n        RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS = (exp.ArrayAgg,)\n\n        def with_properties(self, properties: exp.Properties) -> str:\n            return self.properties(properties, wrapped=False, prefix=self.sep(\"\"), sep=\" \")\n\n        def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:\n            if expression.find(*self.UNSUPPORTED_VALUES_EXPRESSIONS):\n                values_as_table = False\n\n            return super().values_sql(expression, values_as_table=values_as_table)\n\n        def datatype_sql(self, expression: exp.DataType) -> str:\n            # Check if this is a FLOAT type nested inside a VECTOR type\n            # VECTOR only accepts FLOAT (not DOUBLE), INT, and STRING as element types\n            # https://docs.snowflake.com/en/sql-reference/data-types-vector\n            if expression.is_type(exp.DType.DOUBLE):\n                parent = expression.parent\n                if isinstance(parent, exp.DataType) and parent.is_type(exp.DType.VECTOR):\n                    # Preserve FLOAT for VECTOR types instead of mapping to synonym DOUBLE\n                    return \"FLOAT\"\n\n            expressions = expression.expressions\n            if expressions and expression.is_type(*exp.DataType.STRUCT_TYPES):\n                for field_type in expressions:\n                    # The correct syntax is OBJECT [ (<key> <value_type [NOT NULL] [, ...]) ]\n                    if isinstance(field_type, exp.DataType):\n                        return \"OBJECT\"\n                    if (\n                        isinstance(field_type, exp.ColumnDef)\n                        and field_type.this\n                        and field_type.this.is_string\n                    ):\n                        # Doing OBJECT('foo' VARCHAR) is invalid snowflake Syntax. Moreover, besides\n                        # converting 'foo' into an identifier, we also need to quote it because these\n                        # keys are case-sensitive. For example:\n                        #\n                        # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:x FROM t -- correct\n                        # WITH t AS (SELECT OBJECT_CONSTRUCT('x', 'y') AS c) SELECT c:X FROM t -- incorrect, returns NULL\n                        field_type.this.replace(exp.to_identifier(field_type.name, quoted=True))\n\n            return super().datatype_sql(expression)\n\n        def tonumber_sql(self, expression: exp.ToNumber) -> str:\n            return self.func(\n                \"TO_NUMBER\",\n                expression.this,\n                expression.args.get(\"format\"),\n                expression.args.get(\"precision\"),\n                expression.args.get(\"scale\"),\n            )\n\n        def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:\n            milli = expression.args.get(\"milli\")\n            if milli is not None:\n                milli_to_nano = milli.pop() * exp.Literal.number(1000000)\n                expression.set(\"nano\", milli_to_nano)\n\n            return rename_func(\"TIMESTAMP_FROM_PARTS\")(self, expression)\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            if expression.is_type(exp.DType.GEOGRAPHY):\n                return self.func(\"TO_GEOGRAPHY\", expression.this)\n            if expression.is_type(exp.DType.GEOMETRY):\n                return self.func(\"TO_GEOMETRY\", expression.this)\n\n            return super().cast_sql(expression, safe_prefix=safe_prefix)\n\n        def trycast_sql(self, expression: exp.TryCast) -> str:\n            value = expression.this\n\n            if value.type is None:\n                from sqlglot.optimizer.annotate_types import annotate_types\n\n                value = annotate_types(value, dialect=self.dialect)\n\n            # Snowflake requires that TRY_CAST's value be a string\n            # If TRY_CAST is being roundtripped (since Snowflake is the only dialect that sets \"requires_string\") or\n            # if we can deduce that the value is a string, then we can generate TRY_CAST\n            if expression.args.get(\"requires_string\") or value.is_type(*exp.DataType.TEXT_TYPES):\n                return super().trycast_sql(expression)\n\n            return self.cast_sql(expression)\n\n        def log_sql(self, expression: exp.Log) -> str:\n            if not expression.expression:\n                return self.func(\"LN\", expression.this)\n\n            return super().log_sql(expression)\n\n        def greatest_sql(self, expression: exp.Greatest) -> str:\n            name = \"GREATEST_IGNORE_NULLS\" if expression.args.get(\"ignore_nulls\") else \"GREATEST\"\n            return self.func(name, expression.this, *expression.expressions)\n\n        def least_sql(self, expression: exp.Least) -> str:\n            name = \"LEAST_IGNORE_NULLS\" if expression.args.get(\"ignore_nulls\") else \"LEAST\"\n            return self.func(name, expression.this, *expression.expressions)\n\n        def generator_sql(self, expression: exp.Generator) -> str:\n            args = []\n            rowcount = expression.args.get(\"rowcount\")\n            timelimit = expression.args.get(\"timelimit\")\n\n            if rowcount:\n                args.append(exp.Kwarg(this=exp.var(\"ROWCOUNT\"), expression=rowcount))\n            if timelimit:\n                args.append(exp.Kwarg(this=exp.var(\"TIMELIMIT\"), expression=timelimit))\n\n            return self.func(\"GENERATOR\", *args)\n\n        def unnest_sql(self, expression: exp.Unnest) -> str:\n            unnest_alias = expression.args.get(\"alias\")\n            offset = expression.args.get(\"offset\")\n\n            unnest_alias_columns = unnest_alias.columns if unnest_alias else []\n            value = seq_get(unnest_alias_columns, 0) or exp.to_identifier(\"value\")\n\n            columns = [\n                exp.to_identifier(\"seq\"),\n                exp.to_identifier(\"key\"),\n                exp.to_identifier(\"path\"),\n                offset.pop() if isinstance(offset, exp.Expr) else exp.to_identifier(\"index\"),\n                value,\n                exp.to_identifier(\"this\"),\n            ]\n\n            if unnest_alias:\n                unnest_alias.set(\"columns\", columns)\n            else:\n                unnest_alias = exp.TableAlias(this=\"_u\", columns=columns)\n\n            table_input = self.sql(expression.expressions[0])\n            if not table_input.startswith(\"INPUT =>\"):\n                table_input = f\"INPUT => {table_input}\"\n\n            expression_parent = expression.parent\n\n            explode = (\n                f\"FLATTEN({table_input})\"\n                if isinstance(expression_parent, exp.Lateral)\n                else f\"TABLE(FLATTEN({table_input}))\"\n            )\n            alias = self.sql(unnest_alias)\n            alias = f\" AS {alias}\" if alias else \"\"\n            value = (\n                \"\"\n                if isinstance(expression_parent, (exp.From, exp.Join, exp.Lateral))\n                else f\"{value} FROM \"\n            )\n\n            return f\"{value}{explode}{alias}\"\n\n        def show_sql(self, expression: exp.Show) -> str:\n            terse = \"TERSE \" if expression.args.get(\"terse\") else \"\"\n            history = \" HISTORY\" if expression.args.get(\"history\") else \"\"\n            like = self.sql(expression, \"like\")\n            like = f\" LIKE {like}\" if like else \"\"\n\n            scope = self.sql(expression, \"scope\")\n            scope = f\" {scope}\" if scope else \"\"\n\n            scope_kind = self.sql(expression, \"scope_kind\")\n            if scope_kind:\n                scope_kind = f\" IN {scope_kind}\"\n\n            starts_with = self.sql(expression, \"starts_with\")\n            if starts_with:\n                starts_with = f\" STARTS WITH {starts_with}\"\n\n            limit = self.sql(expression, \"limit\")\n\n            from_ = self.sql(expression, \"from_\")\n            if from_:\n                from_ = f\" FROM {from_}\"\n\n            privileges = self.expressions(expression, key=\"privileges\", flat=True)\n            privileges = f\" WITH PRIVILEGES {privileges}\" if privileges else \"\"\n\n            return f\"SHOW {terse}{expression.name}{history}{like}{scope_kind}{scope}{starts_with}{limit}{from_}{privileges}\"\n\n        def describe_sql(self, expression: exp.Describe) -> str:\n            kind_value = expression.args.get(\"kind\") or \"TABLE\"\n\n            properties = expression.args.get(\"properties\")\n            if properties:\n                qualifier = self.expressions(properties, sep=\" \")\n                kind = f\" {qualifier} {kind_value}\"\n            else:\n                kind = f\" {kind_value}\"\n\n            this = f\" {self.sql(expression, 'this')}\"\n            expressions = self.expressions(expression, flat=True)\n            expressions = f\" {expressions}\" if expressions else \"\"\n            return f\"DESCRIBE{kind}{this}{expressions}\"\n\n        def generatedasidentitycolumnconstraint_sql(\n            self, expression: exp.GeneratedAsIdentityColumnConstraint\n        ) -> str:\n            start = expression.args.get(\"start\")\n            start = f\" START {start}\" if start else \"\"\n            increment = expression.args.get(\"increment\")\n            increment = f\" INCREMENT {increment}\" if increment else \"\"\n\n            order = expression.args.get(\"order\")\n            if order is not None:\n                order_clause = \" ORDER\" if order else \" NOORDER\"\n            else:\n                order_clause = \"\"\n\n            return f\"AUTOINCREMENT{start}{increment}{order_clause}\"\n\n        def cluster_sql(self, expression: exp.Cluster) -> str:\n            return f\"CLUSTER BY ({self.expressions(expression, flat=True)})\"\n\n        def struct_sql(self, expression: exp.Struct) -> str:\n            if len(expression.expressions) == 1:\n                arg = expression.expressions[0]\n                if arg.is_star or (isinstance(arg, exp.ILike) and arg.left.is_star):\n                    # Wildcard syntax: https://docs.snowflake.com/en/sql-reference/data-types-semistructured#object\n                    return f\"{{{self.sql(expression.expressions[0])}}}\"\n\n            keys = []\n            values = []\n\n            for i, e in enumerate(expression.expressions):\n                if isinstance(e, exp.PropertyEQ):\n                    keys.append(\n                        exp.Literal.string(e.name) if isinstance(e.this, exp.Identifier) else e.this\n                    )\n                    values.append(e.expression)\n                else:\n                    keys.append(exp.Literal.string(f\"_{i}\"))\n                    values.append(e)\n\n            return self.func(\"OBJECT_CONSTRUCT\", *flatten(zip(keys, values)))\n\n        @unsupported_args(\"weight\", \"accuracy\")\n        def approxquantile_sql(self, expression: exp.ApproxQuantile) -> str:\n            return self.func(\"APPROX_PERCENTILE\", expression.this, expression.args.get(\"quantile\"))\n\n        def alterset_sql(self, expression: exp.AlterSet) -> str:\n            exprs = self.expressions(expression, flat=True)\n            exprs = f\" {exprs}\" if exprs else \"\"\n            file_format = self.expressions(expression, key=\"file_format\", flat=True, sep=\" \")\n            file_format = f\" STAGE_FILE_FORMAT = ({file_format})\" if file_format else \"\"\n            copy_options = self.expressions(expression, key=\"copy_options\", flat=True, sep=\" \")\n            copy_options = f\" STAGE_COPY_OPTIONS = ({copy_options})\" if copy_options else \"\"\n            tag = self.expressions(expression, key=\"tag\", flat=True)\n            tag = f\" TAG {tag}\" if tag else \"\"\n\n            return f\"SET{exprs}{file_format}{copy_options}{tag}\"\n\n        def strtotime_sql(self, expression: exp.StrToTime):\n            # target_type is stored as a DataType instance\n            target_type = expression.args.get(\"target_type\")\n\n            # Get the type enum from DataType instance or from type annotation\n            if isinstance(target_type, exp.DataType):\n                type_enum = target_type.this\n            elif expression.type:\n                type_enum = expression.type.this\n            else:\n                type_enum = exp.DType.TIMESTAMP\n\n            func_name = TIMESTAMP_TYPES.get(type_enum, \"TO_TIMESTAMP\")\n\n            return self.func(\n                f\"{'TRY_' if expression.args.get('safe') else ''}{func_name}\",\n                expression.this,\n                self.format_time(expression),\n            )\n\n        def timestampsub_sql(self, expression: exp.TimestampSub):\n            return self.sql(\n                exp.TimestampAdd(\n                    this=expression.this,\n                    expression=expression.expression * -1,\n                    unit=expression.unit,\n                )\n            )\n\n        def jsonextract_sql(self, expression: exp.JSONExtract):\n            this = expression.this\n\n            # JSON strings are valid coming from other dialects such as BQ so\n            # for these cases we PARSE_JSON preemptively\n            if not isinstance(this, (exp.ParseJSON, exp.JSONExtract)) and not expression.args.get(\n                \"requires_json\"\n            ):\n                this = exp.ParseJSON(this=this)\n\n            return self.func(\n                \"GET_PATH\",\n                this,\n                expression.expression,\n            )\n\n        def timetostr_sql(self, expression: exp.TimeToStr) -> str:\n            this = expression.this\n            if this.is_string:\n                this = exp.cast(this, exp.DType.TIMESTAMP)\n\n            return self.func(\"TO_CHAR\", this, self.format_time(expression))\n\n        def datesub_sql(self, expression: exp.DateSub) -> str:\n            value = expression.expression\n            if value:\n                value.replace(value * (-1))\n            else:\n                self.unsupported(\"DateSub cannot be transpiled if the subtracted count is unknown\")\n\n            return date_delta_sql(\"DATEADD\")(self, expression)\n\n        def select_sql(self, expression: exp.Select) -> str:\n            limit = expression.args.get(\"limit\")\n            offset = expression.args.get(\"offset\")\n            if offset and not limit:\n                expression.limit(exp.Null(), copy=False)\n            return super().select_sql(expression)\n\n        def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:\n            is_materialized = expression.find(exp.MaterializedProperty)\n            copy_grants_property = expression.find(exp.CopyGrantsProperty)\n\n            if expression.kind == \"VIEW\" and is_materialized and copy_grants_property:\n                # For materialized views, COPY GRANTS is located *before* the columns list\n                # This is in contrast to normal views where COPY GRANTS is located *after* the columns list\n                # We default CopyGrantsProperty to POST_SCHEMA which means we need to output it POST_NAME if a materialized view is detected\n                # ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax\n                # ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax\n                post_schema_properties = locations[exp.Properties.Location.POST_SCHEMA]\n                post_schema_properties.pop(post_schema_properties.index(copy_grants_property))\n\n                this_name = self.sql(expression.this, \"this\")\n                copy_grants = self.sql(copy_grants_property)\n                this_schema = self.schema_columns_sql(expression.this)\n                this_schema = f\"{self.sep()}{this_schema}\" if this_schema else \"\"\n\n                return f\"{this_name}{self.sep()}{copy_grants}{this_schema}\"\n\n            return super().createable_sql(expression, locations)\n\n        def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:\n            this = expression.this\n\n            # If an ORDER BY clause is present, we need to remove it from ARRAY_AGG\n            # and add it later as part of the WITHIN GROUP clause\n            order = this if isinstance(this, exp.Order) else None\n            if order:\n                expression.set(\"this\", order.this.pop())\n\n            expr_sql = super().arrayagg_sql(expression)\n\n            if order:\n                expr_sql = self.sql(exp.WithinGroup(this=expr_sql, expression=order))\n\n            return expr_sql\n\n        def arraytostring_sql(self, expression: exp.ArrayToString) -> str:\n            return self.func(\"ARRAY_TO_STRING\", expression.this, expression.expression)\n\n        def array_sql(self, expression: exp.Array) -> str:\n            expressions = expression.expressions\n\n            first_expr = seq_get(expressions, 0)\n            if isinstance(first_expr, exp.Select):\n                # SELECT AS STRUCT foo AS alias_foo -> ARRAY_AGG(OBJECT_CONSTRUCT('alias_foo', foo))\n                if first_expr.text(\"kind\").upper() == \"STRUCT\":\n                    object_construct_args = []\n                    for expr in first_expr.expressions:\n                        # Alias case: SELECT AS STRUCT foo AS alias_foo -> OBJECT_CONSTRUCT('alias_foo', foo)\n                        # Column case: SELECT AS STRUCT foo -> OBJECT_CONSTRUCT('foo', foo)\n                        name = expr.this if isinstance(expr, exp.Alias) else expr\n\n                        object_construct_args.extend([exp.Literal.string(expr.alias_or_name), name])\n\n                    array_agg = exp.ArrayAgg(\n                        this=build_object_construct(args=object_construct_args)\n                    )\n\n                    first_expr.set(\"kind\", None)\n                    first_expr.set(\"expressions\", [array_agg])\n\n                    return self.sql(first_expr.subquery())\n\n            return inline_array_sql(self, expression)\n\n        def currentdate_sql(self, expression: exp.CurrentDate) -> str:\n            zone = self.sql(expression, \"this\")\n            if not zone:\n                return super().currentdate_sql(expression)\n\n            expr = exp.Cast(\n                this=exp.ConvertTimezone(target_tz=zone, timestamp=exp.CurrentTimestamp()),\n                to=exp.DataType(this=exp.DType.DATE),\n            )\n            return self.sql(expr)\n\n        def dot_sql(self, expression: exp.Dot) -> str:\n            this = expression.this\n\n            if not this.type:\n                from sqlglot.optimizer.annotate_types import annotate_types\n\n                this = annotate_types(this, dialect=self.dialect)\n\n            if not isinstance(this, exp.Dot) and this.is_type(exp.DType.STRUCT):\n                # Generate colon notation for the top level STRUCT\n                return f\"{self.sql(this)}:{self.sql(expression, 'expression')}\"\n\n            return super().dot_sql(expression)\n\n        def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:\n            return f\"{self.sql(expression, 'this')}!{self.sql(expression, 'expression')}\"\n\n        def format_sql(self, expression: exp.Format) -> str:\n            if expression.name.lower() == \"%s\" and len(expression.expressions) == 1:\n                return self.func(\"TO_CHAR\", expression.expressions[0])\n\n            return self.function_fallback_sql(expression)\n\n        def splitpart_sql(self, expression: exp.SplitPart) -> str:\n            # Set part_index to 1 if missing\n            if not expression.args.get(\"delimiter\"):\n                expression.set(\"delimiter\", exp.Literal.string(\" \"))\n\n            if not expression.args.get(\"part_index\"):\n                expression.set(\"part_index\", exp.Literal.number(1))\n\n            return rename_func(\"SPLIT_PART\")(self, expression)\n\n        def uniform_sql(self, expression: exp.Uniform) -> str:\n            gen = expression.args.get(\"gen\")\n            seed = expression.args.get(\"seed\")\n\n            # From Databricks UNIFORM(min, max, seed) -> Wrap gen in RANDOM(seed)\n            if seed:\n                gen = exp.Rand(this=seed)\n\n            # No gen argument (from Databricks 2-arg UNIFORM(min, max)) -> Add RANDOM()\n            if not gen:\n                gen = exp.Rand()\n\n            return self.func(\"UNIFORM\", expression.this, expression.expression, gen)\n\n        def window_sql(self, expression: exp.Window) -> str:\n            spec = expression.args.get(\"spec\")\n            this = expression.this\n\n            if (\n                (\n                    isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)\n                    or (\n                        isinstance(this, (exp.RespectNulls, exp.IgnoreNulls))\n                        and isinstance(this.this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)\n                    )\n                )\n                and spec\n                and (\n                    spec.text(\"kind\").upper() == \"ROWS\"\n                    and spec.text(\"start\").upper() == \"UNBOUNDED\"\n                    and spec.text(\"start_side\").upper() == \"PRECEDING\"\n                    and spec.text(\"end\").upper() == \"UNBOUNDED\"\n                    and spec.text(\"end_side\").upper() == \"FOLLOWING\"\n                )\n            ):\n                # omit the default window from window ranking functions\n                expression.set(\"spec\", None)\n            return super().window_sql(expression)\n"
  },
  {
    "path": "sqlglot/dialects/solr.py",
    "content": "from sqlglot import tokens\nfrom sqlglot.dialects.dialect import Dialect, NormalizationStrategy\nfrom sqlglot.parsers.solr import SolrParser\n\n\n# https://solr.apache.org/guide/solr/latest/query-guide/sql-query.html\n\n\nclass Solr(Dialect):\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n    DPIPE_IS_STRING_CONCAT = False\n\n    Parser = SolrParser\n\n    class Tokenizer(tokens.Tokenizer):\n        QUOTES = [\"'\"]\n        IDENTIFIERS = [\"`\"]\n"
  },
  {
    "path": "sqlglot/dialects/spark.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import (\n    array_append_sql,\n    rename_func,\n    unit_to_var,\n    timestampdiff_sql,\n    date_delta_to_binary_interval_op,\n    groupconcat_sql,\n)\nfrom sqlglot.parsers.spark import SparkParser\nfrom sqlglot import generator\nfrom sqlglot.dialects.spark2 import Spark2, temporary_storage_provider\nfrom sqlglot.typing.spark import EXPRESSION_METADATA\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.transforms import (\n    ctas_with_tmp_tables_to_create_tmp_view,\n    remove_unique_constraints,\n    preprocess,\n    move_partitioned_by_to_schema_columns,\n)\n\n\ndef _normalize_partition(e: exp.Expr) -> exp.Expr:\n    \"\"\"Normalize the expressions in PARTITION BY (<expression>, <expression>, ...)\"\"\"\n    if isinstance(e, str):\n        return exp.to_identifier(e)\n    if isinstance(e, exp.Literal):\n        return exp.to_identifier(e.name)\n    return e\n\n\ndef _dateadd_sql(self: Spark.Generator, expression: exp.TsOrDsAdd | exp.TimestampAdd) -> str:\n    if not expression.unit or (\n        isinstance(expression, exp.TsOrDsAdd) and expression.text(\"unit\").upper() == \"DAY\"\n    ):\n        # Coming from Hive/Spark2 DATE_ADD or roundtripping the 2-arg version of Spark3/DB\n        return self.func(\"DATE_ADD\", expression.this, expression.expression)\n\n    this = self.func(\n        \"DATE_ADD\",\n        unit_to_var(expression),\n        expression.expression,\n        expression.this,\n    )\n\n    if isinstance(expression, exp.TsOrDsAdd):\n        # The 3 arg version of DATE_ADD produces a timestamp in Spark3/DB but possibly not\n        # in other dialects\n        return_type = expression.return_type\n        if not return_type.is_type(exp.DType.TIMESTAMP, exp.DType.DATETIME):\n            this = f\"CAST({this} AS {return_type})\"\n\n    return this\n\n\ndef _groupconcat_sql(self: Spark.Generator, expression: exp.GroupConcat) -> str:\n    if self.dialect.version < (4,):\n        expr = exp.ArrayToString(\n            this=exp.ArrayAgg(this=expression.this),\n            expression=expression.args.get(\"separator\") or exp.Literal.string(\"\"),\n        )\n        return self.sql(expr)\n\n    return groupconcat_sql(self, expression)\n\n\nclass Spark(Spark2):\n    SUPPORTS_ORDER_BY_ALL = True\n    SUPPORTS_NULL_TYPE = True\n    ARRAY_FUNCS_PROPAGATES_NULLS = True\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    class Tokenizer(Spark2.Tokenizer):\n        STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS = False\n\n        RAW_STRINGS = [\n            (prefix + q, q)\n            for q in t.cast(t.List[str], Spark2.Tokenizer.QUOTES)\n            for prefix in (\"r\", \"R\")\n        ]\n\n        KEYWORDS = {\n            **Spark2.Tokenizer.KEYWORDS,\n            \"DECLARE\": TokenType.DECLARE,\n        }\n\n    Parser = SparkParser\n\n    class Generator(Spark2.Generator):\n        SUPPORTS_TO_NUMBER = True\n        PAD_FILL_PATTERN_IS_REQUIRED = False\n        SUPPORTS_CONVERT_TIMEZONE = True\n        SUPPORTS_MEDIAN = True\n        SUPPORTS_UNIX_SECONDS = True\n        SUPPORTS_DECODE_CASE = True\n        SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = True\n\n        TYPE_MAPPING = {\n            **Spark2.Generator.TYPE_MAPPING,\n            exp.DType.MONEY: \"DECIMAL(15, 4)\",\n            exp.DType.SMALLMONEY: \"DECIMAL(6, 4)\",\n            exp.DType.UUID: \"STRING\",\n            exp.DType.TIMESTAMPLTZ: \"TIMESTAMP_LTZ\",\n            exp.DType.TIMESTAMPNTZ: \"TIMESTAMP_NTZ\",\n        }\n\n        TRANSFORMS = {\n            **Spark2.Generator.TRANSFORMS,\n            exp.ArrayConstructCompact: lambda self, e: self.func(\n                \"ARRAY_COMPACT\", self.func(\"ARRAY\", *e.expressions)\n            ),\n            exp.ArrayInsert: lambda self, e: self.func(\n                \"ARRAY_INSERT\", e.this, e.args.get(\"position\"), e.expression\n            ),\n            exp.ArrayAppend: array_append_sql(\"ARRAY_APPEND\"),\n            exp.ArrayPrepend: array_append_sql(\"ARRAY_PREPEND\"),\n            exp.BitwiseAndAgg: rename_func(\"BIT_AND\"),\n            exp.BitwiseOrAgg: rename_func(\"BIT_OR\"),\n            exp.BitwiseXorAgg: rename_func(\"BIT_XOR\"),\n            exp.BitwiseCount: rename_func(\"BIT_COUNT\"),\n            exp.Create: preprocess(\n                [\n                    remove_unique_constraints,\n                    lambda e: ctas_with_tmp_tables_to_create_tmp_view(\n                        e, temporary_storage_provider\n                    ),\n                    move_partitioned_by_to_schema_columns,\n                ]\n            ),\n            exp.CurrentVersion: rename_func(\"VERSION\"),\n            exp.DateFromUnixDate: rename_func(\"DATE_FROM_UNIX_DATE\"),\n            exp.DatetimeAdd: date_delta_to_binary_interval_op(cast=False),\n            exp.DatetimeSub: date_delta_to_binary_interval_op(cast=False),\n            exp.GroupConcat: _groupconcat_sql,\n            exp.EndsWith: rename_func(\"ENDSWITH\"),\n            exp.JSONKeys: rename_func(\"JSON_OBJECT_KEYS\"),\n            exp.PartitionedByProperty: lambda self, e: (\n                f\"PARTITIONED BY {self.wrap(self.expressions(sqls=[_normalize_partition(e) for e in e.this.expressions], skip_first=True))}\"\n            ),\n            exp.SafeAdd: rename_func(\"TRY_ADD\"),\n            exp.SafeMultiply: rename_func(\"TRY_MULTIPLY\"),\n            exp.SafeSubtract: rename_func(\"TRY_SUBTRACT\"),\n            exp.StartsWith: rename_func(\"STARTSWITH\"),\n            exp.TimeAdd: date_delta_to_binary_interval_op(cast=False),\n            exp.TimeSub: date_delta_to_binary_interval_op(cast=False),\n            exp.TsOrDsAdd: _dateadd_sql,\n            exp.TimestampAdd: _dateadd_sql,\n            exp.TimestampFromParts: rename_func(\"MAKE_TIMESTAMP\"),\n            exp.TimestampSub: date_delta_to_binary_interval_op(cast=False),\n            exp.DatetimeDiff: timestampdiff_sql,\n            exp.TimestampDiff: timestampdiff_sql,\n            exp.TryCast: lambda self, e: (\n                self.trycast_sql(e) if e.args.get(\"safe\") else self.cast_sql(e)\n            ),\n        }\n        TRANSFORMS.pop(exp.AnyValue)\n        TRANSFORMS.pop(exp.DateDiff)\n        TRANSFORMS.pop(exp.With)\n\n        def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:\n            return generator.Generator.ignorenulls_sql(self, expression)\n\n        def bracket_sql(self, expression: exp.Bracket) -> str:\n            if expression.args.get(\"safe\"):\n                key = seq_get(self.bracket_offset_expressions(expression, index_offset=1), 0)\n                return self.func(\"TRY_ELEMENT_AT\", expression.this, key)\n\n            return super().bracket_sql(expression)\n\n        def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:\n            return f\"GENERATED ALWAYS AS ({self.sql(expression, 'this')})\"\n\n        def anyvalue_sql(self, expression: exp.AnyValue) -> str:\n            return self.function_fallback_sql(expression)\n\n        def datediff_sql(self, expression: exp.DateDiff) -> str:\n            end = self.sql(expression, \"this\")\n            start = self.sql(expression, \"expression\")\n\n            if expression.unit:\n                return self.func(\"DATEDIFF\", unit_to_var(expression), start, end)\n\n            return self.func(\"DATEDIFF\", end, start)\n\n        def placeholder_sql(self, expression: exp.Placeholder) -> str:\n            if not expression.args.get(\"widget\"):\n                return super().placeholder_sql(expression)\n\n            return f\"{{{expression.name}}}\"\n\n        def readparquet_sql(self, expression: exp.ReadParquet) -> str:\n            if len(expression.expressions) != 1:\n                self.unsupported(\"READ_PARQUET with multiple arguments is not supported\")\n                return \"\"\n\n            parquet_file = expression.expressions[0]\n            return f\"parquet.`{parquet_file.name}`\"\n\n        def ifblock_sql(self, expression: exp.IfBlock) -> str:\n            condition = expression.this\n            true_block = expression.args.get(\"true\")\n\n            condition_expr = None\n            if isinstance(condition, exp.Not):\n                inner = condition.this\n                if isinstance(inner, exp.Is) and isinstance(inner.expression, exp.Null):\n                    condition_expr = inner.this\n\n            if isinstance(condition_expr, exp.ObjectId):\n                object_type = condition_expr.expression\n                if (\n                    (object_type is None or object_type.name.upper() == \"U\")\n                    and isinstance(true_block, exp.Block)\n                    and isinstance(drop := true_block.expressions[0], exp.Drop)\n                ):\n                    drop.set(\"exists\", True)\n                    return self.sql(drop)\n\n            return super().ifblock_sql(expression)\n"
  },
  {
    "path": "sqlglot/dialects/spark2.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, transforms\nfrom sqlglot.dialects.dialect import (\n    bracket_to_element_at_sql,\n    is_parse_json,\n    rename_func,\n    unit_to_str,\n)\nfrom sqlglot.dialects.hive import Hive\nfrom sqlglot.parsers.spark2 import Spark2Parser\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.transforms import (\n    preprocess,\n    remove_unique_constraints,\n    ctas_with_tmp_tables_to_create_tmp_view,\n    move_schema_columns_to_partitioned_by,\n)\nfrom sqlglot.typing.spark2 import EXPRESSION_METADATA\n\n\ndef _map_sql(self: Spark2.Generator, expression: exp.Map) -> str:\n    keys = expression.args.get(\"keys\")\n    values = expression.args.get(\"values\")\n\n    if not keys or not values:\n        return self.func(\"MAP\")\n\n    return self.func(\"MAP_FROM_ARRAYS\", keys, values)\n\n\ndef _str_to_date(self: Spark2.Generator, expression: exp.StrToDate) -> str:\n    time_format = self.format_time(expression)\n    if time_format == Hive.DATE_FORMAT:\n        return self.func(\"TO_DATE\", expression.this)\n    return self.func(\"TO_DATE\", expression.this, time_format)\n\n\ndef _unix_to_time_sql(self: Spark2.Generator, expression: exp.UnixToTime) -> str:\n    scale = expression.args.get(\"scale\")\n    timestamp = expression.this\n\n    if scale is None:\n        return self.sql(exp.cast(exp.func(\"from_unixtime\", timestamp), exp.DType.TIMESTAMP))\n    if scale == exp.UnixToTime.SECONDS:\n        return self.func(\"TIMESTAMP_SECONDS\", timestamp)\n    if scale == exp.UnixToTime.MILLIS:\n        return self.func(\"TIMESTAMP_MILLIS\", timestamp)\n    if scale == exp.UnixToTime.MICROS:\n        return self.func(\"TIMESTAMP_MICROS\", timestamp)\n\n    unix_seconds = exp.Div(this=timestamp, expression=exp.func(\"POW\", 10, scale))\n    return self.func(\"TIMESTAMP_SECONDS\", unix_seconds)\n\n\ndef _unalias_pivot(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Spark doesn't allow PIVOT aliases, so we need to remove them and possibly wrap a\n    pivoted source in a subquery with the same alias to preserve the query's semantics.\n\n    Example:\n        >>> from sqlglot import parse_one\n        >>> expr = parse_one(\"SELECT piv.x FROM tbl PIVOT (SUM(a) FOR b IN ('x')) piv\")\n        >>> print(_unalias_pivot(expr).sql(dialect=\"spark\"))\n        SELECT piv.x FROM (SELECT * FROM tbl PIVOT(SUM(a) FOR b IN ('x'))) AS piv\n    \"\"\"\n    if isinstance(expression, exp.From) and expression.this.args.get(\"pivots\"):\n        pivot = expression.this.args[\"pivots\"][0]\n        if pivot.alias:\n            alias = pivot.args[\"alias\"].pop()\n            return exp.From(\n                this=expression.this.replace(\n                    exp.select(\"*\")\n                    .from_(expression.this.copy(), copy=False)\n                    .subquery(alias=alias, copy=False)\n                )\n            )\n\n    return expression\n\n\ndef _unqualify_pivot_columns(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Spark doesn't allow the column referenced in the PIVOT's field to be qualified,\n    so we need to unqualify it.\n\n    Example:\n        >>> from sqlglot import parse_one\n        >>> expr = parse_one(\"SELECT * FROM tbl PIVOT (SUM(tbl.sales) FOR tbl.quarter IN ('Q1', 'Q2'))\")\n        >>> print(_unqualify_pivot_columns(expr).sql(dialect=\"spark\"))\n        SELECT * FROM tbl PIVOT(SUM(tbl.sales) FOR quarter IN ('Q1', 'Q2'))\n    \"\"\"\n    if isinstance(expression, exp.Pivot):\n        expression.set(\n            \"fields\", [transforms.unqualify_columns(field) for field in expression.fields]\n        )\n\n    return expression\n\n\ndef temporary_storage_provider(expression: exp.Expr) -> exp.Expr:\n    # spark2, spark, Databricks require a storage provider for temporary tables\n    provider = exp.FileFormatProperty(this=exp.Literal.string(\"parquet\"))\n    expression.args[\"properties\"].append(\"expressions\", provider)\n    return expression\n\n\nclass Spark2(Hive):\n    ALTER_TABLE_SUPPORTS_CASCADE = False\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    # https://spark.apache.org/docs/latest/api/sql/index.html#initcap\n    # https://docs.databricks.com/aws/en/sql/language-manual/functions/initcap\n    # https://github.com/apache/spark/blob/master/common/unsafe/src/main/java/org/apache/spark/unsafe/types/UTF8String.java#L859-L905\n    INITCAP_DEFAULT_DELIMITER_CHARS = \" \"\n\n    class Tokenizer(Hive.Tokenizer):\n        HEX_STRINGS = [(\"X'\", \"'\"), (\"x'\", \"'\")]\n\n        KEYWORDS = {\n            **Hive.Tokenizer.KEYWORDS,\n            \"TIMESTAMP\": TokenType.TIMESTAMPTZ,\n        }\n\n    Parser = Spark2Parser\n\n    class Generator(Hive.Generator):\n        QUERY_HINTS = True\n        NVL2_SUPPORTED = True\n        CAN_IMPLEMENT_ARRAY_ANY = True\n        ALTER_SET_TYPE = \"TYPE\"\n\n        PROPERTIES_LOCATION = {\n            **Hive.Generator.PROPERTIES_LOCATION,\n            exp.EngineProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.AutoIncrementProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.CharacterSetProperty: exp.Properties.Location.UNSUPPORTED,\n            exp.CollateProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        TS_OR_DS_EXPRESSIONS = (\n            *Hive.Generator.TS_OR_DS_EXPRESSIONS,\n            exp.DayOfMonth,\n            exp.DayOfWeek,\n            exp.DayOfYear,\n            exp.WeekOfYear,\n        )\n\n        TRANSFORMS = {\n            **Hive.Generator.TRANSFORMS,\n            exp.ApproxDistinct: rename_func(\"APPROX_COUNT_DISTINCT\"),\n            exp.ArraySum: lambda self, e: (\n                f\"AGGREGATE({self.sql(e, 'this')}, 0, (acc, x) -> acc + x, acc -> acc)\"\n            ),\n            exp.ArrayToString: rename_func(\"ARRAY_JOIN\"),\n            exp.ArraySlice: rename_func(\"SLICE\"),\n            exp.AtTimeZone: lambda self, e: self.func(\n                \"FROM_UTC_TIMESTAMP\", e.this, e.args.get(\"zone\")\n            ),\n            exp.BitwiseLeftShift: rename_func(\"SHIFTLEFT\"),\n            exp.BitwiseRightShift: rename_func(\"SHIFTRIGHT\"),\n            exp.Create: preprocess(\n                [\n                    remove_unique_constraints,\n                    lambda e: ctas_with_tmp_tables_to_create_tmp_view(\n                        e, temporary_storage_provider\n                    ),\n                    move_schema_columns_to_partitioned_by,\n                ]\n            ),\n            exp.DateFromParts: rename_func(\"MAKE_DATE\"),\n            exp.DateTrunc: lambda self, e: self.func(\"TRUNC\", e.this, unit_to_str(e)),\n            exp.DayOfMonth: rename_func(\"DAYOFMONTH\"),\n            exp.DayOfWeek: rename_func(\"DAYOFWEEK\"),\n            # (DAY_OF_WEEK(datetime) % 7) + 1 is equivalent to DAYOFWEEK_ISO(datetime)\n            exp.DayOfWeekIso: lambda self, e: f\"(({self.func('DAYOFWEEK', e.this)} % 7) + 1)\",\n            exp.DayOfYear: rename_func(\"DAYOFYEAR\"),\n            exp.Format: rename_func(\"FORMAT_STRING\"),\n            exp.From: transforms.preprocess([_unalias_pivot]),\n            exp.FromTimeZone: lambda self, e: self.func(\n                \"TO_UTC_TIMESTAMP\", e.this, e.args.get(\"zone\")\n            ),\n            exp.LogicalAnd: rename_func(\"BOOL_AND\"),\n            exp.LogicalOr: rename_func(\"BOOL_OR\"),\n            exp.Map: _map_sql,\n            exp.Pivot: transforms.preprocess([_unqualify_pivot_columns]),\n            exp.Reduce: rename_func(\"AGGREGATE\"),\n            exp.RegexpReplace: lambda self, e: self.func(\n                \"REGEXP_REPLACE\",\n                e.this,\n                e.expression,\n                e.args[\"replacement\"],\n                e.args.get(\"position\"),\n            ),\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_qualify,\n                    transforms.eliminate_distinct_on,\n                    transforms.unnest_to_explode,\n                    transforms.any_to_exists,\n                ]\n            ),\n            exp.SHA2Digest: lambda self, e: self.func(\n                \"SHA2\", e.this, e.args.get(\"length\") or exp.Literal.number(256)\n            ),\n            exp.StrToDate: _str_to_date,\n            exp.StrToTime: lambda self, e: self.func(\"TO_TIMESTAMP\", e.this, self.format_time(e)),\n            exp.TimestampTrunc: lambda self, e: self.func(\"DATE_TRUNC\", unit_to_str(e), e.this),\n            exp.UnixToTime: _unix_to_time_sql,\n            exp.VariancePop: rename_func(\"VAR_POP\"),\n            exp.WeekOfYear: rename_func(\"WEEKOFYEAR\"),\n            exp.WithinGroup: transforms.preprocess(\n                [transforms.remove_within_group_for_percentiles]\n            ),\n        }\n        TRANSFORMS.pop(exp.ArraySort)\n        TRANSFORMS.pop(exp.ILike)\n        TRANSFORMS.pop(exp.Left)\n        TRANSFORMS.pop(exp.MonthsBetween)\n        TRANSFORMS.pop(exp.Right)\n\n        WRAP_DERIVED_VALUES = False\n        CREATE_FUNCTION_RETURN_AS = False\n\n        def struct_sql(self, expression: exp.Struct) -> str:\n            from sqlglot.generator import Generator\n\n            return Generator.struct_sql(self, expression)\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            arg = expression.this\n            is_json_extract = isinstance(\n                arg, (exp.JSONExtract, exp.JSONExtractScalar)\n            ) and not arg.args.get(\"variant_extract\")\n\n            # We can't use a non-nested type (eg. STRING) as a schema\n            if expression.to.args.get(\"nested\") and (is_parse_json(arg) or is_json_extract):\n                schema = f\"'{self.sql(expression, 'to')}'\"\n                return self.func(\"FROM_JSON\", arg if is_json_extract else arg.this, schema)\n\n            if is_parse_json(expression):\n                return self.func(\"TO_JSON\", arg)\n\n            return super(Hive.Generator, self).cast_sql(expression, safe_prefix=safe_prefix)\n\n        def fileformatproperty_sql(self, expression: exp.FileFormatProperty) -> str:\n            if expression.args.get(\"hive_format\"):\n                return super().fileformatproperty_sql(expression)\n\n            return f\"USING {expression.name.upper()}\"\n\n        def altercolumn_sql(self, expression: exp.AlterColumn) -> str:\n            this = self.sql(expression, \"this\")\n            new_name = self.sql(expression, \"rename_to\") or this\n            comment = self.sql(expression, \"comment\")\n            if new_name == this:\n                if comment:\n                    return f\"ALTER COLUMN {this} COMMENT {comment}\"\n                return super(Hive.Generator, self).altercolumn_sql(expression)\n            return f\"RENAME COLUMN {this} TO {new_name}\"\n\n        def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:\n            return super(Hive.Generator, self).renamecolumn_sql(expression)\n\n        def bracket_sql(self, expression: exp.Bracket) -> str:\n            if expression.args.get(\"safe\") is False:\n                return bracket_to_element_at_sql(self, expression)\n\n            return super().bracket_sql(expression)\n"
  },
  {
    "path": "sqlglot/dialects/sqlite.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    NormalizationStrategy,\n    any_value_to_max_sql,\n    arrow_json_extract_sql,\n    concat_to_dpipe_sql,\n    count_if_to_sum,\n    no_ilike_sql,\n    no_pivot_sql,\n    no_tablesample_sql,\n    no_trycast_sql,\n    rename_func,\n    strposition_sql,\n)\nfrom sqlglot.generator import unsupported_args\nfrom sqlglot.parsers.sqlite import SQLiteParser\nfrom sqlglot.tokens import TokenType\n\n\ndef _transform_create(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Move primary key to a column and enforce auto_increment on primary keys.\"\"\"\n    schema = expression.this\n\n    if isinstance(expression, exp.Create) and isinstance(schema, exp.Schema):\n        defs = {}\n        primary_key = None\n\n        for e in schema.expressions:\n            if isinstance(e, exp.ColumnDef):\n                defs[e.name] = e\n            elif isinstance(e, exp.PrimaryKey):\n                primary_key = e\n\n        if primary_key and len(primary_key.expressions) == 1:\n            column = defs[primary_key.expressions[0].name]\n            column.append(\n                \"constraints\", exp.ColumnConstraint(kind=exp.PrimaryKeyColumnConstraint())\n            )\n            schema.expressions.remove(primary_key)\n        else:\n            for column in defs.values():\n                auto_increment = None\n                for constraint in column.constraints:\n                    if isinstance(constraint.kind, exp.PrimaryKeyColumnConstraint):\n                        break\n                    if isinstance(constraint.kind, exp.AutoIncrementColumnConstraint):\n                        auto_increment = constraint\n                if auto_increment:\n                    column.constraints.remove(auto_increment)\n\n    return expression\n\n\ndef _generated_to_auto_increment(expression: exp.Expr) -> exp.Expr:\n    if not isinstance(expression, exp.ColumnDef):\n        return expression\n\n    generated = expression.find(exp.GeneratedAsIdentityColumnConstraint)\n\n    if generated:\n        t.cast(exp.ColumnConstraint, generated.parent).pop()\n\n        not_null = expression.find(exp.NotNullColumnConstraint)\n        if not_null:\n            t.cast(exp.ColumnConstraint, not_null.parent).pop()\n\n        expression.append(\n            \"constraints\", exp.ColumnConstraint(kind=exp.AutoIncrementColumnConstraint())\n        )\n\n    return expression\n\n\nclass SQLite(Dialect):\n    # https://sqlite.org/forum/forumpost/5e575586ac5c711b?raw\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n    TYPED_DIVISION = True\n    SAFE_DIVISION = True\n    SAFE_TO_ELIMINATE_DOUBLE_NEGATION = False\n\n    class Tokenizer(tokens.Tokenizer):\n        IDENTIFIERS = ['\"', (\"[\", \"]\"), \"`\"]\n        HEX_STRINGS = [(\"x'\", \"'\"), (\"X'\", \"'\"), (\"0x\", \"\"), (\"0X\", \"\")]\n\n        NESTED_COMMENTS = False\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"ATTACH\": TokenType.ATTACH,\n            \"DETACH\": TokenType.DETACH,\n            \"INDEXED BY\": TokenType.INDEXED_BY,\n            \"MATCH\": TokenType.MATCH,\n        }\n\n        KEYWORDS.pop(\"/*+\")\n\n        COMMANDS = {*tokens.Tokenizer.COMMANDS, TokenType.REPLACE}\n\n    Parser = SQLiteParser\n\n    class Generator(generator.Generator):\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n        NVL2_SUPPORTED = False\n        JSON_PATH_BRACKETED_KEY_SUPPORTED = False\n        SUPPORTS_CREATE_TABLE_LIKE = False\n        SUPPORTS_TABLE_ALIAS_COLUMNS = False\n        SUPPORTS_TO_NUMBER = False\n        SUPPORTS_WINDOW_EXCLUDE = True\n        EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False\n        SUPPORTS_MEDIAN = False\n        JSON_KEY_VALUE_PAIR_SEP = \",\"\n        PARSE_JSON_NAME: t.Optional[str] = None\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.BOOLEAN: \"INTEGER\",\n            exp.DType.TINYINT: \"INTEGER\",\n            exp.DType.SMALLINT: \"INTEGER\",\n            exp.DType.INT: \"INTEGER\",\n            exp.DType.BIGINT: \"INTEGER\",\n            exp.DType.FLOAT: \"REAL\",\n            exp.DType.DOUBLE: \"REAL\",\n            exp.DType.DECIMAL: \"REAL\",\n            exp.DType.CHAR: \"TEXT\",\n            exp.DType.NCHAR: \"TEXT\",\n            exp.DType.VARCHAR: \"TEXT\",\n            exp.DType.NVARCHAR: \"TEXT\",\n            exp.DType.BINARY: \"BLOB\",\n            exp.DType.VARBINARY: \"BLOB\",\n        }\n        TYPE_MAPPING.pop(exp.DType.BLOB)\n\n        TOKEN_MAPPING = {\n            TokenType.AUTO_INCREMENT: \"AUTOINCREMENT\",\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.AnyValue: any_value_to_max_sql,\n            exp.Chr: rename_func(\"CHAR\"),\n            exp.Concat: concat_to_dpipe_sql,\n            exp.CountIf: count_if_to_sum,\n            exp.Create: transforms.preprocess([_transform_create]),\n            exp.CurrentDate: lambda *_: \"CURRENT_DATE\",\n            exp.CurrentTime: lambda *_: \"CURRENT_TIME\",\n            exp.CurrentTimestamp: lambda *_: \"CURRENT_TIMESTAMP\",\n            exp.CurrentVersion: lambda *_: \"SQLITE_VERSION()\",\n            exp.ColumnDef: transforms.preprocess([_generated_to_auto_increment]),\n            exp.DateStrToDate: lambda self, e: self.sql(e, \"this\"),\n            exp.If: rename_func(\"IIF\"),\n            exp.ILike: no_ilike_sql,\n            exp.JSONArrayAgg: unsupported_args(\"order\", \"null_handling\", \"return_type\", \"strict\")(\n                rename_func(\"JSON_GROUP_ARRAY\")\n            ),\n            exp.JSONExtractScalar: arrow_json_extract_sql,\n            exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e, name=\"JSON_GROUP_OBJECT\"),\n            exp.Levenshtein: unsupported_args(\"ins_cost\", \"del_cost\", \"sub_cost\", \"max_dist\")(\n                rename_func(\"EDITDIST3\")\n            ),\n            exp.LogicalOr: rename_func(\"MAX\"),\n            exp.LogicalAnd: rename_func(\"MIN\"),\n            exp.Pivot: no_pivot_sql,\n            exp.Rand: rename_func(\"RANDOM\"),\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_distinct_on,\n                    transforms.eliminate_qualify,\n                    transforms.eliminate_semi_and_anti_joins,\n                ]\n            ),\n            exp.StrPosition: lambda self, e: strposition_sql(self, e, func_name=\"INSTR\"),\n            exp.TableSample: no_tablesample_sql,\n            exp.TimeStrToTime: lambda self, e: self.sql(e, \"this\"),\n            exp.TimeToStr: lambda self, e: self.func(\"STRFTIME\", e.args.get(\"format\"), e.this),\n            exp.TryCast: no_trycast_sql,\n            exp.TsOrDsToTimestamp: lambda self, e: self.sql(e, \"this\"),\n        }\n\n        # SQLite doesn't generally support CREATE TABLE .. properties\n        # https://www.sqlite.org/lang_createtable.html\n        PROPERTIES_LOCATION = {\n            prop: exp.Properties.Location.UNSUPPORTED\n            for prop in generator.Generator.PROPERTIES_LOCATION\n        }\n\n        # There are a few exceptions (e.g. temporary tables) which are supported or\n        # can be transpiled to SQLite, so we explicitly override them accordingly\n        PROPERTIES_LOCATION[exp.LikeProperty] = exp.Properties.Location.POST_SCHEMA\n        PROPERTIES_LOCATION[exp.TemporaryProperty] = exp.Properties.Location.POST_CREATE\n\n        LIMIT_FETCH = \"LIMIT\"\n\n        def bitwiseandagg_sql(self, expression: exp.BitwiseAndAgg) -> str:\n            self.unsupported(\"BITWISE_AND aggregation is not supported in SQLite\")\n            return self.function_fallback_sql(expression)\n\n        def bitwiseoragg_sql(self, expression: exp.BitwiseOrAgg) -> str:\n            self.unsupported(\"BITWISE_OR aggregation is not supported in SQLite\")\n            return self.function_fallback_sql(expression)\n\n        def bitwisexoragg_sql(self, expression: exp.BitwiseXorAgg) -> str:\n            self.unsupported(\"BITWISE_XOR aggregation is not supported in SQLite\")\n            return self.function_fallback_sql(expression)\n\n        def jsonextract_sql(self, expression: exp.JSONExtract) -> str:\n            if expression.expressions:\n                return self.function_fallback_sql(expression)\n            return arrow_json_extract_sql(self, expression)\n\n        def dateadd_sql(self, expression: exp.DateAdd) -> str:\n            modifier = expression.expression\n            modifier = modifier.name if modifier.is_string else self.sql(modifier)\n            unit = expression.args.get(\"unit\")\n            modifier = f\"'{modifier} {unit.name}'\" if unit else f\"'{modifier}'\"\n            return self.func(\"DATE\", expression.this, modifier)\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            if expression.is_type(\"date\"):\n                return self.func(\"DATE\", expression.this)\n\n            return super().cast_sql(expression)\n\n        # Note: SQLite's TRUNC always returns REAL (e.g., trunc(10.99) -> 10.0), not INTEGER.\n        # This creates a transpilation gap affecting division semantics, similar to Presto.\n        # Unlike Presto where this only affects decimals=0, SQLite has no decimals parameter\n        # so every use of TRUNC is affected. Modeling precisely would require exp.FloatTrunc.\n        @unsupported_args(\"decimals\")\n        def trunc_sql(self, expression: exp.Trunc) -> str:\n            return self.func(\"TRUNC\", expression.this)\n\n        def generateseries_sql(self, expression: exp.GenerateSeries) -> str:\n            parent = expression.parent\n            alias = parent and parent.args.get(\"alias\")\n\n            if isinstance(alias, exp.TableAlias) and alias.columns:\n                column_alias = alias.columns[0]\n                alias.set(\"columns\", None)\n                sql = self.sql(\n                    exp.select(exp.alias_(\"value\", column_alias)).from_(expression).subquery()\n                )\n            else:\n                sql = self.function_fallback_sql(expression)\n\n            return sql\n\n        def datediff_sql(self, expression: exp.DateDiff) -> str:\n            unit = expression.args.get(\"unit\")\n            unit = unit.name.upper() if unit else \"DAY\"\n\n            sql = f\"(JULIANDAY({self.sql(expression, 'this')}) - JULIANDAY({self.sql(expression, 'expression')}))\"\n\n            if unit == \"MONTH\":\n                sql = f\"{sql} / 30.0\"\n            elif unit == \"YEAR\":\n                sql = f\"{sql} / 365.0\"\n            elif unit == \"HOUR\":\n                sql = f\"{sql} * 24.0\"\n            elif unit == \"MINUTE\":\n                sql = f\"{sql} * 1440.0\"\n            elif unit == \"SECOND\":\n                sql = f\"{sql} * 86400.0\"\n            elif unit == \"MILLISECOND\":\n                sql = f\"{sql} * 86400000.0\"\n            elif unit == \"MICROSECOND\":\n                sql = f\"{sql} * 86400000000.0\"\n            elif unit == \"NANOSECOND\":\n                sql = f\"{sql} * 8640000000000.0\"\n            else:\n                self.unsupported(f\"DATEDIFF unsupported for '{unit}'.\")\n\n            return f\"CAST({sql} AS INTEGER)\"\n\n        # https://www.sqlite.org/lang_aggfunc.html#group_concat\n        def groupconcat_sql(self, expression: exp.GroupConcat) -> str:\n            this = expression.this\n            distinct = expression.find(exp.Distinct)\n\n            if distinct:\n                this = distinct.expressions[0]\n                distinct_sql = \"DISTINCT \"\n            else:\n                distinct_sql = \"\"\n\n            if isinstance(expression.this, exp.Order):\n                self.unsupported(\"SQLite GROUP_CONCAT doesn't support ORDER BY.\")\n                if expression.this.this and not distinct:\n                    this = expression.this.this\n\n            separator = expression.args.get(\"separator\")\n            return f\"GROUP_CONCAT({distinct_sql}{self.format_args(this, separator)})\"\n\n        def least_sql(self, expression: exp.Least) -> str:\n            if expression.expressions:\n                return rename_func(\"MIN\")(self, expression)\n\n            return self.sql(expression, \"this\")\n\n        def greatest_sql(self, expression: exp.Greatest) -> str:\n            if expression.expressions:\n                return rename_func(\"MAX\")(self, expression)\n\n            return self.sql(expression, \"this\")\n\n        def transaction_sql(self, expression: exp.Transaction) -> str:\n            this = expression.this\n            this = f\" {this}\" if this else \"\"\n            return f\"BEGIN{this} TRANSACTION\"\n\n        def isascii_sql(self, expression: exp.IsAscii) -> str:\n            return f\"(NOT {self.sql(expression.this)} GLOB CAST(x'2a5b5e012d7f5d2a' AS TEXT))\"\n\n        @unsupported_args(\"this\")\n        def currentschema_sql(self, expression: exp.CurrentSchema) -> str:\n            return \"'main'\"\n\n        def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:\n            self.unsupported(\"SQLite does not support IGNORE NULLS.\")\n            return self.sql(expression.this)\n\n        def respectnulls_sql(self, expression: exp.RespectNulls) -> str:\n            return self.sql(expression.this)\n\n        def windowspec_sql(self, expression: exp.WindowSpec) -> str:\n            if (\n                expression.text(\"kind\").upper() == \"RANGE\"\n                and expression.text(\"start\").upper() == \"CURRENT ROW\"\n            ):\n                return \"RANGE CURRENT ROW\"\n\n            return super().windowspec_sql(expression)\n"
  },
  {
    "path": "sqlglot/dialects/starrocks.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, transforms\nfrom sqlglot.dialects.dialect import (\n    approx_count_distinct_sql,\n    arrow_json_extract_sql,\n    rename_func,\n    unit_to_str,\n    inline_array_sql,\n    property_sql,\n)\nfrom sqlglot.dialects.mysql import MySQL\nfrom sqlglot.parsers.starrocks import StarRocksParser\nfrom sqlglot.tokens import TokenType\n\n\ndef _eliminate_between_in_delete(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    StarRocks doesn't support BETWEEN in DELETE statements, so we convert\n    BETWEEN expressions to explicit comparisons.\n\n    https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/DELETE/#parameters\n\n    Example:\n        >>> from sqlglot import parse_one\n        >>> expr = parse_one(\"DELETE FROM t WHERE x BETWEEN 1 AND 10\")\n        >>> print(_eliminate_between_in_delete(expr).sql(dialect=\"starrocks\"))\n        DELETE FROM t WHERE x >= 1 AND x <= 10\n    \"\"\"\n    if where := expression.args.get(\"where\"):\n        for between in where.find_all(exp.Between):\n            between.replace(\n                exp.and_(\n                    exp.GTE(this=between.this.copy(), expression=between.args[\"low\"]),\n                    exp.LTE(this=between.this.copy(), expression=between.args[\"high\"]),\n                    copy=False,\n                )\n            )\n    return expression\n\n\n# https://docs.starrocks.io/docs/sql-reference/sql-functions/spatial-functions/st_distance_sphere/\ndef st_distance_sphere(self, expression: exp.StDistance) -> str:\n    point1 = expression.this\n    point2 = expression.expression\n\n    point1_x = self.func(\"ST_X\", point1)\n    point1_y = self.func(\"ST_Y\", point1)\n    point2_x = self.func(\"ST_X\", point2)\n    point2_y = self.func(\"ST_Y\", point2)\n\n    return self.func(\"ST_Distance_Sphere\", point1_x, point1_y, point2_x, point2_y)\n\n\nclass StarRocks(MySQL):\n    STRICT_JSON_PATH_SYNTAX = False\n    INDEX_OFFSET = 1\n\n    class Tokenizer(MySQL.Tokenizer):\n        KEYWORDS = {\n            **MySQL.Tokenizer.KEYWORDS,\n            \"LARGEINT\": TokenType.INT128,\n        }\n\n    Parser = StarRocksParser\n\n    class Generator(MySQL.Generator):\n        EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False\n        JSON_TYPE_REQUIRED_FOR_EXTRACTION = False\n        VARCHAR_REQUIRES_SIZE = False\n        PARSE_JSON_NAME: t.Optional[str] = \"PARSE_JSON\"\n        WITH_PROPERTIES_PREFIX = \"PROPERTIES\"\n        UPDATE_STATEMENT_SUPPORTS_FROM = True\n        INSERT_OVERWRITE = \" OVERWRITE\"\n\n        # StarRocks doesn't support \"IS TRUE/FALSE\" syntax.\n        IS_BOOL_ALLOWED = False\n        # StarRocks doesn't support renaming a table with a database.\n        RENAME_TABLE_WITH_DB = False\n\n        CAST_MAPPING = {}\n\n        TYPE_MAPPING = {\n            **MySQL.Generator.TYPE_MAPPING,\n            exp.DType.INT128: \"LARGEINT\",\n            exp.DType.TEXT: \"STRING\",\n            exp.DType.TIMESTAMP: \"DATETIME\",\n            exp.DType.TIMESTAMPTZ: \"DATETIME\",\n        }\n\n        SQL_SECURITY_VIEW_LOCATION = exp.Properties.Location.POST_SCHEMA\n\n        PROPERTIES_LOCATION = {\n            **MySQL.Generator.PROPERTIES_LOCATION,\n            exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,\n            exp.UniqueKeyProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.RollupProperty: exp.Properties.Location.POST_SCHEMA,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_SCHEMA,\n        }\n\n        TRANSFORMS = {\n            **MySQL.Generator.TRANSFORMS,\n            exp.Array: inline_array_sql,\n            exp.ArrayAgg: rename_func(\"ARRAY_AGG\"),\n            exp.ArrayFilter: rename_func(\"ARRAY_FILTER\"),\n            exp.ArrayToString: rename_func(\"ARRAY_JOIN\"),\n            exp.ApproxDistinct: approx_count_distinct_sql,\n            exp.CurrentVersion: lambda *_: \"CURRENT_VERSION()\",\n            exp.DateDiff: lambda self, e: self.func(\n                \"DATE_DIFF\", unit_to_str(e), e.this, e.expression\n            ),\n            exp.Delete: transforms.preprocess([_eliminate_between_in_delete]),\n            exp.Flatten: rename_func(\"ARRAY_FLATTEN\"),\n            exp.JSONExtractScalar: arrow_json_extract_sql,\n            exp.JSONExtract: arrow_json_extract_sql,\n            exp.Property: property_sql,\n            exp.RegexpLike: rename_func(\"REGEXP\"),\n            exp.SchemaCommentProperty: lambda self, e: self.naked_property(e),\n            exp.SqlSecurityProperty: lambda self, e: f\"SECURITY {self.sql(e.this)}\",\n            exp.StDistance: st_distance_sphere,\n            exp.StrToUnix: lambda self, e: self.func(\"UNIX_TIMESTAMP\", e.this, self.format_time(e)),\n            exp.TimestampTrunc: lambda self, e: self.func(\"DATE_TRUNC\", unit_to_str(e), e.this),\n            exp.TimeStrToDate: rename_func(\"TO_DATE\"),\n            exp.UnixToStr: lambda self, e: self.func(\"FROM_UNIXTIME\", e.this, self.format_time(e)),\n            exp.UnixToTime: rename_func(\"FROM_UNIXTIME\"),\n        }\n\n        TRANSFORMS.pop(exp.DateTrunc)\n\n        # https://docs.starrocks.io/docs/sql-reference/sql-statements/keywords/#reserved-keywords\n        RESERVED_KEYWORDS = {\n            \"add\",\n            \"all\",\n            \"alter\",\n            \"analyze\",\n            \"and\",\n            \"array\",\n            \"as\",\n            \"asc\",\n            \"between\",\n            \"bigint\",\n            \"bitmap\",\n            \"both\",\n            \"by\",\n            \"case\",\n            \"char\",\n            \"character\",\n            \"check\",\n            \"collate\",\n            \"column\",\n            \"compaction\",\n            \"convert\",\n            \"create\",\n            \"cross\",\n            \"cube\",\n            \"current_date\",\n            \"current_role\",\n            \"current_time\",\n            \"current_timestamp\",\n            \"current_user\",\n            \"database\",\n            \"databases\",\n            \"decimal\",\n            \"decimalv2\",\n            \"decimal32\",\n            \"decimal64\",\n            \"decimal128\",\n            \"default\",\n            \"deferred\",\n            \"delete\",\n            \"dense_rank\",\n            \"desc\",\n            \"describe\",\n            \"distinct\",\n            \"double\",\n            \"drop\",\n            \"dual\",\n            \"else\",\n            \"except\",\n            \"exists\",\n            \"explain\",\n            \"false\",\n            \"first_value\",\n            \"float\",\n            \"for\",\n            \"force\",\n            \"from\",\n            \"full\",\n            \"function\",\n            \"grant\",\n            \"group\",\n            \"grouping\",\n            \"grouping_id\",\n            \"groups\",\n            \"having\",\n            \"hll\",\n            \"host\",\n            \"if\",\n            \"ignore\",\n            \"immediate\",\n            \"in\",\n            \"index\",\n            \"infile\",\n            \"inner\",\n            \"insert\",\n            \"int\",\n            \"integer\",\n            \"intersect\",\n            \"into\",\n            \"is\",\n            \"join\",\n            \"json\",\n            \"key\",\n            \"keys\",\n            \"kill\",\n            \"lag\",\n            \"largeint\",\n            \"last_value\",\n            \"lateral\",\n            \"lead\",\n            \"left\",\n            \"like\",\n            \"limit\",\n            \"load\",\n            \"localtime\",\n            \"localtimestamp\",\n            \"maxvalue\",\n            \"minus\",\n            \"mod\",\n            \"not\",\n            \"ntile\",\n            \"null\",\n            \"on\",\n            \"or\",\n            \"order\",\n            \"outer\",\n            \"outfile\",\n            \"over\",\n            \"partition\",\n            \"percentile\",\n            \"primary\",\n            \"procedure\",\n            \"qualify\",\n            \"range\",\n            \"rank\",\n            \"read\",\n            \"regexp\",\n            \"release\",\n            \"rename\",\n            \"replace\",\n            \"revoke\",\n            \"right\",\n            \"rlike\",\n            \"row\",\n            \"row_number\",\n            \"rows\",\n            \"schema\",\n            \"schemas\",\n            \"select\",\n            \"set\",\n            \"set_var\",\n            \"show\",\n            \"smallint\",\n            \"system\",\n            \"table\",\n            \"terminated\",\n            \"text\",\n            \"then\",\n            \"tinyint\",\n            \"to\",\n            \"true\",\n            \"union\",\n            \"unique\",\n            \"unsigned\",\n            \"update\",\n            \"use\",\n            \"using\",\n            \"values\",\n            \"varchar\",\n            \"when\",\n            \"where\",\n            \"with\",\n        }\n\n        def create_sql(self, expression: exp.Create) -> str:\n            # Starrocks' primary key is defined outside of the schema, so we need to move it there\n            schema = expression.this\n            if isinstance(schema, exp.Schema):\n                primary_key = schema.find(exp.PrimaryKey)\n\n                if primary_key:\n                    props = expression.args.get(\"properties\")\n\n                    if not props:\n                        props = exp.Properties(expressions=[])\n                        expression.set(\"properties\", props)\n\n                    # Verify if the first one is an engine property. Is true then insert it after the engine,\n                    # otherwise insert it at the beginning\n                    engine = props.find(exp.EngineProperty)\n                    engine_index = (engine.index or 0) if engine else -1\n                    props.set(\"expressions\", primary_key.pop(), engine_index + 1, overwrite=False)\n\n            return super().create_sql(expression)\n\n        def partitionedbyproperty_sql(self, expression: exp.PartitionedByProperty) -> str:\n            this = expression.this\n            if isinstance(this, exp.Schema):\n                # For MVs, StarRocks needs outer parentheses.\n                create = expression.find_ancestor(exp.Create)\n\n                sql = self.expressions(this, flat=True)\n                if (create and create.kind == \"VIEW\") or all(\n                    isinstance(col, (exp.Column, exp.Identifier)) for col in this.expressions\n                ):\n                    sql = f\"({sql})\"\n\n                return f\"PARTITION BY {sql}\"\n\n            return f\"PARTITION BY {self.sql(this)}\"\n\n        def cluster_sql(self, expression: exp.Cluster) -> str:\n            \"\"\"Generate StarRocks ORDER BY clause for clustering.\"\"\"\n            expressions = self.expressions(expression, flat=True)\n            return f\"ORDER BY ({expressions})\" if expressions else \"\"\n\n        def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:\n            \"\"\"Generate StarRocks REFRESH clause for materialized views.\n            There is a little difference of the syntax between StarRocks and Doris.\n            \"\"\"\n            method = self.sql(expression, \"method\")\n            method = f\" {method}\" if method else \"\"\n            kind = self.sql(expression, \"kind\")\n            kind = f\" {kind}\" if kind else \"\"\n            starts = self.sql(expression, \"starts\")\n            starts = f\" START ({starts})\" if starts else \"\"\n            every = self.sql(expression, \"every\")\n            unit = self.sql(expression, \"unit\")\n            every = f\" EVERY (INTERVAL {every} {unit})\" if every and unit else \"\"\n\n            return f\"REFRESH{method}{kind}{starts}{every}\"\n"
  },
  {
    "path": "sqlglot/dialects/tableau.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import Dialect, rename_func, strposition_sql as _strposition_sql\nfrom sqlglot.parsers.tableau import TableauParser\n\n\nclass Tableau(Dialect):\n    LOG_BASE_FIRST = False\n\n    class Tokenizer(tokens.Tokenizer):\n        IDENTIFIERS = [(\"[\", \"]\")]\n        QUOTES = [\"'\", '\"']\n\n    class Generator(generator.Generator):\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.Coalesce: rename_func(\"IFNULL\"),\n            exp.Select: transforms.preprocess([transforms.eliminate_distinct_on]),\n        }\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        def if_sql(self, expression: exp.If) -> str:\n            this = self.sql(expression, \"this\")\n            true = self.sql(expression, \"true\")\n            false = self.sql(expression, \"false\")\n            return f\"IF {this} THEN {true} ELSE {false} END\"\n\n        def count_sql(self, expression: exp.Count) -> str:\n            this = expression.this\n            if isinstance(this, exp.Distinct):\n                return self.func(\"COUNTD\", *this.expressions)\n            return self.func(\"COUNT\", this)\n\n        def strposition_sql(self, expression: exp.StrPosition) -> str:\n            has_occurrence = \"occurrence\" in expression.args\n            return _strposition_sql(\n                self,\n                expression,\n                func_name=\"FINDNTH\" if has_occurrence else \"FIND\",\n                supports_occurrence=has_occurrence,\n            )\n\n    Parser = TableauParser\n"
  },
  {
    "path": "sqlglot/dialects/teradata.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    max_or_greatest,\n    min_or_least,\n    rename_func,\n    strposition_sql,\n    to_number_with_nls_param,\n)\nfrom sqlglot.parsers.teradata import TeradataParser\nfrom sqlglot.tokens import TokenType\n\n\ndef _date_add_sql(\n    kind: t.Literal[\"+\", \"-\"],\n) -> t.Callable[[Teradata.Generator, exp.DateAdd | exp.DateSub], str]:\n    def func(self: Teradata.Generator, expression: exp.DateAdd | exp.DateSub) -> str:\n        this = self.sql(expression, \"this\")\n        unit = expression.args.get(\"unit\")\n        value = self._simplify_unless_literal(expression.expression)\n\n        if not isinstance(value, exp.Literal):\n            self.unsupported(\"Cannot add non literal\")\n\n        if isinstance(value, exp.Neg):\n            kind_to_op = {\"+\": \"-\", \"-\": \"+\"}\n            value = exp.Literal.string(value.this.to_py())\n        else:\n            kind_to_op = {\"+\": \"+\", \"-\": \"-\"}\n            value.set(\"is_string\", True)\n\n        return f\"{this} {kind_to_op[kind]} {self.sql(exp.Interval(this=value, unit=unit))}\"\n\n    return func\n\n\nclass Teradata(Dialect):\n    TYPED_DIVISION = True\n\n    TIME_MAPPING = {\n        \"YY\": \"%y\",\n        \"Y4\": \"%Y\",\n        \"YYYY\": \"%Y\",\n        \"M4\": \"%B\",\n        \"M3\": \"%b\",\n        \"M\": \"%-M\",\n        \"MI\": \"%M\",\n        \"MM\": \"%m\",\n        \"MMM\": \"%b\",\n        \"MMMM\": \"%B\",\n        \"D\": \"%-d\",\n        \"DD\": \"%d\",\n        \"D3\": \"%j\",\n        \"DDD\": \"%j\",\n        \"H\": \"%-H\",\n        \"HH\": \"%H\",\n        \"HH24\": \"%H\",\n        \"S\": \"%-S\",\n        \"SS\": \"%S\",\n        \"SSSSSS\": \"%f\",\n        \"E\": \"%a\",\n        \"EE\": \"%a\",\n        \"E3\": \"%a\",\n        \"E4\": \"%A\",\n        \"EEE\": \"%a\",\n        \"EEEE\": \"%A\",\n    }\n\n    class Tokenizer(tokens.Tokenizer):\n        # Tested each of these and they work, although there is no\n        # Teradata documentation explicitly mentioning them.\n        HEX_STRINGS = [(\"X'\", \"'\"), (\"x'\", \"'\"), (\"0x\", \"\")]\n        # https://docs.teradata.com/r/Teradata-Database-SQL-Functions-Operators-Exprs-and-Predicates/March-2017/Comparison-Operators-and-Functions/Comparison-Operators/ANSI-Compliance\n        # https://docs.teradata.com/r/SQL-Functions-Operators-Exprs-and-Predicates/June-2017/Arithmetic-Trigonometric-Hyperbolic-Operators/Functions\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"**\": TokenType.DSTAR,\n            \"^=\": TokenType.NEQ,\n            \"BYTEINT\": TokenType.SMALLINT,\n            \"COLLECT\": TokenType.COMMAND,\n            \"DEL\": TokenType.DELETE,\n            \"EQ\": TokenType.EQ,\n            \"GE\": TokenType.GTE,\n            \"GT\": TokenType.GT,\n            \"HELP\": TokenType.COMMAND,\n            \"INS\": TokenType.INSERT,\n            \"LE\": TokenType.LTE,\n            \"LOCKING\": TokenType.LOCK,\n            \"LT\": TokenType.LT,\n            \"MINUS\": TokenType.EXCEPT,\n            \"MOD\": TokenType.MOD,\n            \"NE\": TokenType.NEQ,\n            \"NOT=\": TokenType.NEQ,\n            \"SAMPLE\": TokenType.TABLE_SAMPLE,\n            \"SEL\": TokenType.SELECT,\n            \"ST_GEOMETRY\": TokenType.GEOMETRY,\n            \"TOP\": TokenType.TOP,\n            \"UPD\": TokenType.UPDATE,\n        }\n        KEYWORDS.pop(\"/*+\")\n\n        # Teradata does not support % as a modulo operator\n        SINGLE_TOKENS = {**tokens.Tokenizer.SINGLE_TOKENS}\n        SINGLE_TOKENS.pop(\"%\")\n\n    Parser = TeradataParser\n\n    class Generator(generator.Generator):\n        LIMIT_IS_TOP = True\n        JOIN_HINTS = False\n        TABLE_HINTS = False\n        QUERY_HINTS = False\n        TABLESAMPLE_KEYWORDS = \"SAMPLE\"\n        LAST_DAY_SUPPORTS_DATE_PART = False\n        CAN_IMPLEMENT_ARRAY_ANY = True\n        TZ_TO_WITH_TIME_ZONE = True\n        ARRAY_SIZE_NAME = \"CARDINALITY\"\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.GEOMETRY: \"ST_GEOMETRY\",\n            exp.DType.DOUBLE: \"DOUBLE PRECISION\",\n            exp.DType.TIMESTAMPTZ: \"TIMESTAMP\",\n        }\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.OnCommitProperty: exp.Properties.Location.POST_INDEX,\n            exp.PartitionedByProperty: exp.Properties.Location.POST_EXPRESSION,\n            exp.StabilityProperty: exp.Properties.Location.POST_CREATE,\n        }\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.ArgMax: rename_func(\"MAX_BY\"),\n            exp.ArgMin: rename_func(\"MIN_BY\"),\n            exp.Max: max_or_greatest,\n            exp.Min: min_or_least,\n            exp.Pow: lambda self, e: self.binary(e, \"**\"),\n            exp.Rand: lambda self, e: self.func(\"RANDOM\", e.args.get(\"lower\"), e.args.get(\"upper\")),\n            exp.Select: transforms.preprocess(\n                [transforms.eliminate_distinct_on, transforms.eliminate_semi_and_anti_joins]\n            ),\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self, e, func_name=\"INSTR\", supports_position=True, supports_occurrence=True\n            ),\n            exp.StrToDate: lambda self, e: (\n                f\"CAST({self.sql(e, 'this')} AS DATE FORMAT {self.format_time(e)})\"\n            ),\n            exp.ToChar: lambda self, e: self.function_fallback_sql(e),\n            exp.ToNumber: to_number_with_nls_param,\n            exp.Use: lambda self, e: f\"DATABASE {self.sql(e, 'this')}\",\n            exp.DateAdd: _date_add_sql(\"+\"),\n            exp.DateSub: _date_add_sql(\"-\"),\n            exp.Quarter: lambda self, e: self.sql(exp.Extract(this=\"QUARTER\", expression=e.this)),\n        }\n\n        def currenttimestamp_sql(self, expression: exp.CurrentTimestamp) -> str:\n            prefix, suffix = (\"(\", \")\") if expression.this else (\"\", \"\")\n            return self.func(\"CURRENT_TIMESTAMP\", expression.this, prefix=prefix, suffix=suffix)\n\n        def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n            if expression.to.this == exp.DType.UNKNOWN and expression.args.get(\"format\"):\n                # We don't actually want to print the unknown type in CAST(<value> AS FORMAT <format>)\n                expression.to.pop()\n\n            return super().cast_sql(expression, safe_prefix=safe_prefix)\n\n        def trycast_sql(self, expression: exp.TryCast) -> str:\n            return self.cast_sql(expression, safe_prefix=\"TRY\")\n\n        def tablesample_sql(\n            self,\n            expression: exp.TableSample,\n            tablesample_keyword: t.Optional[str] = None,\n        ) -> str:\n            return f\"{self.sql(expression, 'this')} SAMPLE {self.expressions(expression)}\"\n\n        def partitionedbyproperty_sql(self, expression: exp.PartitionedByProperty) -> str:\n            return f\"PARTITION BY {self.sql(expression, 'this')}\"\n\n        # FROM before SET in Teradata UPDATE syntax\n        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/Teradata-VantageTM-SQL-Data-Manipulation-Language-17.20/Statement-Syntax/UPDATE/UPDATE-Syntax-Basic-Form-FROM-Clause\n        def update_sql(self, expression: exp.Update) -> str:\n            this = self.sql(expression, \"this\")\n            from_sql = self.sql(expression, \"from_\")\n            set_sql = self.expressions(expression, flat=True)\n            where_sql = self.sql(expression, \"where\")\n            sql = f\"UPDATE {this}{from_sql} SET {set_sql}{where_sql}\"\n            return self.prepend_ctes(expression, sql)\n\n        def mod_sql(self, expression: exp.Mod) -> str:\n            return self.binary(expression, \"MOD\")\n\n        def rangen_sql(self, expression: exp.RangeN) -> str:\n            this = self.sql(expression, \"this\")\n            expressions_sql = self.expressions(expression)\n            each_sql = self.sql(expression, \"each\")\n            each_sql = f\" EACH {each_sql}\" if each_sql else \"\"\n\n            return f\"RANGE_N({this} BETWEEN {expressions_sql}{each_sql})\"\n\n        def lockingstatement_sql(self, expression: exp.LockingStatement) -> str:\n            \"\"\"Generate SQL for LOCKING statement\"\"\"\n            locking_clause = self.sql(expression, \"this\")\n            query_sql = self.sql(expression, \"expression\")\n\n            return f\"{locking_clause} {query_sql}\"\n\n        def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:\n            kind = self.sql(expression, \"kind\").upper()\n            if kind == \"TABLE\" and locations.get(exp.Properties.Location.POST_NAME):\n                this_name = self.sql(expression.this, \"this\")\n                this_properties = self.properties(\n                    exp.Properties(expressions=locations[exp.Properties.Location.POST_NAME]),\n                    wrapped=False,\n                    prefix=\",\",\n                )\n                this_schema = self.schema_columns_sql(expression.this)\n                return f\"{this_name}{this_properties}{self.sep()}{this_schema}\"\n\n            return super().createable_sql(expression, locations)\n\n        def extract_sql(self, expression: exp.Extract) -> str:\n            this = self.sql(expression, \"this\")\n            if this.upper() != \"QUARTER\":\n                return super().extract_sql(expression)\n\n            to_char = exp.func(\"to_char\", expression.expression, exp.Literal.string(\"Q\"))\n            return self.sql(exp.cast(to_char, exp.DType.INT))\n\n        def interval_sql(self, expression: exp.Interval) -> str:\n            multiplier = 0\n            unit = expression.text(\"unit\")\n\n            if unit.startswith(\"WEEK\"):\n                multiplier = 7\n            elif unit.startswith(\"QUARTER\"):\n                multiplier = 90\n\n            if multiplier:\n                return f\"({multiplier} * {super().interval_sql(exp.Interval(this=expression.this, unit=exp.var('DAY')))})\"\n\n            return super().interval_sql(expression)\n"
  },
  {
    "path": "sqlglot/dialects/trino.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp, transforms\nfrom sqlglot.dialects.dialect import (\n    merge_without_target_sql,\n    trim_sql,\n    timestrtotime_sql,\n    groupconcat_sql,\n    rename_func,\n)\nfrom sqlglot.dialects.presto import amend_exploded_column_table, Presto\nfrom sqlglot.parsers.trino import TrinoParser\nfrom sqlglot.tokens import TokenType\n\n\nclass Trino(Presto):\n    SUPPORTS_USER_DEFINED_TYPES = False\n    LOG_BASE_FIRST = True\n\n    class Tokenizer(Presto.Tokenizer):\n        KEYWORDS = {\n            **Presto.Tokenizer.KEYWORDS,\n            \"REFRESH\": TokenType.REFRESH,\n        }\n\n    Parser = TrinoParser\n\n    class Generator(Presto.Generator):\n        EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True\n        PROPERTIES_LOCATION = {\n            **Presto.Generator.PROPERTIES_LOCATION,\n            exp.LocationProperty: exp.Properties.Location.POST_WITH,\n        }\n\n        TRANSFORMS = {\n            **Presto.Generator.TRANSFORMS,\n            exp.ArraySum: lambda self, e: (\n                f\"REDUCE({self.sql(e, 'this')}, 0, (acc, x) -> acc + x, acc -> acc)\"\n            ),\n            exp.ArrayUniqueAgg: lambda self, e: f\"ARRAY_AGG(DISTINCT {self.sql(e, 'this')})\",\n            exp.CurrentVersion: rename_func(\"VERSION\"),\n            exp.GroupConcat: lambda self, e: groupconcat_sql(self, e, on_overflow=True),\n            exp.LocationProperty: lambda self, e: self.property_sql(e),\n            exp.Merge: merge_without_target_sql,\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_qualify,\n                    transforms.eliminate_distinct_on,\n                    transforms.explode_projection_to_unnest(1),\n                    transforms.eliminate_semi_and_anti_joins,\n                    amend_exploded_column_table,\n                ]\n            ),\n            exp.TimeStrToTime: lambda self, e: timestrtotime_sql(self, e, include_precision=True),\n            exp.Trim: trim_sql,\n        }\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n        }\n\n        def jsonextract_sql(self, expression: exp.JSONExtract) -> str:\n            if not expression.args.get(\"json_query\"):\n                return super().jsonextract_sql(expression)\n\n            json_path = self.sql(expression, \"expression\")\n            option = self.sql(expression, \"option\")\n            option = f\" {option}\" if option else \"\"\n\n            quote = self.sql(expression, \"quote\")\n            quote = f\" {quote}\" if quote else \"\"\n\n            on_condition = self.sql(expression, \"on_condition\")\n            on_condition = f\" {on_condition}\" if on_condition else \"\"\n\n            return self.func(\n                \"JSON_QUERY\",\n                expression.this,\n                json_path + option + quote + on_condition,\n            )\n"
  },
  {
    "path": "sqlglot/dialects/tsql.py",
    "content": "from __future__ import annotations\n\nimport typing as t\nfrom functools import reduce\n\nfrom sqlglot import exp, generator, tokens, transforms\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    NormalizationStrategy,\n    any_value_to_max_sql,\n    date_delta_sql,\n    datestrtodate_sql,\n    generatedasidentitycolumnconstraint_sql,\n    max_or_greatest,\n    min_or_least,\n    rename_func,\n    strposition_sql,\n    timestrtotime_sql,\n    trim_sql,\n)\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.tsql import OPTIONS_THAT_REQUIRE_EQUAL, TSQLParser\nfrom sqlglot.time import format_time\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.typing.tsql import EXPRESSION_METADATA\n\nDATE_PART_UNMAPPING = {\n    \"WEEKISO\": \"ISO_WEEK\",\n    \"DAYOFWEEK\": \"WEEKDAY\",\n    \"TIMEZONE_MINUTE\": \"TZOFFSET\",\n}\n\nBIT_TYPES = {exp.EQ, exp.NEQ, exp.Is, exp.In, exp.Select, exp.Alias}\n\n\ndef _format_sql(self: TSQL.Generator, expression: exp.NumberToStr | exp.TimeToStr) -> str:\n    fmt = expression.args[\"format\"]\n\n    if not isinstance(expression, exp.NumberToStr):\n        if fmt.is_string:\n            mapped_fmt = format_time(fmt.name, TSQL.INVERSE_TIME_MAPPING)\n            fmt_sql = self.sql(exp.Literal.string(mapped_fmt))\n        else:\n            fmt_sql = self.format_time(expression) or self.sql(fmt)\n    else:\n        fmt_sql = self.sql(fmt)\n\n    return self.func(\"FORMAT\", expression.this, fmt_sql, expression.args.get(\"culture\"))\n\n\ndef _string_agg_sql(self: TSQL.Generator, expression: exp.GroupConcat) -> str:\n    this = expression.this\n    distinct = expression.find(exp.Distinct)\n    if distinct:\n        # exp.Distinct can appear below an exp.Order or an exp.GroupConcat expression\n        self.unsupported(\"T-SQL STRING_AGG doesn't support DISTINCT.\")\n        this = distinct.pop().expressions[0]\n\n    order = \"\"\n    if isinstance(expression.this, exp.Order):\n        if expression.this.this:\n            this = expression.this.this.pop()\n        # Order has a leading space\n        order = f\" WITHIN GROUP ({self.sql(expression.this)[1:]})\"\n\n    separator = expression.args.get(\"separator\") or exp.Literal.string(\",\")\n    return f\"STRING_AGG({self.format_args(this, separator)}){order}\"\n\n\ndef qualify_derived_table_outputs(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Ensures all (unnamed) output columns are aliased for CTEs and Subqueries.\"\"\"\n    alias = expression.args.get(\"alias\")\n\n    if (\n        isinstance(expression, (exp.CTE, exp.Subquery))\n        and isinstance(alias, exp.TableAlias)\n        and not alias.columns\n    ):\n        from sqlglot.optimizer.qualify_columns import qualify_outputs\n\n        # We keep track of the unaliased column projection indexes instead of the expressions\n        # themselves, because the latter are going to be replaced by new nodes when the aliases\n        # are added and hence we won't be able to reach these newly added Alias parents\n        query = expression.this\n        unaliased_column_indexes = (\n            i for i, c in enumerate(query.selects) if isinstance(c, exp.Column) and not c.alias\n        )\n\n        qualify_outputs(query)\n\n        # Preserve the quoting information of columns for newly added Alias nodes\n        query_selects = query.selects\n        for select_index in unaliased_column_indexes:\n            alias = query_selects[select_index]\n            column = alias.this\n            if isinstance(column.this, exp.Identifier):\n                alias.args[\"alias\"].set(\"quoted\", column.this.quoted)\n\n    return expression\n\n\ndef _json_extract_sql(\n    self: TSQL.Generator, expression: exp.JSONExtract | exp.JSONExtractScalar\n) -> str:\n    json_query = self.func(\"JSON_QUERY\", expression.this, expression.expression)\n    json_value = self.func(\"JSON_VALUE\", expression.this, expression.expression)\n    return self.func(\"ISNULL\", json_query, json_value)\n\n\ndef _timestrtotime_sql(self: TSQL.Generator, expression: exp.TimeStrToTime):\n    sql = timestrtotime_sql(self, expression)\n    if expression.args.get(\"zone\"):\n        # If there is a timezone, produce an expression like:\n        # CAST('2020-01-01 12:13:14-08:00' AS DATETIMEOFFSET) AT TIME ZONE 'UTC'\n        # If you dont have AT TIME ZONE 'UTC', wrapping that expression in another cast back to DATETIME2 just drops the timezone information\n        return self.sql(exp.AtTimeZone(this=sql, zone=exp.Literal.string(\"UTC\")))\n    return sql\n\n\nclass TSQL(Dialect):\n    LOG_BASE_FIRST = False\n    TYPED_DIVISION = True\n    CONCAT_COALESCE = True\n    NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n    ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN = False\n\n    TIME_FORMAT = \"'yyyy-mm-dd hh:mm:ss'\"\n\n    EXPRESSION_METADATA = EXPRESSION_METADATA.copy()\n\n    DATE_PART_MAPPING = {\n        **Dialect.DATE_PART_MAPPING,\n        \"QQ\": \"QUARTER\",\n        \"M\": \"MONTH\",\n        \"Y\": \"DAYOFYEAR\",\n        \"WW\": \"WEEK\",\n        \"N\": \"MINUTE\",\n        \"SS\": \"SECOND\",\n        \"MCS\": \"MICROSECOND\",\n        \"TZOFFSET\": \"TIMEZONE_MINUTE\",\n        \"TZ\": \"TIMEZONE_MINUTE\",\n        \"ISO_WEEK\": \"WEEKISO\",\n        \"ISOWK\": \"WEEKISO\",\n        \"ISOWW\": \"WEEKISO\",\n    }\n\n    TIME_MAPPING = {\n        \"year\": \"%Y\",\n        \"dayofyear\": \"%j\",\n        \"day\": \"%d\",\n        \"dy\": \"%d\",\n        \"y\": \"%Y\",\n        \"week\": \"%W\",\n        \"ww\": \"%W\",\n        \"wk\": \"%W\",\n        \"isowk\": \"%V\",\n        \"isoww\": \"%V\",\n        \"iso_week\": \"%V\",\n        \"hour\": \"%h\",\n        \"hh\": \"%I\",\n        \"minute\": \"%M\",\n        \"mi\": \"%M\",\n        \"n\": \"%M\",\n        \"second\": \"%S\",\n        \"ss\": \"%S\",\n        \"s\": \"%-S\",\n        \"millisecond\": \"%f\",\n        \"ms\": \"%f\",\n        \"weekday\": \"%w\",\n        \"dw\": \"%w\",\n        \"month\": \"%m\",\n        \"mm\": \"%M\",\n        \"m\": \"%-M\",\n        \"Y\": \"%Y\",\n        \"YYYY\": \"%Y\",\n        \"YY\": \"%y\",\n        \"MMMM\": \"%B\",\n        \"MMM\": \"%b\",\n        \"MM\": \"%m\",\n        \"M\": \"%-m\",\n        \"dddd\": \"%A\",\n        \"dd\": \"%d\",\n        \"d\": \"%-d\",\n        \"HH\": \"%H\",\n        \"H\": \"%-H\",\n        \"h\": \"%-I\",\n        \"ffffff\": \"%f\",\n        \"yyyy\": \"%Y\",\n        \"yy\": \"%y\",\n    }\n\n    CONVERT_FORMAT_MAPPING = {\n        \"0\": \"%b %d %Y %-I:%M%p\",\n        \"1\": \"%m/%d/%y\",\n        \"2\": \"%y.%m.%d\",\n        \"3\": \"%d/%m/%y\",\n        \"4\": \"%d.%m.%y\",\n        \"5\": \"%d-%m-%y\",\n        \"6\": \"%d %b %y\",\n        \"7\": \"%b %d, %y\",\n        \"8\": \"%H:%M:%S\",\n        \"9\": \"%b %d %Y %-I:%M:%S:%f%p\",\n        \"10\": \"mm-dd-yy\",\n        \"11\": \"yy/mm/dd\",\n        \"12\": \"yymmdd\",\n        \"13\": \"%d %b %Y %H:%M:ss:%f\",\n        \"14\": \"%H:%M:%S:%f\",\n        \"20\": \"%Y-%m-%d %H:%M:%S\",\n        \"21\": \"%Y-%m-%d %H:%M:%S.%f\",\n        \"22\": \"%m/%d/%y %-I:%M:%S %p\",\n        \"23\": \"%Y-%m-%d\",\n        \"24\": \"%H:%M:%S\",\n        \"25\": \"%Y-%m-%d %H:%M:%S.%f\",\n        \"100\": \"%b %d %Y %-I:%M%p\",\n        \"101\": \"%m/%d/%Y\",\n        \"102\": \"%Y.%m.%d\",\n        \"103\": \"%d/%m/%Y\",\n        \"104\": \"%d.%m.%Y\",\n        \"105\": \"%d-%m-%Y\",\n        \"106\": \"%d %b %Y\",\n        \"107\": \"%b %d, %Y\",\n        \"108\": \"%H:%M:%S\",\n        \"109\": \"%b %d %Y %-I:%M:%S:%f%p\",\n        \"110\": \"%m-%d-%Y\",\n        \"111\": \"%Y/%m/%d\",\n        \"112\": \"%Y%m%d\",\n        \"113\": \"%d %b %Y %H:%M:%S:%f\",\n        \"114\": \"%H:%M:%S:%f\",\n        \"120\": \"%Y-%m-%d %H:%M:%S\",\n        \"121\": \"%Y-%m-%d %H:%M:%S.%f\",\n        \"126\": \"%Y-%m-%dT%H:%M:%S.%f\",\n    }\n\n    FORMAT_TIME_MAPPING = {\n        \"y\": \"%B %Y\",\n        \"d\": \"%m/%d/%Y\",\n        \"H\": \"%-H\",\n        \"h\": \"%-I\",\n        \"s\": \"%Y-%m-%d %H:%M:%S\",\n        \"D\": \"%A,%B,%Y\",\n        \"f\": \"%A,%B,%Y %-I:%M %p\",\n        \"F\": \"%A,%B,%Y %-I:%M:%S %p\",\n        \"g\": \"%m/%d/%Y %-I:%M %p\",\n        \"G\": \"%m/%d/%Y %-I:%M:%S %p\",\n        \"M\": \"%B %-d\",\n        \"m\": \"%B %-d\",\n        \"O\": \"%Y-%m-%dT%H:%M:%S\",\n        \"u\": \"%Y-%M-%D %H:%M:%S%z\",\n        \"U\": \"%A, %B %D, %Y %H:%M:%S%z\",\n        \"T\": \"%-I:%M:%S %p\",\n        \"t\": \"%-I:%M\",\n        \"Y\": \"%a %Y\",\n    }\n\n    class Tokenizer(tokens.Tokenizer):\n        IDENTIFIERS = [(\"[\", \"]\"), '\"']\n        QUOTES = [\"'\", '\"']\n        HEX_STRINGS = [(\"0x\", \"\"), (\"0X\", \"\")]\n        VAR_SINGLE_TOKENS = {\"@\", \"$\", \"#\"}\n\n        KEYWORDS = {\n            **tokens.Tokenizer.KEYWORDS,\n            \"CLUSTERED INDEX\": TokenType.INDEX,\n            \"DATETIME2\": TokenType.DATETIME2,\n            \"DATETIMEOFFSET\": TokenType.TIMESTAMPTZ,\n            \"DECLARE\": TokenType.DECLARE,\n            \"EXEC\": TokenType.EXECUTE,\n            \"FOR SYSTEM_TIME\": TokenType.TIMESTAMP_SNAPSHOT,\n            \"GO\": TokenType.COMMAND,\n            \"IMAGE\": TokenType.IMAGE,\n            \"MONEY\": TokenType.MONEY,\n            \"NONCLUSTERED INDEX\": TokenType.INDEX,\n            \"NTEXT\": TokenType.TEXT,\n            \"OPTION\": TokenType.OPTION,\n            \"OUTPUT\": TokenType.RETURNING,\n            \"PRINT\": TokenType.COMMAND,\n            \"PROC\": TokenType.PROCEDURE,\n            \"REAL\": TokenType.FLOAT,\n            \"ROWVERSION\": TokenType.ROWVERSION,\n            \"SMALLDATETIME\": TokenType.SMALLDATETIME,\n            \"SMALLMONEY\": TokenType.SMALLMONEY,\n            \"SQL_VARIANT\": TokenType.VARIANT,\n            \"SYSTEM_USER\": TokenType.CURRENT_USER,\n            \"TOP\": TokenType.TOP,\n            \"TIMESTAMP\": TokenType.ROWVERSION,\n            \"TINYINT\": TokenType.UTINYINT,\n            \"UNIQUEIDENTIFIER\": TokenType.UUID,\n            \"UPDATE STATISTICS\": TokenType.COMMAND,\n            \"XML\": TokenType.XML,\n        }\n        KEYWORDS.pop(\"/*+\")\n\n        COMMANDS = {*tokens.Tokenizer.COMMANDS, TokenType.END} - {TokenType.EXECUTE}\n\n    Parser = TSQLParser\n\n    class Generator(generator.Generator):\n        LIMIT_IS_TOP = True\n        QUERY_HINTS = False\n        RETURNING_END = False\n        NVL2_SUPPORTED = False\n        ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = False\n        LIMIT_FETCH = \"FETCH\"\n        COMPUTED_COLUMN_WITH_TYPE = False\n        CTE_RECURSIVE_KEYWORD_REQUIRED = False\n        ENSURE_BOOLS = True\n        NULL_ORDERING_SUPPORTED = None\n        SUPPORTS_SINGLE_ARG_CONCAT = False\n        TABLESAMPLE_SEED_KEYWORD = \"REPEATABLE\"\n        SUPPORTS_SELECT_INTO = True\n        JSON_PATH_BRACKETED_KEY_SUPPORTED = False\n        SUPPORTS_TO_NUMBER = False\n        SET_OP_MODIFIERS = False\n        COPY_PARAMS_EQ_REQUIRED = True\n        PARSE_JSON_NAME = None\n        EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = False\n        ALTER_SET_WRAPPED = True\n        ALTER_SET_TYPE = \"\"\n\n        EXPRESSIONS_WITHOUT_NESTED_CTES = {\n            exp.Create,\n            exp.Delete,\n            exp.Insert,\n            exp.Intersect,\n            exp.Except,\n            exp.Merge,\n            exp.Select,\n            exp.Subquery,\n            exp.Union,\n            exp.Update,\n        }\n\n        SUPPORTED_JSON_PATH_PARTS = {\n            exp.JSONPathKey,\n            exp.JSONPathRoot,\n            exp.JSONPathSubscript,\n        }\n\n        TYPE_MAPPING = {\n            **generator.Generator.TYPE_MAPPING,\n            exp.DType.BOOLEAN: \"BIT\",\n            exp.DType.DATETIME2: \"DATETIME2\",\n            exp.DType.DECIMAL: \"NUMERIC\",\n            exp.DType.DOUBLE: \"FLOAT\",\n            exp.DType.INT: \"INTEGER\",\n            exp.DType.ROWVERSION: \"ROWVERSION\",\n            exp.DType.TEXT: \"VARCHAR(MAX)\",\n            exp.DType.TIMESTAMP: \"DATETIME2\",\n            exp.DType.TIMESTAMPNTZ: \"DATETIME2\",\n            exp.DType.TIMESTAMPTZ: \"DATETIMEOFFSET\",\n            exp.DType.SMALLDATETIME: \"SMALLDATETIME\",\n            exp.DType.UTINYINT: \"TINYINT\",\n            exp.DType.VARIANT: \"SQL_VARIANT\",\n            exp.DType.UUID: \"UNIQUEIDENTIFIER\",\n        }\n\n        TYPE_MAPPING.pop(exp.DType.NCHAR)\n        TYPE_MAPPING.pop(exp.DType.NVARCHAR)\n\n        TRANSFORMS = {\n            **generator.Generator.TRANSFORMS,\n            exp.AnyValue: any_value_to_max_sql,\n            exp.Atan2: rename_func(\"ATN2\"),\n            exp.ArrayToString: rename_func(\"STRING_AGG\"),\n            exp.AutoIncrementColumnConstraint: lambda *_: \"IDENTITY\",\n            exp.Ceil: rename_func(\"CEILING\"),\n            exp.Chr: rename_func(\"CHAR\"),\n            exp.DateAdd: date_delta_sql(\"DATEADD\"),\n            exp.CTE: transforms.preprocess([qualify_derived_table_outputs]),\n            exp.CurrentDate: rename_func(\"GETDATE\"),\n            exp.CurrentTimestamp: rename_func(\"GETDATE\"),\n            exp.CurrentTimestampLTZ: rename_func(\"SYSDATETIMEOFFSET\"),\n            exp.DateStrToDate: datestrtodate_sql,\n            exp.GeneratedAsIdentityColumnConstraint: generatedasidentitycolumnconstraint_sql,\n            exp.GroupConcat: _string_agg_sql,\n            exp.If: rename_func(\"IIF\"),\n            exp.JSONExtract: _json_extract_sql,\n            exp.JSONExtractScalar: _json_extract_sql,\n            exp.LastDay: lambda self, e: self.func(\"EOMONTH\", e.this),\n            exp.Ln: rename_func(\"LOG\"),\n            exp.Max: max_or_greatest,\n            exp.MD5: lambda self, e: self.func(\"HASHBYTES\", exp.Literal.string(\"MD5\"), e.this),\n            exp.Min: min_or_least,\n            exp.NumberToStr: _format_sql,\n            exp.Repeat: rename_func(\"REPLICATE\"),\n            exp.CurrentSchema: rename_func(\"SCHEMA_NAME\"),\n            exp.Select: transforms.preprocess(\n                [\n                    transforms.eliminate_distinct_on,\n                    transforms.eliminate_semi_and_anti_joins,\n                    transforms.eliminate_qualify,\n                    transforms.unnest_generate_date_array_using_recursive_cte,\n                ]\n            ),\n            exp.Stddev: rename_func(\"STDEV\"),\n            exp.StrPosition: lambda self, e: strposition_sql(\n                self, e, func_name=\"CHARINDEX\", supports_position=True\n            ),\n            exp.Subquery: transforms.preprocess([qualify_derived_table_outputs]),\n            exp.SHA: lambda self, e: self.func(\"HASHBYTES\", exp.Literal.string(\"SHA1\"), e.this),\n            exp.SHA1Digest: lambda self, e: self.func(\n                \"HASHBYTES\", exp.Literal.string(\"SHA1\"), e.this\n            ),\n            exp.SHA2: lambda self, e: self.func(\n                \"HASHBYTES\", exp.Literal.string(f\"SHA2_{e.args.get('length', 256)}\"), e.this\n            ),\n            exp.TemporaryProperty: lambda self, e: \"\",\n            exp.TimeStrToTime: _timestrtotime_sql,\n            exp.TimeToStr: _format_sql,\n            exp.Trim: trim_sql,\n            exp.TsOrDsAdd: date_delta_sql(\"DATEADD\", cast=True),\n            exp.TsOrDsDiff: date_delta_sql(\"DATEDIFF\"),\n            exp.TimestampTrunc: lambda self, e: self.func(\"DATETRUNC\", e.unit, e.this),\n            exp.Trunc: lambda self, e: self.func(\n                \"ROUND\",\n                e.this,\n                e.args.get(\"decimals\") or exp.Literal.number(0),\n                exp.Literal.number(1),\n            ),\n            exp.Uuid: lambda *_: \"NEWID()\",\n            exp.DateFromParts: rename_func(\"DATEFROMPARTS\"),\n        }\n\n        TRANSFORMS.pop(exp.ReturnsProperty)\n\n        PROPERTIES_LOCATION = {\n            **generator.Generator.PROPERTIES_LOCATION,\n            exp.VolatileProperty: exp.Properties.Location.UNSUPPORTED,\n        }\n\n        def scope_resolution(self, rhs: str, scope_name: str) -> str:\n            return f\"{scope_name}::{rhs}\"\n\n        def select_sql(self, expression: exp.Select) -> str:\n            limit = expression.args.get(\"limit\")\n            offset = expression.args.get(\"offset\")\n\n            if isinstance(limit, exp.Fetch) and not offset:\n                # Dialects like Oracle can FETCH directly from a row set but\n                # T-SQL requires an ORDER BY + OFFSET clause in order to FETCH\n                offset = exp.Offset(expression=exp.Literal.number(0))\n                expression.set(\"offset\", offset)\n\n            if offset:\n                if not expression.args.get(\"order\"):\n                    # ORDER BY is required in order to use OFFSET in a query, so we use\n                    # a noop order by, since we don't really care about the order.\n                    # See: https://www.microsoftpressstore.com/articles/article.aspx?p=2314819\n                    expression.order_by(exp.select(exp.null()).subquery(), copy=False)\n\n                if isinstance(limit, exp.Limit):\n                    # TOP and OFFSET can't be combined, we need use FETCH instead of TOP\n                    # we replace here because otherwise TOP would be generated in select_sql\n                    limit.replace(exp.Fetch(direction=\"FIRST\", count=limit.expression))\n\n            return super().select_sql(expression)\n\n        def convert_sql(self, expression: exp.Convert) -> str:\n            name = \"TRY_CONVERT\" if expression.args.get(\"safe\") else \"CONVERT\"\n            return self.func(\n                name, expression.this, expression.expression, expression.args.get(\"style\")\n            )\n\n        def queryoption_sql(self, expression: exp.QueryOption) -> str:\n            option = self.sql(expression, \"this\")\n            value = self.sql(expression, \"expression\")\n            if value:\n                optional_equal_sign = \"= \" if option in OPTIONS_THAT_REQUIRE_EQUAL else \"\"\n                return f\"{option} {optional_equal_sign}{value}\"\n            return option\n\n        def lateral_op(self, expression: exp.Lateral) -> str:\n            cross_apply = expression.args.get(\"cross_apply\")\n            if cross_apply is True:\n                return \"CROSS APPLY\"\n            if cross_apply is False:\n                return \"OUTER APPLY\"\n\n            # TODO: perhaps we can check if the parent is a Join and transpile it appropriately\n            self.unsupported(\"LATERAL clause is not supported.\")\n            return \"LATERAL\"\n\n        def splitpart_sql(self: TSQL.Generator, expression: exp.SplitPart) -> str:\n            this = expression.this\n            split_count = len(this.name.split(\".\"))\n            delimiter = expression.args.get(\"delimiter\")\n            part_index = expression.args.get(\"part_index\")\n\n            if (\n                not all(isinstance(arg, exp.Literal) for arg in (this, delimiter, part_index))\n                or (delimiter and delimiter.name != \".\")\n                or not part_index\n                or split_count > 4\n            ):\n                self.unsupported(\n                    \"SPLIT_PART can be transpiled to PARSENAME only for '.' delimiter and literal values\"\n                )\n                return \"\"\n\n            return self.func(\n                \"PARSENAME\", this, exp.Literal.number(split_count + 1 - part_index.to_py())\n            )\n\n        def extract_sql(self, expression: exp.Extract) -> str:\n            part = expression.this\n            name = DATE_PART_UNMAPPING.get(part.name.upper()) or part\n\n            return self.func(\"DATEPART\", name, expression.expression)\n\n        def timefromparts_sql(self, expression: exp.TimeFromParts) -> str:\n            nano = expression.args.get(\"nano\")\n            if nano is not None:\n                nano.pop()\n                self.unsupported(\"Specifying nanoseconds is not supported in TIMEFROMPARTS.\")\n\n            if expression.args.get(\"fractions\") is None:\n                expression.set(\"fractions\", exp.Literal.number(0))\n            if expression.args.get(\"precision\") is None:\n                expression.set(\"precision\", exp.Literal.number(0))\n\n            return rename_func(\"TIMEFROMPARTS\")(self, expression)\n\n        def timestampfromparts_sql(self, expression: exp.TimestampFromParts) -> str:\n            zone = expression.args.get(\"zone\")\n            if zone is not None:\n                zone.pop()\n                self.unsupported(\"Time zone is not supported in DATETIMEFROMPARTS.\")\n\n            nano = expression.args.get(\"nano\")\n            if nano is not None:\n                nano.pop()\n                self.unsupported(\"Specifying nanoseconds is not supported in DATETIMEFROMPARTS.\")\n\n            if expression.args.get(\"milli\") is None:\n                expression.set(\"milli\", exp.Literal.number(0))\n\n            return rename_func(\"DATETIMEFROMPARTS\")(self, expression)\n\n        def setitem_sql(self, expression: exp.SetItem) -> str:\n            this = expression.this\n            if isinstance(this, exp.EQ) and not isinstance(this.left, exp.Parameter):\n                # T-SQL does not use '=' in SET command, except when the LHS is a variable.\n                return f\"{self.sql(this.left)} {self.sql(this.right)}\"\n\n            return super().setitem_sql(expression)\n\n        def boolean_sql(self, expression: exp.Boolean) -> str:\n            if type(expression.parent) in BIT_TYPES or isinstance(\n                expression.find_ancestor(exp.Values, exp.Select), exp.Values\n            ):\n                return \"1\" if expression.this else \"0\"\n\n            return \"(1 = 1)\" if expression.this else \"(1 = 0)\"\n\n        def is_sql(self, expression: exp.Is) -> str:\n            if isinstance(expression.expression, exp.Boolean):\n                return self.binary(expression, \"=\")\n            return self.binary(expression, \"IS\")\n\n        def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:\n            sql = self.sql(expression, \"this\")\n            properties = expression.args.get(\"properties\")\n\n            if sql[:1] != \"#\" and any(\n                isinstance(prop, exp.TemporaryProperty)\n                for prop in (properties.expressions if properties else [])\n            ):\n                sql = f\"[#{sql[1:]}\" if sql.startswith(\"[\") else f\"#{sql}\"\n\n            return sql\n\n        def create_sql(self, expression: exp.Create) -> str:\n            kind = expression.kind\n            exists = expression.args.get(\"exists\")\n            expression.set(\"exists\", None)\n\n            like_property = expression.find(exp.LikeProperty)\n            if like_property:\n                ctas_expression = like_property.this\n            else:\n                ctas_expression = expression.expression\n\n            if kind == \"VIEW\":\n                expression.this.set(\"catalog\", None)\n                with_ = expression.args.get(\"with_\")\n                if ctas_expression and with_:\n                    # We've already preprocessed the Create expression to bubble up any nested CTEs,\n                    # but CREATE VIEW actually requires the WITH clause to come after it so we need\n                    # to amend the AST by moving the CTEs to the CREATE VIEW statement's query.\n                    ctas_expression.set(\"with_\", with_.pop())\n\n            table = expression.find(exp.Table)\n\n            # Convert CTAS statement to SELECT .. INTO ..\n            if kind == \"TABLE\" and ctas_expression:\n                if isinstance(ctas_expression, exp.UNWRAPPED_QUERIES):\n                    ctas_expression = ctas_expression.subquery()\n\n                properties = expression.args.get(\"properties\") or exp.Properties()\n                is_temp = any(isinstance(p, exp.TemporaryProperty) for p in properties.expressions)\n\n                select_into = exp.select(\"*\").from_(exp.alias_(ctas_expression, \"temp\", table=True))\n                select_into.set(\"into\", exp.Into(this=table, temporary=is_temp))\n\n                if like_property:\n                    select_into.limit(0, copy=False)\n\n                sql = self.sql(select_into)\n            else:\n                sql = super().create_sql(expression)\n\n            if exists:\n                identifier = self.sql(exp.Literal.string(exp.table_name(table) if table else \"\"))\n                sql_with_ctes = self.prepend_ctes(expression, sql)\n                sql_literal = self.sql(exp.Literal.string(sql_with_ctes))\n                if kind == \"SCHEMA\":\n                    return f\"\"\"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = {identifier}) EXEC({sql_literal})\"\"\"\n                elif kind == \"TABLE\":\n                    assert table\n                    where = exp.and_(\n                        exp.column(\"TABLE_NAME\").eq(table.name),\n                        exp.column(\"TABLE_SCHEMA\").eq(table.db) if table.db else None,\n                        exp.column(\"TABLE_CATALOG\").eq(table.catalog) if table.catalog else None,\n                    )\n                    return f\"\"\"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE {where}) EXEC({sql_literal})\"\"\"\n                elif kind == \"INDEX\":\n                    index = self.sql(exp.Literal.string(expression.this.text(\"this\")))\n                    return f\"\"\"IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = object_id({identifier}) AND name = {index}) EXEC({sql_literal})\"\"\"\n            elif expression.args.get(\"replace\"):\n                sql = sql.replace(\"CREATE OR REPLACE \", \"CREATE OR ALTER \", 1)\n\n            return self.prepend_ctes(expression, sql)\n\n        @generator.unsupported_args(\"unlogged\", \"expressions\")\n        def into_sql(self, expression: exp.Into) -> str:\n            if expression.args.get(\"temporary\"):\n                # If the Into expression has a temporary property, push this down to the Identifier\n                table = expression.find(exp.Table)\n                if table and isinstance(table.this, exp.Identifier):\n                    table.this.set(\"temporary\", True)\n\n            return f\"{self.seg('INTO')} {self.sql(expression, 'this')}\"\n\n        def count_sql(self, expression: exp.Count) -> str:\n            func_name = \"COUNT_BIG\" if expression.args.get(\"big_int\") else \"COUNT\"\n            return rename_func(func_name)(self, expression)\n\n        def datediff_sql(self, expression: exp.DateDiff) -> str:\n            func_name = \"DATEDIFF_BIG\" if expression.args.get(\"big_int\") else \"DATEDIFF\"\n            return date_delta_sql(func_name)(self, expression)\n\n        def offset_sql(self, expression: exp.Offset) -> str:\n            return f\"{super().offset_sql(expression)} ROWS\"\n\n        def version_sql(self, expression: exp.Version) -> str:\n            name = \"SYSTEM_TIME\" if expression.name == \"TIMESTAMP\" else expression.name\n            this = f\"FOR {name}\"\n            expr = expression.expression\n            kind = expression.text(\"kind\")\n            if kind in (\"FROM\", \"BETWEEN\"):\n                args = expr.expressions\n                sep = \"TO\" if kind == \"FROM\" else \"AND\"\n                expr_sql = f\"{self.sql(seq_get(args, 0))} {sep} {self.sql(seq_get(args, 1))}\"\n            else:\n                expr_sql = self.sql(expr)\n\n            expr_sql = f\" {expr_sql}\" if expr_sql else \"\"\n            return f\"{this} {kind}{expr_sql}\"\n\n        def returnsproperty_sql(self, expression: exp.ReturnsProperty) -> str:\n            table = expression.args.get(\"table\")\n            table = f\"{table} \" if table else \"\"\n            return f\"RETURNS {table}{self.sql(expression, 'this')}\"\n\n        def returning_sql(self, expression: exp.Returning) -> str:\n            into = self.sql(expression, \"into\")\n            into = self.seg(f\"INTO {into}\") if into else \"\"\n            return f\"{self.seg('OUTPUT')} {self.expressions(expression, flat=True)}{into}\"\n\n        def transaction_sql(self, expression: exp.Transaction) -> str:\n            this = self.sql(expression, \"this\")\n            this = f\" {this}\" if this else \"\"\n            mark = self.sql(expression, \"mark\")\n            mark = f\" WITH MARK {mark}\" if mark else \"\"\n            return f\"BEGIN TRANSACTION{this}{mark}\"\n\n        def commit_sql(self, expression: exp.Commit) -> str:\n            this = self.sql(expression, \"this\")\n            this = f\" {this}\" if this else \"\"\n            durability = expression.args.get(\"durability\")\n            durability = (\n                f\" WITH (DELAYED_DURABILITY = {'ON' if durability else 'OFF'})\"\n                if durability is not None\n                else \"\"\n            )\n            return f\"COMMIT TRANSACTION{this}{durability}\"\n\n        def rollback_sql(self, expression: exp.Rollback) -> str:\n            this = self.sql(expression, \"this\")\n            this = f\" {this}\" if this else \"\"\n            return f\"ROLLBACK TRANSACTION{this}\"\n\n        def identifier_sql(self, expression: exp.Identifier) -> str:\n            identifier = super().identifier_sql(expression)\n\n            if expression.args.get(\"global_\"):\n                identifier = f\"##{identifier}\"\n            elif expression.args.get(\"temporary\"):\n                identifier = f\"#{identifier}\"\n\n            return identifier\n\n        def constraint_sql(self, expression: exp.Constraint) -> str:\n            this = self.sql(expression, \"this\")\n            expressions = self.expressions(expression, flat=True, sep=\" \")\n            return f\"CONSTRAINT {this} {expressions}\"\n\n        def length_sql(self, expression: exp.Length) -> str:\n            return self._uncast_text(expression, \"LEN\")\n\n        def right_sql(self, expression: exp.Right) -> str:\n            return self._uncast_text(expression, \"RIGHT\")\n\n        def left_sql(self, expression: exp.Left) -> str:\n            return self._uncast_text(expression, \"LEFT\")\n\n        def _uncast_text(self, expression: exp.Expr, name: str) -> str:\n            this = expression.this\n            if isinstance(this, exp.Cast) and this.is_type(exp.DType.TEXT):\n                this_sql = self.sql(this, \"this\")\n            else:\n                this_sql = self.sql(this)\n            expression_sql = self.sql(expression, \"expression\")\n            return self.func(name, this_sql, expression_sql if expression_sql else None)\n\n        def partition_sql(self, expression: exp.Partition) -> str:\n            return f\"WITH (PARTITIONS({self.expressions(expression, flat=True)}))\"\n\n        def alter_sql(self, expression: exp.Alter) -> str:\n            action = seq_get(expression.args.get(\"actions\") or [], 0)\n            if isinstance(action, exp.AlterRename):\n                return f\"EXEC sp_rename '{self.sql(expression.this)}', '{action.this.name}'\"\n            return super().alter_sql(expression)\n\n        def drop_sql(self, expression: exp.Drop) -> str:\n            if expression.args[\"kind\"] == \"VIEW\":\n                expression.this.set(\"catalog\", None)\n            return super().drop_sql(expression)\n\n        def options_modifier(self, expression: exp.Expr) -> str:\n            options = self.expressions(expression, key=\"options\")\n            return f\" OPTION{self.wrap(options)}\" if options else \"\"\n\n        def dpipe_sql(self, expression: exp.DPipe) -> str:\n            return self.sql(\n                reduce(lambda x, y: exp.Add(this=x, expression=y), expression.flatten())\n            )\n\n        def isascii_sql(self, expression: exp.IsAscii) -> str:\n            return f\"(PATINDEX(CONVERT(VARCHAR(MAX), 0x255b5e002d7f5d25) COLLATE Latin1_General_BIN, {self.sql(expression.this)}) = 0)\"\n\n        def columndef_sql(self, expression: exp.ColumnDef, sep: str = \" \") -> str:\n            this = super().columndef_sql(expression, sep)\n            default = self.sql(expression, \"default\")\n            default = f\" = {default}\" if default else \"\"\n            output = self.sql(expression, \"output\")\n            output = f\" {output}\" if output else \"\"\n            return f\"{this}{default}{output}\"\n\n        def coalesce_sql(self, expression: exp.Coalesce) -> str:\n            func_name = \"ISNULL\" if expression.args.get(\"is_null\") else \"COALESCE\"\n            return rename_func(func_name)(self, expression)\n\n        def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:\n            this = self.sql(expression, \"this\")\n            expressions = self.expressions(expression)\n            expressions = (\n                self.wrap(expressions) if expression.args.get(\"wrapped\") else f\" {expressions}\"\n            )\n            return f\"{this}{expressions}\" if expressions.strip() != \"\" else this\n\n        def ifblock_sql(self, expression: exp.IfBlock) -> str:\n            this = self.sql(expression, \"this\")\n            true = self.sql(expression, \"true\")\n            true = f\" {true}\" if true else \" \"\n            false = self.sql(expression, \"false\")\n            false = f\"; ELSE BEGIN {false}\" if false else \"\"\n            return f\"IF {this} BEGIN{true}{false}\"\n\n        def whileblock_sql(self, expression: exp.WhileBlock) -> str:\n            this = self.sql(expression, \"this\")\n            body = self.sql(expression, \"body\")\n            body = f\" {body}\" if body else \" \"\n            return f\"WHILE {this} BEGIN{body}\"\n\n        def execute_sql(self, expression: exp.Execute) -> str:\n            this = self.sql(expression, \"this\")\n            expressions = self.expressions(expression)\n            expressions = f\" {expressions}\" if expressions else \"\"\n            return f\"EXECUTE {this}{expressions}\"\n\n        def executesql_sql(self, expression: exp.ExecuteSql) -> str:\n            return self.execute_sql(expression)\n"
  },
  {
    "path": "sqlglot/diff.py",
    "content": "\"\"\"\n.. include:: ../posts/sql_diff.md\n\n----\n\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\nfrom collections import defaultdict\nfrom dataclasses import dataclass\nfrom heapq import heappop, heappush\nfrom itertools import chain\n\nfrom sqlglot import Dialect, expressions as exp\nfrom sqlglot.helper import seq_get\n\nif t.TYPE_CHECKING:\n    from collections.abc import Iterator, Sequence\n    from sqlglot.dialects.dialect import DialectType\n\n\n@dataclass(frozen=True)\nclass Insert:\n    \"\"\"Indicates that a new node has been inserted\"\"\"\n\n    expression: exp.Expr\n\n\n@dataclass(frozen=True)\nclass Remove:\n    \"\"\"Indicates that an existing node has been removed\"\"\"\n\n    expression: exp.Expr\n\n\n@dataclass(frozen=True)\nclass Move:\n    \"\"\"Indicates that an existing node's position within the tree has changed\"\"\"\n\n    source: exp.Expr\n    target: exp.Expr\n\n\n@dataclass(frozen=True)\nclass Update:\n    \"\"\"Indicates that an existing node has been updated\"\"\"\n\n    source: exp.Expr\n    target: exp.Expr\n\n\n@dataclass(frozen=True)\nclass Keep:\n    \"\"\"Indicates that an existing node hasn't been changed\"\"\"\n\n    source: exp.Expr\n    target: exp.Expr\n\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import T\n\n    Edit = t.Union[Insert, Remove, Move, Update, Keep]\n\n\ndef diff(\n    source: exp.Expr,\n    target: exp.Expr,\n    matchings: t.List[t.Tuple[exp.Expr, exp.Expr]] | None = None,\n    delta_only: bool = False,\n    **kwargs: t.Any,\n) -> t.List[Edit]:\n    \"\"\"\n    Returns the list of changes between the source and the target expressions.\n\n    Examples:\n        >>> from sqlglot import parse_one\n        >>> diff(parse_one(\"a + b\"), parse_one(\"a + c\"))  # doctest: +SKIP\n        [...]\n\n    Args:\n        source: the source expression.\n        target: the target expression against which the diff should be calculated.\n        matchings: the list of pre-matched node pairs which is used to help the algorithm's\n            heuristics produce better results for subtrees that are known by a caller to be matching.\n            Note: expression references in this list must refer to the same node objects that are\n            referenced in the source / target trees.\n        delta_only: excludes all `Keep` nodes from the diff.\n        kwargs: additional arguments to pass to the ChangeDistiller instance.\n\n    Returns:\n        the list of Insert, Remove, Move, Update and Keep objects for each node in the source and the\n        target expression trees. This list represents a sequence of steps needed to transform the source\n        expression tree into the target one.\n    \"\"\"\n    matchings = matchings or []\n\n    def compute_node_mappings(\n        old_nodes: tuple[exp.Expr, ...], new_nodes: tuple[exp.Expr, ...]\n    ) -> t.Dict[int, exp.Expr]:\n        node_mapping = {}\n        for old_node, new_node in zip(reversed(old_nodes), reversed(new_nodes)):\n            new_node._hash = hash(new_node)\n            node_mapping[id(old_node)] = new_node\n\n        return node_mapping\n\n    # if the source and target have any shared objects, that means there's an issue with the ast\n    # the algorithm won't work because the parent / hierarchies will be inaccurate\n    source_nodes = tuple(source.walk())\n    target_nodes = tuple(target.walk())\n    source_ids = {id(n) for n in source_nodes}\n    target_ids = {id(n) for n in target_nodes}\n\n    copy = (\n        len(source_nodes) != len(source_ids)\n        or len(target_nodes) != len(target_ids)\n        or source_ids & target_ids\n    )\n\n    source_copy = source.copy() if copy else source\n    target_copy = target.copy() if copy else target\n\n    try:\n        # We cache the hash of each new node here to speed up equality comparisons. If the input\n        # trees aren't copied, these hashes will be evicted before returning the edit script.\n        if copy and matchings:\n            source_mapping = compute_node_mappings(source_nodes, tuple(source_copy.walk()))\n            target_mapping = compute_node_mappings(target_nodes, tuple(target_copy.walk()))\n            matchings = [(source_mapping[id(s)], target_mapping[id(t)]) for s, t in matchings]\n        else:\n            for node in chain(reversed(source_nodes), reversed(target_nodes)):\n                node._hash = hash(node)\n\n        edit_script = ChangeDistiller(**kwargs).diff(\n            source_copy,\n            target_copy,\n            matchings=matchings,\n            delta_only=delta_only,\n        )\n    finally:\n        if not copy:\n            for node in chain(source_nodes, target_nodes):\n                node._hash = None\n\n    return edit_script\n\n\n# The expression types for which Update edits are allowed.\nUPDATABLE_EXPRESSION_TYPES = (\n    exp.Alias,\n    exp.Boolean,\n    exp.Column,\n    exp.DataType,\n    exp.Lambda,\n    exp.Literal,\n    exp.Table,\n    exp.Window,\n)\n\nIGNORED_LEAF_EXPRESSION_TYPES = (exp.Identifier,)\n\n\nclass ChangeDistiller:\n    \"\"\"\n    The implementation of the Change Distiller algorithm described by Beat Fluri and Martin Pinzger in\n    their paper https://ieeexplore.ieee.org/document/4339230, which in turn is based on the algorithm by\n    Chawathe et al. described in http://ilpubs.stanford.edu:8090/115/1/1995-46.pdf.\n    \"\"\"\n\n    def __init__(self, f: float = 0.6, t: float = 0.6, dialect: DialectType = None) -> None:\n        self.f = f\n        self.t = t\n        self._sql_generator = Dialect.get_or_raise(dialect).generator(comments=False)\n\n    def diff(\n        self,\n        source: exp.Expr,\n        target: exp.Expr,\n        matchings: t.List[t.Tuple[exp.Expr, exp.Expr]] | None = None,\n        delta_only: bool = False,\n    ) -> t.List[Edit]:\n        matchings = matchings or []\n        pre_matched_nodes = {id(s): id(t) for s, t in matchings}\n\n        self._source = source\n        self._target = target\n        self._source_index = {\n            id(n): n for n in self._source.bfs() if not isinstance(n, IGNORED_LEAF_EXPRESSION_TYPES)\n        }\n        self._target_index = {\n            id(n): n for n in self._target.bfs() if not isinstance(n, IGNORED_LEAF_EXPRESSION_TYPES)\n        }\n        self._unmatched_source_nodes = set(self._source_index) - set(pre_matched_nodes)\n        self._unmatched_target_nodes = set(self._target_index) - set(pre_matched_nodes.values())\n        self._bigram_histo_cache: t.Dict[int, t.DefaultDict[str, int]] = {}\n\n        matching_set = self._compute_matching_set() | set(pre_matched_nodes.items())\n        return self._generate_edit_script(dict(matching_set), delta_only)\n\n    def _generate_edit_script(self, matchings: t.Dict[int, int], delta_only: bool) -> t.List[Edit]:\n        edit_script: t.List[Edit] = []\n        for removed_node_id in self._unmatched_source_nodes:\n            edit_script.append(Remove(self._source_index[removed_node_id]))\n        for inserted_node_id in self._unmatched_target_nodes:\n            edit_script.append(Insert(self._target_index[inserted_node_id]))\n        for kept_source_node_id, kept_target_node_id in matchings.items():\n            source_node = self._source_index[kept_source_node_id]\n            target_node = self._target_index[kept_target_node_id]\n\n            identical_nodes = source_node == target_node\n\n            if not isinstance(source_node, UPDATABLE_EXPRESSION_TYPES) or identical_nodes:\n                if identical_nodes:\n                    source_parent = source_node.parent\n                    target_parent = target_node.parent\n\n                    if (\n                        (source_parent and not target_parent)\n                        or (not source_parent and target_parent)\n                        or (\n                            source_parent\n                            and target_parent\n                            and matchings.get(id(source_parent)) != id(target_parent)\n                        )\n                    ):\n                        edit_script.append(Move(source=source_node, target=target_node))\n                else:\n                    edit_script.extend(\n                        self._generate_move_edits(source_node, target_node, matchings)\n                    )\n\n                source_non_expression_leaves = dict(_get_non_expression_leaves(source_node))\n                target_non_expression_leaves = dict(_get_non_expression_leaves(target_node))\n\n                if source_non_expression_leaves != target_non_expression_leaves:\n                    edit_script.append(Update(source_node, target_node))\n                elif not delta_only:\n                    edit_script.append(Keep(source_node, target_node))\n            else:\n                edit_script.append(Update(source_node, target_node))\n\n        return edit_script\n\n    def _generate_move_edits(\n        self, source: exp.Expr, target: exp.Expr, matchings: t.Dict[int, int]\n    ) -> t.List[Move]:\n        source_args = [id(e) for e in _expression_only_args(source)]\n        target_args = [id(e) for e in _expression_only_args(target)]\n\n        args_lcs = set(\n            _lcs(source_args, target_args, lambda l, r: matchings.get(t.cast(int, l)) == r)\n        )\n\n        move_edits = []\n        for a in source_args:\n            if a not in args_lcs and a not in self._unmatched_source_nodes:\n                move_edits.append(\n                    Move(source=self._source_index[a], target=self._target_index[matchings[a]])\n                )\n\n        return move_edits\n\n    def _compute_matching_set(self) -> t.Set[t.Tuple[int, int]]:\n        leaves_matching_set = self._compute_leaf_matching_set()\n        matching_set = leaves_matching_set.copy()\n\n        ordered_unmatched_source_nodes = {\n            id(n): None for n in self._source.bfs() if id(n) in self._unmatched_source_nodes\n        }\n        ordered_unmatched_target_nodes = {\n            id(n): None for n in self._target.bfs() if id(n) in self._unmatched_target_nodes\n        }\n\n        for source_node_id in ordered_unmatched_source_nodes:\n            for target_node_id in ordered_unmatched_target_nodes:\n                source_node = self._source_index[source_node_id]\n                target_node = self._target_index[target_node_id]\n                if _is_same_type(source_node, target_node):\n                    source_leaf_ids = {id(l) for l in _get_expression_leaves(source_node)}\n                    target_leaf_ids = {id(l) for l in _get_expression_leaves(target_node)}\n\n                    max_leaves_num = max(len(source_leaf_ids), len(target_leaf_ids))\n                    if max_leaves_num:\n                        common_leaves_num = sum(\n                            1 if s in source_leaf_ids and t in target_leaf_ids else 0\n                            for s, t in leaves_matching_set\n                        )\n                        leaf_similarity_score = common_leaves_num / max_leaves_num\n                    else:\n                        leaf_similarity_score = 0.0\n\n                    adjusted_t = (\n                        self.t if min(len(source_leaf_ids), len(target_leaf_ids)) > 4 else 0.4\n                    )\n\n                    if leaf_similarity_score >= 0.8 or (\n                        leaf_similarity_score >= adjusted_t\n                        and self._dice_coefficient(source_node, target_node) >= self.f\n                    ):\n                        matching_set.add((source_node_id, target_node_id))\n                        self._unmatched_source_nodes.remove(source_node_id)\n                        self._unmatched_target_nodes.remove(target_node_id)\n                        ordered_unmatched_target_nodes.pop(target_node_id, None)\n                        break\n\n        return matching_set\n\n    def _compute_leaf_matching_set(self) -> t.Set[t.Tuple[int, int]]:\n        candidate_matchings: t.List[t.Tuple[float, int, int, exp.Expr, exp.Expr]] = []\n        source_expression_leaves = list(_get_expression_leaves(self._source))\n        target_expression_leaves = list(_get_expression_leaves(self._target))\n        for source_leaf in source_expression_leaves:\n            for target_leaf in target_expression_leaves:\n                if _is_same_type(source_leaf, target_leaf):\n                    similarity_score = self._dice_coefficient(source_leaf, target_leaf)\n                    if similarity_score >= self.f:\n                        heappush(\n                            candidate_matchings,\n                            (\n                                -similarity_score,\n                                -_parent_similarity_score(source_leaf, target_leaf),\n                                len(candidate_matchings),\n                                source_leaf,\n                                target_leaf,\n                            ),\n                        )\n\n        # Pick best matchings based on the highest score\n        matching_set = set()\n        while candidate_matchings:\n            _, _, _, source_leaf, target_leaf = heappop(candidate_matchings)\n            if (\n                id(source_leaf) in self._unmatched_source_nodes\n                and id(target_leaf) in self._unmatched_target_nodes\n            ):\n                matching_set.add((id(source_leaf), id(target_leaf)))\n                self._unmatched_source_nodes.remove(id(source_leaf))\n                self._unmatched_target_nodes.remove(id(target_leaf))\n\n        return matching_set\n\n    def _dice_coefficient(self, source: exp.Expr, target: exp.Expr) -> float:\n        source_histo = self._bigram_histo(source)\n        target_histo = self._bigram_histo(target)\n\n        total_grams = sum(source_histo.values()) + sum(target_histo.values())\n        if not total_grams:\n            return 1.0 if source == target else 0.0\n\n        overlap_len = 0\n        overlapping_grams = set(source_histo) & set(target_histo)\n        for g in overlapping_grams:\n            overlap_len += min(source_histo[g], target_histo[g])\n\n        return 2 * overlap_len / total_grams\n\n    def _bigram_histo(self, expression: exp.Expr) -> t.DefaultDict[str, int]:\n        if id(expression) in self._bigram_histo_cache:\n            return self._bigram_histo_cache[id(expression)]\n\n        expression_str = self._sql_generator.generate(expression)\n        count = max(0, len(expression_str) - 1)\n        bigram_histo: t.DefaultDict[str, int] = defaultdict(int)\n        for i in range(count):\n            bigram_histo[expression_str[i : i + 2]] += 1\n\n        self._bigram_histo_cache[id(expression)] = bigram_histo\n        return bigram_histo\n\n\ndef _get_expression_leaves(expression: exp.Expr) -> Iterator[exp.Expr]:\n    has_child_exprs = False\n\n    for node in expression.iter_expressions():\n        if not isinstance(node, IGNORED_LEAF_EXPRESSION_TYPES):\n            has_child_exprs = True\n            yield from _get_expression_leaves(node)\n\n    if not has_child_exprs:\n        yield expression\n\n\ndef _get_non_expression_leaves(expression: exp.Expr) -> Iterator[tuple[str, t.Any]]:\n    for arg, value in expression.args.items():\n        if (\n            value is None\n            or isinstance(value, exp.Expr)\n            or (isinstance(value, list) and isinstance(seq_get(value, 0), exp.Expr))\n        ):\n            continue\n\n        yield (arg, value)\n\n\ndef _is_same_type(source: exp.Expr, target: exp.Expr) -> bool:\n    if type(source) is type(target):\n        if isinstance(source, exp.Join):\n            return source.args.get(\"side\") == target.args.get(\"side\")\n\n        if isinstance(source, exp.Anonymous):\n            return source.this == target.this\n\n        return True\n\n    return False\n\n\ndef _parent_similarity_score(source: t.Optional[exp.Expr], target: t.Optional[exp.Expr]) -> int:\n    if source is None or target is None or type(source) is not type(target):\n        return 0\n\n    return 1 + _parent_similarity_score(source.parent, target.parent)\n\n\ndef _expression_only_args(expression: exp.Expr) -> Iterator[exp.Expr]:\n    yield from (\n        arg\n        for arg in expression.iter_expressions()\n        if not isinstance(arg, IGNORED_LEAF_EXPRESSION_TYPES)\n    )\n\n\ndef _lcs(\n    seq_a: Sequence[T], seq_b: Sequence[T], equal: t.Callable[[T, T], bool]\n) -> Sequence[t.Optional[T]]:\n    \"\"\"Calculates the longest common subsequence\"\"\"\n\n    len_a = len(seq_a)\n    len_b = len(seq_b)\n    lcs_result = [[None] * (len_b + 1) for i in range(len_a + 1)]\n\n    for i in range(len_a + 1):\n        for j in range(len_b + 1):\n            if i == 0 or j == 0:\n                lcs_result[i][j] = []  # type: ignore\n            elif equal(seq_a[i - 1], seq_b[j - 1]):\n                lcs_result[i][j] = lcs_result[i - 1][j - 1] + [seq_a[i - 1]]  # type: ignore\n            else:\n                lcs_result[i][j] = (\n                    lcs_result[i - 1][j]\n                    if len(lcs_result[i - 1][j]) > len(lcs_result[i][j - 1])  # type: ignore\n                    else lcs_result[i][j - 1]\n                )\n\n    return lcs_result[len_a][len_b]  # type: ignore\n"
  },
  {
    "path": "sqlglot/errors.py",
    "content": "from __future__ import annotations\n\nimport typing as t\nfrom enum import auto\nfrom collections.abc import Sequence\nfrom sqlglot.helper import AutoName\n\n\n# ANSI escape codes for error formatting\nANSI_UNDERLINE = \"\\033[4m\"\nANSI_RESET = \"\\033[0m\"\nERROR_MESSAGE_CONTEXT_DEFAULT = 100\n\n\nclass ErrorLevel(AutoName):\n    IGNORE = auto()\n    \"\"\"Ignore all errors.\"\"\"\n\n    WARN = auto()\n    \"\"\"Log all errors.\"\"\"\n\n    RAISE = auto()\n    \"\"\"Collect all errors and raise a single exception.\"\"\"\n\n    IMMEDIATE = auto()\n    \"\"\"Immediately raise an exception on the first error found.\"\"\"\n\n\nclass SqlglotError(Exception):\n    pass\n\n\nclass UnsupportedError(SqlglotError):\n    pass\n\n\nclass ParseError(SqlglotError):\n    def __init__(\n        self,\n        message: str,\n        errors: t.Optional[t.List[t.Dict[str, t.Any]]] = None,\n    ):\n        super().__init__(message)\n        self.errors = errors or []\n\n    @classmethod\n    def new(\n        cls,\n        message: str,\n        description: t.Optional[str] = None,\n        line: t.Optional[int] = None,\n        col: t.Optional[int] = None,\n        start_context: t.Optional[str] = None,\n        highlight: t.Optional[str] = None,\n        end_context: t.Optional[str] = None,\n        into_expression: t.Optional[str] = None,\n    ) -> ParseError:\n        return cls(\n            message,\n            [\n                {\n                    \"description\": description,\n                    \"line\": line,\n                    \"col\": col,\n                    \"start_context\": start_context,\n                    \"highlight\": highlight,\n                    \"end_context\": end_context,\n                    \"into_expression\": into_expression,\n                }\n            ],\n        )\n\n\nclass TokenError(SqlglotError):\n    pass\n\n\nclass OptimizeError(SqlglotError):\n    pass\n\n\nclass SchemaError(SqlglotError):\n    pass\n\n\nclass ExecuteError(SqlglotError):\n    pass\n\n\ndef highlight_sql(\n    sql: str,\n    positions: t.List[t.Tuple[int, int]],\n    context_length: int = ERROR_MESSAGE_CONTEXT_DEFAULT,\n) -> t.Tuple[str, str, str, str]:\n    \"\"\"\n    Highlight a SQL string using ANSI codes at the given positions.\n\n    Args:\n        sql: The complete SQL string.\n        positions: List of (start, end) tuples where both start and end are inclusive 0-based\n            indexes. For example, to highlight \"foo\" in \"SELECT foo\", use (7, 9).\n            The positions will be sorted and de-duplicated if they overlap.\n        context_length: Number of characters to show before the first highlight and after\n            the last highlight.\n\n    Returns:\n        A tuple of (formatted_sql, start_context, highlight, end_context) where:\n        - formatted_sql: The SQL with ANSI underline codes applied to highlighted sections\n        - start_context: Plain text before the first highlight\n        - highlight: Plain text from the first highlight start to the last highlight end,\n            including any non-highlighted text in between (no ANSI)\n        - end_context: Plain text after the last highlight\n\n    Note:\n        If positions is empty, raises a ValueError.\n    \"\"\"\n    if not positions:\n        raise ValueError(\"positions must contain at least one (start, end) tuple\")\n\n    start_context = \"\"\n    end_context = \"\"\n    first_highlight_start = 0\n    formatted_parts = []\n    previous_part_end = 0\n    sorted_positions = sorted(positions, key=lambda pos: pos[0])\n\n    if sorted_positions[0][0] > 0:\n        first_highlight_start = sorted_positions[0][0]\n        start_context = sql[max(0, first_highlight_start - context_length) : first_highlight_start]\n        formatted_parts.append(start_context)\n        previous_part_end = first_highlight_start\n\n    for start, end in sorted_positions:\n        highlight_start = max(start, previous_part_end)\n        highlight_end = end + 1\n        if highlight_start >= highlight_end:\n            continue  # Skip invalid or overlapping highlights\n        if highlight_start > previous_part_end:\n            formatted_parts.append(sql[previous_part_end:highlight_start])\n        formatted_parts.append(f\"{ANSI_UNDERLINE}{sql[highlight_start:highlight_end]}{ANSI_RESET}\")\n        previous_part_end = highlight_end\n\n    if previous_part_end < len(sql):\n        end_context = sql[previous_part_end : previous_part_end + context_length]\n        formatted_parts.append(end_context)\n\n    formatted_sql = \"\".join(formatted_parts)\n    highlight = sql[first_highlight_start:previous_part_end]\n\n    return formatted_sql, start_context, highlight, end_context\n\n\ndef concat_messages(errors: Sequence[t.Any], maximum: int) -> str:\n    msg = [str(e) for e in errors[:maximum]]\n    remaining = len(errors) - maximum\n    if remaining > 0:\n        msg.append(f\"... and {remaining} more\")\n    return \"\\n\\n\".join(msg)\n\n\ndef merge_errors(errors: Sequence[ParseError]) -> list[dict[str, t.Any]]:\n    return [e_dict for error in errors for e_dict in error.errors]\n"
  },
  {
    "path": "sqlglot/executor/__init__.py",
    "content": "\"\"\"\n.. include:: ../../posts/python_sql_engine.md\n\n----\n\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nimport time\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.errors import ExecuteError\nfrom sqlglot.executor.python import PythonExecutor\nfrom sqlglot.executor.table import Table, ensure_tables\nfrom sqlglot.helper import dict_depth\nfrom sqlglot.optimizer import optimize\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom sqlglot.planner import Plan\nfrom sqlglot.schema import ensure_schema, flatten_schema, nested_get, nested_set\n\nlogger = logging.getLogger(\"sqlglot\")\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n    from sqlglot.expressions import Expr\n    from sqlglot.schema import Schema\n\n\ndef execute(\n    sql: str | Expr,\n    schema: t.Optional[t.Dict | Schema] = None,\n    dialect: DialectType = None,\n    tables: t.Optional[t.Dict] = None,\n) -> Table:\n    \"\"\"\n    Run a sql query against data.\n\n    Args:\n        sql: a sql statement.\n        schema: database schema.\n            This can either be an instance of `Schema` or a mapping in one of the following forms:\n            1. {table: {col: type}}\n            2. {db: {table: {col: type}}}\n            3. {catalog: {db: {table: {col: type}}}}\n        dialect: the SQL dialect to apply during parsing (eg. \"spark\", \"hive\", \"presto\", \"mysql\").\n        tables: additional tables to register.\n\n    Returns:\n        Simple columnar data structure.\n    \"\"\"\n    tables_ = ensure_tables(tables, dialect=dialect)\n\n    if not schema:\n        schema = {}\n        flattened_tables = flatten_schema(tables_.mapping, depth=dict_depth(tables_.mapping))\n\n        for keys in flattened_tables:\n            table = nested_get(tables_.mapping, *zip(keys, keys))\n            assert table is not None\n\n            for column in table.columns:\n                value = table[0][column]\n                column_type = (\n                    annotate_types(exp.convert(value), dialect=dialect).type or type(value).__name__\n                )\n                nested_set(schema, [*keys, column], column_type)\n\n    schema = ensure_schema(schema, dialect=dialect)\n\n    if tables_.supported_table_args and tables_.supported_table_args != schema.supported_table_args:\n        raise ExecuteError(\"Tables must support the same table args as schema\")\n\n    now = time.time()\n    expression = optimize(sql, schema, leave_tables_isolated=True, dialect=dialect)\n\n    logger.debug(\"Optimization finished: %f\", time.time() - now)\n    logger.debug(\"Optimized SQL: %s\", expression.sql(pretty=True))\n\n    plan = Plan(expression)\n\n    logger.debug(\"Logical Plan: %s\", plan)\n\n    now = time.time()\n    result = PythonExecutor(tables=tables_).execute(plan)\n\n    logger.debug(\"Query finished: %f\", time.time() - now)\n\n    return result\n"
  },
  {
    "path": "sqlglot/executor/context.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.executor.env import ENV\n\nif t.TYPE_CHECKING:\n    from sqlglot.executor.table import Table, TableIter\n\n\nclass Context:\n    \"\"\"\n    Execution context for sql expressions.\n\n    Context is used to hold relevant data tables which can then be queried on with eval.\n\n    References to columns can either be scalar or vectors. When set_row is used, column references\n    evaluate to scalars while set_range evaluates to vectors. This allows convenient and efficient\n    evaluation of aggregation functions.\n    \"\"\"\n\n    def __init__(self, tables: t.Dict[str, Table], env: t.Optional[t.Dict] = None) -> None:\n        \"\"\"\n        Args\n            tables: representing the scope of the current execution context.\n            env: dictionary of functions within the execution context.\n        \"\"\"\n        self.tables = tables\n        self._table: t.Optional[Table] = None\n        self.range_readers = {name: table.range_reader for name, table in self.tables.items()}\n        self.row_readers = {name: table.reader for name, table in tables.items()}\n        self.env = {**ENV, **(env or {}), \"scope\": self.row_readers}\n\n    def eval(self, code):\n        return eval(code, self.env)\n\n    def eval_tuple(self, codes):\n        return tuple(self.eval(code) for code in codes)\n\n    @property\n    def table(self) -> Table:\n        if self._table is None:\n            self._table = list(self.tables.values())[0]\n\n            for other in self.tables.values():\n                if self._table.columns != other.columns:\n                    raise Exception(\"Columns are different.\")\n                if len(self._table.rows) != len(other.rows):\n                    raise Exception(\"Rows are different.\")\n\n        return self._table\n\n    def add_columns(self, *columns: str) -> None:\n        for table in self.tables.values():\n            table.add_columns(*columns)\n\n    @property\n    def columns(self) -> t.Tuple:\n        return self.table.columns\n\n    def __iter__(self):\n        self.env[\"scope\"] = self.row_readers\n        for i in range(len(self.table.rows)):\n            for table in self.tables.values():\n                reader = table[i]\n            yield reader, self\n\n    def table_iter(self, table: str) -> TableIter:\n        self.env[\"scope\"] = self.row_readers\n        return iter(self.tables[table])\n\n    def filter(self, condition) -> None:\n        rows = [reader.row for reader, _ in self if self.eval(condition)]\n\n        for table in self.tables.values():\n            table.rows = rows\n\n    def sort(self, key) -> None:\n        def sort_key(row: t.Tuple) -> t.Tuple:\n            self.set_row(row)\n            return tuple((t is None, t) for t in self.eval_tuple(key))\n\n        self.table.rows.sort(key=sort_key)\n\n    def set_row(self, row: t.Tuple) -> None:\n        for table in self.tables.values():\n            table.reader.row = row\n        self.env[\"scope\"] = self.row_readers\n\n    def set_index(self, index: int) -> None:\n        for table in self.tables.values():\n            table[index]\n        self.env[\"scope\"] = self.row_readers\n\n    def set_range(self, start: int, end: int) -> None:\n        for name in self.tables:\n            self.range_readers[name].range = range(start, end)\n        self.env[\"scope\"] = self.range_readers\n\n    def __contains__(self, table: str) -> bool:\n        return table in self.tables\n"
  },
  {
    "path": "sqlglot/executor/env.py",
    "content": "import datetime\nimport inspect\nimport re\nimport statistics\nfrom functools import wraps\n\nfrom sqlglot import exp\nfrom sqlglot.generator import Generator\nfrom sqlglot.helper import PYTHON_VERSION, is_int, seq_get\n\n\nclass reverse_key:\n    def __init__(self, obj):\n        self.obj = obj\n\n    def __eq__(self, other):\n        return other.obj == self.obj\n\n    def __lt__(self, other):\n        return other.obj < self.obj\n\n\ndef filter_nulls(func, empty_null=True):\n    @wraps(func)\n    def _func(values):\n        filtered = tuple(v for v in values if v is not None)\n        if not filtered and empty_null:\n            return None\n        return func(filtered)\n\n    return _func\n\n\ndef null_if_any(*required):\n    \"\"\"\n    Decorator that makes a function return `None` if any of the `required` arguments are `None`.\n\n    This also supports decoration with no arguments, e.g.:\n\n        @null_if_any\n        def foo(a, b): ...\n\n    In which case all arguments are required.\n    \"\"\"\n    f = None\n    if len(required) == 1 and callable(required[0]):\n        f = required[0]\n        required = ()\n\n    def decorator(func):\n        if required:\n            required_indices = [\n                i for i, param in enumerate(inspect.signature(func).parameters) if param in required\n            ]\n\n            def predicate(*args):\n                return any(args[i] is None for i in required_indices)\n\n        else:\n\n            def predicate(*args):\n                return any(a is None for a in args)\n\n        @wraps(func)\n        def _func(*args):\n            if predicate(*args):\n                return None\n            return func(*args)\n\n        return _func\n\n    if f:\n        return decorator(f)\n\n    return decorator\n\n\n@null_if_any(\"this\", \"substr\")\ndef str_position(this, substr, position=None):\n    position = position - 1 if position is not None else position\n    return this.find(substr, position) + 1\n\n\n@null_if_any(\"this\")\ndef substring(this, start=None, length=None):\n    if start is None:\n        return this\n    elif start == 0:\n        return \"\"\n    elif start < 0:\n        start = len(this) + start\n    else:\n        start -= 1\n\n    end = None if length is None else start + length\n\n    return this[start:end]\n\n\n@null_if_any\ndef cast(this, to):\n    if to == exp.DType.DATE:\n        if isinstance(this, datetime.datetime):\n            return this.date()\n        if isinstance(this, datetime.date):\n            return this\n        if isinstance(this, str):\n            return datetime.date.fromisoformat(this)\n    if to == exp.DType.TIME:\n        if isinstance(this, datetime.datetime):\n            return this.time()\n        if isinstance(this, datetime.time):\n            return this\n        if isinstance(this, str):\n            return datetime.time.fromisoformat(this)\n    if to in (exp.DType.DATETIME, exp.DType.TIMESTAMP):\n        if isinstance(this, datetime.datetime):\n            return this\n        if isinstance(this, datetime.date):\n            return datetime.datetime(this.year, this.month, this.day)\n        if isinstance(this, str):\n            return datetime.datetime.fromisoformat(this)\n    if to == exp.DType.BOOLEAN:\n        return bool(this)\n    if to in exp.DataType.TEXT_TYPES:\n        return str(this)\n    if to in {exp.DType.FLOAT, exp.DType.DOUBLE}:\n        return float(this)\n    if to in exp.DataType.NUMERIC_TYPES:\n        return int(this)\n    raise NotImplementedError(f\"Casting {this} to '{to}' not implemented.\")\n\n\ndef ordered(this, desc, nulls_first):\n    if desc:\n        return reverse_key(this)\n    return this\n\n\n@null_if_any\ndef interval(this, unit):\n    plural = unit + \"S\"\n    if plural in Generator.TIME_PART_SINGULARS:\n        unit = plural\n    return datetime.timedelta(**{unit.lower(): float(this)})\n\n\n@null_if_any(\"this\", \"expression\")\ndef arraytostring(this, expression, null=None):\n    return expression.join(x for x in (x if x is not None else null for x in this) if x is not None)\n\n\n@null_if_any(\"this\", \"expression\")\ndef jsonextract(this, expression):\n    for path_segment in expression:\n        if isinstance(this, dict):\n            this = this.get(path_segment)\n        elif isinstance(this, list) and is_int(path_segment):\n            this = seq_get(this, int(path_segment))\n        else:\n            raise NotImplementedError(f\"Unable to extract value for {this} at {path_segment}.\")\n\n        if this is None:\n            break\n\n    return this\n\n\nENV = {\n    \"exp\": exp,\n    # aggs\n    \"ARRAYAGG\": list,\n    \"ARRAYUNIQUEAGG\": filter_nulls(lambda acc: list(set(acc))),\n    \"AVG\": filter_nulls(statistics.fmean if PYTHON_VERSION >= (3, 8) else statistics.mean),  # type: ignore\n    \"COUNT\": filter_nulls(lambda acc: sum(1 for _ in acc), False),\n    \"MAX\": filter_nulls(max),\n    \"MIN\": filter_nulls(min),\n    \"SUM\": filter_nulls(sum),\n    # scalar functions\n    \"ABS\": null_if_any(lambda this: abs(this)),\n    \"ADD\": null_if_any(lambda e, this: e + this),\n    \"ARRAYANY\": null_if_any(lambda arr, func: any(func(e) for e in arr)),\n    \"ARRAYTOSTRING\": arraytostring,\n    \"BETWEEN\": null_if_any(lambda this, low, high: low <= this and this <= high),\n    \"BITWISEAND\": null_if_any(lambda this, e: this & e),\n    \"BITWISELEFTSHIFT\": null_if_any(lambda this, e: this << e),\n    \"BITWISEOR\": null_if_any(lambda this, e: this | e),\n    \"BITWISERIGHTSHIFT\": null_if_any(lambda this, e: this >> e),\n    \"BITWISEXOR\": null_if_any(lambda this, e: this ^ e),\n    \"CAST\": cast,\n    \"COALESCE\": lambda *args: next((a for a in args if a is not None), None),\n    \"CONCAT\": null_if_any(lambda *args: \"\".join(args)),\n    \"SAFECONCAT\": null_if_any(lambda *args: \"\".join(str(arg) for arg in args)),\n    \"CONCATWS\": null_if_any(lambda this, *args: this.join(args)),\n    \"DATEDIFF\": null_if_any(lambda this, expression, *_: (this - expression).days),\n    \"DATESTRTODATE\": null_if_any(lambda arg: datetime.date.fromisoformat(arg)),\n    \"DIV\": null_if_any(lambda e, this: e / this),\n    \"DOT\": null_if_any(lambda e, this: e[this]),\n    \"EQ\": null_if_any(lambda this, e: this == e),\n    \"EXTRACT\": null_if_any(lambda this, e: getattr(e, this)),\n    \"GT\": null_if_any(lambda this, e: this > e),\n    \"GTE\": null_if_any(lambda this, e: this >= e),\n    \"IF\": lambda predicate, true, false: true if predicate else false,\n    \"INTDIV\": null_if_any(lambda e, this: e // this),\n    \"INTERVAL\": interval,\n    \"JSONEXTRACT\": jsonextract,\n    \"LEFT\": null_if_any(lambda this, e: this[:e]),\n    \"LIKE\": null_if_any(\n        lambda this, e: bool(re.match(e.replace(\"_\", \".\").replace(\"%\", \".*\"), this))\n    ),\n    \"LOWER\": null_if_any(lambda arg: arg.lower()),\n    \"LT\": null_if_any(lambda this, e: this < e),\n    \"LTE\": null_if_any(lambda this, e: this <= e),\n    \"MAP\": null_if_any(lambda *args: dict(zip(*args))),  # type: ignore\n    \"MOD\": null_if_any(lambda e, this: e % this),\n    \"MUL\": null_if_any(lambda e, this: e * this),\n    \"NEQ\": null_if_any(lambda this, e: this != e),\n    \"ORD\": null_if_any(ord),\n    \"ORDERED\": ordered,\n    \"POW\": pow,\n    \"RIGHT\": null_if_any(lambda this, e: this[-e:]),\n    \"ROUND\": null_if_any(lambda this, decimals=None, truncate=None: round(this, ndigits=decimals)),\n    \"STRPOSITION\": str_position,\n    \"SUB\": null_if_any(lambda e, this: e - this),\n    \"SUBSTRING\": substring,\n    \"TIMESTRTOTIME\": null_if_any(lambda arg: datetime.datetime.fromisoformat(arg)),\n    \"UPPER\": null_if_any(lambda arg: arg.upper()),\n    \"YEAR\": null_if_any(lambda arg: arg.year),\n    \"MONTH\": null_if_any(lambda arg: arg.month),\n    \"DAY\": null_if_any(lambda arg: arg.day),\n    \"CURRENTDATETIME\": datetime.datetime.now,\n    \"CURRENTTIMESTAMP\": datetime.datetime.now,\n    \"CURRENTTIME\": datetime.datetime.now,\n    \"CURRENTDATE\": datetime.date.today,\n    \"STRFTIME\": null_if_any(lambda fmt, arg: datetime.datetime.fromisoformat(arg).strftime(fmt)),\n    \"STRTOTIME\": null_if_any(lambda arg, format: datetime.datetime.strptime(arg, format)),\n    \"TRIM\": null_if_any(lambda this, e=None: this.strip(e)),\n    \"STRUCT\": lambda *args: {\n        args[x]: args[x + 1]\n        for x in range(0, len(args), 2)\n        if (args[x + 1] is not None and args[x] is not None)\n    },\n    \"UNIXTOTIME\": null_if_any(\n        lambda arg: datetime.datetime.fromtimestamp(arg, datetime.timezone.utc)\n    ),\n}\n"
  },
  {
    "path": "sqlglot/executor/python.py",
    "content": "import collections\nimport itertools\nimport math\n\nfrom sqlglot import exp, generator, planner, tokens\nfrom sqlglot.dialects.dialect import Dialect, inline_array_sql\nfrom sqlglot.errors import ExecuteError\nfrom sqlglot.executor.context import Context\nfrom sqlglot.executor.env import ENV\nfrom sqlglot.executor.table import RowReader, Table\nfrom sqlglot.helper import subclasses\n\n\nclass PythonExecutor:\n    def __init__(self, env=None, tables=None):\n        self.generator = Python().generator(identify=True, comments=False)\n        self.env = {**ENV, **(env or {})}\n        self.tables = tables or {}\n\n    def execute(self, plan):\n        finished = set()\n        queue = set(plan.leaves)\n        contexts = {}\n\n        while queue:\n            node = queue.pop()\n            try:\n                context = self.context(\n                    {\n                        name: table\n                        for dep in node.dependencies\n                        for name, table in contexts[dep].tables.items()\n                    }\n                )\n\n                if isinstance(node, planner.Scan):\n                    contexts[node] = self.scan(node, context)\n                elif isinstance(node, planner.Aggregate):\n                    contexts[node] = self.aggregate(node, context)\n                elif isinstance(node, planner.Join):\n                    contexts[node] = self.join(node, context)\n                elif isinstance(node, planner.Sort):\n                    contexts[node] = self.sort(node, context)\n                elif isinstance(node, planner.SetOperation):\n                    contexts[node] = self.set_operation(node, context)\n                else:\n                    raise NotImplementedError\n\n                finished.add(node)\n\n                for dep in node.dependents:\n                    if all(d in contexts for d in dep.dependencies):\n                        queue.add(dep)\n\n                for dep in node.dependencies:\n                    if all(d in finished for d in dep.dependents):\n                        contexts.pop(dep)\n            except Exception as e:\n                raise ExecuteError(f\"Step '{node.id}' failed: {e}\") from e\n\n        root = plan.root\n        return contexts[root].tables[root.name]\n\n    def generate(self, expression):\n        \"\"\"Convert a SQL expression into literal Python code and compile it into bytecode.\"\"\"\n        if not expression:\n            return None\n\n        sql = self.generator.generate(expression)\n        return compile(sql, sql, \"eval\", optimize=2)\n\n    def generate_tuple(self, expressions):\n        \"\"\"Convert an array of SQL expressions into tuple of Python byte code.\"\"\"\n        if not expressions:\n            return tuple()\n        return tuple(self.generate(expression) for expression in expressions)\n\n    def context(self, tables):\n        return Context(tables, env=self.env)\n\n    def table(self, expressions):\n        return Table(\n            expression.alias_or_name if isinstance(expression, exp.Expr) else expression\n            for expression in expressions\n        )\n\n    def scan(self, step, context):\n        source = step.source\n\n        if source and isinstance(source, exp.Expr):\n            source = source.name or source.alias\n\n        if source is None:\n            context, table_iter = self.static()\n        elif source in context:\n            if not step.projections and not step.condition:\n                return self.context({step.name: context.tables[source]})\n            table_iter = context.table_iter(source)\n        else:\n            context, table_iter = self.scan_table(step)\n\n        return self.context({step.name: self._project_and_filter(context, step, table_iter)})\n\n    def _project_and_filter(self, context, step, table_iter):\n        sink = self.table(step.projections if step.projections else context.columns)\n        condition = self.generate(step.condition)\n        projections = self.generate_tuple(step.projections)\n\n        for reader in table_iter:\n            if len(sink) >= step.limit:\n                break\n\n            if condition and not context.eval(condition):\n                continue\n\n            if projections:\n                sink.append(context.eval_tuple(projections))\n            else:\n                sink.append(reader.row)\n\n        return sink\n\n    def static(self):\n        return self.context({}), [RowReader(())]\n\n    def scan_table(self, step):\n        table = self.tables.find(step.source)\n        context = self.context({step.source.alias_or_name: table})\n        return context, iter(table)\n\n    def join(self, step, context):\n        source = step.source_name\n\n        source_table = context.tables[source]\n        source_context = self.context({source: source_table})\n        column_ranges = {source: range(0, len(source_table.columns))}\n\n        for name, join in step.joins.items():\n            table = context.tables[name]\n            start = max(r.stop for r in column_ranges.values())\n            column_ranges[name] = range(start, len(table.columns) + start)\n            join_context = self.context({name: table})\n\n            if join.get(\"source_key\"):\n                table = self.hash_join(join, source_context, join_context)\n            else:\n                table = self.nested_loop_join(join, source_context, join_context)\n\n            source_context = self.context(\n                {\n                    name: Table(table.columns, table.rows, column_range)\n                    for name, column_range in column_ranges.items()\n                }\n            )\n            condition = self.generate(join[\"condition\"])\n            if condition:\n                source_context.filter(condition)\n\n        if not step.condition and not step.projections:\n            return source_context\n\n        sink = self._project_and_filter(\n            source_context,\n            step,\n            (reader for reader, _ in iter(source_context)),\n        )\n\n        if step.projections:\n            return self.context({step.name: sink})\n        else:\n            return self.context(\n                {\n                    name: Table(table.columns, sink.rows, table.column_range)\n                    for name, table in source_context.tables.items()\n                }\n            )\n\n    def nested_loop_join(self, _join, source_context, join_context):\n        table = Table(source_context.columns + join_context.columns)\n\n        for reader_a, _ in source_context:\n            for reader_b, _ in join_context:\n                table.append(reader_a.row + reader_b.row)\n\n        return table\n\n    def hash_join(self, join, source_context, join_context):\n        source_key = self.generate_tuple(join[\"source_key\"])\n        join_key = self.generate_tuple(join[\"join_key\"])\n        left = join.get(\"side\") == \"LEFT\"\n        right = join.get(\"side\") == \"RIGHT\"\n\n        results = collections.defaultdict(lambda: ([], []))\n\n        for reader, ctx in source_context:\n            results[ctx.eval_tuple(source_key)][0].append(reader.row)\n        for reader, ctx in join_context:\n            results[ctx.eval_tuple(join_key)][1].append(reader.row)\n\n        table = Table(source_context.columns + join_context.columns)\n        nulls = [(None,) * len(join_context.columns if left else source_context.columns)]\n\n        for a_group, b_group in results.values():\n            if left:\n                b_group = b_group or nulls\n            elif right:\n                a_group = a_group or nulls\n\n            for a_row, b_row in itertools.product(a_group, b_group):\n                table.append(a_row + b_row)\n\n        return table\n\n    def aggregate(self, step, context):\n        group_by = self.generate_tuple(step.group.values())\n        aggregations = self.generate_tuple(step.aggregations)\n        operands = self.generate_tuple(step.operands)\n\n        if operands:\n            operand_table = Table(self.table(step.operands).columns)\n\n            for reader, ctx in context:\n                operand_table.append(ctx.eval_tuple(operands))\n\n            for i, (a, b) in enumerate(zip(context.table.rows, operand_table.rows)):\n                context.table.rows[i] = a + b\n\n            width = len(context.columns)\n            context.add_columns(*operand_table.columns)\n\n            operand_table = Table(\n                context.columns,\n                context.table.rows,\n                range(width, width + len(operand_table.columns)),\n            )\n\n            context = self.context(\n                {\n                    None: operand_table,\n                    **context.tables,\n                }\n            )\n\n        context.sort(group_by)\n\n        group = None\n        start = 0\n        end = 1\n        length = len(context.table)\n        table = self.table(list(step.group) + step.aggregations)\n\n        def add_row():\n            table.append(group + context.eval_tuple(aggregations))\n\n        if length:\n            for i in range(length):\n                context.set_index(i)\n                key = context.eval_tuple(group_by)\n                group = key if group is None else group\n                end += 1\n                if key != group:\n                    context.set_range(start, end - 2)\n                    add_row()\n                    group = key\n                    start = end - 2\n                if len(table.rows) >= step.limit:\n                    break\n                if i == length - 1:\n                    context.set_range(start, end - 1)\n                    add_row()\n        elif step.limit > 0 and not group_by:\n            context.set_range(0, 0)\n            table.append(context.eval_tuple(aggregations))\n\n        context = self.context({step.name: table, **{name: table for name in context.tables}})\n\n        if step.projections or step.condition:\n            return self.scan(step, context)\n        return context\n\n    def sort(self, step, context):\n        projections = self.generate_tuple(step.projections)\n        projection_columns = [p.alias_or_name for p in step.projections]\n        all_columns = list(context.columns) + projection_columns\n        sink = self.table(all_columns)\n        for reader, ctx in context:\n            sink.append(reader.row + ctx.eval_tuple(projections))\n\n        sort_ctx = self.context(\n            {\n                None: sink,\n                **{table: sink for table in context.tables},\n            }\n        )\n        sort_ctx.sort(self.generate_tuple(step.key))\n\n        if not math.isinf(step.limit):\n            sort_ctx.table.rows = sort_ctx.table.rows[0 : step.limit]\n\n        output = Table(\n            projection_columns,\n            rows=[r[len(context.columns) : len(all_columns)] for r in sort_ctx.table.rows],\n        )\n        return self.context({step.name: output})\n\n    def set_operation(self, step, context):\n        left = context.tables[step.left]\n        right = context.tables[step.right]\n\n        sink = self.table(left.columns)\n\n        if issubclass(step.op, exp.Intersect):\n            sink.rows = list(set(left.rows).intersection(set(right.rows)))\n        elif issubclass(step.op, exp.Except):\n            sink.rows = list(set(left.rows).difference(set(right.rows)))\n        elif issubclass(step.op, exp.Union) and step.distinct:\n            sink.rows = list(set(left.rows).union(set(right.rows)))\n        else:\n            sink.rows = left.rows + right.rows\n\n        if not math.isinf(step.limit):\n            sink.rows = sink.rows[0 : step.limit]\n\n        return self.context({step.name: sink})\n\n\ndef _ordered_py(self, expression):\n    this = self.sql(expression, \"this\")\n    desc = \"True\" if expression.args.get(\"desc\") else \"False\"\n    nulls_first = \"True\" if expression.args.get(\"nulls_first\") else \"False\"\n    return f\"ORDERED({this}, {desc}, {nulls_first})\"\n\n\ndef _rename(self, e):\n    try:\n        values = list(e.args.values())\n\n        if len(values) == 1:\n            values = values[0]\n            if not isinstance(values, list):\n                return self.func(e.key, values)\n            return self.func(e.key, *values)\n\n        if isinstance(e, exp.Func) and e.is_var_len_args:\n            args = itertools.chain.from_iterable(x if isinstance(x, list) else [x] for x in values)\n            return self.func(e.key, *args)\n\n        return self.func(e.key, *values)\n    except Exception as ex:\n        raise Exception(f\"Could not rename {repr(e)}\") from ex\n\n\ndef _case_sql(self, expression):\n    this = self.sql(expression, \"this\")\n    chain = self.sql(expression, \"default\") or \"None\"\n\n    for e in reversed(expression.args[\"ifs\"]):\n        true = self.sql(e, \"true\")\n        condition = self.sql(e, \"this\")\n        condition = f\"{this} = ({condition})\" if this else condition\n        chain = f\"{true} if {condition} else ({chain})\"\n\n    return chain\n\n\ndef _lambda_sql(self, e: exp.Lambda) -> str:\n    names = {e.name.lower() for e in e.expressions}\n\n    e = e.transform(\n        lambda n: (\n            exp.var(n.name) if isinstance(n, exp.Identifier) and n.name.lower() in names else n\n        )\n    ).assert_is(exp.Lambda)\n\n    return f\"lambda {self.expressions(e, flat=True)}: {self.sql(e, 'this')}\"\n\n\ndef _div_sql(self: generator.Generator, e: exp.Div) -> str:\n    denominator = self.sql(e, \"expression\")\n\n    if e.args.get(\"safe\"):\n        denominator += \" or None\"\n\n    sql = f\"DIV({self.sql(e, 'this')}, {denominator})\"\n\n    if e.args.get(\"typed\"):\n        sql = f\"int({sql})\"\n\n    return sql\n\n\nclass Python(Dialect):\n    class Tokenizer(tokens.Tokenizer):\n        STRING_ESCAPES = [\"\\\\\"]\n\n    class Generator(generator.Generator):\n        TRANSFORMS = {\n            **{klass: _rename for klass in subclasses(exp.__name__, exp.Binary)},\n            **{klass: _rename for klass in exp.ALL_FUNCTIONS},\n            exp.Case: _case_sql,\n            exp.Alias: lambda self, e: self.sql(e.this),\n            exp.Array: inline_array_sql,\n            exp.And: lambda self, e: self.binary(e, \"and\"),\n            exp.Between: _rename,\n            exp.Boolean: lambda self, e: \"True\" if e.this else \"False\",\n            exp.Cast: lambda self, e: f\"CAST({self.sql(e.this)}, exp.DType.{e.args['to']})\",\n            exp.Column: lambda self, e: (\n                f\"scope[{self.sql(e, 'table') or None}][{self.sql(e.this)}]\"\n            ),\n            exp.Concat: lambda self, e: self.func(\n                \"SAFECONCAT\" if e.args.get(\"safe\") else \"CONCAT\", *e.expressions\n            ),\n            exp.Distinct: lambda self, e: f\"set({self.sql(e, 'this')})\",\n            exp.Div: _div_sql,\n            exp.Extract: lambda self, e: (\n                f\"EXTRACT('{e.name.lower()}', {self.sql(e, 'expression')})\"\n            ),\n            exp.In: lambda self, e: (\n                f\"{self.sql(e, 'this')} in {{{self.expressions(e, flat=True)}}}\"\n            ),\n            exp.Interval: lambda self, e: f\"INTERVAL({self.sql(e.this)}, '{self.sql(e.unit)}')\",\n            exp.Is: lambda self, e: (\n                self.binary(e, \"==\") if isinstance(e.this, exp.Literal) else self.binary(e, \"is\")\n            ),\n            exp.JSONExtract: lambda self, e: self.func(e.key, e.this, e.expression, *e.expressions),\n            exp.JSONPath: lambda self, e: f\"[{','.join(self.sql(p) for p in e.expressions[1:])}]\",\n            exp.JSONPathKey: lambda self, e: f\"'{self.sql(e.this)}'\",\n            exp.JSONPathSubscript: lambda self, e: f\"'{e.this}'\",\n            exp.Lambda: _lambda_sql,\n            exp.Not: lambda self, e: f\"not {self.sql(e.this)}\",\n            exp.Null: lambda *_: \"None\",\n            exp.Or: lambda self, e: self.binary(e, \"or\"),\n            exp.Ordered: _ordered_py,\n            exp.Star: lambda *_: \"1\",\n        }\n"
  },
  {
    "path": "sqlglot/executor/table.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.dialects.dialect import DialectType\nfrom sqlglot.helper import dict_depth\nfrom sqlglot.schema import AbstractMappingSchema, normalize_name\n\n\nclass Table:\n    def __init__(\n        self,\n        columns: t.Any = None,\n        rows: t.Any = None,\n        column_range: t.Optional[range] = None,\n    ) -> None:\n        self.columns: t.Any = tuple(columns) if columns is not None else ()\n        self.column_range = column_range\n        self.reader: t.Any = RowReader(self.columns, self.column_range)\n        self.rows: t.Any = rows or []\n        if rows:\n            assert len(rows[0]) == len(self.columns)\n        self.range_reader = RangeReader(self)\n\n    def add_columns(self, *columns: str) -> None:\n        self.columns += columns\n        if self.column_range:\n            self.column_range = range(\n                self.column_range.start, self.column_range.stop + len(columns)\n            )\n        self.reader = RowReader(self.columns, self.column_range)\n\n    def append(self, row: t.Any) -> None:\n        assert len(row) == len(self.columns)\n        self.rows.append(row)\n\n    def pop(self) -> None:\n        self.rows.pop()\n\n    def to_pylist(self) -> t.List:\n        return [dict(zip(self.columns, row)) for row in self.rows]\n\n    @property\n    def width(self) -> int:\n        return len(self.columns)\n\n    def __len__(self) -> int:\n        return len(self.rows)\n\n    def __iter__(self) -> TableIter:\n        return TableIter(self)\n\n    def __getitem__(self, index: int) -> RowReader:\n        self.reader.row = self.rows[index]\n        return self.reader\n\n    def __repr__(self) -> str:\n        columns = tuple(\n            column\n            for i, column in enumerate(self.columns)\n            if not self.column_range or i in self.column_range\n        )\n        widths = {column: len(column) for column in columns}\n        lines = [\" \".join(column for column in columns)]\n\n        for i, row in enumerate(self):\n            if i > 10:\n                break\n\n            lines.append(\n                \" \".join(\n                    str(row[column]).rjust(widths[column])[0 : widths[column]] for column in columns\n                )\n            )\n        return \"\\n\".join(lines)\n\n\nclass TableIter:\n    def __init__(self, table: Table) -> None:\n        self.table = table\n        self.index = -1\n\n    def __iter__(self) -> TableIter:\n        return self\n\n    def __next__(self) -> RowReader:\n        self.index += 1\n        if self.index < len(self.table):\n            return self.table[self.index]\n        raise StopIteration\n\n\nclass RangeReader:\n    def __init__(self, table: t.Any = None) -> None:\n        self.table: t.Any = table\n        self.range = range(0)\n\n    def __len__(self) -> int:\n        return len(self.range)\n\n    def __getitem__(self, column: str):\n        return (self.table[i][column] for i in self.range)\n\n\nclass RowReader:\n    def __init__(self, columns=None, column_range=None):\n        self.columns = (\n            {column: i for i, column in enumerate(columns) if not column_range or i in column_range}\n            if columns is not None\n            else {}\n        )\n        self.row = None\n\n    def __getitem__(self, column):\n        return self.row[self.columns[column]]\n\n\nclass Tables(AbstractMappingSchema):\n    pass\n\n\ndef ensure_tables(d: t.Optional[t.Dict], dialect: DialectType = None) -> Tables:\n    return Tables(_ensure_tables(d, dialect=dialect))\n\n\ndef _ensure_tables(d: t.Optional[t.Dict], dialect: DialectType = None) -> t.Dict:\n    if not d:\n        return {}\n\n    depth = dict_depth(d)\n    if depth > 1:\n        return {\n            normalize_name(k, dialect=dialect, is_table=True).name: _ensure_tables(\n                v, dialect=dialect\n            )\n            for k, v in d.items()\n        }\n\n    result = {}\n    for table_name, table in d.items():\n        table_name = normalize_name(table_name, dialect=dialect).name\n\n        if isinstance(table, Table):\n            result[table_name] = table\n        else:\n            table = [\n                {\n                    normalize_name(column_name, dialect=dialect).name: value\n                    for column_name, value in row.items()\n                }\n                for row in table\n            ]\n            column_names = tuple(column_name for column_name in table[0]) if table else ()\n            rows = [tuple(row[name] for name in column_names) for row in table]\n            result[table_name] = Table(columns=column_names, rows=rows)\n\n    return result\n"
  },
  {
    "path": "sqlglot/expressions/__init__.py",
    "content": "# ruff: noqa: F405\n\"\"\"\n## Exprs\n\nEvery AST node in SQLGlot is represented by a subclass of `Expr`.\n\nThis module contains the implementation of all supported `Expr` types. Additionally,\nit exposes a number of helper functions, which are mainly used to programmatically build\nSQL expressions, such as `sqlglot.expressions.select`.\n\n----\n\"\"\"\n\nimport typing as t\n\nfrom sqlglot.expressions.core import *  # noqa: F401,F403\nfrom sqlglot.expressions.datatypes import *  # noqa: F401,F403\nfrom sqlglot.expressions.constraints import *  # noqa: F401,F403\nfrom sqlglot.expressions.properties import *  # noqa: F401,F403\nfrom sqlglot.expressions.query import *  # noqa: F401,F403\nfrom sqlglot.expressions.ddl import *  # noqa: F401,F403\nfrom sqlglot.expressions.dml import *  # noqa: F401,F403\nfrom sqlglot.expressions.math import *  # noqa: F401,F403\nfrom sqlglot.expressions.string import *  # noqa: F401,F403\nfrom sqlglot.expressions.temporal import *  # noqa: F401,F403\nfrom sqlglot.expressions.aggregate import *  # noqa: F401,F403\nfrom sqlglot.expressions.array import *  # noqa: F401,F403\nfrom sqlglot.expressions.json import *  # noqa: F401,F403\nfrom sqlglot.expressions.functions import *  # noqa: F401,F403\nfrom sqlglot.expressions.builders import *  # noqa: F401,F403\n\n# Explicitly import private helpers (not exported by star imports)\nfrom sqlglot.expressions.core import (  # noqa: F401,E402\n    Expression,\n    _apply_builder,\n    _apply_child_list_builder,\n    _apply_list_builder,\n    _apply_conjunction_builder,\n    _apply_set_operation,\n    _combine,\n    _wrap,\n    _is_wrong_expression,\n    _to_s,\n)\nfrom sqlglot.expressions.query import _apply_cte_builder  # noqa: F401,E402\nfrom sqlglot.expressions.dml import DML  # noqa: F401,E402\nfrom sqlglot.expressions.array import _ExplodeOuter  # noqa: F401,E402\n\nfrom sqlglot.helper import subclasses\n\nALL_FUNCTIONS = subclasses(__name__, Func, {AggFunc, Anonymous, Func})\nFUNCTION_BY_NAME = {name: func for func in ALL_FUNCTIONS for name in func.sql_names()}\n\n\ndef _init_subclasses(cls: t.Type[Expr]) -> None:\n    # mypyc fires __init_subclass__ before setting compiled ClassVar attributes,\n    # so required_args may have been computed from the inherited arg_types rather\n    # than the class-specific one. Recompute now that all modules are fully loaded.\n    for sub in cls.__subclasses__():\n        sub.required_args = {k for k, v in sub.arg_types.items() if v}\n        _init_subclasses(sub)\n\n\n_init_subclasses(Expr)\n"
  },
  {
    "path": "sqlglot/expressions/aggregate.py",
    "content": "\"\"\"sqlglot expressions - aggregate, window, and statistical functions.\"\"\"\n\nfrom __future__ import annotations\n\nfrom sqlglot.expressions.core import Expression, Func, AggFunc, Binary\n\n\nclass AIAgg(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n    _sql_names = [\"AI_AGG\"]\n\n\nclass AISummarizeAgg(Expression, AggFunc):\n    _sql_names = [\"AI_SUMMARIZE_AGG\"]\n\n\nclass AnyValue(Expression, AggFunc):\n    pass\n\n\nclass ApproximateSimilarity(Expression, AggFunc):\n    _sql_names = [\"APPROXIMATE_SIMILARITY\", \"APPROXIMATE_JACCARD_INDEX\"]\n\n\nclass ApproxPercentileAccumulate(Expression, AggFunc):\n    pass\n\n\nclass ApproxPercentileCombine(Expression, AggFunc):\n    pass\n\n\nclass ApproxPercentileEstimate(Expression, Func):\n    arg_types = {\"this\": True, \"percentile\": True}\n\n\nclass ApproxQuantiles(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass ApproxTopK(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": False, \"counters\": False}\n\n\nclass ApproxTopKAccumulate(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass ApproxTopKCombine(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass ApproxTopKEstimate(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass ApproxTopSum(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True, \"count\": True}\n\n\nclass ArgMax(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True, \"count\": False}\n    _sql_names = [\"ARG_MAX\", \"ARGMAX\", \"MAX_BY\"]\n\n\nclass ArgMin(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True, \"count\": False}\n    _sql_names = [\"ARG_MIN\", \"ARGMIN\", \"MIN_BY\"]\n\n\nclass ArrayAgg(Expression, AggFunc):\n    arg_types = {\"this\": True, \"nulls_excluded\": False}\n\n\nclass ArrayConcatAgg(Expression, AggFunc):\n    pass\n\n\nclass ArrayUnionAgg(Expression, AggFunc):\n    pass\n\n\nclass ArrayUniqueAgg(Expression, AggFunc):\n    pass\n\n\nclass Avg(Expression, AggFunc):\n    pass\n\n\nclass Corr(Expression, AggFunc, Binary):\n    # Correlation divides by variance(column). If a column has 0 variance, the denominator\n    # is 0 - some dialects return NaN (DuckDB) while others return NULL (Snowflake).\n    # `null_on_zero_variance` is set to True at parse time for dialects that return NULL.\n    arg_types = {\"this\": True, \"expression\": True, \"null_on_zero_variance\": False}\n\n\nclass Count(Expression, AggFunc):\n    arg_types = {\"this\": False, \"expressions\": False, \"big_int\": False}\n    is_var_len_args = True\n\n\nclass CountIf(Expression, AggFunc):\n    _sql_names = [\"COUNT_IF\", \"COUNTIF\"]\n\n\nclass CovarPop(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass CovarSamp(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass CumeDist(Expression, AggFunc):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass DenseRank(Expression, AggFunc):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass First(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass FirstValue(Expression, AggFunc):\n    pass\n\n\nclass GroupConcat(Expression, AggFunc):\n    arg_types = {\"this\": True, \"separator\": False, \"on_overflow\": False}\n\n\nclass Grouping(Expression, AggFunc):\n    arg_types = {\"expressions\": True}\n    is_var_len_args = True\n\n\nclass GroupingId(Expression, AggFunc):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass Kurtosis(Expression, AggFunc):\n    pass\n\n\nclass Lag(Expression, AggFunc):\n    arg_types = {\"this\": True, \"offset\": False, \"default\": False}\n\n\nclass Last(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass LastValue(Expression, AggFunc):\n    pass\n\n\nclass Lead(Expression, AggFunc):\n    arg_types = {\"this\": True, \"offset\": False, \"default\": False}\n\n\nclass LogicalAnd(Expression, AggFunc):\n    _sql_names = [\"LOGICAL_AND\", \"BOOL_AND\", \"BOOLAND_AGG\"]\n\n\nclass LogicalOr(Expression, AggFunc):\n    _sql_names = [\"LOGICAL_OR\", \"BOOL_OR\", \"BOOLOR_AGG\"]\n\n\nclass Max(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n\n\nclass Median(Expression, AggFunc):\n    pass\n\n\nclass Min(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n\n\nclass Minhash(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expressions\": True}\n    is_var_len_args = True\n\n\nclass MinhashCombine(Expression, AggFunc):\n    pass\n\n\nclass Mode(Expression, AggFunc):\n    arg_types = {\"this\": False, \"deterministic\": False}\n\n\nclass Ntile(Expression, AggFunc):\n    arg_types = {\"this\": False}\n\n\nclass NthValue(Expression, AggFunc):\n    arg_types = {\"this\": True, \"offset\": True, \"from_first\": False}\n\n\nclass ObjectAgg(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass PercentileCont(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass PercentileDisc(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nPERCENTILES = (PercentileCont, PercentileDisc)\n\n\nclass PercentRank(Expression, AggFunc):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass Quantile(Expression, AggFunc):\n    arg_types = {\"this\": True, \"quantile\": True}\n\n\nclass ApproxQuantile(Quantile):\n    arg_types = {\n        \"this\": True,\n        \"quantile\": True,\n        \"accuracy\": False,\n        \"weight\": False,\n        \"error_tolerance\": False,\n    }\n\n\nclass Rank(Expression, AggFunc):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass RegrAvgx(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrAvgy(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrCount(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrIntercept(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrR2(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrSlope(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrSxx(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrSxy(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrSyy(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrValx(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RegrValy(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RowNumber(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass Skewness(Expression, AggFunc):\n    pass\n\n\nclass Stddev(Expression, AggFunc):\n    _sql_names = [\"STDDEV\", \"STDEV\"]\n\n\nclass StddevPop(Expression, AggFunc):\n    pass\n\n\nclass StddevSamp(Expression, AggFunc):\n    pass\n\n\nclass Sum(Expression, AggFunc):\n    pass\n\n\nclass Variance(Expression, AggFunc):\n    _sql_names = [\"VARIANCE\", \"VARIANCE_SAMP\", \"VAR_SAMP\"]\n\n\nclass VariancePop(Expression, AggFunc):\n    _sql_names = [\"VARIANCE_POP\", \"VAR_POP\"]\n"
  },
  {
    "path": "sqlglot/expressions/array.py",
    "content": "\"\"\"sqlglot expressions - array, map, struct, and table-valued functions.\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.expressions.core import (\n    Expression,\n    Expr,\n    Func,\n    Binary,\n    to_identifier,\n)\nfrom sqlglot.helper import trait\nfrom sqlglot.expressions.query import UDTF\n\n\n# Array creation / construction\n\n\nclass Array(Expression, Func):\n    arg_types = {\n        \"expressions\": False,\n        \"bracket_notation\": False,\n        \"struct_name_inheritance\": False,\n    }\n    is_var_len_args = True\n\n\nclass ArrayConstructCompact(Expression, Func):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass List(Expression, Func):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass ToArray(Expression, Func):\n    pass\n\n\n# Array manipulation\n\n\nclass ArrayAppend(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"null_propagation\": False}\n\n\nclass ArrayCompact(Expression, Func):\n    pass\n\n\nclass ArrayConcat(Expression, Func):\n    _sql_names = [\"ARRAY_CONCAT\", \"ARRAY_CAT\"]\n    arg_types = {\"this\": True, \"expressions\": False, \"null_propagation\": False}\n    is_var_len_args = True\n\n\nclass ArrayFilter(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n    _sql_names = [\"FILTER\", \"ARRAY_FILTER\"]\n\n\nclass ArrayInsert(Expression, Func):\n    arg_types = {\"this\": True, \"position\": True, \"expression\": True, \"offset\": False}\n\n\nclass ArrayPrepend(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"null_propagation\": False}\n\n\nclass ArrayRemove(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"null_propagation\": False}\n\n\nclass ArrayRemoveAt(Expression, Func):\n    arg_types = {\"this\": True, \"position\": True}\n\n\nclass ArrayReverse(Expression, Func):\n    pass\n\n\nclass ArraySlice(Expression, Func):\n    arg_types = {\"this\": True, \"start\": True, \"end\": False, \"step\": False, \"zero_based\": False}\n\n\nclass ArraySort(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass SortArray(Expression, Func):\n    arg_types = {\"this\": True, \"asc\": False, \"nulls_first\": False}\n\n\n# Array predicates / search\n\n\nclass ArrayAll(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass ArrayAny(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass ArrayContains(Expression, Binary, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"ensure_variant\": False, \"check_null\": False}\n    _sql_names = [\"ARRAY_CONTAINS\", \"ARRAY_HAS\"]\n\n\nclass ArrayContainsAll(Expression, Binary, Func):\n    _sql_names = [\"ARRAY_CONTAINS_ALL\", \"ARRAY_HAS_ALL\"]\n\n\nclass ArrayExcept(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"is_multiset\": False}\n\n\nclass ArrayIntersect(Expression, Func):\n    arg_types = {\"expressions\": True, \"is_multiset\": False}\n    is_var_len_args = True\n    _sql_names = [\"ARRAY_INTERSECT\", \"ARRAY_INTERSECTION\"]\n\n\nclass ArrayOverlaps(Expression, Binary, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"null_safe\": False}\n\n\nclass ArrayPosition(Expression, Binary, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"zero_based\": False}\n\n\n# Array properties\n\n\nclass ArrayDistinct(Expression, Func):\n    arg_types = {\"this\": True, \"check_null\": False}\n\n\nclass ArrayFirst(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass ArrayLast(Expression, Func):\n    pass\n\n\nclass ArrayMax(Expression, Func):\n    pass\n\n\nclass ArrayMin(Expression, Func):\n    pass\n\n\nclass ArraySize(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n    _sql_names = [\"ARRAY_SIZE\", \"ARRAY_LENGTH\"]\n\n\nclass ArraySum(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\n# Array conversion / utility\n\n\nclass ArraysZip(Expression, Func):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass ArrayToString(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"null\": False,\n        \"null_is_empty\": False,\n        \"null_delim_is_null\": False,\n    }\n    _sql_names = [\"ARRAY_TO_STRING\", \"ARRAY_JOIN\"]\n\n\nclass Flatten(Expression, Func):\n    pass\n\n\nclass StringToArray(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False, \"null\": False}\n    _sql_names = [\"STRING_TO_ARRAY\", \"SPLIT_BY_STRING\", \"STRTOK_TO_ARRAY\"]\n\n\n# Higher-order / lambda\n\n\nclass Apply(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Reduce(Expression, Func):\n    arg_types = {\"this\": True, \"initial\": True, \"merge\": True, \"finish\": False}\n\n\nclass Transform(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\n# Table-valued / UDTF\n\n\nclass GenerateSeries(Expression, Func):\n    arg_types = {\"start\": True, \"end\": True, \"step\": False, \"is_end_exclusive\": False}\n\n\nclass ExplodingGenerateSeries(GenerateSeries):\n    pass\n\n\nclass Generator(Expression, Func, UDTF):\n    arg_types = {\"rowcount\": False, \"timelimit\": False}\n\n\nclass Explode(Expression, Func, UDTF):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n\n\nclass Inline(Expression, Func):\n    pass\n\n\n@trait\nclass ExplodeOuter(Expr):\n    pass\n\n\nclass _ExplodeOuter(Explode, ExplodeOuter):\n    _sql_names = [\"EXPLODE_OUTER\"]\n\n\nclass Posexplode(Explode):\n    pass\n\n\nclass PosexplodeOuter(Posexplode, ExplodeOuter):\n    pass\n\n\nclass PositionalColumn(Expression):\n    pass\n\n\nclass Unnest(Expression, Func, UDTF):\n    arg_types = {\n        \"expressions\": True,\n        \"alias\": False,\n        \"offset\": False,\n        \"explode_array\": False,\n    }\n\n    @property\n    def selects(self) -> t.List[Expr]:\n        columns = super().selects\n        offset = self.args.get(\"offset\")\n        if offset:\n            columns = columns + [to_identifier(\"offset\") if offset is True else offset]\n        return columns\n\n\n# Map\n\n\nclass Map(Expression, Func):\n    arg_types = {\"keys\": False, \"values\": False}\n\n    @property\n    def keys(self) -> t.List[Expr]:\n        keys = self.args.get(\"keys\")\n        return keys.expressions if keys else []\n\n    @property\n    def values(self) -> t.List[Expr]:\n        values = self.args.get(\"values\")\n        return values.expressions if values else []\n\n\nclass MapCat(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass MapContainsKey(Expression, Func):\n    arg_types = {\"this\": True, \"key\": True}\n\n\nclass MapDelete(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True}\n    is_var_len_args = True\n\n\nclass MapFromEntries(Expression, Func):\n    pass\n\n\nclass MapInsert(Expression, Func):\n    arg_types = {\"this\": True, \"key\": False, \"value\": True, \"update_flag\": False}\n\n\nclass MapKeys(Expression, Func):\n    pass\n\n\nclass MapPick(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True}\n    is_var_len_args = True\n\n\nclass MapSize(Expression, Func):\n    pass\n\n\nclass StarMap(Expression, Func):\n    pass\n\n\nclass ToMap(Expression, Func):\n    pass\n\n\nclass VarMap(Expression, Func):\n    arg_types = {\"keys\": True, \"values\": True}\n    is_var_len_args = True\n\n    @property\n    def keys(self) -> t.List[Expr]:\n        return self.args[\"keys\"].expressions\n\n    @property\n    def values(self) -> t.List[Expr]:\n        return self.args[\"values\"].expressions\n\n\n# Struct\n\n\nclass Struct(Expression, Func):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass StructExtract(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\n# Geospatial\n\n\nclass StDistance(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"use_spheroid\": False}\n\n\nclass StPoint(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"null\": False}\n    _sql_names = [\"ST_POINT\", \"ST_MAKEPOINT\"]\n"
  },
  {
    "path": "sqlglot/expressions/builders.py",
    "content": "\"\"\"sqlglot expressions builders.\"\"\"\n\nfrom __future__ import annotations\n\nimport re\nimport typing as t\n\nfrom sqlglot._typing import E\nfrom sqlglot.helper import seq_get, ensure_collection, split_num_words\nfrom sqlglot.errors import ParseError, TokenError\nfrom sqlglot.expressions.core import (\n    Alias,\n    Anonymous,\n    Boolean,\n    Column,\n    Condition,\n    EQ,\n    Expr,\n    Identifier,\n    Literal,\n    Null,\n    Placeholder,\n    TABLE_PARTS,\n    Var,\n    logger,\n    SAFE_IDENTIFIER_RE,\n    maybe_parse,\n    maybe_copy,\n    to_identifier,\n    convert,\n    alias_,\n    column,\n)\nfrom sqlglot.expressions.datatypes import DataType, DType, Interval\nfrom sqlglot.expressions.query import (\n    CTE,\n    From,\n    Schema,\n    Select,\n    Table,\n    TableAlias,\n    Tuple,\n    Values,\n    Where,\n    With,\n)\nfrom sqlglot.expressions.ddl import Alter, AlterRename, RenameColumn\nfrom sqlglot.expressions.dml import Delete, Insert, Merge, Update, When, Whens\nfrom sqlglot.expressions.functions import Case, Cast\nfrom sqlglot.expressions.array import Array\n\nif t.TYPE_CHECKING:\n    from collections.abc import Sequence, Iterable\n    from sqlglot.dialects.dialect import DialectType\n    from sqlglot.expressions.core import ExpOrStr, Func\n    from sqlglot.expressions.datatypes import DATA_TYPE\n    from sqlglot.expressions.query import Query\n\n\ndef select(*expressions: ExpOrStr, dialect: DialectType = None, **opts) -> Select:\n    \"\"\"\n    Initializes a syntax tree from one or multiple SELECT expressions.\n\n    Example:\n        >>> select(\"col1\", \"col2\").from_(\"tbl\").sql()\n        'SELECT col1, col2 FROM tbl'\n\n    Args:\n        *expressions: the SQL code string to parse as the expressions of a\n            SELECT statement. If an Expr instance is passed, this is used as-is.\n        dialect: the dialect used to parse the input expressions (in the case that an\n            input expression is a SQL string).\n        **opts: other options to use to parse the input expressions (again, in the case\n            that an input expression is a SQL string).\n\n    Returns:\n        Select: the syntax tree for the SELECT statement.\n    \"\"\"\n    return Select().select(*expressions, dialect=dialect, **opts)\n\n\ndef from_(expression: ExpOrStr, dialect: DialectType = None, **opts) -> Select:\n    \"\"\"\n    Initializes a syntax tree from a FROM expression.\n\n    Example:\n        >>> from_(\"tbl\").select(\"col1\", \"col2\").sql()\n        'SELECT col1, col2 FROM tbl'\n\n    Args:\n        *expression: the SQL code string to parse as the FROM expressions of a\n            SELECT statement. If an Expr instance is passed, this is used as-is.\n        dialect: the dialect used to parse the input expression (in the case that the\n            input expression is a SQL string).\n        **opts: other options to use to parse the input expressions (again, in the case\n            that the input expression is a SQL string).\n\n    Returns:\n        Select: the syntax tree for the SELECT statement.\n    \"\"\"\n    return Select().from_(expression, dialect=dialect, **opts)\n\n\ndef update(\n    table: str | Table,\n    properties: t.Optional[dict] = None,\n    where: t.Optional[ExpOrStr] = None,\n    from_: t.Optional[ExpOrStr] = None,\n    with_: t.Optional[t.Dict[str, ExpOrStr]] = None,\n    dialect: DialectType = None,\n    **opts,\n) -> Update:\n    \"\"\"\n    Creates an update statement.\n\n    Example:\n        >>> update(\"my_table\", {\"x\": 1, \"y\": \"2\", \"z\": None}, from_=\"baz_cte\", where=\"baz_cte.id > 1 and my_table.id = baz_cte.id\", with_={\"baz_cte\": \"SELECT id FROM foo\"}).sql()\n        \"WITH baz_cte AS (SELECT id FROM foo) UPDATE my_table SET x = 1, y = '2', z = NULL FROM baz_cte WHERE baz_cte.id > 1 AND my_table.id = baz_cte.id\"\n\n    Args:\n        properties: dictionary of properties to SET which are\n            auto converted to sql objects eg None -> NULL\n        where: sql conditional parsed into a WHERE statement\n        from_: sql statement parsed into a FROM statement\n        with_: dictionary of CTE aliases / select statements to include in a WITH clause.\n        dialect: the dialect used to parse the input expressions.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        Update: the syntax tree for the UPDATE statement.\n    \"\"\"\n    update_expr = Update(this=maybe_parse(table, into=Table, dialect=dialect))\n    if properties:\n        update_expr.set(\n            \"expressions\",\n            [\n                EQ(this=maybe_parse(k, dialect=dialect, **opts), expression=convert(v))\n                for k, v in properties.items()\n            ],\n        )\n    if from_:\n        update_expr.set(\n            \"from_\",\n            maybe_parse(from_, into=From, dialect=dialect, prefix=\"FROM\", **opts),\n        )\n    if isinstance(where, Condition):\n        where = Where(this=where)\n    if where:\n        update_expr.set(\n            \"where\",\n            maybe_parse(where, into=Where, dialect=dialect, prefix=\"WHERE\", **opts),\n        )\n    if with_:\n        cte_list = [\n            alias_(CTE(this=maybe_parse(qry, dialect=dialect, **opts)), alias, table=True)\n            for alias, qry in with_.items()\n        ]\n        update_expr.set(\n            \"with_\",\n            With(expressions=cte_list),\n        )\n    return update_expr\n\n\ndef delete(\n    table: ExpOrStr,\n    where: t.Optional[ExpOrStr] = None,\n    returning: t.Optional[ExpOrStr] = None,\n    dialect: DialectType = None,\n    **opts,\n) -> Delete:\n    \"\"\"\n    Builds a delete statement.\n\n    Example:\n        >>> delete(\"my_table\", where=\"id > 1\").sql()\n        'DELETE FROM my_table WHERE id > 1'\n\n    Args:\n        where: sql conditional parsed into a WHERE statement\n        returning: sql conditional parsed into a RETURNING statement\n        dialect: the dialect used to parse the input expressions.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        Delete: the syntax tree for the DELETE statement.\n    \"\"\"\n    delete_expr = Delete().delete(table, dialect=dialect, copy=False, **opts)\n    if where:\n        delete_expr = delete_expr.where(where, dialect=dialect, copy=False, **opts)\n    if returning:\n        delete_expr = delete_expr.returning(returning, dialect=dialect, copy=False, **opts)\n    return delete_expr\n\n\ndef insert(\n    expression: ExpOrStr,\n    into: ExpOrStr,\n    columns: t.Optional[Sequence[str | Identifier]] = None,\n    overwrite: t.Optional[bool] = None,\n    returning: t.Optional[ExpOrStr] = None,\n    dialect: DialectType = None,\n    copy: bool = True,\n    **opts,\n) -> Insert:\n    \"\"\"\n    Builds an INSERT statement.\n\n    Example:\n        >>> insert(\"VALUES (1, 2, 3)\", \"tbl\").sql()\n        'INSERT INTO tbl VALUES (1, 2, 3)'\n\n    Args:\n        expression: the sql string or expression of the INSERT statement\n        into: the tbl to insert data to.\n        columns: optionally the table's column names.\n        overwrite: whether to INSERT OVERWRITE or not.\n        returning: sql conditional parsed into a RETURNING statement\n        dialect: the dialect used to parse the input expressions.\n        copy: whether to copy the expression.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        Insert: the syntax tree for the INSERT statement.\n    \"\"\"\n    expr = maybe_parse(expression, dialect=dialect, copy=copy, **opts)\n    this: Table | Schema = maybe_parse(into, into=Table, dialect=dialect, copy=copy, **opts)\n\n    if columns:\n        this = Schema(this=this, expressions=[to_identifier(c, copy=copy) for c in columns])\n\n    insert = Insert(this=this, expression=expr, overwrite=overwrite)\n\n    if returning:\n        insert = insert.returning(returning, dialect=dialect, copy=False, **opts)\n\n    return insert\n\n\ndef merge(\n    *when_exprs: ExpOrStr,\n    into: ExpOrStr,\n    using: ExpOrStr,\n    on: ExpOrStr,\n    returning: t.Optional[ExpOrStr] = None,\n    dialect: DialectType = None,\n    copy: bool = True,\n    **opts,\n) -> Merge:\n    \"\"\"\n    Builds a MERGE statement.\n\n    Example:\n        >>> merge(\"WHEN MATCHED THEN UPDATE SET col1 = source_table.col1\",\n        ...       \"WHEN NOT MATCHED THEN INSERT (col1) VALUES (source_table.col1)\",\n        ...       into=\"my_table\",\n        ...       using=\"source_table\",\n        ...       on=\"my_table.id = source_table.id\").sql()\n        'MERGE INTO my_table USING source_table ON my_table.id = source_table.id WHEN MATCHED THEN UPDATE SET col1 = source_table.col1 WHEN NOT MATCHED THEN INSERT (col1) VALUES (source_table.col1)'\n\n    Args:\n        *when_exprs: The WHEN clauses specifying actions for matched and unmatched rows.\n        into: The target table to merge data into.\n        using: The source table to merge data from.\n        on: The join condition for the merge.\n        returning: The columns to return from the merge.\n        dialect: The dialect used to parse the input expressions.\n        copy: Whether to copy the expression.\n        **opts: Other options to use to parse the input expressions.\n\n    Returns:\n        Merge: The syntax tree for the MERGE statement.\n    \"\"\"\n    expressions: t.List[Expr] = []\n    for when_expr in when_exprs:\n        expression = maybe_parse(when_expr, dialect=dialect, copy=copy, into=Whens, **opts)\n        expressions.extend([expression] if isinstance(expression, When) else expression.expressions)\n\n    merge = Merge(\n        this=maybe_parse(into, dialect=dialect, copy=copy, **opts),\n        using=maybe_parse(using, dialect=dialect, copy=copy, **opts),\n        on=maybe_parse(on, dialect=dialect, copy=copy, **opts),\n        whens=Whens(expressions=expressions),\n    )\n    if returning:\n        merge = merge.returning(returning, dialect=dialect, copy=False, **opts)\n\n    if isinstance(using_clause := merge.args.get(\"using\"), Alias):\n        using_clause.replace(alias_(using_clause.this, using_clause.args[\"alias\"], table=True))\n\n    return merge\n\n\ndef parse_identifier(name: str | Identifier, dialect: DialectType = None) -> Identifier:\n    \"\"\"\n    Parses a given string into an identifier.\n\n    Args:\n        name: The name to parse into an identifier.\n        dialect: The dialect to parse against.\n\n    Returns:\n        The identifier ast node.\n    \"\"\"\n    try:\n        expression = maybe_parse(name, dialect=dialect, into=Identifier)\n    except (ParseError, TokenError):\n        expression = to_identifier(name)\n\n    return expression\n\n\nINTERVAL_STRING_RE = re.compile(r\"\\s*(-?[0-9]+(?:\\.[0-9]+)?)\\s*([a-zA-Z]+)\\s*\")\n\n\nINTERVAL_DAY_TIME_RE = re.compile(\n    r\"\\s*-?\\s*\\d+(?:\\.\\d+)?\\s+(?:-?(?:\\d+:)?\\d+:\\d+(?:\\.\\d+)?|-?(?:\\d+:){1,2}|:)\\s*\"\n)\n\n\ndef to_interval(interval: str | Expr) -> Interval:\n    \"\"\"Builds an interval expression from a string like '1 day' or '5 months'.\"\"\"\n    if isinstance(interval, Literal):\n        if not interval.is_string:\n            raise ValueError(\"Invalid interval string.\")\n\n        interval = interval.this\n\n    interval = maybe_parse(f\"INTERVAL {interval}\")\n    assert isinstance(interval, Interval)\n    return interval\n\n\ndef to_table(\n    sql_path: str | Table, dialect: DialectType = None, copy: bool = True, **kwargs\n) -> Table:\n    \"\"\"\n    Create a table expression from a `[catalog].[schema].[table]` sql path. Catalog and schema are optional.\n    If a table is passed in then that table is returned.\n\n    Args:\n        sql_path: a `[catalog].[schema].[table]` string.\n        dialect: the source dialect according to which the table name will be parsed.\n        copy: Whether to copy a table if it is passed in.\n        kwargs: the kwargs to instantiate the resulting `Table` expression with.\n\n    Returns:\n        A table expression.\n    \"\"\"\n    if isinstance(sql_path, Table):\n        return maybe_copy(sql_path, copy=copy)\n\n    try:\n        table = maybe_parse(sql_path, into=Table, dialect=dialect)\n    except ParseError:\n        catalog, db, this = split_num_words(sql_path, \".\", 3)\n\n        if not this:\n            raise\n\n        table = table_(this, db=db, catalog=catalog)\n\n    for k, v in kwargs.items():\n        table.set(k, v)\n\n    return table\n\n\ndef to_column(\n    sql_path: str | Column,\n    quoted: t.Optional[bool] = None,\n    dialect: DialectType = None,\n    copy: bool = True,\n    **kwargs,\n) -> Column:\n    \"\"\"\n    Create a column from a `[table].[column]` sql path. Table is optional.\n    If a column is passed in then that column is returned.\n\n    Args:\n        sql_path: a `[table].[column]` string.\n        quoted: Whether or not to force quote identifiers.\n        dialect: the source dialect according to which the column name will be parsed.\n        copy: Whether to copy a column if it is passed in.\n        kwargs: the kwargs to instantiate the resulting `Column` expression with.\n\n    Returns:\n        A column expression.\n    \"\"\"\n    if isinstance(sql_path, Column):\n        return maybe_copy(sql_path, copy=copy)\n\n    try:\n        col = maybe_parse(sql_path, into=Column, dialect=dialect)\n    except ParseError:\n        return column(*reversed(sql_path.split(\".\")), quoted=quoted, **kwargs)\n\n    for k, v in kwargs.items():\n        col.set(k, v)\n\n    if quoted:\n        for i in col.find_all(Identifier):\n            i.set(\"quoted\", True)\n\n    return col\n\n\ndef subquery(\n    expression: ExpOrStr,\n    alias: t.Optional[Identifier | str] = None,\n    dialect: DialectType = None,\n    **opts,\n) -> Select:\n    \"\"\"\n    Build a subquery expression that's selected from.\n\n    Example:\n        >>> subquery('select x from tbl', 'bar').select('x').sql()\n        'SELECT x FROM (SELECT x FROM tbl) AS bar'\n\n    Args:\n        expression: the SQL code strings to parse.\n            If an Expr instance is passed, this is used as-is.\n        alias: the alias name to use.\n        dialect: the dialect used to parse the input expression.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        A new Select instance with the subquery expression included.\n    \"\"\"\n\n    expression = maybe_parse(expression, dialect=dialect, **opts).subquery(alias, **opts)\n    return Select().from_(expression, dialect=dialect, **opts)\n\n\ndef cast(\n    expression: ExpOrStr, to: DATA_TYPE, copy: bool = True, dialect: DialectType = None, **opts\n) -> Cast:\n    \"\"\"Cast an expression to a data type.\n\n    Example:\n        >>> cast('x + 1', 'int').sql()\n        'CAST(x + 1 AS INT)'\n\n    Args:\n        expression: The expression to cast.\n        to: The datatype to cast to.\n        copy: Whether to copy the supplied expressions.\n        dialect: The target dialect. This is used to prevent a re-cast in the following scenario:\n            - The expression to be cast is already a exp.Cast expression\n            - The existing cast is to a type that is logically equivalent to new type\n\n            For example, if :expression='CAST(x as DATETIME)' and :to=Type.TIMESTAMP,\n            but in the target dialect DATETIME is mapped to TIMESTAMP, then we will NOT return `CAST(x (as DATETIME) as TIMESTAMP)`\n            and instead just return the original expression `CAST(x as DATETIME)`.\n\n            This is to prevent it being output as a double cast `CAST(x (as TIMESTAMP) as TIMESTAMP)` once the DATETIME -> TIMESTAMP\n            mapping is applied in the target dialect generator.\n\n    Returns:\n        The new Cast instance.\n    \"\"\"\n    expr = maybe_parse(expression, copy=copy, dialect=dialect, **opts)\n    data_type = DataType.build(to, copy=copy, dialect=dialect, **opts)\n\n    # dont re-cast if the expression is already a cast to the correct type\n    if isinstance(expr, Cast):\n        from sqlglot.dialects.dialect import Dialect\n\n        target_dialect = Dialect.get_or_raise(dialect)\n        type_mapping = target_dialect.generator_class.TYPE_MAPPING\n\n        existing_cast_type: DType = expr.to.this\n        new_cast_type: DType = data_type.this\n        types_are_equivalent = type_mapping.get(\n            existing_cast_type, existing_cast_type.value\n        ) == type_mapping.get(new_cast_type, new_cast_type.value)\n\n        if expr.is_type(data_type) or types_are_equivalent:\n            return expr\n\n    expr = Cast(this=expr, to=data_type)\n    expr.type = data_type\n\n    return expr\n\n\ndef table_(\n    table: Identifier | str,\n    db: t.Optional[Identifier | str] = None,\n    catalog: t.Optional[Identifier | str] = None,\n    quoted: t.Optional[bool] = None,\n    alias: t.Optional[Identifier | str] = None,\n) -> Table:\n    \"\"\"Build a Table.\n\n    Args:\n        table: Table name.\n        db: Database name.\n        catalog: Catalog name.\n        quote: Whether to force quotes on the table's identifiers.\n        alias: Table's alias.\n\n    Returns:\n        The new Table instance.\n    \"\"\"\n    return Table(\n        this=to_identifier(table, quoted=quoted) if table else None,\n        db=to_identifier(db, quoted=quoted) if db else None,\n        catalog=to_identifier(catalog, quoted=quoted) if catalog else None,\n        alias=TableAlias(this=to_identifier(alias)) if alias else None,\n    )\n\n\ndef values(\n    values: Iterable[tuple[object, ...] | Tuple],\n    alias: t.Optional[str] = None,\n    columns: t.Optional[Iterable[str] | dict[str, DataType]] = None,\n) -> Values:\n    \"\"\"Build VALUES statement.\n\n    Example:\n        >>> values([(1, '2')]).sql()\n        \"VALUES (1, '2')\"\n\n    Args:\n        values: values statements that will be converted to SQL\n        alias: optional alias\n        columns: Optional list of ordered column names or ordered dictionary of column names to types.\n         If either are provided then an alias is also required.\n\n    Returns:\n        Values: the Values expression object\n    \"\"\"\n    if columns and not alias:\n        raise ValueError(\"Alias is required when providing columns\")\n\n    return Values(\n        expressions=[convert(tup) for tup in values],\n        alias=(\n            TableAlias(this=to_identifier(alias), columns=[to_identifier(x) for x in columns])\n            if columns\n            else (TableAlias(this=to_identifier(alias)) if alias else None)\n        ),\n    )\n\n\ndef var(name: t.Optional[ExpOrStr]) -> Var:\n    \"\"\"Build a SQL variable.\n\n    Example:\n        >>> repr(var('x'))\n        'Var(this=x)'\n\n        >>> repr(var(column('x', table='y')))\n        'Var(this=x)'\n\n    Args:\n        name: The name of the var or an expression who's name will become the var.\n\n    Returns:\n        The new variable node.\n    \"\"\"\n    if not name:\n        raise ValueError(\"Cannot convert empty name into var.\")\n\n    if isinstance(name, Expr):\n        name = name.name\n    return Var(this=name)\n\n\ndef rename_table(\n    old_name: str | Table,\n    new_name: str | Table,\n    dialect: DialectType = None,\n) -> Alter:\n    \"\"\"Build ALTER TABLE... RENAME... expression\n\n    Args:\n        old_name: The old name of the table\n        new_name: The new name of the table\n        dialect: The dialect to parse the table.\n\n    Returns:\n        Alter table expression\n    \"\"\"\n    old_table = to_table(old_name, dialect=dialect)\n    new_table = to_table(new_name, dialect=dialect)\n    return Alter(\n        this=old_table,\n        kind=\"TABLE\",\n        actions=[\n            AlterRename(this=new_table),\n        ],\n    )\n\n\ndef rename_column(\n    table_name: str | Table,\n    old_column_name: str | Column,\n    new_column_name: str | Column,\n    exists: t.Optional[bool] = None,\n    dialect: DialectType = None,\n) -> Alter:\n    \"\"\"Build ALTER TABLE... RENAME COLUMN... expression\n\n    Args:\n        table_name: Name of the table\n        old_column: The old name of the column\n        new_column: The new name of the column\n        exists: Whether to add the `IF EXISTS` clause\n        dialect: The dialect to parse the table/column.\n\n    Returns:\n        Alter table expression\n    \"\"\"\n    table = to_table(table_name, dialect=dialect)\n    old_column = to_column(old_column_name, dialect=dialect)\n    new_column = to_column(new_column_name, dialect=dialect)\n    return Alter(\n        this=table,\n        kind=\"TABLE\",\n        actions=[\n            RenameColumn(this=old_column, to=new_column, exists=exists),\n        ],\n    )\n\n\ndef replace_children(expression: Expr, fun: t.Callable, *args, **kwargs) -> None:\n    \"\"\"\n    Replace children of an expression with the result of a lambda fun(child) -> exp.\n    \"\"\"\n    for k, v in tuple(expression.args.items()):\n        is_list_arg = type(v) is list\n\n        child_nodes = v if is_list_arg else [v]\n        new_child_nodes = []\n\n        for cn in child_nodes:\n            if isinstance(cn, Expr):\n                for child_node in ensure_collection(fun(cn, *args, **kwargs)):\n                    new_child_nodes.append(child_node)\n            else:\n                new_child_nodes.append(cn)\n\n        if is_list_arg:\n            expression.set(k, new_child_nodes)\n        else:\n            expression.set(k, seq_get(new_child_nodes, 0))\n\n\ndef replace_tree(\n    expression: Expr,\n    fun: t.Callable,\n    prune: t.Optional[t.Callable[[Expr], bool]] = None,\n) -> Expr:\n    \"\"\"\n    Replace an entire tree with the result of function calls on each node.\n\n    This will be traversed in reverse dfs, so leaves first.\n    If new nodes are created as a result of function calls, they will also be traversed.\n    \"\"\"\n    stack = list(expression.dfs(prune=prune))\n\n    while stack:\n        node = stack.pop()\n        new_node = fun(node)\n\n        if new_node is not node:\n            node.replace(new_node)\n\n            if isinstance(new_node, Expr):\n                stack.append(new_node)\n\n    return new_node\n\n\ndef find_tables(expression: Expr) -> t.Set[Table]:\n    \"\"\"\n    Find all tables referenced in a query.\n\n    Args:\n        expressions: The query to find the tables in.\n\n    Returns:\n        A set of all the tables.\n    \"\"\"\n    from sqlglot.optimizer.scope import traverse_scope\n\n    return {\n        table\n        for scope in traverse_scope(expression)\n        for table in scope.tables\n        if isinstance(table, Table) and table.name and table.name not in scope.cte_sources\n    }\n\n\ndef column_table_names(expression: Expr, exclude: str = \"\") -> t.Set[str]:\n    \"\"\"\n    Return all table names referenced through columns in an expression.\n\n    Example:\n        >>> import sqlglot\n        >>> sorted(column_table_names(sqlglot.parse_one(\"a.b AND c.d AND c.e\")))\n        ['a', 'c']\n\n    Args:\n        expression: expression to find table names.\n        exclude: a table name to exclude\n\n    Returns:\n        A list of unique names.\n    \"\"\"\n    return {\n        table\n        for table in (column.table for column in expression.find_all(Column))\n        if table and table != exclude\n    }\n\n\ndef table_name(table: Table | str, dialect: DialectType = None, identify: bool = False) -> str:\n    \"\"\"Get the full name of a table as a string.\n\n    Args:\n        table: Table expression node or string.\n        dialect: The dialect to generate the table name for.\n        identify: Determines when an identifier should be quoted. Possible values are:\n            False (default): Never quote, except in cases where it's mandatory by the dialect.\n            True: Always quote.\n\n    Examples:\n        >>> from sqlglot import exp, parse_one\n        >>> table_name(parse_one(\"select * from a.b.c\").find(exp.Table))\n        'a.b.c'\n\n    Returns:\n        The table name.\n    \"\"\"\n\n    expr = maybe_parse(table, into=Table, dialect=dialect)\n\n    if not expr:\n        raise ValueError(f\"Cannot parse {table}\")\n\n    return \".\".join(\n        (\n            part.sql(dialect=dialect, identify=True, copy=False, comments=False)\n            if identify or not SAFE_IDENTIFIER_RE.match(part.name)\n            else part.name\n        )\n        for part in expr.parts\n    )\n\n\ndef normalize_table_name(table: str | Table, dialect: DialectType = None, copy: bool = True) -> str:\n    \"\"\"Returns a case normalized table name without quotes.\n\n    Args:\n        table: the table to normalize\n        dialect: the dialect to use for normalization rules\n        copy: whether to copy the expression.\n\n    Examples:\n        >>> normalize_table_name(\"`A-B`.c\", dialect=\"bigquery\")\n        'A-B.c'\n    \"\"\"\n    from sqlglot.optimizer.normalize_identifiers import normalize_identifiers\n\n    return \".\".join(\n        p.name\n        for p in normalize_identifiers(\n            to_table(table, dialect=dialect, copy=copy), dialect=dialect\n        ).parts\n    )\n\n\ndef replace_tables(\n    expression: E, mapping: t.Dict[str, str], dialect: DialectType = None, copy: bool = True\n) -> E:\n    \"\"\"Replace all tables in expression according to the mapping.\n\n    Args:\n        expression: expression node to be transformed and replaced.\n        mapping: mapping of table names.\n        dialect: the dialect of the mapping table\n        copy: whether to copy the expression.\n\n    Examples:\n        >>> from sqlglot import exp, parse_one\n        >>> replace_tables(parse_one(\"select * from a.b\"), {\"a.b\": \"c\"}).sql()\n        'SELECT * FROM c /* a.b */'\n\n    Returns:\n        The mapped expression.\n    \"\"\"\n\n    mapping = {normalize_table_name(k, dialect=dialect): v for k, v in mapping.items()}\n\n    def _replace_tables(node: Expr) -> Expr:\n        if isinstance(node, Table) and node.meta.get(\"replace\") is not False:\n            original = normalize_table_name(node, dialect=dialect)\n            new_name = mapping.get(original)\n\n            if new_name:\n                table = to_table(\n                    new_name,\n                    **{k: v for k, v in node.args.items() if k not in TABLE_PARTS},\n                    dialect=dialect,\n                )\n                table.add_comments([original])\n                return table\n        return node\n\n    return expression.transform(_replace_tables, copy=copy)  # type: ignore\n\n\ndef replace_placeholders(expression: Expr, *args, **kwargs) -> Expr:\n    \"\"\"Replace placeholders in an expression.\n\n    Args:\n        expression: expression node to be transformed and replaced.\n        args: positional names that will substitute unnamed placeholders in the given order.\n        kwargs: keyword arguments that will substitute named placeholders.\n\n    Examples:\n        >>> from sqlglot import exp, parse_one\n        >>> replace_placeholders(\n        ...     parse_one(\"select * from :tbl where ? = ?\"),\n        ...     exp.to_identifier(\"str_col\"), \"b\", tbl=exp.to_identifier(\"foo\")\n        ... ).sql()\n        \"SELECT * FROM foo WHERE str_col = 'b'\"\n\n    Returns:\n        The mapped expression.\n    \"\"\"\n\n    def _replace_placeholders(node: Expr, args, **kwargs) -> Expr:\n        if isinstance(node, Placeholder):\n            if node.this:\n                new_name = kwargs.get(node.this)\n                if new_name is not None:\n                    return convert(new_name)\n            else:\n                try:\n                    return convert(next(args))\n                except StopIteration:\n                    pass\n        return node\n\n    return expression.transform(_replace_placeholders, iter(args), **kwargs)\n\n\ndef expand(\n    expression: Expr,\n    sources: t.Dict[str, Query | t.Callable[[], Query]],\n    dialect: DialectType = None,\n    copy: bool = True,\n) -> Expr:\n    \"\"\"Transforms an expression by expanding all referenced sources into subqueries.\n\n    Examples:\n        >>> from sqlglot import parse_one\n        >>> expand(parse_one(\"select * from x AS z\"), {\"x\": parse_one(\"select * from y\")}).sql()\n        'SELECT * FROM (SELECT * FROM y) AS z /* source: x */'\n\n        >>> expand(parse_one(\"select * from x AS z\"), {\"x\": parse_one(\"select * from y\"), \"y\": parse_one(\"select * from z\")}).sql()\n        'SELECT * FROM (SELECT * FROM (SELECT * FROM z) AS y /* source: y */) AS z /* source: x */'\n\n    Args:\n        expression: The expression to expand.\n        sources: A dict of name to query or a callable that provides a query on demand.\n        dialect: The dialect of the sources dict or the callable.\n        copy: Whether to copy the expression during transformation. Defaults to True.\n\n    Returns:\n        The transformed expression.\n    \"\"\"\n    normalized_sources = {normalize_table_name(k, dialect=dialect): v for k, v in sources.items()}\n\n    def _expand(node: Expr):\n        if isinstance(node, Table):\n            name = normalize_table_name(node, dialect=dialect)\n            source = normalized_sources.get(name)\n\n            if source:\n                # Create a subquery with the same alias (or table name if no alias)\n                parsed_source = source() if callable(source) else source\n                subquery = parsed_source.subquery(node.alias or name)\n                subquery.comments = [f\"source: {name}\"]\n\n                # Continue expanding within the subquery\n                return subquery.transform(_expand, copy=False)\n\n        return node\n\n    return expression.transform(_expand, copy=copy)\n\n\ndef func(name: str, *args, copy: bool = True, dialect: DialectType = None, **kwargs) -> Func:\n    \"\"\"\n    Returns a Func expression.\n\n    Examples:\n        >>> func(\"abs\", 5).sql()\n        'ABS(5)'\n\n        >>> func(\"cast\", this=5, to=DataType.build(\"DOUBLE\")).sql()\n        'CAST(5 AS DOUBLE)'\n\n    Args:\n        name: the name of the function to build.\n        args: the args used to instantiate the function of interest.\n        copy: whether to copy the argument expressions.\n        dialect: the source dialect.\n        kwargs: the kwargs used to instantiate the function of interest.\n\n    Note:\n        The arguments `args` and `kwargs` are mutually exclusive.\n\n    Returns:\n        An instance of the function of interest, or an anonymous function, if `name` doesn't\n        correspond to an existing `sqlglot.expressions.Func` class.\n    \"\"\"\n    if args and kwargs:\n        raise ValueError(\"Can't use both args and kwargs to instantiate a function.\")\n\n    from sqlglot.dialects.dialect import Dialect\n\n    dialect = Dialect.get_or_raise(dialect)\n\n    converted: t.List[Expr] = [maybe_parse(arg, dialect=dialect, copy=copy) for arg in args]\n    kwargs = {key: maybe_parse(value, dialect=dialect, copy=copy) for key, value in kwargs.items()}\n\n    constructor = dialect.parser_class.FUNCTIONS.get(name.upper())\n    if constructor:\n        if converted:\n            try:\n                function = constructor(converted)\n            except TypeError:\n                function = constructor(converted, dialect=dialect)\n        elif constructor.__name__ == \"from_arg_list\":\n            function = constructor.__self__(**kwargs)  # type: ignore\n        else:\n            from sqlglot.expressions import FUNCTION_BY_NAME as _FUNCTION_BY_NAME\n\n            constructor = _FUNCTION_BY_NAME.get(name.upper())\n            if constructor:\n                function = constructor(**kwargs)\n            else:\n                raise ValueError(\n                    f\"Unable to convert '{name}' into a Func. Either manually construct \"\n                    \"the Func expression of interest or parse the function call.\"\n                )\n    else:\n        kwargs = kwargs or {\"expressions\": converted}\n        function = Anonymous(this=name, **kwargs)\n\n    for error_message in function.error_messages(converted):\n        raise ValueError(error_message)\n\n    return function\n\n\ndef case(\n    expression: t.Optional[ExpOrStr] = None,\n    **opts,\n) -> Case:\n    \"\"\"\n    Initialize a CASE statement.\n\n    Example:\n        case().when(\"a = 1\", \"foo\").else_(\"bar\")\n\n    Args:\n        expression: Optionally, the input expression (not all dialects support this)\n        **opts: Extra keyword arguments for parsing `expression`\n    \"\"\"\n    if expression is not None:\n        this = maybe_parse(expression, **opts)\n    else:\n        this = None\n    return Case(this=this, ifs=[])\n\n\ndef array(\n    *expressions: ExpOrStr, copy: bool = True, dialect: DialectType = None, **kwargs\n) -> Array:\n    \"\"\"\n    Returns an array.\n\n    Examples:\n        >>> array(1, 'x').sql()\n        'ARRAY(1, x)'\n\n    Args:\n        expressions: the expressions to add to the array.\n        copy: whether to copy the argument expressions.\n        dialect: the source dialect.\n        kwargs: the kwargs used to instantiate the function of interest.\n\n    Returns:\n        An array expression.\n    \"\"\"\n    return Array(\n        expressions=[\n            maybe_parse(expression, copy=copy, dialect=dialect, **kwargs)\n            for expression in expressions\n        ]\n    )\n\n\ndef tuple_(\n    *expressions: ExpOrStr, copy: bool = True, dialect: DialectType = None, **kwargs\n) -> Tuple:\n    \"\"\"\n    Returns an tuple.\n\n    Examples:\n        >>> tuple_(1, 'x').sql()\n        '(1, x)'\n\n    Args:\n        expressions: the expressions to add to the tuple.\n        copy: whether to copy the argument expressions.\n        dialect: the source dialect.\n        kwargs: the kwargs used to instantiate the function of interest.\n\n    Returns:\n        A tuple expression.\n    \"\"\"\n    return Tuple(\n        expressions=[\n            maybe_parse(expression, copy=copy, dialect=dialect, **kwargs)\n            for expression in expressions\n        ]\n    )\n\n\ndef true() -> Boolean:\n    \"\"\"\n    Returns a true Boolean expression.\n    \"\"\"\n    return Boolean(this=True)\n\n\ndef false() -> Boolean:\n    \"\"\"\n    Returns a false Boolean expression.\n    \"\"\"\n    return Boolean(this=False)\n\n\ndef null() -> Null:\n    \"\"\"\n    Returns a Null expression.\n    \"\"\"\n    return Null()\n\n\ndef apply_index_offset(\n    this: Expr,\n    expressions: t.List[E],\n    offset: int,\n    dialect: DialectType = None,\n) -> t.List[E]:\n    if not offset or len(expressions) != 1:\n        return expressions\n\n    expression = expressions[0]\n\n    from sqlglot.optimizer.annotate_types import annotate_types\n    from sqlglot.optimizer.simplify import simplify\n\n    if not this.type:\n        annotate_types(this, dialect=dialect)\n\n    if t.cast(DataType, this.type).this not in (\n        DType.UNKNOWN,\n        DType.ARRAY,\n    ):\n        return expressions\n\n    if not expression.type:\n        annotate_types(expression, dialect=dialect)\n\n    if t.cast(DataType, expression.type).this in DataType.INTEGER_TYPES:\n        logger.info(\"Applying array index offset (%s)\", offset)\n        expression = simplify(expression + offset)\n        return [expression]\n\n    return expressions\n\n\nNONNULL_CONSTANTS = (\n    Literal,\n    Boolean,\n)\n\nCONSTANTS = (\n    Literal,\n    Boolean,\n    Null,\n)\n"
  },
  {
    "path": "sqlglot/expressions/constraints.py",
    "content": "\"\"\"sqlglot expressions constraints.\"\"\"\n\nfrom __future__ import annotations\n\nfrom sqlglot.expressions.core import Expression, ColumnConstraintKind\n\n\nclass IndexConstraintOption(Expression):\n    arg_types = {\n        \"key_block_size\": False,\n        \"using\": False,\n        \"parser\": False,\n        \"comment\": False,\n        \"visible\": False,\n        \"engine_attr\": False,\n        \"secondary_engine_attr\": False,\n    }\n\n\nclass Reference(Expression):\n    arg_types = {\"this\": True, \"expressions\": False, \"options\": False}\n\n\nclass ColumnConstraint(Expression):\n    arg_types = {\"this\": False, \"kind\": True}\n\n    @property\n    def kind(self) -> ColumnConstraintKind | Reference:\n        return self.args[\"kind\"]\n\n\nclass AutoIncrementColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass ZeroFillColumnConstraint(ColumnConstraint):\n    arg_types = {}\n\n\nclass PeriodForSystemTimeConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass CaseSpecificColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"not_\": True}\n\n\nclass CharacterSetColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass CheckColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"this\": True, \"enforced\": False}\n\n\nclass AssumeColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass ClusteredColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass CollateColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass CommentColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass CompressColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"this\": False}\n\n\nclass DateFormatColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass DefaultColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass EncodeColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass ExcludeColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass EphemeralColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"this\": False}\n\n\nclass WithOperator(Expression):\n    arg_types = {\"this\": True, \"op\": True}\n\n\nclass GeneratedAsIdentityColumnConstraint(Expression, ColumnConstraintKind):\n    # this: True -> ALWAYS, this: False -> BY DEFAULT\n    arg_types = {\n        \"this\": False,\n        \"expression\": False,\n        \"on_null\": False,\n        \"start\": False,\n        \"increment\": False,\n        \"minvalue\": False,\n        \"maxvalue\": False,\n        \"cycle\": False,\n        \"order\": False,\n    }\n\n\nclass GeneratedAsRowColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"start\": False, \"hidden\": False}\n\n\nclass IndexColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\n        \"this\": False,\n        \"expressions\": False,\n        \"kind\": False,\n        \"index_type\": False,\n        \"options\": False,\n        \"expression\": False,  # Clickhouse\n        \"granularity\": False,\n    }\n\n\nclass InlineLengthColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass NonClusteredColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass NotForReplicationColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {}\n\n\nclass MaskingPolicyColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"this\": True, \"expressions\": False}\n\n\nclass NotNullColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"allow_null\": False}\n\n\nclass OnUpdateColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass PrimaryKeyColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"desc\": False, \"options\": False}\n\n\nclass TitleColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass UniqueColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\n        \"this\": False,\n        \"index_type\": False,\n        \"on_conflict\": False,\n        \"nulls\": False,\n        \"options\": False,\n    }\n\n\nclass UppercaseColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {}\n\n\nclass WatermarkColumnConstraint(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass PathColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass ProjectionPolicyColumnConstraint(Expression, ColumnConstraintKind):\n    pass\n\n\nclass ComputedColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"this\": True, \"persisted\": False, \"not_null\": False, \"data_type\": False}\n\n\nclass InOutColumnConstraint(Expression, ColumnConstraintKind):\n    arg_types = {\"input_\": False, \"output\": False, \"variadic\": False}\n\n\nclass Constraint(Expression):\n    arg_types = {\"this\": True, \"expressions\": True}\n\n\nclass ForeignKey(Expression):\n    arg_types = {\n        \"expressions\": False,\n        \"reference\": False,\n        \"delete\": False,\n        \"update\": False,\n        \"options\": False,\n    }\n\n\nclass ColumnPrefix(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass PrimaryKey(Expression):\n    arg_types = {\"this\": False, \"expressions\": True, \"options\": False, \"include\": False}\n\n\nclass IndexParameters(Expression):\n    arg_types = {\n        \"using\": False,\n        \"include\": False,\n        \"columns\": False,\n        \"with_storage\": False,\n        \"partition_by\": False,\n        \"tablespace\": False,\n        \"where\": False,\n        \"on\": False,\n    }\n\n\nclass AddConstraint(Expression):\n    arg_types = {\"expressions\": True}\n"
  },
  {
    "path": "sqlglot/expressions/core.py",
    "content": "\"\"\"sqlglot expressions core - base classes, traits, operators, and helpers.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport logging\nimport math\nimport numbers\nimport re\nimport sys\nimport textwrap\nimport typing as t\nfrom collections import deque\nfrom copy import deepcopy\nfrom decimal import Decimal\nfrom functools import reduce\nfrom collections.abc import Iterator, Sequence, Collection\nfrom sqlglot._typing import E\nfrom sqlglot.errors import ParseError\nfrom sqlglot.helper import (\n    camel_to_snake_case,\n    ensure_list,\n    seq_get,\n    to_bool,\n    trait,\n)\n\nfrom sqlglot.tokenizer_core import Token\nfrom builtins import type as Type\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n    from sqlglot.expressions.datatypes import DATA_TYPE, DataType, DType, Interval\n    from sqlglot.expressions.query import Select\n\nlogger = logging.getLogger(\"sqlglot\")\n\nSQLGLOT_META: str = \"sqlglot.meta\"\nSQLGLOT_ANONYMOUS = \"sqlglot.anonymous\"\nTABLE_PARTS = (\"this\", \"db\", \"catalog\")\nCOLUMN_PARTS = (\"this\", \"table\", \"db\", \"catalog\")\nPOSITION_META_KEYS: t.Tuple[str, ...] = (\"line\", \"col\", \"start\", \"end\")\nUNITTEST: bool = \"unittest\" in sys.modules or \"pytest\" in sys.modules\n\n\n@trait\nclass Expr:\n    \"\"\"\n    The base class for all expressions in a syntax tree. Each Expr encapsulates any necessary\n    context, such as its child expressions, their names (arg keys), and whether a given child expression\n    is optional or not.\n\n    Attributes:\n        key: a unique key for each class in the Expr hierarchy. This is useful for hashing\n            and representing expressions as strings.\n        arg_types: determines the arguments (child nodes) supported by an expression. It maps\n            arg keys to booleans that indicate whether the corresponding args are optional.\n        parent: a reference to the parent expression (or None, in case of root expressions).\n        arg_key: the arg key an expression is associated with, i.e. the name its parent expression\n            uses to refer to it.\n        index: the index of an expression if it is inside of a list argument in its parent.\n        comments: a list of comments that are associated with a given expression. This is used in\n            order to preserve comments when transpiling SQL code.\n        type: the `sqlglot.expressions.DataType` type of an expression. This is inferred by the\n            optimizer, in order to enable some transformations that require type information.\n        meta: a dictionary that can be used to store useful metadata for a given expression.\n\n    Example:\n        >>> class Foo(Expr):\n        ...     arg_types = {\"this\": True, \"expression\": False}\n\n        The above definition informs us that Foo is an Expr that requires an argument called\n        \"this\" and may also optionally receive an argument called \"expression\".\n\n    Args:\n        args: a mapping used for retrieving the arguments of an expression, given their arg keys.\n    \"\"\"\n\n    key: t.ClassVar[str] = \"expression\"\n    arg_types: t.ClassVar[t.Dict[str, bool]] = {\"this\": True}\n    required_args: t.ClassVar[t.Set[str]] = {\"this\"}\n    is_var_len_args: t.ClassVar[bool] = False\n    _hash_raw_args: t.ClassVar[bool] = False\n    is_subquery: t.ClassVar[bool] = False\n    is_cast: t.ClassVar[bool] = False\n\n    args: t.Dict[str, t.Any]\n    parent: t.Optional[Expr]\n    arg_key: t.Optional[str]\n    index: t.Optional[int]\n    comments: t.Optional[t.List[str]]\n    _type: t.Optional[DataType]\n    _meta: t.Optional[t.Dict[str, t.Any]]\n    _hash: t.Optional[int]\n\n    @classmethod\n    def __init_subclass__(cls, **kwargs: t.Any) -> None:\n        super().__init_subclass__(**kwargs)\n        # When an Expr class is created, its key is automatically set\n        # to be the lowercase version of the class' name.\n        cls.key = cls.__name__.lower()\n        cls.required_args = {k for k, v in cls.arg_types.items() if v}\n        # This is so that docstrings are not inherited in pdoc\n        setattr(cls, \"__doc__\", getattr(cls, \"__doc__\", None) or \"\")\n\n    is_primitive: t.ClassVar[bool] = False\n\n    def __init__(self, **args: object) -> None:\n        self.args: t.Dict[str, t.Any] = args\n        self.parent: t.Optional[Expr] = None\n        self.arg_key: t.Optional[str] = None\n        self.index: t.Optional[int] = None\n        self.comments: t.Optional[t.List[str]] = None\n        self._type: t.Optional[DataType] = None\n        self._meta: t.Optional[t.Dict[str, t.Any]] = None\n        self._hash: t.Optional[int] = None\n\n        if not self.is_primitive:\n            for arg_key, value in self.args.items():\n                self._set_parent(arg_key, value)\n\n    @property\n    def this(self) -> t.Any:\n        raise NotImplementedError\n\n    @property\n    def expression(self) -> t.Any:\n        raise NotImplementedError\n\n    @property\n    def expressions(self) -> t.List[t.Any]:\n        raise NotImplementedError\n\n    def text(self, key: str) -> str:\n        raise NotImplementedError\n\n    @property\n    def is_string(self) -> bool:\n        raise NotImplementedError\n\n    @property\n    def is_number(self) -> bool:\n        raise NotImplementedError\n\n    def to_py(self) -> t.Any:\n        raise NotImplementedError\n\n    @property\n    def is_int(self) -> bool:\n        raise NotImplementedError\n\n    @property\n    def is_star(self) -> bool:\n        raise NotImplementedError\n\n    @property\n    def alias(self) -> str:\n        raise NotImplementedError\n\n    @property\n    def alias_column_names(self) -> t.List[str]:\n        raise NotImplementedError\n\n    @property\n    def name(self) -> str:\n        raise NotImplementedError\n\n    @property\n    def alias_or_name(self) -> str:\n        raise NotImplementedError\n\n    @property\n    def output_name(self) -> str:\n        raise NotImplementedError\n\n    @property\n    def type(self) -> t.Optional[DataType]:\n        raise NotImplementedError\n\n    @type.setter\n    def type(self, dtype: t.Optional[DataType | DType | str]) -> None:\n        raise NotImplementedError\n\n    def is_type(self, *dtypes: DATA_TYPE) -> bool:\n        raise NotImplementedError\n\n    def is_leaf(self) -> bool:\n        raise NotImplementedError\n\n    @property\n    def meta(self) -> t.Dict[str, t.Any]:\n        raise NotImplementedError\n\n    def __deepcopy__(self, memo: t.Any) -> Expr:\n        raise NotImplementedError\n\n    def copy(self: E) -> E:\n        raise NotImplementedError\n\n    def add_comments(self, comments: t.Optional[t.List[str]] = None, prepend: bool = False) -> None:\n        raise NotImplementedError\n\n    def pop_comments(self) -> t.List[str]:\n        raise NotImplementedError\n\n    def append(self, arg_key: str, value: t.Any) -> None:\n        raise NotImplementedError\n\n    def set(\n        self,\n        arg_key: str,\n        value: object,\n        index: t.Optional[int] = None,\n        overwrite: bool = True,\n    ) -> None:\n        raise NotImplementedError\n\n    def _set_parent(self, arg_key: str, value: object, index: t.Optional[int] = None) -> None:\n        raise NotImplementedError\n\n    @property\n    def depth(self) -> int:\n        raise NotImplementedError\n\n    def iter_expressions(self: E, reverse: bool = False) -> Iterator[E]:\n        raise NotImplementedError\n\n    def find(self, *expression_types: Type[E], bfs: bool = True) -> t.Optional[E]:\n        raise NotImplementedError\n\n    def find_all(self, *expression_types: Type[E], bfs: bool = True) -> Iterator[E]:\n        raise NotImplementedError\n\n    def find_ancestor(self, *expression_types: Type[E]) -> t.Optional[E]:\n        raise NotImplementedError\n\n    @property\n    def parent_select(self) -> t.Optional[Select]:\n        raise NotImplementedError\n\n    @property\n    def same_parent(self) -> bool:\n        raise NotImplementedError\n\n    def root(self) -> Expr:\n        raise NotImplementedError\n\n    def walk(\n        self, bfs: bool = True, prune: t.Optional[t.Callable[[Expr], bool]] = None\n    ) -> Iterator[Expr]:\n        raise NotImplementedError\n\n    def dfs(self, prune: t.Optional[t.Callable[[Expr], bool]] = None) -> Iterator[Expr]:\n        raise NotImplementedError\n\n    def bfs(self, prune: t.Optional[t.Callable[[Expr], bool]] = None) -> Iterator[Expr]:\n        raise NotImplementedError\n\n    def unnest(self) -> Expr:\n        raise NotImplementedError\n\n    def unalias(self) -> Expr:\n        raise NotImplementedError\n\n    def unnest_operands(self) -> t.Tuple[Expr, ...]:\n        raise NotImplementedError\n\n    def flatten(self, unnest: bool = True) -> Iterator[Expr]:\n        raise NotImplementedError\n\n    def to_s(self) -> str:\n        raise NotImplementedError\n\n    def sql(self, dialect: DialectType = None, **opts: t.Any) -> str:\n        raise NotImplementedError\n\n    def transform(\n        self, fun: t.Callable, *args: object, copy: bool = True, **kwargs: object\n    ) -> t.Any:\n        raise NotImplementedError\n\n    def replace(self, expression: t.Any) -> t.Any:\n        raise NotImplementedError\n\n    def pop(self: E) -> E:\n        raise NotImplementedError\n\n    def assert_is(self, type_: Type[E]) -> E:\n        raise NotImplementedError\n\n    def error_messages(self, args: t.Optional[Sequence[object]] = None) -> list[str]:\n        raise NotImplementedError\n\n    def dump(self) -> t.Any:\n        \"\"\"\n        Dump this Expr to a JSON-serializable dict.\n        \"\"\"\n        from sqlglot.serde import dump\n\n        return dump(self)\n\n    @classmethod\n    def load(cls, obj: t.Any) -> Expr:\n        \"\"\"\n        Load a dict (as returned by `Expr.dump`) into an Expr instance.\n        \"\"\"\n        from sqlglot.serde import load\n\n        result = load(obj)\n        assert isinstance(result, Expr)\n        return result\n\n    def and_(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        dialect: DialectType = None,\n        copy: bool = True,\n        wrap: bool = True,\n        **opts: t.Any,\n    ) -> Condition:\n        raise NotImplementedError\n\n    def or_(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        dialect: DialectType = None,\n        copy: bool = True,\n        wrap: bool = True,\n        **opts: t.Any,\n    ) -> Condition:\n        raise NotImplementedError\n\n    def not_(self, copy: bool = True) -> Not:\n        raise NotImplementedError\n\n    def update_positions(\n        self: E,\n        other: t.Optional[Token | Expr] = None,\n        line: t.Optional[int] = None,\n        col: t.Optional[int] = None,\n        start: t.Optional[int] = None,\n        end: t.Optional[int] = None,\n    ) -> E:\n        raise NotImplementedError\n\n    def as_(\n        self,\n        alias: str | Identifier,\n        quoted: t.Optional[bool] = None,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts: t.Any,\n    ) -> Expr:\n        raise NotImplementedError\n\n    def _binop(self, klass: Type[E], other: t.Any, reverse: bool = False) -> E:\n        raise NotImplementedError\n\n    def __getitem__(self, other: ExpOrStr | t.Tuple[ExpOrStr, ...]) -> Bracket:\n        raise NotImplementedError\n\n    def __iter__(self) -> Iterator:\n        raise NotImplementedError\n\n    def isin(\n        self,\n        *expressions: t.Any,\n        query: t.Optional[ExpOrStr] = None,\n        unnest: t.Optional[ExpOrStr] | Collection[ExpOrStr] = None,\n        copy: bool = True,\n        **opts,\n    ) -> In:\n        raise NotImplementedError\n\n    def between(\n        self,\n        low: t.Any,\n        high: t.Any,\n        copy: bool = True,\n        symmetric: t.Optional[bool] = None,\n        **opts,\n    ) -> Between:\n        raise NotImplementedError\n\n    def is_(self, other: ExpOrStr) -> Is:\n        raise NotImplementedError\n\n    def like(self, other: ExpOrStr) -> Like:\n        raise NotImplementedError\n\n    def ilike(self, other: ExpOrStr) -> ILike:\n        raise NotImplementedError\n\n    def eq(self, other: t.Any) -> EQ:\n        raise NotImplementedError\n\n    def neq(self, other: t.Any) -> NEQ:\n        raise NotImplementedError\n\n    def rlike(self, other: ExpOrStr) -> RegexpLike:\n        raise NotImplementedError\n\n    def div(self, other: ExpOrStr, typed: bool = False, safe: bool = False) -> Div:\n        raise NotImplementedError\n\n    def asc(self, nulls_first: bool = True) -> Ordered:\n        raise NotImplementedError\n\n    def desc(self, nulls_first: bool = False) -> Ordered:\n        raise NotImplementedError\n\n    def __lt__(self, other: t.Any) -> LT:\n        raise NotImplementedError\n\n    def __le__(self, other: t.Any) -> LTE:\n        raise NotImplementedError\n\n    def __gt__(self, other: t.Any) -> GT:\n        raise NotImplementedError\n\n    def __ge__(self, other: t.Any) -> GTE:\n        raise NotImplementedError\n\n    def __add__(self, other: t.Any) -> Add:\n        raise NotImplementedError\n\n    def __radd__(self, other: t.Any) -> Add:\n        raise NotImplementedError\n\n    def __sub__(self, other: t.Any) -> Sub:\n        raise NotImplementedError\n\n    def __rsub__(self, other: t.Any) -> Sub:\n        raise NotImplementedError\n\n    def __mul__(self, other: t.Any) -> Mul:\n        raise NotImplementedError\n\n    def __rmul__(self, other: t.Any) -> Mul:\n        raise NotImplementedError\n\n    def __truediv__(self, other: t.Any) -> Div:\n        raise NotImplementedError\n\n    def __rtruediv__(self, other: t.Any) -> Div:\n        raise NotImplementedError\n\n    def __floordiv__(self, other: t.Any) -> IntDiv:\n        raise NotImplementedError\n\n    def __rfloordiv__(self, other: t.Any) -> IntDiv:\n        raise NotImplementedError\n\n    def __mod__(self, other: t.Any) -> Mod:\n        raise NotImplementedError\n\n    def __rmod__(self, other: t.Any) -> Mod:\n        raise NotImplementedError\n\n    def __pow__(self, other: t.Any) -> Pow:\n        raise NotImplementedError\n\n    def __rpow__(self, other: t.Any) -> Pow:\n        raise NotImplementedError\n\n    def __and__(self, other: t.Any) -> And:\n        raise NotImplementedError\n\n    def __rand__(self, other: t.Any) -> And:\n        raise NotImplementedError\n\n    def __or__(self, other: t.Any) -> Or:\n        raise NotImplementedError\n\n    def __ror__(self, other: t.Any) -> Or:\n        raise NotImplementedError\n\n    def __neg__(self) -> Neg:\n        raise NotImplementedError\n\n    def __invert__(self) -> Not:\n        raise NotImplementedError\n\n\nclass Expression(Expr):\n    __slots__ = (\n        \"args\",\n        \"parent\",\n        \"arg_key\",\n        \"index\",\n        \"comments\",\n        \"_type\",\n        \"_meta\",\n        \"_hash\",\n    )\n\n    def __eq__(self, other: object) -> bool:\n        return self is other or (type(self) is type(other) and hash(self) == hash(other))\n\n    def __ne__(self, other: object) -> bool:\n        return not self.__eq__(other)\n\n    def __hash__(self) -> int:\n        if self._hash is None:\n            nodes: t.List[Expr] = []\n            queue: t.Deque[Expr] = deque()\n            queue.append(self)\n\n            while queue:\n                node = queue.popleft()\n                nodes.append(node)\n\n                for child in node.iter_expressions():\n                    if child._hash is None:\n                        queue.append(child)\n\n            for node in reversed(nodes):\n                hash_ = hash(node.key)\n\n                if node._hash_raw_args:\n                    for k, v in sorted(node.args.items()):\n                        if v:\n                            hash_ = hash((hash_, k, v))\n                else:\n                    for k, v in sorted(node.args.items()):\n                        vt = type(v)\n\n                        if vt is list:\n                            for x in v:\n                                if x is not None and x is not False:\n                                    hash_ = hash((hash_, k, x.lower() if type(x) is str else x))\n                                else:\n                                    hash_ = hash((hash_, k))\n                        elif v is not None and v is not False:\n                            hash_ = hash((hash_, k, v.lower() if vt is str else v))\n\n                node._hash = hash_\n        assert self._hash\n        return self._hash\n\n    def __reduce__(self) -> t.Tuple[t.Callable, t.Tuple[t.List[t.Dict[str, t.Any]]]]:\n        from sqlglot.serde import dump, load\n\n        return (load, (dump(self),))\n\n    @property\n    def this(self) -> t.Any:\n        \"\"\"\n        Retrieves the argument with key \"this\".\n        \"\"\"\n        return self.args.get(\"this\")\n\n    @property\n    def expression(self) -> t.Any:\n        \"\"\"\n        Retrieves the argument with key \"expression\".\n        \"\"\"\n        return self.args.get(\"expression\")\n\n    @property\n    def expressions(self) -> t.List[t.Any]:\n        \"\"\"\n        Retrieves the argument with key \"expressions\".\n        \"\"\"\n        return self.args.get(\"expressions\") or []\n\n    def text(self, key: str) -> str:\n        \"\"\"\n        Returns a textual representation of the argument corresponding to \"key\". This can only be used\n        for args that are strings or leaf Expr instances, such as identifiers and literals.\n        \"\"\"\n        field = self.args.get(key)\n        if isinstance(field, str):\n            return field\n        if isinstance(field, (Identifier, Literal, Var)):\n            return field.this\n        if isinstance(field, (Star, Null)):\n            return field.name\n        return \"\"\n\n    @property\n    def is_string(self) -> bool:\n        \"\"\"\n        Checks whether a Literal expression is a string.\n        \"\"\"\n        return isinstance(self, Literal) and self.args[\"is_string\"]\n\n    @property\n    def is_number(self) -> bool:\n        \"\"\"\n        Checks whether a Literal expression is a number.\n        \"\"\"\n        return (isinstance(self, Literal) and not self.args[\"is_string\"]) or (\n            isinstance(self, Neg) and self.this.is_number\n        )\n\n    def to_py(self) -> t.Any:\n        \"\"\"\n        Returns a Python object equivalent of the SQL node.\n        \"\"\"\n        raise ValueError(f\"{self} cannot be converted to a Python object.\")\n\n    @property\n    def is_int(self) -> bool:\n        \"\"\"\n        Checks whether an expression is an integer.\n        \"\"\"\n        return self.is_number and isinstance(self.to_py(), int)\n\n    @property\n    def is_star(self) -> bool:\n        \"\"\"Checks whether an expression is a star.\"\"\"\n        return isinstance(self, Star) or (isinstance(self, Column) and isinstance(self.this, Star))\n\n    @property\n    def alias(self) -> str:\n        \"\"\"\n        Returns the alias of the expression, or an empty string if it's not aliased.\n        \"\"\"\n        alias = self.args.get(\"alias\")\n        if isinstance(alias, Expression):\n            return alias.name\n        return self.text(\"alias\")\n\n    @property\n    def alias_column_names(self) -> t.List[str]:\n        table_alias = self.args.get(\"alias\")\n        if not table_alias:\n            return []\n        return [c.name for c in table_alias.args.get(\"columns\") or []]\n\n    @property\n    def name(self) -> str:\n        return self.text(\"this\")\n\n    @property\n    def alias_or_name(self) -> str:\n        return self.alias or self.name\n\n    @property\n    def output_name(self) -> str:\n        \"\"\"\n        Name of the output column if this expression is a selection.\n\n        If the Expr has no output name, an empty string is returned.\n\n        Example:\n            >>> from sqlglot import parse_one\n            >>> parse_one(\"SELECT a\").expressions[0].output_name\n            'a'\n            >>> parse_one(\"SELECT b AS c\").expressions[0].output_name\n            'c'\n            >>> parse_one(\"SELECT 1 + 2\").expressions[0].output_name\n            ''\n        \"\"\"\n        return \"\"\n\n    @property\n    def type(self) -> t.Optional[DataType]:\n        if self.is_cast:\n            return self._type or self.to  # type: ignore[attr-defined]\n        return self._type\n\n    @type.setter\n    def type(self, dtype: t.Optional[DataType | DType | str]) -> None:\n        if dtype and type(dtype).__name__ != \"DataType\":\n            from sqlglot.expressions.datatypes import DataType as _DataType\n\n            dtype = _DataType.build(dtype)\n        self._type = dtype  # type: ignore[assignment]\n\n    def is_type(self, *dtypes: DATA_TYPE) -> bool:\n        t = self._type\n        return t is not None and t.is_type(*dtypes)\n\n    def is_leaf(self) -> bool:\n        return not any((isinstance(v, Expr) or type(v) is list) and v for v in self.args.values())\n\n    @property\n    def meta(self) -> t.Dict[str, t.Any]:\n        if self._meta is None:\n            self._meta = {}\n        return self._meta\n\n    def __deepcopy__(self, memo: t.Any) -> Expr:\n        root = self.__class__()\n        stack: t.List[t.Tuple[Expr, Expr]] = [(self, root)]\n\n        while stack:\n            node, copy = stack.pop()\n\n            if node.comments is not None:\n                copy.comments = deepcopy(node.comments)\n            if node._type is not None:\n                copy._type = deepcopy(node._type)\n            if node._meta is not None:\n                copy._meta = deepcopy(node._meta)\n            if node._hash is not None:\n                copy._hash = node._hash\n\n            for k, vs in node.args.items():\n                if isinstance(vs, Expr):\n                    stack.append((vs, vs.__class__()))\n                    copy.set(k, stack[-1][-1])\n                elif type(vs) is list:\n                    copy.args[k] = []\n\n                    for v in vs:\n                        if isinstance(v, Expr):\n                            stack.append((v, v.__class__()))\n                            copy.append(k, stack[-1][-1])\n                        else:\n                            copy.append(k, v)\n                else:\n                    copy.args[k] = vs\n\n        return root\n\n    def copy(self: E) -> E:\n        \"\"\"\n        Returns a deep copy of the expression.\n        \"\"\"\n        return deepcopy(self)\n\n    def add_comments(self, comments: t.Optional[t.List[str]] = None, prepend: bool = False) -> None:\n        if self.comments is None:\n            self.comments = []\n\n        if comments:\n            for comment in comments:\n                _, *meta = comment.split(SQLGLOT_META)\n                if meta:\n                    for kv in \"\".join(meta).split(\",\"):\n                        k, *v = kv.split(\"=\")\n                        self.meta[k.strip()] = to_bool(v[0].strip() if v else True)\n\n                if not prepend:\n                    self.comments.append(comment)\n\n            if prepend:\n                self.comments = comments + self.comments\n\n    def pop_comments(self) -> t.List[str]:\n        comments = self.comments or []\n        self.comments = None\n        return comments\n\n    def append(self, arg_key: str, value: t.Any) -> None:\n        \"\"\"\n        Appends value to arg_key if it's a list or sets it as a new list.\n\n        Args:\n            arg_key (str): name of the list expression arg\n            value (Any): value to append to the list\n        \"\"\"\n        if type(self.args.get(arg_key)) is not list:\n            self.args[arg_key] = []\n        self._set_parent(arg_key, value)\n        values = self.args[arg_key]\n        if isinstance(value, Expr):\n            value.index = len(values)\n        values.append(value)\n\n    def set(\n        self,\n        arg_key: str,\n        value: object,\n        index: t.Optional[int] = None,\n        overwrite: bool = True,\n    ) -> None:\n        \"\"\"\n        Sets arg_key to value.\n\n        Args:\n            arg_key: name of the expression arg.\n            value: value to set the arg to.\n            index: if the arg is a list, this specifies what position to add the value in it.\n            overwrite: assuming an index is given, this determines whether to overwrite the\n                list entry instead of only inserting a new value (i.e., like list.insert).\n        \"\"\"\n        node: t.Optional[Expr] = self\n\n        while node and node._hash is not None:\n            node._hash = None\n            node = node.parent\n\n        if index is not None:\n            expressions = self.args.get(arg_key) or []\n\n            if seq_get(expressions, index) is None:\n                return\n\n            if value is None:\n                expressions.pop(index)\n                for v in expressions[index:]:\n                    v.index = v.index - 1\n                return\n\n            if isinstance(value, list):\n                expressions.pop(index)\n                expressions[index:index] = value\n            elif overwrite:\n                expressions[index] = value\n            else:\n                expressions.insert(index, value)\n\n            value = expressions\n        elif value is None:\n            self.args.pop(arg_key, None)\n            return\n\n        self.args[arg_key] = value\n        self._set_parent(arg_key, value, index)\n\n    def _set_parent(self, arg_key: str, value: object, index: t.Optional[int] = None) -> None:\n        if isinstance(value, Expr):\n            value.parent = self\n            value.arg_key = arg_key\n            value.index = index\n        elif isinstance(value, list):\n            for i, v in enumerate(value):\n                if isinstance(v, Expr):\n                    v.parent = self\n                    v.arg_key = arg_key\n                    v.index = i\n\n    @property\n    def depth(self) -> int:\n        \"\"\"\n        Returns the depth of this tree.\n        \"\"\"\n        if self.parent:\n            return self.parent.depth + 1\n        return 0\n\n    def iter_expressions(self: E, reverse: bool = False) -> Iterator[E]:\n        \"\"\"Yields the key and expression for all arguments, exploding list args.\"\"\"\n        for vs in reversed(self.args.values()) if reverse else self.args.values():\n            if isinstance(vs, list):\n                for v in reversed(vs) if reverse else vs:\n                    if isinstance(v, Expr):\n                        yield t.cast(E, v)\n            elif isinstance(vs, Expr):\n                yield t.cast(E, vs)\n\n    def find(self, *expression_types: Type[E], bfs: bool = True) -> t.Optional[E]:\n        \"\"\"\n        Returns the first node in this tree which matches at least one of\n        the specified types.\n\n        Args:\n            expression_types: the expression type(s) to match.\n            bfs: whether to search the AST using the BFS algorithm (DFS is used if false).\n\n        Returns:\n            The node which matches the criteria or None if no such node was found.\n        \"\"\"\n        return next(self.find_all(*expression_types, bfs=bfs), None)\n\n    def find_all(self, *expression_types: Type[E], bfs: bool = True) -> Iterator[E]:\n        \"\"\"\n        Returns a generator object which visits all nodes in this tree and only\n        yields those that match at least one of the specified expression types.\n\n        Args:\n            expression_types: the expression type(s) to match.\n            bfs: whether to search the AST using the BFS algorithm (DFS is used if false).\n\n        Returns:\n            The generator object.\n        \"\"\"\n        for expression in self.walk(bfs=bfs):\n            if isinstance(expression, expression_types):\n                yield expression\n\n    def find_ancestor(self, *expression_types: Type[E]) -> t.Optional[E]:\n        \"\"\"\n        Returns a nearest parent matching expression_types.\n\n        Args:\n            expression_types: the expression type(s) to match.\n\n        Returns:\n            The parent node.\n        \"\"\"\n        ancestor = self.parent\n        while ancestor and not isinstance(ancestor, expression_types):\n            ancestor = ancestor.parent\n        return ancestor  # type: ignore[return-value]\n\n    @property\n    def parent_select(self) -> t.Optional[Select]:\n        \"\"\"\n        Returns the parent select statement.\n        \"\"\"\n        from sqlglot.expressions.query import Select as _Select\n\n        return self.find_ancestor(_Select)\n\n    @property\n    def same_parent(self) -> bool:\n        \"\"\"Returns if the parent is the same class as itself.\"\"\"\n        return type(self.parent) is self.__class__\n\n    def root(self) -> Expr:\n        \"\"\"\n        Returns the root expression of this tree.\n        \"\"\"\n        expression: Expr = self\n        while expression.parent:\n            expression = expression.parent\n        return expression\n\n    def walk(\n        self, bfs: bool = True, prune: t.Optional[t.Callable[[Expr], bool]] = None\n    ) -> Iterator[Expr]:\n        \"\"\"\n        Returns a generator object which visits all nodes in this tree.\n\n        Args:\n            bfs: if set to True the BFS traversal order will be applied,\n                otherwise the DFS traversal will be used instead.\n            prune: callable that returns True if the generator should stop traversing\n                this branch of the tree.\n\n        Returns:\n            the generator object.\n        \"\"\"\n        if bfs:\n            yield from self.bfs(prune=prune)\n        else:\n            yield from self.dfs(prune=prune)\n\n    def dfs(self, prune: t.Optional[t.Callable[[Expr], bool]] = None) -> Iterator[Expr]:\n        \"\"\"\n        Returns a generator object which visits all nodes in this tree in\n        the DFS (Depth-first) order.\n\n        Returns:\n            The generator object.\n        \"\"\"\n        stack = [self]\n\n        while stack:\n            node = stack.pop()\n            yield node\n            if prune and prune(node):\n                continue\n            for v in node.iter_expressions(reverse=True):\n                stack.append(v)\n\n    def bfs(self, prune: t.Optional[t.Callable[[Expr], bool]] = None) -> Iterator[Expr]:\n        \"\"\"\n        Returns a generator object which visits all nodes in this tree in\n        the BFS (Breadth-first) order.\n\n        Returns:\n            The generator object.\n        \"\"\"\n        queue: t.Deque[Expr] = deque()\n        queue.append(self)\n\n        while queue:\n            node = queue.popleft()\n            yield node\n            if prune and prune(node):\n                continue\n            for v in node.iter_expressions():\n                queue.append(v)\n\n    def unnest(self) -> Expr:\n        \"\"\"\n        Returns the first non parenthesis child or self.\n        \"\"\"\n        expression = self\n        while type(expression) is Paren:\n            expression = expression.this\n        return expression\n\n    def unalias(self) -> Expr:\n        \"\"\"\n        Returns the inner expression if this is an Alias.\n        \"\"\"\n        if isinstance(self, Alias):\n            return self.this\n        return self\n\n    def unnest_operands(self) -> t.Tuple[Expr, ...]:\n        \"\"\"\n        Returns unnested operands as a tuple.\n        \"\"\"\n        return tuple(arg.unnest() for arg in self.iter_expressions())\n\n    def flatten(self, unnest: bool = True) -> Iterator[Expr]:\n        \"\"\"\n        Returns a generator which yields child nodes whose parents are the same class.\n\n        A AND B AND C -> [A, B, C]\n        \"\"\"\n        for node in self.dfs(prune=lambda n: bool(n.parent and type(n) is not self.__class__)):\n            if type(node) is not self.__class__:\n                yield node.unnest() if unnest and not node.is_subquery else node\n\n    def __str__(self) -> str:\n        return self.sql()\n\n    def __repr__(self) -> str:\n        return _to_s(self)\n\n    def to_s(self) -> str:\n        \"\"\"\n        Same as __repr__, but includes additional information which can be useful\n        for debugging, like empty or missing args and the AST nodes' object IDs.\n        \"\"\"\n        return _to_s(self, verbose=True)\n\n    def sql(self, dialect: DialectType = None, **opts: t.Any) -> str:\n        \"\"\"\n        Returns SQL string representation of this tree.\n\n        Args:\n            dialect: the dialect of the output SQL string (eg. \"spark\", \"hive\", \"presto\", \"mysql\").\n            opts: other `sqlglot.generator.Generator` options.\n\n        Returns:\n            The SQL string.\n        \"\"\"\n        from sqlglot.dialects.dialect import Dialect\n\n        return Dialect.get_or_raise(dialect).generate(self, **opts)\n\n    def transform(\n        self, fun: t.Callable, *args: object, copy: bool = True, **kwargs: object\n    ) -> t.Any:\n        \"\"\"\n        Visits all tree nodes (excluding already transformed ones)\n        and applies the given transformation function to each node.\n\n        Args:\n            fun: a function which takes a node as an argument and returns a\n                new transformed node or the same node without modifications. If the function\n                returns None, then the corresponding node will be removed from the syntax tree.\n            copy: if set to True a new tree instance is constructed, otherwise the tree is\n                modified in place.\n\n        Returns:\n            The transformed tree.\n        \"\"\"\n        root: t.Any = None\n        new_node: t.Any = None\n\n        for node in (self.copy() if copy else self).dfs(prune=lambda n: n is not new_node):\n            parent, arg_key, index = node.parent, node.arg_key, node.index\n            new_node = fun(node, *args, **kwargs)\n\n            if not root:\n                root = new_node\n            elif parent and arg_key and new_node is not node:\n                parent.set(arg_key, new_node, index)\n\n        assert root\n        return root\n\n    def replace(self, expression: t.Any) -> t.Any:\n        \"\"\"\n        Swap out this expression with a new expression.\n\n        For example::\n\n            >>> import sqlglot\n            >>> tree = sqlglot.parse_one(\"SELECT x FROM tbl\")\n            >>> tree.find(sqlglot.exp.Column).replace(sqlglot.exp.column(\"y\"))\n            Column(\n              this=Identifier(this=y, quoted=False))\n            >>> tree.sql()\n            'SELECT y FROM tbl'\n\n        Args:\n            expression: new node\n\n        Returns:\n            The new expression or expressions.\n        \"\"\"\n        parent = self.parent\n\n        if not parent or parent is expression:\n            return expression\n\n        key = self.arg_key\n\n        if key:\n            value = parent.args.get(key)\n\n            if type(expression) is list and isinstance(value, Expr):\n                # We are trying to replace an Expr with a list, so it's assumed that\n                # the intention was to really replace the parent of this expression.\n                if value.parent:\n                    value.parent.replace(expression)\n            else:\n                parent.set(key, expression, self.index)\n\n        if expression is not self:\n            self.parent = None\n            self.arg_key = None\n            self.index = None\n\n        return expression\n\n    def pop(self: E) -> E:\n        \"\"\"\n        Remove this expression from its AST.\n\n        Returns:\n            The popped expression.\n        \"\"\"\n        self.replace(None)\n        return self\n\n    def assert_is(self, type_: Type[E]) -> E:\n        \"\"\"\n        Assert that this `Expr` is an instance of `type_`.\n\n        If it is NOT an instance of `type_`, this raises an assertion error.\n        Otherwise, this returns this expression.\n\n        Examples:\n            This is useful for type security in chained expressions:\n\n            >>> import sqlglot\n            >>> sqlglot.parse_one(\"SELECT x from y\").assert_is(sqlglot.exp.Select).select(\"z\").sql()\n            'SELECT x, z FROM y'\n        \"\"\"\n        if not isinstance(self, type_):\n            raise AssertionError(f\"{self} is not {type_}.\")\n        return self\n\n    def error_messages(self, args: t.Optional[Sequence[object]] = None) -> list[str]:\n        \"\"\"\n        Checks if this expression is valid (e.g. all mandatory args are set).\n\n        Args:\n            args: a sequence of values that were used to instantiate a Func expression. This is used\n                to check that the provided arguments don't exceed the function argument limit.\n\n        Returns:\n            A list of error messages for all possible errors that were found.\n        \"\"\"\n        if UNITTEST:\n            for k in self.args:\n                if k not in self.arg_types:\n                    raise TypeError(f\"Unexpected keyword: '{k}' for {self.__class__}\")\n\n        errors: t.Optional[list[str]] = None\n\n        for k in self.required_args:\n            v = self.args.get(k)\n            if v is None or (isinstance(v, list) and not v):\n                if errors is None:\n                    errors = []\n                errors.append(f\"Required keyword: '{k}' missing for {self.__class__}\")\n\n        if (\n            args\n            and isinstance(self, Func)\n            and len(args) > len(self.arg_types)\n            and not self.is_var_len_args\n        ):\n            if errors is None:\n                errors = []\n            errors.append(\n                f\"The number of provided arguments ({len(args)}) is greater than \"\n                f\"the maximum number of supported arguments ({len(self.arg_types)})\"\n            )\n\n        return errors or []\n\n    def and_(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        dialect: DialectType = None,\n        copy: bool = True,\n        wrap: bool = True,\n        **opts: t.Any,\n    ) -> Condition:\n        \"\"\"\n        AND this condition with one or multiple expressions.\n\n        Example:\n            >>> condition(\"x=1\").and_(\"y=1\").sql()\n            'x = 1 AND y = 1'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n            dialect: the dialect used to parse the input expression.\n            copy: whether to copy the involved expressions (only applies to Exprs).\n            wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid\n                precedence issues, but can be turned off when the produced AST is too deep and\n                causes recursion-related issues.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The new And condition.\n        \"\"\"\n        return and_(self, *expressions, dialect=dialect, copy=copy, wrap=wrap, **opts)\n\n    def or_(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        dialect: DialectType = None,\n        copy: bool = True,\n        wrap: bool = True,\n        **opts: t.Any,\n    ) -> Condition:\n        \"\"\"\n        OR this condition with one or multiple expressions.\n\n        Example:\n            >>> condition(\"x=1\").or_(\"y=1\").sql()\n            'x = 1 OR y = 1'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n            dialect: the dialect used to parse the input expression.\n            copy: whether to copy the involved expressions (only applies to Exprs).\n            wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid\n                precedence issues, but can be turned off when the produced AST is too deep and\n                causes recursion-related issues.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The new Or condition.\n        \"\"\"\n        return or_(self, *expressions, dialect=dialect, copy=copy, wrap=wrap, **opts)\n\n    def not_(self, copy: bool = True) -> Not:\n        \"\"\"\n        Wrap this condition with NOT.\n\n        Example:\n            >>> condition(\"x=1\").not_().sql()\n            'NOT x = 1'\n\n        Args:\n            copy: whether to copy this object.\n\n        Returns:\n            The new Not instance.\n        \"\"\"\n        return not_(self, copy=copy)\n\n    def update_positions(\n        self: E,\n        other: t.Optional[Token | Expr] = None,\n        line: t.Optional[int] = None,\n        col: t.Optional[int] = None,\n        start: t.Optional[int] = None,\n        end: t.Optional[int] = None,\n    ) -> E:\n        \"\"\"\n        Update this expression with positions from a token or other expression.\n\n        Args:\n            other: a token or expression to update this expression with.\n            line: the line number to use if other is None\n            col: column number\n            start: start char index\n            end:  end char index\n\n        Returns:\n            The updated expression.\n        \"\"\"\n        if isinstance(other, Token):\n            meta = self.meta\n            meta[\"line\"] = other.line\n            meta[\"col\"] = other.col\n            meta[\"start\"] = other.start\n            meta[\"end\"] = other.end\n        elif other is not None:\n            other_meta = other._meta\n            if other_meta:\n                meta = self.meta\n                for k in POSITION_META_KEYS:\n                    if k in other_meta:\n                        meta[k] = other_meta[k]\n        else:\n            meta = self.meta\n            meta[\"line\"] = line\n            meta[\"col\"] = col\n            meta[\"start\"] = start\n            meta[\"end\"] = end\n        return self\n\n    def as_(\n        self,\n        alias: str | Identifier,\n        quoted: t.Optional[bool] = None,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts: t.Any,\n    ) -> Expr:\n        return alias_(self, alias, quoted=quoted, dialect=dialect, copy=copy, **opts)\n\n    def _binop(self, klass: Type[E], other: t.Any, reverse: bool = False) -> E:\n        this = self.copy()\n        other = convert(other, copy=True)\n        if not isinstance(this, klass) and not isinstance(other, klass):\n            this = _wrap(this, Binary)\n            other = _wrap(other, Binary)\n        if reverse:\n            return klass(this=other, expression=this)\n        return klass(this=this, expression=other)\n\n    def __getitem__(self, other: ExpOrStr | tuple[ExpOrStr, ...]) -> Bracket:\n        return Bracket(\n            this=self.copy(), expressions=[convert(e, copy=True) for e in ensure_list(other)]\n        )\n\n    def __iter__(self) -> Iterator:\n        if \"expressions\" in self.arg_types:\n            return iter(self.args.get(\"expressions\") or [])\n        # We define this because __getitem__ converts Expr into an iterable, which is\n        # problematic because one can hit infinite loops if they do \"for x in some_expr: ...\"\n        # See: https://peps.python.org/pep-0234/\n        raise TypeError(f\"'{self.__class__.__name__}' object is not iterable\")\n\n    def isin(\n        self,\n        *expressions: t.Any,\n        query: t.Optional[ExpOrStr] = None,\n        unnest: t.Optional[ExpOrStr] | Collection[ExpOrStr] = None,\n        copy: bool = True,\n        **opts,\n    ) -> In:\n        subquery = maybe_parse(query, copy=copy, **opts) if query else None\n        if subquery and not subquery.is_subquery:\n            subquery = subquery.subquery(copy=False)\n\n        return In(\n            this=maybe_copy(self, copy),\n            expressions=[convert(e, copy=copy) for e in expressions],\n            query=subquery,\n            unnest=(\n                _lazy_unnest(\n                    expressions=[\n                        maybe_parse(t.cast(ExpOrStr, e), copy=copy, **opts)\n                        for e in ensure_list(unnest)\n                    ]\n                )\n                if unnest\n                else None\n            ),\n        )\n\n    def between(\n        self,\n        low: t.Any,\n        high: t.Any,\n        copy: bool = True,\n        symmetric: t.Optional[bool] = None,\n        **opts,\n    ) -> Between:\n        between = Between(\n            this=maybe_copy(self, copy),\n            low=convert(low, copy=copy, **opts),\n            high=convert(high, copy=copy, **opts),\n        )\n        if symmetric is not None:\n            between.set(\"symmetric\", symmetric)\n\n        return between\n\n    def is_(self, other: ExpOrStr) -> Is:\n        return self._binop(Is, other)\n\n    def like(self, other: ExpOrStr) -> Like:\n        return self._binop(Like, other)\n\n    def ilike(self, other: ExpOrStr) -> ILike:\n        return self._binop(ILike, other)\n\n    def eq(self, other: t.Any) -> EQ:\n        return self._binop(EQ, other)\n\n    def neq(self, other: t.Any) -> NEQ:\n        return self._binop(NEQ, other)\n\n    def rlike(self, other: ExpOrStr) -> RegexpLike:\n        return self._binop(RegexpLike, other)\n\n    def div(self, other: ExpOrStr, typed: bool = False, safe: bool = False) -> Div:\n        div = self._binop(Div, other)\n        div.set(\"typed\", typed)\n        div.set(\"safe\", safe)\n        return div\n\n    def asc(self, nulls_first: bool = True) -> Ordered:\n        return Ordered(this=self.copy(), nulls_first=nulls_first)\n\n    def desc(self, nulls_first: bool = False) -> Ordered:\n        return Ordered(this=self.copy(), desc=True, nulls_first=nulls_first)\n\n    def __lt__(self, other: t.Any) -> LT:\n        return self._binop(LT, other)\n\n    def __le__(self, other: t.Any) -> LTE:\n        return self._binop(LTE, other)\n\n    def __gt__(self, other: t.Any) -> GT:\n        return self._binop(GT, other)\n\n    def __ge__(self, other: t.Any) -> GTE:\n        return self._binop(GTE, other)\n\n    def __add__(self, other: t.Any) -> Add:\n        return self._binop(Add, other)\n\n    def __radd__(self, other: t.Any) -> Add:\n        return self._binop(Add, other, reverse=True)\n\n    def __sub__(self, other: t.Any) -> Sub:\n        return self._binop(Sub, other)\n\n    def __rsub__(self, other: t.Any) -> Sub:\n        return self._binop(Sub, other, reverse=True)\n\n    def __mul__(self, other: t.Any) -> Mul:\n        return self._binop(Mul, other)\n\n    def __rmul__(self, other: t.Any) -> Mul:\n        return self._binop(Mul, other, reverse=True)\n\n    def __truediv__(self, other: t.Any) -> Div:\n        return self._binop(Div, other)\n\n    def __rtruediv__(self, other: t.Any) -> Div:\n        return self._binop(Div, other, reverse=True)\n\n    def __floordiv__(self, other: t.Any) -> IntDiv:\n        return self._binop(IntDiv, other)\n\n    def __rfloordiv__(self, other: t.Any) -> IntDiv:\n        return self._binop(IntDiv, other, reverse=True)\n\n    def __mod__(self, other: t.Any) -> Mod:\n        return self._binop(Mod, other)\n\n    def __rmod__(self, other: t.Any) -> Mod:\n        return self._binop(Mod, other, reverse=True)\n\n    def __pow__(self, other: t.Any) -> Pow:\n        return self._binop(Pow, other)\n\n    def __rpow__(self, other: t.Any) -> Pow:\n        return self._binop(Pow, other, reverse=True)\n\n    def __and__(self, other: t.Any) -> And:\n        return self._binop(And, other)\n\n    def __rand__(self, other: t.Any) -> And:\n        return self._binop(And, other, reverse=True)\n\n    def __or__(self, other: t.Any) -> Or:\n        return self._binop(Or, other)\n\n    def __ror__(self, other: t.Any) -> Or:\n        return self._binop(Or, other, reverse=True)\n\n    def __neg__(self) -> Neg:\n        return Neg(this=_wrap(self.copy(), Binary))\n\n    def __invert__(self) -> Not:\n        return not_(self.copy())\n\n\nIntoType = t.Union[Type[Expr], Collection[Type[Expr]]]\nExpOrStr = t.Union[int, str, Expr]\n\n\n@trait\nclass Condition(Expr):\n    \"\"\"Logical conditions like x AND y, or simply x\"\"\"\n\n\n@trait\nclass Predicate(Condition):\n    \"\"\"Relationships like x = y, x > 1, x >= y.\"\"\"\n\n\nclass Cache(Expression):\n    arg_types = {\n        \"this\": True,\n        \"lazy\": False,\n        \"options\": False,\n        \"expression\": False,\n    }\n\n\nclass Uncache(Expression):\n    arg_types = {\"this\": True, \"exists\": False}\n\n\nclass Refresh(Expression):\n    arg_types = {\"this\": True, \"kind\": True}\n\n\nclass LockingStatement(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\n@trait\nclass ColumnConstraintKind(Expr):\n    pass\n\n\n@trait\nclass SubqueryPredicate(Predicate):\n    pass\n\n\nclass All(Expression, SubqueryPredicate):\n    pass\n\n\nclass Any(Expression, SubqueryPredicate):\n    pass\n\n\n@trait\nclass Binary(Condition):\n    arg_types: t.ClassVar[t.Dict[str, bool]] = {\"this\": True, \"expression\": True}\n\n    @property\n    def left(self) -> Expr:\n        return self.args[\"this\"]\n\n    @property\n    def right(self) -> Expr:\n        return self.args[\"expression\"]\n\n\n@trait\nclass Connector(Binary):\n    pass\n\n\n@trait\nclass Func(Condition):\n    \"\"\"\n    The base class for all function expressions.\n\n    Attributes:\n        is_var_len_args (bool): if set to True the last argument defined in arg_types will be\n            treated as a variable length argument and the argument's value will be stored as a list.\n        _sql_names (list): the SQL name (1st item in the list) and aliases (subsequent items) for this\n            function expression. These values are used to map this node to a name during parsing as\n            well as to provide the function's name during SQL string generation. By default the SQL\n            name is set to the expression's class name transformed to snake case.\n    \"\"\"\n\n    is_var_len_args: t.ClassVar[bool] = False\n    _sql_names: t.ClassVar[t.List[str]] = []\n\n    @classmethod\n    def from_arg_list(cls, args):\n        if cls.is_var_len_args:\n            all_arg_keys = list(cls.arg_types)\n            # If this function supports variable length argument treat the last argument as such.\n            non_var_len_arg_keys = all_arg_keys[:-1] if cls.is_var_len_args else all_arg_keys\n            num_non_var = len(non_var_len_arg_keys)\n\n            args_dict = {arg_key: arg for arg, arg_key in zip(args, non_var_len_arg_keys)}\n            args_dict[all_arg_keys[-1]] = args[num_non_var:]\n        else:\n            args_dict = {arg_key: arg for arg, arg_key in zip(args, cls.arg_types)}\n\n        return cls(**args_dict)\n\n    @classmethod\n    def sql_names(cls):\n        if cls is Func:\n            raise NotImplementedError(\n                \"SQL name is only supported by concrete function implementations\"\n            )\n        if not cls._sql_names:\n            return [camel_to_snake_case(cls.__name__)]\n        return cls._sql_names\n\n    @classmethod\n    def sql_name(cls):\n        sql_names = cls.sql_names()\n        assert sql_names, f\"Expected non-empty 'sql_names' for Func: {cls.__name__}.\"\n        return sql_names[0]\n\n    @classmethod\n    def default_parser_mappings(cls):\n        return {name: cls.from_arg_list for name in cls.sql_names()}\n\n\n@trait\nclass AggFunc(Func):\n    pass\n\n\nclass Column(Expression, Condition):\n    arg_types = {\"this\": True, \"table\": False, \"db\": False, \"catalog\": False, \"join_mark\": False}\n\n    @property\n    def table(self) -> str:\n        return self.text(\"table\")\n\n    @property\n    def db(self) -> str:\n        return self.text(\"db\")\n\n    @property\n    def catalog(self) -> str:\n        return self.text(\"catalog\")\n\n    @property\n    def output_name(self) -> str:\n        return self.name\n\n    @property\n    def parts(self) -> t.List[Identifier | Star]:\n        \"\"\"Return the parts of a column in order catalog, db, table, name.\"\"\"\n        return [\n            self.args[part] for part in (\"catalog\", \"db\", \"table\", \"this\") if self.args.get(part)\n        ]\n\n    def to_dot(self, include_dots: bool = True) -> Dot | Identifier | Star:\n        \"\"\"Converts the column into a dot expression.\"\"\"\n        parts = self.parts\n        parent = self.parent\n\n        if include_dots:\n            while isinstance(parent, Dot):\n                parts.append(parent.expression)\n                parent = parent.parent\n\n        return Dot.build(deepcopy(parts)) if len(parts) > 1 else parts[0]\n\n\nclass Literal(Expression, Condition):\n    arg_types = {\"this\": True, \"is_string\": True}\n    _hash_raw_args = True\n    is_primitive = True\n\n    @classmethod\n    def number(cls, number) -> Literal | Neg:\n        lit = cls(this=str(number), is_string=False)\n        try:\n            to_py = lit.to_py()\n            if not isinstance(to_py, str) and to_py < 0:\n                lit.set(\"this\", str(abs(to_py)))\n                return Neg(this=lit)\n        except Exception:\n            pass\n        return lit\n\n    @classmethod\n    def string(cls, string) -> Literal:\n        return cls(this=str(string), is_string=True)\n\n    @property\n    def output_name(self) -> str:\n        return self.name\n\n    def to_py(self) -> int | str | Decimal:\n        if self.is_number:\n            try:\n                return int(self.this)\n            except ValueError:\n                return Decimal(self.this)\n        return self.this\n\n\nclass Var(Expression):\n    is_primitive = True\n\n\nclass WithinGroup(Expression):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass Pseudocolumn(Column):\n    pass\n\n\nclass Hint(Expression):\n    arg_types = {\"expressions\": True}\n\n\nclass JoinHint(Expression):\n    arg_types = {\"this\": True, \"expressions\": True}\n\n\nclass Identifier(Expression):\n    arg_types = {\"this\": True, \"quoted\": False, \"global_\": False, \"temporary\": False}\n    is_primitive = True\n    _hash_raw_args = True\n\n    @property\n    def quoted(self) -> bool:\n        return bool(self.args.get(\"quoted\"))\n\n    @property\n    def output_name(self) -> str:\n        return self.name\n\n\nclass Opclass(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Star(Expression):\n    arg_types = {\"except_\": False, \"replace\": False, \"rename\": False}\n\n    @property\n    def name(self) -> str:\n        return \"*\"\n\n    @property\n    def output_name(self) -> str:\n        return self.name\n\n\nclass Parameter(Expression, Condition):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass SessionParameter(Expression, Condition):\n    arg_types = {\"this\": True, \"kind\": False}\n\n\nclass Placeholder(Expression, Condition):\n    arg_types = {\"this\": False, \"kind\": False, \"widget\": False, \"jdbc\": False}\n\n    @property\n    def name(self) -> str:\n        return self.text(\"this\") or \"?\"\n\n\nclass Null(Expression, Condition):\n    arg_types = {}\n\n    @property\n    def name(self) -> str:\n        return \"NULL\"\n\n    def to_py(self) -> t.Literal[None]:\n        return None\n\n\nclass Boolean(Expression, Condition):\n    is_primitive = True\n\n    def to_py(self) -> bool:\n        return self.this\n\n\nclass Dot(Expression, Binary):\n    @property\n    def is_star(self) -> bool:\n        return self.expression.is_star\n\n    @property\n    def name(self) -> str:\n        return self.expression.name\n\n    @property\n    def output_name(self) -> str:\n        return self.name\n\n    @classmethod\n    def build(self, expressions: Sequence[Expr]) -> Dot:\n        \"\"\"Build a Dot object with a sequence of expressions.\"\"\"\n        if len(expressions) < 2:\n            raise ValueError(\"Dot requires >= 2 expressions.\")\n\n        return t.cast(Dot, reduce(lambda x, y: Dot(this=x, expression=y), expressions))\n\n    @property\n    def parts(self) -> t.List[Expr]:\n        \"\"\"Return the parts of a table / column in order catalog, db, table.\"\"\"\n        this, *parts = self.flatten()\n\n        parts.reverse()\n\n        for arg in COLUMN_PARTS:\n            part = this.args.get(arg)\n\n            if isinstance(part, Expr):\n                parts.append(part)\n\n        parts.reverse()\n        return parts\n\n\nclass Kwarg(Expression, Binary):\n    \"\"\"Kwarg in special functions like func(kwarg => y).\"\"\"\n\n\nclass Alias(Expression):\n    arg_types = {\"this\": True, \"alias\": False}\n\n    @property\n    def output_name(self) -> str:\n        return self.alias\n\n\nclass PivotAlias(Alias):\n    pass\n\n\nclass PivotAny(Expression):\n    arg_types = {\"this\": False}\n\n\nclass Aliases(Expression):\n    arg_types = {\"this\": True, \"expressions\": True}\n\n    @property\n    def aliases(self) -> t.List[Expr]:\n        return self.expressions\n\n\nclass Bracket(Expression, Condition):\n    # https://cloud.google.com/bigquery/docs/reference/standard-sql/operators#array_subscript_operator\n    arg_types = {\n        \"this\": True,\n        \"expressions\": True,\n        \"offset\": False,\n        \"safe\": False,\n        \"returns_list_for_maps\": False,\n    }\n\n    @property\n    def output_name(self) -> str:\n        if len(self.expressions) == 1:\n            return self.expressions[0].output_name\n\n        return super().output_name\n\n\nclass ForIn(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass IgnoreNulls(Expression):\n    pass\n\n\nclass RespectNulls(Expression):\n    pass\n\n\nclass HavingMax(Expression):\n    arg_types = {\"this\": True, \"expression\": True, \"max\": True}\n\n\nclass SafeFunc(Expression, Func):\n    pass\n\n\nclass Typeof(Expression, Func):\n    pass\n\n\nclass ParameterizedAgg(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expressions\": True, \"params\": True}\n\n\nclass Anonymous(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n\n    @property\n    def name(self) -> str:\n        return self.this if isinstance(self.this, str) else self.this.name\n\n\nclass AnonymousAggFunc(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n\n\nclass CombinedAggFunc(AnonymousAggFunc):\n    arg_types = {\"this\": True, \"expressions\": False}\n\n\nclass CombinedParameterizedAgg(ParameterizedAgg):\n    arg_types = {\"this\": True, \"expressions\": True, \"params\": True}\n\n\nclass HashAgg(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n\n\nclass Hll(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n\n\nclass ApproxDistinct(Expression, AggFunc):\n    arg_types = {\"this\": True, \"accuracy\": False}\n    _sql_names = [\"APPROX_DISTINCT\", \"APPROX_COUNT_DISTINCT\"]\n\n\nclass Slice(Expression):\n    arg_types = {\"this\": False, \"expression\": False, \"step\": False}\n\n\n@trait\nclass TimeUnit(Expr):\n    \"\"\"Automatically converts unit arg into a var.\"\"\"\n\n    UNABBREVIATED_UNIT_NAME: t.ClassVar[t.Dict[str, str]] = {\n        \"D\": \"DAY\",\n        \"H\": \"HOUR\",\n        \"M\": \"MINUTE\",\n        \"MS\": \"MILLISECOND\",\n        \"NS\": \"NANOSECOND\",\n        \"Q\": \"QUARTER\",\n        \"S\": \"SECOND\",\n        \"US\": \"MICROSECOND\",\n        \"W\": \"WEEK\",\n        \"Y\": \"YEAR\",\n    }\n\n    VAR_LIKE: t.ClassVar[t.Tuple[Type[Expr], ...]] = (Column, Literal, Var)\n\n    def __init__(self, **args: object) -> None:\n        super().__init__(**args)\n\n        unit = self.args.get(\"unit\")\n        if (\n            unit\n            and type(unit) in TimeUnit.VAR_LIKE\n            and not (isinstance(unit, Column) and len(unit.parts) != 1)\n        ):\n            unit = Var(this=(self.UNABBREVIATED_UNIT_NAME.get(unit.name) or unit.name).upper())\n            self.args[\"unit\"] = unit\n            self._set_parent(\"unit\", unit)\n        elif type(unit).__name__ == \"Week\":\n            unit.set(\"this\", Var(this=unit.this.name.upper()))  # type: ignore[union-attr]\n\n    @property\n    def unit(self) -> t.Optional[Expr]:\n        return self.args.get(\"unit\")\n\n\nclass _TimeUnit(Expression, TimeUnit):\n    \"\"\"Automatically converts unit arg into a var.\"\"\"\n\n    arg_types = {\"unit\": False}\n\n\n@trait\nclass IntervalOp(TimeUnit):\n    def interval(self) -> \"Interval\":\n        from sqlglot.expressions.datatypes import Interval\n\n        expr = self.expression\n        return Interval(\n            this=expr.copy() if expr is not None else None,\n            unit=self.unit.copy() if self.unit else None,\n        )\n\n\nclass Filter(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Check(Expression):\n    pass\n\n\nclass Ordered(Expression):\n    arg_types = {\"this\": True, \"desc\": False, \"nulls_first\": True, \"with_fill\": False}\n\n    @property\n    def name(self) -> str:\n        return self.this.name\n\n\nclass Add(Expression, Binary):\n    pass\n\n\nclass BitwiseAnd(Expression, Binary):\n    arg_types = {\"this\": True, \"expression\": True, \"padside\": False}\n\n\nclass BitwiseLeftShift(Expression, Binary):\n    arg_types = {\"this\": True, \"expression\": True, \"requires_int128\": False}\n\n\nclass BitwiseOr(Expression, Binary):\n    arg_types = {\"this\": True, \"expression\": True, \"padside\": False}\n\n\nclass BitwiseRightShift(Expression, Binary):\n    arg_types = {\"this\": True, \"expression\": True, \"requires_int128\": False}\n\n\nclass BitwiseXor(Expression, Binary):\n    arg_types = {\"this\": True, \"expression\": True, \"padside\": False}\n\n\nclass Div(Expression, Binary):\n    arg_types = {\"this\": True, \"expression\": True, \"typed\": False, \"safe\": False}\n\n\nclass Overlaps(Expression, Binary):\n    pass\n\n\nclass ExtendsLeft(Expression, Binary):\n    pass\n\n\nclass ExtendsRight(Expression, Binary):\n    pass\n\n\nclass DPipe(Expression, Binary):\n    arg_types = {\"this\": True, \"expression\": True, \"safe\": False}\n\n\nclass EQ(Expression, Binary, Predicate):\n    pass\n\n\nclass NullSafeEQ(Expression, Binary, Predicate):\n    pass\n\n\nclass NullSafeNEQ(Expression, Binary, Predicate):\n    pass\n\n\nclass PropertyEQ(Expression, Binary):\n    pass\n\n\nclass Distance(Expression, Binary):\n    pass\n\n\nclass Escape(Expression, Binary):\n    pass\n\n\nclass Glob(Expression, Binary, Predicate):\n    pass\n\n\nclass GT(Expression, Binary, Predicate):\n    pass\n\n\nclass GTE(Expression, Binary, Predicate):\n    pass\n\n\nclass ILike(Expression, Binary, Predicate):\n    pass\n\n\nclass IntDiv(Expression, Binary):\n    pass\n\n\nclass Is(Expression, Binary, Predicate):\n    pass\n\n\nclass Like(Expression, Binary, Predicate):\n    pass\n\n\nclass Match(Expression, Binary, Predicate):\n    pass\n\n\nclass LT(Expression, Binary, Predicate):\n    pass\n\n\nclass LTE(Expression, Binary, Predicate):\n    pass\n\n\nclass Mod(Expression, Binary):\n    pass\n\n\nclass Mul(Expression, Binary):\n    pass\n\n\nclass NEQ(Expression, Binary, Predicate):\n    pass\n\n\nclass NestedJSONSelect(Expression, Binary):\n    pass\n\n\nclass Operator(Expression, Binary):\n    arg_types = {\"this\": True, \"operator\": True, \"expression\": True}\n\n\nclass SimilarTo(Expression, Binary, Predicate):\n    pass\n\n\nclass Sub(Expression, Binary):\n    pass\n\n\nclass Adjacent(Expression, Binary):\n    pass\n\n\nclass Unary(Expression, Condition):\n    pass\n\n\nclass BitwiseNot(Unary):\n    pass\n\n\nclass Not(Unary):\n    pass\n\n\nclass Paren(Unary):\n    @property\n    def output_name(self) -> str:\n        return self.this.name\n\n\nclass Neg(Unary):\n    def to_py(self) -> int | Decimal:\n        if self.is_number:\n            return self.this.to_py() * -1\n        return super().to_py()\n\n\nclass AtIndex(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass AtTimeZone(Expression):\n    arg_types = {\"this\": True, \"zone\": True}\n\n\nclass FromTimeZone(Expression):\n    arg_types = {\"this\": True, \"zone\": True}\n\n\nclass FormatPhrase(Expression):\n    \"\"\"Format override for a column in Teradata.\n    Can be expanded to additional dialects as needed\n\n    https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT\n    \"\"\"\n\n    arg_types = {\"this\": True, \"format\": True}\n\n\nclass Between(Expression, Predicate):\n    arg_types = {\"this\": True, \"low\": True, \"high\": True, \"symmetric\": False}\n\n\nclass Distinct(Expression):\n    arg_types = {\"expressions\": False, \"on\": False}\n\n\nclass In(Expression, Predicate):\n    arg_types = {\n        \"this\": True,\n        \"expressions\": False,\n        \"query\": False,\n        \"unnest\": False,\n        \"field\": False,\n        \"is_global\": False,\n    }\n\n\nclass And(Expression, Connector, Func):\n    pass\n\n\nclass Or(Expression, Connector, Func):\n    pass\n\n\nclass Xor(Expression, Connector, Func):\n    arg_types = {\"this\": False, \"expression\": False, \"expressions\": False, \"round_input\": False}\n    is_var_len_args = True\n\n\nclass Pow(Expression, Binary, Func):\n    _sql_names = [\"POWER\", \"POW\"]\n\n\nclass RegexpLike(Expression, Binary, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"flag\": False, \"full_match\": False}\n\n\ndef not_(expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts) -> Not:\n    \"\"\"\n    Wrap a condition with a NOT operator.\n\n    Example:\n        >>> not_(\"this_suit='black'\").sql()\n        \"NOT this_suit = 'black'\"\n\n    Args:\n        expression: the SQL code string to parse.\n            If an Expr instance is passed, this is used as-is.\n        dialect: the dialect used to parse the input expression.\n        copy: whether to copy the expression or not.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        The new condition.\n    \"\"\"\n    this = condition(\n        expression,\n        dialect=dialect,\n        copy=copy,\n        **opts,\n    )\n    return Not(this=_wrap(this, Connector))\n\n\ndef _lazy_unnest(**kwargs: object) -> \"Expr\":\n    from sqlglot.expressions.array import Unnest\n\n    return Unnest(**kwargs)\n\n\ndef convert(value: t.Any, copy: bool = False) -> Expr:\n    \"\"\"Convert a python value into an expression object.\n\n    Raises an error if a conversion is not possible.\n\n    Args:\n        value: A python object.\n        copy: Whether to copy `value` (only applies to Exprs and collections).\n\n    Returns:\n        The equivalent expression object.\n    \"\"\"\n    if isinstance(value, Expr):\n        return maybe_copy(value, copy)\n    if isinstance(value, str):\n        return Literal.string(value)\n    if isinstance(value, bool):\n        return Boolean(this=value)\n    if value is None or (isinstance(value, float) and math.isnan(value)):\n        return Null()\n    if isinstance(value, numbers.Number):\n        return Literal.number(value)\n    if isinstance(value, bytes):\n        from sqlglot.expressions.query import HexString as _HexString\n\n        return _HexString(this=value.hex())\n    if isinstance(value, datetime.datetime):\n        datetime_literal = Literal.string(value.isoformat(sep=\" \"))\n\n        tz = None\n        if value.tzinfo:\n            # this works for zoneinfo.ZoneInfo, pytz.timezone and datetime.datetime.utc to return IANA timezone names like \"America/Los_Angeles\"\n            # instead of abbreviations like \"PDT\". This is for consistency with other timezone handling functions in SQLGlot\n            tz = Literal.string(str(value.tzinfo))\n\n        from sqlglot.expressions.temporal import TimeStrToTime as _TimeStrToTime\n\n        return _TimeStrToTime(this=datetime_literal, zone=tz)\n    if isinstance(value, datetime.date):\n        date_literal = Literal.string(value.strftime(\"%Y-%m-%d\"))\n        from sqlglot.expressions.temporal import DateStrToDate as _DateStrToDate\n\n        return _DateStrToDate(this=date_literal)\n    if isinstance(value, datetime.time):\n        time_literal = Literal.string(value.isoformat())\n        from sqlglot.expressions.temporal import TsOrDsToTime as _TsOrDsToTime\n\n        return _TsOrDsToTime(this=time_literal)\n    if isinstance(value, tuple):\n        if hasattr(value, \"_fields\"):\n            from sqlglot.expressions.array import Struct as _Struct\n\n            return _Struct(\n                expressions=[\n                    PropertyEQ(\n                        this=to_identifier(k), expression=convert(getattr(value, k), copy=copy)\n                    )\n                    for k in value._fields\n                ]\n            )\n        from sqlglot.expressions.query import Tuple as _Tuple\n\n        return _Tuple(expressions=[convert(v, copy=copy) for v in value])\n    if isinstance(value, list):\n        from sqlglot.expressions.array import Array as _Array\n\n        return _Array(expressions=[convert(v, copy=copy) for v in value])\n    if isinstance(value, dict):\n        from sqlglot.expressions.array import Array as _Array, Map as _Map\n\n        return _Map(\n            keys=_Array(expressions=[convert(k, copy=copy) for k in value]),\n            values=_Array(expressions=[convert(v, copy=copy) for v in value.values()]),\n        )\n    if hasattr(value, \"__dict__\"):\n        from sqlglot.expressions.array import Struct as _Struct\n\n        return _Struct(\n            expressions=[\n                PropertyEQ(this=to_identifier(k), expression=convert(v, copy=copy))\n                for k, v in value.__dict__.items()\n            ]\n        )\n    raise ValueError(f\"Cannot convert {value}\")\n\n\nQUERY_MODIFIERS = {\n    \"match\": False,\n    \"laterals\": False,\n    \"joins\": False,\n    \"connect\": False,\n    \"pivots\": False,\n    \"prewhere\": False,\n    \"where\": False,\n    \"group\": False,\n    \"having\": False,\n    \"qualify\": False,\n    \"windows\": False,\n    \"distribute\": False,\n    \"sort\": False,\n    \"cluster\": False,\n    \"order\": False,\n    \"limit\": False,\n    \"offset\": False,\n    \"locks\": False,\n    \"sample\": False,\n    \"settings\": False,\n    \"format\": False,\n    \"options\": False,\n}\n\n\nTIMESTAMP_PARTS = {\n    \"year\": False,\n    \"month\": False,\n    \"day\": False,\n    \"hour\": False,\n    \"min\": False,\n    \"sec\": False,\n    \"nano\": False,\n}\n\n\n@t.overload\ndef maybe_parse(\n    sql_or_expression: ExpOrStr,\n    *,\n    into: Type[E],\n    dialect: DialectType = None,\n    prefix: t.Optional[str] = None,\n    copy: bool = False,\n    **opts,\n) -> E: ...\n\n\n@t.overload\ndef maybe_parse(\n    sql_or_expression: int | str | E,\n    *,\n    into: t.Optional[IntoType] = None,\n    dialect: DialectType = None,\n    prefix: t.Optional[str] = None,\n    copy: bool = False,\n    **opts,\n) -> E: ...\n\n\ndef maybe_parse(\n    sql_or_expression: ExpOrStr,\n    *,\n    into: t.Optional[IntoType] = None,\n    dialect: DialectType = None,\n    prefix: t.Optional[str] = None,\n    copy: bool = False,\n    **opts: t.Any,\n) -> Expr:\n    \"\"\"Gracefully handle a possible string or expression.\n\n    Example:\n        >>> maybe_parse(\"1\")\n        Literal(this=1, is_string=False)\n        >>> maybe_parse(to_identifier(\"x\"))\n        Identifier(this=x, quoted=False)\n\n    Args:\n        sql_or_expression: the SQL code string or an expression\n        into: the SQLGlot Expr to parse into\n        dialect: the dialect used to parse the input expressions (in the case that an\n            input expression is a SQL string).\n        prefix: a string to prefix the sql with before it gets parsed\n            (automatically includes a space)\n        copy: whether to copy the expression.\n        **opts: other options to use to parse the input expressions (again, in the case\n            that an input expression is a SQL string).\n\n    Returns:\n        Expr: the parsed or given expression.\n    \"\"\"\n    if isinstance(sql_or_expression, Expr):\n        if copy:\n            return sql_or_expression.copy()\n        return sql_or_expression\n\n    if sql_or_expression is None:\n        raise ParseError(\"SQL cannot be None\")\n\n    import sqlglot\n\n    sql = str(sql_or_expression)\n    if prefix:\n        sql = f\"{prefix} {sql}\"\n\n    return sqlglot.parse_one(sql, read=dialect, into=into, **opts)\n\n\n@t.overload\ndef maybe_copy(instance: None, copy: bool = True) -> None: ...\n\n\n@t.overload\ndef maybe_copy(instance: E, copy: bool = True) -> E: ...\n\n\ndef maybe_copy(instance, copy=True):\n    return instance.copy() if copy and instance else instance\n\n\ndef _to_s(node: t.Any, verbose: bool = False, level: int = 0, repr_str: bool = False) -> str:\n    \"\"\"Generate a textual representation of an Expr tree\"\"\"\n    indent = \"\\n\" + (\"  \" * (level + 1))\n    delim = f\",{indent}\"\n\n    if isinstance(node, Expr):\n        args = {k: v for k, v in node.args.items() if (v is not None and v != []) or verbose}\n\n        if (node.type or verbose) and type(node).__name__ != \"DataType\":\n            args[\"_type\"] = node.type\n        if node.comments or verbose:\n            args[\"_comments\"] = node.comments\n\n        if verbose:\n            args[\"_id\"] = id(node)\n\n        # Inline leaves for a more compact representation\n        if node.is_leaf():\n            indent = \"\"\n            delim = \", \"\n\n        repr_str = node.is_string or (isinstance(node, Identifier) and node.quoted)\n        items = delim.join(\n            [f\"{k}={_to_s(v, verbose, level + 1, repr_str=repr_str)}\" for k, v in args.items()]\n        )\n        return f\"{node.__class__.__name__}({indent}{items})\"\n\n    if isinstance(node, list):\n        items = delim.join(_to_s(i, verbose, level + 1) for i in node)\n        items = f\"{indent}{items}\" if items else \"\"\n        return f\"[{items}]\"\n\n    # We use the representation of the string to avoid stripping out important whitespace\n    if repr_str and isinstance(node, str):\n        node = repr(node)\n\n    # Indent multiline strings to match the current level\n    return indent.join(textwrap.dedent(str(node).strip(\"\\n\")).splitlines())\n\n\ndef _is_wrong_expression(expression, into):\n    return isinstance(expression, Expr) and not isinstance(expression, into)\n\n\ndef _apply_builder(\n    expression,\n    instance,\n    arg,\n    copy=True,\n    prefix=None,\n    into=None,\n    dialect=None,\n    into_arg=\"this\",\n    **opts,\n):\n    if _is_wrong_expression(expression, into):\n        expression = into(**{into_arg: expression})\n    instance = maybe_copy(instance, copy)\n    expression = maybe_parse(\n        sql_or_expression=expression,\n        prefix=prefix,\n        into=into,\n        dialect=dialect,\n        **opts,\n    )\n    instance.set(arg, expression)\n    return instance\n\n\ndef _apply_child_list_builder(\n    *expressions,\n    instance,\n    arg,\n    append=True,\n    copy=True,\n    prefix=None,\n    into=None,\n    dialect=None,\n    properties=None,\n    **opts,\n):\n    instance = maybe_copy(instance, copy)\n    parsed = []\n    properties = {} if properties is None else properties\n\n    for expression in expressions:\n        if expression is not None:\n            if _is_wrong_expression(expression, into):\n                expression = into(expressions=[expression])\n\n            expression = maybe_parse(\n                expression,\n                into=into,\n                dialect=dialect,\n                prefix=prefix,\n                **opts,\n            )\n            for k, v in expression.args.items():\n                if k == \"expressions\":\n                    parsed.extend(v)\n                else:\n                    properties[k] = v\n\n    existing = instance.args.get(arg)\n    if append and existing:\n        parsed = existing.expressions + parsed\n\n    child = into(expressions=parsed)\n    for k, v in properties.items():\n        child.set(k, v)\n    instance.set(arg, child)\n\n    return instance\n\n\ndef _apply_list_builder(\n    *expressions,\n    instance,\n    arg,\n    append=True,\n    copy=True,\n    prefix=None,\n    into=None,\n    dialect=None,\n    **opts,\n):\n    inst = maybe_copy(instance, copy)\n\n    parsed = [\n        maybe_parse(\n            sql_or_expression=expression,\n            into=into,\n            prefix=prefix,\n            dialect=dialect,\n            **opts,\n        )\n        for expression in expressions\n        if expression is not None\n    ]\n\n    existing_expressions = inst.args.get(arg)\n    if append and existing_expressions:\n        parsed = existing_expressions + parsed\n\n    inst.set(arg, parsed)\n    return inst\n\n\ndef _apply_conjunction_builder(\n    *expressions,\n    instance,\n    arg,\n    into=None,\n    append=True,\n    copy=True,\n    dialect=None,\n    **opts,\n):\n    filtered = [exp for exp in expressions if exp is not None and exp != \"\"]\n    if not filtered:\n        return instance\n\n    inst = maybe_copy(instance, copy)\n\n    existing = inst.args.get(arg)\n    if append and existing is not None:\n        filtered = [existing.this if into else existing] + filtered\n\n    node = and_(*filtered, dialect=dialect, copy=copy, **opts)\n\n    inst.set(arg, into(this=node) if into else node)\n    return inst\n\n\ndef _combine(\n    expressions: Sequence[t.Optional[ExpOrStr]],\n    operator: t.Any,\n    dialect: DialectType = None,\n    copy: bool = True,\n    wrap: bool = True,\n    **opts,\n) -> Expr:\n    conditions = [\n        condition(expression, dialect=dialect, copy=copy, **opts)\n        for expression in expressions\n        if expression is not None\n    ]\n\n    this, *rest = conditions\n    if rest and wrap:\n        this = _wrap(this, Connector)\n    for expression in rest:\n        this = operator(this=this, expression=_wrap(expression, Connector) if wrap else expression)\n\n    return this\n\n\n@t.overload\ndef _wrap(expression: None, kind: Type[Expr]) -> None: ...\n\n\n@t.overload\ndef _wrap(expression: E, kind: Type[Expr]) -> E | Paren: ...\n\n\ndef _wrap(expression: t.Optional[E], kind: Type[Expr]) -> t.Optional[E] | Paren:\n    return Paren(this=expression) if isinstance(expression, kind) else expression\n\n\ndef _apply_set_operation(\n    *expressions: ExpOrStr,\n    set_operation: Type,\n    distinct: bool = True,\n    dialect: DialectType = None,\n    copy: bool = True,\n    **opts,\n) -> t.Any:\n    return reduce(\n        lambda x, y: set_operation(this=x, expression=y, distinct=distinct, **opts),\n        (maybe_parse(e, dialect=dialect, copy=copy, **opts) for e in expressions),\n    )\n\n\nSAFE_IDENTIFIER_RE: t.Pattern[str] = re.compile(r\"^[_a-zA-Z][\\w]*$\")\n\n\n@t.overload\ndef to_identifier(name: None, quoted: t.Optional[bool] = None, copy: bool = True) -> None: ...\n\n\n@t.overload\ndef to_identifier(\n    name: int | str | Identifier, quoted: t.Optional[bool] = None, copy: bool = True\n) -> Identifier: ...\n\n\ndef to_identifier(name, quoted=None, copy=True):\n    \"\"\"Builds an identifier.\n\n    Args:\n        name: The name to turn into an identifier.\n        quoted: Whether to force quote the identifier.\n        copy: Whether to copy name if it's an Identifier.\n\n    Returns:\n        The identifier ast node.\n    \"\"\"\n\n    if name is None:\n        return None\n\n    if isinstance(name, Identifier):\n        identifier = maybe_copy(name, copy)\n    elif isinstance(name, str):\n        identifier = Identifier(\n            this=name,\n            quoted=not SAFE_IDENTIFIER_RE.match(name) if quoted is None else quoted,\n        )\n    else:\n        raise ValueError(f\"Name needs to be a string or an Identifier, got: {name.__class__}\")\n    return identifier\n\n\ndef condition(expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts) -> Expr:\n    \"\"\"\n    Initialize a logical condition expression.\n\n    Example:\n        >>> condition(\"x=1\").sql()\n        'x = 1'\n\n        This is helpful for composing larger logical syntax trees:\n        >>> where = condition(\"x=1\")\n        >>> where = where.and_(\"y=1\")\n        >>> where.sql()\n        'x = 1 AND y = 1'\n\n    Args:\n        *expression: the SQL code string to parse.\n            If an Expr instance is passed, this is used as-is.\n        dialect: the dialect used to parse the input expression (in the case that the\n            input expression is a SQL string).\n        copy: Whether to copy `expression` (only applies to expressions).\n        **opts: other options to use to parse the input expressions (again, in the case\n            that the input expression is a SQL string).\n\n    Returns:\n        The new Condition instance\n    \"\"\"\n    return maybe_parse(\n        expression,\n        into=Condition,\n        dialect=dialect,\n        copy=copy,\n        **opts,\n    )\n\n\ndef and_(\n    *expressions: t.Optional[ExpOrStr],\n    dialect: DialectType = None,\n    copy: bool = True,\n    wrap: bool = True,\n    **opts,\n) -> Condition:\n    \"\"\"\n    Combine multiple conditions with an AND logical operator.\n\n    Example:\n        >>> and_(\"x=1\", and_(\"y=1\", \"z=1\")).sql()\n        'x = 1 AND (y = 1 AND z = 1)'\n\n    Args:\n        *expressions: the SQL code strings to parse.\n            If an Expr instance is passed, this is used as-is.\n        dialect: the dialect used to parse the input expression.\n        copy: whether to copy `expressions` (only applies to Exprs).\n        wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid\n            precedence issues, but can be turned off when the produced AST is too deep and\n            causes recursion-related issues.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        The new condition\n    \"\"\"\n    return t.cast(Condition, _combine(expressions, And, dialect, copy=copy, wrap=wrap, **opts))\n\n\ndef or_(\n    *expressions: t.Optional[ExpOrStr],\n    dialect: DialectType = None,\n    copy: bool = True,\n    wrap: bool = True,\n    **opts,\n) -> Condition:\n    \"\"\"\n    Combine multiple conditions with an OR logical operator.\n\n    Example:\n        >>> or_(\"x=1\", or_(\"y=1\", \"z=1\")).sql()\n        'x = 1 OR (y = 1 OR z = 1)'\n\n    Args:\n        *expressions: the SQL code strings to parse.\n            If an Expr instance is passed, this is used as-is.\n        dialect: the dialect used to parse the input expression.\n        copy: whether to copy `expressions` (only applies to Exprs).\n        wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid\n            precedence issues, but can be turned off when the produced AST is too deep and\n            causes recursion-related issues.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        The new condition\n    \"\"\"\n    return t.cast(Condition, _combine(expressions, Or, dialect, copy=copy, wrap=wrap, **opts))\n\n\ndef xor(\n    *expressions: t.Optional[ExpOrStr],\n    dialect: DialectType = None,\n    copy: bool = True,\n    wrap: bool = True,\n    **opts,\n) -> Condition:\n    \"\"\"\n    Combine multiple conditions with an XOR logical operator.\n\n    Example:\n        >>> xor(\"x=1\", xor(\"y=1\", \"z=1\")).sql()\n        'x = 1 XOR (y = 1 XOR z = 1)'\n\n    Args:\n        *expressions: the SQL code strings to parse.\n            If an Expr instance is passed, this is used as-is.\n        dialect: the dialect used to parse the input expression.\n        copy: whether to copy `expressions` (only applies to Exprs).\n        wrap: whether to wrap the operands in `Paren`s. This is true by default to avoid\n            precedence issues, but can be turned off when the produced AST is too deep and\n            causes recursion-related issues.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        The new condition\n    \"\"\"\n    return t.cast(Condition, _combine(expressions, Xor, dialect, copy=copy, wrap=wrap, **opts))\n\n\ndef paren(expression: ExpOrStr, copy: bool = True) -> Paren:\n    \"\"\"\n    Wrap an expression in parentheses.\n\n    Example:\n        >>> paren(\"5 + 3\").sql()\n        '(5 + 3)'\n\n    Args:\n        expression: the SQL code string to parse.\n            If an Expr instance is passed, this is used as-is.\n        copy: whether to copy the expression or not.\n\n    Returns:\n        The wrapped expression.\n    \"\"\"\n    return Paren(this=maybe_parse(expression, copy=copy))\n\n\ndef alias_(\n    expression: ExpOrStr,\n    alias: t.Optional[str | Identifier],\n    table: bool | Sequence[str | Identifier] = False,\n    quoted: t.Optional[bool] = None,\n    dialect: DialectType = None,\n    copy: bool = True,\n    **opts,\n) -> Expr:\n    \"\"\"Create an Alias expression.\n\n    Example:\n        >>> alias_('foo', 'bar').sql()\n        'foo AS bar'\n\n        >>> alias_('(select 1, 2)', 'bar', table=['a', 'b']).sql()\n        '(SELECT 1, 2) AS bar(a, b)'\n\n    Args:\n        expression: the SQL code strings to parse.\n            If an Expr instance is passed, this is used as-is.\n        alias: the alias name to use. If the name has\n            special characters it is quoted.\n        table: Whether to create a table alias, can also be a list of columns.\n        quoted: whether to quote the alias\n        dialect: the dialect used to parse the input expression.\n        copy: Whether to copy the expression.\n        **opts: other options to use to parse the input expressions.\n\n    Returns:\n        Alias: the aliased expression\n    \"\"\"\n    exp = maybe_parse(expression, dialect=dialect, copy=copy, **opts)\n    alias = to_identifier(alias, quoted=quoted)\n\n    if table:\n        from sqlglot.expressions.query import TableAlias as _TableAlias\n\n        table_alias = _TableAlias(this=alias)\n        exp.set(\"alias\", table_alias)\n\n        if not isinstance(table, bool):\n            for column in table:\n                table_alias.append(\"columns\", to_identifier(column, quoted=quoted))\n\n        return exp\n\n    # We don't set the \"alias\" arg for Window expressions, because that would add an IDENTIFIER node in\n    # the AST, representing a \"named_window\" [1] construct (eg. bigquery). What we want is an ALIAS node\n    # for the complete Window expression.\n    #\n    # [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls\n\n    if \"alias\" in exp.arg_types and type(exp).__name__ != \"Window\":\n        exp.set(\"alias\", alias)\n        return exp\n    return Alias(this=exp, alias=alias)\n\n\n@t.overload\ndef column(\n    col: str | Identifier,\n    table: t.Optional[str | Identifier] = None,\n    db: t.Optional[str | Identifier] = None,\n    catalog: t.Optional[str | Identifier] = None,\n    *,\n    fields: Collection[t.Union[str, Identifier]],\n    quoted: t.Optional[bool] = None,\n    copy: bool = True,\n) -> Dot:\n    pass\n\n\n@t.overload\ndef column(\n    col: str | Identifier | Star,\n    table: t.Optional[str | Identifier] = None,\n    db: t.Optional[str | Identifier] = None,\n    catalog: t.Optional[str | Identifier] = None,\n    *,\n    fields: t.Literal[None] = None,\n    quoted: t.Optional[bool] = None,\n    copy: bool = True,\n) -> Column:\n    pass\n\n\ndef column(\n    col,\n    table=None,\n    db=None,\n    catalog=None,\n    *,\n    fields=None,\n    quoted=None,\n    copy=True,\n):\n    \"\"\"\n    Build a Column.\n\n    Args:\n        col: Column name.\n        table: Table name.\n        db: Database name.\n        catalog: Catalog name.\n        fields: Additional fields using dots.\n        quoted: Whether to force quotes on the column's identifiers.\n        copy: Whether to copy identifiers if passed in.\n\n    Returns:\n        The new Column instance.\n    \"\"\"\n    if not isinstance(col, Star):\n        col = to_identifier(col, quoted=quoted, copy=copy)\n\n    this = Column(\n        this=col,\n        table=to_identifier(table, quoted=quoted, copy=copy),\n        db=to_identifier(db, quoted=quoted, copy=copy),\n        catalog=to_identifier(catalog, quoted=quoted, copy=copy),\n    )\n\n    if fields:\n        this = Dot.build(\n            (this, *(to_identifier(field, quoted=quoted, copy=copy) for field in fields))\n        )\n    return this\n"
  },
  {
    "path": "sqlglot/expressions/datatypes.py",
    "content": "\"\"\"sqlglot expressions datatypes.\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\nfrom enum import auto\n\nfrom sqlglot.helper import AutoName\nfrom sqlglot.errors import ErrorLevel, ParseError\nfrom sqlglot.expressions.core import (\n    Expression,\n    _TimeUnit,\n    Identifier,\n    Dot,\n    maybe_copy,\n)\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n\n\nclass DataTypeParam(Expression):\n    arg_types = {\"this\": True, \"expression\": False}\n\n    @property\n    def name(self) -> str:\n        return self.this.name\n\n\nclass DType(AutoName):\n    ARRAY = auto()\n    AGGREGATEFUNCTION = auto()\n    SIMPLEAGGREGATEFUNCTION = auto()\n    BIGDECIMAL = auto()\n    BIGINT = auto()\n    BIGNUM = auto()\n    BIGSERIAL = auto()\n    BINARY = auto()\n    BIT = auto()\n    BLOB = auto()\n    BOOLEAN = auto()\n    BPCHAR = auto()\n    CHAR = auto()\n    CHARACTER_SET = auto()\n    DATE = auto()\n    DATE32 = auto()\n    DATEMULTIRANGE = auto()\n    DATERANGE = auto()\n    DATETIME = auto()\n    DATETIME2 = auto()\n    DATETIME64 = auto()\n    DECIMAL = auto()\n    DECIMAL32 = auto()\n    DECIMAL64 = auto()\n    DECIMAL128 = auto()\n    DECIMAL256 = auto()\n    DECFLOAT = auto()\n    DOUBLE = auto()\n    DYNAMIC = auto()\n    ENUM = auto()\n    ENUM8 = auto()\n    ENUM16 = auto()\n    FILE = auto()\n    FIXEDSTRING = auto()\n    FLOAT = auto()\n    GEOGRAPHY = auto()\n    GEOGRAPHYPOINT = auto()\n    GEOMETRY = auto()\n    POINT = auto()\n    RING = auto()\n    LINESTRING = auto()\n    MULTILINESTRING = auto()\n    POLYGON = auto()\n    MULTIPOLYGON = auto()\n    HLLSKETCH = auto()\n    HSTORE = auto()\n    IMAGE = auto()\n    INET = auto()\n    INT = auto()\n    INT128 = auto()\n    INT256 = auto()\n    INT4MULTIRANGE = auto()\n    INT4RANGE = auto()\n    INT8MULTIRANGE = auto()\n    INT8RANGE = auto()\n    INTERVAL = auto()\n    IPADDRESS = auto()\n    IPPREFIX = auto()\n    IPV4 = auto()\n    IPV6 = auto()\n    JSON = auto()\n    JSONB = auto()\n    LIST = auto()\n    LONGBLOB = auto()\n    LONGTEXT = auto()\n    LOWCARDINALITY = auto()\n    MAP = auto()\n    MEDIUMBLOB = auto()\n    MEDIUMINT = auto()\n    MEDIUMTEXT = auto()\n    MONEY = auto()\n    NAME = auto()\n    NCHAR = auto()\n    NESTED = auto()\n    NOTHING = auto()\n    NULL = auto()\n    NUMMULTIRANGE = auto()\n    NUMRANGE = auto()\n    NVARCHAR = auto()\n    OBJECT = auto()\n    RANGE = auto()\n    ROWVERSION = auto()\n    SERIAL = auto()\n    SET = auto()\n    SMALLDATETIME = auto()\n    SMALLINT = auto()\n    SMALLMONEY = auto()\n    SMALLSERIAL = auto()\n    STRUCT = auto()\n    SUPER = auto()\n    TEXT = auto()\n    TINYBLOB = auto()\n    TINYTEXT = auto()\n    TIME = auto()\n    TIMETZ = auto()\n    TIME_NS = auto()\n    TIMESTAMP = auto()\n    TIMESTAMPNTZ = auto()\n    TIMESTAMPLTZ = auto()\n    TIMESTAMPTZ = auto()\n    TIMESTAMP_S = auto()\n    TIMESTAMP_MS = auto()\n    TIMESTAMP_NS = auto()\n    TINYINT = auto()\n    TSMULTIRANGE = auto()\n    TSRANGE = auto()\n    TSTZMULTIRANGE = auto()\n    TSTZRANGE = auto()\n    UBIGINT = auto()\n    UINT = auto()\n    UINT128 = auto()\n    UINT256 = auto()\n    UMEDIUMINT = auto()\n    UDECIMAL = auto()\n    UDOUBLE = auto()\n    UNION = auto()\n    UNKNOWN = auto()  # Sentinel value, useful for type annotation\n    USERDEFINED = \"USER-DEFINED\"\n    USMALLINT = auto()\n    UTINYINT = auto()\n    UUID = auto()\n    VARBINARY = auto()\n    VARCHAR = auto()\n    VARIANT = auto()\n    VECTOR = auto()\n    XML = auto()\n    YEAR = auto()\n    TDIGEST = auto()\n\n\nclass DataType(Expression):\n    arg_types = {\n        \"this\": True,\n        \"expressions\": False,\n        \"nested\": False,\n        \"values\": False,\n        \"kind\": False,\n        \"nullable\": False,\n    }\n\n    Type: t.ClassVar[t.Type[DType]] = DType\n\n    STRUCT_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.FILE,\n        DType.NESTED,\n        DType.OBJECT,\n        DType.STRUCT,\n        DType.UNION,\n    }\n\n    ARRAY_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.ARRAY,\n        DType.LIST,\n    }\n\n    NESTED_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.FILE,\n        DType.NESTED,\n        DType.OBJECT,\n        DType.STRUCT,\n        DType.UNION,\n        DType.ARRAY,\n        DType.LIST,\n        DType.MAP,\n    }\n\n    TEXT_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.CHAR,\n        DType.NCHAR,\n        DType.NVARCHAR,\n        DType.TEXT,\n        DType.VARCHAR,\n        DType.NAME,\n    }\n\n    SIGNED_INTEGER_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.BIGINT,\n        DType.INT,\n        DType.INT128,\n        DType.INT256,\n        DType.MEDIUMINT,\n        DType.SMALLINT,\n        DType.TINYINT,\n    }\n\n    UNSIGNED_INTEGER_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.UBIGINT,\n        DType.UINT,\n        DType.UINT128,\n        DType.UINT256,\n        DType.UMEDIUMINT,\n        DType.USMALLINT,\n        DType.UTINYINT,\n    }\n\n    INTEGER_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.BIGINT,\n        DType.INT,\n        DType.INT128,\n        DType.INT256,\n        DType.MEDIUMINT,\n        DType.SMALLINT,\n        DType.TINYINT,\n        DType.UBIGINT,\n        DType.UINT,\n        DType.UINT128,\n        DType.UINT256,\n        DType.UMEDIUMINT,\n        DType.USMALLINT,\n        DType.UTINYINT,\n        DType.BIT,\n    }\n\n    FLOAT_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.DOUBLE,\n        DType.FLOAT,\n    }\n\n    REAL_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.DOUBLE,\n        DType.FLOAT,\n        DType.BIGDECIMAL,\n        DType.DECIMAL,\n        DType.DECIMAL32,\n        DType.DECIMAL64,\n        DType.DECIMAL128,\n        DType.DECIMAL256,\n        DType.DECFLOAT,\n        DType.MONEY,\n        DType.SMALLMONEY,\n        DType.UDECIMAL,\n        DType.UDOUBLE,\n    }\n\n    NUMERIC_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.BIGINT,\n        DType.INT,\n        DType.INT128,\n        DType.INT256,\n        DType.MEDIUMINT,\n        DType.SMALLINT,\n        DType.TINYINT,\n        DType.UBIGINT,\n        DType.UINT,\n        DType.UINT128,\n        DType.UINT256,\n        DType.UMEDIUMINT,\n        DType.USMALLINT,\n        DType.UTINYINT,\n        DType.BIT,\n        DType.DOUBLE,\n        DType.FLOAT,\n        DType.BIGDECIMAL,\n        DType.DECIMAL,\n        DType.DECIMAL32,\n        DType.DECIMAL64,\n        DType.DECIMAL128,\n        DType.DECIMAL256,\n        DType.DECFLOAT,\n        DType.MONEY,\n        DType.SMALLMONEY,\n        DType.UDECIMAL,\n        DType.UDOUBLE,\n    }\n\n    TEMPORAL_TYPES: t.ClassVar[t.Set[DType]] = {\n        DType.DATE,\n        DType.DATE32,\n        DType.DATETIME,\n        DType.DATETIME2,\n        DType.DATETIME64,\n        DType.SMALLDATETIME,\n        DType.TIME,\n        DType.TIMESTAMP,\n        DType.TIMESTAMPNTZ,\n        DType.TIMESTAMPLTZ,\n        DType.TIMESTAMPTZ,\n        DType.TIMESTAMP_MS,\n        DType.TIMESTAMP_NS,\n        DType.TIMESTAMP_S,\n        DType.TIMETZ,\n    }\n\n    @classmethod\n    def build(\n        cls,\n        dtype: DATA_TYPE,\n        dialect: DialectType = None,\n        udt: bool = False,\n        copy: bool = True,\n        **kwargs,\n    ) -> DataType:\n        \"\"\"\n        Constructs a DataType object.\n\n        Args:\n            dtype: the data type of interest.\n            dialect: the dialect to use for parsing `dtype`, in case it's a string.\n            udt: when set to True, `dtype` will be used as-is if it can't be parsed into a\n                DataType, thus creating a user-defined type.\n            copy: whether to copy the data type.\n            kwargs: additional arguments to pass in the constructor of DataType.\n\n        Returns:\n            The constructed DataType object.\n        \"\"\"\n        from sqlglot import parse_one\n\n        if isinstance(dtype, str):\n            if dtype.upper() == \"UNKNOWN\":\n                return DataType(this=DType.UNKNOWN, **kwargs)\n\n            try:\n                data_type_exp = parse_one(\n                    dtype, read=dialect, into=DataType, error_level=ErrorLevel.IGNORE\n                )\n            except ParseError:\n                if udt:\n                    return DataType(this=DType.USERDEFINED, kind=dtype, **kwargs)\n                raise\n        elif isinstance(dtype, (Identifier, Dot)) and udt:\n            return DataType(this=DType.USERDEFINED, kind=dtype, **kwargs)\n        elif isinstance(dtype, DType):\n            data_type_exp = DataType(this=dtype)\n        elif isinstance(dtype, DataType):\n            return maybe_copy(dtype, copy)\n        else:\n            raise ValueError(f\"Invalid data type: {type(dtype)}. Expected str or DType\")\n\n        if kwargs:\n            for k, v in kwargs.items():\n                data_type_exp.set(k, v)\n        return data_type_exp\n\n    def is_type(self, *dtypes: DATA_TYPE, check_nullable: bool = False) -> bool:\n        \"\"\"\n        Checks whether this DataType matches one of the provided data types. Nested types or precision\n        will be compared using \"structural equivalence\" semantics, so e.g. array<int> != array<float>.\n\n        Args:\n            dtypes: the data types to compare this DataType to.\n            check_nullable: whether to take the NULLABLE type constructor into account for the comparison.\n                If false, it means that NULLABLE<INT> is equivalent to INT.\n\n        Returns:\n            True, if and only if there is a type in `dtypes` which is equal to this DataType.\n        \"\"\"\n        self_is_nullable = self.args.get(\"nullable\")\n        for dtype in dtypes:\n            other_type = DataType.build(dtype, copy=False, udt=True)\n            other_is_nullable = other_type.args.get(\"nullable\")\n            if (\n                other_type.expressions\n                or (check_nullable and (self_is_nullable or other_is_nullable))\n                or self.this == DType.USERDEFINED\n                or other_type.this == DType.USERDEFINED\n            ):\n                matches = self == other_type\n            else:\n                matches = self.this == other_type.this\n\n            if matches:\n                return True\n        return False\n\n\nclass PseudoType(DataType):\n    arg_types = {\"this\": True}\n\n\nclass ObjectIdentifier(DataType):\n    arg_types = {\"this\": True}\n\n\nclass IntervalSpan(DataType):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Interval(_TimeUnit):\n    arg_types = {\"this\": False, \"unit\": False}\n\n\nDATA_TYPE = t.Union[str, Identifier, Dot, DataType, DType]\n"
  },
  {
    "path": "sqlglot/expressions/ddl.py",
    "content": "\"\"\"sqlglot expressions DDL.\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.helper import trait\nfrom sqlglot.expressions.core import Expression, Expr, Func\nfrom sqlglot.expressions.query import Query, Selectable\n\nif t.TYPE_CHECKING:\n    from sqlglot.expressions.query import CTE\n\n\n@trait\nclass DDL(Selectable):\n    @property\n    def ctes(self) -> t.List[CTE]:\n        \"\"\"Returns a list of all the CTEs attached to this statement.\"\"\"\n        with_ = self.args.get(\"with_\")\n        return with_.expressions if with_ else []\n\n    @property\n    def selects(self) -> t.List[Expr]:\n        \"\"\"If this statement contains a query (e.g. a CTAS), this returns the query's projections.\"\"\"\n        expression = self.expression\n        return expression.selects if isinstance(expression, Query) else []\n\n    @property\n    def named_selects(self) -> t.List[str]:\n        \"\"\"\n        If this statement contains a query (e.g. a CTAS), this returns the output\n        names of the query's projections.\n        \"\"\"\n        expression = self.expression\n        return expression.named_selects if isinstance(expression, Query) else []\n\n\nclass Create(Expression, DDL):\n    arg_types = {\n        \"with_\": False,\n        \"this\": True,\n        \"kind\": True,\n        \"expression\": False,\n        \"exists\": False,\n        \"properties\": False,\n        \"replace\": False,\n        \"refresh\": False,\n        \"unique\": False,\n        \"indexes\": False,\n        \"no_schema_binding\": False,\n        \"begin\": False,\n        \"clone\": False,\n        \"concurrently\": False,\n        \"clustered\": False,\n    }\n\n    @property\n    def kind(self) -> t.Optional[str]:\n        kind = self.args.get(\"kind\")\n        return kind and kind.upper()\n\n\nclass SequenceProperties(Expression):\n    arg_types = {\n        \"increment\": False,\n        \"minvalue\": False,\n        \"maxvalue\": False,\n        \"cache\": False,\n        \"start\": False,\n        \"owned\": False,\n        \"options\": False,\n    }\n\n\nclass TriggerProperties(Expression):\n    arg_types = {\n        \"table\": True,\n        \"timing\": True,\n        \"events\": True,\n        \"execute\": True,\n        \"constraint\": False,\n        \"referenced_table\": False,\n        \"deferrable\": False,\n        \"initially\": False,\n        \"referencing\": False,\n        \"for_each\": False,\n        \"when\": False,\n    }\n\n\nclass TriggerExecute(Expression):\n    pass\n\n\nclass TriggerEvent(Expression):\n    arg_types = {\"this\": True, \"columns\": False}\n\n\nclass TriggerReferencing(Expression):\n    arg_types = {\"old\": False, \"new\": False}\n\n\nclass TruncateTable(Expression):\n    arg_types = {\n        \"expressions\": True,\n        \"is_database\": False,\n        \"exists\": False,\n        \"only\": False,\n        \"cluster\": False,\n        \"identity\": False,\n        \"option\": False,\n        \"partition\": False,\n    }\n\n\nclass Clone(Expression):\n    arg_types = {\"this\": True, \"shallow\": False, \"copy\": False}\n\n\nclass Describe(Expression):\n    arg_types = {\n        \"this\": True,\n        \"style\": False,\n        \"kind\": False,\n        \"properties\": False,\n        \"expressions\": False,\n        \"partition\": False,\n        \"format\": False,\n        \"as_json\": False,\n    }\n\n\nclass Attach(Expression):\n    arg_types = {\"this\": True, \"exists\": False, \"expressions\": False}\n\n\nclass Detach(Expression):\n    arg_types = {\n        \"this\": True,\n        \"kind\": False,\n        \"exists\": False,\n        \"cluster\": False,\n        \"permanent\": False,\n        \"sync\": False,\n    }\n\n\nclass Install(Expression):\n    arg_types = {\"this\": True, \"from_\": False, \"force\": False}\n\n\nclass Summarize(Expression):\n    arg_types = {\"this\": True, \"table\": False}\n\n\nclass Kill(Expression):\n    arg_types = {\"this\": True, \"kind\": False}\n\n\nclass Pragma(Expression):\n    pass\n\n\nclass Declare(Expression):\n    arg_types = {\"expressions\": True, \"replace\": False}\n\n\nclass DeclareItem(Expression):\n    arg_types = {\"this\": True, \"kind\": False, \"default\": False}\n\n\nclass Set(Expression):\n    arg_types = {\"expressions\": False, \"unset\": False, \"tag\": False}\n\n\nclass Heredoc(Expression):\n    arg_types = {\"this\": True, \"tag\": False}\n\n\nclass SetItem(Expression):\n    arg_types = {\n        \"this\": False,\n        \"expressions\": False,\n        \"kind\": False,\n        \"collate\": False,  # MySQL SET NAMES statement\n        \"global_\": False,\n    }\n\n\nclass Show(Expression):\n    arg_types = {\n        \"this\": True,\n        \"history\": False,\n        \"terse\": False,\n        \"target\": False,\n        \"offset\": False,\n        \"starts_with\": False,\n        \"limit\": False,\n        \"from_\": False,\n        \"like\": False,\n        \"where\": False,\n        \"db\": False,\n        \"scope\": False,\n        \"scope_kind\": False,\n        \"full\": False,\n        \"mutex\": False,\n        \"query\": False,\n        \"channel\": False,\n        \"global_\": False,\n        \"log\": False,\n        \"position\": False,\n        \"types\": False,\n        \"privileges\": False,\n        \"for_table\": False,\n        \"for_group\": False,\n        \"for_user\": False,\n        \"for_role\": False,\n        \"into_outfile\": False,\n        \"json\": False,\n    }\n\n\nclass UserDefinedFunction(Expression):\n    arg_types = {\"this\": True, \"expressions\": False, \"wrapped\": False}\n\n\nclass CharacterSet(Expression):\n    arg_types = {\"this\": True, \"default\": False}\n\n\nclass AlterColumn(Expression):\n    arg_types = {\n        \"this\": True,\n        \"dtype\": False,\n        \"collate\": False,\n        \"using\": False,\n        \"default\": False,\n        \"drop\": False,\n        \"comment\": False,\n        \"allow_null\": False,\n        \"visible\": False,\n        \"rename_to\": False,\n    }\n\n\nclass AlterIndex(Expression):\n    arg_types = {\"this\": True, \"visible\": True}\n\n\nclass AlterDistStyle(Expression):\n    pass\n\n\nclass AlterSortKey(Expression):\n    arg_types = {\"this\": False, \"expressions\": False, \"compound\": False}\n\n\nclass AlterSet(Expression):\n    arg_types = {\n        \"expressions\": False,\n        \"option\": False,\n        \"tablespace\": False,\n        \"access_method\": False,\n        \"file_format\": False,\n        \"copy_options\": False,\n        \"tag\": False,\n        \"location\": False,\n        \"serde\": False,\n    }\n\n\nclass RenameColumn(Expression):\n    arg_types = {\"this\": True, \"to\": True, \"exists\": False}\n\n\nclass AlterRename(Expression):\n    pass\n\n\nclass AlterModifySqlSecurity(Expression):\n    arg_types = {\"expressions\": True}\n\n\nclass SwapTable(Expression):\n    pass\n\n\nclass Comment(Expression):\n    arg_types = {\n        \"this\": True,\n        \"kind\": True,\n        \"expression\": True,\n        \"exists\": False,\n        \"materialized\": False,\n    }\n\n\nclass Comprehension(Expression):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"position\": False,\n        \"iterator\": True,\n        \"condition\": False,\n    }\n\n\nclass MergeTreeTTLAction(Expression):\n    arg_types = {\n        \"this\": True,\n        \"delete\": False,\n        \"recompress\": False,\n        \"to_disk\": False,\n        \"to_volume\": False,\n    }\n\n\nclass MergeTreeTTL(Expression):\n    arg_types = {\n        \"expressions\": True,\n        \"where\": False,\n        \"group\": False,\n        \"aggregates\": False,\n    }\n\n\nclass Drop(Expression):\n    arg_types = {\n        \"this\": False,\n        \"kind\": False,\n        \"expressions\": False,\n        \"exists\": False,\n        \"temporary\": False,\n        \"materialized\": False,\n        \"cascade\": False,\n        \"constraints\": False,\n        \"purge\": False,\n        \"cluster\": False,\n        \"concurrently\": False,\n        \"sync\": False,\n    }\n\n    @property\n    def kind(self) -> t.Optional[str]:\n        kind = self.args.get(\"kind\")\n        return kind and kind.upper()\n\n\nclass Command(Expression):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass Transaction(Expression):\n    arg_types = {\"this\": False, \"modes\": False, \"mark\": False}\n\n\nclass Commit(Expression):\n    arg_types = {\"chain\": False, \"this\": False, \"durability\": False}\n\n\nclass Rollback(Expression):\n    arg_types = {\"savepoint\": False, \"this\": False}\n\n\nclass Alter(Expression):\n    arg_types = {\n        \"this\": False,\n        \"kind\": True,\n        \"actions\": True,\n        \"exists\": False,\n        \"only\": False,\n        \"options\": False,\n        \"cluster\": False,\n        \"not_valid\": False,\n        \"check\": False,\n        \"cascade\": False,\n    }\n\n    @property\n    def kind(self) -> t.Optional[str]:\n        kind = self.args.get(\"kind\")\n        return kind and kind.upper()\n\n    @property\n    def actions(self) -> t.List[Expr]:\n        return self.args.get(\"actions\") or []\n\n\nclass AlterSession(Expression):\n    arg_types = {\"expressions\": True, \"unset\": False}\n\n\nclass Use(Expression):\n    arg_types = {\"this\": False, \"expressions\": False, \"kind\": False}\n\n\nclass NextValueFor(Expression, Func):\n    arg_types = {\"this\": True, \"order\": False}\n\n\nclass Execute(Expression):\n    arg_types = {\"this\": True, \"expressions\": False}\n\n    @property\n    def name(self) -> str:\n        return self.this.name\n\n\nclass ExecuteSql(Execute):\n    pass\n"
  },
  {
    "path": "sqlglot/expressions/dml.py",
    "content": "\"\"\"sqlglot expressions DML.\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.helper import trait\nfrom sqlglot.expressions.core import (\n    Expr,\n    Expression,\n    _apply_builder,\n    _apply_list_builder,\n    maybe_copy,\n    _apply_conjunction_builder,\n)\nfrom sqlglot.expressions.ddl import DDL\nfrom sqlglot.expressions.query import (\n    Table,\n    Where,\n    From,\n    _apply_cte_builder,\n)\n\nif t.TYPE_CHECKING:\n    from typing_extensions import Self\n    from sqlglot.dialects.dialect import DialectType\n    from sqlglot.expressions.core import ExpOrStr\n\n\n@trait\nclass DML(Expr):\n    \"\"\"Trait for data manipulation language statements.\"\"\"\n\n    def returning(\n        self,\n        expression: ExpOrStr,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> \"Self\":\n        \"\"\"\n        Set the RETURNING expression. Not supported by all dialects.\n\n        Example:\n            >>> Delete().delete(\"tbl\").returning(\"*\", dialect=\"postgres\").sql()\n            'DELETE FROM tbl RETURNING *'\n\n        Args:\n            expression: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            Delete: the modified expression.\n        \"\"\"\n        return _apply_builder(\n            expression=expression,\n            instance=self,\n            arg=\"returning\",\n            prefix=\"RETURNING\",\n            dialect=dialect,\n            copy=copy,\n            into=Returning,\n            **opts,\n        )\n\n\nclass Delete(Expression, DML):\n    arg_types = {\n        \"with_\": False,\n        \"this\": False,\n        \"using\": False,\n        \"where\": False,\n        \"returning\": False,\n        \"order\": False,\n        \"limit\": False,\n        \"tables\": False,  # Multiple-Table Syntax (MySQL)\n        \"cluster\": False,  # Clickhouse\n    }\n\n    def delete(\n        self,\n        table: ExpOrStr,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Delete:\n        \"\"\"\n        Create a DELETE expression or replace the table on an existing DELETE expression.\n\n        Example:\n            >>> Delete().delete(\"tbl\").sql()\n            'DELETE FROM tbl'\n\n        Args:\n            table: the table from which to delete.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            Delete: the modified expression.\n        \"\"\"\n        return _apply_builder(\n            expression=table,\n            instance=self,\n            arg=\"this\",\n            dialect=dialect,\n            into=Table,\n            copy=copy,\n            **opts,\n        )\n\n    def where(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Delete:\n        \"\"\"\n        Append to or set the WHERE expressions.\n\n        Example:\n            >>> Delete().delete(\"tbl\").where(\"x = 'a' OR x < 'b'\").sql()\n            \"DELETE FROM tbl WHERE x = 'a' OR x < 'b'\"\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n                Multiple expressions are combined with an AND operator.\n            append: if `True`, AND the new expressions to any existing expression.\n                Otherwise, this resets the expression.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            Delete: the modified expression.\n        \"\"\"\n        return _apply_conjunction_builder(\n            *expressions,\n            instance=self,\n            arg=\"where\",\n            append=append,\n            into=Where,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n\nclass Export(Expression):\n    arg_types = {\"this\": True, \"connection\": False, \"options\": True}\n\n\nclass CopyParameter(Expression):\n    arg_types = {\"this\": True, \"expression\": False, \"expressions\": False}\n\n\nclass Copy(Expression, DML):\n    arg_types = {\n        \"this\": True,\n        \"kind\": True,\n        \"files\": False,\n        \"credentials\": False,\n        \"format\": False,\n        \"params\": False,\n    }\n\n\nclass Credentials(Expression):\n    arg_types = {\n        \"credentials\": False,\n        \"encryption\": False,\n        \"storage\": False,\n        \"iam_role\": False,\n        \"region\": False,\n    }\n\n\nclass Directory(Expression):\n    arg_types = {\"this\": True, \"local\": False, \"row_format\": False}\n\n\nclass DirectoryStage(Expression):\n    pass\n\n\nclass Insert(Expression, DDL, DML):\n    arg_types = {\n        \"hint\": False,\n        \"with_\": False,\n        \"is_function\": False,\n        \"this\": False,\n        \"expression\": False,\n        \"conflict\": False,\n        \"returning\": False,\n        \"overwrite\": False,\n        \"exists\": False,\n        \"alternative\": False,\n        \"where\": False,\n        \"ignore\": False,\n        \"by_name\": False,\n        \"stored\": False,\n        \"partition\": False,\n        \"settings\": False,\n        \"source\": False,\n        \"default\": False,\n    }\n\n    def with_(\n        self,\n        alias: ExpOrStr,\n        as_: ExpOrStr,\n        recursive: t.Optional[bool] = None,\n        materialized: t.Optional[bool] = None,\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Insert:\n        \"\"\"\n        Append to or set the common table expressions.\n\n        Example:\n            >>> import sqlglot\n            >>> sqlglot.parse_one(\"INSERT INTO t SELECT x FROM cte\").with_(\"cte\", as_=\"SELECT * FROM tbl\").sql()\n            'WITH cte AS (SELECT * FROM tbl) INSERT INTO t SELECT x FROM cte'\n\n        Args:\n            alias: the SQL code string to parse as the table name.\n                If an `Expr` instance is passed, this is used as-is.\n            as_: the SQL code string to parse as the table expression.\n                If an `Expr` instance is passed, it will be used as-is.\n            recursive: set the RECURSIVE part of the expression. Defaults to `False`.\n            materialized: set the MATERIALIZED part of the expression.\n            append: if `True`, add to any existing expressions.\n                Otherwise, this resets the expressions.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified expression.\n        \"\"\"\n        return _apply_cte_builder(\n            self,\n            alias,\n            as_,\n            recursive=recursive,\n            materialized=materialized,\n            append=append,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n\nclass OnConflict(Expression):\n    arg_types = {\n        \"duplicate\": False,\n        \"expressions\": False,\n        \"action\": False,\n        \"conflict_keys\": False,\n        \"index_predicate\": False,\n        \"constraint\": False,\n        \"where\": False,\n    }\n\n\nclass Returning(Expression):\n    arg_types = {\"expressions\": True, \"into\": False}\n\n\nclass LoadData(Expression):\n    arg_types = {\n        \"this\": True,\n        \"local\": False,\n        \"overwrite\": False,\n        \"inpath\": True,\n        \"partition\": False,\n        \"input_format\": False,\n        \"serde\": False,\n    }\n\n\nclass Update(Expression, DML):\n    arg_types = {\n        \"with_\": False,\n        \"this\": False,\n        \"expressions\": False,\n        \"from_\": False,\n        \"where\": False,\n        \"returning\": False,\n        \"order\": False,\n        \"limit\": False,\n        \"options\": False,\n    }\n\n    def table(\n        self, expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts\n    ) -> Update:\n        \"\"\"\n        Set the table to update.\n\n        Example:\n            >>> Update().table(\"my_table\").set_(\"x = 1\").sql()\n            'UPDATE my_table SET x = 1'\n\n        Args:\n            expression : the SQL code strings to parse.\n                If a `Table` instance is passed, this is used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `Table`.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Update expression.\n        \"\"\"\n        return _apply_builder(\n            expression=expression,\n            instance=self,\n            arg=\"this\",\n            into=Table,\n            prefix=None,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def set_(\n        self,\n        *expressions: ExpOrStr,\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Update:\n        \"\"\"\n        Append to or set the SET expressions.\n\n        Example:\n            >>> Update().table(\"my_table\").set_(\"x = 1\").sql()\n            'UPDATE my_table SET x = 1'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If `Expr` instance(s) are passed, they will be used as-is.\n                Multiple expressions are combined with a comma.\n            append: if `True`, add the new expressions to any existing SET expressions.\n                Otherwise, this resets the expressions.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n        \"\"\"\n        return _apply_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"expressions\",\n            append=append,\n            into=Expr,\n            prefix=None,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def where(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Update:\n        \"\"\"\n        Append to or set the WHERE expressions.\n\n        Example:\n            >>> Update().table(\"tbl\").set_(\"x = 1\").where(\"x = 'a' OR x < 'b'\").sql()\n            \"UPDATE tbl SET x = 1 WHERE x = 'a' OR x < 'b'\"\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n                Multiple expressions are combined with an AND operator.\n            append: if `True`, AND the new expressions to any existing expression.\n                Otherwise, this resets the expression.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            Update: the modified expression.\n        \"\"\"\n        return _apply_conjunction_builder(\n            *expressions,\n            instance=self,\n            arg=\"where\",\n            append=append,\n            into=Where,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def from_(\n        self,\n        expression: t.Optional[ExpOrStr] = None,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Update:\n        \"\"\"\n        Set the FROM expression.\n\n        Example:\n            >>> Update().table(\"my_table\").set_(\"x = 1\").from_(\"baz\").sql()\n            'UPDATE my_table SET x = 1 FROM baz'\n\n        Args:\n            expression : the SQL code strings to parse.\n                If a `From` instance is passed, this is used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `From`.\n                If nothing is passed in then a from is not applied to the expression\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Update expression.\n        \"\"\"\n        if not expression:\n            return maybe_copy(self, copy)\n\n        return _apply_builder(\n            expression=expression,\n            instance=self,\n            arg=\"from_\",\n            into=From,\n            prefix=\"FROM\",\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def with_(\n        self,\n        alias: ExpOrStr,\n        as_: ExpOrStr,\n        recursive: t.Optional[bool] = None,\n        materialized: t.Optional[bool] = None,\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Update:\n        \"\"\"\n        Append to or set the common table expressions.\n\n        Example:\n            >>> Update().table(\"my_table\").set_(\"x = 1\").from_(\"baz\").with_(\"baz\", \"SELECT id FROM foo\").sql()\n            'WITH baz AS (SELECT id FROM foo) UPDATE my_table SET x = 1 FROM baz'\n\n        Args:\n            alias: the SQL code string to parse as the table name.\n                If an `Expr` instance is passed, this is used as-is.\n            as_: the SQL code string to parse as the table expression.\n                If an `Expr` instance is passed, it will be used as-is.\n            recursive: set the RECURSIVE part of the expression. Defaults to `False`.\n            materialized: set the MATERIALIZED part of the expression.\n            append: if `True`, add to any existing expressions.\n                Otherwise, this resets the expressions.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified expression.\n        \"\"\"\n        return _apply_cte_builder(\n            self,\n            alias,\n            as_,\n            recursive=recursive,\n            materialized=materialized,\n            append=append,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n\nclass Merge(Expression, DML):\n    arg_types = {\n        \"this\": True,\n        \"using\": True,\n        \"on\": False,\n        \"using_cond\": False,\n        \"whens\": True,\n        \"with_\": False,\n        \"returning\": False,\n    }\n\n\nclass When(Expression):\n    arg_types = {\"matched\": True, \"source\": False, \"condition\": False, \"then\": True}\n\n\nclass Whens(Expression):\n    \"\"\"Wraps around one or more WHEN [NOT] MATCHED [...] clauses.\"\"\"\n\n    arg_types = {\"expressions\": True}\n"
  },
  {
    "path": "sqlglot/expressions/functions.py",
    "content": "\"\"\"sqlglot expressions functions.\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.expressions.core import (\n    Expression,\n    Func,\n    Binary,\n    SubqueryPredicate,\n    ExpOrStr,\n    maybe_parse,\n    maybe_copy,\n)\n\n# Re-export from focused submodules (backward compatibility)\nfrom sqlglot.expressions.math import *  # noqa: F401,F403\nfrom sqlglot.expressions.string import *  # noqa: F401,F403\nfrom sqlglot.expressions.temporal import *  # noqa: F401,F403\nfrom sqlglot.expressions.aggregate import *  # noqa: F401,F403\nfrom sqlglot.expressions.array import *  # noqa: F401,F403\nfrom sqlglot.expressions.json import *  # noqa: F401,F403\n\nif t.TYPE_CHECKING:\n    from sqlglot.expressions.datatypes import DataType, DATA_TYPE\n\n\n# Cast / type conversion\n\n\nclass Cast(Expression, Func):\n    is_cast: t.ClassVar[bool] = True\n    arg_types = {\n        \"this\": True,\n        \"to\": True,\n        \"format\": False,\n        \"safe\": False,\n        \"action\": False,\n        \"default\": False,\n    }\n\n    @property\n    def name(self) -> str:\n        return self.this.name\n\n    @property\n    def to(self) -> DataType:\n        return self.args[\"to\"]\n\n    @property\n    def output_name(self) -> str:\n        return self.name\n\n    def is_type(self, *dtypes: DATA_TYPE) -> bool:\n        \"\"\"\n        Checks whether this Cast's DataType matches one of the provided data types. Nested types\n        like arrays or structs will be compared using \"structural equivalence\" semantics, so e.g.\n        array<int> != array<float>.\n\n        Args:\n            dtypes: the data types to compare this Cast's DataType to.\n\n        Returns:\n            True, if and only if there is a type in `dtypes` which is equal to this Cast's DataType.\n        \"\"\"\n        return self.to.is_type(*dtypes)\n\n\nclass TryCast(Cast):\n    arg_types = {**Cast.arg_types, \"requires_string\": False}\n\n\nclass JSONCast(Cast):\n    pass\n\n\nclass CastToStrType(Expression, Func):\n    arg_types = {\"this\": True, \"to\": True}\n\n\nclass Convert(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"style\": False, \"safe\": False}\n\n\n# Conditional\n\n\nclass If(Expression, Func):\n    arg_types = {\"this\": True, \"true\": True, \"false\": False}\n    _sql_names = [\"IF\", \"IIF\"]\n\n\nclass Case(Expression, Func):\n    arg_types = {\"this\": False, \"ifs\": True, \"default\": False}\n\n    def when(self, condition: ExpOrStr, then: ExpOrStr, copy: bool = True, **opts) -> Case:\n        instance = maybe_copy(self, copy)\n        instance.append(\n            \"ifs\",\n            If(\n                this=maybe_parse(condition, copy=copy, **opts),\n                true=maybe_parse(then, copy=copy, **opts),\n            ),\n        )\n        return instance\n\n    def else_(self, condition: ExpOrStr, copy: bool = True, **opts) -> Case:\n        instance = maybe_copy(self, copy)\n        instance.set(\"default\", maybe_parse(condition, copy=copy, **opts))\n        return instance\n\n\nclass Coalesce(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": False, \"is_nvl\": False, \"is_null\": False}\n    is_var_len_args = True\n    _sql_names = [\"COALESCE\", \"IFNULL\", \"NVL\"]\n\n\nclass DecodeCase(Expression, Func):\n    arg_types = {\"expressions\": True}\n    is_var_len_args = True\n\n\nclass EqualNull(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Greatest(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": False, \"ignore_nulls\": True}\n    is_var_len_args = True\n\n\nclass Least(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": False, \"ignore_nulls\": True}\n    is_var_len_args = True\n\n\nclass Nullif(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Nvl2(Expression, Func):\n    arg_types = {\"this\": True, \"true\": True, \"false\": False}\n\n\nclass Try(Expression, Func):\n    pass\n\n\n# Predicates / misc functions\n\n\nclass Collate(Expression, Binary, Func):\n    pass\n\n\nclass Collation(Expression, Func):\n    pass\n\n\nclass ConnectByRoot(Expression, Func):\n    pass\n\n\nclass CheckXml(Expression, Func):\n    arg_types = {\"this\": True, \"disable_auto_convert\": False}\n\n\nclass Exists(Expression, Func, SubqueryPredicate):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\n# Type coercions / lax types\n\n\nclass Float64(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass Int64(Expression, Func):\n    pass\n\n\nclass IsArray(Expression, Func):\n    pass\n\n\nclass IsNullValue(Expression, Func):\n    pass\n\n\nclass LaxBool(Expression, Func):\n    pass\n\n\nclass LaxFloat64(Expression, Func):\n    pass\n\n\nclass LaxInt64(Expression, Func):\n    pass\n\n\nclass LaxString(Expression, Func):\n    pass\n\n\nclass ToBoolean(Expression, Func):\n    arg_types = {\"this\": True, \"safe\": False}\n\n\nclass ToVariant(Expression, Func):\n    pass\n\n\n# Session / context functions\n\n\nclass CurrentAccount(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentAccountName(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentAvailableRoles(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentCatalog(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentClient(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentDatabase(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentIpAddress(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentOrganizationName(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentOrganizationUser(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentRegion(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentRole(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentRoleType(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentSchema(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass CurrentSchemas(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass CurrentSecondaryRoles(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentSession(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentStatement(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentTransaction(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentUser(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass CurrentVersion(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentWarehouse(Expression, Func):\n    arg_types = {}\n\n\nclass SessionUser(Expression, Func):\n    arg_types = {}\n\n\n# ML / AI\n\n\nclass AIClassify(Expression, Func):\n    arg_types = {\"this\": True, \"categories\": True, \"config\": False}\n    _sql_names = [\"AI_CLASSIFY\"]\n\n\nclass FeaturesAtTime(Expression, Func):\n    arg_types = {\"this\": True, \"time\": False, \"num_rows\": False, \"ignore_feature_nulls\": False}\n\n\nclass GenerateEmbedding(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"params_struct\": False, \"is_text\": False}\n\n\nclass MLForecast(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False, \"params_struct\": False}\n\n\nclass MLTranslate(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"params_struct\": True}\n\n\nclass Predict(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"params_struct\": False}\n\n\nclass VectorSearch(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"column_to_search\": True,\n        \"query_table\": True,\n        \"query_column_to_search\": False,\n        \"top_k\": False,\n        \"distance_type\": False,\n        \"options\": False,\n    }\n\n\n# Data reading\n\n\nclass ReadCSV(Expression, Func):\n    _sql_names = [\"READ_CSV\"]\n    is_var_len_args = True\n    arg_types = {\"this\": True, \"expressions\": False}\n\n\nclass ReadParquet(Expression, Func):\n    is_var_len_args = True\n    arg_types = {\"expressions\": True}\n\n\n# XML\n\n\nclass XMLElement(Expression, Func):\n    _sql_names = [\"XMLELEMENT\"]\n    arg_types = {\"this\": True, \"expressions\": False, \"evalname\": False}\n\n\nclass XMLGet(Expression, Func):\n    _sql_names = [\"XMLGET\"]\n    arg_types = {\"this\": True, \"expression\": True, \"instance\": False}\n\n\nclass XMLTable(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"namespaces\": False,\n        \"passing\": False,\n        \"columns\": False,\n        \"by_ref\": False,\n    }\n\n\n# Network / domain\n\n\nclass Host(Expression, Func):\n    pass\n\n\nclass NetFunc(Expression, Func):\n    pass\n\n\nclass ParseIp(Expression, Func):\n    arg_types = {\"this\": True, \"type\": True, \"permissive\": False}\n\n\nclass RegDomain(Expression, Func):\n    pass\n\n\n# Misc utility\n\n\nclass Columns(Expression, Func):\n    arg_types = {\"this\": True, \"unpack\": False}\n\n\nclass Normal(Expression, Func):\n    arg_types = {\"this\": True, \"stddev\": True, \"gen\": True}\n\n\nclass Rand(Expression, Func):\n    _sql_names = [\"RAND\", \"RANDOM\"]\n    arg_types = {\"this\": False, \"lower\": False, \"upper\": False}\n\n\nclass Randn(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass Randstr(Expression, Func):\n    arg_types = {\"this\": True, \"generator\": False}\n\n\nclass RangeBucket(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RangeN(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True, \"each\": False}\n\n\nclass Seq1(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass Seq2(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass Seq4(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass Seq8(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass Uniform(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"gen\": False, \"seed\": False}\n\n\nclass Uuid(Expression, Func):\n    _sql_names = [\"UUID\", \"GEN_RANDOM_UUID\", \"GENERATE_UUID\", \"UUID_STRING\"]\n\n    arg_types = {\"this\": False, \"name\": False, \"is_string\": False}\n\n\nclass WeekStart(Expression, Func):\n    pass\n\n\nclass WidthBucket(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"min_value\": False,\n        \"max_value\": False,\n        \"num_buckets\": False,\n        \"threshold\": False,\n    }\n\n\nclass Zipf(Expression, Func):\n    arg_types = {\"this\": True, \"elementcount\": True, \"gen\": True}\n"
  },
  {
    "path": "sqlglot/expressions/json.py",
    "content": "\"\"\"sqlglot expressions - JSON functions.\"\"\"\n\nfrom __future__ import annotations\n\nfrom sqlglot.expressions.core import Expression, Func, AggFunc, Binary, Predicate\n\n\nclass CheckJson(Expression, Func):\n    arg_types = {\"this\": True}\n\n\nclass JSONArray(Expression, Func):\n    arg_types = {\n        \"expressions\": False,\n        \"null_handling\": False,\n        \"return_type\": False,\n        \"strict\": False,\n    }\n\n\nclass JSONArrayAgg(Expression, AggFunc):\n    arg_types = {\n        \"this\": True,\n        \"order\": False,\n        \"null_handling\": False,\n        \"return_type\": False,\n        \"strict\": False,\n    }\n\n\nclass JSONArrayAppend(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True}\n    is_var_len_args = True\n    _sql_names = [\"JSON_ARRAY_APPEND\"]\n\n\nclass JSONArrayContains(Expression, Binary, Predicate, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"json_type\": False}\n    _sql_names = [\"JSON_ARRAY_CONTAINS\"]\n\n\nclass JSONArrayInsert(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True}\n    is_var_len_args = True\n    _sql_names = [\"JSON_ARRAY_INSERT\"]\n\n\nclass JSONBContains(Expression, Binary, Func):\n    _sql_names = [\"JSONB_CONTAINS\"]\n\n\nclass JSONBContainsAllTopKeys(Expression, Binary, Func):\n    pass\n\n\nclass JSONBContainsAnyTopKeys(Expression, Binary, Func):\n    pass\n\n\nclass JSONBDeleteAtPath(Expression, Binary, Func):\n    pass\n\n\nclass JSONBExists(Expression, Func):\n    arg_types = {\"this\": True, \"path\": True}\n    _sql_names = [\"JSONB_EXISTS\"]\n\n\nclass JSONBExtract(Expression, Binary, Func):\n    _sql_names = [\"JSONB_EXTRACT\"]\n\n\nclass JSONBExtractScalar(Expression, Binary, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"json_type\": False}\n    _sql_names = [\"JSONB_EXTRACT_SCALAR\"]\n\n\nclass JSONBObjectAgg(Expression, AggFunc):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass JSONBool(Expression, Func):\n    pass\n\n\nclass JSONExists(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"path\": True,\n        \"passing\": False,\n        \"on_condition\": False,\n        \"from_dcolonqmark\": False,\n    }\n\n\nclass JSONExtract(Expression, Binary, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"only_json_types\": False,\n        \"expressions\": False,\n        \"variant_extract\": False,\n        \"json_query\": False,\n        \"option\": False,\n        \"quote\": False,\n        \"on_condition\": False,\n        \"requires_json\": False,\n        \"emits\": False,\n    }\n    _sql_names = [\"JSON_EXTRACT\"]\n    is_var_len_args = True\n\n    @property\n    def output_name(self) -> str:\n        return self.expression.output_name if not self.expressions else \"\"\n\n\nclass JSONExtractArray(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n    _sql_names = [\"JSON_EXTRACT_ARRAY\"]\n\n\nclass JSONExtractScalar(Expression, Binary, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"only_json_types\": False,\n        \"expressions\": False,\n        \"json_type\": False,\n        \"scalar_only\": False,\n    }\n    _sql_names = [\"JSON_EXTRACT_SCALAR\"]\n    is_var_len_args = True\n\n    @property\n    def output_name(self) -> str:\n        return self.expression.output_name\n\n\nclass JSONFormat(Expression, Func):\n    arg_types = {\"this\": False, \"options\": False, \"is_json\": False, \"to_json\": False}\n    _sql_names = [\"JSON_FORMAT\"]\n\n\nclass JSONKeys(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False, \"expressions\": False}\n    is_var_len_args = True\n    _sql_names = [\"JSON_KEYS\"]\n\n\nclass JSONKeysAtDepth(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False, \"mode\": False}\n\n\nclass JSONObject(Expression, Func):\n    arg_types = {\n        \"expressions\": False,\n        \"null_handling\": False,\n        \"unique_keys\": False,\n        \"return_type\": False,\n        \"encoding\": False,\n    }\n\n\nclass JSONObjectAgg(Expression, AggFunc):\n    arg_types = {\n        \"expressions\": False,\n        \"null_handling\": False,\n        \"unique_keys\": False,\n        \"return_type\": False,\n        \"encoding\": False,\n    }\n\n\nclass JSONRemove(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True}\n    is_var_len_args = True\n    _sql_names = [\"JSON_REMOVE\"]\n\n\nclass JSONSet(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True}\n    is_var_len_args = True\n    _sql_names = [\"JSON_SET\"]\n\n\nclass JSONStripNulls(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": False,\n        \"include_arrays\": False,\n        \"remove_empty\": False,\n    }\n    _sql_names = [\"JSON_STRIP_NULLS\"]\n\n\nclass JSONTable(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"schema\": True,\n        \"path\": False,\n        \"error_handling\": False,\n        \"empty_handling\": False,\n    }\n\n\nclass JSONType(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n    _sql_names = [\"JSON_TYPE\"]\n\n\nclass ObjectId(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass ObjectInsert(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"key\": True,\n        \"value\": True,\n        \"update_flag\": False,\n    }\n\n\nclass OpenJSON(Expression, Func):\n    arg_types = {\"this\": True, \"path\": False, \"expressions\": False}\n\n\nclass ParseJSON(Expression, Func):\n    # BigQuery, Snowflake have PARSE_JSON, Presto has JSON_PARSE\n    # Snowflake also has TRY_PARSE_JSON, which is represented using `safe`\n    _sql_names = [\"PARSE_JSON\", \"JSON_PARSE\"]\n    arg_types = {\"this\": True, \"expression\": False, \"safe\": False}\n"
  },
  {
    "path": "sqlglot/expressions/math.py",
    "content": "\"\"\"sqlglot expressions - math, trigonometry, and bitwise functions.\"\"\"\n\nfrom __future__ import annotations\n\nfrom sqlglot.expressions.core import Expression, Func, AggFunc\n\n\n# Trigonometric\n\n\nclass Acos(Expression, Func):\n    pass\n\n\nclass Acosh(Expression, Func):\n    pass\n\n\nclass Asin(Expression, Func):\n    pass\n\n\nclass Asinh(Expression, Func):\n    pass\n\n\nclass Atan(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass Atanh(Expression, Func):\n    pass\n\n\nclass Atan2(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Cos(Expression, Func):\n    pass\n\n\nclass Cosh(Expression, Func):\n    pass\n\n\nclass Cot(Expression, Func):\n    pass\n\n\nclass Coth(Expression, Func):\n    pass\n\n\nclass Csc(Expression, Func):\n    pass\n\n\nclass Csch(Expression, Func):\n    pass\n\n\nclass Degrees(Expression, Func):\n    pass\n\n\nclass Radians(Expression, Func):\n    pass\n\n\nclass Sec(Expression, Func):\n    pass\n\n\nclass Sech(Expression, Func):\n    pass\n\n\nclass Sin(Expression, Func):\n    pass\n\n\nclass Sinh(Expression, Func):\n    pass\n\n\nclass Tan(Expression, Func):\n    pass\n\n\nclass Tanh(Expression, Func):\n    pass\n\n\n# Geometric distance / similarity\n\n\nclass CosineDistance(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass DotProduct(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass EuclideanDistance(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass JarowinklerSimilarity(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"case_insensitive\": False}\n\n\nclass ManhattanDistance(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\n# Basic arithmetic / math\n\n\nclass Abs(Expression, Func):\n    pass\n\n\nclass Cbrt(Expression, Func):\n    pass\n\n\nclass Ceil(Expression, Func):\n    arg_types = {\"this\": True, \"decimals\": False, \"to\": False}\n    _sql_names = [\"CEIL\", \"CEILING\"]\n\n\nclass Exp(Expression, Func):\n    pass\n\n\nclass Factorial(Expression, Func):\n    pass\n\n\nclass Floor(Expression, Func):\n    arg_types = {\"this\": True, \"decimals\": False, \"to\": False}\n\n\nclass IsInf(Expression, Func):\n    _sql_names = [\"IS_INF\", \"ISINF\"]\n\n\nclass IsNan(Expression, Func):\n    _sql_names = [\"IS_NAN\", \"ISNAN\"]\n\n\nclass Ln(Expression, Func):\n    pass\n\n\nclass Log(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass Pi(Expression, Func):\n    arg_types = {}\n\n\nclass Round(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"decimals\": False,\n        \"truncate\": False,\n        \"casts_non_integer_decimals\": False,\n    }\n\n\nclass Sign(Expression, Func):\n    _sql_names = [\"SIGN\", \"SIGNUM\"]\n\n\nclass Sqrt(Expression, Func):\n    pass\n\n\nclass Trunc(Expression, Func):\n    arg_types = {\"this\": True, \"decimals\": False}\n    _sql_names = [\"TRUNC\", \"TRUNCATE\"]\n\n\n# Safe arithmetic\n\n\nclass SafeAdd(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass SafeDivide(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass SafeMultiply(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass SafeNegate(Expression, Func):\n    pass\n\n\nclass SafeSubtract(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\n# Bitwise\n\n\nclass BitwiseAndAgg(Expression, AggFunc):\n    pass\n\n\nclass BitwiseCount(Expression, Func):\n    pass\n\n\nclass BitwiseOrAgg(Expression, AggFunc):\n    pass\n\n\nclass BitwiseXorAgg(Expression, AggFunc):\n    pass\n\n\nclass BitmapBitPosition(Expression, Func):\n    pass\n\n\nclass BitmapBucketNumber(Expression, Func):\n    pass\n\n\nclass BitmapConstructAgg(Expression, AggFunc):\n    pass\n\n\nclass BitmapCount(Expression, Func):\n    pass\n\n\nclass BitmapOrAgg(Expression, AggFunc):\n    pass\n\n\nclass Booland(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"round_input\": False}\n\n\nclass Boolnot(Expression, Func):\n    arg_types = {\"this\": True, \"round_input\": False}\n\n\nclass Boolor(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"round_input\": False}\n\n\nclass BoolxorAgg(Expression, AggFunc):\n    pass\n\n\nclass Getbit(Expression, Func):\n    _sql_names = [\"GETBIT\", \"GET_BIT\"]\n    # zero_is_msb means the most significant bit is indexed 0\n    arg_types = {\"this\": True, \"expression\": True, \"zero_is_msb\": False}\n"
  },
  {
    "path": "sqlglot/expressions/properties.py",
    "content": "\"\"\"sqlglot expressions properties.\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\nfrom enum import auto\n\nfrom sqlglot.helper import AutoName\nfrom sqlglot.expressions.core import Expression, ColumnConstraintKind, Literal, convert\n\n\nclass Property(Expression):\n    arg_types = {\"this\": True, \"value\": True}\n\n\nclass GrantPrivilege(Expression):\n    arg_types = {\"this\": True, \"expressions\": False}\n\n\nclass GrantPrincipal(Expression):\n    arg_types = {\"this\": True, \"kind\": False}\n\n\nclass AllowedValuesProperty(Expression):\n    arg_types = {\"expressions\": True}\n\n\nclass AlgorithmProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass ApiProperty(Property):\n    arg_types = {}\n\n\nclass ApplicationProperty(Property):\n    arg_types = {}\n\n\nclass AutoIncrementProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass AutoRefreshProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass BackupProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass BuildProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass BlockCompressionProperty(Property):\n    arg_types = {\n        \"autotemp\": False,\n        \"always\": False,\n        \"default\": False,\n        \"manual\": False,\n        \"never\": False,\n    }\n\n\nclass CatalogProperty(Property):\n    arg_types = {}\n\n\nclass CharacterSetProperty(Property):\n    arg_types = {\"this\": True, \"default\": True}\n\n\nclass ChecksumProperty(Property):\n    arg_types = {\"on\": False, \"default\": False}\n\n\nclass CollateProperty(Property):\n    arg_types = {\"this\": True, \"default\": False}\n\n\nclass ComputeProperty(Property):\n    arg_types = {}\n\n\nclass CopyGrantsProperty(Property):\n    arg_types = {}\n\n\nclass DataBlocksizeProperty(Property):\n    arg_types = {\n        \"size\": False,\n        \"units\": False,\n        \"minimum\": False,\n        \"maximum\": False,\n        \"default\": False,\n    }\n\n\nclass DataDeletionProperty(Property):\n    arg_types = {\"on\": True, \"filter_column\": False, \"retention_period\": False}\n\n\nclass DatabaseProperty(Property):\n    arg_types = {}\n\n\nclass DefinerProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass DistKeyProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass DistributedByProperty(Property):\n    arg_types = {\"expressions\": False, \"kind\": True, \"buckets\": False, \"order\": False}\n\n\nclass DistStyleProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass DuplicateKeyProperty(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass EngineProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass HeapProperty(Property):\n    arg_types = {}\n\n\nclass HybridProperty(Property):\n    arg_types = {}\n\n\nclass HandlerProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass ParameterStyleProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass ToTableProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass ExecuteAsProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass ExternalProperty(Property):\n    arg_types = {\"this\": False}\n\n\nclass FallbackProperty(Property):\n    arg_types = {\"no\": True, \"protection\": False}\n\n\nclass FileFormatProperty(Property):\n    arg_types = {\"this\": False, \"expressions\": False, \"hive_format\": False}\n\n\nclass CredentialsProperty(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass FreespaceProperty(Property):\n    arg_types = {\"this\": True, \"percent\": False}\n\n\nclass GlobalProperty(Property):\n    arg_types = {}\n\n\nclass IcebergProperty(Property):\n    arg_types = {}\n\n\nclass InheritsProperty(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass InputModelProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass OutputModelProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass IsolatedLoadingProperty(Property):\n    arg_types = {\"no\": False, \"concurrent\": False, \"target\": False}\n\n\nclass JournalProperty(Property):\n    arg_types = {\n        \"no\": False,\n        \"dual\": False,\n        \"before\": False,\n        \"local\": False,\n        \"after\": False,\n    }\n\n\nclass LanguageProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass EnviromentProperty(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass ClusteredByProperty(Property):\n    arg_types = {\"expressions\": True, \"sorted_by\": False, \"buckets\": True}\n\n\nclass DictProperty(Property):\n    arg_types = {\"this\": True, \"kind\": True, \"settings\": False}\n\n\nclass DictSubProperty(Property):\n    pass\n\n\nclass DictRange(Property):\n    arg_types = {\"this\": True, \"min\": True, \"max\": True}\n\n\nclass DynamicProperty(Property):\n    arg_types = {}\n\n\nclass OnCluster(Property):\n    arg_types = {\"this\": True}\n\n\nclass EmptyProperty(Property):\n    arg_types = {}\n\n\nclass LikeProperty(Property):\n    arg_types = {\"this\": True, \"expressions\": False}\n\n\nclass LocationProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass LockProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass LockingProperty(Property):\n    arg_types = {\n        \"this\": False,\n        \"kind\": True,\n        \"for_or_in\": False,\n        \"lock_type\": True,\n        \"override\": False,\n    }\n\n\nclass LogProperty(Property):\n    arg_types = {\"no\": True}\n\n\nclass MaskingProperty(Property):\n    arg_types = {}\n\n\nclass MaterializedProperty(Property):\n    arg_types = {\"this\": False}\n\n\nclass MergeBlockRatioProperty(Property):\n    arg_types = {\"this\": False, \"no\": False, \"default\": False, \"percent\": False}\n\n\nclass NetworkProperty(Property):\n    arg_types = {}\n\n\nclass NoPrimaryIndexProperty(Property):\n    arg_types = {}\n\n\nclass OnProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass OnCommitProperty(Property):\n    arg_types = {\"delete\": False}\n\n\nclass PartitionedByProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass PartitionedByBucket(Property):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass PartitionByTruncate(Property):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass PartitionByRangeProperty(Property):\n    arg_types = {\"partition_expressions\": True, \"create_expressions\": True}\n\n\nclass PartitionByRangePropertyDynamic(Expression):\n    arg_types = {\"this\": False, \"start\": True, \"end\": True, \"every\": True}\n\n\nclass RollupProperty(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass RollupIndex(Expression):\n    arg_types = {\"this\": True, \"expressions\": True, \"from_index\": False, \"properties\": False}\n\n\nclass RowAccessProperty(Property):\n    arg_types = {}\n\n\nclass PartitionByListProperty(Property):\n    arg_types = {\"partition_expressions\": True, \"create_expressions\": True}\n\n\nclass PartitionList(Expression):\n    arg_types = {\"this\": True, \"expressions\": True}\n\n\nclass RefreshTriggerProperty(Property):\n    arg_types = {\n        \"method\": False,\n        \"kind\": False,\n        \"every\": False,\n        \"unit\": False,\n        \"starts\": False,\n    }\n\n\nclass UniqueKeyProperty(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass PartitionBoundSpec(Expression):\n    # this -> IN / MODULUS, expression -> REMAINDER, from_expressions -> FROM (...), to_expressions -> TO (...)\n    arg_types = {\n        \"this\": False,\n        \"expression\": False,\n        \"from_expressions\": False,\n        \"to_expressions\": False,\n    }\n\n\nclass PartitionedOfProperty(Property):\n    # this -> parent_table (schema), expression -> FOR VALUES ... / DEFAULT\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass StreamingTableProperty(Property):\n    arg_types = {}\n\n\nclass RemoteWithConnectionModelProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass ReturnsProperty(Property):\n    arg_types = {\"this\": False, \"is_table\": False, \"table\": False, \"null\": False}\n\n\nclass StrictProperty(Property):\n    arg_types = {}\n\n\nclass RowFormatProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass RowFormatDelimitedProperty(Property):\n    # https://cwiki.apache.org/confluence/display/hive/languagemanual+dml\n    arg_types = {\n        \"fields\": False,\n        \"escaped\": False,\n        \"collection_items\": False,\n        \"map_keys\": False,\n        \"lines\": False,\n        \"null\": False,\n        \"serde\": False,\n    }\n\n\nclass RowFormatSerdeProperty(Property):\n    arg_types = {\"this\": True, \"serde_properties\": False}\n\n\nclass QueryTransform(Expression):\n    arg_types = {\n        \"expressions\": True,\n        \"command_script\": True,\n        \"schema\": False,\n        \"row_format_before\": False,\n        \"record_writer\": False,\n        \"row_format_after\": False,\n        \"record_reader\": False,\n    }\n\n\nclass SampleProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass SchemaCommentProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass SemanticView(Expression):\n    arg_types = {\n        \"this\": True,\n        \"metrics\": False,\n        \"dimensions\": False,\n        \"facts\": False,\n        \"where\": False,\n    }\n\n\nclass SerdeProperties(Property):\n    arg_types = {\"expressions\": True, \"with_\": False}\n\n\nclass SetProperty(Property):\n    arg_types = {\"multi\": True}\n\n\nclass SharingProperty(Property):\n    arg_types = {\"this\": False}\n\n\nclass SetConfigProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass SettingsProperty(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass SortKeyProperty(Property):\n    arg_types = {\"this\": True, \"compound\": False}\n\n\nclass SqlReadWriteProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass SqlSecurityProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass StabilityProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass StorageHandlerProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass TemporaryProperty(Property):\n    arg_types = {\"this\": False}\n\n\nclass SecureProperty(Property):\n    arg_types = {}\n\n\nclass SecurityIntegrationProperty(Property):\n    arg_types = {}\n\n\nclass Tags(Property, ColumnConstraintKind):\n    arg_types = {\"expressions\": True}\n\n\nclass PropertiesLocation(AutoName):\n    POST_CREATE = auto()\n    POST_NAME = auto()\n    POST_SCHEMA = auto()\n    POST_WITH = auto()\n    POST_ALIAS = auto()\n    POST_EXPRESSION = auto()\n    POST_INDEX = auto()\n    UNSUPPORTED = auto()\n\n\nclass TransformModelProperty(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass TransientProperty(Property):\n    arg_types = {\"this\": False}\n\n\nclass UnloggedProperty(Property):\n    arg_types = {}\n\n\nclass UsingTemplateProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass ViewAttributeProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass VolatileProperty(Property):\n    arg_types = {\"this\": False}\n\n\nclass WithDataProperty(Property):\n    arg_types = {\"no\": True, \"statistics\": False}\n\n\nclass WithJournalTableProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass WithSchemaBindingProperty(Property):\n    arg_types = {\"this\": True}\n\n\nclass WithSystemVersioningProperty(Property):\n    arg_types = {\n        \"on\": False,\n        \"this\": False,\n        \"data_consistency\": False,\n        \"retention_period\": False,\n        \"with_\": True,\n    }\n\n\nclass WithProcedureOptions(Property):\n    arg_types = {\"expressions\": True}\n\n\nclass EncodeProperty(Property):\n    arg_types = {\"this\": True, \"properties\": False, \"key\": False}\n\n\nclass IncludeProperty(Property):\n    arg_types = {\"this\": True, \"alias\": False, \"column_def\": False}\n\n\nclass ForceProperty(Property):\n    arg_types = {}\n\n\nclass Properties(Expression):\n    arg_types = {\"expressions\": True}\n\n    NAME_TO_PROPERTY: t.ClassVar[t.Dict[str, t.Type[Property]]] = {\n        \"ALGORITHM\": AlgorithmProperty,\n        \"AUTO_INCREMENT\": AutoIncrementProperty,\n        \"CHARACTER SET\": CharacterSetProperty,\n        \"CLUSTERED_BY\": ClusteredByProperty,\n        \"COLLATE\": CollateProperty,\n        \"COMMENT\": SchemaCommentProperty,\n        \"CREDENTIALS\": CredentialsProperty,\n        \"DEFINER\": DefinerProperty,\n        \"DISTKEY\": DistKeyProperty,\n        \"DISTRIBUTED_BY\": DistributedByProperty,\n        \"DISTSTYLE\": DistStyleProperty,\n        \"ENGINE\": EngineProperty,\n        \"EXECUTE AS\": ExecuteAsProperty,\n        \"FORMAT\": FileFormatProperty,\n        \"LANGUAGE\": LanguageProperty,\n        \"LOCATION\": LocationProperty,\n        \"LOCK\": LockProperty,\n        \"PARTITIONED_BY\": PartitionedByProperty,\n        \"RETURNS\": ReturnsProperty,\n        \"ROW_FORMAT\": RowFormatProperty,\n        \"SORTKEY\": SortKeyProperty,\n        \"ENCODE\": EncodeProperty,\n        \"INCLUDE\": IncludeProperty,\n    }\n\n    PROPERTY_TO_NAME: t.ClassVar[t.Dict[t.Type[Property], str]] = {}\n\n    # CREATE property locations\n    # Form: schema specified\n    #   create [POST_CREATE]\n    #     table a [POST_NAME]\n    #     (b int) [POST_SCHEMA]\n    #     with ([POST_WITH])\n    #     index (b) [POST_INDEX]\n    #\n    # Form: alias selection\n    #   create [POST_CREATE]\n    #     table a [POST_NAME]\n    #     as [POST_ALIAS] (select * from b) [POST_EXPRESSION]\n    #     index (c) [POST_INDEX]\n    Location: t.ClassVar[t.Type[PropertiesLocation]] = PropertiesLocation\n\n    @classmethod\n    def from_dict(cls, properties_dict: t.Dict) -> Properties:\n        expressions = []\n        for key, value in properties_dict.items():\n            property_cls = cls.NAME_TO_PROPERTY.get(key.upper())\n            if property_cls:\n                expressions.append(property_cls(this=convert(value)))\n            else:\n                expressions.append(Property(this=Literal.string(key), value=convert(value)))\n\n        return cls(expressions=expressions)\n\n\n# TODO (mypyc)\nProperties.PROPERTY_TO_NAME = {v: k for k, v in Properties.NAME_TO_PROPERTY.items()}\n"
  },
  {
    "path": "sqlglot/expressions/query.py",
    "content": "\"\"\"sqlglot expressions query.\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot._typing import E\nfrom sqlglot.errors import ParseError\nfrom sqlglot.helper import trait, ensure_list\nfrom sqlglot.expressions.core import (\n    Aliases,\n    Condition,\n    Distinct,\n    Dot,\n    Expr,\n    Expression,\n    Func,\n    Hint,\n    Identifier,\n    In,\n    _apply_builder,\n    _apply_child_list_builder,\n    _apply_list_builder,\n    _apply_conjunction_builder,\n    _apply_set_operation,\n    ExpOrStr,\n    QUERY_MODIFIERS,\n    maybe_parse,\n    maybe_copy,\n    to_identifier,\n    convert,\n    and_,\n    alias_,\n    column,\n)\n\nif t.TYPE_CHECKING:\n    from collections.abc import Collection\n    from sqlglot.dialects.dialect import DialectType\n    from sqlglot.expressions.datatypes import DataType\n    from sqlglot.expressions.constraints import ColumnConstraint\n    from sqlglot.expressions.ddl import Create\n    from sqlglot.expressions.array import Unnest\n\n    S = t.TypeVar(\"S\", bound=\"SetOperation\")\n    Q = t.TypeVar(\"Q\", bound=\"Query\")\n\n\ndef _apply_cte_builder(\n    instance: E,\n    alias: ExpOrStr,\n    as_: ExpOrStr,\n    recursive: t.Optional[bool] = None,\n    materialized: t.Optional[bool] = None,\n    append: bool = True,\n    dialect: DialectType = None,\n    copy: bool = True,\n    scalar: t.Optional[bool] = None,\n    **opts,\n) -> E:\n    alias_expression = maybe_parse(alias, dialect=dialect, into=TableAlias, **opts)\n    as_expression = maybe_parse(as_, dialect=dialect, copy=copy, **opts)\n    if scalar and not isinstance(as_expression, Subquery):\n        # scalar CTE must be wrapped in a subquery\n        as_expression = Subquery(this=as_expression)\n    cte = CTE(this=as_expression, alias=alias_expression, materialized=materialized, scalar=scalar)\n    return _apply_child_list_builder(\n        cte,\n        instance=instance,\n        arg=\"with_\",\n        append=append,\n        copy=copy,\n        into=With,\n        properties={\"recursive\": recursive} if recursive else {},\n    )\n\n\n@trait\nclass Selectable(Expr):\n    @property\n    def selects(self) -> t.List[Expr]:\n        raise NotImplementedError(\"Subclasses must implement selects\")\n\n    @property\n    def named_selects(self) -> t.List[str]:\n        return _named_selects(self)\n\n\ndef _named_selects(self: Expr) -> t.List[str]:\n    selectable = t.cast(Selectable, self)\n    return [select.output_name for select in selectable.selects]\n\n\n@trait\nclass DerivedTable(Selectable):\n    @property\n    def selects(self) -> t.List[Expr]:\n        this = self.this\n        return this.selects if isinstance(this, Query) else []\n\n\n@trait\nclass UDTF(DerivedTable):\n    @property\n    def selects(self) -> t.List[Expr]:\n        alias = self.args.get(\"alias\")\n        return alias.columns if alias else []\n\n\n@trait\nclass Query(Selectable):\n    \"\"\"Trait for any SELECT/UNION/etc. query expression.\"\"\"\n\n    @property\n    def ctes(self) -> t.List[CTE]:\n        with_ = self.args.get(\"with_\")\n        return with_.expressions if with_ else []\n\n    def select(\n        self: Q,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Q:\n        raise NotImplementedError(\"Query objects must implement `select`\")\n\n    def subquery(self, alias: t.Optional[ExpOrStr] = None, copy: bool = True) -> Subquery:\n        \"\"\"\n        Returns a `Subquery` that wraps around this query.\n\n        Example:\n            >>> subquery = Select().select(\"x\").from_(\"tbl\").subquery()\n            >>> Select().select(\"x\").from_(subquery).sql()\n            'SELECT x FROM (SELECT x FROM tbl)'\n\n        Args:\n            alias: an optional alias for the subquery.\n            copy: if `False`, modify this expression instance in-place.\n        \"\"\"\n        instance = maybe_copy(self, copy)\n        if not isinstance(alias, Expr):\n            alias = TableAlias(this=to_identifier(alias)) if alias else None\n\n        return Subquery(this=instance, alias=alias)\n\n    def limit(\n        self: Q, expression: ExpOrStr | int, dialect: DialectType = None, copy: bool = True, **opts\n    ) -> Q:\n        \"\"\"\n        Adds a LIMIT clause to this query.\n\n        Example:\n            >>> Select().select(\"1\").union(Select().select(\"1\")).limit(1).sql()\n            'SELECT 1 UNION SELECT 1 LIMIT 1'\n\n        Args:\n            expression: the SQL code string to parse.\n                This can also be an integer.\n                If a `Limit` instance is passed, it will be used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `Limit`.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            A limited Select expression.\n        \"\"\"\n        return _apply_builder(\n            expression=expression,\n            instance=self,\n            arg=\"limit\",\n            into=Limit,\n            prefix=\"LIMIT\",\n            dialect=dialect,\n            copy=copy,\n            into_arg=\"expression\",\n            **opts,\n        )\n\n    def offset(\n        self: Q, expression: ExpOrStr | int, dialect: DialectType = None, copy: bool = True, **opts\n    ) -> Q:\n        \"\"\"\n        Set the OFFSET expression.\n\n        Example:\n            >>> Select().from_(\"tbl\").select(\"x\").offset(10).sql()\n            'SELECT x FROM tbl OFFSET 10'\n\n        Args:\n            expression: the SQL code string to parse.\n                This can also be an integer.\n                If a `Offset` instance is passed, this is used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `Offset`.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Select expression.\n        \"\"\"\n        return _apply_builder(\n            expression=expression,\n            instance=self,\n            arg=\"offset\",\n            into=Offset,\n            prefix=\"OFFSET\",\n            dialect=dialect,\n            copy=copy,\n            into_arg=\"expression\",\n            **opts,\n        )\n\n    def order_by(\n        self: Q,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Q:\n        \"\"\"\n        Set the ORDER BY expression.\n\n        Example:\n            >>> Select().from_(\"tbl\").select(\"x\").order_by(\"x DESC\").sql()\n            'SELECT x FROM tbl ORDER BY x DESC'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If a `Group` instance is passed, this is used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `Order`.\n            append: if `True`, add to any existing expressions.\n                Otherwise, this flattens all the `Order` expression into a single expression.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Select expression.\n        \"\"\"\n        return _apply_child_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"order\",\n            append=append,\n            copy=copy,\n            prefix=\"ORDER BY\",\n            into=Order,\n            dialect=dialect,\n            **opts,\n        )\n\n    def where(\n        self: Q,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Q:\n        \"\"\"\n        Append to or set the WHERE expressions.\n\n        Examples:\n            >>> Select().select(\"x\").from_(\"tbl\").where(\"x = 'a' OR x < 'b'\").sql()\n            \"SELECT x FROM tbl WHERE x = 'a' OR x < 'b'\"\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n                Multiple expressions are combined with an AND operator.\n            append: if `True`, AND the new expressions to any existing expression.\n                Otherwise, this resets the expression.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified expression.\n        \"\"\"\n        return _apply_conjunction_builder(\n            *[expr.this if isinstance(expr, Where) else expr for expr in expressions],\n            instance=self,\n            arg=\"where\",\n            append=append,\n            into=Where,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def with_(\n        self: Q,\n        alias: ExpOrStr,\n        as_: ExpOrStr,\n        recursive: t.Optional[bool] = None,\n        materialized: t.Optional[bool] = None,\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        scalar: t.Optional[bool] = None,\n        **opts,\n    ) -> Q:\n        \"\"\"\n        Append to or set the common table expressions.\n\n        Example:\n            >>> Select().with_(\"tbl2\", as_=\"SELECT * FROM tbl\").select(\"x\").from_(\"tbl2\").sql()\n            'WITH tbl2 AS (SELECT * FROM tbl) SELECT x FROM tbl2'\n\n        Args:\n            alias: the SQL code string to parse as the table name.\n                If an `Expr` instance is passed, this is used as-is.\n            as_: the SQL code string to parse as the table expression.\n                If an `Expr` instance is passed, it will be used as-is.\n            recursive: set the RECURSIVE part of the expression. Defaults to `False`.\n            materialized: set the MATERIALIZED part of the expression.\n            append: if `True`, add to any existing expressions.\n                Otherwise, this resets the expressions.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            scalar: if `True`, this is a scalar common table expression.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified expression.\n        \"\"\"\n        return _apply_cte_builder(\n            self,\n            alias,\n            as_,\n            recursive=recursive,\n            materialized=materialized,\n            append=append,\n            dialect=dialect,\n            copy=copy,\n            scalar=scalar,\n            **opts,\n        )\n\n    def union(\n        self, *expressions: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts\n    ) -> Union:\n        \"\"\"\n        Builds a UNION expression.\n\n        Example:\n            >>> import sqlglot\n            >>> sqlglot.parse_one(\"SELECT * FROM foo\").union(\"SELECT * FROM bla\").sql()\n            'SELECT * FROM foo UNION SELECT * FROM bla'\n\n        Args:\n            expressions: the SQL code strings.\n                If `Expr` instances are passed, they will be used as-is.\n            distinct: set the DISTINCT flag if and only if this is true.\n            dialect: the dialect used to parse the input expression.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The new Union expression.\n        \"\"\"\n        return union(self, *expressions, distinct=distinct, dialect=dialect, **opts)\n\n    def intersect(\n        self, *expressions: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts\n    ) -> Intersect:\n        \"\"\"\n        Builds an INTERSECT expression.\n\n        Example:\n            >>> import sqlglot\n            >>> sqlglot.parse_one(\"SELECT * FROM foo\").intersect(\"SELECT * FROM bla\").sql()\n            'SELECT * FROM foo INTERSECT SELECT * FROM bla'\n\n        Args:\n            expressions: the SQL code strings.\n                If `Expr` instances are passed, they will be used as-is.\n            distinct: set the DISTINCT flag if and only if this is true.\n            dialect: the dialect used to parse the input expression.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The new Intersect expression.\n        \"\"\"\n        return intersect(self, *expressions, distinct=distinct, dialect=dialect, **opts)\n\n    def except_(\n        self, *expressions: ExpOrStr, distinct: bool = True, dialect: DialectType = None, **opts\n    ) -> Except:\n        \"\"\"\n        Builds an EXCEPT expression.\n\n        Example:\n            >>> import sqlglot\n            >>> sqlglot.parse_one(\"SELECT * FROM foo\").except_(\"SELECT * FROM bla\").sql()\n            'SELECT * FROM foo EXCEPT SELECT * FROM bla'\n\n        Args:\n            expressions: the SQL code strings.\n                If `Expr` instance are passed, they will be used as-is.\n            distinct: set the DISTINCT flag if and only if this is true.\n            dialect: the dialect used to parse the input expression.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The new Except expression.\n        \"\"\"\n        return except_(self, *expressions, distinct=distinct, dialect=dialect, **opts)\n\n\nclass QueryBand(Expression):\n    arg_types = {\"this\": True, \"scope\": False, \"update\": False}\n\n\nclass RecursiveWithSearch(Expression):\n    arg_types = {\"kind\": True, \"this\": True, \"expression\": True, \"using\": False}\n\n\nclass With(Expression):\n    arg_types = {\"expressions\": True, \"recursive\": False, \"search\": False}\n\n    @property\n    def recursive(self) -> bool:\n        return bool(self.args.get(\"recursive\"))\n\n\nclass CTE(Expression, DerivedTable):\n    arg_types = {\n        \"this\": True,\n        \"alias\": True,\n        \"scalar\": False,\n        \"materialized\": False,\n        \"key_expressions\": False,\n    }\n\n\nclass ProjectionDef(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass TableAlias(Expression):\n    arg_types = {\"this\": False, \"columns\": False}\n\n    @property\n    def columns(self) -> t.List[t.Any]:\n        return self.args.get(\"columns\") or []\n\n\nclass BitString(Expression, Condition):\n    is_primitive = True\n\n\nclass HexString(Expression, Condition):\n    arg_types = {\"this\": True, \"is_integer\": False}\n    is_primitive = True\n\n\nclass ByteString(Expression, Condition):\n    arg_types = {\"this\": True, \"is_bytes\": False}\n    is_primitive = True\n\n\nclass RawString(Expression, Condition):\n    is_primitive = True\n\n\nclass UnicodeString(Expression, Condition):\n    arg_types = {\"this\": True, \"escape\": False}\n\n\nclass ColumnPosition(Expression):\n    arg_types = {\"this\": False, \"position\": True}\n\n\nclass ColumnDef(Expression):\n    arg_types = {\n        \"this\": True,\n        \"kind\": False,\n        \"constraints\": False,\n        \"exists\": False,\n        \"position\": False,\n        \"default\": False,\n        \"output\": False,\n    }\n\n    @property\n    def constraints(self) -> t.List[ColumnConstraint]:\n        return self.args.get(\"constraints\") or []\n\n    @property\n    def kind(self) -> t.Optional[DataType]:\n        return self.args.get(\"kind\")\n\n\nclass Changes(Expression):\n    arg_types = {\"information\": True, \"at_before\": False, \"end\": False}\n\n\nclass Connect(Expression):\n    arg_types = {\"start\": False, \"connect\": True, \"nocycle\": False}\n\n\nclass Prior(Expression):\n    pass\n\n\nclass Into(Expression):\n    arg_types = {\n        \"this\": False,\n        \"temporary\": False,\n        \"unlogged\": False,\n        \"bulk_collect\": False,\n        \"expressions\": False,\n    }\n\n\nclass From(Expression):\n    @property\n    def name(self) -> str:\n        return self.this.name\n\n    @property\n    def alias_or_name(self) -> str:\n        return self.this.alias_or_name\n\n\nclass Having(Expression):\n    pass\n\n\nclass Index(Expression):\n    arg_types = {\n        \"this\": False,\n        \"table\": False,\n        \"unique\": False,\n        \"primary\": False,\n        \"amp\": False,  # teradata\n        \"params\": False,\n    }\n\n\nclass ConditionalInsert(Expression):\n    arg_types = {\"this\": True, \"expression\": False, \"else_\": False}\n\n\nclass MultitableInserts(Expression):\n    arg_types = {\"expressions\": True, \"kind\": True, \"source\": True}\n\n\nclass OnCondition(Expression):\n    arg_types = {\"error\": False, \"empty\": False, \"null\": False}\n\n\nclass Introducer(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass National(Expression):\n    is_primitive = True\n\n\nclass Partition(Expression):\n    arg_types = {\"expressions\": True, \"subpartition\": False}\n\n\nclass PartitionRange(Expression):\n    arg_types = {\"this\": True, \"expression\": False, \"expressions\": False}\n\n\nclass PartitionId(Expression):\n    pass\n\n\nclass Fetch(Expression):\n    arg_types = {\n        \"direction\": False,\n        \"count\": False,\n        \"limit_options\": False,\n    }\n\n\nclass Grant(Expression):\n    arg_types = {\n        \"privileges\": True,\n        \"kind\": False,\n        \"securable\": True,\n        \"principals\": True,\n        \"grant_option\": False,\n    }\n\n\nclass Revoke(Expression):\n    arg_types = {**Grant.arg_types, \"cascade\": False}\n\n\nclass Group(Expression):\n    arg_types = {\n        \"expressions\": False,\n        \"grouping_sets\": False,\n        \"cube\": False,\n        \"rollup\": False,\n        \"totals\": False,\n        \"all\": False,\n    }\n\n\nclass Cube(Expression):\n    arg_types = {\"expressions\": False}\n\n\nclass Rollup(Expression):\n    arg_types = {\"expressions\": False}\n\n\nclass GroupingSets(Expression):\n    arg_types = {\"expressions\": True}\n\n\nclass Lambda(Expression):\n    arg_types = {\"this\": True, \"expressions\": True, \"colon\": False}\n\n\nclass Limit(Expression):\n    arg_types = {\n        \"this\": False,\n        \"expression\": True,\n        \"offset\": False,\n        \"limit_options\": False,\n        \"expressions\": False,\n    }\n\n\nclass LimitOptions(Expression):\n    arg_types = {\n        \"percent\": False,\n        \"rows\": False,\n        \"with_ties\": False,\n    }\n\n\nclass Join(Expression):\n    arg_types = {\n        \"this\": True,\n        \"on\": False,\n        \"side\": False,\n        \"kind\": False,\n        \"using\": False,\n        \"method\": False,\n        \"global_\": False,\n        \"hint\": False,\n        \"match_condition\": False,  # Snowflake\n        \"directed\": False,  # Snowflake\n        \"expressions\": False,\n        \"pivots\": False,\n    }\n\n    @property\n    def method(self) -> str:\n        return self.text(\"method\").upper()\n\n    @property\n    def kind(self) -> str:\n        return self.text(\"kind\").upper()\n\n    @property\n    def side(self) -> str:\n        return self.text(\"side\").upper()\n\n    @property\n    def hint(self) -> str:\n        return self.text(\"hint\").upper()\n\n    @property\n    def alias_or_name(self) -> str:\n        return self.this.alias_or_name\n\n    @property\n    def is_semi_or_anti_join(self) -> bool:\n        return self.kind in (\"SEMI\", \"ANTI\")\n\n    def on(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Join:\n        \"\"\"\n        Append to or set the ON expressions.\n\n        Example:\n            >>> import sqlglot\n            >>> sqlglot.parse_one(\"JOIN x\", into=Join).on(\"y = 1\").sql()\n            'JOIN x ON y = 1'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n                Multiple expressions are combined with an AND operator.\n            append: if `True`, AND the new expressions to any existing expression.\n                Otherwise, this resets the expression.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Join expression.\n        \"\"\"\n        join = _apply_conjunction_builder(\n            *expressions,\n            instance=self,\n            arg=\"on\",\n            append=append,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n        if join.kind == \"CROSS\":\n            join.set(\"kind\", None)\n\n        return join\n\n    def using(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Join:\n        \"\"\"\n        Append to or set the USING expressions.\n\n        Example:\n            >>> import sqlglot\n            >>> sqlglot.parse_one(\"JOIN x\", into=Join).using(\"foo\", \"bla\").sql()\n            'JOIN x USING (foo, bla)'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n            append: if `True`, concatenate the new expressions to the existing \"using\" list.\n                Otherwise, this resets the expression.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Join expression.\n        \"\"\"\n        join = _apply_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"using\",\n            append=append,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n        if join.kind == \"CROSS\":\n            join.set(\"kind\", None)\n\n        return join\n\n\nclass Lateral(Expression, UDTF):\n    arg_types = {\n        \"this\": True,\n        \"view\": False,\n        \"outer\": False,\n        \"alias\": False,\n        \"cross_apply\": False,  # True -> CROSS APPLY, False -> OUTER APPLY\n        \"ordinality\": False,\n    }\n\n\nclass TableFromRows(Expression, UDTF):\n    arg_types = {\n        \"this\": True,\n        \"alias\": False,\n        \"joins\": False,\n        \"pivots\": False,\n        \"sample\": False,\n    }\n\n\nclass MatchRecognizeMeasure(Expression):\n    arg_types = {\n        \"this\": True,\n        \"window_frame\": False,\n    }\n\n\nclass MatchRecognize(Expression):\n    arg_types = {\n        \"partition_by\": False,\n        \"order\": False,\n        \"measures\": False,\n        \"rows\": False,\n        \"after\": False,\n        \"pattern\": False,\n        \"define\": False,\n        \"alias\": False,\n    }\n\n\nclass Final(Expression):\n    pass\n\n\nclass Offset(Expression):\n    arg_types = {\"this\": False, \"expression\": True, \"expressions\": False}\n\n\nclass Order(Expression):\n    arg_types = {\"this\": False, \"expressions\": True, \"siblings\": False}\n\n\nclass WithFill(Expression):\n    arg_types = {\n        \"from_\": False,\n        \"to\": False,\n        \"step\": False,\n        \"interpolate\": False,\n    }\n\n\nclass SkipJSONColumn(Expression):\n    arg_types = {\"regexp\": False, \"expression\": True}\n\n\nclass Cluster(Order):\n    pass\n\n\nclass Distribute(Order):\n    pass\n\n\nclass Sort(Order):\n    pass\n\n\nclass Qualify(Expression):\n    pass\n\n\nclass InputOutputFormat(Expression):\n    arg_types = {\"input_format\": False, \"output_format\": False}\n\n\nclass Return(Expression):\n    pass\n\n\nclass Tuple(Expression):\n    arg_types = {\"expressions\": False}\n\n    def isin(\n        self,\n        *expressions: t.Any,\n        query: t.Optional[ExpOrStr] = None,\n        unnest: t.Optional[ExpOrStr] | Collection[ExpOrStr] = None,\n        copy: bool = True,\n        **opts,\n    ) -> In:\n        return In(\n            this=maybe_copy(self, copy),\n            expressions=[convert(e, copy=copy) for e in expressions],\n            query=maybe_parse(query, copy=copy, **opts) if query else None,\n            unnest=(\n                Unnest(\n                    expressions=[\n                        maybe_parse(t.cast(ExpOrStr, e), copy=copy, **opts)\n                        for e in ensure_list(unnest)\n                    ]\n                )\n                if unnest\n                else None\n            ),\n        )\n\n\nclass QueryOption(Expression):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass WithTableHint(Expression):\n    arg_types = {\"expressions\": True}\n\n\nclass IndexTableHint(Expression):\n    arg_types = {\"this\": True, \"expressions\": False, \"target\": False}\n\n\nclass HistoricalData(Expression):\n    arg_types = {\"this\": True, \"kind\": True, \"expression\": True}\n\n\nclass Put(Expression):\n    arg_types = {\"this\": True, \"target\": True, \"properties\": False}\n\n\nclass Get(Expression):\n    arg_types = {\"this\": True, \"target\": True, \"properties\": False}\n\n\nclass Table(Expression, Selectable):\n    arg_types = {\n        \"this\": False,\n        \"alias\": False,\n        \"db\": False,\n        \"catalog\": False,\n        \"laterals\": False,\n        \"joins\": False,\n        \"pivots\": False,\n        \"hints\": False,\n        \"system_time\": False,\n        \"version\": False,\n        \"format\": False,\n        \"pattern\": False,\n        \"ordinality\": False,\n        \"when\": False,\n        \"only\": False,\n        \"partition\": False,\n        \"changes\": False,\n        \"rows_from\": False,\n        \"sample\": False,\n        \"indexed\": False,\n    }\n\n    @property\n    def name(self) -> str:\n        if not self.this or isinstance(self.this, Func):\n            return \"\"\n        return self.this.name\n\n    @property\n    def db(self) -> str:\n        return self.text(\"db\")\n\n    @property\n    def catalog(self) -> str:\n        return self.text(\"catalog\")\n\n    @property\n    def selects(self) -> t.List[Expr]:\n        return []\n\n    @property\n    def named_selects(self) -> t.List[str]:\n        return []\n\n    @property\n    def parts(self) -> t.List[Expr]:\n        \"\"\"Return the parts of a table in order catalog, db, table.\"\"\"\n        parts: t.List[Expr] = []\n\n        for arg in (\"catalog\", \"db\", \"this\"):\n            part = self.args.get(arg)\n\n            if isinstance(part, Dot):\n                parts.extend(part.flatten())\n            elif isinstance(part, Expr):\n                parts.append(part)\n\n        return parts\n\n    def to_column(self, copy: bool = True) -> Expr:\n        parts = self.parts\n        last_part = parts[-1]\n\n        if isinstance(last_part, Identifier):\n            col: Expr = column(*reversed(parts[0:4]), fields=parts[4:], copy=copy)  # type: ignore\n        else:\n            # This branch will be reached if a function or array is wrapped in a `Table`\n            col = last_part\n\n        alias = self.args.get(\"alias\")\n        if alias:\n            col = alias_(col, alias.this, copy=copy)\n\n        return col\n\n\nclass SetOperation(Expression, Query):\n    arg_types = {\n        \"with_\": False,\n        \"this\": True,\n        \"expression\": True,\n        \"distinct\": False,\n        \"by_name\": False,\n        \"side\": False,\n        \"kind\": False,\n        \"on\": False,\n        **QUERY_MODIFIERS,\n    }\n\n    def select(\n        self: S,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> S:\n        this = maybe_copy(self, copy)\n        this.this.unnest().select(*expressions, append=append, dialect=dialect, copy=False, **opts)\n        this.expression.unnest().select(\n            *expressions, append=append, dialect=dialect, copy=False, **opts\n        )\n        return this\n\n    @property\n    def named_selects(self) -> t.List[str]:\n        expr: Expr = self\n        while isinstance(expr, SetOperation):\n            expr = expr.this.unnest()\n        return _named_selects(expr)\n\n    @property\n    def is_star(self) -> bool:\n        return self.this.is_star or self.expression.is_star\n\n    @property\n    def selects(self) -> t.List[Expr]:\n        expr: Expr = self\n        while isinstance(expr, SetOperation):\n            expr = expr.this.unnest()\n        return getattr(expr, \"selects\", [])\n\n    @property\n    def left(self) -> Query:\n        return self.this\n\n    @property\n    def right(self) -> Query:\n        return self.expression\n\n    @property\n    def kind(self) -> str:\n        return self.text(\"kind\").upper()\n\n    @property\n    def side(self) -> str:\n        return self.text(\"side\").upper()\n\n\nclass Union(SetOperation):\n    pass\n\n\nclass Except(SetOperation):\n    pass\n\n\nclass Intersect(SetOperation):\n    pass\n\n\nclass Values(Expression, UDTF):\n    arg_types = {\n        \"expressions\": True,\n        \"alias\": False,\n        \"order\": False,\n        \"limit\": False,\n        \"offset\": False,\n    }\n\n\nclass Version(Expression):\n    \"\"\"\n    Time travel, iceberg, bigquery etc\n    https://trino.io/docs/current/connector/iceberg.html?highlight=snapshot#using-snapshots\n    https://www.databricks.com/blog/2019/02/04/introducing-delta-time-travel-for-large-scale-data-lakes.html\n    https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#for_system_time_as_of\n    https://learn.microsoft.com/en-us/sql/relational-databases/tables/querying-data-in-a-system-versioned-temporal-table?view=sql-server-ver16\n    this is either TIMESTAMP or VERSION\n    kind is (\"AS OF\", \"BETWEEN\")\n    \"\"\"\n\n    arg_types = {\"this\": True, \"kind\": True, \"expression\": False}\n\n\nclass Schema(Expression):\n    arg_types = {\"this\": False, \"expressions\": False}\n\n\nclass Lock(Expression):\n    arg_types = {\"update\": True, \"expressions\": False, \"wait\": False, \"key\": False}\n\n\nclass Select(Expression, Query):\n    arg_types = {\n        \"with_\": False,\n        \"kind\": False,\n        \"expressions\": False,\n        \"hint\": False,\n        \"distinct\": False,\n        \"into\": False,\n        \"from_\": False,\n        \"operation_modifiers\": False,\n        \"exclude\": False,\n        **QUERY_MODIFIERS,\n    }\n\n    def from_(\n        self, expression: ExpOrStr, dialect: DialectType = None, copy: bool = True, **opts\n    ) -> Select:\n        \"\"\"\n        Set the FROM expression.\n\n        Example:\n            >>> Select().from_(\"tbl\").select(\"x\").sql()\n            'SELECT x FROM tbl'\n\n        Args:\n            expression : the SQL code strings to parse.\n                If a `From` instance is passed, this is used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `From`.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Select expression.\n        \"\"\"\n        return _apply_builder(\n            expression=expression,\n            instance=self,\n            arg=\"from_\",\n            into=From,\n            prefix=\"FROM\",\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def group_by(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        \"\"\"\n        Set the GROUP BY expression.\n\n        Example:\n            >>> Select().from_(\"tbl\").select(\"x\", \"COUNT(1)\").group_by(\"x\").sql()\n            'SELECT x, COUNT(1) FROM tbl GROUP BY x'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If a `Group` instance is passed, this is used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `Group`.\n                If nothing is passed in then a group by is not applied to the expression\n            append: if `True`, add to any existing expressions.\n                Otherwise, this flattens all the `Group` expression into a single expression.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Select expression.\n        \"\"\"\n        if not expressions:\n            return self if not copy else self.copy()\n\n        return _apply_child_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"group\",\n            append=append,\n            copy=copy,\n            prefix=\"GROUP BY\",\n            into=Group,\n            dialect=dialect,\n            **opts,\n        )\n\n    def sort_by(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        \"\"\"\n        Set the SORT BY expression.\n\n        Example:\n            >>> Select().from_(\"tbl\").select(\"x\").sort_by(\"x DESC\").sql(dialect=\"hive\")\n            'SELECT x FROM tbl SORT BY x DESC'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If a `Group` instance is passed, this is used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `SORT`.\n            append: if `True`, add to any existing expressions.\n                Otherwise, this flattens all the `Order` expression into a single expression.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Select expression.\n        \"\"\"\n        return _apply_child_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"sort\",\n            append=append,\n            copy=copy,\n            prefix=\"SORT BY\",\n            into=Sort,\n            dialect=dialect,\n            **opts,\n        )\n\n    def cluster_by(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        \"\"\"\n        Set the CLUSTER BY expression.\n\n        Example:\n            >>> Select().from_(\"tbl\").select(\"x\").cluster_by(\"x DESC\").sql(dialect=\"hive\")\n            'SELECT x FROM tbl CLUSTER BY x DESC'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If a `Group` instance is passed, this is used as-is.\n                If another `Expr` instance is passed, it will be wrapped in a `Cluster`.\n            append: if `True`, add to any existing expressions.\n                Otherwise, this flattens all the `Order` expression into a single expression.\n            dialect: the dialect used to parse the input expression.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Select expression.\n        \"\"\"\n        return _apply_child_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"cluster\",\n            append=append,\n            copy=copy,\n            prefix=\"CLUSTER BY\",\n            into=Cluster,\n            dialect=dialect,\n            **opts,\n        )\n\n    def select(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        return _apply_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"expressions\",\n            append=append,\n            dialect=dialect,\n            into=Expr,\n            copy=copy,\n            **opts,\n        )\n\n    def lateral(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        \"\"\"\n        Append to or set the LATERAL expressions.\n\n        Example:\n            >>> Select().select(\"x\").lateral(\"OUTER explode(y) tbl2 AS z\").from_(\"tbl\").sql()\n            'SELECT x FROM tbl LATERAL VIEW OUTER EXPLODE(y) tbl2 AS z'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n            append: if `True`, add to any existing expressions.\n                Otherwise, this resets the expressions.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Select expression.\n        \"\"\"\n        return _apply_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"laterals\",\n            append=append,\n            into=Lateral,\n            prefix=\"LATERAL VIEW\",\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def join(\n        self,\n        expression: ExpOrStr,\n        on: t.Optional[ExpOrStr | list[ExpOrStr]] = None,\n        using: t.Optional[ExpOrStr | Collection[ExpOrStr]] = None,\n        append: bool = True,\n        join_type: t.Optional[str] = None,\n        join_alias: t.Optional[Identifier | str] = None,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        \"\"\"\n        Append to or set the JOIN expressions.\n\n        Example:\n            >>> Select().select(\"*\").from_(\"tbl\").join(\"tbl2\", on=\"tbl1.y = tbl2.y\").sql()\n            'SELECT * FROM tbl JOIN tbl2 ON tbl1.y = tbl2.y'\n\n            >>> Select().select(\"1\").from_(\"a\").join(\"b\", using=[\"x\", \"y\", \"z\"]).sql()\n            'SELECT 1 FROM a JOIN b USING (x, y, z)'\n\n            Use `join_type` to change the type of join:\n\n            >>> Select().select(\"*\").from_(\"tbl\").join(\"tbl2\", on=\"tbl1.y = tbl2.y\", join_type=\"left outer\").sql()\n            'SELECT * FROM tbl LEFT OUTER JOIN tbl2 ON tbl1.y = tbl2.y'\n\n        Args:\n            expression: the SQL code string to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n            on: optionally specify the join \"on\" criteria as a SQL string.\n                If an `Expr` instance is passed, it will be used as-is.\n            using: optionally specify the join \"using\" criteria as a SQL string.\n                If an `Expr` instance is passed, it will be used as-is.\n            append: if `True`, add to any existing expressions.\n                Otherwise, this resets the expressions.\n            join_type: if set, alter the parsed join type.\n            join_alias: an optional alias for the joined source.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            Select: the modified expression.\n        \"\"\"\n        parse_args: t.Dict[str, t.Any] = {\"dialect\": dialect, **opts}\n\n        try:\n            expression = maybe_parse(expression, into=Join, prefix=\"JOIN\", **parse_args)\n        except ParseError:\n            expression = maybe_parse(expression, into=(Join, Expr), **parse_args)\n\n        join = expression if isinstance(expression, Join) else Join(this=expression)\n\n        if isinstance(join.this, Select):\n            join.this.replace(join.this.subquery())\n\n        if join_type:\n            new_join = maybe_parse(f\"FROM _ {join_type} JOIN _\", **parse_args).find(Join)\n            method = new_join.method\n            side = new_join.side\n            kind = new_join.kind\n\n            if method:\n                join.set(\"method\", method)\n            if side:\n                join.set(\"side\", side)\n            if kind:\n                join.set(\"kind\", kind)\n\n        if on:\n            on = and_(\n                *t.cast(t.List[ExpOrStr], ensure_list(on)), dialect=dialect, copy=copy, **opts\n            )\n            join.set(\"on\", on)\n\n        if using:\n            join = _apply_list_builder(\n                *ensure_list(using),\n                instance=join,\n                arg=\"using\",\n                append=append,\n                copy=copy,\n                into=Identifier,\n                **opts,\n            )\n\n        if join_alias:\n            join.set(\"this\", alias_(join.this, join_alias, table=True))\n\n        return _apply_list_builder(\n            join,\n            instance=self,\n            arg=\"joins\",\n            append=append,\n            copy=copy,\n            **opts,\n        )\n\n    def having(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        \"\"\"\n        Append to or set the HAVING expressions.\n\n        Example:\n            >>> Select().select(\"x\", \"COUNT(y)\").from_(\"tbl\").group_by(\"x\").having(\"COUNT(y) > 3\").sql()\n            'SELECT x, COUNT(y) FROM tbl GROUP BY x HAVING COUNT(y) > 3'\n\n        Args:\n            *expressions: the SQL code strings to parse.\n                If an `Expr` instance is passed, it will be used as-is.\n                Multiple expressions are combined with an AND operator.\n            append: if `True`, AND the new expressions to any existing expression.\n                Otherwise, this resets the expression.\n            dialect: the dialect used to parse the input expressions.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input expressions.\n\n        Returns:\n            The modified Select expression.\n        \"\"\"\n        return _apply_conjunction_builder(\n            *expressions,\n            instance=self,\n            arg=\"having\",\n            append=append,\n            into=Having,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def window(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        return _apply_list_builder(\n            *expressions,\n            instance=self,\n            arg=\"windows\",\n            append=append,\n            into=Window,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def qualify(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Select:\n        return _apply_conjunction_builder(\n            *expressions,\n            instance=self,\n            arg=\"qualify\",\n            append=append,\n            into=Qualify,\n            dialect=dialect,\n            copy=copy,\n            **opts,\n        )\n\n    def distinct(\n        self, *ons: t.Optional[ExpOrStr], distinct: bool = True, copy: bool = True\n    ) -> Select:\n        \"\"\"\n        Set the OFFSET expression.\n\n        Example:\n            >>> Select().from_(\"tbl\").select(\"x\").distinct().sql()\n            'SELECT DISTINCT x FROM tbl'\n\n        Args:\n            ons: the expressions to distinct on\n            distinct: whether the Select should be distinct\n            copy: if `False`, modify this expression instance in-place.\n\n        Returns:\n            Select: the modified expression.\n        \"\"\"\n        instance = maybe_copy(self, copy)\n        on = Tuple(expressions=[maybe_parse(on, copy=copy) for on in ons if on]) if ons else None\n        instance.set(\"distinct\", Distinct(on=on) if distinct else None)\n        return instance\n\n    def ctas(\n        self,\n        table: ExpOrStr,\n        properties: t.Optional[t.Dict] = None,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Create:\n        \"\"\"\n        Convert this expression to a CREATE TABLE AS statement.\n\n        Example:\n            >>> Select().select(\"*\").from_(\"tbl\").ctas(\"x\").sql()\n            'CREATE TABLE x AS SELECT * FROM tbl'\n\n        Args:\n            table: the SQL code string to parse as the table name.\n                If another `Expr` instance is passed, it will be used as-is.\n            properties: an optional mapping of table properties\n            dialect: the dialect used to parse the input table.\n            copy: if `False`, modify this expression instance in-place.\n            opts: other options to use to parse the input table.\n\n        Returns:\n            The new Create expression.\n        \"\"\"\n        instance = maybe_copy(self, copy)\n        table_expression = maybe_parse(table, into=Table, dialect=dialect, **opts)\n\n        properties_expression = None\n        if properties:\n            from sqlglot.expressions.properties import Properties as _Properties\n\n            properties_expression = _Properties.from_dict(properties)\n\n        from sqlglot.expressions.ddl import Create as _Create\n\n        return _Create(\n            this=table_expression,\n            kind=\"TABLE\",\n            expression=instance,\n            properties=properties_expression,\n        )\n\n    def lock(self, update: bool = True, copy: bool = True) -> Select:\n        \"\"\"\n        Set the locking read mode for this expression.\n\n        Examples:\n            >>> Select().select(\"x\").from_(\"tbl\").where(\"x = 'a'\").lock().sql(\"mysql\")\n            \"SELECT x FROM tbl WHERE x = 'a' FOR UPDATE\"\n\n            >>> Select().select(\"x\").from_(\"tbl\").where(\"x = 'a'\").lock(update=False).sql(\"mysql\")\n            \"SELECT x FROM tbl WHERE x = 'a' FOR SHARE\"\n\n        Args:\n            update: if `True`, the locking type will be `FOR UPDATE`, else it will be `FOR SHARE`.\n            copy: if `False`, modify this expression instance in-place.\n\n        Returns:\n            The modified expression.\n        \"\"\"\n        inst = maybe_copy(self, copy)\n        inst.set(\"locks\", [Lock(update=update)])\n\n        return inst\n\n    def hint(self, *hints: ExpOrStr, dialect: DialectType = None, copy: bool = True) -> Select:\n        \"\"\"\n        Set hints for this expression.\n\n        Examples:\n            >>> Select().select(\"x\").from_(\"tbl\").hint(\"BROADCAST(y)\").sql(dialect=\"spark\")\n            'SELECT /*+ BROADCAST(y) */ x FROM tbl'\n\n        Args:\n            hints: The SQL code strings to parse as the hints.\n                If an `Expr` instance is passed, it will be used as-is.\n            dialect: The dialect used to parse the hints.\n            copy: If `False`, modify this expression instance in-place.\n\n        Returns:\n            The modified expression.\n        \"\"\"\n        inst = maybe_copy(self, copy)\n        inst.set(\n            \"hint\", Hint(expressions=[maybe_parse(h, copy=copy, dialect=dialect) for h in hints])\n        )\n\n        return inst\n\n    @property\n    def named_selects(self) -> t.List[str]:\n        selects = []\n\n        for e in self.expressions:\n            if e.alias_or_name:\n                selects.append(e.output_name)\n            elif isinstance(e, Aliases):\n                selects.extend([a.name for a in e.aliases])\n        return selects\n\n    @property\n    def is_star(self) -> bool:\n        return any(expression.is_star for expression in self.expressions)\n\n    @property\n    def selects(self) -> t.List[Expr]:\n        return self.expressions\n\n\nclass Subquery(Expression, DerivedTable, Query):\n    is_subquery: t.ClassVar[bool] = True\n    arg_types = {\n        \"this\": True,\n        \"alias\": False,\n        \"with_\": False,\n        **QUERY_MODIFIERS,\n    }\n\n    def unnest(self) -> Expr:\n        \"\"\"Returns the first non subquery.\"\"\"\n        expression: Expr = self\n        while isinstance(expression, Subquery):\n            expression = expression.this\n        return expression\n\n    def unwrap(self) -> Subquery:\n        expression = self\n        while expression.same_parent and expression.is_wrapper:\n            expression = t.cast(Subquery, expression.parent)\n        return expression\n\n    def select(\n        self,\n        *expressions: t.Optional[ExpOrStr],\n        append: bool = True,\n        dialect: DialectType = None,\n        copy: bool = True,\n        **opts,\n    ) -> Subquery:\n        this = maybe_copy(self, copy)\n        inner = this.unnest()\n        if hasattr(inner, \"select\"):\n            inner.select(*expressions, append=append, dialect=dialect, copy=False, **opts)\n        return this\n\n    @property\n    def is_wrapper(self) -> bool:\n        \"\"\"\n        Whether this Subquery acts as a simple wrapper around another expression.\n\n        SELECT * FROM (((SELECT * FROM t)))\n                      ^\n                      This corresponds to a \"wrapper\" Subquery node\n        \"\"\"\n        return all(v is None for k, v in self.args.items() if k != \"this\")\n\n    @property\n    def is_star(self) -> bool:\n        return self.this.is_star\n\n    @property\n    def output_name(self) -> str:\n        return self.alias\n\n\nclass TableSample(Expression):\n    arg_types = {\n        \"expressions\": False,\n        \"method\": False,\n        \"bucket_numerator\": False,\n        \"bucket_denominator\": False,\n        \"bucket_field\": False,\n        \"percent\": False,\n        \"rows\": False,\n        \"size\": False,\n        \"seed\": False,\n    }\n\n\nclass Tag(Expression):\n    \"\"\"Tags are used for generating arbitrary sql like SELECT <span>x</span>.\"\"\"\n\n    arg_types = {\n        \"this\": False,\n        \"prefix\": False,\n        \"postfix\": False,\n    }\n\n\nclass Pivot(Expression):\n    arg_types = {\n        \"this\": False,\n        \"alias\": False,\n        \"expressions\": False,\n        \"fields\": False,\n        \"unpivot\": False,\n        \"using\": False,\n        \"group\": False,\n        \"columns\": False,\n        \"include_nulls\": False,\n        \"default_on_null\": False,\n        \"into\": False,\n        \"with_\": False,\n    }\n\n    @property\n    def unpivot(self) -> bool:\n        return bool(self.args.get(\"unpivot\"))\n\n    @property\n    def fields(self) -> t.List[Expr]:\n        return self.args.get(\"fields\", [])\n\n\nclass UnpivotColumns(Expression):\n    arg_types = {\"this\": True, \"expressions\": True}\n\n\nclass Window(Expression, Condition):\n    arg_types = {\n        \"this\": True,\n        \"partition_by\": False,\n        \"order\": False,\n        \"spec\": False,\n        \"alias\": False,\n        \"over\": False,\n        \"first\": False,\n    }\n\n\nclass WindowSpec(Expression):\n    arg_types = {\n        \"kind\": False,\n        \"start\": False,\n        \"start_side\": False,\n        \"end\": False,\n        \"end_side\": False,\n        \"exclude\": False,\n    }\n\n\nclass PreWhere(Expression):\n    pass\n\n\nclass Where(Expression):\n    pass\n\n\nclass Analyze(Expression):\n    arg_types = {\n        \"kind\": False,\n        \"this\": False,\n        \"options\": False,\n        \"mode\": False,\n        \"partition\": False,\n        \"expression\": False,\n        \"properties\": False,\n    }\n\n\nclass AnalyzeStatistics(Expression):\n    arg_types = {\n        \"kind\": True,\n        \"option\": False,\n        \"this\": False,\n        \"expressions\": False,\n    }\n\n\nclass AnalyzeHistogram(Expression):\n    arg_types = {\n        \"this\": True,\n        \"expressions\": True,\n        \"expression\": False,\n        \"update_options\": False,\n    }\n\n\nclass AnalyzeSample(Expression):\n    arg_types = {\"kind\": True, \"sample\": True}\n\n\nclass AnalyzeListChainedRows(Expression):\n    arg_types = {\"expression\": False}\n\n\nclass AnalyzeDelete(Expression):\n    arg_types = {\"kind\": False}\n\n\nclass AnalyzeWith(Expression):\n    arg_types = {\"expressions\": True}\n\n\nclass AnalyzeValidate(Expression):\n    arg_types = {\n        \"kind\": True,\n        \"this\": False,\n        \"expression\": False,\n    }\n\n\nclass AnalyzeColumns(Expression):\n    pass\n\n\nclass UsingData(Expression):\n    pass\n\n\nclass AddPartition(Expression):\n    arg_types = {\"this\": True, \"exists\": False, \"location\": False}\n\n\nclass AttachOption(Expression):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass DropPartition(Expression):\n    arg_types = {\"expressions\": True, \"exists\": False}\n\n\nclass ReplacePartition(Expression):\n    arg_types = {\"expression\": True, \"source\": True}\n\n\nclass TranslateCharacters(Expression):\n    arg_types = {\"this\": True, \"expression\": True, \"with_error\": False}\n\n\nclass OverflowTruncateBehavior(Expression):\n    arg_types = {\"this\": False, \"with_count\": True}\n\n\nclass JSON(Expression):\n    arg_types = {\"this\": False, \"with_\": False, \"unique\": False}\n\n\nclass JSONPath(Expression):\n    arg_types = {\"expressions\": True, \"escape\": False}\n\n    @property\n    def output_name(self) -> str:\n        last_segment = self.expressions[-1].this\n        return last_segment if isinstance(last_segment, str) else \"\"\n\n\nclass JSONPathPart(Expression):\n    arg_types = {}\n\n\nclass JSONPathFilter(JSONPathPart):\n    arg_types = {\"this\": True}\n\n\nclass JSONPathKey(JSONPathPart):\n    arg_types = {\"this\": True}\n\n\nclass JSONPathRecursive(JSONPathPart):\n    arg_types = {\"this\": False}\n\n\nclass JSONPathRoot(JSONPathPart):\n    pass\n\n\nclass JSONPathScript(JSONPathPart):\n    arg_types = {\"this\": True}\n\n\nclass JSONPathSlice(JSONPathPart):\n    arg_types = {\"start\": False, \"end\": False, \"step\": False}\n\n\nclass JSONPathSelector(JSONPathPart):\n    arg_types = {\"this\": True}\n\n\nclass JSONPathSubscript(JSONPathPart):\n    arg_types = {\"this\": True}\n\n\nclass JSONPathUnion(JSONPathPart):\n    arg_types = {\"expressions\": True}\n\n\nclass JSONPathWildcard(JSONPathPart):\n    pass\n\n\nclass FormatJson(Expression):\n    pass\n\n\nclass JSONKeyValue(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass JSONColumnDef(Expression):\n    arg_types = {\n        \"this\": False,\n        \"kind\": False,\n        \"path\": False,\n        \"nested_schema\": False,\n        \"ordinality\": False,\n    }\n\n\nclass JSONSchema(Expression):\n    arg_types = {\"expressions\": True}\n\n\nclass JSONValue(Expression):\n    arg_types = {\n        \"this\": True,\n        \"path\": True,\n        \"returning\": False,\n        \"on_condition\": False,\n    }\n\n\nclass JSONValueArray(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass OpenJSONColumnDef(Expression):\n    arg_types = {\"this\": True, \"kind\": True, \"path\": False, \"as_json\": False}\n\n\nclass JSONExtractQuote(Expression):\n    arg_types = {\n        \"option\": True,\n        \"scalar\": False,\n    }\n\n\nclass ScopeResolution(Expression):\n    arg_types = {\"this\": False, \"expression\": True}\n\n\nclass Stream(Expression):\n    pass\n\n\nclass ModelAttribute(Expression):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass XMLNamespace(Expression):\n    pass\n\n\nclass XMLKeyValueOption(Expression):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass Semicolon(Expression):\n    arg_types = {}\n\n\nclass TableColumn(Expression):\n    pass\n\n\nclass Variadic(Expression):\n    pass\n\n\nclass StoredProcedure(Expression):\n    arg_types = {\"this\": True, \"expressions\": False, \"wrapped\": False}\n\n\nclass Block(Expression):\n    arg_types = {\"expressions\": True}\n\n\nclass IfBlock(Expression):\n    arg_types = {\"this\": True, \"true\": True, \"false\": False}\n\n\nclass WhileBlock(Expression):\n    arg_types = {\"this\": True, \"body\": True}\n\n\nclass EndStatement(Expression):\n    arg_types = {}\n\n\nUNWRAPPED_QUERIES = (Select, SetOperation)\n\n\ndef union(\n    *expressions: ExpOrStr,\n    distinct: bool = True,\n    dialect: DialectType = None,\n    copy: bool = True,\n    **opts,\n) -> Union:\n    \"\"\"\n    Initializes a syntax tree for the `UNION` operation.\n\n    Example:\n        >>> union(\"SELECT * FROM foo\", \"SELECT * FROM bla\").sql()\n        'SELECT * FROM foo UNION SELECT * FROM bla'\n\n    Args:\n        expressions: the SQL code strings, corresponding to the `UNION`'s operands.\n            If `Expr` instances are passed, they will be used as-is.\n        distinct: set the DISTINCT flag if and only if this is true.\n        dialect: the dialect used to parse the input expression.\n        copy: whether to copy the expression.\n        opts: other options to use to parse the input expressions.\n\n    Returns:\n        The new Union instance.\n    \"\"\"\n    assert len(expressions) >= 2, \"At least two expressions are required by `union`.\"\n    return _apply_set_operation(\n        *expressions, set_operation=Union, distinct=distinct, dialect=dialect, copy=copy, **opts\n    )\n\n\ndef intersect(\n    *expressions: ExpOrStr,\n    distinct: bool = True,\n    dialect: DialectType = None,\n    copy: bool = True,\n    **opts,\n) -> Intersect:\n    \"\"\"\n    Initializes a syntax tree for the `INTERSECT` operation.\n\n    Example:\n        >>> intersect(\"SELECT * FROM foo\", \"SELECT * FROM bla\").sql()\n        'SELECT * FROM foo INTERSECT SELECT * FROM bla'\n\n    Args:\n        expressions: the SQL code strings, corresponding to the `INTERSECT`'s operands.\n            If `Expr` instances are passed, they will be used as-is.\n        distinct: set the DISTINCT flag if and only if this is true.\n        dialect: the dialect used to parse the input expression.\n        copy: whether to copy the expression.\n        opts: other options to use to parse the input expressions.\n\n    Returns:\n        The new Intersect instance.\n    \"\"\"\n    assert len(expressions) >= 2, \"At least two expressions are required by `intersect`.\"\n    return _apply_set_operation(\n        *expressions, set_operation=Intersect, distinct=distinct, dialect=dialect, copy=copy, **opts\n    )\n\n\ndef except_(\n    *expressions: ExpOrStr,\n    distinct: bool = True,\n    dialect: DialectType = None,\n    copy: bool = True,\n    **opts,\n) -> Except:\n    \"\"\"\n    Initializes a syntax tree for the `EXCEPT` operation.\n\n    Example:\n        >>> except_(\"SELECT * FROM foo\", \"SELECT * FROM bla\").sql()\n        'SELECT * FROM foo EXCEPT SELECT * FROM bla'\n\n    Args:\n        expressions: the SQL code strings, corresponding to the `EXCEPT`'s operands.\n            If `Expr` instances are passed, they will be used as-is.\n        distinct: set the DISTINCT flag if and only if this is true.\n        dialect: the dialect used to parse the input expression.\n        copy: whether to copy the expression.\n        opts: other options to use to parse the input expressions.\n\n    Returns:\n        The new Except instance.\n    \"\"\"\n    assert len(expressions) >= 2, \"At least two expressions are required by `except_`.\"\n    return _apply_set_operation(\n        *expressions, set_operation=Except, distinct=distinct, dialect=dialect, copy=copy, **opts\n    )\n"
  },
  {
    "path": "sqlglot/expressions/string.py",
    "content": "\"\"\"sqlglot expressions - string, encoding, hashing, and regex functions.\"\"\"\n\nfrom __future__ import annotations\n\nfrom sqlglot.expressions.core import Expression, Func, Binary\n\n\n# String basics\n\n\nclass Ascii(Expression, Func):\n    pass\n\n\nclass BitLength(Expression, Func):\n    pass\n\n\nclass ByteLength(Expression, Func):\n    pass\n\n\nclass Chr(Expression, Func):\n    arg_types = {\"expressions\": True, \"charset\": False}\n    is_var_len_args = True\n    _sql_names = [\"CHR\", \"CHAR\"]\n\n\nclass Concat(Expression, Func):\n    arg_types = {\"expressions\": True, \"safe\": False, \"coalesce\": False}\n    is_var_len_args = True\n\n\nclass ConcatWs(Concat):\n    _sql_names = [\"CONCAT_WS\"]\n\n\nclass Contains(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"json_scope\": False}\n\n\nclass Elt(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True}\n    is_var_len_args = True\n\n\nclass EndsWith(Expression, Func):\n    _sql_names = [\"ENDS_WITH\", \"ENDSWITH\"]\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Format(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n\n\nclass Initcap(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass IsAscii(Expression, Func):\n    pass\n\n\nclass Left(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Length(Expression, Func):\n    arg_types = {\"this\": True, \"binary\": False, \"encoding\": False}\n    _sql_names = [\"LENGTH\", \"LEN\", \"CHAR_LENGTH\", \"CHARACTER_LENGTH\"]\n\n\nclass Levenshtein(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": False,\n        \"ins_cost\": False,\n        \"del_cost\": False,\n        \"sub_cost\": False,\n        \"max_dist\": False,\n    }\n\n\nclass Lower(Expression, Func):\n    _sql_names = [\"LOWER\", \"LCASE\"]\n\n\nclass MatchAgainst(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": True, \"modifier\": False}\n\n\nclass Normalize(Expression, Func):\n    arg_types = {\"this\": True, \"form\": False, \"is_casefold\": False}\n\n\nclass NumberToStr(Expression, Func):\n    arg_types = {\"this\": True, \"format\": True, \"culture\": False}\n\n\nclass Overlay(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"from_\": True, \"for_\": False}\n\n\nclass Pad(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"fill_pattern\": False, \"is_left\": True}\n\n\nclass Repeat(Expression, Func):\n    arg_types = {\"this\": True, \"times\": True}\n\n\nclass Replace(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"replacement\": False}\n\n\nclass Reverse(Expression, Func):\n    pass\n\n\nclass Right(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass RtrimmedLength(Expression, Func):\n    pass\n\n\nclass Search(Expression, Func):\n    arg_types = {\n        \"this\": True,  # data_to_search / search_data\n        \"expression\": True,  # search_query / search_string\n        \"json_scope\": False,  # BigQuery: JSON_VALUES | JSON_KEYS | JSON_KEYS_AND_VALUES\n        \"analyzer\": False,  # Both: analyzer / ANALYZER\n        \"analyzer_options\": False,  # BigQuery: analyzer_options_values\n        \"search_mode\": False,  # Snowflake: OR | AND\n    }\n\n\nclass SearchIp(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Soundex(Expression, Func):\n    pass\n\n\nclass SoundexP123(Expression, Func):\n    pass\n\n\nclass Space(Expression, Func):\n    \"\"\"\n    SPACE(n) → string consisting of n blank characters\n    \"\"\"\n\n    pass\n\n\nclass Split(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"limit\": False,\n        \"null_returns_null\": False,\n        \"empty_delimiter_returns_whole\": False,\n    }\n\n\nclass SplitPart(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"delimiter\": False,\n        \"part_index\": False,\n        \"part_index_zero_as_one\": False,  # usually part_index is 1-based, however Snowflake allows 0 and treats it as 1\n        \"empty_delimiter_returns_whole\": False,  # whether the whole input string should be returned if the delimiter string is empty (i.e. Snowflake)\n    }\n\n\nclass Strtok(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"delimiter\": False,\n        \"part_index\": False,\n    }\n\n\nclass StartsWith(Expression, Func):\n    _sql_names = [\"STARTS_WITH\", \"STARTSWITH\"]\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass StrPosition(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"substr\": True,\n        \"position\": False,\n        \"occurrence\": False,\n        \"clamp_position\": False,\n    }\n\n\nclass StrToMap(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"pair_delim\": False,\n        \"key_value_delim\": False,\n        \"duplicate_resolution_callback\": False,\n    }\n\n\nclass String(Expression, Func):\n    arg_types = {\"this\": True, \"zone\": False}\n\n\nclass Stuff(Expression, Func):\n    _sql_names = [\"STUFF\", \"INSERT\"]\n    arg_types = {\"this\": True, \"start\": True, \"length\": True, \"expression\": True}\n\n\nclass Substring(Expression, Func):\n    _sql_names = [\"SUBSTRING\", \"SUBSTR\"]\n    arg_types = {\"this\": True, \"start\": False, \"length\": False}\n\n\nclass SubstringIndex(Expression, Func):\n    \"\"\"\n    SUBSTRING_INDEX(str, delim, count)\n\n    *count* > 0  → left slice before the *count*-th delimiter\n    *count* < 0  → right slice after the |count|-th delimiter\n    \"\"\"\n\n    arg_types = {\"this\": True, \"delimiter\": True, \"count\": True}\n\n\nclass Translate(Expression, Func):\n    arg_types = {\"this\": True, \"from_\": True, \"to\": True}\n\n\nclass Trim(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": False,\n        \"position\": False,\n        \"collation\": False,\n    }\n\n\nclass Unicode(Expression, Func):\n    pass\n\n\nclass Upper(Expression, Func):\n    _sql_names = [\"UPPER\", \"UCASE\"]\n\n\n# Encoding / base conversion\n\n\nclass Base64DecodeBinary(Expression, Func):\n    arg_types = {\"this\": True, \"alphabet\": False}\n\n\nclass Base64DecodeString(Expression, Func):\n    arg_types = {\"this\": True, \"alphabet\": False}\n\n\nclass Base64Encode(Expression, Func):\n    arg_types = {\"this\": True, \"max_line_length\": False, \"alphabet\": False}\n\n\nclass CodePointsToBytes(Expression, Func):\n    pass\n\n\nclass CodePointsToString(Expression, Func):\n    pass\n\n\nclass ConvertToCharset(Expression, Func):\n    arg_types = {\"this\": True, \"dest\": True, \"source\": False}\n\n\nclass Decode(Expression, Func):\n    arg_types = {\"this\": True, \"charset\": True, \"replace\": False}\n\n\nclass Encode(Expression, Func):\n    arg_types = {\"this\": True, \"charset\": True}\n\n\nclass FromBase(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass FromBase32(Expression, Func):\n    pass\n\n\nclass FromBase64(Expression, Func):\n    pass\n\n\nclass Hex(Expression, Func):\n    pass\n\n\nclass HexDecodeString(Expression, Func):\n    pass\n\n\nclass HexEncode(Expression, Func):\n    arg_types = {\"this\": True, \"case\": False}\n\n\nclass LowerHex(Hex):\n    pass\n\n\nclass SafeConvertBytesToString(Expression, Func):\n    pass\n\n\nclass ToBase32(Expression, Func):\n    pass\n\n\nclass ToBase64(Expression, Func):\n    pass\n\n\nclass ToBinary(Expression, Func):\n    arg_types = {\"this\": True, \"format\": False, \"safe\": False}\n\n\nclass ToChar(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"format\": False,\n        \"nlsparam\": False,\n        \"is_numeric\": False,\n    }\n\n\nclass ToCodePoints(Expression, Func):\n    pass\n\n\nclass ToDecfloat(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"format\": False,\n    }\n\n\nclass ToDouble(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"format\": False,\n        \"safe\": False,\n    }\n\n\nclass ToFile(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"path\": False,\n        \"safe\": False,\n    }\n\n\nclass ToNumber(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"format\": False,\n        \"nlsparam\": False,\n        \"precision\": False,\n        \"scale\": False,\n        \"safe\": False,\n        \"safe_name\": False,\n    }\n\n\nclass TryBase64DecodeBinary(Expression, Func):\n    arg_types = {\"this\": True, \"alphabet\": False}\n\n\nclass TryBase64DecodeString(Expression, Func):\n    arg_types = {\"this\": True, \"alphabet\": False}\n\n\nclass TryHexDecodeBinary(Expression, Func):\n    pass\n\n\nclass TryHexDecodeString(Expression, Func):\n    pass\n\n\nclass TryToDecfloat(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"format\": False,\n    }\n\n\nclass Unhex(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\n# Regex\n\n\nclass RegexpCount(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"position\": False,\n        \"parameters\": False,\n    }\n\n\nclass RegexpExtract(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"position\": False,\n        \"occurrence\": False,\n        \"parameters\": False,\n        \"group\": False,\n        \"null_if_pos_overflow\": False,  # for transpilation target behavior\n    }\n\n\nclass RegexpExtractAll(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"group\": False,\n        \"parameters\": False,\n        \"position\": False,\n        \"occurrence\": False,\n    }\n\n\nclass RegexpFullMatch(Expression, Binary, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"options\": False}\n\n\nclass RegexpILike(Expression, Binary, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"flag\": False}\n\n\nclass RegexpInstr(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"position\": False,\n        \"occurrence\": False,\n        \"option\": False,\n        \"parameters\": False,\n        \"group\": False,\n    }\n\n\nclass RegexpReplace(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"replacement\": False,\n        \"position\": False,\n        \"occurrence\": False,\n        \"modifiers\": False,\n        \"single_replace\": False,\n    }\n\n\nclass RegexpSplit(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"limit\": False}\n\n\n# Hashing / cryptographic\n\n\nclass Compress(Expression, Func):\n    arg_types = {\"this\": True, \"method\": False}\n\n\nclass Decrypt(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"passphrase\": True,\n        \"aad\": False,\n        \"encryption_method\": False,\n        \"safe\": False,\n    }\n\n\nclass DecryptRaw(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"key\": True,\n        \"iv\": True,\n        \"aad\": False,\n        \"encryption_method\": False,\n        \"aead\": False,\n        \"safe\": False,\n    }\n\n\nclass DecompressBinary(Expression, Func):\n    arg_types = {\"this\": True, \"method\": True}\n\n\nclass DecompressString(Expression, Func):\n    arg_types = {\"this\": True, \"method\": True}\n\n\nclass Encrypt(Expression, Func):\n    arg_types = {\"this\": True, \"passphrase\": True, \"aad\": False, \"encryption_method\": False}\n\n\nclass EncryptRaw(Expression, Func):\n    arg_types = {\"this\": True, \"key\": True, \"iv\": True, \"aad\": False, \"encryption_method\": False}\n\n\nclass CityHash64(Expression, Func):\n    arg_types = {\"expressions\": False}\n    is_var_len_args = True\n\n\nclass FarmFingerprint(Expression, Func):\n    arg_types = {\"expressions\": True}\n    is_var_len_args = True\n    _sql_names = [\"FARM_FINGERPRINT\", \"FARMFINGERPRINT64\"]\n\n\nclass MD5(Expression, Func):\n    _sql_names = [\"MD5\"]\n\n\nclass MD5Digest(Expression, Func):\n    arg_types = {\"this\": True, \"expressions\": False}\n    is_var_len_args = True\n    _sql_names = [\"MD5_DIGEST\"]\n\n\nclass MD5NumberLower64(Expression, Func):\n    pass\n\n\nclass MD5NumberUpper64(Expression, Func):\n    pass\n\n\nclass SHA(Expression, Func):\n    _sql_names = [\"SHA\", \"SHA1\"]\n\n\nclass SHA1Digest(Expression, Func):\n    pass\n\n\nclass SHA2(Expression, Func):\n    _sql_names = [\"SHA2\"]\n    arg_types = {\"this\": True, \"length\": False}\n\n\nclass SHA2Digest(Expression, Func):\n    arg_types = {\"this\": True, \"length\": False}\n\n\nclass StandardHash(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\n# Parse\n\n\nclass ParseBignumeric(Expression, Func):\n    pass\n\n\nclass ParseNumeric(Expression, Func):\n    pass\n\n\nclass ParseUrl(Expression, Func):\n    arg_types = {\"this\": True, \"part_to_extract\": False, \"key\": False, \"permissive\": False}\n"
  },
  {
    "path": "sqlglot/expressions/temporal.py",
    "content": "\"\"\"sqlglot expressions - date, time, and timestamp functions.\"\"\"\n\nfrom __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.expressions.core import (\n    Expression,\n    Func,\n    TimeUnit,\n    IntervalOp,\n    Literal,\n    Column,\n    TIMESTAMP_PARTS,\n)\nfrom sqlglot.expressions.datatypes import DataType, DType\n\nif t.TYPE_CHECKING:\n    from sqlglot.expressions.core import Expr, Neg\n\n\n# Current date/time\n\n\nclass CurrentDate(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass CurrentDatetime(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass CurrentTime(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass CurrentTimestamp(Expression, Func):\n    arg_types = {\"this\": False, \"sysdate\": False}\n\n\nclass CurrentTimestampLTZ(Expression, Func):\n    arg_types = {}\n\n\nclass CurrentTimezone(Expression, Func):\n    arg_types = {}\n\n\nclass Localtime(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass Localtimestamp(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass Systimestamp(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass UtcDate(Expression, Func):\n    arg_types = {}\n\n\nclass UtcTime(Expression, Func):\n    arg_types = {\"this\": False}\n\n\nclass UtcTimestamp(Expression, Func):\n    arg_types = {\"this\": False}\n\n\n# Date arithmetic\n\n\nclass AddMonths(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"preserve_end_of_month\": False}\n\n\nclass DateAdd(Expression, Func, IntervalOp):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass DateBin(Expression, Func, IntervalOp):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False, \"zone\": False, \"origin\": False}\n\n\nclass DateDiff(Expression, Func, TimeUnit):\n    _sql_names = [\"DATEDIFF\", \"DATE_DIFF\"]\n    arg_types = {\n        \"this\": True,\n        \"expression\": True,\n        \"unit\": False,\n        \"zone\": False,\n        \"big_int\": False,\n        \"date_part_boundary\": False,\n    }\n\n\nclass DateSub(Expression, Func, IntervalOp):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass DatetimeAdd(Expression, Func, IntervalOp):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass DatetimeDiff(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass DatetimeSub(Expression, Func, IntervalOp):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass MonthsBetween(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True, \"roundoff\": False}\n\n\nclass TimeAdd(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass TimeDiff(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass TimeSub(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass TimestampAdd(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass TimestampDiff(Expression, Func, TimeUnit):\n    _sql_names = [\"TIMESTAMPDIFF\", \"TIMESTAMP_DIFF\"]\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass TimestampSub(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\nclass TsOrDsAdd(Expression, Func, TimeUnit):\n    # return_type is used to correctly cast the arguments of this expression when transpiling it\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False, \"return_type\": False}\n\n    @property\n    def return_type(self) -> DataType:\n        return DataType.build(self.args.get(\"return_type\") or DType.DATE)\n\n\nclass TsOrDsDiff(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": False}\n\n\n# Truncation\n\n\nclass DatetimeTrunc(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"unit\": True, \"zone\": False}\n\n\nclass DateTrunc(Expression, Func):\n    arg_types = {\"unit\": True, \"this\": True, \"zone\": False, \"input_type_preserved\": False}\n\n    def __init__(self, **args):\n        # Across most dialects it's safe to unabbreviate the unit (e.g. 'Q' -> 'QUARTER') except Oracle\n        # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ROUND-and-TRUNC-Date-Functions.html\n        unabbreviate = args.pop(\"unabbreviate\", True)\n\n        unit = args.get(\"unit\")\n        if isinstance(unit, TimeUnit.VAR_LIKE) and not (\n            isinstance(unit, Column) and len(unit.parts) != 1\n        ):\n            unit_name = unit.name.upper()\n            if unabbreviate and unit_name in TimeUnit.UNABBREVIATED_UNIT_NAME:\n                unit_name = TimeUnit.UNABBREVIATED_UNIT_NAME[unit_name]\n\n            args[\"unit\"] = Literal.string(unit_name)\n\n        super().__init__(**args)\n\n    @property\n    def unit(self) -> Expr:\n        return self.args[\"unit\"]\n\n\nclass TimestampTrunc(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"unit\": True, \"zone\": False, \"input_type_preserved\": False}\n\n\nclass TimeSlice(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"expression\": True, \"unit\": True, \"kind\": False}\n\n\nclass TimeTrunc(Expression, Func, TimeUnit):\n    arg_types = {\"this\": True, \"unit\": True, \"zone\": False}\n\n\n# Date/time extraction\n\n\nclass Day(Expression, Func):\n    pass\n\n\nclass DayOfMonth(Expression, Func):\n    _sql_names = [\"DAY_OF_MONTH\", \"DAYOFMONTH\"]\n\n\nclass DayOfWeek(Expression, Func):\n    _sql_names = [\"DAY_OF_WEEK\", \"DAYOFWEEK\"]\n\n\nclass DayOfWeekIso(Expression, Func):\n    _sql_names = [\"DAYOFWEEK_ISO\", \"ISODOW\"]\n\n\nclass DayOfYear(Expression, Func):\n    _sql_names = [\"DAY_OF_YEAR\", \"DAYOFYEAR\"]\n\n\nclass Dayname(Expression, Func):\n    arg_types = {\"this\": True, \"abbreviated\": False}\n\n\nclass Extract(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass GetExtract(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Hour(Expression, Func):\n    pass\n\n\nclass Minute(Expression, Func):\n    pass\n\n\nclass Month(Expression, Func):\n    pass\n\n\nclass Monthname(Expression, Func):\n    arg_types = {\"this\": True, \"abbreviated\": False}\n\n\nclass Quarter(Expression, Func):\n    pass\n\n\nclass Second(Expression, Func):\n    pass\n\n\nclass ToDays(Expression, Func):\n    pass\n\n\nclass Week(Expression, Func):\n    arg_types = {\"this\": True, \"mode\": False}\n\n\nclass WeekOfYear(Expression, Func):\n    _sql_names = [\"WEEK_OF_YEAR\", \"WEEKOFYEAR\"]\n\n\nclass Year(Expression, Func):\n    pass\n\n\nclass YearOfWeek(Expression, Func):\n    _sql_names = [\"YEAR_OF_WEEK\", \"YEAROFWEEK\"]\n\n\nclass YearOfWeekIso(Expression, Func):\n    _sql_names = [\"YEAR_OF_WEEK_ISO\", \"YEAROFWEEKISO\"]\n\n\n# Date/time construction\n\n\nclass Date(Expression, Func):\n    arg_types = {\"this\": False, \"zone\": False, \"expressions\": False}\n    is_var_len_args = True\n\n\nclass DateFromParts(Expression, Func):\n    _sql_names = [\"DATE_FROM_PARTS\", \"DATEFROMPARTS\"]\n    arg_types = {\"year\": True, \"month\": False, \"day\": False, \"allow_overflow\": False}\n\n\nclass DateFromUnixDate(Expression, Func):\n    pass\n\n\nclass Datetime(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": False}\n\n\nclass GapFill(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"ts_column\": True,\n        \"bucket_width\": True,\n        \"partitioning_columns\": False,\n        \"value_columns\": False,\n        \"origin\": False,\n        \"ignore_nulls\": False,\n    }\n\n\nclass GenerateDateArray(Expression, Func):\n    arg_types = {\"start\": True, \"end\": True, \"step\": False}\n\n\nclass GenerateTimestampArray(Expression, Func):\n    arg_types = {\"start\": True, \"end\": True, \"step\": True}\n\n\nclass JustifyDays(Expression, Func):\n    pass\n\n\nclass JustifyHours(Expression, Func):\n    pass\n\n\nclass JustifyInterval(Expression, Func):\n    pass\n\n\nclass LastDay(Expression, Func, TimeUnit):\n    _sql_names = [\"LAST_DAY\", \"LAST_DAY_OF_MONTH\"]\n    arg_types = {\"this\": True, \"unit\": False}\n\n\nclass MakeInterval(Expression, Func):\n    arg_types = {\n        \"year\": False,\n        \"month\": False,\n        \"week\": False,\n        \"day\": False,\n        \"hour\": False,\n        \"minute\": False,\n        \"second\": False,\n    }\n\n\nclass NextDay(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass PreviousDay(Expression, Func):\n    arg_types = {\"this\": True, \"expression\": True}\n\n\nclass Time(Expression, Func):\n    arg_types = {\"this\": False, \"zone\": False}\n\n\nclass TimeFromParts(Expression, Func):\n    _sql_names = [\"TIME_FROM_PARTS\", \"TIMEFROMPARTS\"]\n    arg_types = {\n        \"hour\": True,\n        \"min\": True,\n        \"sec\": True,\n        \"nano\": False,\n        \"fractions\": False,\n        \"precision\": False,\n        \"overflow\": False,\n    }\n\n\nclass Timestamp(Expression, Func):\n    arg_types = {\"this\": False, \"zone\": False, \"with_tz\": False}\n\n\nclass TimestampFromParts(Expression, Func):\n    _sql_names = [\"TIMESTAMP_FROM_PARTS\", \"TIMESTAMPFROMPARTS\"]\n    arg_types = {\n        **TIMESTAMP_PARTS,\n        \"zone\": False,\n        \"milli\": False,\n        \"this\": False,\n        \"expression\": False,\n    }\n\n\nclass TimestampLtzFromParts(Expression, Func):\n    _sql_names = [\"TIMESTAMP_LTZ_FROM_PARTS\", \"TIMESTAMPLTZFROMPARTS\"]\n    arg_types = TIMESTAMP_PARTS.copy()\n\n\nclass TimestampTzFromParts(Expression, Func):\n    _sql_names = [\"TIMESTAMP_TZ_FROM_PARTS\", \"TIMESTAMPTZFROMPARTS\"]\n    arg_types = {\n        **TIMESTAMP_PARTS,\n        \"zone\": False,\n    }\n\n\n# Date/time conversion\n\n\nclass ConvertTimezone(Expression, Func):\n    arg_types = {\n        \"source_tz\": False,\n        \"target_tz\": True,\n        \"timestamp\": True,\n        \"options\": False,\n    }\n\n\nclass DateStrToDate(Expression, Func):\n    pass\n\n\nclass DateToDateStr(Expression, Func):\n    pass\n\n\nclass DateToDi(Expression, Func):\n    pass\n\n\nclass DiToDate(Expression, Func):\n    pass\n\n\nclass FromISO8601Timestamp(Expression, Func):\n    _sql_names = [\"FROM_ISO8601_TIMESTAMP\"]\n\n\nclass ParseDatetime(Expression, Func):\n    arg_types = {\"this\": True, \"format\": False, \"zone\": False}\n\n\nclass ParseTime(Expression, Func):\n    arg_types = {\"this\": True, \"format\": True}\n\n\nclass StrToDate(Expression, Func):\n    arg_types = {\"this\": True, \"format\": False, \"safe\": False}\n\n\nclass StrToTime(Expression, Func):\n    arg_types = {\"this\": True, \"format\": True, \"zone\": False, \"safe\": False, \"target_type\": False}\n\n\nclass StrToUnix(Expression, Func):\n    arg_types = {\"this\": False, \"format\": False}\n\n\nclass TimeStrToDate(Expression, Func):\n    pass\n\n\nclass TimeStrToTime(Expression, Func):\n    arg_types = {\"this\": True, \"zone\": False}\n\n\nclass TimeStrToUnix(Expression, Func):\n    pass\n\n\nclass TimeToStr(Expression, Func):\n    arg_types = {\"this\": True, \"format\": True, \"culture\": False, \"zone\": False}\n\n\nclass TimeToTimeStr(Expression, Func):\n    pass\n\n\nclass TimeToUnix(Expression, Func):\n    pass\n\n\nclass TsOrDiToDi(Expression, Func):\n    pass\n\n\nclass TsOrDsToDate(Expression, Func):\n    arg_types = {\"this\": True, \"format\": False, \"safe\": False}\n\n\nclass TsOrDsToDateStr(Expression, Func):\n    pass\n\n\nclass TsOrDsToDatetime(Expression, Func):\n    pass\n\n\nclass TsOrDsToTime(Expression, Func):\n    arg_types = {\"this\": True, \"format\": False, \"safe\": False}\n\n\nclass TsOrDsToTimestamp(Expression, Func):\n    pass\n\n\nclass UnixDate(Expression, Func):\n    pass\n\n\nclass UnixMicros(Expression, Func):\n    pass\n\n\nclass UnixMillis(Expression, Func):\n    pass\n\n\nclass UnixSeconds(Expression, Func):\n    pass\n\n\nclass UnixToStr(Expression, Func):\n    arg_types = {\"this\": True, \"format\": False}\n\n\nclass UnixToTime(Expression, Func):\n    arg_types = {\n        \"this\": True,\n        \"scale\": False,\n        \"zone\": False,\n        \"hours\": False,\n        \"minutes\": False,\n        \"format\": False,\n        \"target_type\": False,\n    }\n\n    SECONDS: t.ClassVar[Literal | Neg] = Literal.number(0)\n    DECIS: t.ClassVar[Literal | Neg] = Literal.number(1)\n    CENTIS: t.ClassVar[Literal | Neg] = Literal.number(2)\n    MILLIS: t.ClassVar[Literal | Neg] = Literal.number(3)\n    DECIMILLIS: t.ClassVar[Literal | Neg] = Literal.number(4)\n    CENTIMILLIS: t.ClassVar[Literal | Neg] = Literal.number(5)\n    MICROS: t.ClassVar[Literal | Neg] = Literal.number(6)\n    DECIMICROS: t.ClassVar[Literal | Neg] = Literal.number(7)\n    CENTIMICROS: t.ClassVar[Literal | Neg] = Literal.number(8)\n    NANOS: t.ClassVar[Literal | Neg] = Literal.number(9)\n\n\nclass UnixToTimeStr(Expression, Func):\n    pass\n"
  },
  {
    "path": "sqlglot/generator.py",
    "content": "from __future__ import annotations\n\nimport logging\nimport re\nimport typing as t\nfrom collections import defaultdict\nfrom functools import reduce, wraps\n\nfrom sqlglot import exp\nfrom sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages\nfrom sqlglot.expressions import apply_index_offset\nfrom sqlglot.helper import csv, name_sequence, seq_get\nfrom sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS\nfrom sqlglot.time import format_time\nfrom sqlglot.tokens import TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from sqlglot.dialects.dialect import DialectType\n\n    G = t.TypeVar(\"G\", bound=\"Generator\")\n    GeneratorMethod = t.Callable[[G, E], str]\n\nlogger = logging.getLogger(\"sqlglot\")\n\nESCAPED_UNICODE_RE = re.compile(r\"\\\\(\\d+)\")\nUNSUPPORTED_TEMPLATE = \"Argument '{}' is not supported for expression '{}' when targeting {}.\"\n\n\ndef unsupported_args(\n    *args: t.Union[str, t.Tuple[str, str]],\n) -> t.Callable[[GeneratorMethod], GeneratorMethod]:\n    \"\"\"\n    Decorator that can be used to mark certain args of an `Expr` subclass as unsupported.\n    It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).\n    \"\"\"\n    diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {}\n    for arg in args:\n        if isinstance(arg, str):\n            diagnostic_by_arg[arg] = None\n        else:\n            diagnostic_by_arg[arg[0]] = arg[1]\n\n    def decorator(func: GeneratorMethod) -> GeneratorMethod:\n        @wraps(func)\n        def _func(generator: G, expression: E) -> str:\n            expression_name = expression.__class__.__name__\n            dialect_name = generator.dialect.__class__.__name__\n\n            for arg_name, diagnostic in diagnostic_by_arg.items():\n                if expression.args.get(arg_name):\n                    diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format(\n                        arg_name, expression_name, dialect_name\n                    )\n                    generator.unsupported(diagnostic)\n\n            return func(generator, expression)\n\n        return _func\n\n    return decorator\n\n\nclass _Generator(type):\n    def __new__(cls, clsname, bases, attrs):\n        klass = super().__new__(cls, clsname, bases, attrs)\n\n        # Remove transforms that correspond to unsupported JSONPathPart expressions\n        for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS:\n            klass.TRANSFORMS.pop(part, None)\n\n        return klass\n\n\nclass Generator(metaclass=_Generator):\n    \"\"\"\n    Generator converts a given syntax tree to the corresponding SQL string.\n\n    Args:\n        pretty: Whether to format the produced SQL string.\n            Default: False.\n        identify: Determines when an identifier should be quoted. Possible values are:\n            False (default): Never quote, except in cases where it's mandatory by the dialect.\n            True: Always quote except for specials cases.\n            'safe': Only quote identifiers that are case insensitive.\n        normalize: Whether to normalize identifiers to lowercase.\n            Default: False.\n        pad: The pad size in a formatted string. For example, this affects the indentation of\n            a projection in a query, relative to its nesting level.\n            Default: 2.\n        indent: The indentation size in a formatted string. For example, this affects the\n            indentation of subqueries and filters under a `WHERE` clause.\n            Default: 2.\n        normalize_functions: How to normalize function names. Possible values are:\n            \"upper\" or True (default): Convert names to uppercase.\n            \"lower\": Convert names to lowercase.\n            False: Disables function name normalization.\n        unsupported_level: Determines the generator's behavior when it encounters unsupported expressions.\n            Default ErrorLevel.WARN.\n        max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError.\n            This is only relevant if unsupported_level is ErrorLevel.RAISE.\n            Default: 3\n        leading_comma: Whether the comma is leading or trailing in select expressions.\n            This is only relevant when generating in pretty mode.\n            Default: False\n        max_text_width: The max number of characters in a segment before creating new lines in pretty mode.\n            The default is on the smaller end because the length only represents a segment and not the true\n            line length.\n            Default: 80\n        comments: Whether to preserve comments in the output SQL code.\n            Default: True\n    \"\"\"\n\n    TRANSFORMS: t.Dict[t.Type[exp.Expr], t.Callable[..., str]] = {\n        **JSON_PATH_PART_TRANSFORMS,\n        exp.Adjacent: lambda self, e: self.binary(e, \"-|-\"),\n        exp.AllowedValuesProperty: lambda self, e: (\n            f\"ALLOWED_VALUES {self.expressions(e, flat=True)}\"\n        ),\n        exp.AnalyzeColumns: lambda self, e: self.sql(e, \"this\"),\n        exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix=\"WITH \", sep=\" \"),\n        exp.ArrayContainsAll: lambda self, e: self.binary(e, \"@>\"),\n        exp.ArrayOverlaps: lambda self, e: self.binary(e, \"&&\"),\n        exp.AssumeColumnConstraint: lambda self, e: f\"ASSUME ({self.sql(e, 'this')})\",\n        exp.AutoRefreshProperty: lambda self, e: f\"AUTO REFRESH {self.sql(e, 'this')}\",\n        exp.BackupProperty: lambda self, e: f\"BACKUP {self.sql(e, 'this')}\",\n        exp.CaseSpecificColumnConstraint: lambda _, e: (\n            f\"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC\"\n        ),\n        exp.Ceil: lambda self, e: self.ceil_floor(e),\n        exp.CharacterSetColumnConstraint: lambda self, e: f\"CHARACTER SET {self.sql(e, 'this')}\",\n        exp.CharacterSetProperty: lambda self, e: (\n            f\"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}\"\n        ),\n        exp.ClusteredColumnConstraint: lambda self, e: (\n            f\"CLUSTERED ({self.expressions(e, 'this', indent=False)})\"\n        ),\n        exp.CollateColumnConstraint: lambda self, e: f\"COLLATE {self.sql(e, 'this')}\",\n        exp.CommentColumnConstraint: lambda self, e: f\"COMMENT {self.sql(e, 'this')}\",\n        exp.ConnectByRoot: lambda self, e: f\"CONNECT_BY_ROOT {self.sql(e, 'this')}\",\n        exp.ConvertToCharset: lambda self, e: self.func(\n            \"CONVERT\", e.this, e.args[\"dest\"], e.args.get(\"source\")\n        ),\n        exp.CopyGrantsProperty: lambda *_: \"COPY GRANTS\",\n        exp.CredentialsProperty: lambda self, e: (\n            f\"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})\"\n        ),\n        exp.CurrentCatalog: lambda *_: \"CURRENT_CATALOG\",\n        exp.SessionUser: lambda *_: \"SESSION_USER\",\n        exp.DateFormatColumnConstraint: lambda self, e: f\"FORMAT {self.sql(e, 'this')}\",\n        exp.DefaultColumnConstraint: lambda self, e: f\"DEFAULT {self.sql(e, 'this')}\",\n        exp.ApiProperty: lambda *_: \"API\",\n        exp.ApplicationProperty: lambda *_: \"APPLICATION\",\n        exp.CatalogProperty: lambda *_: \"CATALOG\",\n        exp.ComputeProperty: lambda *_: \"COMPUTE\",\n        exp.DatabaseProperty: lambda *_: \"DATABASE\",\n        exp.DynamicProperty: lambda *_: \"DYNAMIC\",\n        exp.EmptyProperty: lambda *_: \"EMPTY\",\n        exp.EncodeColumnConstraint: lambda self, e: f\"ENCODE {self.sql(e, 'this')}\",\n        exp.EndStatement: lambda *_: \"END\",\n        exp.EnviromentProperty: lambda self, e: f\"ENVIRONMENT ({self.expressions(e, flat=True)})\",\n        exp.HandlerProperty: lambda self, e: f\"HANDLER {self.sql(e, 'this')}\",\n        exp.ParameterStyleProperty: lambda self, e: f\"PARAMETER STYLE {self.sql(e, 'this')}\",\n        exp.EphemeralColumnConstraint: lambda self, e: (\n            f\"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}\"\n        ),\n        exp.ExcludeColumnConstraint: lambda self, e: f\"EXCLUDE {self.sql(e, 'this').lstrip()}\",\n        exp.ExecuteAsProperty: lambda self, e: self.naked_property(e),\n        exp.Except: lambda self, e: self.set_operations(e),\n        exp.ExternalProperty: lambda *_: \"EXTERNAL\",\n        exp.Floor: lambda self, e: self.ceil_floor(e),\n        exp.Get: lambda self, e: self.get_put_sql(e),\n        exp.GlobalProperty: lambda *_: \"GLOBAL\",\n        exp.HeapProperty: lambda *_: \"HEAP\",\n        exp.HybridProperty: lambda *_: \"HYBRID\",\n        exp.IcebergProperty: lambda *_: \"ICEBERG\",\n        exp.InheritsProperty: lambda self, e: f\"INHERITS ({self.expressions(e, flat=True)})\",\n        exp.InlineLengthColumnConstraint: lambda self, e: f\"INLINE LENGTH {self.sql(e, 'this')}\",\n        exp.InputModelProperty: lambda self, e: f\"INPUT{self.sql(e, 'this')}\",\n        exp.Intersect: lambda self, e: self.set_operations(e),\n        exp.IntervalSpan: lambda self, e: f\"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}\",\n        exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DType.BIGINT)),\n        exp.JSONBContainsAnyTopKeys: lambda self, e: self.binary(e, \"?|\"),\n        exp.JSONBContainsAllTopKeys: lambda self, e: self.binary(e, \"?&\"),\n        exp.JSONBDeleteAtPath: lambda self, e: self.binary(e, \"#-\"),\n        exp.JSONObject: lambda self, e: self._jsonobject_sql(e),\n        exp.JSONObjectAgg: lambda self, e: self._jsonobject_sql(e),\n        exp.LanguageProperty: lambda self, e: self.naked_property(e),\n        exp.LocationProperty: lambda self, e: self.naked_property(e),\n        exp.LogProperty: lambda _, e: f\"{'NO ' if e.args.get('no') else ''}LOG\",\n        exp.MaskingProperty: lambda *_: \"MASKING\",\n        exp.MaterializedProperty: lambda *_: \"MATERIALIZED\",\n        exp.NetFunc: lambda self, e: f\"NET.{self.sql(e, 'this')}\",\n        exp.NetworkProperty: lambda *_: \"NETWORK\",\n        exp.NonClusteredColumnConstraint: lambda self, e: (\n            f\"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})\"\n        ),\n        exp.NoPrimaryIndexProperty: lambda *_: \"NO PRIMARY INDEX\",\n        exp.NotForReplicationColumnConstraint: lambda *_: \"NOT FOR REPLICATION\",\n        exp.OnCommitProperty: lambda _, e: (\n            f\"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS\"\n        ),\n        exp.OnProperty: lambda self, e: f\"ON {self.sql(e, 'this')}\",\n        exp.OnUpdateColumnConstraint: lambda self, e: f\"ON UPDATE {self.sql(e, 'this')}\",\n        exp.Operator: lambda self, e: self.binary(e, \"\"),  # The operator is produced in `binary`\n        exp.OutputModelProperty: lambda self, e: f\"OUTPUT{self.sql(e, 'this')}\",\n        exp.ExtendsLeft: lambda self, e: self.binary(e, \"&<\"),\n        exp.ExtendsRight: lambda self, e: self.binary(e, \"&>\"),\n        exp.PathColumnConstraint: lambda self, e: f\"PATH {self.sql(e, 'this')}\",\n        exp.PartitionedByBucket: lambda self, e: self.func(\"BUCKET\", e.this, e.expression),\n        exp.PartitionByTruncate: lambda self, e: self.func(\"TRUNCATE\", e.this, e.expression),\n        exp.PivotAny: lambda self, e: f\"ANY{self.sql(e, 'this')}\",\n        exp.PositionalColumn: lambda self, e: f\"#{self.sql(e, 'this')}\",\n        exp.ProjectionPolicyColumnConstraint: lambda self, e: (\n            f\"PROJECTION POLICY {self.sql(e, 'this')}\"\n        ),\n        exp.ZeroFillColumnConstraint: lambda self, e: \"ZEROFILL\",\n        exp.Put: lambda self, e: self.get_put_sql(e),\n        exp.RemoteWithConnectionModelProperty: lambda self, e: (\n            f\"REMOTE WITH CONNECTION {self.sql(e, 'this')}\"\n        ),\n        exp.ReturnsProperty: lambda self, e: (\n            \"RETURNS NULL ON NULL INPUT\" if e.args.get(\"null\") else self.naked_property(e)\n        ),\n        exp.RowAccessProperty: lambda *_: \"ROW ACCESS\",\n        exp.SafeFunc: lambda self, e: f\"SAFE.{self.sql(e, 'this')}\",\n        exp.SampleProperty: lambda self, e: f\"SAMPLE BY {self.sql(e, 'this')}\",\n        exp.SecureProperty: lambda *_: \"SECURE\",\n        exp.SecurityIntegrationProperty: lambda *_: \"SECURITY\",\n        exp.SetConfigProperty: lambda self, e: self.sql(e, \"this\"),\n        exp.SetProperty: lambda _, e: f\"{'MULTI' if e.args.get('multi') else ''}SET\",\n        exp.SettingsProperty: lambda self, e: f\"SETTINGS{self.seg('')}{(self.expressions(e))}\",\n        exp.SharingProperty: lambda self, e: f\"SHARING={self.sql(e, 'this')}\",\n        exp.SqlReadWriteProperty: lambda _, e: e.name,\n        exp.SqlSecurityProperty: lambda self, e: f\"SQL SECURITY {self.sql(e, 'this')}\",\n        exp.StabilityProperty: lambda _, e: e.name,\n        exp.Stream: lambda self, e: f\"STREAM {self.sql(e, 'this')}\",\n        exp.StreamingTableProperty: lambda *_: \"STREAMING\",\n        exp.StrictProperty: lambda *_: \"STRICT\",\n        exp.SwapTable: lambda self, e: f\"SWAP WITH {self.sql(e, 'this')}\",\n        exp.TableColumn: lambda self, e: self.sql(e.this),\n        exp.Tags: lambda self, e: f\"TAG ({self.expressions(e, flat=True)})\",\n        exp.TemporaryProperty: lambda *_: \"TEMPORARY\",\n        exp.TitleColumnConstraint: lambda self, e: f\"TITLE {self.sql(e, 'this')}\",\n        exp.ToMap: lambda self, e: f\"MAP {self.sql(e, 'this')}\",\n        exp.ToTableProperty: lambda self, e: f\"TO {self.sql(e.this)}\",\n        exp.TransformModelProperty: lambda self, e: self.func(\"TRANSFORM\", *e.expressions),\n        exp.TransientProperty: lambda *_: \"TRANSIENT\",\n        exp.TriggerExecute: lambda self, e: f\"EXECUTE FUNCTION {self.sql(e, 'this')}\",\n        exp.Union: lambda self, e: self.set_operations(e),\n        exp.UnloggedProperty: lambda *_: \"UNLOGGED\",\n        exp.UsingTemplateProperty: lambda self, e: f\"USING TEMPLATE {self.sql(e, 'this')}\",\n        exp.UsingData: lambda self, e: f\"USING DATA {self.sql(e, 'this')}\",\n        exp.UppercaseColumnConstraint: lambda *_: \"UPPERCASE\",\n        exp.UtcDate: lambda self, e: self.sql(exp.CurrentDate(this=exp.Literal.string(\"UTC\"))),\n        exp.UtcTime: lambda self, e: self.sql(exp.CurrentTime(this=exp.Literal.string(\"UTC\"))),\n        exp.UtcTimestamp: lambda self, e: self.sql(\n            exp.CurrentTimestamp(this=exp.Literal.string(\"UTC\"))\n        ),\n        exp.Variadic: lambda self, e: f\"VARIADIC {self.sql(e, 'this')}\",\n        exp.VarMap: lambda self, e: self.func(\"MAP\", e.args[\"keys\"], e.args[\"values\"]),\n        exp.ViewAttributeProperty: lambda self, e: f\"WITH {self.sql(e, 'this')}\",\n        exp.VolatileProperty: lambda *_: \"VOLATILE\",\n        exp.WithJournalTableProperty: lambda self, e: f\"WITH JOURNAL TABLE={self.sql(e, 'this')}\",\n        exp.WithProcedureOptions: lambda self, e: f\"WITH {self.expressions(e, flat=True)}\",\n        exp.WithSchemaBindingProperty: lambda self, e: f\"WITH SCHEMA {self.sql(e, 'this')}\",\n        exp.WithOperator: lambda self, e: f\"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}\",\n        exp.ForceProperty: lambda *_: \"FORCE\",\n    }\n\n    # Whether null ordering is supported in order by\n    # True: Full Support, None: No support, False: No support for certain cases\n    # such as window specifications, aggregate functions etc\n    NULL_ORDERING_SUPPORTED: t.Optional[bool] = True\n\n    # Window functions that support NULLS FIRST/LAST\n    WINDOW_FUNCS_WITH_NULL_ORDERING: t.ClassVar[t.Tuple[t.Type[exp.Expression], ...]] = ()\n\n    # Whether ignore nulls is inside the agg or outside.\n    # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER\n    IGNORE_NULLS_IN_FUNC = False\n\n    # Whether IGNORE NULLS is placed before ORDER BY in the agg.\n    # FIRST(x IGNORE NULLS ORDER BY y) vs FIRST(x ORDER BY y IGNORE NULLS)\n    IGNORE_NULLS_BEFORE_ORDER = True\n\n    # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported\n    LOCKING_READS_SUPPORTED = False\n\n    # Whether the EXCEPT and INTERSECT operations can return duplicates\n    EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True\n\n    # Wrap derived values in parens, usually standard but spark doesn't support it\n    WRAP_DERIVED_VALUES = True\n\n    # Whether create function uses an AS before the RETURN\n    CREATE_FUNCTION_RETURN_AS = True\n\n    # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed\n    MATCHED_BY_SOURCE = True\n\n    # Whether the INTERVAL expression works only with values like '1 day'\n    SINGLE_STRING_INTERVAL = False\n\n    # Whether the plural form of date parts like day (i.e. \"days\") is supported in INTERVALs\n    INTERVAL_ALLOWS_PLURAL_FORM = True\n\n    # Whether limit and fetch are supported (possible values: \"ALL\", \"LIMIT\", \"FETCH\")\n    LIMIT_FETCH = \"ALL\"\n\n    # Whether limit and fetch allows expresions or just limits\n    LIMIT_ONLY_LITERALS = False\n\n    # Whether a table is allowed to be renamed with a db\n    RENAME_TABLE_WITH_DB = True\n\n    # The separator for grouping sets and rollups\n    GROUPINGS_SEP = \",\"\n\n    # The string used for creating an index on a table\n    INDEX_ON = \"ON\"\n\n    # Separator for IN/OUT parameter mode (Oracle uses \" \" for \"IN OUT\", PostgreSQL uses \"\" for \"INOUT\")\n    INOUT_SEPARATOR = \" \"\n\n    # Whether join hints should be generated\n    JOIN_HINTS = True\n\n    # Whether directed joins are supported\n    DIRECTED_JOINS = False\n\n    # Whether table hints should be generated\n    TABLE_HINTS = True\n\n    # Whether query hints should be generated\n    QUERY_HINTS = True\n\n    # What kind of separator to use for query hints\n    QUERY_HINT_SEP = \", \"\n\n    # Whether comparing against booleans (e.g. x IS TRUE) is supported\n    IS_BOOL_ALLOWED = True\n\n    # Whether to include the \"SET\" keyword in the \"INSERT ... ON DUPLICATE KEY UPDATE\" statement\n    DUPLICATE_KEY_UPDATE_WITH_SET = True\n\n    # Whether to generate the limit as TOP <value> instead of LIMIT <value>\n    LIMIT_IS_TOP = False\n\n    # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ...\n    RETURNING_END = True\n\n    # Whether to generate an unquoted value for EXTRACT's date part argument\n    EXTRACT_ALLOWS_QUOTES = True\n\n    # Whether TIMETZ / TIMESTAMPTZ will be generated using the \"WITH TIME ZONE\" syntax\n    TZ_TO_WITH_TIME_ZONE = False\n\n    # Whether the NVL2 function is supported\n    NVL2_SUPPORTED = True\n\n    # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax\n    SELECT_KINDS: t.Tuple[str, ...] = (\"STRUCT\", \"VALUE\")\n\n    # Whether VALUES statements can be used as derived tables.\n    # MySQL 5 and Redshift do not allow this, so when False, it will convert\n    # SELECT * VALUES into SELECT UNION\n    VALUES_AS_TABLE = True\n\n    # Whether the word COLUMN is included when adding a column with ALTER TABLE\n    ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True\n\n    # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery)\n    UNNEST_WITH_ORDINALITY = True\n\n    # Whether FILTER (WHERE cond) can be used for conditional aggregation\n    AGGREGATE_FILTER_SUPPORTED = True\n\n    # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds\n    SEMI_ANTI_JOIN_WITH_SIDE = True\n\n    # Whether to include the type of a computed column in the CREATE DDL\n    COMPUTED_COLUMN_WITH_TYPE = True\n\n    # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY\n    SUPPORTS_TABLE_COPY = True\n\n    # Whether parentheses are required around the table sample's expression\n    TABLESAMPLE_REQUIRES_PARENS = True\n\n    # Whether a table sample clause's size needs to be followed by the ROWS keyword\n    TABLESAMPLE_SIZE_IS_ROWS = True\n\n    # The keyword(s) to use when generating a sample clause\n    TABLESAMPLE_KEYWORDS = \"TABLESAMPLE\"\n\n    # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI\n    TABLESAMPLE_WITH_METHOD = True\n\n    # The keyword to use when specifying the seed of a sample clause\n    TABLESAMPLE_SEED_KEYWORD = \"SEED\"\n\n    # Whether COLLATE is a function instead of a binary operator\n    COLLATE_IS_FUNC = False\n\n    # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle)\n    DATA_TYPE_SPECIFIERS_ALLOWED = False\n\n    # Whether conditions require booleans WHERE x = 0 vs WHERE x\n    ENSURE_BOOLS = False\n\n    # Whether the \"RECURSIVE\" keyword is required when defining recursive CTEs\n    CTE_RECURSIVE_KEYWORD_REQUIRED = True\n\n    # Whether CONCAT requires >1 arguments\n    SUPPORTS_SINGLE_ARG_CONCAT = True\n\n    # Whether LAST_DAY function supports a date part argument\n    LAST_DAY_SUPPORTS_DATE_PART = True\n\n    # Whether named columns are allowed in table aliases\n    SUPPORTS_TABLE_ALIAS_COLUMNS = True\n\n    # Whether UNPIVOT aliases are Identifiers (False means they're Literals)\n    UNPIVOT_ALIASES_ARE_IDENTIFIERS = True\n\n    # What delimiter to use for separating JSON key/value pairs\n    JSON_KEY_VALUE_PAIR_SEP = \":\"\n\n    # INSERT OVERWRITE TABLE x override\n    INSERT_OVERWRITE = \" OVERWRITE TABLE\"\n\n    # Whether the SELECT .. INTO syntax is used instead of CTAS\n    SUPPORTS_SELECT_INTO = False\n\n    # Whether UNLOGGED tables can be created\n    SUPPORTS_UNLOGGED_TABLES = False\n\n    # Whether the CREATE TABLE LIKE statement is supported\n    SUPPORTS_CREATE_TABLE_LIKE = True\n\n    # Whether the LikeProperty needs to be specified inside of the schema clause\n    LIKE_PROPERTY_INSIDE_SCHEMA = False\n\n    # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be\n    # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args\n    MULTI_ARG_DISTINCT = True\n\n    # Whether the JSON extraction operators expect a value of type JSON\n    JSON_TYPE_REQUIRED_FOR_EXTRACTION = False\n\n    # Whether bracketed keys like [\"foo\"] are supported in JSON paths\n    JSON_PATH_BRACKETED_KEY_SUPPORTED = True\n\n    # Whether to escape keys using single quotes in JSON paths\n    JSON_PATH_SINGLE_QUOTE_ESCAPE = False\n\n    # The JSONPathPart expressions supported by this dialect\n    SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy()\n\n    # Whether any(f(x) for x in array) can be implemented by this dialect\n    CAN_IMPLEMENT_ARRAY_ANY = False\n\n    # Whether the function TO_NUMBER is supported\n    SUPPORTS_TO_NUMBER = True\n\n    # Whether EXCLUDE in window specification is supported\n    SUPPORTS_WINDOW_EXCLUDE = False\n\n    # Whether or not set op modifiers apply to the outer set op or select.\n    # SELECT * FROM x UNION SELECT * FROM y LIMIT 1\n    # True means limit 1 happens after the set op, False means it it happens on y.\n    SET_OP_MODIFIERS = True\n\n    # Whether parameters from COPY statement are wrapped in parentheses\n    COPY_PARAMS_ARE_WRAPPED = True\n\n    # Whether values of params are set with \"=\" token or empty space\n    COPY_PARAMS_EQ_REQUIRED = False\n\n    # Whether COPY statement has INTO keyword\n    COPY_HAS_INTO_KEYWORD = True\n\n    # Whether the conditional TRY(expression) function is supported\n    TRY_SUPPORTED = True\n\n    # Whether the UESCAPE syntax in unicode strings is supported\n    SUPPORTS_UESCAPE = True\n\n    # Function used to replace escaped unicode codes in unicode strings\n    UNICODE_SUBSTITUTE: t.Optional[t.Callable[[re.Match[str]], str]] = None\n\n    # The keyword to use when generating a star projection with excluded columns\n    STAR_EXCEPT = \"EXCEPT\"\n\n    # The HEX function name\n    HEX_FUNC = \"HEX\"\n\n    # The keywords to use when prefixing & separating WITH based properties\n    WITH_PROPERTIES_PREFIX = \"WITH\"\n\n    # Whether to quote the generated expression of exp.JsonPath\n    QUOTE_JSON_PATH = True\n\n    # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space)\n    PAD_FILL_PATTERN_IS_REQUIRED = False\n\n    # Whether a projection can explode into multiple rows, e.g. by unnesting an array.\n    SUPPORTS_EXPLODING_PROJECTIONS = True\n\n    # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version\n    ARRAY_CONCAT_IS_VAR_LEN = True\n\n    # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone\n    SUPPORTS_CONVERT_TIMEZONE = False\n\n    # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5)\n    SUPPORTS_MEDIAN = True\n\n    # Whether UNIX_SECONDS(timestamp) is supported\n    SUPPORTS_UNIX_SECONDS = False\n\n    # Whether to wrap <props> in `AlterSet`, e.g., ALTER ... SET (<props>)\n    ALTER_SET_WRAPPED = False\n\n    # Whether to normalize the date parts in EXTRACT(<date_part> FROM <expr>) into a common representation\n    # For instance, to extract the day of week in ISO semantics, one can use ISODOW, DAYOFWEEKISO etc depending on the dialect.\n    # TODO: The normalization should be done by default once we've tested it across all dialects.\n    NORMALIZE_EXTRACT_DATE_PARTS = False\n\n    # The name to generate for the JSONPath expression. If `None`, only `this` will be generated\n    PARSE_JSON_NAME: t.Optional[str] = \"PARSE_JSON\"\n\n    # The function name of the exp.ArraySize expression\n    ARRAY_SIZE_NAME: str = \"ARRAY_LENGTH\"\n\n    # The syntax to use when altering the type of a column\n    ALTER_SET_TYPE = \"SET DATA TYPE\"\n\n    # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB)\n    # None -> Doesn't support it at all\n    # False (DuckDB) -> Has backwards-compatible support, but preferably generated without\n    # True (Postgres) -> Explicitly requires it\n    ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None\n\n    # Whether a multi-argument DECODE(...) function is supported. If not, a CASE expression is generated\n    SUPPORTS_DECODE_CASE = True\n\n    # Whether SYMMETRIC and ASYMMETRIC flags are supported with BETWEEN expression\n    SUPPORTS_BETWEEN_FLAGS = False\n\n    # Whether LIKE and ILIKE support quantifiers such as LIKE ANY/ALL/SOME\n    SUPPORTS_LIKE_QUANTIFIERS = True\n\n    # Prefix which is appended to exp.Table expressions in MATCH AGAINST\n    MATCH_AGAINST_TABLE_PREFIX: t.Optional[str] = None\n\n    # Whether to include the VARIABLE keyword for SET assignments\n    SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD = False\n\n    # The keyword to use for default value assignment in DECLARE statements\n    DECLARE_DEFAULT_ASSIGNMENT = \"=\"\n\n    # Whether FROM is supported in UPDATE statements or if joins must be generated instead, e.g:\n    # Supported (Postgres, Doris etc): UPDATE t1 SET t1.a = t2.b FROM t2\n    # Unsupported (MySQL, SingleStore): UPDATE t1 JOIN t2 ON TRUE SET t1.a = t2.b\n    UPDATE_STATEMENT_SUPPORTS_FROM = True\n\n    # Whether SELECT *, ... EXCLUDE requires wrapping in a subquery for transpilation.\n    STAR_EXCLUDE_REQUIRES_DERIVED_TABLE = True\n\n    TYPE_MAPPING = {\n        exp.DType.DATETIME2: \"TIMESTAMP\",\n        exp.DType.NCHAR: \"CHAR\",\n        exp.DType.NVARCHAR: \"VARCHAR\",\n        exp.DType.MEDIUMTEXT: \"TEXT\",\n        exp.DType.LONGTEXT: \"TEXT\",\n        exp.DType.TINYTEXT: \"TEXT\",\n        exp.DType.BLOB: \"VARBINARY\",\n        exp.DType.MEDIUMBLOB: \"BLOB\",\n        exp.DType.LONGBLOB: \"BLOB\",\n        exp.DType.TINYBLOB: \"BLOB\",\n        exp.DType.INET: \"INET\",\n        exp.DType.ROWVERSION: \"VARBINARY\",\n        exp.DType.SMALLDATETIME: \"TIMESTAMP\",\n    }\n\n    UNSUPPORTED_TYPES: set[exp.DType] = set()\n\n    TIME_PART_SINGULARS = {\n        \"MICROSECONDS\": \"MICROSECOND\",\n        \"SECONDS\": \"SECOND\",\n        \"MINUTES\": \"MINUTE\",\n        \"HOURS\": \"HOUR\",\n        \"DAYS\": \"DAY\",\n        \"WEEKS\": \"WEEK\",\n        \"MONTHS\": \"MONTH\",\n        \"QUARTERS\": \"QUARTER\",\n        \"YEARS\": \"YEAR\",\n    }\n\n    AFTER_HAVING_MODIFIER_TRANSFORMS = {\n        \"cluster\": lambda self, e: self.sql(e, \"cluster\"),\n        \"distribute\": lambda self, e: self.sql(e, \"distribute\"),\n        \"sort\": lambda self, e: self.sql(e, \"sort\"),\n        \"windows\": lambda self, e: (\n            self.seg(\"WINDOW \") + self.expressions(e, key=\"windows\", flat=True)\n            if e.args.get(\"windows\")\n            else \"\"\n        ),\n        \"qualify\": lambda self, e: self.sql(e, \"qualify\"),\n    }\n\n    TOKEN_MAPPING: t.Dict[TokenType, str] = {}\n\n    STRUCT_DELIMITER = (\"<\", \">\")\n\n    PARAMETER_TOKEN = \"@\"\n    NAMED_PLACEHOLDER_TOKEN = \":\"\n\n    EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set()\n\n    PROPERTIES_LOCATION = {\n        exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE,\n        exp.ApiProperty: exp.Properties.Location.POST_CREATE,\n        exp.ApplicationProperty: exp.Properties.Location.POST_CREATE,\n        exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.BackupProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME,\n        exp.CatalogProperty: exp.Properties.Location.POST_CREATE,\n        exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.ChecksumProperty: exp.Properties.Location.POST_NAME,\n        exp.CollateProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.ComputeProperty: exp.Properties.Location.POST_CREATE,\n        exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.Cluster: exp.Properties.Location.POST_SCHEMA,\n        exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME,\n        exp.DatabaseProperty: exp.Properties.Location.POST_CREATE,\n        exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.DefinerProperty: exp.Properties.Location.POST_CREATE,\n        exp.DictRange: exp.Properties.Location.POST_SCHEMA,\n        exp.DictProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.DynamicProperty: exp.Properties.Location.POST_CREATE,\n        exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION,\n        exp.EngineProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.HandlerProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.ParameterStyleProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.ExternalProperty: exp.Properties.Location.POST_CREATE,\n        exp.FallbackProperty: exp.Properties.Location.POST_NAME,\n        exp.FileFormatProperty: exp.Properties.Location.POST_WITH,\n        exp.FreespaceProperty: exp.Properties.Location.POST_NAME,\n        exp.GlobalProperty: exp.Properties.Location.POST_CREATE,\n        exp.HeapProperty: exp.Properties.Location.POST_WITH,\n        exp.HybridProperty: exp.Properties.Location.POST_CREATE,\n        exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.IcebergProperty: exp.Properties.Location.POST_CREATE,\n        exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME,\n        exp.JournalProperty: exp.Properties.Location.POST_NAME,\n        exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.LikeProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.LocationProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.LockProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.LockingProperty: exp.Properties.Location.POST_ALIAS,\n        exp.LogProperty: exp.Properties.Location.POST_NAME,\n        exp.MaskingProperty: exp.Properties.Location.POST_CREATE,\n        exp.MaterializedProperty: exp.Properties.Location.POST_CREATE,\n        exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME,\n        exp.NetworkProperty: exp.Properties.Location.POST_CREATE,\n        exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION,\n        exp.OnProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION,\n        exp.Order: exp.Properties.Location.POST_SCHEMA,\n        exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.PartitionedByProperty: exp.Properties.Location.POST_WITH,\n        exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA,\n        exp.Property: exp.Properties.Location.POST_WITH,\n        exp.RefreshTriggerProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.RollupProperty: exp.Properties.Location.UNSUPPORTED,\n        exp.RowAccessProperty: exp.Properties.Location.POST_CREATE,\n        exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.SampleProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.SecureProperty: exp.Properties.Location.POST_CREATE,\n        exp.SecurityIntegrationProperty: exp.Properties.Location.POST_CREATE,\n        exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA,\n        exp.Set: exp.Properties.Location.POST_SCHEMA,\n        exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.SetProperty: exp.Properties.Location.POST_CREATE,\n        exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION,\n        exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION,\n        exp.TriggerProperties: exp.Properties.Location.POST_EXPRESSION,\n        exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.SqlSecurityProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE,\n        exp.StrictProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.Tags: exp.Properties.Location.POST_WITH,\n        exp.TemporaryProperty: exp.Properties.Location.POST_CREATE,\n        exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.TransientProperty: exp.Properties.Location.POST_CREATE,\n        exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA,\n        exp.UnloggedProperty: exp.Properties.Location.POST_CREATE,\n        exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.VolatileProperty: exp.Properties.Location.POST_CREATE,\n        exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION,\n        exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME,\n        exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA,\n        exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA,\n        exp.ForceProperty: exp.Properties.Location.POST_CREATE,\n    }\n\n    # Keywords that can't be used as unquoted identifier names\n    RESERVED_KEYWORDS: t.Set[str] = set()\n\n    # Exprs whose comments are separated from them for better formatting\n    WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expr], ...] = (\n        exp.Command,\n        exp.Create,\n        exp.Describe,\n        exp.Delete,\n        exp.Drop,\n        exp.From,\n        exp.Insert,\n        exp.Join,\n        exp.MultitableInserts,\n        exp.Order,\n        exp.Group,\n        exp.Having,\n        exp.Select,\n        exp.SetOperation,\n        exp.Update,\n        exp.Where,\n        exp.With,\n    )\n\n    # Exprs that should not have their comments generated in maybe_comment\n    EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expr], ...] = (\n        exp.Binary,\n        exp.SetOperation,\n    )\n\n    # Exprs that can remain unwrapped when appearing in the context of an INTERVAL\n    UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expr], ...] = (\n        exp.Column,\n        exp.Literal,\n        exp.Neg,\n        exp.Paren,\n    )\n\n    PARAMETERIZABLE_TEXT_TYPES = {\n        exp.DType.NVARCHAR,\n        exp.DType.VARCHAR,\n        exp.DType.CHAR,\n        exp.DType.NCHAR,\n    }\n\n    # Exprs that need to have all CTEs under them bubbled up to them\n    EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expr]] = set()\n\n    RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expr], ...] = ()\n\n    SAFE_JSON_PATH_KEY_RE = exp.SAFE_IDENTIFIER_RE\n\n    SENTINEL_LINE_BREAK = \"__SQLGLOT__LB__\"\n\n    __slots__ = (\n        \"pretty\",\n        \"identify\",\n        \"normalize\",\n        \"pad\",\n        \"_indent\",\n        \"normalize_functions\",\n        \"unsupported_level\",\n        \"max_unsupported\",\n        \"leading_comma\",\n        \"max_text_width\",\n        \"comments\",\n        \"dialect\",\n        \"unsupported_messages\",\n        \"_escaped_quote_end\",\n        \"_escaped_byte_quote_end\",\n        \"_escaped_identifier_end\",\n        \"_next_name\",\n        \"_identifier_start\",\n        \"_identifier_end\",\n        \"_quote_json_path_key_using_brackets\",\n    )\n\n    def __init__(\n        self,\n        pretty: t.Optional[bool] = None,\n        identify: str | bool = False,\n        normalize: bool = False,\n        pad: int = 2,\n        indent: int = 2,\n        normalize_functions: t.Optional[str | bool] = None,\n        unsupported_level: ErrorLevel = ErrorLevel.WARN,\n        max_unsupported: int = 3,\n        leading_comma: bool = False,\n        max_text_width: int = 80,\n        comments: bool = True,\n        dialect: DialectType = None,\n    ):\n        import sqlglot\n        from sqlglot.dialects import Dialect\n\n        self.pretty = pretty if pretty is not None else sqlglot.pretty\n        self.identify = identify\n        self.normalize = normalize\n        self.pad = pad\n        self._indent = indent\n        self.unsupported_level = unsupported_level\n        self.max_unsupported = max_unsupported\n        self.leading_comma = leading_comma\n        self.max_text_width = max_text_width\n        self.comments = comments\n        self.dialect = Dialect.get_or_raise(dialect)\n\n        # This is both a Dialect property and a Generator argument, so we prioritize the latter\n        self.normalize_functions = (\n            self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions\n        )\n\n        self.unsupported_messages: t.List[str] = []\n        self._escaped_quote_end: str = (\n            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END\n        )\n        self._escaped_byte_quote_end: str = (\n            self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.BYTE_END\n            if self.dialect.BYTE_END\n            else \"\"\n        )\n        self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2\n\n        self._next_name = name_sequence(\"_t\")\n\n        self._identifier_start = self.dialect.IDENTIFIER_START\n        self._identifier_end = self.dialect.IDENTIFIER_END\n\n        self._quote_json_path_key_using_brackets = True\n\n    def generate(self, expression: exp.Expr, copy: bool = True) -> str:\n        \"\"\"\n        Generates the SQL string corresponding to the given syntax tree.\n\n        Args:\n            expression: The syntax tree.\n            copy: Whether to copy the expression. The generator performs mutations so\n                it is safer to copy.\n\n        Returns:\n            The SQL string corresponding to `expression`.\n        \"\"\"\n        if copy:\n            expression = expression.copy()\n\n        expression = self.preprocess(expression)\n\n        self.unsupported_messages = []\n        sql = self.sql(expression).strip()\n\n        if self.pretty:\n            sql = sql.replace(self.SENTINEL_LINE_BREAK, \"\\n\")\n\n        if self.unsupported_level == ErrorLevel.IGNORE:\n            return sql\n\n        if self.unsupported_level == ErrorLevel.WARN:\n            for msg in self.unsupported_messages:\n                logger.warning(msg)\n        elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages:\n            raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported))\n\n        return sql\n\n    def preprocess(self, expression: exp.Expr) -> exp.Expr:\n        \"\"\"Apply generic preprocessing transformations to a given expression.\"\"\"\n        expression = self._move_ctes_to_top_level(expression)\n\n        if self.ENSURE_BOOLS:\n            from sqlglot.transforms import ensure_bools\n\n            expression = ensure_bools(expression)\n\n        return expression\n\n    def _move_ctes_to_top_level(self, expression: E) -> E:\n        if (\n            not expression.parent\n            and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES\n            and any(node.parent is not expression for node in expression.find_all(exp.With))\n        ):\n            from sqlglot.transforms import move_ctes_to_top_level\n\n            expression = move_ctes_to_top_level(expression)\n        return expression\n\n    def unsupported(self, message: str) -> None:\n        if self.unsupported_level == ErrorLevel.IMMEDIATE:\n            raise UnsupportedError(message)\n        self.unsupported_messages.append(message)\n\n    def sep(self, sep: str = \" \") -> str:\n        return f\"{sep.strip()}\\n\" if self.pretty else sep\n\n    def seg(self, sql: str, sep: str = \" \") -> str:\n        return f\"{self.sep(sep)}{sql}\"\n\n    def sanitize_comment(self, comment: str) -> str:\n        comment = \" \" + comment if comment[0].strip() else comment\n        comment = comment + \" \" if comment[-1].strip() else comment\n\n        # Escape block comment markers to prevent premature closure or unintended nesting.\n        # This is necessary because single-line comments (--) are converted to block comments\n        # (/* */) on output, and any */ in the original text would close the comment early.\n        comment = comment.replace(\"*/\", \"* /\").replace(\"/*\", \"/ *\")\n\n        return comment\n\n    def maybe_comment(\n        self,\n        sql: str,\n        expression: t.Optional[exp.Expr] = None,\n        comments: t.Optional[t.List[str]] = None,\n        separated: bool = False,\n    ) -> str:\n        comments = (\n            ((expression and expression.comments) if comments is None else comments)  # type: ignore\n            if self.comments\n            else None\n        )\n\n        if not comments or isinstance(expression, self.EXCLUDE_COMMENTS):\n            return sql\n\n        comments_sql = \" \".join(\n            f\"/*{self.sanitize_comment(comment)}*/\" for comment in comments if comment\n        )\n\n        if not comments_sql:\n            return sql\n\n        comments_sql = self._replace_line_breaks(comments_sql)\n\n        if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS):\n            return (\n                f\"{self.sep()}{comments_sql}{sql}\"\n                if not sql or sql[0].isspace()\n                else f\"{comments_sql}{self.sep()}{sql}\"\n            )\n\n        return f\"{sql} {comments_sql}\"\n\n    def wrap(self, expression: exp.Expr | str) -> str:\n        this_sql = (\n            self.sql(expression)\n            if isinstance(expression, exp.UNWRAPPED_QUERIES)\n            else self.sql(expression, \"this\")\n        )\n        if not this_sql:\n            return \"()\"\n\n        this_sql = self.indent(this_sql, level=1, pad=0)\n        return f\"({self.sep('')}{this_sql}{self.seg(')', sep='')}\"\n\n    def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str:\n        original = self.identify\n        self.identify = False\n        result = func(*args, **kwargs)\n        self.identify = original\n        return result\n\n    def normalize_func(self, name: str) -> str:\n        if self.normalize_functions == \"upper\" or self.normalize_functions is True:\n            return name.upper()\n        if self.normalize_functions == \"lower\":\n            return name.lower()\n        return name\n\n    def indent(\n        self,\n        sql: str,\n        level: int = 0,\n        pad: t.Optional[int] = None,\n        skip_first: bool = False,\n        skip_last: bool = False,\n    ) -> str:\n        if not self.pretty or not sql:\n            return sql\n\n        pad = self.pad if pad is None else pad\n        lines = sql.split(\"\\n\")\n\n        return \"\\n\".join(\n            (\n                line\n                if (skip_first and i == 0) or (skip_last and i == len(lines) - 1)\n                else f\"{' ' * (level * self._indent + pad)}{line}\"\n            )\n            for i, line in enumerate(lines)\n        )\n\n    def sql(\n        self,\n        expression: t.Optional[str | exp.Expr],\n        key: t.Optional[str] = None,\n        comment: bool = True,\n    ) -> str:\n        if not expression:\n            return \"\"\n\n        if isinstance(expression, str):\n            return expression\n\n        if key:\n            value = expression.args.get(key)\n            if value:\n                return self.sql(value)\n            return \"\"\n\n        transform = self.TRANSFORMS.get(expression.__class__)\n\n        if transform:\n            sql = transform(self, expression)\n        else:\n            exp_handler_name = expression.key + \"_sql\"\n\n            if handler := getattr(self, exp_handler_name, None):\n                sql = handler(expression)\n            elif isinstance(expression, exp.Func):\n                sql = self.function_fallback_sql(expression)\n            elif isinstance(expression, exp.Property):\n                sql = self.property_sql(expression)\n            else:\n                raise ValueError(f\"Unsupported expression type {expression.__class__.__name__}\")\n\n        return self.maybe_comment(sql, expression) if self.comments and comment else sql\n\n    def uncache_sql(self, expression: exp.Uncache) -> str:\n        table = self.sql(expression, \"this\")\n        exists_sql = \" IF EXISTS\" if expression.args.get(\"exists\") else \"\"\n        return f\"UNCACHE TABLE{exists_sql} {table}\"\n\n    def cache_sql(self, expression: exp.Cache) -> str:\n        lazy = \" LAZY\" if expression.args.get(\"lazy\") else \"\"\n        table = self.sql(expression, \"this\")\n        options = expression.args.get(\"options\")\n        options = f\" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})\" if options else \"\"\n        sql = self.sql(expression, \"expression\")\n        sql = f\" AS{self.sep()}{sql}\" if sql else \"\"\n        sql = f\"CACHE{lazy} TABLE {table}{options}{sql}\"\n        return self.prepend_ctes(expression, sql)\n\n    def characterset_sql(self, expression: exp.CharacterSet) -> str:\n        default = \"DEFAULT \" if expression.args.get(\"default\") else \"\"\n        return f\"{default}CHARACTER SET={self.sql(expression, 'this')}\"\n\n    def column_parts(self, expression: exp.Column) -> str:\n        return \".\".join(\n            self.sql(part)\n            for part in (\n                expression.args.get(\"catalog\"),\n                expression.args.get(\"db\"),\n                expression.args.get(\"table\"),\n                expression.args.get(\"this\"),\n            )\n            if part\n        )\n\n    def column_sql(self, expression: exp.Column) -> str:\n        join_mark = \" (+)\" if expression.args.get(\"join_mark\") else \"\"\n\n        if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:\n            join_mark = \"\"\n            self.unsupported(\"Outer join syntax using the (+) operator is not supported.\")\n\n        return f\"{self.column_parts(expression)}{join_mark}\"\n\n    def pseudocolumn_sql(self, expression: exp.Pseudocolumn) -> str:\n        return self.column_sql(expression)\n\n    def columnposition_sql(self, expression: exp.ColumnPosition) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        position = self.sql(expression, \"position\")\n        return f\"{position}{this}\"\n\n    def columndef_sql(self, expression: exp.ColumnDef, sep: str = \" \") -> str:\n        column = self.sql(expression, \"this\")\n        kind = self.sql(expression, \"kind\")\n        constraints = self.expressions(expression, key=\"constraints\", sep=\" \", flat=True)\n        exists = \"IF NOT EXISTS \" if expression.args.get(\"exists\") else \"\"\n        kind = f\"{sep}{kind}\" if kind else \"\"\n        constraints = f\" {constraints}\" if constraints else \"\"\n        position = self.sql(expression, \"position\")\n        position = f\" {position}\" if position else \"\"\n\n        if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE:\n            kind = \"\"\n\n        return f\"{exists}{column}{kind}{constraints}{position}\"\n\n    def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str:\n        this = self.sql(expression, \"this\")\n        kind_sql = self.sql(expression, \"kind\").strip()\n        return f\"CONSTRAINT {this} {kind_sql}\" if this else kind_sql\n\n    def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str:\n        this = self.sql(expression, \"this\")\n        if expression.args.get(\"not_null\"):\n            persisted = \" PERSISTED NOT NULL\"\n        elif expression.args.get(\"persisted\"):\n            persisted = \" PERSISTED\"\n        else:\n            persisted = \"\"\n\n        return f\"AS {this}{persisted}\"\n\n    def autoincrementcolumnconstraint_sql(self, _: exp.AutoIncrementColumnConstraint) -> str:\n        return self.token_sql(TokenType.AUTO_INCREMENT)\n\n    def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str:\n        if isinstance(expression.this, list):\n            this = self.wrap(self.expressions(expression, key=\"this\", flat=True))\n        else:\n            this = self.sql(expression, \"this\")\n\n        return f\"COMPRESS {this}\"\n\n    def generatedasidentitycolumnconstraint_sql(\n        self, expression: exp.GeneratedAsIdentityColumnConstraint\n    ) -> str:\n        this = \"\"\n        if expression.this is not None:\n            on_null = \" ON NULL\" if expression.args.get(\"on_null\") else \"\"\n            this = \" ALWAYS\" if expression.this else f\" BY DEFAULT{on_null}\"\n\n        start = expression.args.get(\"start\")\n        start = f\"START WITH {start}\" if start else \"\"\n        increment = expression.args.get(\"increment\")\n        increment = f\" INCREMENT BY {increment}\" if increment else \"\"\n        minvalue = expression.args.get(\"minvalue\")\n        minvalue = f\" MINVALUE {minvalue}\" if minvalue else \"\"\n        maxvalue = expression.args.get(\"maxvalue\")\n        maxvalue = f\" MAXVALUE {maxvalue}\" if maxvalue else \"\"\n        cycle = expression.args.get(\"cycle\")\n        cycle_sql = \"\"\n\n        if cycle is not None:\n            cycle_sql = f\"{' NO' if not cycle else ''} CYCLE\"\n            cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql\n\n        sequence_opts = \"\"\n        if start or increment or cycle_sql:\n            sequence_opts = f\"{start}{increment}{minvalue}{maxvalue}{cycle_sql}\"\n            sequence_opts = f\" ({sequence_opts.strip()})\"\n\n        expr = self.sql(expression, \"expression\")\n        expr = f\"({expr})\" if expr else \"IDENTITY\"\n\n        return f\"GENERATED{this} AS {expr}{sequence_opts}\"\n\n    def generatedasrowcolumnconstraint_sql(\n        self, expression: exp.GeneratedAsRowColumnConstraint\n    ) -> str:\n        start = \"START\" if expression.args.get(\"start\") else \"END\"\n        hidden = \" HIDDEN\" if expression.args.get(\"hidden\") else \"\"\n        return f\"GENERATED ALWAYS AS ROW {start}{hidden}\"\n\n    def periodforsystemtimeconstraint_sql(\n        self, expression: exp.PeriodForSystemTimeConstraint\n    ) -> str:\n        return f\"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})\"\n\n    def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str:\n        return f\"{'' if expression.args.get('allow_null') else 'NOT '}NULL\"\n\n    def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str:\n        desc = expression.args.get(\"desc\")\n        if desc is not None:\n            return f\"PRIMARY KEY{' DESC' if desc else ' ASC'}\"\n        options = self.expressions(expression, key=\"options\", flat=True, sep=\" \")\n        options = f\" {options}\" if options else \"\"\n        return f\"PRIMARY KEY{options}\"\n\n    def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        index_type = expression.args.get(\"index_type\")\n        index_type = f\" USING {index_type}\" if index_type else \"\"\n        on_conflict = self.sql(expression, \"on_conflict\")\n        on_conflict = f\" {on_conflict}\" if on_conflict else \"\"\n        nulls_sql = \" NULLS NOT DISTINCT\" if expression.args.get(\"nulls\") else \"\"\n        options = self.expressions(expression, key=\"options\", flat=True, sep=\" \")\n        options = f\" {options}\" if options else \"\"\n        return f\"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}\"\n\n    def inoutcolumnconstraint_sql(self, expression: exp.InOutColumnConstraint) -> str:\n        input_ = expression.args.get(\"input_\")\n        output = expression.args.get(\"output\")\n        variadic = expression.args.get(\"variadic\")\n\n        # VARIADIC is mutually exclusive with IN/OUT/INOUT\n        if variadic:\n            return \"VARIADIC\"\n\n        if input_ and output:\n            return f\"IN{self.INOUT_SEPARATOR}OUT\"\n        if input_:\n            return \"IN\"\n        if output:\n            return \"OUT\"\n\n        return \"\"\n\n    def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str:\n        return self.sql(expression, \"this\")\n\n    def create_sql(self, expression: exp.Create) -> str:\n        kind = self.sql(expression, \"kind\")\n        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind\n\n        properties = expression.args.get(\"properties\")\n\n        if (\n            kind == \"TRIGGER\"\n            and properties\n            and properties.expressions\n            and isinstance(properties.expressions[0], exp.TriggerProperties)\n            and properties.expressions[0].args.get(\"constraint\")\n        ):\n            kind = f\"CONSTRAINT {kind}\"\n\n        properties_locs = self.locate_properties(properties) if properties else defaultdict()\n\n        this = self.createable_sql(expression, properties_locs)\n\n        properties_sql = \"\"\n        if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get(\n            exp.Properties.Location.POST_WITH\n        ):\n            props_ast = exp.Properties(\n                expressions=[\n                    *properties_locs[exp.Properties.Location.POST_SCHEMA],\n                    *properties_locs[exp.Properties.Location.POST_WITH],\n                ]\n            )\n            props_ast.parent = expression\n            properties_sql = self.sql(props_ast)\n\n            if properties_locs.get(exp.Properties.Location.POST_SCHEMA):\n                properties_sql = self.sep() + properties_sql\n            elif not self.pretty:\n                # Standalone POST_WITH properties need a leading whitespace in non-pretty mode\n                properties_sql = f\" {properties_sql}\"\n\n        begin = \" BEGIN\" if expression.args.get(\"begin\") else \"\"\n\n        expression_sql = self.sql(expression, \"expression\")\n        if expression_sql:\n            expression_sql = f\"{begin}{self.sep()}{expression_sql}\"\n\n            if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return):\n                postalias_props_sql = \"\"\n                if properties_locs.get(exp.Properties.Location.POST_ALIAS):\n                    postalias_props_sql = self.properties(\n                        exp.Properties(\n                            expressions=properties_locs[exp.Properties.Location.POST_ALIAS]\n                        ),\n                        wrapped=False,\n                    )\n                postalias_props_sql = f\" {postalias_props_sql}\" if postalias_props_sql else \"\"\n                expression_sql = f\" AS{postalias_props_sql}{expression_sql}\"\n\n        postindex_props_sql = \"\"\n        if properties_locs.get(exp.Properties.Location.POST_INDEX):\n            postindex_props_sql = self.properties(\n                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]),\n                wrapped=False,\n                prefix=\" \",\n            )\n\n        indexes = self.expressions(expression, key=\"indexes\", indent=False, sep=\" \")\n        indexes = f\" {indexes}\" if indexes else \"\"\n        index_sql = indexes + postindex_props_sql\n\n        replace = \" OR REPLACE\" if expression.args.get(\"replace\") else \"\"\n        refresh = \" OR REFRESH\" if expression.args.get(\"refresh\") else \"\"\n        unique = \" UNIQUE\" if expression.args.get(\"unique\") else \"\"\n\n        clustered = expression.args.get(\"clustered\")\n        if clustered is None:\n            clustered_sql = \"\"\n        elif clustered:\n            clustered_sql = \" CLUSTERED COLUMNSTORE\"\n        else:\n            clustered_sql = \" NONCLUSTERED COLUMNSTORE\"\n\n        postcreate_props_sql = \"\"\n        if properties_locs.get(exp.Properties.Location.POST_CREATE):\n            postcreate_props_sql = self.properties(\n                exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]),\n                sep=\" \",\n                prefix=\" \",\n                wrapped=False,\n            )\n\n        modifiers = \"\".join((clustered_sql, replace, refresh, unique, postcreate_props_sql))\n\n        postexpression_props_sql = \"\"\n        if properties_locs.get(exp.Properties.Location.POST_EXPRESSION):\n            postexpression_props_sql = self.properties(\n                exp.Properties(\n                    expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION]\n                ),\n                sep=\" \",\n                prefix=\" \",\n                wrapped=False,\n            )\n\n        concurrently = \" CONCURRENTLY\" if expression.args.get(\"concurrently\") else \"\"\n        exists_sql = \" IF NOT EXISTS\" if expression.args.get(\"exists\") else \"\"\n        no_schema_binding = (\n            \" WITH NO SCHEMA BINDING\" if expression.args.get(\"no_schema_binding\") else \"\"\n        )\n\n        clone = self.sql(expression, \"clone\")\n        clone = f\" {clone}\" if clone else \"\"\n\n        if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES:\n            properties_expression = f\"{expression_sql}{properties_sql}\"\n        else:\n            properties_expression = f\"{properties_sql}{expression_sql}\"\n\n        expression_sql = f\"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}\"\n        return self.prepend_ctes(expression, expression_sql)\n\n    def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str:\n        start = self.sql(expression, \"start\")\n        start = f\"START WITH {start}\" if start else \"\"\n        increment = self.sql(expression, \"increment\")\n        increment = f\" INCREMENT BY {increment}\" if increment else \"\"\n        minvalue = self.sql(expression, \"minvalue\")\n        minvalue = f\" MINVALUE {minvalue}\" if minvalue else \"\"\n        maxvalue = self.sql(expression, \"maxvalue\")\n        maxvalue = f\" MAXVALUE {maxvalue}\" if maxvalue else \"\"\n        owned = self.sql(expression, \"owned\")\n        owned = f\" OWNED BY {owned}\" if owned else \"\"\n\n        cache = expression.args.get(\"cache\")\n        if cache is None:\n            cache_str = \"\"\n        elif cache is True:\n            cache_str = \" CACHE\"\n        else:\n            cache_str = f\" CACHE {cache}\"\n\n        options = self.expressions(expression, key=\"options\", flat=True, sep=\" \")\n        options = f\" {options}\" if options else \"\"\n\n        return f\"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}\".lstrip()\n\n    def triggerproperties_sql(self, expression: exp.TriggerProperties) -> str:\n        timing = expression.args.get(\"timing\", \"\")\n        events = \" OR \".join(self.sql(event) for event in expression.args.get(\"events\") or [])\n        timing_events = f\"{timing} {events}\".strip() if timing or events else \"\"\n\n        parts = [timing_events, \"ON\", self.sql(expression, \"table\")]\n\n        if referenced_table := expression.args.get(\"referenced_table\"):\n            parts.extend([\"FROM\", self.sql(referenced_table)])\n\n        if deferrable := expression.args.get(\"deferrable\"):\n            parts.append(deferrable)\n\n        if initially := expression.args.get(\"initially\"):\n            parts.append(f\"INITIALLY {initially}\")\n\n        if referencing := expression.args.get(\"referencing\"):\n            parts.append(self.sql(referencing))\n\n        if for_each := expression.args.get(\"for_each\"):\n            parts.append(f\"FOR EACH {for_each}\")\n\n        if when := expression.args.get(\"when\"):\n            parts.append(f\"WHEN ({self.sql(when)})\")\n\n        parts.append(self.sql(expression, \"execute\"))\n\n        return self.sep().join(parts)\n\n    def triggerreferencing_sql(self, expression: exp.TriggerReferencing) -> str:\n        parts = []\n\n        if old_alias := expression.args.get(\"old\"):\n            parts.append(f\"OLD TABLE AS {self.sql(old_alias)}\")\n\n        if new_alias := expression.args.get(\"new\"):\n            parts.append(f\"NEW TABLE AS {self.sql(new_alias)}\")\n\n        return f\"REFERENCING {' '.join(parts)}\"\n\n    def triggerevent_sql(self, expression: exp.TriggerEvent) -> str:\n        columns = expression.args.get(\"columns\")\n        if columns:\n            return f\"{expression.this} OF {self.expressions(expression, key='columns', flat=True)}\"\n\n        return self.sql(expression, \"this\")\n\n    def clone_sql(self, expression: exp.Clone) -> str:\n        this = self.sql(expression, \"this\")\n        shallow = \"SHALLOW \" if expression.args.get(\"shallow\") else \"\"\n        keyword = \"COPY\" if expression.args.get(\"copy\") and self.SUPPORTS_TABLE_COPY else \"CLONE\"\n        return f\"{shallow}{keyword} {this}\"\n\n    def describe_sql(self, expression: exp.Describe) -> str:\n        style = expression.args.get(\"style\")\n        style = f\" {style}\" if style else \"\"\n        partition = self.sql(expression, \"partition\")\n        partition = f\" {partition}\" if partition else \"\"\n        format = self.sql(expression, \"format\")\n        format = f\" {format}\" if format else \"\"\n        as_json = \" AS JSON\" if expression.args.get(\"as_json\") else \"\"\n\n        return f\"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}{as_json}\"\n\n    def heredoc_sql(self, expression: exp.Heredoc) -> str:\n        tag = self.sql(expression, \"tag\")\n        return f\"${tag}${self.sql(expression, 'this')}${tag}$\"\n\n    def prepend_ctes(self, expression: exp.Expr, sql: str) -> str:\n        with_ = self.sql(expression, \"with_\")\n        if with_:\n            sql = f\"{with_}{self.sep()}{sql}\"\n        return sql\n\n    def with_sql(self, expression: exp.With) -> str:\n        sql = self.expressions(expression, flat=True)\n        recursive = (\n            \"RECURSIVE \"\n            if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get(\"recursive\")\n            else \"\"\n        )\n        search = self.sql(expression, \"search\")\n        search = f\" {search}\" if search else \"\"\n\n        return f\"WITH {recursive}{sql}{search}\"\n\n    def cte_sql(self, expression: exp.CTE) -> str:\n        alias = expression.args.get(\"alias\")\n        if alias:\n            alias.add_comments(expression.pop_comments())\n\n        alias_sql = self.sql(expression, \"alias\")\n\n        materialized = expression.args.get(\"materialized\")\n        if materialized is False:\n            materialized = \"NOT MATERIALIZED \"\n        elif materialized:\n            materialized = \"MATERIALIZED \"\n\n        key_expressions = self.expressions(expression, key=\"key_expressions\", flat=True)\n        key_expressions = f\" USING KEY ({key_expressions})\" if key_expressions else \"\"\n\n        return f\"{alias_sql}{key_expressions} AS {materialized or ''}{self.wrap(expression)}\"\n\n    def tablealias_sql(self, expression: exp.TableAlias) -> str:\n        alias = self.sql(expression, \"this\")\n        columns = self.expressions(expression, key=\"columns\", flat=True)\n        columns = f\"({columns})\" if columns else \"\"\n\n        if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS:\n            columns = \"\"\n            self.unsupported(\"Named columns are not supported in table alias.\")\n\n        if not alias and not self.dialect.UNNEST_COLUMN_ONLY:\n            alias = self._next_name()\n\n        return f\"{alias}{columns}\"\n\n    def bitstring_sql(self, expression: exp.BitString) -> str:\n        this = self.sql(expression, \"this\")\n        if self.dialect.BIT_START:\n            return f\"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}\"\n        return f\"{int(this, 2)}\"\n\n    def hexstring_sql(\n        self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None\n    ) -> str:\n        this = self.sql(expression, \"this\")\n        is_integer_type = expression.args.get(\"is_integer\")\n\n        if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or (\n            not self.dialect.HEX_START and not binary_function_repr\n        ):\n            # Integer representation will be returned if:\n            # - The read dialect treats the hex value as integer literal but not the write\n            # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag)\n            return f\"{int(this, 16)}\"\n\n        if not is_integer_type:\n            # Read dialect treats the hex value as BINARY/BLOB\n            if binary_function_repr:\n                # The write dialect supports the transpilation to its equivalent BINARY/BLOB\n                return self.func(binary_function_repr, exp.Literal.string(this))\n            if self.dialect.HEX_STRING_IS_INTEGER_TYPE:\n                # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER\n                self.unsupported(\"Unsupported transpilation from BINARY/BLOB hex string\")\n\n        return f\"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}\"\n\n    def bytestring_sql(self, expression: exp.ByteString) -> str:\n        this = self.sql(expression, \"this\")\n        if self.dialect.BYTE_START:\n            escaped_byte_string = self.escape_str(\n                this,\n                escape_backslash=False,\n                delimiter=self.dialect.BYTE_END,\n                escaped_delimiter=self._escaped_byte_quote_end,\n                is_byte_string=True,\n            )\n            is_bytes = expression.args.get(\"is_bytes\", False)\n            delimited_byte_string = (\n                f\"{self.dialect.BYTE_START}{escaped_byte_string}{self.dialect.BYTE_END}\"\n            )\n            if is_bytes and not self.dialect.BYTE_STRING_IS_BYTES_TYPE:\n                return self.sql(\n                    exp.cast(delimited_byte_string, exp.DType.BINARY, dialect=self.dialect)\n                )\n            if not is_bytes and self.dialect.BYTE_STRING_IS_BYTES_TYPE:\n                return self.sql(\n                    exp.cast(delimited_byte_string, exp.DType.VARCHAR, dialect=self.dialect)\n                )\n\n            return delimited_byte_string\n        return this\n\n    def unicodestring_sql(self, expression: exp.UnicodeString) -> str:\n        this = self.sql(expression, \"this\")\n        escape = expression.args.get(\"escape\")\n\n        if self.dialect.UNICODE_START:\n            escape_substitute = r\"\\\\\\1\"\n            left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END\n        else:\n            escape_substitute = r\"\\\\u\\1\"\n            left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END\n\n        if escape:\n            escape_pattern = re.compile(rf\"{escape.name}(\\d+)\")\n            escape_sql = f\" UESCAPE {self.sql(escape)}\" if self.SUPPORTS_UESCAPE else \"\"\n        else:\n            escape_pattern = ESCAPED_UNICODE_RE\n            escape_sql = \"\"\n\n        if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE):\n            this = escape_pattern.sub(self.UNICODE_SUBSTITUTE or escape_substitute, this)\n\n        return f\"{left_quote}{this}{right_quote}{escape_sql}\"\n\n    def rawstring_sql(self, expression: exp.RawString) -> str:\n        string = expression.this\n        if \"\\\\\" in self.dialect.tokenizer_class.STRING_ESCAPES:\n            string = string.replace(\"\\\\\", \"\\\\\\\\\")\n\n        string = self.escape_str(string, escape_backslash=False)\n        return f\"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}\"\n\n    def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str:\n        this = self.sql(expression, \"this\")\n        specifier = self.sql(expression, \"expression\")\n        specifier = f\" {specifier}\" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else \"\"\n        return f\"{this}{specifier}\"\n\n    def datatype_sql(self, expression: exp.DataType) -> str:\n        nested = \"\"\n        values = \"\"\n\n        expr_nested = expression.args.get(\"nested\")\n        interior = (\n            self.expressions(\n                expression, dynamic=True, new_line=True, skip_first=True, skip_last=True\n            )\n            if expr_nested and self.pretty\n            else self.expressions(expression, flat=True)\n        )\n\n        type_value = expression.this\n        if type_value in self.UNSUPPORTED_TYPES:\n            self.unsupported(\n                f\"Data type {type_value.value} is not supported when targeting {self.dialect.__class__.__name__}\"\n            )\n\n        if type_value == exp.DType.USERDEFINED and expression.args.get(\"kind\"):\n            type_sql = self.sql(expression, \"kind\")\n        elif type_value == exp.DType.CHARACTER_SET:\n            return f\"CHAR CHARACTER SET {self.sql(expression, 'kind')}\"\n        else:\n            type_sql = (\n                self.TYPE_MAPPING.get(type_value, type_value.value)\n                if isinstance(type_value, exp.DType)\n                else type_value\n            )\n\n        if interior:\n            if expr_nested:\n                nested = f\"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}\"\n                if expression.args.get(\"values\") is not None:\n                    delimiters = (\"[\", \"]\") if type_value == exp.DType.ARRAY else (\"(\", \")\")\n                    values = self.expressions(expression, key=\"values\", flat=True)\n                    values = f\"{delimiters[0]}{values}{delimiters[1]}\"\n            elif type_value == exp.DType.INTERVAL:\n                nested = f\" {interior}\"\n            else:\n                nested = f\"({interior})\"\n\n        type_sql = f\"{type_sql}{nested}{values}\"\n        if self.TZ_TO_WITH_TIME_ZONE and type_value in (\n            exp.DType.TIMETZ,\n            exp.DType.TIMESTAMPTZ,\n        ):\n            type_sql = f\"{type_sql} WITH TIME ZONE\"\n\n        return type_sql\n\n    def directory_sql(self, expression: exp.Directory) -> str:\n        local = \"LOCAL \" if expression.args.get(\"local\") else \"\"\n        row_format = self.sql(expression, \"row_format\")\n        row_format = f\" {row_format}\" if row_format else \"\"\n        return f\"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}\"\n\n    def delete_sql(self, expression: exp.Delete) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\" FROM {this}\" if this else \"\"\n        using = self.expressions(expression, key=\"using\")\n        using = f\" USING {using}\" if using else \"\"\n        cluster = self.sql(expression, \"cluster\")\n        cluster = f\" {cluster}\" if cluster else \"\"\n        where = self.sql(expression, \"where\")\n        returning = self.sql(expression, \"returning\")\n        order = self.sql(expression, \"order\")\n        limit = self.sql(expression, \"limit\")\n        tables = self.expressions(expression, key=\"tables\")\n        tables = f\" {tables}\" if tables else \"\"\n        if self.RETURNING_END:\n            expression_sql = f\"{this}{using}{cluster}{where}{returning}{order}{limit}\"\n        else:\n            expression_sql = f\"{returning}{this}{using}{cluster}{where}{order}{limit}\"\n        return self.prepend_ctes(expression, f\"DELETE{tables}{expression_sql}\")\n\n    def drop_sql(self, expression: exp.Drop) -> str:\n        this = self.sql(expression, \"this\")\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\" ({expressions})\" if expressions else \"\"\n        kind = expression.args[\"kind\"]\n        kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind\n        exists_sql = \" IF EXISTS \" if expression.args.get(\"exists\") else \" \"\n        concurrently_sql = \" CONCURRENTLY\" if expression.args.get(\"concurrently\") else \"\"\n        on_cluster = self.sql(expression, \"cluster\")\n        on_cluster = f\" {on_cluster}\" if on_cluster else \"\"\n        temporary = \" TEMPORARY\" if expression.args.get(\"temporary\") else \"\"\n        materialized = \" MATERIALIZED\" if expression.args.get(\"materialized\") else \"\"\n        cascade = \" CASCADE\" if expression.args.get(\"cascade\") else \"\"\n        constraints = \" CONSTRAINTS\" if expression.args.get(\"constraints\") else \"\"\n        purge = \" PURGE\" if expression.args.get(\"purge\") else \"\"\n        sync = \" SYNC\" if expression.args.get(\"sync\") else \"\"\n        return f\"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}{sync}\"\n\n    def set_operation(self, expression: exp.SetOperation) -> str:\n        op_type = type(expression)\n        op_name = op_type.key.upper()\n\n        distinct = expression.args.get(\"distinct\")\n        if (\n            distinct is False\n            and op_type in (exp.Except, exp.Intersect)\n            and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE\n        ):\n            self.unsupported(f\"{op_name} ALL is not supported\")\n\n        default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type]\n\n        if distinct is None:\n            distinct = default_distinct\n            if distinct is None:\n                self.unsupported(f\"{op_name} requires DISTINCT or ALL to be specified\")\n\n        if distinct is default_distinct:\n            distinct_or_all = \"\"\n        else:\n            distinct_or_all = \" DISTINCT\" if distinct else \" ALL\"\n\n        side_kind = \" \".join(filter(None, [expression.side, expression.kind]))\n        side_kind = f\"{side_kind} \" if side_kind else \"\"\n\n        by_name = \" BY NAME\" if expression.args.get(\"by_name\") else \"\"\n        on = self.expressions(expression, key=\"on\", flat=True)\n        on = f\" ON ({on})\" if on else \"\"\n\n        return f\"{side_kind}{op_name}{distinct_or_all}{by_name}{on}\"\n\n    def set_operations(self, expression: exp.SetOperation) -> str:\n        if not self.SET_OP_MODIFIERS:\n            limit = expression.args.get(\"limit\")\n            order = expression.args.get(\"order\")\n\n            if limit or order:\n                select = self._move_ctes_to_top_level(\n                    exp.subquery(expression, \"_l_0\", copy=False).select(\"*\", copy=False)\n                )\n\n                if limit:\n                    select = select.limit(limit.pop(), copy=False)\n                if order:\n                    select = select.order_by(order.pop(), copy=False)\n                return self.sql(select)\n\n        sqls: t.List[str] = []\n        stack: t.List[t.Union[str, exp.Expr]] = [expression]\n\n        while stack:\n            node = stack.pop()\n\n            if isinstance(node, exp.SetOperation):\n                stack.append(node.expression)\n                stack.append(\n                    self.maybe_comment(\n                        self.set_operation(node), comments=node.comments, separated=True\n                    )\n                )\n                stack.append(node.this)\n            else:\n                sqls.append(self.sql(node))\n\n        this = self.sep().join(sqls)\n        this = self.query_modifiers(expression, this)\n        return self.prepend_ctes(expression, this)\n\n    def fetch_sql(self, expression: exp.Fetch) -> str:\n        direction = expression.args.get(\"direction\")\n        direction = f\" {direction}\" if direction else \"\"\n        count = self.sql(expression, \"count\")\n        count = f\" {count}\" if count else \"\"\n        limit_options = self.sql(expression, \"limit_options\")\n        limit_options = f\"{limit_options}\" if limit_options else \" ROWS ONLY\"\n        return f\"{self.seg('FETCH')}{direction}{count}{limit_options}\"\n\n    def limitoptions_sql(self, expression: exp.LimitOptions) -> str:\n        percent = \" PERCENT\" if expression.args.get(\"percent\") else \"\"\n        rows = \" ROWS\" if expression.args.get(\"rows\") else \"\"\n        with_ties = \" WITH TIES\" if expression.args.get(\"with_ties\") else \"\"\n        if not with_ties and rows:\n            with_ties = \" ONLY\"\n        return f\"{percent}{rows}{with_ties}\"\n\n    def filter_sql(self, expression: exp.Filter) -> str:\n        if self.AGGREGATE_FILTER_SUPPORTED:\n            this = self.sql(expression, \"this\")\n            where = self.sql(expression, \"expression\").strip()\n            return f\"{this} FILTER({where})\"\n\n        agg = expression.this\n        agg_arg = agg.this\n        cond = expression.expression.this\n        agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy()))\n        return self.sql(agg)\n\n    def hint_sql(self, expression: exp.Hint) -> str:\n        if not self.QUERY_HINTS:\n            self.unsupported(\"Hints are not supported\")\n            return \"\"\n\n        return f\" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */\"\n\n    def indexparameters_sql(self, expression: exp.IndexParameters) -> str:\n        using = self.sql(expression, \"using\")\n        using = f\" USING {using}\" if using else \"\"\n        columns = self.expressions(expression, key=\"columns\", flat=True)\n        columns = f\"({columns})\" if columns else \"\"\n        partition_by = self.expressions(expression, key=\"partition_by\", flat=True)\n        partition_by = f\" PARTITION BY {partition_by}\" if partition_by else \"\"\n        where = self.sql(expression, \"where\")\n        include = self.expressions(expression, key=\"include\", flat=True)\n        if include:\n            include = f\" INCLUDE ({include})\"\n        with_storage = self.expressions(expression, key=\"with_storage\", flat=True)\n        with_storage = f\" WITH ({with_storage})\" if with_storage else \"\"\n        tablespace = self.sql(expression, \"tablespace\")\n        tablespace = f\" USING INDEX TABLESPACE {tablespace}\" if tablespace else \"\"\n        on = self.sql(expression, \"on\")\n        on = f\" ON {on}\" if on else \"\"\n\n        return f\"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}\"\n\n    def index_sql(self, expression: exp.Index) -> str:\n        unique = \"UNIQUE \" if expression.args.get(\"unique\") else \"\"\n        primary = \"PRIMARY \" if expression.args.get(\"primary\") else \"\"\n        amp = \"AMP \" if expression.args.get(\"amp\") else \"\"\n        name = self.sql(expression, \"this\")\n        name = f\"{name} \" if name else \"\"\n        table = self.sql(expression, \"table\")\n        table = f\"{self.INDEX_ON} {table}\" if table else \"\"\n\n        index = \"INDEX \" if not table else \"\"\n\n        params = self.sql(expression, \"params\")\n        return f\"{unique}{primary}{amp}{index}{name}{table}{params}\"\n\n    def identifier_sql(self, expression: exp.Identifier) -> str:\n        text = expression.name\n        lower = text.lower()\n        quoted = expression.quoted\n        text = lower if self.normalize and not quoted else text\n        text = text.replace(self._identifier_end, self._escaped_identifier_end)\n        if (\n            quoted\n            or self.dialect.can_quote(expression, self.identify)\n            or lower in self.RESERVED_KEYWORDS\n            or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit())\n        ):\n            text = f\"{self._identifier_start}{text}{self._identifier_end}\"\n        return text\n\n    def hex_sql(self, expression: exp.Hex) -> str:\n        text = self.func(self.HEX_FUNC, self.sql(expression, \"this\"))\n        if self.dialect.HEX_LOWERCASE:\n            text = self.func(\"LOWER\", text)\n\n        return text\n\n    def lowerhex_sql(self, expression: exp.LowerHex) -> str:\n        text = self.func(self.HEX_FUNC, self.sql(expression, \"this\"))\n        if not self.dialect.HEX_LOWERCASE:\n            text = self.func(\"LOWER\", text)\n        return text\n\n    def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str:\n        input_format = self.sql(expression, \"input_format\")\n        input_format = f\"INPUTFORMAT {input_format}\" if input_format else \"\"\n        output_format = self.sql(expression, \"output_format\")\n        output_format = f\"OUTPUTFORMAT {output_format}\" if output_format else \"\"\n        return self.sep().join((input_format, output_format))\n\n    def national_sql(self, expression: exp.National, prefix: str = \"N\") -> str:\n        string = self.sql(exp.Literal.string(expression.name))\n        return f\"{prefix}{string}\"\n\n    def partition_sql(self, expression: exp.Partition) -> str:\n        partition_keyword = \"SUBPARTITION\" if expression.args.get(\"subpartition\") else \"PARTITION\"\n        return f\"{partition_keyword}({self.expressions(expression, flat=True)})\"\n\n    def properties_sql(self, expression: exp.Properties) -> str:\n        root_properties = []\n        with_properties = []\n\n        for p in expression.expressions:\n            p_loc = self.PROPERTIES_LOCATION[p.__class__]\n            if p_loc == exp.Properties.Location.POST_WITH:\n                with_properties.append(p)\n            elif p_loc == exp.Properties.Location.POST_SCHEMA:\n                root_properties.append(p)\n\n        root_props_ast = exp.Properties(expressions=root_properties)\n        root_props_ast.parent = expression.parent\n\n        with_props_ast = exp.Properties(expressions=with_properties)\n        with_props_ast.parent = expression.parent\n\n        root_props = self.root_properties(root_props_ast)\n        with_props = self.with_properties(with_props_ast)\n\n        if root_props and with_props and not self.pretty:\n            with_props = \" \" + with_props\n\n        return root_props + with_props\n\n    def root_properties(self, properties: exp.Properties) -> str:\n        if properties.expressions:\n            return self.expressions(properties, indent=False, sep=\" \")\n        return \"\"\n\n    def properties(\n        self,\n        properties: exp.Properties,\n        prefix: str = \"\",\n        sep: str = \", \",\n        suffix: str = \"\",\n        wrapped: bool = True,\n    ) -> str:\n        if properties.expressions:\n            expressions = self.expressions(properties, sep=sep, indent=False)\n            if expressions:\n                expressions = self.wrap(expressions) if wrapped else expressions\n                return f\"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}\"\n        return \"\"\n\n    def with_properties(self, properties: exp.Properties) -> str:\n        return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep=\"\"))\n\n    def locate_properties(self, properties: exp.Properties) -> t.DefaultDict:\n        properties_locs = defaultdict(list)\n        for p in properties.expressions:\n            p_loc = self.PROPERTIES_LOCATION[p.__class__]\n            if p_loc != exp.Properties.Location.UNSUPPORTED:\n                properties_locs[p_loc].append(p)\n            else:\n                self.unsupported(f\"Unsupported property {p.key}\")\n\n        return properties_locs\n\n    def property_name(self, expression: exp.Property, string_key: bool = False) -> str:\n        if isinstance(expression.this, exp.Dot):\n            return self.sql(expression, \"this\")\n        return f\"'{expression.name}'\" if string_key else expression.name\n\n    def property_sql(self, expression: exp.Property) -> str:\n        property_cls = expression.__class__\n        if property_cls == exp.Property:\n            return f\"{self.property_name(expression)}={self.sql(expression, 'value')}\"\n\n        property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls)\n        if not property_name:\n            self.unsupported(f\"Unsupported property {expression.key}\")\n\n        return f\"{property_name}={self.sql(expression, 'this')}\"\n\n    def likeproperty_sql(self, expression: exp.LikeProperty) -> str:\n        if self.SUPPORTS_CREATE_TABLE_LIKE:\n            options = \" \".join(f\"{e.name} {self.sql(e, 'value')}\" for e in expression.expressions)\n            options = f\" {options}\" if options else \"\"\n\n            like = f\"LIKE {self.sql(expression, 'this')}{options}\"\n            if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema):\n                like = f\"({like})\"\n\n            return like\n\n        if expression.expressions:\n            self.unsupported(\"Transpilation of LIKE property options is unsupported\")\n\n        select = exp.select(\"*\").from_(expression.this).limit(0)\n        return f\"AS {self.sql(select)}\"\n\n    def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str:\n        no = \"NO \" if expression.args.get(\"no\") else \"\"\n        protection = \" PROTECTION\" if expression.args.get(\"protection\") else \"\"\n        return f\"{no}FALLBACK{protection}\"\n\n    def journalproperty_sql(self, expression: exp.JournalProperty) -> str:\n        no = \"NO \" if expression.args.get(\"no\") else \"\"\n        local = expression.args.get(\"local\")\n        local = f\"{local} \" if local else \"\"\n        dual = \"DUAL \" if expression.args.get(\"dual\") else \"\"\n        before = \"BEFORE \" if expression.args.get(\"before\") else \"\"\n        after = \"AFTER \" if expression.args.get(\"after\") else \"\"\n        return f\"{no}{local}{dual}{before}{after}JOURNAL\"\n\n    def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str:\n        freespace = self.sql(expression, \"this\")\n        percent = \" PERCENT\" if expression.args.get(\"percent\") else \"\"\n        return f\"FREESPACE={freespace}{percent}\"\n\n    def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str:\n        if expression.args.get(\"default\"):\n            property = \"DEFAULT\"\n        elif expression.args.get(\"on\"):\n            property = \"ON\"\n        else:\n            property = \"OFF\"\n        return f\"CHECKSUM={property}\"\n\n    def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str:\n        if expression.args.get(\"no\"):\n            return \"NO MERGEBLOCKRATIO\"\n        if expression.args.get(\"default\"):\n            return \"DEFAULT MERGEBLOCKRATIO\"\n\n        percent = \" PERCENT\" if expression.args.get(\"percent\") else \"\"\n        return f\"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}\"\n\n    def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str:\n        default = expression.args.get(\"default\")\n        minimum = expression.args.get(\"minimum\")\n        maximum = expression.args.get(\"maximum\")\n        if default or minimum or maximum:\n            if default:\n                prop = \"DEFAULT\"\n            elif minimum:\n                prop = \"MINIMUM\"\n            else:\n                prop = \"MAXIMUM\"\n            return f\"{prop} DATABLOCKSIZE\"\n        units = expression.args.get(\"units\")\n        units = f\" {units}\" if units else \"\"\n        return f\"DATABLOCKSIZE={self.sql(expression, 'size')}{units}\"\n\n    def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str:\n        autotemp = expression.args.get(\"autotemp\")\n        always = expression.args.get(\"always\")\n        default = expression.args.get(\"default\")\n        manual = expression.args.get(\"manual\")\n        never = expression.args.get(\"never\")\n\n        if autotemp is not None:\n            prop = f\"AUTOTEMP({self.expressions(autotemp)})\"\n        elif always:\n            prop = \"ALWAYS\"\n        elif default:\n            prop = \"DEFAULT\"\n        elif manual:\n            prop = \"MANUAL\"\n        elif never:\n            prop = \"NEVER\"\n        return f\"BLOCKCOMPRESSION={prop}\"\n\n    def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str:\n        no = expression.args.get(\"no\")\n        no = \" NO\" if no else \"\"\n        concurrent = expression.args.get(\"concurrent\")\n        concurrent = \" CONCURRENT\" if concurrent else \"\"\n        target = self.sql(expression, \"target\")\n        target = f\" {target}\" if target else \"\"\n        return f\"WITH{no}{concurrent} ISOLATED LOADING{target}\"\n\n    def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str:\n        if isinstance(expression.this, list):\n            return f\"IN ({self.expressions(expression, key='this', flat=True)})\"\n        if expression.this:\n            modulus = self.sql(expression, \"this\")\n            remainder = self.sql(expression, \"expression\")\n            return f\"WITH (MODULUS {modulus}, REMAINDER {remainder})\"\n\n        from_expressions = self.expressions(expression, key=\"from_expressions\", flat=True)\n        to_expressions = self.expressions(expression, key=\"to_expressions\", flat=True)\n        return f\"FROM ({from_expressions}) TO ({to_expressions})\"\n\n    def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str:\n        this = self.sql(expression, \"this\")\n\n        for_values_or_default = expression.expression\n        if isinstance(for_values_or_default, exp.PartitionBoundSpec):\n            for_values_or_default = f\" FOR VALUES {self.sql(for_values_or_default)}\"\n        else:\n            for_values_or_default = \" DEFAULT\"\n\n        return f\"PARTITION OF {this}{for_values_or_default}\"\n\n    def lockingproperty_sql(self, expression: exp.LockingProperty) -> str:\n        kind = expression.args.get(\"kind\")\n        this = f\" {self.sql(expression, 'this')}\" if expression.this else \"\"\n        for_or_in = expression.args.get(\"for_or_in\")\n        for_or_in = f\" {for_or_in}\" if for_or_in else \"\"\n        lock_type = expression.args.get(\"lock_type\")\n        override = \" OVERRIDE\" if expression.args.get(\"override\") else \"\"\n        return f\"LOCKING {kind}{this}{for_or_in} {lock_type}{override}\"\n\n    def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str:\n        data_sql = f\"WITH {'NO ' if expression.args.get('no') else ''}DATA\"\n        statistics = expression.args.get(\"statistics\")\n        statistics_sql = \"\"\n        if statistics is not None:\n            statistics_sql = f\" AND {'NO ' if not statistics else ''}STATISTICS\"\n        return f\"{data_sql}{statistics_sql}\"\n\n    def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\"HISTORY_TABLE={this}\" if this else \"\"\n        data_consistency: t.Optional[str] = self.sql(expression, \"data_consistency\")\n        data_consistency = (\n            f\"DATA_CONSISTENCY_CHECK={data_consistency}\" if data_consistency else None\n        )\n        retention_period: t.Optional[str] = self.sql(expression, \"retention_period\")\n        retention_period = (\n            f\"HISTORY_RETENTION_PERIOD={retention_period}\" if retention_period else None\n        )\n\n        if this:\n            on_sql = self.func(\"ON\", this, data_consistency, retention_period)\n        else:\n            on_sql = \"ON\" if expression.args.get(\"on\") else \"OFF\"\n\n        sql = f\"SYSTEM_VERSIONING={on_sql}\"\n\n        return f\"WITH({sql})\" if expression.args.get(\"with_\") else sql\n\n    def insert_sql(self, expression: exp.Insert) -> str:\n        hint = self.sql(expression, \"hint\")\n        overwrite = expression.args.get(\"overwrite\")\n\n        if isinstance(expression.this, exp.Directory):\n            this = \" OVERWRITE\" if overwrite else \" INTO\"\n        else:\n            this = self.INSERT_OVERWRITE if overwrite else \" INTO\"\n\n        stored = self.sql(expression, \"stored\")\n        stored = f\" {stored}\" if stored else \"\"\n        alternative = expression.args.get(\"alternative\")\n        alternative = f\" OR {alternative}\" if alternative else \"\"\n        ignore = \" IGNORE\" if expression.args.get(\"ignore\") else \"\"\n        is_function = expression.args.get(\"is_function\")\n        if is_function:\n            this = f\"{this} FUNCTION\"\n        this = f\"{this} {self.sql(expression, 'this')}\"\n\n        exists = \" IF EXISTS\" if expression.args.get(\"exists\") else \"\"\n        where = self.sql(expression, \"where\")\n        where = f\"{self.sep()}REPLACE WHERE {where}\" if where else \"\"\n        expression_sql = f\"{self.sep()}{self.sql(expression, 'expression')}\"\n        on_conflict = self.sql(expression, \"conflict\")\n        on_conflict = f\" {on_conflict}\" if on_conflict else \"\"\n        by_name = \" BY NAME\" if expression.args.get(\"by_name\") else \"\"\n        default_values = \"DEFAULT VALUES\" if expression.args.get(\"default\") else \"\"\n        returning = self.sql(expression, \"returning\")\n\n        if self.RETURNING_END:\n            expression_sql = f\"{expression_sql}{on_conflict}{default_values}{returning}\"\n        else:\n            expression_sql = f\"{returning}{expression_sql}{on_conflict}\"\n\n        partition_by = self.sql(expression, \"partition\")\n        partition_by = f\" {partition_by}\" if partition_by else \"\"\n        settings = self.sql(expression, \"settings\")\n        settings = f\" {settings}\" if settings else \"\"\n\n        source = self.sql(expression, \"source\")\n        source = f\"TABLE {source}\" if source else \"\"\n\n        sql = f\"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}\"\n        return self.prepend_ctes(expression, sql)\n\n    def introducer_sql(self, expression: exp.Introducer) -> str:\n        return f\"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}\"\n\n    def kill_sql(self, expression: exp.Kill) -> str:\n        kind = self.sql(expression, \"kind\")\n        kind = f\" {kind}\" if kind else \"\"\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        return f\"KILL{kind}{this}\"\n\n    def pseudotype_sql(self, expression: exp.PseudoType) -> str:\n        return expression.name\n\n    def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str:\n        return expression.name\n\n    def onconflict_sql(self, expression: exp.OnConflict) -> str:\n        conflict = \"ON DUPLICATE KEY\" if expression.args.get(\"duplicate\") else \"ON CONFLICT\"\n\n        constraint = self.sql(expression, \"constraint\")\n        constraint = f\" ON CONSTRAINT {constraint}\" if constraint else \"\"\n\n        conflict_keys = self.expressions(expression, key=\"conflict_keys\", flat=True)\n        if conflict_keys:\n            conflict_keys = f\"({conflict_keys})\"\n\n        index_predicate = self.sql(expression, \"index_predicate\")\n        conflict_keys = f\"{conflict_keys}{index_predicate} \"\n\n        action = self.sql(expression, \"action\")\n\n        expressions = self.expressions(expression, flat=True)\n        if expressions:\n            set_keyword = \"SET \" if self.DUPLICATE_KEY_UPDATE_WITH_SET else \"\"\n            expressions = f\" {set_keyword}{expressions}\"\n\n        where = self.sql(expression, \"where\")\n        return f\"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}\"\n\n    def returning_sql(self, expression: exp.Returning) -> str:\n        return f\"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}\"\n\n    def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str:\n        fields = self.sql(expression, \"fields\")\n        fields = f\" FIELDS TERMINATED BY {fields}\" if fields else \"\"\n        escaped = self.sql(expression, \"escaped\")\n        escaped = f\" ESCAPED BY {escaped}\" if escaped else \"\"\n        items = self.sql(expression, \"collection_items\")\n        items = f\" COLLECTION ITEMS TERMINATED BY {items}\" if items else \"\"\n        keys = self.sql(expression, \"map_keys\")\n        keys = f\" MAP KEYS TERMINATED BY {keys}\" if keys else \"\"\n        lines = self.sql(expression, \"lines\")\n        lines = f\" LINES TERMINATED BY {lines}\" if lines else \"\"\n        null = self.sql(expression, \"null\")\n        null = f\" NULL DEFINED AS {null}\" if null else \"\"\n        return f\"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}\"\n\n    def withtablehint_sql(self, expression: exp.WithTableHint) -> str:\n        return f\"WITH ({self.expressions(expression, flat=True)})\"\n\n    def indextablehint_sql(self, expression: exp.IndexTableHint) -> str:\n        this = f\"{self.sql(expression, 'this')} INDEX\"\n        target = self.sql(expression, \"target\")\n        target = f\" FOR {target}\" if target else \"\"\n        return f\"{this}{target} ({self.expressions(expression, flat=True)})\"\n\n    def historicaldata_sql(self, expression: exp.HistoricalData) -> str:\n        this = self.sql(expression, \"this\")\n        kind = self.sql(expression, \"kind\")\n        expr = self.sql(expression, \"expression\")\n        return f\"{this} ({kind} => {expr})\"\n\n    def table_parts(self, expression: exp.Table) -> str:\n        return \".\".join(\n            self.sql(part)\n            for part in (\n                expression.args.get(\"catalog\"),\n                expression.args.get(\"db\"),\n                expression.args.get(\"this\"),\n            )\n            if part is not None\n        )\n\n    def table_sql(self, expression: exp.Table, sep: str = \" AS \") -> str:\n        table = self.table_parts(expression)\n        only = \"ONLY \" if expression.args.get(\"only\") else \"\"\n        partition = self.sql(expression, \"partition\")\n        partition = f\" {partition}\" if partition else \"\"\n        version = self.sql(expression, \"version\")\n        version = f\" {version}\" if version else \"\"\n        alias = self.sql(expression, \"alias\")\n        alias = f\"{sep}{alias}\" if alias else \"\"\n\n        sample = self.sql(expression, \"sample\")\n        post_alias = \"\"\n        pre_alias = \"\"\n\n        if self.dialect.ALIAS_POST_TABLESAMPLE:\n            pre_alias = sample\n        else:\n            post_alias = sample\n\n        if self.dialect.ALIAS_POST_VERSION:\n            pre_alias = f\"{pre_alias}{version}\"\n        else:\n            post_alias = f\"{post_alias}{version}\"\n\n        hints = self.expressions(expression, key=\"hints\", sep=\" \")\n        hints = f\" {hints}\" if hints and self.TABLE_HINTS else \"\"\n        pivots = self.expressions(expression, key=\"pivots\", sep=\"\", flat=True)\n        joins = self.indent(\n            self.expressions(expression, key=\"joins\", sep=\"\", flat=True), skip_first=True\n        )\n        laterals = self.expressions(expression, key=\"laterals\", sep=\"\")\n\n        file_format = self.sql(expression, \"format\")\n        if file_format:\n            pattern = self.sql(expression, \"pattern\")\n            pattern = f\", PATTERN => {pattern}\" if pattern else \"\"\n            file_format = f\" (FILE_FORMAT => {file_format}{pattern})\"\n\n        ordinality = expression.args.get(\"ordinality\") or \"\"\n        if ordinality:\n            ordinality = f\" WITH ORDINALITY{alias}\"\n            alias = \"\"\n\n        when = self.sql(expression, \"when\")\n        if when:\n            table = f\"{table} {when}\"\n\n        changes = self.sql(expression, \"changes\")\n        changes = f\" {changes}\" if changes else \"\"\n\n        rows_from = self.expressions(expression, key=\"rows_from\")\n        if rows_from:\n            table = f\"ROWS FROM {self.wrap(rows_from)}\"\n\n        indexed = expression.args.get(\"indexed\")\n        if indexed is not None:\n            indexed = f\" INDEXED BY {self.sql(indexed)}\" if indexed else \" NOT INDEXED\"\n        else:\n            indexed = \"\"\n\n        return f\"{only}{table}{changes}{partition}{file_format}{pre_alias}{alias}{indexed}{hints}{pivots}{post_alias}{joins}{laterals}{ordinality}\"\n\n    def tablefromrows_sql(self, expression: exp.TableFromRows) -> str:\n        table = self.func(\"TABLE\", expression.this)\n        alias = self.sql(expression, \"alias\")\n        alias = f\" AS {alias}\" if alias else \"\"\n        sample = self.sql(expression, \"sample\")\n        pivots = self.expressions(expression, key=\"pivots\", sep=\"\", flat=True)\n        joins = self.indent(\n            self.expressions(expression, key=\"joins\", sep=\"\", flat=True), skip_first=True\n        )\n        return f\"{table}{alias}{pivots}{sample}{joins}\"\n\n    def tablesample_sql(\n        self,\n        expression: exp.TableSample,\n        tablesample_keyword: t.Optional[str] = None,\n    ) -> str:\n        method = self.sql(expression, \"method\")\n        method = f\"{method} \" if method and self.TABLESAMPLE_WITH_METHOD else \"\"\n        numerator = self.sql(expression, \"bucket_numerator\")\n        denominator = self.sql(expression, \"bucket_denominator\")\n        field = self.sql(expression, \"bucket_field\")\n        field = f\" ON {field}\" if field else \"\"\n        bucket = f\"BUCKET {numerator} OUT OF {denominator}{field}\" if numerator else \"\"\n        seed = self.sql(expression, \"seed\")\n        seed = f\" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})\" if seed else \"\"\n\n        size = self.sql(expression, \"size\")\n        if size and self.TABLESAMPLE_SIZE_IS_ROWS:\n            size = f\"{size} ROWS\"\n\n        percent = self.sql(expression, \"percent\")\n        if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:\n            percent = f\"{percent} PERCENT\"\n\n        expr = f\"{bucket}{percent}{size}\"\n        if self.TABLESAMPLE_REQUIRES_PARENS:\n            expr = f\"({expr})\"\n\n        return f\" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}\"\n\n    def pivot_sql(self, expression: exp.Pivot) -> str:\n        expressions = self.expressions(expression, flat=True)\n        direction = \"UNPIVOT\" if expression.unpivot else \"PIVOT\"\n\n        group = self.sql(expression, \"group\")\n\n        if expression.this:\n            this = self.sql(expression, \"this\")\n            if not expressions:\n                sql = f\"UNPIVOT {this}\"\n            else:\n                on = f\"{self.seg('ON')} {expressions}\"\n                into = self.sql(expression, \"into\")\n                into = f\"{self.seg('INTO')} {into}\" if into else \"\"\n                using = self.expressions(expression, key=\"using\", flat=True)\n                using = f\"{self.seg('USING')} {using}\" if using else \"\"\n                sql = f\"{direction} {this}{on}{into}{using}{group}\"\n            return self.prepend_ctes(expression, sql)\n\n        alias = self.sql(expression, \"alias\")\n        alias = f\" AS {alias}\" if alias else \"\"\n\n        fields = self.expressions(\n            expression,\n            \"fields\",\n            sep=\" \",\n            dynamic=True,\n            new_line=True,\n            skip_first=True,\n            skip_last=True,\n        )\n\n        include_nulls = expression.args.get(\"include_nulls\")\n        if include_nulls is not None:\n            nulls = \" INCLUDE NULLS \" if include_nulls else \" EXCLUDE NULLS \"\n        else:\n            nulls = \"\"\n\n        default_on_null = self.sql(expression, \"default_on_null\")\n        default_on_null = f\" DEFAULT ON NULL ({default_on_null})\" if default_on_null else \"\"\n        sql = f\"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}\"\n        return self.prepend_ctes(expression, sql)\n\n    def version_sql(self, expression: exp.Version) -> str:\n        this = f\"FOR {expression.name}\"\n        kind = expression.text(\"kind\")\n        expr = self.sql(expression, \"expression\")\n        return f\"{this} {kind} {expr}\"\n\n    def tuple_sql(self, expression: exp.Tuple) -> str:\n        return f\"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})\"\n\n    def _update_from_joins_sql(self, expression: exp.Update) -> t.Tuple[str, str]:\n        \"\"\"\n        Returns (join_sql, from_sql) for UPDATE statements.\n        - join_sql: placed after UPDATE table, before SET\n        - from_sql: placed after SET clause (standard position)\n        Dialects like MySQL need to convert FROM to JOIN syntax.\n        \"\"\"\n        if self.UPDATE_STATEMENT_SUPPORTS_FROM or not (from_expr := expression.args.get(\"from_\")):\n            return (\"\", self.sql(expression, \"from_\"))\n\n        # Qualify unqualified columns in SET clause with the target table\n        # MySQL requires qualified column names in multi-table UPDATE to avoid ambiguity\n        target_table = expression.this\n        if isinstance(target_table, exp.Table):\n            target_name = exp.to_identifier(target_table.alias_or_name)\n            for eq in expression.expressions:\n                col = eq.this\n                if isinstance(col, exp.Column) and not col.table:\n                    col.set(\"table\", target_name)\n\n        table = from_expr.this\n        if nested_joins := table.args.get(\"joins\", []):\n            table.set(\"joins\", None)\n\n        join_sql = self.sql(exp.Join(this=table, on=exp.true()))\n        for nested in nested_joins:\n            if not nested.args.get(\"on\") and not nested.args.get(\"using\"):\n                nested.set(\"on\", exp.true())\n            join_sql += self.sql(nested)\n\n        return (join_sql, \"\")\n\n    def update_sql(self, expression: exp.Update) -> str:\n        this = self.sql(expression, \"this\")\n        join_sql, from_sql = self._update_from_joins_sql(expression)\n        set_sql = self.expressions(expression, flat=True)\n        where_sql = self.sql(expression, \"where\")\n        returning = self.sql(expression, \"returning\")\n        order = self.sql(expression, \"order\")\n        limit = self.sql(expression, \"limit\")\n        if self.RETURNING_END:\n            expression_sql = f\"{from_sql}{where_sql}{returning}\"\n        else:\n            expression_sql = f\"{returning}{from_sql}{where_sql}\"\n        options = self.expressions(expression, key=\"options\")\n        options = f\" OPTION({options})\" if options else \"\"\n        sql = f\"UPDATE {this}{join_sql} SET {set_sql}{expression_sql}{order}{limit}{options}\"\n        return self.prepend_ctes(expression, sql)\n\n    def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str:\n        values_as_table = values_as_table and self.VALUES_AS_TABLE\n\n        # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example\n        if values_as_table or not expression.find_ancestor(exp.From, exp.Join):\n            args = self.expressions(expression)\n            alias = self.sql(expression, \"alias\")\n            values = f\"VALUES{self.seg('')}{args}\"\n            values = (\n                f\"({values})\"\n                if self.WRAP_DERIVED_VALUES\n                and (alias or isinstance(expression.parent, (exp.From, exp.Table)))\n                else values\n            )\n            values = self.query_modifiers(expression, values)\n            return f\"{values} AS {alias}\" if alias else values\n\n        # Converts `VALUES...` expression into a series of select unions.\n        alias_node = expression.args.get(\"alias\")\n        column_names = alias_node and alias_node.columns\n\n        selects: t.List[exp.Query] = []\n\n        for i, tup in enumerate(expression.expressions):\n            row = tup.expressions\n\n            if i == 0 and column_names:\n                row = [\n                    exp.alias_(value, column_name) for value, column_name in zip(row, column_names)\n                ]\n\n            selects.append(exp.Select(expressions=row))\n\n        if self.pretty:\n            # This may result in poor performance for large-cardinality `VALUES` tables, due to\n            # the deep nesting of the resulting exp.Unions. If this is a problem, either increase\n            # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`.\n            query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects)\n            return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False))\n\n        alias = f\" AS {self.sql(alias_node, 'this')}\" if alias_node else \"\"\n        unions = \" UNION ALL \".join(self.sql(select) for select in selects)\n        return f\"({unions}){alias}\"\n\n    def var_sql(self, expression: exp.Var) -> str:\n        return self.sql(expression, \"this\")\n\n    @unsupported_args(\"expressions\")\n    def into_sql(self, expression: exp.Into) -> str:\n        temporary = \" TEMPORARY\" if expression.args.get(\"temporary\") else \"\"\n        unlogged = \" UNLOGGED\" if expression.args.get(\"unlogged\") else \"\"\n        return f\"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}\"\n\n    def from_sql(self, expression: exp.From) -> str:\n        return f\"{self.seg('FROM')} {self.sql(expression, 'this')}\"\n\n    def groupingsets_sql(self, expression: exp.GroupingSets) -> str:\n        grouping_sets = self.expressions(expression, indent=False)\n        return f\"GROUPING SETS {self.wrap(grouping_sets)}\"\n\n    def rollup_sql(self, expression: exp.Rollup) -> str:\n        expressions = self.expressions(expression, indent=False)\n        return f\"ROLLUP {self.wrap(expressions)}\" if expressions else \"WITH ROLLUP\"\n\n    def rollupindex_sql(self, expression: exp.RollupIndex) -> str:\n        this = self.sql(expression, \"this\")\n\n        columns = self.expressions(expression, flat=True)\n\n        from_sql = self.sql(expression, \"from_index\")\n        from_sql = f\" FROM {from_sql}\" if from_sql else \"\"\n\n        properties = expression.args.get(\"properties\")\n        properties_sql = (\n            f\" {self.properties(properties, prefix='PROPERTIES')}\" if properties else \"\"\n        )\n\n        return f\"{this}({columns}){from_sql}{properties_sql}\"\n\n    def rollupproperty_sql(self, expression: exp.RollupProperty) -> str:\n        return f\"ROLLUP ({self.expressions(expression, flat=True)})\"\n\n    def cube_sql(self, expression: exp.Cube) -> str:\n        expressions = self.expressions(expression, indent=False)\n        return f\"CUBE {self.wrap(expressions)}\" if expressions else \"WITH CUBE\"\n\n    def group_sql(self, expression: exp.Group) -> str:\n        group_by_all = expression.args.get(\"all\")\n        if group_by_all is True:\n            modifier = \" ALL\"\n        elif group_by_all is False:\n            modifier = \" DISTINCT\"\n        else:\n            modifier = \"\"\n\n        group_by = self.op_expressions(f\"GROUP BY{modifier}\", expression)\n\n        grouping_sets = self.expressions(expression, key=\"grouping_sets\")\n        cube = self.expressions(expression, key=\"cube\")\n        rollup = self.expressions(expression, key=\"rollup\")\n\n        groupings = csv(\n            self.seg(grouping_sets) if grouping_sets else \"\",\n            self.seg(cube) if cube else \"\",\n            self.seg(rollup) if rollup else \"\",\n            self.seg(\"WITH TOTALS\") if expression.args.get(\"totals\") else \"\",\n            sep=self.GROUPINGS_SEP,\n        )\n\n        if (\n            expression.expressions\n            and groupings\n            and groupings.strip() not in (\"WITH CUBE\", \"WITH ROLLUP\")\n        ):\n            group_by = f\"{group_by}{self.GROUPINGS_SEP}\"\n\n        return f\"{group_by}{groupings}\"\n\n    def having_sql(self, expression: exp.Having) -> str:\n        this = self.indent(self.sql(expression, \"this\"))\n        return f\"{self.seg('HAVING')}{self.sep()}{this}\"\n\n    def connect_sql(self, expression: exp.Connect) -> str:\n        start = self.sql(expression, \"start\")\n        start = self.seg(f\"START WITH {start}\") if start else \"\"\n        nocycle = \" NOCYCLE\" if expression.args.get(\"nocycle\") else \"\"\n        connect = self.sql(expression, \"connect\")\n        connect = self.seg(f\"CONNECT BY{nocycle} {connect}\")\n        return start + connect\n\n    def prior_sql(self, expression: exp.Prior) -> str:\n        return f\"PRIOR {self.sql(expression, 'this')}\"\n\n    def join_sql(self, expression: exp.Join) -> str:\n        if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in (\"SEMI\", \"ANTI\"):\n            side = None\n        else:\n            side = expression.side\n\n        op_sql = \" \".join(\n            op\n            for op in (\n                expression.method,\n                \"GLOBAL\" if expression.args.get(\"global_\") else None,\n                side,\n                expression.kind,\n                expression.hint if self.JOIN_HINTS else None,\n                \"DIRECTED\" if expression.args.get(\"directed\") and self.DIRECTED_JOINS else None,\n            )\n            if op\n        )\n        match_cond = self.sql(expression, \"match_condition\")\n        match_cond = f\" MATCH_CONDITION ({match_cond})\" if match_cond else \"\"\n        on_sql = self.sql(expression, \"on\")\n        using = expression.args.get(\"using\")\n\n        if not on_sql and using:\n            on_sql = csv(*(self.sql(column) for column in using))\n\n        this = expression.this\n        this_sql = self.sql(this)\n\n        exprs = self.expressions(expression)\n        if exprs:\n            this_sql = f\"{this_sql},{self.seg(exprs)}\"\n\n        if on_sql:\n            on_sql = self.indent(on_sql, skip_first=True)\n            space = self.seg(\" \" * self.pad) if self.pretty else \" \"\n            if using:\n                on_sql = f\"{space}USING ({on_sql})\"\n            else:\n                on_sql = f\"{space}ON {on_sql}\"\n        elif not op_sql:\n            if isinstance(this, exp.Lateral) and this.args.get(\"cross_apply\") is not None:\n                return f\" {this_sql}\"\n\n            return f\", {this_sql}\"\n\n        if op_sql != \"STRAIGHT_JOIN\":\n            op_sql = f\"{op_sql} JOIN\" if op_sql else \"JOIN\"\n\n        pivots = self.expressions(expression, key=\"pivots\", sep=\"\", flat=True)\n        return f\"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}\"\n\n    def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = \"->\", wrap: bool = True) -> str:\n        args = self.expressions(expression, flat=True)\n        args = f\"({args})\" if wrap and len(args.split(\",\")) > 1 else args\n        return f\"{args} {arrow_sep} {self.sql(expression, 'this')}\"\n\n    def lateral_op(self, expression: exp.Lateral) -> str:\n        cross_apply = expression.args.get(\"cross_apply\")\n\n        # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/\n        if cross_apply is True:\n            op = \"INNER JOIN \"\n        elif cross_apply is False:\n            op = \"LEFT JOIN \"\n        else:\n            op = \"\"\n\n        return f\"{op}LATERAL\"\n\n    def lateral_sql(self, expression: exp.Lateral) -> str:\n        this = self.sql(expression, \"this\")\n\n        if expression.args.get(\"view\"):\n            alias = expression.args[\"alias\"]\n            columns = self.expressions(alias, key=\"columns\", flat=True)\n            table = f\" {alias.name}\" if alias.name else \"\"\n            columns = f\" AS {columns}\" if columns else \"\"\n            op_sql = self.seg(f\"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}\")\n            return f\"{op_sql}{self.sep()}{this}{table}{columns}\"\n\n        alias = self.sql(expression, \"alias\")\n        alias = f\" AS {alias}\" if alias else \"\"\n\n        ordinality = expression.args.get(\"ordinality\") or \"\"\n        if ordinality:\n            ordinality = f\" WITH ORDINALITY{alias}\"\n            alias = \"\"\n\n        return f\"{self.lateral_op(expression)} {this}{alias}{ordinality}\"\n\n    def limit_sql(self, expression: exp.Limit, top: bool = False) -> str:\n        this = self.sql(expression, \"this\")\n\n        args = [\n            self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e\n            for e in (expression.args.get(k) for k in (\"offset\", \"expression\"))\n            if e\n        ]\n\n        args_sql = \", \".join(self.sql(e) for e in args)\n        args_sql = f\"({args_sql})\" if top and any(not e.is_number for e in args) else args_sql\n        expressions = self.expressions(expression, flat=True)\n        limit_options = self.sql(expression, \"limit_options\")\n        expressions = f\" BY {expressions}\" if expressions else \"\"\n\n        return f\"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}\"\n\n    def offset_sql(self, expression: exp.Offset) -> str:\n        this = self.sql(expression, \"this\")\n        value = expression.expression\n        value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\" BY {expressions}\" if expressions else \"\"\n        return f\"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}\"\n\n    def setitem_sql(self, expression: exp.SetItem) -> str:\n        kind = self.sql(expression, \"kind\")\n        if not self.SET_ASSIGNMENT_REQUIRES_VARIABLE_KEYWORD and kind == \"VARIABLE\":\n            kind = \"\"\n        else:\n            kind = f\"{kind} \" if kind else \"\"\n        this = self.sql(expression, \"this\")\n        expressions = self.expressions(expression)\n        collate = self.sql(expression, \"collate\")\n        collate = f\" COLLATE {collate}\" if collate else \"\"\n        global_ = \"GLOBAL \" if expression.args.get(\"global_\") else \"\"\n        return f\"{global_}{kind}{this}{expressions}{collate}\"\n\n    def set_sql(self, expression: exp.Set) -> str:\n        expressions = f\" {self.expressions(expression, flat=True)}\"\n        tag = \" TAG\" if expression.args.get(\"tag\") else \"\"\n        return f\"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}\"\n\n    def queryband_sql(self, expression: exp.QueryBand) -> str:\n        this = self.sql(expression, \"this\")\n        update = \" UPDATE\" if expression.args.get(\"update\") else \"\"\n        scope = self.sql(expression, \"scope\")\n        scope = f\" FOR {scope}\" if scope else \"\"\n\n        return f\"QUERY_BAND = {this}{update}{scope}\"\n\n    def pragma_sql(self, expression: exp.Pragma) -> str:\n        return f\"PRAGMA {self.sql(expression, 'this')}\"\n\n    def lock_sql(self, expression: exp.Lock) -> str:\n        if not self.LOCKING_READS_SUPPORTED:\n            self.unsupported(\"Locking reads using 'FOR UPDATE/SHARE' are not supported\")\n            return \"\"\n\n        update = expression.args[\"update\"]\n        key = expression.args.get(\"key\")\n        if update:\n            lock_type = \"FOR NO KEY UPDATE\" if key else \"FOR UPDATE\"\n        else:\n            lock_type = \"FOR KEY SHARE\" if key else \"FOR SHARE\"\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\" OF {expressions}\" if expressions else \"\"\n        wait = expression.args.get(\"wait\")\n\n        if wait is not None:\n            if isinstance(wait, exp.Literal):\n                wait = f\" WAIT {self.sql(wait)}\"\n            else:\n                wait = \" NOWAIT\" if wait else \" SKIP LOCKED\"\n\n        return f\"{lock_type}{expressions}{wait or ''}\"\n\n    def literal_sql(self, expression: exp.Literal) -> str:\n        text = expression.this or \"\"\n        if expression.is_string:\n            text = f\"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}\"\n        return text\n\n    def escape_str(\n        self,\n        text: str,\n        escape_backslash: bool = True,\n        delimiter: t.Optional[str] = None,\n        escaped_delimiter: t.Optional[str] = None,\n        is_byte_string: bool = False,\n    ) -> str:\n        if is_byte_string:\n            supports_escape_sequences = self.dialect.BYTE_STRINGS_SUPPORT_ESCAPED_SEQUENCES\n        else:\n            supports_escape_sequences = self.dialect.STRINGS_SUPPORT_ESCAPED_SEQUENCES\n\n        if supports_escape_sequences:\n            text = \"\".join(\n                self.dialect.ESCAPED_SEQUENCES.get(ch, ch) if escape_backslash or ch != \"\\\\\" else ch\n                for ch in text\n            )\n\n        delimiter = delimiter or self.dialect.QUOTE_END\n        escaped_delimiter = escaped_delimiter or self._escaped_quote_end\n\n        return self._replace_line_breaks(text).replace(delimiter, escaped_delimiter)\n\n    def loaddata_sql(self, expression: exp.LoadData) -> str:\n        local = \" LOCAL\" if expression.args.get(\"local\") else \"\"\n        inpath = f\" INPATH {self.sql(expression, 'inpath')}\"\n        overwrite = \" OVERWRITE\" if expression.args.get(\"overwrite\") else \"\"\n        this = f\" INTO TABLE {self.sql(expression, 'this')}\"\n        partition = self.sql(expression, \"partition\")\n        partition = f\" {partition}\" if partition else \"\"\n        input_format = self.sql(expression, \"input_format\")\n        input_format = f\" INPUTFORMAT {input_format}\" if input_format else \"\"\n        serde = self.sql(expression, \"serde\")\n        serde = f\" SERDE {serde}\" if serde else \"\"\n        return f\"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}\"\n\n    def null_sql(self, *_) -> str:\n        return \"NULL\"\n\n    def boolean_sql(self, expression: exp.Boolean) -> str:\n        return \"TRUE\" if expression.this else \"FALSE\"\n\n    def booland_sql(self, expression: exp.Booland) -> str:\n        return f\"(({self.sql(expression, 'this')}) AND ({self.sql(expression, 'expression')}))\"\n\n    def boolor_sql(self, expression: exp.Boolor) -> str:\n        return f\"(({self.sql(expression, 'this')}) OR ({self.sql(expression, 'expression')}))\"\n\n    def order_sql(self, expression: exp.Order, flat: bool = False) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\"{this} \" if this else this\n        siblings = \"SIBLINGS \" if expression.args.get(\"siblings\") else \"\"\n        return self.op_expressions(f\"{this}ORDER {siblings}BY\", expression, flat=this or flat)  # type: ignore\n\n    def withfill_sql(self, expression: exp.WithFill) -> str:\n        from_sql = self.sql(expression, \"from_\")\n        from_sql = f\" FROM {from_sql}\" if from_sql else \"\"\n        to_sql = self.sql(expression, \"to\")\n        to_sql = f\" TO {to_sql}\" if to_sql else \"\"\n        step_sql = self.sql(expression, \"step\")\n        step_sql = f\" STEP {step_sql}\" if step_sql else \"\"\n        interpolated_values = [\n            f\"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}\"\n            if isinstance(e, exp.Alias)\n            else self.sql(e, \"this\")\n            for e in expression.args.get(\"interpolate\") or []\n        ]\n        interpolate = (\n            f\" INTERPOLATE ({', '.join(interpolated_values)})\" if interpolated_values else \"\"\n        )\n        return f\"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}\"\n\n    def cluster_sql(self, expression: exp.Cluster) -> str:\n        return self.op_expressions(\"CLUSTER BY\", expression)\n\n    def distribute_sql(self, expression: exp.Distribute) -> str:\n        return self.op_expressions(\"DISTRIBUTE BY\", expression)\n\n    def sort_sql(self, expression: exp.Sort) -> str:\n        return self.op_expressions(\"SORT BY\", expression)\n\n    def ordered_sql(self, expression: exp.Ordered) -> str:\n        desc = expression.args.get(\"desc\")\n        asc = not desc\n\n        nulls_first = expression.args.get(\"nulls_first\")\n        nulls_last = not nulls_first\n        nulls_are_large = self.dialect.NULL_ORDERING == \"nulls_are_large\"\n        nulls_are_small = self.dialect.NULL_ORDERING == \"nulls_are_small\"\n        nulls_are_last = self.dialect.NULL_ORDERING == \"nulls_are_last\"\n\n        this = self.sql(expression, \"this\")\n\n        sort_order = \" DESC\" if desc else (\" ASC\" if desc is False else \"\")\n        nulls_sort_change = \"\"\n        if nulls_first and (\n            (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last\n        ):\n            nulls_sort_change = \" NULLS FIRST\"\n        elif (\n            nulls_last\n            and ((asc and nulls_are_small) or (desc and nulls_are_large))\n            and not nulls_are_last\n        ):\n            nulls_sort_change = \" NULLS LAST\"\n\n        # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it\n        if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED:\n            window = expression.find_ancestor(exp.Window, exp.Select)\n\n            if isinstance(window, exp.Window):\n                window_this = window.this\n                spec = window.args.get(\"spec\")\n            else:\n                window_this = None\n                spec = None\n\n            # Some window functions (e.g. LAST_VALUE, RANK) support NULLS FIRST/LAST\n            # without a spec or with a ROWS spec, but not with RANGE\n            if not (\n                isinstance(window_this, self.WINDOW_FUNCS_WITH_NULL_ORDERING)\n                and (not spec or spec.text(\"kind\").upper() == \"ROWS\")\n            ):\n                if window_this and spec:\n                    self.unsupported(\n                        f\"'{nulls_sort_change.strip()}' translation not supported in window function {window_this.sql_name()}\"\n                    )\n                    nulls_sort_change = \"\"\n                elif self.NULL_ORDERING_SUPPORTED is False and (\n                    (asc and nulls_sort_change == \" NULLS LAST\")\n                    or (desc and nulls_sort_change == \" NULLS FIRST\")\n                ):\n                    # BigQuery does not allow these ordering/nulls combinations when used under\n                    # an aggregation func or under a window containing one\n                    ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select)\n\n                    if isinstance(ancestor, exp.Window):\n                        ancestor = ancestor.this\n                    if isinstance(ancestor, exp.AggFunc):\n                        self.unsupported(\n                            f\"'{nulls_sort_change.strip()}' translation not supported for aggregate function {ancestor.sql_name()} with {sort_order} sort order\"\n                        )\n                        nulls_sort_change = \"\"\n                elif self.NULL_ORDERING_SUPPORTED is None:\n                    if expression.this.is_int:\n                        self.unsupported(\n                            f\"'{nulls_sort_change.strip()}' translation not supported with positional ordering\"\n                        )\n                    elif not isinstance(expression.this, exp.Rand):\n                        null_sort_order = \" DESC\" if nulls_sort_change == \" NULLS FIRST\" else \"\"\n                        this = (\n                            f\"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}\"\n                        )\n                    nulls_sort_change = \"\"\n\n        with_fill = self.sql(expression, \"with_fill\")\n        with_fill = f\" {with_fill}\" if with_fill else \"\"\n\n        return f\"{this}{sort_order}{nulls_sort_change}{with_fill}\"\n\n    def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str:\n        window_frame = self.sql(expression, \"window_frame\")\n        window_frame = f\"{window_frame} \" if window_frame else \"\"\n\n        this = self.sql(expression, \"this\")\n\n        return f\"{window_frame}{this}\"\n\n    def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str:\n        partition = self.partition_by_sql(expression)\n        order = self.sql(expression, \"order\")\n        measures = self.expressions(expression, key=\"measures\")\n        measures = self.seg(f\"MEASURES{self.seg(measures)}\") if measures else \"\"\n        rows = self.sql(expression, \"rows\")\n        rows = self.seg(rows) if rows else \"\"\n        after = self.sql(expression, \"after\")\n        after = self.seg(after) if after else \"\"\n        pattern = self.sql(expression, \"pattern\")\n        pattern = self.seg(f\"PATTERN ({pattern})\") if pattern else \"\"\n        definition_sqls = [\n            f\"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}\"\n            for definition in expression.args.get(\"define\", [])\n        ]\n        definitions = self.expressions(sqls=definition_sqls)\n        define = self.seg(f\"DEFINE{self.seg(definitions)}\") if definitions else \"\"\n        body = \"\".join(\n            (\n                partition,\n                order,\n                measures,\n                rows,\n                after,\n                pattern,\n                define,\n            )\n        )\n        alias = self.sql(expression, \"alias\")\n        alias = f\" {alias}\" if alias else \"\"\n        return f\"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}\"\n\n    def query_modifiers(self, expression: exp.Expr, *sqls: str) -> str:\n        limit = expression.args.get(\"limit\")\n\n        if self.LIMIT_FETCH == \"LIMIT\" and isinstance(limit, exp.Fetch):\n            limit = exp.Limit(expression=exp.maybe_copy(limit.args.get(\"count\")))\n        elif self.LIMIT_FETCH == \"FETCH\" and isinstance(limit, exp.Limit):\n            limit = exp.Fetch(direction=\"FIRST\", count=exp.maybe_copy(limit.expression))\n\n        return csv(\n            *sqls,\n            *[self.sql(join) for join in expression.args.get(\"joins\") or []],\n            self.sql(expression, \"match\"),\n            *[self.sql(lateral) for lateral in expression.args.get(\"laterals\") or []],\n            self.sql(expression, \"prewhere\"),\n            self.sql(expression, \"where\"),\n            self.sql(expression, \"connect\"),\n            self.sql(expression, \"group\"),\n            self.sql(expression, \"having\"),\n            *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()],\n            self.sql(expression, \"order\"),\n            *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit),\n            *self.after_limit_modifiers(expression),\n            self.options_modifier(expression),\n            self.for_modifiers(expression),\n            sep=\"\",\n        )\n\n    def options_modifier(self, expression: exp.Expr) -> str:\n        options = self.expressions(expression, key=\"options\")\n        return f\" {options}\" if options else \"\"\n\n    def for_modifiers(self, expression: exp.Expr) -> str:\n        for_modifiers = self.expressions(expression, key=\"for_\")\n        return f\"{self.sep()}FOR XML{self.seg(for_modifiers)}\" if for_modifiers else \"\"\n\n    def queryoption_sql(self, expression: exp.QueryOption) -> str:\n        self.unsupported(\"Unsupported query option.\")\n        return \"\"\n\n    def offset_limit_modifiers(\n        self, expression: exp.Expr, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit]\n    ) -> t.List[str]:\n        return [\n            self.sql(expression, \"offset\") if fetch else self.sql(limit),\n            self.sql(limit) if fetch else self.sql(expression, \"offset\"),\n        ]\n\n    def after_limit_modifiers(self, expression: exp.Expr) -> t.List[str]:\n        locks = self.expressions(expression, key=\"locks\", sep=\" \")\n        locks = f\" {locks}\" if locks else \"\"\n        return [locks, self.sql(expression, \"sample\")]\n\n    def select_sql(self, expression: exp.Select) -> str:\n        into = expression.args.get(\"into\")\n        if not self.SUPPORTS_SELECT_INTO and into:\n            into.pop()\n\n        hint = self.sql(expression, \"hint\")\n        distinct = self.sql(expression, \"distinct\")\n        distinct = f\" {distinct}\" if distinct else \"\"\n        kind = self.sql(expression, \"kind\")\n\n        limit = expression.args.get(\"limit\")\n        if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP:\n            top = self.limit_sql(limit, top=True)\n            limit.pop()\n        else:\n            top = \"\"\n\n        expressions = self.expressions(expression)\n\n        if kind:\n            if kind in self.SELECT_KINDS:\n                kind = f\" AS {kind}\"\n            else:\n                if kind == \"STRUCT\":\n                    expressions = self.expressions(\n                        sqls=[\n                            self.sql(\n                                exp.Struct(\n                                    expressions=[\n                                        exp.PropertyEQ(this=e.args.get(\"alias\"), expression=e.this)\n                                        if isinstance(e, exp.Alias)\n                                        else e\n                                        for e in expression.expressions\n                                    ]\n                                )\n                            )\n                        ]\n                    )\n                kind = \"\"\n\n        operation_modifiers = self.expressions(expression, key=\"operation_modifiers\", sep=\" \")\n        operation_modifiers = f\"{self.sep()}{operation_modifiers}\" if operation_modifiers else \"\"\n\n        exclude = expression.args.get(\"exclude\")\n\n        if not self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:\n            exclude_sql = self.expressions(sqls=exclude, flat=True)\n            expressions = f\"{expressions}{self.seg('EXCLUDE')} ({exclude_sql})\"\n\n        # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata\n        # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first.\n        top_distinct = f\"{distinct}{hint}{top}\" if self.LIMIT_IS_TOP else f\"{top}{hint}{distinct}\"\n        expressions = f\"{self.sep()}{expressions}\" if expressions else expressions\n        sql = self.query_modifiers(\n            expression,\n            f\"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}\",\n            self.sql(expression, \"into\", comment=False),\n            self.sql(expression, \"from_\", comment=False),\n        )\n\n        # If both the CTE and SELECT clauses have comments, generate the latter earlier\n        if expression.args.get(\"with_\"):\n            sql = self.maybe_comment(sql, expression)\n            expression.pop_comments()\n\n        sql = self.prepend_ctes(expression, sql)\n\n        if self.STAR_EXCLUDE_REQUIRES_DERIVED_TABLE and exclude:\n            expression.set(\"exclude\", None)\n            subquery = expression.subquery(copy=False)\n            star = exp.Star(except_=exclude)\n            sql = self.sql(exp.select(star).from_(subquery, copy=False))\n\n        if not self.SUPPORTS_SELECT_INTO and into:\n            if into.args.get(\"temporary\"):\n                table_kind = \" TEMPORARY\"\n            elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get(\"unlogged\"):\n                table_kind = \" UNLOGGED\"\n            else:\n                table_kind = \"\"\n            sql = f\"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}\"\n\n        return sql\n\n    def schema_sql(self, expression: exp.Schema) -> str:\n        this = self.sql(expression, \"this\")\n        sql = self.schema_columns_sql(expression)\n        return f\"{this} {sql}\" if this and sql else this or sql\n\n    def schema_columns_sql(self, expression: exp.Schema) -> str:\n        if expression.expressions:\n            return f\"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}\"\n        return \"\"\n\n    def star_sql(self, expression: exp.Star) -> str:\n        except_ = self.expressions(expression, key=\"except_\", flat=True)\n        except_ = f\"{self.seg(self.STAR_EXCEPT)} ({except_})\" if except_ else \"\"\n        replace = self.expressions(expression, key=\"replace\", flat=True)\n        replace = f\"{self.seg('REPLACE')} ({replace})\" if replace else \"\"\n        rename = self.expressions(expression, key=\"rename\", flat=True)\n        rename = f\"{self.seg('RENAME')} ({rename})\" if rename else \"\"\n        return f\"*{except_}{replace}{rename}\"\n\n    def parameter_sql(self, expression: exp.Parameter) -> str:\n        this = self.sql(expression, \"this\")\n        return f\"{self.PARAMETER_TOKEN}{this}\"\n\n    def sessionparameter_sql(self, expression: exp.SessionParameter) -> str:\n        this = self.sql(expression, \"this\")\n        kind = expression.text(\"kind\")\n        if kind:\n            kind = f\"{kind}.\"\n        return f\"@@{kind}{this}\"\n\n    def placeholder_sql(self, expression: exp.Placeholder) -> str:\n        return f\"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}\" if expression.this else \"?\"\n\n    def subquery_sql(self, expression: exp.Subquery, sep: str = \" AS \") -> str:\n        alias = self.sql(expression, \"alias\")\n        alias = f\"{sep}{alias}\" if alias else \"\"\n        sample = self.sql(expression, \"sample\")\n        if self.dialect.ALIAS_POST_TABLESAMPLE and sample:\n            alias = f\"{sample}{alias}\"\n\n            # Set to None so it's not generated again by self.query_modifiers()\n            expression.set(\"sample\", None)\n\n        pivots = self.expressions(expression, key=\"pivots\", sep=\"\", flat=True)\n        sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots)\n        return self.prepend_ctes(expression, sql)\n\n    def qualify_sql(self, expression: exp.Qualify) -> str:\n        this = self.indent(self.sql(expression, \"this\"))\n        return f\"{self.seg('QUALIFY')}{self.sep()}{this}\"\n\n    def unnest_sql(self, expression: exp.Unnest) -> str:\n        args = self.expressions(expression, flat=True)\n\n        alias = expression.args.get(\"alias\")\n        offset = expression.args.get(\"offset\")\n\n        if self.UNNEST_WITH_ORDINALITY:\n            if alias and isinstance(offset, exp.Expr):\n                alias.append(\"columns\", offset)\n\n        if alias and self.dialect.UNNEST_COLUMN_ONLY:\n            columns = alias.columns\n            alias = self.sql(columns[0]) if columns else \"\"\n        else:\n            alias = self.sql(alias)\n\n        alias = f\" AS {alias}\" if alias else alias\n        if self.UNNEST_WITH_ORDINALITY:\n            suffix = f\" WITH ORDINALITY{alias}\" if offset else alias\n        else:\n            if isinstance(offset, exp.Expr):\n                suffix = f\"{alias} WITH OFFSET AS {self.sql(offset)}\"\n            elif offset:\n                suffix = f\"{alias} WITH OFFSET\"\n            else:\n                suffix = alias\n\n        return f\"UNNEST({args}){suffix}\"\n\n    def prewhere_sql(self, expression: exp.PreWhere) -> str:\n        return \"\"\n\n    def where_sql(self, expression: exp.Where) -> str:\n        this = self.indent(self.sql(expression, \"this\"))\n        return f\"{self.seg('WHERE')}{self.sep()}{this}\"\n\n    def window_sql(self, expression: exp.Window) -> str:\n        this = self.sql(expression, \"this\")\n        partition = self.partition_by_sql(expression)\n        order = expression.args.get(\"order\")\n        order = self.order_sql(order, flat=True) if order else \"\"\n        spec = self.sql(expression, \"spec\")\n        alias = self.sql(expression, \"alias\")\n        over = self.sql(expression, \"over\") or \"OVER\"\n\n        this = f\"{this} {'AS' if expression.arg_key == 'windows' else over}\"\n\n        first = expression.args.get(\"first\")\n        if first is None:\n            first = \"\"\n        else:\n            first = \"FIRST\" if first else \"LAST\"\n\n        if not partition and not order and not spec and alias:\n            return f\"{this} {alias}\"\n\n        args = self.format_args(\n            *[arg for arg in (alias, first, partition, order, spec) if arg], sep=\" \"\n        )\n        return f\"{this} ({args})\"\n\n    def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str:\n        partition = self.expressions(expression, key=\"partition_by\", flat=True)\n        return f\"PARTITION BY {partition}\" if partition else \"\"\n\n    def windowspec_sql(self, expression: exp.WindowSpec) -> str:\n        kind = self.sql(expression, \"kind\")\n        start = csv(self.sql(expression, \"start\"), self.sql(expression, \"start_side\"), sep=\" \")\n        end = (\n            csv(self.sql(expression, \"end\"), self.sql(expression, \"end_side\"), sep=\" \")\n            or \"CURRENT ROW\"\n        )\n\n        window_spec = f\"{kind} BETWEEN {start} AND {end}\"\n\n        exclude = self.sql(expression, \"exclude\")\n        if exclude:\n            if self.SUPPORTS_WINDOW_EXCLUDE:\n                window_spec += f\" EXCLUDE {exclude}\"\n            else:\n                self.unsupported(\"EXCLUDE clause is not supported in the WINDOW clause\")\n\n        return window_spec\n\n    def withingroup_sql(self, expression: exp.WithinGroup) -> str:\n        this = self.sql(expression, \"this\")\n        expression_sql = self.sql(expression, \"expression\")[1:]  # order has a leading space\n        return f\"{this} WITHIN GROUP ({expression_sql})\"\n\n    def between_sql(self, expression: exp.Between) -> str:\n        this = self.sql(expression, \"this\")\n        low = self.sql(expression, \"low\")\n        high = self.sql(expression, \"high\")\n        symmetric = expression.args.get(\"symmetric\")\n\n        if symmetric and not self.SUPPORTS_BETWEEN_FLAGS:\n            return f\"({this} BETWEEN {low} AND {high} OR {this} BETWEEN {high} AND {low})\"\n\n        flag = (\n            \" SYMMETRIC\"\n            if symmetric\n            else \" ASYMMETRIC\"\n            if symmetric is False and self.SUPPORTS_BETWEEN_FLAGS\n            else \"\"  # silently drop ASYMMETRIC – semantics identical\n        )\n        return f\"{this} BETWEEN{flag} {low} AND {high}\"\n\n    def bracket_offset_expressions(\n        self, expression: exp.Bracket, index_offset: t.Optional[int] = None\n    ) -> t.List[exp.Expr]:\n        return apply_index_offset(\n            expression.this,\n            expression.expressions,\n            (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get(\"offset\", 0),\n            dialect=self.dialect,\n        )\n\n    def bracket_sql(self, expression: exp.Bracket) -> str:\n        expressions = self.bracket_offset_expressions(expression)\n        expressions_sql = \", \".join(self.sql(e) for e in expressions)\n        return f\"{self.sql(expression, 'this')}[{expressions_sql}]\"\n\n    def all_sql(self, expression: exp.All) -> str:\n        this = self.sql(expression, \"this\")\n        if not isinstance(expression.this, (exp.Tuple, exp.Paren)):\n            this = self.wrap(this)\n        return f\"ALL {this}\"\n\n    def any_sql(self, expression: exp.Any) -> str:\n        this = self.sql(expression, \"this\")\n        if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)):\n            if isinstance(expression.this, exp.UNWRAPPED_QUERIES):\n                this = self.wrap(this)\n            return f\"ANY{this}\"\n        return f\"ANY {this}\"\n\n    def exists_sql(self, expression: exp.Exists) -> str:\n        return f\"EXISTS{self.wrap(expression)}\"\n\n    def case_sql(self, expression: exp.Case) -> str:\n        this = self.sql(expression, \"this\")\n        statements = [f\"CASE {this}\" if this else \"CASE\"]\n\n        for e in expression.args[\"ifs\"]:\n            statements.append(f\"WHEN {self.sql(e, 'this')}\")\n            statements.append(f\"THEN {self.sql(e, 'true')}\")\n\n        default = self.sql(expression, \"default\")\n\n        if default:\n            statements.append(f\"ELSE {default}\")\n\n        statements.append(\"END\")\n\n        if self.pretty and self.too_wide(statements):\n            return self.indent(\"\\n\".join(statements), skip_first=True, skip_last=True)\n\n        return \" \".join(statements)\n\n    def constraint_sql(self, expression: exp.Constraint) -> str:\n        this = self.sql(expression, \"this\")\n        expressions = self.expressions(expression, flat=True)\n        return f\"CONSTRAINT {this} {expressions}\"\n\n    def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str:\n        order = expression.args.get(\"order\")\n        order = f\" OVER ({self.order_sql(order, flat=True)})\" if order else \"\"\n        return f\"NEXT VALUE FOR {self.sql(expression, 'this')}{order}\"\n\n    def extract_sql(self, expression: exp.Extract) -> str:\n        from sqlglot.dialects.dialect import map_date_part\n\n        this = (\n            map_date_part(expression.this, self.dialect)\n            if self.NORMALIZE_EXTRACT_DATE_PARTS\n            else expression.this\n        )\n        this_sql = self.sql(this) if self.EXTRACT_ALLOWS_QUOTES else this.name\n        expression_sql = self.sql(expression, \"expression\")\n\n        return f\"EXTRACT({this_sql} FROM {expression_sql})\"\n\n    def trim_sql(self, expression: exp.Trim) -> str:\n        trim_type = self.sql(expression, \"position\")\n\n        if trim_type == \"LEADING\":\n            func_name = \"LTRIM\"\n        elif trim_type == \"TRAILING\":\n            func_name = \"RTRIM\"\n        else:\n            func_name = \"TRIM\"\n\n        return self.func(func_name, expression.this, expression.expression)\n\n    def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expr]:\n        args = expression.expressions\n        if isinstance(expression, exp.ConcatWs):\n            args = args[1:]  # Skip the delimiter\n\n        if self.dialect.STRICT_STRING_CONCAT and expression.args.get(\"safe\"):\n            args = [exp.cast(e, exp.DType.TEXT) for e in args]\n\n        if not self.dialect.CONCAT_COALESCE and expression.args.get(\"coalesce\"):\n\n            def _wrap_with_coalesce(e: exp.Expr) -> exp.Expr:\n                if not e.type:\n                    from sqlglot.optimizer.annotate_types import annotate_types\n\n                    e = annotate_types(e, dialect=self.dialect)\n\n                if e.is_string or e.is_type(exp.DType.ARRAY):\n                    return e\n\n                return exp.func(\"coalesce\", e, exp.Literal.string(\"\"))\n\n            args = [_wrap_with_coalesce(e) for e in args]\n\n        return args\n\n    def concat_sql(self, expression: exp.Concat) -> str:\n        if self.dialect.CONCAT_COALESCE and not expression.args.get(\"coalesce\"):\n            # Dialect's CONCAT function coalesces NULLs to empty strings, but the expression does not.\n            # Transpile to double pipe operators, which typically returns NULL if any args are NULL\n            # instead of coalescing them to empty string.\n            from sqlglot.dialects.dialect import concat_to_dpipe_sql\n\n            return concat_to_dpipe_sql(self, expression)\n\n        expressions = self.convert_concat_args(expression)\n\n        # Some dialects don't allow a single-argument CONCAT call\n        if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1:\n            return self.sql(expressions[0])\n\n        return self.func(\"CONCAT\", *expressions)\n\n    def concatws_sql(self, expression: exp.ConcatWs) -> str:\n        return self.func(\n            \"CONCAT_WS\", seq_get(expression.expressions, 0), *self.convert_concat_args(expression)\n        )\n\n    def check_sql(self, expression: exp.Check) -> str:\n        this = self.sql(expression, key=\"this\")\n        return f\"CHECK ({this})\"\n\n    def foreignkey_sql(self, expression: exp.ForeignKey) -> str:\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\" ({expressions})\" if expressions else \"\"\n        reference = self.sql(expression, \"reference\")\n        reference = f\" {reference}\" if reference else \"\"\n        delete = self.sql(expression, \"delete\")\n        delete = f\" ON DELETE {delete}\" if delete else \"\"\n        update = self.sql(expression, \"update\")\n        update = f\" ON UPDATE {update}\" if update else \"\"\n        options = self.expressions(expression, key=\"options\", flat=True, sep=\" \")\n        options = f\" {options}\" if options else \"\"\n        return f\"FOREIGN KEY{expressions}{reference}{delete}{update}{options}\"\n\n    def primarykey_sql(self, expression: exp.PrimaryKey) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        expressions = self.expressions(expression, flat=True)\n        include = self.sql(expression, \"include\")\n        options = self.expressions(expression, key=\"options\", flat=True, sep=\" \")\n        options = f\" {options}\" if options else \"\"\n        return f\"PRIMARY KEY{this} ({expressions}){include}{options}\"\n\n    def if_sql(self, expression: exp.If) -> str:\n        return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get(\"false\")))\n\n    def matchagainst_sql(self, expression: exp.MatchAgainst) -> str:\n        if self.MATCH_AGAINST_TABLE_PREFIX:\n            expressions = []\n            for expr in expression.expressions:\n                if isinstance(expr, exp.Table):\n                    expressions.append(f\"TABLE {self.sql(expr)}\")\n                else:\n                    expressions.append(expr)\n        else:\n            expressions = expression.expressions\n\n        modifier = expression.args.get(\"modifier\")\n        modifier = f\" {modifier}\" if modifier else \"\"\n        return (\n            f\"{self.func('MATCH', *expressions)} AGAINST({self.sql(expression, 'this')}{modifier})\"\n        )\n\n    def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str:\n        return f\"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}\"\n\n    def jsonpath_sql(self, expression: exp.JSONPath) -> str:\n        path = self.expressions(expression, sep=\"\", flat=True).lstrip(\".\")\n\n        if expression.args.get(\"escape\"):\n            path = self.escape_str(path)\n\n        if self.QUOTE_JSON_PATH:\n            path = f\"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}\"\n\n        return path\n\n    def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str:\n        if isinstance(expression, exp.JSONPathPart):\n            transform = self.TRANSFORMS.get(expression.__class__)\n            if not callable(transform):\n                self.unsupported(f\"Unsupported JSONPathPart type {expression.__class__.__name__}\")\n                return \"\"\n\n            return transform(self, expression)\n\n        if isinstance(expression, int):\n            return str(expression)\n\n        if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE:\n            escaped = expression.replace(\"'\", \"\\\\'\")\n            escaped = f\"\\\\'{expression}\\\\'\"\n        else:\n            escaped = expression.replace('\"', '\\\\\"')\n            escaped = f'\"{escaped}\"'\n\n        return escaped\n\n    def formatjson_sql(self, expression: exp.FormatJson) -> str:\n        return f\"{self.sql(expression, 'this')} FORMAT JSON\"\n\n    def formatphrase_sql(self, expression: exp.FormatPhrase) -> str:\n        # Output the Teradata column FORMAT override.\n        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT\n        this = self.sql(expression, \"this\")\n        fmt = self.sql(expression, \"format\")\n        return f\"{this} (FORMAT {fmt})\"\n\n    def _jsonobject_sql(\n        self, expression: exp.JSONObject | exp.JSONObjectAgg, name: str = \"\"\n    ) -> str:\n        null_handling = expression.args.get(\"null_handling\")\n        null_handling = f\" {null_handling}\" if null_handling else \"\"\n\n        unique_keys = expression.args.get(\"unique_keys\")\n        if unique_keys is not None:\n            unique_keys = f\" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS\"\n        else:\n            unique_keys = \"\"\n\n        return_type = self.sql(expression, \"return_type\")\n        return_type = f\" RETURNING {return_type}\" if return_type else \"\"\n        encoding = self.sql(expression, \"encoding\")\n        encoding = f\" ENCODING {encoding}\" if encoding else \"\"\n\n        if not name:\n            name = \"JSON_OBJECT\" if isinstance(expression, exp.JSONObject) else \"JSON_OBJECTAGG\"\n\n        return self.func(\n            name,\n            *expression.expressions,\n            suffix=f\"{null_handling}{unique_keys}{return_type}{encoding})\",\n        )\n\n    def jsonarray_sql(self, expression: exp.JSONArray) -> str:\n        null_handling = expression.args.get(\"null_handling\")\n        null_handling = f\" {null_handling}\" if null_handling else \"\"\n        return_type = self.sql(expression, \"return_type\")\n        return_type = f\" RETURNING {return_type}\" if return_type else \"\"\n        strict = \" STRICT\" if expression.args.get(\"strict\") else \"\"\n        return self.func(\n            \"JSON_ARRAY\", *expression.expressions, suffix=f\"{null_handling}{return_type}{strict})\"\n        )\n\n    def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str:\n        this = self.sql(expression, \"this\")\n        order = self.sql(expression, \"order\")\n        null_handling = expression.args.get(\"null_handling\")\n        null_handling = f\" {null_handling}\" if null_handling else \"\"\n        return_type = self.sql(expression, \"return_type\")\n        return_type = f\" RETURNING {return_type}\" if return_type else \"\"\n        strict = \" STRICT\" if expression.args.get(\"strict\") else \"\"\n        return self.func(\n            \"JSON_ARRAYAGG\",\n            this,\n            suffix=f\"{order}{null_handling}{return_type}{strict})\",\n        )\n\n    def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str:\n        path = self.sql(expression, \"path\")\n        path = f\" PATH {path}\" if path else \"\"\n        nested_schema = self.sql(expression, \"nested_schema\")\n\n        if nested_schema:\n            return f\"NESTED{path} {nested_schema}\"\n\n        this = self.sql(expression, \"this\")\n        kind = self.sql(expression, \"kind\")\n        kind = f\" {kind}\" if kind else \"\"\n\n        ordinality = \" FOR ORDINALITY\" if expression.args.get(\"ordinality\") else \"\"\n        return f\"{this}{kind}{path}{ordinality}\"\n\n    def jsonschema_sql(self, expression: exp.JSONSchema) -> str:\n        return self.func(\"COLUMNS\", *expression.expressions)\n\n    def jsontable_sql(self, expression: exp.JSONTable) -> str:\n        this = self.sql(expression, \"this\")\n        path = self.sql(expression, \"path\")\n        path = f\", {path}\" if path else \"\"\n        error_handling = expression.args.get(\"error_handling\")\n        error_handling = f\" {error_handling}\" if error_handling else \"\"\n        empty_handling = expression.args.get(\"empty_handling\")\n        empty_handling = f\" {empty_handling}\" if empty_handling else \"\"\n        schema = self.sql(expression, \"schema\")\n        return self.func(\n            \"JSON_TABLE\", this, suffix=f\"{path}{error_handling}{empty_handling} {schema})\"\n        )\n\n    def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str:\n        this = self.sql(expression, \"this\")\n        kind = self.sql(expression, \"kind\")\n        path = self.sql(expression, \"path\")\n        path = f\" {path}\" if path else \"\"\n        as_json = \" AS JSON\" if expression.args.get(\"as_json\") else \"\"\n        return f\"{this} {kind}{path}{as_json}\"\n\n    def openjson_sql(self, expression: exp.OpenJSON) -> str:\n        this = self.sql(expression, \"this\")\n        path = self.sql(expression, \"path\")\n        path = f\", {path}\" if path else \"\"\n        expressions = self.expressions(expression)\n        with_ = (\n            f\" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}\"\n            if expressions\n            else \"\"\n        )\n        return f\"OPENJSON({this}{path}){with_}\"\n\n    def in_sql(self, expression: exp.In) -> str:\n        query = expression.args.get(\"query\")\n        unnest = expression.args.get(\"unnest\")\n        field = expression.args.get(\"field\")\n        is_global = \" GLOBAL\" if expression.args.get(\"is_global\") else \"\"\n\n        if query:\n            in_sql = self.sql(query)\n        elif unnest:\n            in_sql = self.in_unnest_op(unnest)\n        elif field:\n            in_sql = self.sql(field)\n        else:\n            in_sql = f\"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})\"\n\n        return f\"{self.sql(expression, 'this')}{is_global} IN {in_sql}\"\n\n    def in_unnest_op(self, unnest: exp.Unnest) -> str:\n        return f\"(SELECT {self.sql(unnest)})\"\n\n    def interval_sql(self, expression: exp.Interval) -> str:\n        unit_expression = expression.args.get(\"unit\")\n        unit = self.sql(unit_expression) if unit_expression else \"\"\n        if not self.INTERVAL_ALLOWS_PLURAL_FORM:\n            unit = self.TIME_PART_SINGULARS.get(unit, unit)\n        unit = f\" {unit}\" if unit else \"\"\n\n        if self.SINGLE_STRING_INTERVAL:\n            this = expression.this.name if expression.this else \"\"\n            if this:\n                if unit_expression and isinstance(unit_expression, exp.IntervalSpan):\n                    return f\"INTERVAL '{this}'{unit}\"\n                return f\"INTERVAL '{this}{unit}'\"\n            return f\"INTERVAL{unit}\"\n\n        this = self.sql(expression, \"this\")\n        if this:\n            unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES)\n            this = f\" {this}\" if unwrapped else f\" ({this})\"\n\n        return f\"INTERVAL{this}{unit}\"\n\n    def return_sql(self, expression: exp.Return) -> str:\n        return f\"RETURN {self.sql(expression, 'this')}\"\n\n    def reference_sql(self, expression: exp.Reference) -> str:\n        this = self.sql(expression, \"this\")\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\"({expressions})\" if expressions else \"\"\n        options = self.expressions(expression, key=\"options\", flat=True, sep=\" \")\n        options = f\" {options}\" if options else \"\"\n        return f\"REFERENCES {this}{expressions}{options}\"\n\n    def anonymous_sql(self, expression: exp.Anonymous) -> str:\n        # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive\n        parent = expression.parent\n        is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression\n\n        return self.func(\n            self.sql(expression, \"this\"), *expression.expressions, normalize=not is_qualified\n        )\n\n    def paren_sql(self, expression: exp.Paren) -> str:\n        sql = self.seg(self.indent(self.sql(expression, \"this\")), sep=\"\")\n        return f\"({sql}{self.seg(')', sep='')}\"\n\n    def neg_sql(self, expression: exp.Neg) -> str:\n        # This makes sure we don't convert \"- - 5\" to \"--5\", which is a comment\n        this_sql = self.sql(expression, \"this\")\n        sep = \" \" if this_sql[0] == \"-\" else \"\"\n        return f\"-{sep}{this_sql}\"\n\n    def not_sql(self, expression: exp.Not) -> str:\n        return f\"NOT {self.sql(expression, 'this')}\"\n\n    def alias_sql(self, expression: exp.Alias) -> str:\n        alias = self.sql(expression, \"alias\")\n        alias = f\" AS {alias}\" if alias else \"\"\n        return f\"{self.sql(expression, 'this')}{alias}\"\n\n    def pivotalias_sql(self, expression: exp.PivotAlias) -> str:\n        alias = expression.args[\"alias\"]\n\n        parent = expression.parent\n        pivot = parent and parent.parent\n\n        if isinstance(pivot, exp.Pivot) and pivot.unpivot:\n            identifier_alias = isinstance(alias, exp.Identifier)\n            literal_alias = isinstance(alias, exp.Literal)\n\n            if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:\n                alias.replace(exp.Literal.string(alias.output_name))\n            elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS:\n                alias.replace(exp.to_identifier(alias.output_name))\n\n        return self.alias_sql(expression)\n\n    def aliases_sql(self, expression: exp.Aliases) -> str:\n        return f\"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})\"\n\n    def atindex_sql(self, expression: exp.AtTimeZone) -> str:\n        this = self.sql(expression, \"this\")\n        index = self.sql(expression, \"expression\")\n        return f\"{this} AT {index}\"\n\n    def attimezone_sql(self, expression: exp.AtTimeZone) -> str:\n        this = self.sql(expression, \"this\")\n        zone = self.sql(expression, \"zone\")\n        return f\"{this} AT TIME ZONE {zone}\"\n\n    def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str:\n        this = self.sql(expression, \"this\")\n        zone = self.sql(expression, \"zone\")\n        return f\"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'\"\n\n    def add_sql(self, expression: exp.Add) -> str:\n        return self.binary(expression, \"+\")\n\n    def and_sql(self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expr]] = None) -> str:\n        return self.connector_sql(expression, \"AND\", stack)\n\n    def or_sql(self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expr]] = None) -> str:\n        return self.connector_sql(expression, \"OR\", stack)\n\n    def xor_sql(self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expr]] = None) -> str:\n        return self.connector_sql(expression, \"XOR\", stack)\n\n    def connector_sql(\n        self,\n        expression: exp.Connector,\n        op: str,\n        stack: t.Optional[t.List[str | exp.Expr]] = None,\n    ) -> str:\n        if stack is not None:\n            if expression.expressions:\n                stack.append(self.expressions(expression, sep=f\" {op} \"))\n            else:\n                stack.append(expression.right)\n                if expression.comments and self.comments:\n                    for comment in expression.comments:\n                        if comment:\n                            op += f\" /*{self.sanitize_comment(comment)}*/\"\n                stack.extend((op, expression.left))\n            return op\n\n        stack = [expression]\n        sqls: t.List[str] = []\n        ops = set()\n\n        while stack:\n            node = stack.pop()\n            if isinstance(node, exp.Connector):\n                ops.add(getattr(self, f\"{node.key}_sql\")(node, stack))\n            else:\n                sql = self.sql(node)\n                if sqls and sqls[-1] in ops:\n                    sqls[-1] += f\" {sql}\"\n                else:\n                    sqls.append(sql)\n\n        sep = \"\\n\" if self.pretty and self.too_wide(sqls) else \" \"\n        return sep.join(sqls)\n\n    def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str:\n        return self.binary(expression, \"&\")\n\n    def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str:\n        return self.binary(expression, \"<<\")\n\n    def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str:\n        return f\"~{self.sql(expression, 'this')}\"\n\n    def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str:\n        return self.binary(expression, \"|\")\n\n    def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str:\n        return self.binary(expression, \">>\")\n\n    def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str:\n        return self.binary(expression, \"^\")\n\n    def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str:\n        format_sql = self.sql(expression, \"format\")\n        format_sql = f\" FORMAT {format_sql}\" if format_sql else \"\"\n        to_sql = self.sql(expression, \"to\")\n        to_sql = f\" {to_sql}\" if to_sql else \"\"\n        action = self.sql(expression, \"action\")\n        action = f\" {action}\" if action else \"\"\n        default = self.sql(expression, \"default\")\n        default = f\" DEFAULT {default} ON CONVERSION ERROR\" if default else \"\"\n        return f\"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})\"\n\n    # Base implementation that excludes safe, zone, and target_type metadata args\n    def strtotime_sql(self, expression: exp.StrToTime) -> str:\n        return self.func(\"STR_TO_TIME\", expression.this, expression.args.get(\"format\"))\n\n    def currentdate_sql(self, expression: exp.CurrentDate) -> str:\n        zone = self.sql(expression, \"this\")\n        return f\"CURRENT_DATE({zone})\" if zone else \"CURRENT_DATE\"\n\n    def collate_sql(self, expression: exp.Collate) -> str:\n        if self.COLLATE_IS_FUNC:\n            return self.function_fallback_sql(expression)\n        return self.binary(expression, \"COLLATE\")\n\n    def command_sql(self, expression: exp.Command) -> str:\n        return f\"{self.sql(expression, 'this')} {expression.text('expression').strip()}\"\n\n    def comment_sql(self, expression: exp.Comment) -> str:\n        this = self.sql(expression, \"this\")\n        kind = expression.args[\"kind\"]\n        materialized = \" MATERIALIZED\" if expression.args.get(\"materialized\") else \"\"\n        exists_sql = \" IF EXISTS \" if expression.args.get(\"exists\") else \" \"\n        expression_sql = self.sql(expression, \"expression\")\n        return f\"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}\"\n\n    def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str:\n        this = self.sql(expression, \"this\")\n        delete = \" DELETE\" if expression.args.get(\"delete\") else \"\"\n        recompress = self.sql(expression, \"recompress\")\n        recompress = f\" RECOMPRESS {recompress}\" if recompress else \"\"\n        to_disk = self.sql(expression, \"to_disk\")\n        to_disk = f\" TO DISK {to_disk}\" if to_disk else \"\"\n        to_volume = self.sql(expression, \"to_volume\")\n        to_volume = f\" TO VOLUME {to_volume}\" if to_volume else \"\"\n        return f\"{this}{delete}{recompress}{to_disk}{to_volume}\"\n\n    def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str:\n        where = self.sql(expression, \"where\")\n        group = self.sql(expression, \"group\")\n        aggregates = self.expressions(expression, key=\"aggregates\")\n        aggregates = self.seg(\"SET\") + self.seg(aggregates) if aggregates else \"\"\n\n        if not (where or group or aggregates) and len(expression.expressions) == 1:\n            return f\"TTL {self.expressions(expression, flat=True)}\"\n\n        return f\"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}\"\n\n    def transaction_sql(self, expression: exp.Transaction) -> str:\n        modes = self.expressions(expression, key=\"modes\")\n        modes = f\" {modes}\" if modes else \"\"\n        return f\"BEGIN{modes}\"\n\n    def commit_sql(self, expression: exp.Commit) -> str:\n        chain = expression.args.get(\"chain\")\n        if chain is not None:\n            chain = \" AND CHAIN\" if chain else \" AND NO CHAIN\"\n\n        return f\"COMMIT{chain or ''}\"\n\n    def rollback_sql(self, expression: exp.Rollback) -> str:\n        savepoint = expression.args.get(\"savepoint\")\n        savepoint = f\" TO {savepoint}\" if savepoint else \"\"\n        return f\"ROLLBACK{savepoint}\"\n\n    def altercolumn_sql(self, expression: exp.AlterColumn) -> str:\n        this = self.sql(expression, \"this\")\n\n        dtype = self.sql(expression, \"dtype\")\n        if dtype:\n            collate = self.sql(expression, \"collate\")\n            collate = f\" COLLATE {collate}\" if collate else \"\"\n            using = self.sql(expression, \"using\")\n            using = f\" USING {using}\" if using else \"\"\n            alter_set_type = self.ALTER_SET_TYPE + \" \" if self.ALTER_SET_TYPE else \"\"\n            return f\"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}\"\n\n        default = self.sql(expression, \"default\")\n        if default:\n            return f\"ALTER COLUMN {this} SET DEFAULT {default}\"\n\n        comment = self.sql(expression, \"comment\")\n        if comment:\n            return f\"ALTER COLUMN {this} COMMENT {comment}\"\n\n        visible = expression.args.get(\"visible\")\n        if visible:\n            return f\"ALTER COLUMN {this} SET {visible}\"\n\n        allow_null = expression.args.get(\"allow_null\")\n        drop = expression.args.get(\"drop\")\n\n        if not drop and not allow_null:\n            self.unsupported(\"Unsupported ALTER COLUMN syntax\")\n\n        if allow_null is not None:\n            keyword = \"DROP\" if drop else \"SET\"\n            return f\"ALTER COLUMN {this} {keyword} NOT NULL\"\n\n        return f\"ALTER COLUMN {this} DROP DEFAULT\"\n\n    def alterindex_sql(self, expression: exp.AlterIndex) -> str:\n        this = self.sql(expression, \"this\")\n\n        visible = expression.args.get(\"visible\")\n        visible_sql = \"VISIBLE\" if visible else \"INVISIBLE\"\n\n        return f\"ALTER INDEX {this} {visible_sql}\"\n\n    def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str:\n        this = self.sql(expression, \"this\")\n        if not isinstance(expression.this, exp.Var):\n            this = f\"KEY DISTKEY {this}\"\n        return f\"ALTER DISTSTYLE {this}\"\n\n    def altersortkey_sql(self, expression: exp.AlterSortKey) -> str:\n        compound = \" COMPOUND\" if expression.args.get(\"compound\") else \"\"\n        this = self.sql(expression, \"this\")\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\"({expressions})\" if expressions else \"\"\n        return f\"ALTER{compound} SORTKEY {this or expressions}\"\n\n    def alterrename_sql(self, expression: exp.AlterRename, include_to: bool = True) -> str:\n        if not self.RENAME_TABLE_WITH_DB:\n            # Remove db from tables\n            expression = expression.transform(\n                lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n\n            ).assert_is(exp.AlterRename)\n        this = self.sql(expression, \"this\")\n        to_kw = \" TO\" if include_to else \"\"\n        return f\"RENAME{to_kw} {this}\"\n\n    def renamecolumn_sql(self, expression: exp.RenameColumn) -> str:\n        exists = \" IF EXISTS\" if expression.args.get(\"exists\") else \"\"\n        old_column = self.sql(expression, \"this\")\n        new_column = self.sql(expression, \"to\")\n        return f\"RENAME COLUMN{exists} {old_column} TO {new_column}\"\n\n    def alterset_sql(self, expression: exp.AlterSet) -> str:\n        exprs = self.expressions(expression, flat=True)\n        if self.ALTER_SET_WRAPPED:\n            exprs = f\"({exprs})\"\n\n        return f\"SET {exprs}\"\n\n    def alter_sql(self, expression: exp.Alter) -> str:\n        actions = expression.args[\"actions\"]\n\n        if not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN and isinstance(\n            actions[0], exp.ColumnDef\n        ):\n            actions_sql = self.expressions(expression, key=\"actions\", flat=True)\n            actions_sql = f\"ADD {actions_sql}\"\n        else:\n            actions_list = []\n            for action in actions:\n                if isinstance(action, (exp.ColumnDef, exp.Schema)):\n                    action_sql = self.add_column_sql(action)\n                else:\n                    action_sql = self.sql(action)\n                    if isinstance(action, exp.Query):\n                        action_sql = f\"AS {action_sql}\"\n\n                actions_list.append(action_sql)\n\n            actions_sql = self.format_args(*actions_list).lstrip(\"\\n\")\n\n        exists = \" IF EXISTS\" if expression.args.get(\"exists\") else \"\"\n        on_cluster = self.sql(expression, \"cluster\")\n        on_cluster = f\" {on_cluster}\" if on_cluster else \"\"\n        only = \" ONLY\" if expression.args.get(\"only\") else \"\"\n        options = self.expressions(expression, key=\"options\")\n        options = f\", {options}\" if options else \"\"\n        kind = self.sql(expression, \"kind\")\n        not_valid = \" NOT VALID\" if expression.args.get(\"not_valid\") else \"\"\n        check = \" WITH CHECK\" if expression.args.get(\"check\") else \"\"\n        cascade = (\n            \" CASCADE\"\n            if expression.args.get(\"cascade\") and self.dialect.ALTER_TABLE_SUPPORTS_CASCADE\n            else \"\"\n        )\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n\n        return f\"ALTER {kind}{exists}{only}{this}{on_cluster}{check}{self.sep()}{actions_sql}{not_valid}{options}{cascade}\"\n\n    def altersession_sql(self, expression: exp.AlterSession) -> str:\n        items_sql = self.expressions(expression, flat=True)\n        keyword = \"UNSET\" if expression.args.get(\"unset\") else \"SET\"\n        return f\"{keyword} {items_sql}\"\n\n    def add_column_sql(self, expression: exp.Expr) -> str:\n        sql = self.sql(expression)\n        if isinstance(expression, exp.Schema):\n            column_text = \" COLUMNS\"\n        elif isinstance(expression, exp.ColumnDef) and self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD:\n            column_text = \" COLUMN\"\n        else:\n            column_text = \"\"\n\n        return f\"ADD{column_text} {sql}\"\n\n    def droppartition_sql(self, expression: exp.DropPartition) -> str:\n        expressions = self.expressions(expression)\n        exists = \" IF EXISTS \" if expression.args.get(\"exists\") else \" \"\n        return f\"DROP{exists}{expressions}\"\n\n    def addconstraint_sql(self, expression: exp.AddConstraint) -> str:\n        return f\"ADD {self.expressions(expression, indent=False)}\"\n\n    def addpartition_sql(self, expression: exp.AddPartition) -> str:\n        exists = \"IF NOT EXISTS \" if expression.args.get(\"exists\") else \"\"\n        location = self.sql(expression, \"location\")\n        location = f\" {location}\" if location else \"\"\n        return f\"ADD {exists}{self.sql(expression.this)}{location}\"\n\n    def distinct_sql(self, expression: exp.Distinct) -> str:\n        this = self.expressions(expression, flat=True)\n\n        if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1:\n            case = exp.case()\n            for arg in expression.expressions:\n                case = case.when(arg.is_(exp.null()), exp.null())\n            this = self.sql(case.else_(f\"({this})\"))\n\n        this = f\" {this}\" if this else \"\"\n\n        on = self.sql(expression, \"on\")\n        on = f\" ON {on}\" if on else \"\"\n        return f\"DISTINCT{this}{on}\"\n\n    def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str:\n        return self._embed_ignore_nulls(expression, \"IGNORE NULLS\")\n\n    def respectnulls_sql(self, expression: exp.RespectNulls) -> str:\n        return self._embed_ignore_nulls(expression, \"RESPECT NULLS\")\n\n    def havingmax_sql(self, expression: exp.HavingMax) -> str:\n        this_sql = self.sql(expression, \"this\")\n        expression_sql = self.sql(expression, \"expression\")\n        kind = \"MAX\" if expression.args.get(\"max\") else \"MIN\"\n        return f\"{this_sql} HAVING {kind} {expression_sql}\"\n\n    def intdiv_sql(self, expression: exp.IntDiv) -> str:\n        return self.sql(\n            exp.Cast(\n                this=exp.Div(this=expression.this, expression=expression.expression),\n                to=exp.DataType(this=exp.DType.INT),\n            )\n        )\n\n    def dpipe_sql(self, expression: exp.DPipe) -> str:\n        if self.dialect.STRICT_STRING_CONCAT and expression.args.get(\"safe\"):\n            return self.func(\"CONCAT\", *(exp.cast(e, exp.DType.TEXT) for e in expression.flatten()))\n        return self.binary(expression, \"||\")\n\n    def div_sql(self, expression: exp.Div) -> str:\n        l, r = expression.left, expression.right\n\n        if not self.dialect.SAFE_DIVISION and expression.args.get(\"safe\"):\n            r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0)))\n\n        if self.dialect.TYPED_DIVISION and not expression.args.get(\"typed\"):\n            if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES):\n                l.replace(exp.cast(l.copy(), to=exp.DType.DOUBLE))\n\n        elif not self.dialect.TYPED_DIVISION and expression.args.get(\"typed\"):\n            if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES):\n                return self.sql(\n                    exp.cast(\n                        l / r,\n                        to=exp.DType.BIGINT,\n                    )\n                )\n\n        return self.binary(expression, \"/\")\n\n    def safedivide_sql(self, expression: exp.SafeDivide) -> str:\n        n = exp._wrap(expression.this, exp.Binary)\n        d = exp._wrap(expression.expression, exp.Binary)\n        return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null()))\n\n    def overlaps_sql(self, expression: exp.Overlaps) -> str:\n        return self.binary(expression, \"OVERLAPS\")\n\n    def distance_sql(self, expression: exp.Distance) -> str:\n        return self.binary(expression, \"<->\")\n\n    def dot_sql(self, expression: exp.Dot) -> str:\n        return f\"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}\"\n\n    def eq_sql(self, expression: exp.EQ) -> str:\n        return self.binary(expression, \"=\")\n\n    def propertyeq_sql(self, expression: exp.PropertyEQ) -> str:\n        return self.binary(expression, \":=\")\n\n    def escape_sql(self, expression: exp.Escape) -> str:\n        return self.binary(expression, \"ESCAPE\")\n\n    def glob_sql(self, expression: exp.Glob) -> str:\n        return self.binary(expression, \"GLOB\")\n\n    def gt_sql(self, expression: exp.GT) -> str:\n        return self.binary(expression, \">\")\n\n    def gte_sql(self, expression: exp.GTE) -> str:\n        return self.binary(expression, \">=\")\n\n    def is_sql(self, expression: exp.Is) -> str:\n        if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean):\n            return self.sql(\n                expression.this if expression.expression.this else exp.not_(expression.this)\n            )\n        return self.binary(expression, \"IS\")\n\n    def _like_sql(self, expression: exp.Like | exp.ILike) -> str:\n        this = expression.this\n        rhs = expression.expression\n\n        if isinstance(expression, exp.Like):\n            exp_class: t.Type[exp.Like | exp.ILike] = exp.Like\n            op = \"LIKE\"\n        else:\n            exp_class = exp.ILike\n            op = \"ILIKE\"\n\n        if isinstance(rhs, (exp.All, exp.Any)) and not self.SUPPORTS_LIKE_QUANTIFIERS:\n            exprs = rhs.this.unnest()\n\n            if isinstance(exprs, exp.Tuple):\n                exprs = exprs.expressions\n            else:\n                exprs = [exprs]\n\n            connective = exp.or_ if isinstance(rhs, exp.Any) else exp.and_\n\n            like_expr: exp.Expr = exp_class(this=this, expression=exprs[0])\n            for expr in exprs[1:]:\n                like_expr = connective(like_expr, exp_class(this=this, expression=expr))\n\n            parent = expression.parent\n            if not isinstance(parent, type(like_expr)) and isinstance(parent, exp.Condition):\n                like_expr = exp.paren(like_expr, copy=False)\n\n            return self.sql(like_expr)\n\n        return self.binary(expression, op)\n\n    def like_sql(self, expression: exp.Like) -> str:\n        return self._like_sql(expression)\n\n    def ilike_sql(self, expression: exp.ILike) -> str:\n        return self._like_sql(expression)\n\n    def match_sql(self, expression: exp.Match) -> str:\n        return self.binary(expression, \"MATCH\")\n\n    def similarto_sql(self, expression: exp.SimilarTo) -> str:\n        return self.binary(expression, \"SIMILAR TO\")\n\n    def lt_sql(self, expression: exp.LT) -> str:\n        return self.binary(expression, \"<\")\n\n    def lte_sql(self, expression: exp.LTE) -> str:\n        return self.binary(expression, \"<=\")\n\n    def mod_sql(self, expression: exp.Mod) -> str:\n        return self.binary(expression, \"%\")\n\n    def mul_sql(self, expression: exp.Mul) -> str:\n        return self.binary(expression, \"*\")\n\n    def neq_sql(self, expression: exp.NEQ) -> str:\n        return self.binary(expression, \"<>\")\n\n    def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str:\n        return self.binary(expression, \"IS NOT DISTINCT FROM\")\n\n    def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str:\n        return self.binary(expression, \"IS DISTINCT FROM\")\n\n    def sub_sql(self, expression: exp.Sub) -> str:\n        return self.binary(expression, \"-\")\n\n    def trycast_sql(self, expression: exp.TryCast) -> str:\n        return self.cast_sql(expression, safe_prefix=\"TRY_\")\n\n    def jsoncast_sql(self, expression: exp.JSONCast) -> str:\n        return self.cast_sql(expression)\n\n    def try_sql(self, expression: exp.Try) -> str:\n        if not self.TRY_SUPPORTED:\n            self.unsupported(\"Unsupported TRY function\")\n            return self.sql(expression, \"this\")\n\n        return self.func(\"TRY\", expression.this)\n\n    def log_sql(self, expression: exp.Log) -> str:\n        this = expression.this\n        expr = expression.expression\n\n        if self.dialect.LOG_BASE_FIRST is False:\n            this, expr = expr, this\n        elif self.dialect.LOG_BASE_FIRST is None and expr:\n            if this.name in (\"2\", \"10\"):\n                return self.func(f\"LOG{this.name}\", expr)\n\n            self.unsupported(f\"Unsupported logarithm with base {self.sql(this)}\")\n\n        return self.func(\"LOG\", this, expr)\n\n    def use_sql(self, expression: exp.Use) -> str:\n        kind = self.sql(expression, \"kind\")\n        kind = f\" {kind}\" if kind else \"\"\n        this = self.sql(expression, \"this\") or self.expressions(expression, flat=True)\n        this = f\" {this}\" if this else \"\"\n        return f\"USE{kind}{this}\"\n\n    def binary(self, expression: exp.Binary, op: str) -> str:\n        sqls: t.List[str] = []\n        stack: t.List[None | str | exp.Expr] = [expression]\n        binary_type = type(expression)\n\n        while stack:\n            node = stack.pop()\n\n            if type(node) is binary_type:\n                op_func = node.args.get(\"operator\")\n                if op_func:\n                    op = f\"OPERATOR({self.sql(op_func)})\"\n\n                stack.append(node.args.get(\"expression\"))\n                stack.append(f\" {self.maybe_comment(op, comments=node.comments)} \")\n                stack.append(node.args.get(\"this\"))\n            else:\n                sqls.append(self.sql(node))\n\n        return \"\".join(sqls)\n\n    def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str:\n        to_clause = self.sql(expression, \"to\")\n        if to_clause:\n            return f\"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})\"\n\n        return self.function_fallback_sql(expression)\n\n    def function_fallback_sql(self, expression: exp.Func) -> str:\n        args = []\n\n        for key in expression.arg_types:\n            arg_value = expression.args.get(key)\n\n            if isinstance(arg_value, list):\n                for value in arg_value:\n                    args.append(value)\n            elif arg_value is not None:\n                args.append(arg_value)\n\n        if self.dialect.PRESERVE_ORIGINAL_NAMES:\n            name = (expression._meta and expression.meta.get(\"name\")) or expression.sql_name()\n        else:\n            name = expression.sql_name()\n\n        return self.func(name, *args)\n\n    def func(\n        self,\n        name: str,\n        *args: t.Optional[exp.Expr | str],\n        prefix: str = \"(\",\n        suffix: str = \")\",\n        normalize: bool = True,\n    ) -> str:\n        name = self.normalize_func(name) if normalize else name\n        return f\"{name}{prefix}{self.format_args(*args)}{suffix}\"\n\n    def format_args(self, *args: t.Optional[str | exp.Expr], sep: str = \", \") -> str:\n        arg_sqls = tuple(\n            self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool)\n        )\n        if self.pretty and self.too_wide(arg_sqls):\n            return self.indent(\n                \"\\n\" + f\"{sep.strip()}\\n\".join(arg_sqls) + \"\\n\", skip_first=True, skip_last=True\n            )\n        return sep.join(arg_sqls)\n\n    def too_wide(self, args: t.Iterable) -> bool:\n        return sum(len(arg) for arg in args) > self.max_text_width\n\n    def format_time(\n        self,\n        expression: exp.Expr,\n        inverse_time_mapping: t.Optional[t.Dict[str, str]] = None,\n        inverse_time_trie: t.Optional[t.Dict] = None,\n    ) -> t.Optional[str]:\n        return format_time(\n            self.sql(expression, \"format\"),\n            inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING,\n            inverse_time_trie or self.dialect.INVERSE_TIME_TRIE,\n        )\n\n    def expressions(\n        self,\n        expression: t.Optional[exp.Expr] = None,\n        key: t.Optional[str] = None,\n        sqls: t.Optional[t.Collection[str | exp.Expr]] = None,\n        flat: bool = False,\n        indent: bool = True,\n        skip_first: bool = False,\n        skip_last: bool = False,\n        sep: str = \", \",\n        prefix: str = \"\",\n        dynamic: bool = False,\n        new_line: bool = False,\n    ) -> str:\n        expressions = expression.args.get(key or \"expressions\") if expression else sqls\n\n        if not expressions:\n            return \"\"\n\n        if flat:\n            return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql)\n\n        num_sqls = len(expressions)\n        result_sqls = []\n\n        for i, e in enumerate(expressions):\n            sql = self.sql(e, comment=False)\n            if not sql:\n                continue\n\n            comments = self.maybe_comment(\"\", e) if isinstance(e, exp.Expr) else \"\"\n\n            if self.pretty:\n                if self.leading_comma:\n                    result_sqls.append(f\"{sep if i > 0 else ''}{prefix}{sql}{comments}\")\n                else:\n                    result_sqls.append(\n                        f\"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}\"\n                    )\n            else:\n                result_sqls.append(f\"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}\")\n\n        if self.pretty and (not dynamic or self.too_wide(result_sqls)):\n            if new_line:\n                result_sqls.insert(0, \"\")\n                result_sqls.append(\"\")\n            result_sql = \"\\n\".join(s.rstrip() for s in result_sqls)\n        else:\n            result_sql = \"\".join(result_sqls)\n\n        return (\n            self.indent(result_sql, skip_first=skip_first, skip_last=skip_last)\n            if indent\n            else result_sql\n        )\n\n    def op_expressions(self, op: str, expression: exp.Expr, flat: bool = False) -> str:\n        flat = flat or isinstance(expression.parent, exp.Properties)\n        expressions_sql = self.expressions(expression, flat=flat)\n        if flat:\n            return f\"{op} {expressions_sql}\"\n        return f\"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}\"\n\n    def naked_property(self, expression: exp.Property) -> str:\n        property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__)\n        if not property_name:\n            self.unsupported(f\"Unsupported property {expression.__class__.__name__}\")\n        return f\"{property_name} {self.sql(expression, 'this')}\"\n\n    def tag_sql(self, expression: exp.Tag) -> str:\n        return f\"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}\"\n\n    def token_sql(self, token_type: TokenType) -> str:\n        return self.TOKEN_MAPPING.get(token_type, token_type.name)\n\n    def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str:\n        this = self.sql(expression, \"this\")\n        expressions = self.no_identify(self.expressions, expression)\n        expressions = (\n            self.wrap(expressions) if expression.args.get(\"wrapped\") else f\" {expressions}\"\n        )\n        return f\"{this}{expressions}\" if expressions.strip() != \"\" else this\n\n    def joinhint_sql(self, expression: exp.JoinHint) -> str:\n        this = self.sql(expression, \"this\")\n        expressions = self.expressions(expression, flat=True)\n        return f\"{this}({expressions})\"\n\n    def kwarg_sql(self, expression: exp.Kwarg) -> str:\n        return self.binary(expression, \"=>\")\n\n    def when_sql(self, expression: exp.When) -> str:\n        matched = \"MATCHED\" if expression.args[\"matched\"] else \"NOT MATCHED\"\n        source = \" BY SOURCE\" if self.MATCHED_BY_SOURCE and expression.args.get(\"source\") else \"\"\n        condition = self.sql(expression, \"condition\")\n        condition = f\" AND {condition}\" if condition else \"\"\n\n        then_expression = expression.args.get(\"then\")\n        if isinstance(then_expression, exp.Insert):\n            this = self.sql(then_expression, \"this\")\n            this = f\"INSERT {this}\" if this else \"INSERT\"\n            then = self.sql(then_expression, \"expression\")\n            then = f\"{this} VALUES {then}\" if then else this\n        elif isinstance(then_expression, exp.Update):\n            if isinstance(then_expression.args.get(\"expressions\"), exp.Star):\n                then = f\"UPDATE {self.sql(then_expression, 'expressions')}\"\n            else:\n                expressions_sql = self.expressions(then_expression)\n                then = f\"UPDATE SET{self.sep()}{expressions_sql}\" if expressions_sql else \"UPDATE\"\n\n        else:\n            then = self.sql(then_expression)\n        return f\"WHEN {matched}{source}{condition} THEN {then}\"\n\n    def whens_sql(self, expression: exp.Whens) -> str:\n        return self.expressions(expression, sep=\" \", indent=False)\n\n    def merge_sql(self, expression: exp.Merge) -> str:\n        table = expression.this\n        table_alias = \"\"\n\n        hints = table.args.get(\"hints\")\n        if hints and table.alias and isinstance(hints[0], exp.WithTableHint):\n            # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias]\n            table_alias = f\" AS {self.sql(table.args['alias'].pop())}\"\n\n        this = self.sql(table)\n        using = f\"USING {self.sql(expression, 'using')}\"\n        whens = self.sql(expression, \"whens\")\n\n        on = self.sql(expression, \"on\")\n        on = f\"ON {on}\" if on else \"\"\n\n        if not on:\n            on = self.expressions(expression, key=\"using_cond\")\n            on = f\"USING ({on})\" if on else \"\"\n\n        returning = self.sql(expression, \"returning\")\n        if returning:\n            whens = f\"{whens}{returning}\"\n\n        sep = self.sep()\n\n        return self.prepend_ctes(\n            expression,\n            f\"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}\",\n        )\n\n    @unsupported_args(\"format\")\n    def tochar_sql(self, expression: exp.ToChar) -> str:\n        return self.sql(exp.cast(expression.this, exp.DType.TEXT))\n\n    def tonumber_sql(self, expression: exp.ToNumber) -> str:\n        if not self.SUPPORTS_TO_NUMBER:\n            self.unsupported(\"Unsupported TO_NUMBER function\")\n            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))\n\n        fmt = expression.args.get(\"format\")\n        if not fmt:\n            self.unsupported(\"Conversion format is required for TO_NUMBER\")\n            return self.sql(exp.cast(expression.this, exp.DType.DOUBLE))\n\n        return self.func(\"TO_NUMBER\", expression.this, fmt)\n\n    def dictproperty_sql(self, expression: exp.DictProperty) -> str:\n        this = self.sql(expression, \"this\")\n        kind = self.sql(expression, \"kind\")\n        settings_sql = self.expressions(expression, key=\"settings\", sep=\" \")\n        args = f\"({self.sep('')}{settings_sql}{self.seg(')', sep='')}\" if settings_sql else \"()\"\n        return f\"{this}({kind}{args})\"\n\n    def dictrange_sql(self, expression: exp.DictRange) -> str:\n        this = self.sql(expression, \"this\")\n        max = self.sql(expression, \"max\")\n        min = self.sql(expression, \"min\")\n        return f\"{this}(MIN {min} MAX {max})\"\n\n    def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str:\n        return f\"{self.sql(expression, 'this')} {self.sql(expression, 'value')}\"\n\n    def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str:\n        return f\"DUPLICATE KEY ({self.expressions(expression, flat=True)})\"\n\n    # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/\n    def uniquekeyproperty_sql(\n        self, expression: exp.UniqueKeyProperty, prefix: str = \"UNIQUE KEY\"\n    ) -> str:\n        return f\"{prefix} ({self.expressions(expression, flat=True)})\"\n\n    # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc\n    def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str:\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\" {self.wrap(expressions)}\" if expressions else \"\"\n        buckets = self.sql(expression, \"buckets\")\n        kind = self.sql(expression, \"kind\")\n        buckets = f\" BUCKETS {buckets}\" if buckets else \"\"\n        order = self.sql(expression, \"order\")\n        return f\"DISTRIBUTED BY {kind}{expressions}{buckets}{order}\"\n\n    def oncluster_sql(self, expression: exp.OnCluster) -> str:\n        return \"\"\n\n    def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str:\n        expressions = self.expressions(expression, key=\"expressions\", flat=True)\n        sorted_by = self.expressions(expression, key=\"sorted_by\", flat=True)\n        sorted_by = f\" SORTED BY ({sorted_by})\" if sorted_by else \"\"\n        buckets = self.sql(expression, \"buckets\")\n        return f\"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS\"\n\n    def anyvalue_sql(self, expression: exp.AnyValue) -> str:\n        this = self.sql(expression, \"this\")\n        having = self.sql(expression, \"having\")\n\n        if having:\n            this = f\"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}\"\n\n        return self.func(\"ANY_VALUE\", this)\n\n    def querytransform_sql(self, expression: exp.QueryTransform) -> str:\n        transform = self.func(\"TRANSFORM\", *expression.expressions)\n        row_format_before = self.sql(expression, \"row_format_before\")\n        row_format_before = f\" {row_format_before}\" if row_format_before else \"\"\n        record_writer = self.sql(expression, \"record_writer\")\n        record_writer = f\" RECORDWRITER {record_writer}\" if record_writer else \"\"\n        using = f\" USING {self.sql(expression, 'command_script')}\"\n        schema = self.sql(expression, \"schema\")\n        schema = f\" AS {schema}\" if schema else \"\"\n        row_format_after = self.sql(expression, \"row_format_after\")\n        row_format_after = f\" {row_format_after}\" if row_format_after else \"\"\n        record_reader = self.sql(expression, \"record_reader\")\n        record_reader = f\" RECORDREADER {record_reader}\" if record_reader else \"\"\n        return f\"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}\"\n\n    def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str:\n        key_block_size = self.sql(expression, \"key_block_size\")\n        if key_block_size:\n            return f\"KEY_BLOCK_SIZE = {key_block_size}\"\n\n        using = self.sql(expression, \"using\")\n        if using:\n            return f\"USING {using}\"\n\n        parser = self.sql(expression, \"parser\")\n        if parser:\n            return f\"WITH PARSER {parser}\"\n\n        comment = self.sql(expression, \"comment\")\n        if comment:\n            return f\"COMMENT {comment}\"\n\n        visible = expression.args.get(\"visible\")\n        if visible is not None:\n            return \"VISIBLE\" if visible else \"INVISIBLE\"\n\n        engine_attr = self.sql(expression, \"engine_attr\")\n        if engine_attr:\n            return f\"ENGINE_ATTRIBUTE = {engine_attr}\"\n\n        secondary_engine_attr = self.sql(expression, \"secondary_engine_attr\")\n        if secondary_engine_attr:\n            return f\"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}\"\n\n        self.unsupported(\"Unsupported index constraint option.\")\n        return \"\"\n\n    def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str:\n        enforced = \" ENFORCED\" if expression.args.get(\"enforced\") else \"\"\n        return f\"CHECK ({self.sql(expression, 'this')}){enforced}\"\n\n    def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str:\n        kind = self.sql(expression, \"kind\")\n        kind = f\"{kind} INDEX\" if kind else \"INDEX\"\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        index_type = self.sql(expression, \"index_type\")\n        index_type = f\" USING {index_type}\" if index_type else \"\"\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\" ({expressions})\" if expressions else \"\"\n        options = self.expressions(expression, key=\"options\", sep=\" \")\n        options = f\" {options}\" if options else \"\"\n        return f\"{kind}{this}{index_type}{expressions}{options}\"\n\n    def nvl2_sql(self, expression: exp.Nvl2) -> str:\n        if self.NVL2_SUPPORTED:\n            return self.function_fallback_sql(expression)\n\n        case = exp.Case().when(\n            expression.this.is_(exp.null()).not_(copy=False),\n            expression.args[\"true\"],\n            copy=False,\n        )\n        else_cond = expression.args.get(\"false\")\n        if else_cond:\n            case.else_(else_cond, copy=False)\n\n        return self.sql(case)\n\n    def comprehension_sql(self, expression: exp.Comprehension) -> str:\n        this = self.sql(expression, \"this\")\n        expr = self.sql(expression, \"expression\")\n        position = self.sql(expression, \"position\")\n        position = f\", {position}\" if position else \"\"\n        iterator = self.sql(expression, \"iterator\")\n        condition = self.sql(expression, \"condition\")\n        condition = f\" IF {condition}\" if condition else \"\"\n        return f\"{this} FOR {expr}{position} IN {iterator}{condition}\"\n\n    def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str:\n        return f\"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})\"\n\n    def opclass_sql(self, expression: exp.Opclass) -> str:\n        return f\"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}\"\n\n    def _ml_sql(self, expression: exp.Func, name: str) -> str:\n        model = self.sql(expression, \"this\")\n        model = f\"MODEL {model}\"\n        expr = expression.expression\n        if expr:\n            expr_sql = self.sql(expression, \"expression\")\n            expr_sql = f\"TABLE {expr_sql}\" if not isinstance(expr, exp.Subquery) else expr_sql\n        else:\n            expr_sql = None\n\n        parameters = self.sql(expression, \"params_struct\") or None\n\n        return self.func(name, model, expr_sql, parameters)\n\n    def predict_sql(self, expression: exp.Predict) -> str:\n        return self._ml_sql(expression, \"PREDICT\")\n\n    def generateembedding_sql(self, expression: exp.GenerateEmbedding) -> str:\n        name = \"GENERATE_TEXT_EMBEDDING\" if expression.args.get(\"is_text\") else \"GENERATE_EMBEDDING\"\n        return self._ml_sql(expression, name)\n\n    def mltranslate_sql(self, expression: exp.MLTranslate) -> str:\n        return self._ml_sql(expression, \"TRANSLATE\")\n\n    def mlforecast_sql(self, expression: exp.MLForecast) -> str:\n        return self._ml_sql(expression, \"FORECAST\")\n\n    def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str:\n        this_sql = self.sql(expression, \"this\")\n        if isinstance(expression.this, exp.Table):\n            this_sql = f\"TABLE {this_sql}\"\n\n        return self.func(\n            \"FEATURES_AT_TIME\",\n            this_sql,\n            expression.args.get(\"time\"),\n            expression.args.get(\"num_rows\"),\n            expression.args.get(\"ignore_feature_nulls\"),\n        )\n\n    def vectorsearch_sql(self, expression: exp.VectorSearch) -> str:\n        this_sql = self.sql(expression, \"this\")\n        if isinstance(expression.this, exp.Table):\n            this_sql = f\"TABLE {this_sql}\"\n\n        query_table = self.sql(expression, \"query_table\")\n        if isinstance(expression.args[\"query_table\"], exp.Table):\n            query_table = f\"TABLE {query_table}\"\n\n        return self.func(\n            \"VECTOR_SEARCH\",\n            this_sql,\n            expression.args.get(\"column_to_search\"),\n            query_table,\n            expression.args.get(\"query_column_to_search\"),\n            expression.args.get(\"top_k\"),\n            expression.args.get(\"distance_type\"),\n            expression.args.get(\"options\"),\n        )\n\n    def forin_sql(self, expression: exp.ForIn) -> str:\n        this = self.sql(expression, \"this\")\n        expression_sql = self.sql(expression, \"expression\")\n        return f\"FOR {this} DO {expression_sql}\"\n\n    def refresh_sql(self, expression: exp.Refresh) -> str:\n        this = self.sql(expression, \"this\")\n        kind = \"\" if isinstance(expression.this, exp.Literal) else f\"{expression.text('kind')} \"\n        return f\"REFRESH {kind}{this}\"\n\n    def toarray_sql(self, expression: exp.ToArray) -> str:\n        arg = expression.this\n        if not arg.type:\n            from sqlglot.optimizer.annotate_types import annotate_types\n\n            arg = annotate_types(arg, dialect=self.dialect)\n\n        if arg.is_type(exp.DType.ARRAY):\n            return self.sql(arg)\n\n        cond_for_null = arg.is_(exp.null())\n        return self.sql(exp.func(\"IF\", cond_for_null, exp.null(), exp.array(arg, copy=False)))\n\n    def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str:\n        this = expression.this\n        time_format = self.format_time(expression)\n\n        if time_format:\n            return self.sql(\n                exp.cast(\n                    exp.StrToTime(this=this, format=expression.args[\"format\"]),\n                    exp.DType.TIME,\n                )\n            )\n\n        if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DType.TIME):\n            return self.sql(this)\n\n        return self.sql(exp.cast(this, exp.DType.TIME))\n\n    def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str:\n        this = expression.this\n        if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DType.TIMESTAMP):\n            return self.sql(this)\n\n        return self.sql(exp.cast(this, exp.DType.TIMESTAMP, dialect=self.dialect))\n\n    def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str:\n        this = expression.this\n        if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DType.DATETIME):\n            return self.sql(this)\n\n        return self.sql(exp.cast(this, exp.DType.DATETIME, dialect=self.dialect))\n\n    def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str:\n        this = expression.this\n        time_format = self.format_time(expression)\n        safe = expression.args.get(\"safe\")\n        if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT):\n            return self.sql(\n                exp.cast(\n                    exp.StrToTime(this=this, format=expression.args[\"format\"], safe=safe),\n                    exp.DType.DATE,\n                )\n            )\n\n        if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DType.DATE):\n            return self.sql(this)\n\n        if safe:\n            return self.sql(exp.TryCast(this=this, to=exp.DataType(this=exp.DType.DATE)))\n\n        return self.sql(exp.cast(this, exp.DType.DATE))\n\n    def unixdate_sql(self, expression: exp.UnixDate) -> str:\n        return self.sql(\n            exp.func(\n                \"DATEDIFF\",\n                expression.this,\n                exp.cast(exp.Literal.string(\"1970-01-01\"), exp.DType.DATE),\n                \"day\",\n            )\n        )\n\n    def lastday_sql(self, expression: exp.LastDay) -> str:\n        if self.LAST_DAY_SUPPORTS_DATE_PART:\n            return self.function_fallback_sql(expression)\n\n        unit = expression.text(\"unit\")\n        if unit and unit != \"MONTH\":\n            self.unsupported(\"Date parts are not supported in LAST_DAY.\")\n\n        return self.func(\"LAST_DAY\", expression.this)\n\n    def dateadd_sql(self, expression: exp.DateAdd) -> str:\n        from sqlglot.dialects.dialect import unit_to_str\n\n        return self.func(\n            \"DATE_ADD\", expression.this, expression.expression, unit_to_str(expression)\n        )\n\n    def arrayany_sql(self, expression: exp.ArrayAny) -> str:\n        if self.CAN_IMPLEMENT_ARRAY_ANY:\n            filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression)\n            filtered_not_empty = exp.ArraySize(this=filtered).neq(0)\n            original_is_empty = exp.ArraySize(this=expression.this).eq(0)\n            return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty)))\n\n        from sqlglot.dialects import Dialect\n\n        # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect\n        if self.dialect.__class__ != Dialect:\n            self.unsupported(\"ARRAY_ANY is unsupported\")\n\n        return self.function_fallback_sql(expression)\n\n    def struct_sql(self, expression: exp.Struct) -> str:\n        expression.set(\n            \"expressions\",\n            [\n                exp.alias_(e.expression, e.name if e.this.is_string else e.this)\n                if isinstance(e, exp.PropertyEQ)\n                else e\n                for e in expression.expressions\n            ],\n        )\n\n        return self.function_fallback_sql(expression)\n\n    def partitionrange_sql(self, expression: exp.PartitionRange) -> str:\n        low = self.sql(expression, \"this\")\n        high = self.sql(expression, \"expression\")\n\n        return f\"{low} TO {high}\"\n\n    def truncatetable_sql(self, expression: exp.TruncateTable) -> str:\n        target = \"DATABASE\" if expression.args.get(\"is_database\") else \"TABLE\"\n        tables = f\" {self.expressions(expression)}\"\n\n        exists = \" IF EXISTS\" if expression.args.get(\"exists\") else \"\"\n\n        on_cluster = self.sql(expression, \"cluster\")\n        on_cluster = f\" {on_cluster}\" if on_cluster else \"\"\n\n        identity = self.sql(expression, \"identity\")\n        identity = f\" {identity} IDENTITY\" if identity else \"\"\n\n        option = self.sql(expression, \"option\")\n        option = f\" {option}\" if option else \"\"\n\n        partition = self.sql(expression, \"partition\")\n        partition = f\" {partition}\" if partition else \"\"\n\n        return f\"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}\"\n\n    # This transpiles T-SQL's CONVERT function\n    # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16\n    def convert_sql(self, expression: exp.Convert) -> str:\n        to = expression.this\n        value = expression.expression\n        style = expression.args.get(\"style\")\n        safe = expression.args.get(\"safe\")\n        strict = expression.args.get(\"strict\")\n\n        if not to or not value:\n            return \"\"\n\n        # Retrieve length of datatype and override to default if not specified\n        if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES:\n            to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False)\n\n        transformed: t.Optional[exp.Expr] = None\n        cast = exp.Cast if strict else exp.TryCast\n\n        # Check whether a conversion with format (T-SQL calls this 'style') is applicable\n        if isinstance(style, exp.Literal) and style.is_int:\n            from sqlglot.dialects.tsql import TSQL\n\n            style_value = style.name\n            converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value)\n            if not converted_style:\n                self.unsupported(f\"Unsupported T-SQL 'style' value: {style_value}\")\n\n            fmt = exp.Literal.string(converted_style)\n\n            if to.this == exp.DType.DATE:\n                transformed = exp.StrToDate(this=value, format=fmt)\n            elif to.this in (exp.DType.DATETIME, exp.DType.DATETIME2):\n                transformed = exp.StrToTime(this=value, format=fmt)\n            elif to.this in self.PARAMETERIZABLE_TEXT_TYPES:\n                transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe)\n            elif to.this == exp.DType.TEXT:\n                transformed = exp.TimeToStr(this=value, format=fmt)\n\n        if not transformed:\n            transformed = cast(this=value, to=to, safe=safe)\n\n        return self.sql(transformed)\n\n    def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str:\n        this = expression.this\n        if isinstance(this, exp.JSONPathWildcard):\n            this = self.json_path_part(this)\n            return f\".{this}\" if this else \"\"\n\n        if self.SAFE_JSON_PATH_KEY_RE.match(this):\n            return f\".{this}\"\n\n        this = self.json_path_part(this)\n        return (\n            f\"[{this}]\"\n            if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED\n            else f\".{this}\"\n        )\n\n    def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str:\n        this = self.json_path_part(expression.this)\n        return f\"[{this}]\" if this else \"\"\n\n    def _simplify_unless_literal(self, expression: E) -> E:\n        if not isinstance(expression, exp.Literal):\n            from sqlglot.optimizer.simplify import simplify\n\n            expression = simplify(expression, dialect=self.dialect)\n\n        return expression\n\n    def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str:\n        this = expression.this\n        if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS):\n            self.unsupported(\n                f\"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}\"\n            )\n            return self.sql(this)\n\n        if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get(\"inline\"):\n            if self.IGNORE_NULLS_BEFORE_ORDER:\n                # The first modifier here will be the one closest to the AggFunc's arg\n                mods = sorted(\n                    expression.find_all(exp.HavingMax, exp.Order, exp.Limit),\n                    key=lambda x: (\n                        0\n                        if isinstance(x, exp.HavingMax)\n                        else (1 if isinstance(x, exp.Order) else 2)\n                    ),\n                )\n\n                if mods:\n                    mod = mods[0]\n                    this = expression.__class__(this=mod.this.copy())\n                    this.meta[\"inline\"] = True\n                    mod.this.replace(this)\n                    return self.sql(expression.this)\n\n            agg_func = expression.find(exp.AggFunc)\n\n            if agg_func:\n                agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f\" {text})\"\n                return self.maybe_comment(agg_func_sql, comments=agg_func.comments)\n\n        return f\"{self.sql(expression, 'this')} {text}\"\n\n    def _replace_line_breaks(self, string: str) -> str:\n        \"\"\"We don't want to extra indent line breaks so we temporarily replace them with sentinels.\"\"\"\n        if self.pretty:\n            return string.replace(\"\\n\", self.SENTINEL_LINE_BREAK)\n        return string\n\n    def copyparameter_sql(self, expression: exp.CopyParameter) -> str:\n        option = self.sql(expression, \"this\")\n\n        if expression.expressions:\n            upper = option.upper()\n\n            # Snowflake FILE_FORMAT options are separated by whitespace\n            sep = \" \" if upper == \"FILE_FORMAT\" else \", \"\n\n            # Databricks copy/format options do not set their list of values with EQ\n            op = \" \" if upper in (\"COPY_OPTIONS\", \"FORMAT_OPTIONS\") else \" = \"\n            values = self.expressions(expression, flat=True, sep=sep)\n            return f\"{option}{op}({values})\"\n\n        value = self.sql(expression, \"expression\")\n\n        if not value:\n            return option\n\n        op = \" = \" if self.COPY_PARAMS_EQ_REQUIRED else \" \"\n\n        return f\"{option}{op}{value}\"\n\n    def credentials_sql(self, expression: exp.Credentials) -> str:\n        cred_expr = expression.args.get(\"credentials\")\n        if isinstance(cred_expr, exp.Literal):\n            # Redshift case: CREDENTIALS <string>\n            credentials = self.sql(expression, \"credentials\")\n            credentials = f\"CREDENTIALS {credentials}\" if credentials else \"\"\n        else:\n            # Snowflake case: CREDENTIALS = (...)\n            credentials = self.expressions(expression, key=\"credentials\", flat=True, sep=\" \")\n            credentials = f\"CREDENTIALS = ({credentials})\" if cred_expr is not None else \"\"\n\n        storage = self.sql(expression, \"storage\")\n        storage = f\"STORAGE_INTEGRATION = {storage}\" if storage else \"\"\n\n        encryption = self.expressions(expression, key=\"encryption\", flat=True, sep=\" \")\n        encryption = f\" ENCRYPTION = ({encryption})\" if encryption else \"\"\n\n        iam_role = self.sql(expression, \"iam_role\")\n        iam_role = f\"IAM_ROLE {iam_role}\" if iam_role else \"\"\n\n        region = self.sql(expression, \"region\")\n        region = f\" REGION {region}\" if region else \"\"\n\n        return f\"{credentials}{storage}{encryption}{iam_role}{region}\"\n\n    def copy_sql(self, expression: exp.Copy) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\" INTO {this}\" if self.COPY_HAS_INTO_KEYWORD else f\" {this}\"\n\n        credentials = self.sql(expression, \"credentials\")\n        credentials = self.seg(credentials) if credentials else \"\"\n        files = self.expressions(expression, key=\"files\", flat=True)\n        kind = self.seg(\"FROM\" if expression.args.get(\"kind\") else \"TO\") if files else \"\"\n\n        sep = \", \" if self.dialect.COPY_PARAMS_ARE_CSV else \" \"\n        params = self.expressions(\n            expression,\n            key=\"params\",\n            sep=sep,\n            new_line=True,\n            skip_last=True,\n            skip_first=True,\n            indent=self.COPY_PARAMS_ARE_WRAPPED,\n        )\n\n        if params:\n            if self.COPY_PARAMS_ARE_WRAPPED:\n                params = f\" WITH ({params})\"\n            elif not self.pretty and (files or credentials):\n                params = f\" {params}\"\n\n        return f\"COPY{this}{kind} {files}{credentials}{params}\"\n\n    def semicolon_sql(self, expression: exp.Semicolon) -> str:\n        return \"\"\n\n    def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str:\n        on_sql = \"ON\" if expression.args.get(\"on\") else \"OFF\"\n        filter_col: t.Optional[str] = self.sql(expression, \"filter_column\")\n        filter_col = f\"FILTER_COLUMN={filter_col}\" if filter_col else None\n        retention_period: t.Optional[str] = self.sql(expression, \"retention_period\")\n        retention_period = f\"RETENTION_PERIOD={retention_period}\" if retention_period else None\n\n        if filter_col or retention_period:\n            on_sql = self.func(\"ON\", filter_col, retention_period)\n\n        return f\"DATA_DELETION={on_sql}\"\n\n    def maskingpolicycolumnconstraint_sql(\n        self, expression: exp.MaskingPolicyColumnConstraint\n    ) -> str:\n        this = self.sql(expression, \"this\")\n        expressions = self.expressions(expression, flat=True)\n        expressions = f\" USING ({expressions})\" if expressions else \"\"\n        return f\"MASKING POLICY {this}{expressions}\"\n\n    def gapfill_sql(self, expression: exp.GapFill) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\"TABLE {this}\"\n        return self.func(\"GAP_FILL\", this, *[v for k, v in expression.args.items() if k != \"this\"])\n\n    def scope_resolution(self, rhs: str, scope_name: str) -> str:\n        return self.func(\"SCOPE_RESOLUTION\", scope_name or None, rhs)\n\n    def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str:\n        this = self.sql(expression, \"this\")\n        expr = expression.expression\n\n        if isinstance(expr, exp.Func):\n            # T-SQL's CLR functions are case sensitive\n            expr = f\"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})\"\n        else:\n            expr = self.sql(expression, \"expression\")\n\n        return self.scope_resolution(expr, this)\n\n    def parsejson_sql(self, expression: exp.ParseJSON) -> str:\n        if self.PARSE_JSON_NAME is None:\n            return self.sql(expression.this)\n\n        return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression)\n\n    def rand_sql(self, expression: exp.Rand) -> str:\n        lower = self.sql(expression, \"lower\")\n        upper = self.sql(expression, \"upper\")\n\n        if lower and upper:\n            return f\"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}\"\n        return self.func(\"RAND\", expression.this)\n\n    def changes_sql(self, expression: exp.Changes) -> str:\n        information = self.sql(expression, \"information\")\n        information = f\"INFORMATION => {information}\"\n        at_before = self.sql(expression, \"at_before\")\n        at_before = f\"{self.seg('')}{at_before}\" if at_before else \"\"\n        end = self.sql(expression, \"end\")\n        end = f\"{self.seg('')}{end}\" if end else \"\"\n\n        return f\"CHANGES ({information}){at_before}{end}\"\n\n    def pad_sql(self, expression: exp.Pad) -> str:\n        prefix = \"L\" if expression.args.get(\"is_left\") else \"R\"\n\n        fill_pattern = self.sql(expression, \"fill_pattern\") or None\n        if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED:\n            fill_pattern = \"' '\"\n\n        return self.func(f\"{prefix}PAD\", expression.this, expression.expression, fill_pattern)\n\n    def summarize_sql(self, expression: exp.Summarize) -> str:\n        table = \" TABLE\" if expression.args.get(\"table\") else \"\"\n        return f\"SUMMARIZE{table} {self.sql(expression.this)}\"\n\n    def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str:\n        generate_series = exp.GenerateSeries(**expression.args)\n\n        parent = expression.parent\n        if isinstance(parent, (exp.Alias, exp.TableAlias)):\n            parent = parent.parent\n\n        if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)):\n            return self.sql(exp.Unnest(expressions=[generate_series]))\n\n        if isinstance(parent, exp.Select):\n            self.unsupported(\"GenerateSeries projection unnesting is not supported.\")\n\n        return self.sql(generate_series)\n\n    def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str:\n        if self.SUPPORTS_CONVERT_TIMEZONE:\n            return self.function_fallback_sql(expression)\n\n        source_tz = expression.args.get(\"source_tz\")\n        target_tz = expression.args.get(\"target_tz\")\n        timestamp = expression.args.get(\"timestamp\")\n\n        if source_tz and timestamp:\n            timestamp = exp.AtTimeZone(\n                this=exp.cast(timestamp, exp.DType.TIMESTAMPNTZ), zone=source_tz\n            )\n\n        expr = exp.AtTimeZone(this=timestamp, zone=target_tz)\n\n        return self.sql(expr)\n\n    def json_sql(self, expression: exp.JSON) -> str:\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n\n        _with = expression.args.get(\"with_\")\n\n        if _with is None:\n            with_sql = \"\"\n        elif not _with:\n            with_sql = \" WITHOUT\"\n        else:\n            with_sql = \" WITH\"\n\n        unique_sql = \" UNIQUE KEYS\" if expression.args.get(\"unique\") else \"\"\n\n        return f\"JSON{this}{with_sql}{unique_sql}\"\n\n    def jsonvalue_sql(self, expression: exp.JSONValue) -> str:\n        path = self.sql(expression, \"path\")\n        returning = self.sql(expression, \"returning\")\n        returning = f\" RETURNING {returning}\" if returning else \"\"\n\n        on_condition = self.sql(expression, \"on_condition\")\n        on_condition = f\" {on_condition}\" if on_condition else \"\"\n\n        return self.func(\"JSON_VALUE\", expression.this, f\"{path}{returning}{on_condition}\")\n\n    def skipjsoncolumn_sql(self, expression: exp.SkipJSONColumn) -> str:\n        regexp = \" REGEXP\" if expression.args.get(\"regexp\") else \"\"\n        return f\"SKIP{regexp} {self.sql(expression.expression)}\"\n\n    def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str:\n        else_ = \"ELSE \" if expression.args.get(\"else_\") else \"\"\n        condition = self.sql(expression, \"expression\")\n        condition = f\"WHEN {condition} THEN \" if condition else else_\n        insert = self.sql(expression, \"this\")[len(\"INSERT\") :].strip()\n        return f\"{condition}{insert}\"\n\n    def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str:\n        kind = self.sql(expression, \"kind\")\n        expressions = self.seg(self.expressions(expression, sep=\" \"))\n        res = f\"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}\"\n        return res\n\n    def oncondition_sql(self, expression: exp.OnCondition) -> str:\n        # Static options like \"NULL ON ERROR\" are stored as strings, in contrast to \"DEFAULT <expr> ON ERROR\"\n        empty = expression.args.get(\"empty\")\n        empty = (\n            f\"DEFAULT {empty} ON EMPTY\"\n            if isinstance(empty, exp.Expr)\n            else self.sql(expression, \"empty\")\n        )\n\n        error = expression.args.get(\"error\")\n        error = (\n            f\"DEFAULT {error} ON ERROR\"\n            if isinstance(error, exp.Expr)\n            else self.sql(expression, \"error\")\n        )\n\n        if error and empty:\n            error = (\n                f\"{empty} {error}\"\n                if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR\n                else f\"{error} {empty}\"\n            )\n            empty = \"\"\n\n        null = self.sql(expression, \"null\")\n\n        return f\"{empty}{error}{null}\"\n\n    def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str:\n        scalar = \" ON SCALAR STRING\" if expression.args.get(\"scalar\") else \"\"\n        return f\"{self.sql(expression, 'option')} QUOTES{scalar}\"\n\n    def jsonexists_sql(self, expression: exp.JSONExists) -> str:\n        this = self.sql(expression, \"this\")\n        path = self.sql(expression, \"path\")\n\n        passing = self.expressions(expression, \"passing\")\n        passing = f\" PASSING {passing}\" if passing else \"\"\n\n        on_condition = self.sql(expression, \"on_condition\")\n        on_condition = f\" {on_condition}\" if on_condition else \"\"\n\n        path = f\"{path}{passing}{on_condition}\"\n\n        return self.func(\"JSON_EXISTS\", this, path)\n\n    def _add_arrayagg_null_filter(\n        self,\n        array_agg_sql: str,\n        array_agg_expr: exp.ArrayAgg,\n        column_expr: exp.Expr,\n    ) -> str:\n        \"\"\"\n        Add NULL filter to ARRAY_AGG if dialect requires it.\n\n        Args:\n            array_agg_sql: The generated ARRAY_AGG SQL string\n            array_agg_expr: The ArrayAgg expression node\n            column_expr: The column/expression to filter (before ORDER BY wrapping)\n\n        Returns:\n            SQL string with FILTER clause added if needed\n        \"\"\"\n        # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls\n        # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB)\n        if not (\n            self.dialect.ARRAY_AGG_INCLUDES_NULLS and array_agg_expr.args.get(\"nulls_excluded\")\n        ):\n            return array_agg_sql\n\n        parent = array_agg_expr.parent\n        if isinstance(parent, exp.Filter):\n            parent_cond = parent.expression.this\n            parent_cond.replace(parent_cond.and_(column_expr.is_(exp.null()).not_()))\n        elif column_expr.find(exp.Column):\n            # Do not add the filter if the input is not a column (e.g. literal, struct etc)\n            # DISTINCT is already present in the agg function, do not propagate it to FILTER as well\n            this_sql = (\n                self.expressions(column_expr)\n                if isinstance(column_expr, exp.Distinct)\n                else self.sql(column_expr)\n            )\n            array_agg_sql = f\"{array_agg_sql} FILTER(WHERE {this_sql} IS NOT NULL)\"\n\n        return array_agg_sql\n\n    def arrayagg_sql(self, expression: exp.ArrayAgg) -> str:\n        array_agg = self.function_fallback_sql(expression)\n        return self._add_arrayagg_null_filter(array_agg, expression, expression.this)\n\n    def slice_sql(self, expression: exp.Slice) -> str:\n        step = self.sql(expression, \"step\")\n        end = self.sql(expression.expression)\n        begin = self.sql(expression.this)\n\n        sql = f\"{end}:{step}\" if step else end\n        return f\"{begin}:{sql}\" if sql else f\"{begin}:\"\n\n    def apply_sql(self, expression: exp.Apply) -> str:\n        this = self.sql(expression, \"this\")\n        expr = self.sql(expression, \"expression\")\n\n        return f\"{this} APPLY({expr})\"\n\n    def _grant_or_revoke_sql(\n        self,\n        expression: exp.Grant | exp.Revoke,\n        keyword: str,\n        preposition: str,\n        grant_option_prefix: str = \"\",\n        grant_option_suffix: str = \"\",\n    ) -> str:\n        privileges_sql = self.expressions(expression, key=\"privileges\", flat=True)\n\n        kind = self.sql(expression, \"kind\")\n        kind = f\" {kind}\" if kind else \"\"\n\n        securable = self.sql(expression, \"securable\")\n        securable = f\" {securable}\" if securable else \"\"\n\n        principals = self.expressions(expression, key=\"principals\", flat=True)\n\n        if not expression.args.get(\"grant_option\"):\n            grant_option_prefix = grant_option_suffix = \"\"\n\n        # cascade for revoke only\n        cascade = self.sql(expression, \"cascade\")\n        cascade = f\" {cascade}\" if cascade else \"\"\n\n        return f\"{keyword} {grant_option_prefix}{privileges_sql} ON{kind}{securable} {preposition} {principals}{grant_option_suffix}{cascade}\"\n\n    def grant_sql(self, expression: exp.Grant) -> str:\n        return self._grant_or_revoke_sql(\n            expression,\n            keyword=\"GRANT\",\n            preposition=\"TO\",\n            grant_option_suffix=\" WITH GRANT OPTION\",\n        )\n\n    def revoke_sql(self, expression: exp.Revoke) -> str:\n        return self._grant_or_revoke_sql(\n            expression,\n            keyword=\"REVOKE\",\n            preposition=\"FROM\",\n            grant_option_prefix=\"GRANT OPTION FOR \",\n        )\n\n    def grantprivilege_sql(self, expression: exp.GrantPrivilege) -> str:\n        this = self.sql(expression, \"this\")\n        columns = self.expressions(expression, flat=True)\n        columns = f\"({columns})\" if columns else \"\"\n\n        return f\"{this}{columns}\"\n\n    def grantprincipal_sql(self, expression: exp.GrantPrincipal) -> str:\n        this = self.sql(expression, \"this\")\n\n        kind = self.sql(expression, \"kind\")\n        kind = f\"{kind} \" if kind else \"\"\n\n        return f\"{kind}{this}\"\n\n    def columns_sql(self, expression: exp.Columns) -> str:\n        func = self.function_fallback_sql(expression)\n        if expression.args.get(\"unpack\"):\n            func = f\"*{func}\"\n\n        return func\n\n    def overlay_sql(self, expression: exp.Overlay) -> str:\n        this = self.sql(expression, \"this\")\n        expr = self.sql(expression, \"expression\")\n        from_sql = self.sql(expression, \"from_\")\n        for_sql = self.sql(expression, \"for_\")\n        for_sql = f\" FOR {for_sql}\" if for_sql else \"\"\n\n        return f\"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})\"\n\n    @unsupported_args(\"format\")\n    def todouble_sql(self, expression: exp.ToDouble) -> str:\n        cast = exp.TryCast if expression.args.get(\"safe\") else exp.Cast\n        return self.sql(cast(this=expression.this, to=exp.DataType.build(exp.DType.DOUBLE)))\n\n    def string_sql(self, expression: exp.String) -> str:\n        this = expression.this\n        zone = expression.args.get(\"zone\")\n\n        if zone:\n            # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>)\n            # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC\n            # set for source_tz to transpile the time conversion before the STRING cast\n            this = exp.ConvertTimezone(\n                source_tz=exp.Literal.string(\"UTC\"), target_tz=zone, timestamp=this\n            )\n\n        return self.sql(exp.cast(this, exp.DType.VARCHAR))\n\n    def median_sql(self, expression: exp.Median) -> str:\n        if not self.SUPPORTS_MEDIAN:\n            return self.sql(\n                exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5))\n            )\n\n        return self.function_fallback_sql(expression)\n\n    def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str:\n        filler = self.sql(expression, \"this\")\n        filler = f\" {filler}\" if filler else \"\"\n        with_count = \"WITH COUNT\" if expression.args.get(\"with_count\") else \"WITHOUT COUNT\"\n        return f\"TRUNCATE{filler} {with_count}\"\n\n    def unixseconds_sql(self, expression: exp.UnixSeconds) -> str:\n        if self.SUPPORTS_UNIX_SECONDS:\n            return self.function_fallback_sql(expression)\n\n        start_ts = exp.cast(exp.Literal.string(\"1970-01-01 00:00:00+00\"), to=exp.DType.TIMESTAMPTZ)\n\n        return self.sql(\n            exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var(\"SECONDS\"))\n        )\n\n    def arraysize_sql(self, expression: exp.ArraySize) -> str:\n        dim = expression.expression\n\n        # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension)\n        if dim and self.ARRAY_SIZE_DIM_REQUIRED is None:\n            if not (dim.is_int and dim.name == \"1\"):\n                self.unsupported(\"Cannot transpile dimension argument for ARRAY_LENGTH\")\n            dim = None\n\n        # If dimension is required but not specified, default initialize it\n        if self.ARRAY_SIZE_DIM_REQUIRED and not dim:\n            dim = exp.Literal.number(1)\n\n        return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)\n\n    def attach_sql(self, expression: exp.Attach) -> str:\n        this = self.sql(expression, \"this\")\n        exists_sql = \" IF NOT EXISTS\" if expression.args.get(\"exists\") else \"\"\n        expressions = self.expressions(expression)\n        expressions = f\" ({expressions})\" if expressions else \"\"\n\n        return f\"ATTACH{exists_sql} {this}{expressions}\"\n\n    def detach_sql(self, expression: exp.Detach) -> str:\n        kind = self.sql(expression, \"kind\")\n        kind = f\" {kind}\" if kind else \"\"\n        # the DATABASE keyword is required if IF EXISTS is set for DuckDB\n        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax\n        exists = \" IF EXISTS\" if expression.args.get(\"exists\") else \"\"\n        if exists:\n            kind = kind or \" DATABASE\"\n\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        cluster = self.sql(expression, \"cluster\")\n        cluster = f\" {cluster}\" if cluster else \"\"\n        permanent = \" PERMANENTLY\" if expression.args.get(\"permanent\") else \"\"\n        sync = \" SYNC\" if expression.args.get(\"sync\") else \"\"\n        return f\"DETACH{kind}{exists}{this}{cluster}{permanent}{sync}\"\n\n    def attachoption_sql(self, expression: exp.AttachOption) -> str:\n        this = self.sql(expression, \"this\")\n        value = self.sql(expression, \"expression\")\n        value = f\" {value}\" if value else \"\"\n        return f\"{this}{value}\"\n\n    def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str:\n        return (\n            f\"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}\"\n        )\n\n    def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str:\n        encode = \"KEY ENCODE\" if expression.args.get(\"key\") else \"ENCODE\"\n        encode = f\"{encode} {self.sql(expression, 'this')}\"\n\n        properties = expression.args.get(\"properties\")\n        if properties:\n            encode = f\"{encode} {self.properties(properties)}\"\n\n        return encode\n\n    def includeproperty_sql(self, expression: exp.IncludeProperty) -> str:\n        this = self.sql(expression, \"this\")\n        include = f\"INCLUDE {this}\"\n\n        column_def = self.sql(expression, \"column_def\")\n        if column_def:\n            include = f\"{include} {column_def}\"\n\n        alias = self.sql(expression, \"alias\")\n        if alias:\n            include = f\"{include} AS {alias}\"\n\n        return include\n\n    def xmlelement_sql(self, expression: exp.XMLElement) -> str:\n        prefix = \"EVALNAME\" if expression.args.get(\"evalname\") else \"NAME\"\n        name = f\"{prefix} {self.sql(expression, 'this')}\"\n        return self.func(\"XMLELEMENT\", name, *expression.expressions)\n\n    def xmlkeyvalueoption_sql(self, expression: exp.XMLKeyValueOption) -> str:\n        this = self.sql(expression, \"this\")\n        expr = self.sql(expression, \"expression\")\n        expr = f\"({expr})\" if expr else \"\"\n        return f\"{this}{expr}\"\n\n    def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str:\n        partitions = self.expressions(expression, \"partition_expressions\")\n        create = self.expressions(expression, \"create_expressions\")\n        return f\"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}\"\n\n    def partitionbyrangepropertydynamic_sql(\n        self, expression: exp.PartitionByRangePropertyDynamic\n    ) -> str:\n        start = self.sql(expression, \"start\")\n        end = self.sql(expression, \"end\")\n\n        every = expression.args[\"every\"]\n        if isinstance(every, exp.Interval) and every.this.is_string:\n            every.this.replace(exp.Literal.number(every.name))\n\n        return f\"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}\"\n\n    def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str:\n        name = self.sql(expression, \"this\")\n        values = self.expressions(expression, flat=True)\n\n        return f\"NAME {name} VALUE {values}\"\n\n    def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str:\n        kind = self.sql(expression, \"kind\")\n        sample = self.sql(expression, \"sample\")\n        return f\"SAMPLE {sample} {kind}\"\n\n    def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str:\n        kind = self.sql(expression, \"kind\")\n        option = self.sql(expression, \"option\")\n        option = f\" {option}\" if option else \"\"\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        columns = self.expressions(expression)\n        columns = f\" {columns}\" if columns else \"\"\n        return f\"{kind}{option} STATISTICS{this}{columns}\"\n\n    def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str:\n        this = self.sql(expression, \"this\")\n        columns = self.expressions(expression)\n        inner_expression = self.sql(expression, \"expression\")\n        inner_expression = f\" {inner_expression}\" if inner_expression else \"\"\n        update_options = self.sql(expression, \"update_options\")\n        update_options = f\" {update_options} UPDATE\" if update_options else \"\"\n        return f\"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}\"\n\n    def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str:\n        kind = self.sql(expression, \"kind\")\n        kind = f\" {kind}\" if kind else \"\"\n        return f\"DELETE{kind} STATISTICS\"\n\n    def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str:\n        inner_expression = self.sql(expression, \"expression\")\n        return f\"LIST CHAINED ROWS{inner_expression}\"\n\n    def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str:\n        kind = self.sql(expression, \"kind\")\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        inner_expression = self.sql(expression, \"expression\")\n        return f\"VALIDATE {kind}{this}{inner_expression}\"\n\n    def analyze_sql(self, expression: exp.Analyze) -> str:\n        options = self.expressions(expression, key=\"options\", sep=\" \")\n        options = f\" {options}\" if options else \"\"\n        kind = self.sql(expression, \"kind\")\n        kind = f\" {kind}\" if kind else \"\"\n        this = self.sql(expression, \"this\")\n        this = f\" {this}\" if this else \"\"\n        mode = self.sql(expression, \"mode\")\n        mode = f\" {mode}\" if mode else \"\"\n        properties = self.sql(expression, \"properties\")\n        properties = f\" {properties}\" if properties else \"\"\n        partition = self.sql(expression, \"partition\")\n        partition = f\" {partition}\" if partition else \"\"\n        inner_expression = self.sql(expression, \"expression\")\n        inner_expression = f\" {inner_expression}\" if inner_expression else \"\"\n        return f\"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}\"\n\n    def xmltable_sql(self, expression: exp.XMLTable) -> str:\n        this = self.sql(expression, \"this\")\n        namespaces = self.expressions(expression, key=\"namespaces\")\n        namespaces = f\"XMLNAMESPACES({namespaces}), \" if namespaces else \"\"\n        passing = self.expressions(expression, key=\"passing\")\n        passing = f\"{self.sep()}PASSING{self.seg(passing)}\" if passing else \"\"\n        columns = self.expressions(expression, key=\"columns\")\n        columns = f\"{self.sep()}COLUMNS{self.seg(columns)}\" if columns else \"\"\n        by_ref = f\"{self.sep()}RETURNING SEQUENCE BY REF\" if expression.args.get(\"by_ref\") else \"\"\n        return f\"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}\"\n\n    def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str:\n        this = self.sql(expression, \"this\")\n        return this if isinstance(expression.this, exp.Alias) else f\"DEFAULT {this}\"\n\n    def export_sql(self, expression: exp.Export) -> str:\n        this = self.sql(expression, \"this\")\n        connection = self.sql(expression, \"connection\")\n        connection = f\"WITH CONNECTION {connection} \" if connection else \"\"\n        options = self.sql(expression, \"options\")\n        return f\"EXPORT DATA {connection}{options} AS {this}\"\n\n    def declare_sql(self, expression: exp.Declare) -> str:\n        replace = \"OR REPLACE \" if expression.args.get(\"replace\") else \"\"\n        return f\"DECLARE {replace}{self.expressions(expression, flat=True)}\"\n\n    def declareitem_sql(self, expression: exp.DeclareItem) -> str:\n        variables = self.expressions(expression, \"this\")\n        default = self.sql(expression, \"default\")\n        default = f\" {self.DECLARE_DEFAULT_ASSIGNMENT} {default}\" if default else \"\"\n\n        kind = self.sql(expression, \"kind\")\n        if isinstance(expression.args.get(\"kind\"), exp.Schema):\n            kind = f\"TABLE {kind}\"\n\n        kind = f\" {kind}\" if kind else \"\"\n\n        return f\"{variables}{kind}{default}\"\n\n    def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str:\n        kind = self.sql(expression, \"kind\")\n        this = self.sql(expression, \"this\")\n        set = self.sql(expression, \"expression\")\n        using = self.sql(expression, \"using\")\n        using = f\" USING {using}\" if using else \"\"\n\n        kind_sql = kind if kind == \"CYCLE\" else f\"SEARCH {kind} FIRST BY\"\n\n        return f\"{kind_sql} {this} SET {set}{using}\"\n\n    def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str:\n        params = self.expressions(expression, key=\"params\", flat=True)\n        return self.func(expression.name, *expression.expressions) + f\"({params})\"\n\n    def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str:\n        return self.func(expression.name, *expression.expressions)\n\n    def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str:\n        return self.anonymousaggfunc_sql(expression)\n\n    def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str:\n        return self.parameterizedagg_sql(expression)\n\n    def show_sql(self, expression: exp.Show) -> str:\n        self.unsupported(\"Unsupported SHOW statement\")\n        return \"\"\n\n    def install_sql(self, expression: exp.Install) -> str:\n        self.unsupported(\"Unsupported INSTALL statement\")\n        return \"\"\n\n    def get_put_sql(self, expression: exp.Put | exp.Get) -> str:\n        # Snowflake GET/PUT statements:\n        #   PUT <file> <internalStage> <properties>\n        #   GET <internalStage> <file> <properties>\n        props = expression.args.get(\"properties\")\n        props_sql = self.properties(props, prefix=\" \", sep=\" \", wrapped=False) if props else \"\"\n        this = self.sql(expression, \"this\")\n        target = self.sql(expression, \"target\")\n\n        if isinstance(expression, exp.Put):\n            return f\"PUT {this} {target}{props_sql}\"\n        else:\n            return f\"GET {target} {this}{props_sql}\"\n\n    def translatecharacters_sql(self, expression: exp.TranslateCharacters) -> str:\n        this = self.sql(expression, \"this\")\n        expr = self.sql(expression, \"expression\")\n        with_error = \" WITH ERROR\" if expression.args.get(\"with_error\") else \"\"\n        return f\"TRANSLATE({this} USING {expr}{with_error})\"\n\n    def decodecase_sql(self, expression: exp.DecodeCase) -> str:\n        if self.SUPPORTS_DECODE_CASE:\n            return self.func(\"DECODE\", *expression.expressions)\n\n        expression, *expressions = expression.expressions\n\n        ifs = []\n        for search, result in zip(expressions[::2], expressions[1::2]):\n            if isinstance(search, exp.Literal):\n                ifs.append(exp.If(this=expression.eq(search), true=result))\n            elif isinstance(search, exp.Null):\n                ifs.append(exp.If(this=expression.is_(exp.Null()), true=result))\n            else:\n                if isinstance(search, exp.Binary):\n                    search = exp.paren(search)\n\n                cond = exp.or_(\n                    expression.eq(search),\n                    exp.and_(expression.is_(exp.Null()), search.is_(exp.Null()), copy=False),\n                    copy=False,\n                )\n                ifs.append(exp.If(this=cond, true=result))\n\n        case = exp.Case(ifs=ifs, default=expressions[-1] if len(expressions) % 2 == 1 else None)\n        return self.sql(case)\n\n    def semanticview_sql(self, expression: exp.SemanticView) -> str:\n        this = self.sql(expression, \"this\")\n        this = self.seg(this, sep=\"\")\n        dimensions = self.expressions(\n            expression, \"dimensions\", dynamic=True, skip_first=True, skip_last=True\n        )\n        dimensions = self.seg(f\"DIMENSIONS {dimensions}\") if dimensions else \"\"\n        metrics = self.expressions(\n            expression, \"metrics\", dynamic=True, skip_first=True, skip_last=True\n        )\n        metrics = self.seg(f\"METRICS {metrics}\") if metrics else \"\"\n        facts = self.expressions(expression, \"facts\", dynamic=True, skip_first=True, skip_last=True)\n        facts = self.seg(f\"FACTS {facts}\") if facts else \"\"\n        where = self.sql(expression, \"where\")\n        where = self.seg(f\"WHERE {where}\") if where else \"\"\n        body = self.indent(this + metrics + dimensions + facts + where, skip_first=True)\n        return f\"SEMANTIC_VIEW({body}{self.seg(')', sep='')}\"\n\n    def getextract_sql(self, expression: exp.GetExtract) -> str:\n        this = expression.this\n        expr = expression.expression\n\n        if not this.type or not expression.type:\n            from sqlglot.optimizer.annotate_types import annotate_types\n\n            this = annotate_types(this, dialect=self.dialect)\n\n        if this.is_type(*(exp.DType.ARRAY, exp.DType.MAP)):\n            return self.sql(exp.Bracket(this=this, expressions=[expr]))\n\n        return self.sql(exp.JSONExtract(this=this, expression=self.dialect.to_json_path(expr)))\n\n    def datefromunixdate_sql(self, expression: exp.DateFromUnixDate) -> str:\n        return self.sql(\n            exp.DateAdd(\n                this=exp.cast(exp.Literal.string(\"1970-01-01\"), exp.DType.DATE),\n                expression=expression.this,\n                unit=exp.var(\"DAY\"),\n            )\n        )\n\n    def space_sql(self: Generator, expression: exp.Space) -> str:\n        return self.sql(exp.Repeat(this=exp.Literal.string(\" \"), times=expression.this))\n\n    def buildproperty_sql(self, expression: exp.BuildProperty) -> str:\n        return f\"BUILD {self.sql(expression, 'this')}\"\n\n    def refreshtriggerproperty_sql(self, expression: exp.RefreshTriggerProperty) -> str:\n        method = self.sql(expression, \"method\")\n        kind = expression.args.get(\"kind\")\n        if not kind:\n            return f\"REFRESH {method}\"\n\n        every = self.sql(expression, \"every\")\n        unit = self.sql(expression, \"unit\")\n        every = f\" EVERY {every} {unit}\" if every else \"\"\n        starts = self.sql(expression, \"starts\")\n        starts = f\" STARTS {starts}\" if starts else \"\"\n\n        return f\"REFRESH {method} ON {kind}{every}{starts}\"\n\n    def modelattribute_sql(self, expression: exp.ModelAttribute) -> str:\n        self.unsupported(\"The model!attribute syntax is not supported\")\n        return \"\"\n\n    def directorystage_sql(self, expression: exp.DirectoryStage) -> str:\n        return self.func(\"DIRECTORY\", expression.this)\n\n    def uuid_sql(self, expression: exp.Uuid) -> str:\n        is_string = expression.args.get(\"is_string\", False)\n        uuid_func_sql = self.func(\"UUID\")\n\n        if is_string and not self.dialect.UUID_IS_STRING_TYPE:\n            return self.sql(exp.cast(uuid_func_sql, exp.DType.VARCHAR, dialect=self.dialect))\n\n        return uuid_func_sql\n\n    def initcap_sql(self, expression: exp.Initcap) -> str:\n        delimiters = expression.expression\n\n        if delimiters:\n            # do not generate delimiters arg if we are round-tripping from default delimiters\n            if (\n                delimiters.is_string\n                and delimiters.this == self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS\n            ):\n                delimiters = None\n            elif not self.dialect.INITCAP_SUPPORTS_CUSTOM_DELIMITERS:\n                self.unsupported(\"INITCAP does not support custom delimiters\")\n                delimiters = None\n\n        return self.func(\"INITCAP\", expression.this, delimiters)\n\n    def localtime_sql(self, expression: exp.Localtime) -> str:\n        this = expression.this\n        return self.func(\"LOCALTIME\", this) if this else \"LOCALTIME\"\n\n    def localtimestamp_sql(self, expression: exp.Localtime) -> str:\n        this = expression.this\n        return self.func(\"LOCALTIMESTAMP\", this) if this else \"LOCALTIMESTAMP\"\n\n    def weekstart_sql(self, expression: exp.WeekStart) -> str:\n        this = expression.this.name.upper()\n        if self.dialect.WEEK_OFFSET == -1 and this == \"SUNDAY\":\n            # BigQuery specific optimization since WEEK(SUNDAY) == WEEK\n            return \"WEEK\"\n\n        return self.func(\"WEEK\", expression.this)\n\n    def chr_sql(self, expression: exp.Chr, name: str = \"CHR\") -> str:\n        this = self.expressions(expression)\n        charset = self.sql(expression, \"charset\")\n        using = f\" USING {charset}\" if charset else \"\"\n        return self.func(name, this + using)\n\n    def block_sql(self, expression: exp.Block) -> str:\n        expressions = self.expressions(expression, sep=\"; \", flat=True)\n        return f\"{expressions}\" if expressions else \"\"\n\n    def storedprocedure_sql(self, expression: exp.StoredProcedure) -> str:\n        self.unsupported(\"Unsupported Stored Procedure syntax\")\n        return \"\"\n\n    def ifblock_sql(self, expression: exp.IfBlock) -> str:\n        self.unsupported(\"Unsupported If block syntax\")\n        return \"\"\n\n    def whileblock_sql(self, expression: exp.WhileBlock) -> str:\n        self.unsupported(\"Unsupported While block syntax\")\n        return \"\"\n\n    def execute_sql(self, expression: exp.Execute) -> str:\n        self.unsupported(\"Unsupported Execute syntax\")\n        return \"\"\n\n    def executesql_sql(self, expression: exp.ExecuteSql) -> str:\n        self.unsupported(\"Unsupported Execute syntax\")\n        return \"\"\n\n    def altermodifysqlsecurity_sql(self, expression: exp.AlterModifySqlSecurity) -> str:\n        props = self.expressions(expression, sep=\" \")\n        return f\"MODIFY {props}\"\n"
  },
  {
    "path": "sqlglot/helper.py",
    "content": "from __future__ import annotations\n\nimport datetime\nimport inspect\nimport logging\nimport re\nimport sys\nimport typing as t\nfrom collections.abc import Collection, Set, Iterable, Sequence, Iterator, Mapping\nfrom copy import copy\nfrom difflib import get_close_matches\nfrom enum import Enum\nfrom itertools import count\nfrom builtins import type as Type\n\ntry:\n    from mypy_extensions import mypyc_attr, trait, i64\nexcept ImportError:\n\n    def mypyc_attr(*attrs: str, **kwattrs: object) -> t.Callable[[t.Any], t.Any]:  # type: ignore[misc]\n        return lambda f: f\n\n    def trait(f: t.Any) -> t.Any:  # type: ignore[misc]\n        return f\n\n    i64 = int  # type: ignore[misc,assignment]\n\n\nT = t.TypeVar(\"T\")\nE = t.TypeVar(\"E\")\n\nif t.TYPE_CHECKING:\n    from sqlglot.expressions import Expr\n\n\nCAMEL_CASE_PATTERN = re.compile(\"(?<!^)(?=[A-Z])\")\nPYTHON_VERSION = sys.version_info[:2]\nlogger = logging.getLogger(\"sqlglot\")\n\n\nclass AutoName(Enum):\n    \"\"\"\n    This is used for creating Enum classes where `auto()` is the string form\n    of the corresponding enum's identifier (e.g. FOO.value results in \"FOO\").\n\n    Reference: https://docs.python.org/3/howto/enum.html#using-automatic-values\n    \"\"\"\n\n    def _generate_next_value_(name, _start, _count, _last_values):\n        return name\n\n\ndef suggest_closest_match_and_fail(\n    kind: str,\n    word: str,\n    possibilities: Iterable[str],\n) -> None:\n    close_matches = get_close_matches(word, possibilities, n=1)\n\n    similar = seq_get(close_matches, 0) or \"\"\n    if similar:\n        similar = f\" Did you mean {similar}?\"\n\n    raise ValueError(f\"Unknown {kind} '{word}'.{similar}\")\n\n\ndef seq_get(seq: Sequence[T], index: int) -> t.Optional[T]:\n    \"\"\"Returns the value in `seq` at position `index`, or `None` if `index` is out of bounds.\"\"\"\n    try:\n        return seq[index]\n    except IndexError:\n        return None\n\n\n@t.overload\ndef ensure_list(value: Collection[T]) -> list[T]: ...\n\n\n@t.overload\ndef ensure_list(value: None) -> t.List: ...\n\n\n@t.overload\ndef ensure_list(value: T) -> list[T]: ...\n\n\ndef ensure_list(value):\n    \"\"\"\n    Ensures that a value is a list, otherwise casts or wraps it into one.\n\n    Args:\n        value: The value of interest.\n\n    Returns:\n        The value cast as a list if it's a list or a tuple, or else the value wrapped in a list.\n    \"\"\"\n    if value is None:\n        return []\n    if isinstance(value, (list, tuple)):\n        return list(value)\n\n    return [value]\n\n\n@t.overload\ndef ensure_collection(value: Collection[T]) -> Collection[T]: ...\n\n\n@t.overload\ndef ensure_collection(value: T) -> Collection[T]: ...\n\n\ndef ensure_collection(value):\n    \"\"\"\n    Ensures that a value is a collection (excluding `str` and `bytes`), otherwise wraps it into a list.\n\n    Args:\n        value: The value of interest.\n\n    Returns:\n        The value if it's a collection, or else the value wrapped in a list.\n    \"\"\"\n    if value is None:\n        return []\n    return (\n        value if isinstance(value, Collection) and not isinstance(value, (str, bytes)) else [value]\n    )\n\n\ndef csv(*args: str, sep: str = \", \") -> str:\n    \"\"\"\n    Formats any number of string arguments as CSV.\n\n    Args:\n        args: The string arguments to format.\n        sep: The argument separator.\n\n    Returns:\n        The arguments formatted as a CSV string.\n    \"\"\"\n    return sep.join(arg for arg in args if arg)\n\n\ndef subclasses(\n    module_name: str,\n    classes: Type[T] | tuple[Type[T], ...],\n    exclude: set[Type[T]] = set(),\n) -> list[Type[T]]:\n    \"\"\"\n    Returns all subclasses for a collection of classes, possibly excluding some of them.\n\n    Args:\n        module_name: The name of the module to search for subclasses in.\n        classes: Class(es) we want to find the subclasses of.\n        exclude: Classes we want to exclude from the returned list.\n\n    Returns:\n        The target subclasses.\n    \"\"\"\n    return [\n        obj\n        for _, obj in inspect.getmembers(\n            sys.modules[module_name],\n            lambda obj: inspect.isclass(obj) and issubclass(obj, classes) and obj not in exclude,\n        )\n    ]\n\n\ndef camel_to_snake_case(name: str) -> str:\n    \"\"\"Converts `name` from camelCase to snake_case and returns the result.\"\"\"\n    return CAMEL_CASE_PATTERN.sub(\"_\", name).upper()\n\n\ndef while_changing(expression: E, func: t.Callable[[E], E]) -> E:\n    \"\"\"\n    Applies a transformation to a given expression until a fix point is reached.\n\n    Args:\n        expression: The expression to be transformed.\n        func: The transformation to be applied.\n\n    Returns:\n        The transformed expression.\n    \"\"\"\n\n    while True:\n        start_hash = hash(expression)\n        expression = func(expression)\n        end_hash = hash(expression)\n\n        if start_hash == end_hash:\n            break\n\n    return expression\n\n\ndef tsort(dag: t.Dict[T, t.Set[T]]) -> t.List[T]:\n    \"\"\"\n    Sorts a given directed acyclic graph in topological order.\n\n    Args:\n        dag: The graph to be sorted.\n\n    Returns:\n        A list that contains all of the graph's nodes in topological order.\n    \"\"\"\n    result = []\n\n    for node, deps in tuple(dag.items()):\n        for dep in deps:\n            if dep not in dag:\n                dag[dep] = set()\n\n    while dag:\n        current = {node for node, deps in dag.items() if not deps}\n\n        if not current:\n            raise ValueError(\"Cycle error\")\n\n        for node in current:\n            dag.pop(node)\n\n        for deps in dag.values():\n            deps -= current\n\n        result.extend(sorted(current))  # type: ignore\n\n    return result\n\n\ndef find_new_name(taken: Collection[str], base: str) -> str:\n    \"\"\"\n    Searches for a new name.\n\n    Args:\n        taken: A collection of taken names.\n        base: Base name to alter.\n\n    Returns:\n        The new, available name.\n    \"\"\"\n    if base not in taken:\n        return base\n\n    i = 2\n    new = f\"{base}_{i}\"\n    while new in taken:\n        i += 1\n        new = f\"{base}_{i}\"\n\n    return new\n\n\ndef is_int(text: str) -> bool:\n    return is_type(text, int)\n\n\ndef is_float(text: str) -> bool:\n    return is_type(text, float)\n\n\ndef is_type(text: str, target_type: Type) -> bool:\n    try:\n        target_type(text)\n        return True\n    except ValueError:\n        return False\n\n\ndef name_sequence(prefix: str) -> t.Callable[[], str]:\n    \"\"\"Returns a name generator given a prefix (e.g. a0, a1, a2, ... if the prefix is \"a\").\"\"\"\n    sequence = count()\n    return lambda: f\"{prefix}{next(sequence)}\"\n\n\ndef object_to_dict(obj: t.Any, **kwargs) -> t.Dict:\n    \"\"\"Returns a dictionary created from an object's attributes.\"\"\"\n    return {\n        **{k: v.copy() if hasattr(v, \"copy\") else copy(v) for k, v in vars(obj).items()},\n        **kwargs,\n    }\n\n\ndef split_num_words(\n    value: str, sep: str, min_num_words: int, fill_from_start: bool = True\n) -> t.List[t.Optional[str]]:\n    \"\"\"\n    Perform a split on a value and return N words as a result with `None` used for words that don't exist.\n\n    Args:\n        value: The value to be split.\n        sep: The value to use to split on.\n        min_num_words: The minimum number of words that are going to be in the result.\n        fill_from_start: Indicates that if `None` values should be inserted at the start or end of the list.\n\n    Examples:\n        >>> split_num_words(\"db.table\", \".\", 3)\n        [None, 'db', 'table']\n        >>> split_num_words(\"db.table\", \".\", 3, fill_from_start=False)\n        ['db', 'table', None]\n        >>> split_num_words(\"db.table\", \".\", 1)\n        ['db', 'table']\n\n    Returns:\n        The list of words returned by `split`, possibly augmented by a number of `None` values.\n    \"\"\"\n    words = value.split(sep)\n    if fill_from_start:\n        return [None] * (min_num_words - len(words)) + words\n    return words + [None] * (min_num_words - len(words))\n\n\ndef is_iterable(value: t.Any) -> bool:\n    \"\"\"\n    Checks if the value is an iterable, excluding the types `str` and `bytes`.\n\n    Examples:\n        >>> is_iterable([1,2])\n        True\n        >>> is_iterable(\"test\")\n        False\n\n    Args:\n        value: The value to check if it is an iterable.\n\n    Returns:\n        A `bool` value indicating if it is an iterable.\n    \"\"\"\n    from sqlglot.expressions import Expr\n\n    return hasattr(value, \"__iter__\") and not isinstance(value, (str, bytes, Expr))\n\n\ndef flatten(values: Iterable[Iterable[t.Any] | t.Any]) -> Iterator[t.Any]:\n    \"\"\"\n    Flattens an iterable that can contain both iterable and non-iterable elements. Objects of\n    type `str` and `bytes` are not regarded as iterables.\n\n    Examples:\n        >>> list(flatten([[1, 2], 3, {4}, (5, \"bla\")]))\n        [1, 2, 3, 4, 5, 'bla']\n        >>> list(flatten([1, 2, 3]))\n        [1, 2, 3]\n\n    Args:\n        values: The value to be flattened.\n\n    Yields:\n        Non-iterable elements in `values`.\n    \"\"\"\n    for value in values:\n        if is_iterable(value):\n            yield from flatten(value)\n        else:\n            yield value\n\n\ndef dict_depth(d: t.Any) -> int:\n    \"\"\"\n    Get the nesting depth of a dictionary.\n\n    Example:\n        >>> dict_depth(None)\n        0\n        >>> dict_depth({})\n        1\n        >>> dict_depth({\"a\": \"b\"})\n        1\n        >>> dict_depth({\"a\": {}})\n        2\n        >>> dict_depth({\"a\": {\"b\": {}}})\n        3\n    \"\"\"\n    try:\n        return 1 + dict_depth(next(iter(d.values())))\n    except AttributeError:\n        # d doesn't have attribute \"values\"\n        return 0\n    except StopIteration:\n        # d.values() returns an empty sequence\n        return 1\n\n\ndef first(it: Iterable[T]) -> T:\n    \"\"\"Returns the first element from an iterable (useful for sets).\"\"\"\n    return next(i for i in it)\n\n\ndef to_bool(value: t.Optional[str | bool]) -> t.Optional[str | bool]:\n    if isinstance(value, bool) or value is None:\n        return value\n\n    # Coerce the value to boolean if it matches to the truthy/falsy values below\n    value_lower = value.lower()\n    if value_lower in (\"true\", \"1\"):\n        return True\n    if value_lower in (\"false\", \"0\"):\n        return False\n\n    return value\n\n\ndef merge_ranges(ranges: t.List[t.Tuple[t.Any, t.Any]]) -> t.List[t.Tuple[t.Any, t.Any]]:\n    \"\"\"\n    Merges a sequence of ranges, represented as tuples (low, high) whose values\n    belong to some totally-ordered set.\n\n    Example:\n        >>> merge_ranges([(1, 3), (2, 6)])\n        [(1, 6)]\n    \"\"\"\n    if not ranges:\n        return []\n\n    ranges = sorted(ranges)\n\n    merged = [ranges[0]]\n\n    for start, end in ranges[1:]:\n        last_start, last_end = merged[-1]\n\n        if start <= last_end:\n            merged[-1] = (last_start, max(last_end, end))\n        else:\n            merged.append((start, end))\n\n    return merged\n\n\ndef is_iso_date(text: str) -> bool:\n    try:\n        datetime.date.fromisoformat(text)\n        return True\n    except ValueError:\n        return False\n\n\ndef is_iso_datetime(text: str) -> bool:\n    try:\n        datetime.datetime.fromisoformat(text)\n        return True\n    except ValueError:\n        return False\n\n\n# Interval units that operate on date components\nDATE_UNITS = {\"day\", \"week\", \"month\", \"quarter\", \"year\", \"year_month\"}\n\n\ndef is_date_unit(expression: t.Optional[Expr]) -> bool:\n    return expression is not None and expression.name.lower() in DATE_UNITS\n\n\nK = t.TypeVar(\"K\")\nV = t.TypeVar(\"V\")\n\n\nclass SingleValuedMapping(Mapping[K, V]):\n    \"\"\"\n    Mapping where all keys return the same value.\n\n    This rigamarole is meant to avoid copying keys, which was originally intended\n    as an optimization while qualifying columns for tables with lots of columns.\n    \"\"\"\n\n    def __init__(self, keys: Collection[K], value: V):\n        self._keys = keys if isinstance(keys, Set) else set(keys)\n        self._value = value\n\n    def __getitem__(self, key: K) -> V:\n        if key in self._keys:\n            return self._value\n        raise KeyError(key)\n\n    def __len__(self) -> int:\n        return len(self._keys)\n\n    def __iter__(self) -> Iterator[K]:\n        return iter(self._keys)\n"
  },
  {
    "path": "sqlglot/jsonpath.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nimport sqlglot.expressions as exp\nfrom sqlglot.errors import ParseError\nfrom sqlglot.tokens import Token, Tokenizer, TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n    from collections.abc import Collection\n\n\nclass JSONPathTokenizer(Tokenizer):\n    SINGLE_TOKENS = {\n        \"(\": TokenType.L_PAREN,\n        \")\": TokenType.R_PAREN,\n        \"[\": TokenType.L_BRACKET,\n        \"]\": TokenType.R_BRACKET,\n        \":\": TokenType.COLON,\n        \",\": TokenType.COMMA,\n        \"-\": TokenType.DASH,\n        \".\": TokenType.DOT,\n        \"?\": TokenType.PLACEHOLDER,\n        \"@\": TokenType.PARAMETER,\n        \"'\": TokenType.QUOTE,\n        '\"': TokenType.QUOTE,\n        \"$\": TokenType.DOLLAR,\n        \"*\": TokenType.STAR,\n    }\n\n    KEYWORDS = {\n        \"..\": TokenType.DOT,\n    }\n\n    IDENTIFIER_ESCAPES = [\"\\\\\"]\n    STRING_ESCAPES = [\"\\\\\"]\n\n    VAR_TOKENS = {\n        TokenType.VAR,\n    }\n\n\ndef parse(path: str, dialect: DialectType = None) -> exp.JSONPath:\n    \"\"\"Takes in a JSON path string and parses it into a JSONPath expression.\"\"\"\n    from sqlglot.dialects import Dialect\n\n    jsonpath_tokenizer = Dialect.get_or_raise(dialect).jsonpath_tokenizer()\n    tokens = jsonpath_tokenizer.tokenize(path)\n    size = len(tokens)\n\n    i = 0\n\n    def _curr() -> t.Optional[TokenType]:\n        return tokens[i].token_type if i < size else None\n\n    def _prev() -> Token:\n        return tokens[i - 1]\n\n    def _advance() -> Token:\n        nonlocal i\n        i += 1\n        return _prev()\n\n    def _error(msg: str) -> str:\n        return f\"{msg} at index {i}: {path}\"\n\n    @t.overload\n    def _match(token_type: TokenType, raise_unmatched: t.Literal[True] = True) -> Token:\n        pass\n\n    @t.overload\n    def _match(\n        token_type: TokenType, raise_unmatched: t.Literal[False] = False\n    ) -> t.Optional[Token]:\n        pass\n\n    def _match(token_type, raise_unmatched=False):\n        if _curr() == token_type:\n            return _advance()\n        if raise_unmatched:\n            raise ParseError(_error(f\"Expected {token_type}\"))\n        return None\n\n    def _match_set(types: Collection[TokenType]) -> t.Optional[Token]:\n        return _advance() if _curr() in types else None\n\n    def _parse_literal() -> t.Any:\n        token = _match(TokenType.STRING) or _match(TokenType.IDENTIFIER)\n        if token:\n            return token.text\n        if _match(TokenType.STAR):\n            return exp.JSONPathWildcard()\n        if _match(TokenType.PLACEHOLDER) or _match(TokenType.L_PAREN):\n            script = _prev().text == \"(\"\n            start = i\n\n            while True:\n                if _match(TokenType.L_BRACKET):\n                    _parse_bracket()  # nested call which we can throw away\n                if _curr() in (TokenType.R_BRACKET, None):\n                    break\n                _advance()\n\n            expr_type = exp.JSONPathScript if script else exp.JSONPathFilter\n            return expr_type(this=path[tokens[start].start : tokens[i].end])\n\n        number = \"-\" if _match(TokenType.DASH) else \"\"\n\n        token = _match(TokenType.NUMBER)\n        if token:\n            number += token.text\n\n        if number:\n            return int(number)\n\n        return False\n\n    def _parse_slice() -> t.Any:\n        start = _parse_literal()\n        end = _parse_literal() if _match(TokenType.COLON) else None\n        step = _parse_literal() if _match(TokenType.COLON) else None\n\n        if end is None and step is None:\n            return start\n\n        return exp.JSONPathSlice(start=start, end=end, step=step)\n\n    def _parse_bracket() -> exp.JSONPathPart:\n        literal = _parse_slice()\n\n        if isinstance(literal, str) or literal is not False:\n            indexes = [literal]\n            while _match(TokenType.COMMA):\n                literal = _parse_slice()\n\n                if literal:\n                    indexes.append(literal)\n\n            if len(indexes) == 1:\n                if isinstance(literal, str):\n                    node: exp.JSONPathPart = exp.JSONPathKey(this=indexes[0])\n                elif isinstance(literal, exp.JSONPathPart) and isinstance(\n                    literal, (exp.JSONPathScript, exp.JSONPathFilter)\n                ):\n                    node = exp.JSONPathSelector(this=indexes[0])\n                else:\n                    node = exp.JSONPathSubscript(this=indexes[0])\n            else:\n                node = exp.JSONPathUnion(expressions=indexes)\n        else:\n            raise ParseError(_error(\"Cannot have empty segment\"))\n\n        _match(TokenType.R_BRACKET, raise_unmatched=True)\n\n        return node\n\n    def _parse_var_text() -> str:\n        \"\"\"\n        Consumes & returns the text for a var. In BigQuery it's valid to have a key with spaces\n        in it, e.g JSON_QUERY(..., '$. a b c ') should produce a single JSONPathKey(' a b c ').\n        This is done by merging \"consecutive\" vars until a key separator is found (dot, colon etc)\n        or the path string is exhausted.\n        \"\"\"\n        prev_index = i - 2\n\n        while _match_set(jsonpath_tokenizer.VAR_TOKENS):\n            pass\n\n        start = 0 if prev_index < 0 else tokens[prev_index].end + 1\n\n        if i >= len(tokens):\n            # This key is the last token for the path, so it's text is the remaining path\n            text = path[start:]\n        else:\n            text = path[start : tokens[i].start]\n\n        return text\n\n    # We canonicalize the JSON path AST so that it always starts with a\n    # \"root\" element, so paths like \"field\" will be generated as \"$.field\"\n    _match(TokenType.DOLLAR)\n    expressions: t.List[exp.JSONPathPart] = [exp.JSONPathRoot()]\n\n    while _curr():\n        if _match(TokenType.DOT) or _match(TokenType.COLON):\n            recursive = _prev().text == \"..\"\n\n            if _match_set(jsonpath_tokenizer.VAR_TOKENS):\n                value: t.Optional[str | exp.JSONPathWildcard] = _parse_var_text()\n            elif _match(TokenType.IDENTIFIER):\n                value = _prev().text\n            elif _match(TokenType.STAR):\n                value = exp.JSONPathWildcard()\n            else:\n                value = None\n\n            if recursive:\n                expressions.append(exp.JSONPathRecursive(this=value))\n            elif value:\n                expressions.append(exp.JSONPathKey(this=value))\n            else:\n                raise ParseError(_error(\"Expected key name or * after DOT\"))\n        elif _match(TokenType.L_BRACKET):\n            expressions.append(_parse_bracket())\n        elif _match_set(jsonpath_tokenizer.VAR_TOKENS):\n            expressions.append(exp.JSONPathKey(this=_parse_var_text()))\n        elif _match(TokenType.IDENTIFIER):\n            expressions.append(exp.JSONPathKey(this=_prev().text))\n        elif _match(TokenType.STAR):\n            expressions.append(exp.JSONPathWildcard())\n        else:\n            raise ParseError(_error(f\"Unexpected {tokens[i].token_type}\"))\n\n    return exp.JSONPath(expressions=expressions)\n\n\nJSON_PATH_PART_TRANSFORMS: t.Dict[t.Type[exp.Expr], t.Callable[..., str]] = {\n    exp.JSONPathFilter: lambda _, e: f\"?{e.this}\",\n    exp.JSONPathKey: lambda self, e: self._jsonpathkey_sql(e),\n    exp.JSONPathRecursive: lambda _, e: f\"..{e.this or ''}\",\n    exp.JSONPathRoot: lambda *_: \"$\",\n    exp.JSONPathScript: lambda _, e: f\"({e.this}\",\n    exp.JSONPathSelector: lambda self, e: f\"[{self.json_path_part(e.this)}]\",\n    exp.JSONPathSlice: lambda self, e: \":\".join(\n        \"\" if p is False else self.json_path_part(p)\n        for p in [e.args.get(\"start\"), e.args.get(\"end\"), e.args.get(\"step\")]\n        if p is not None\n    ),\n    exp.JSONPathSubscript: lambda self, e: self._jsonpathsubscript_sql(e),\n    exp.JSONPathUnion: lambda self, e: (\n        f\"[{','.join(self.json_path_part(p) for p in e.expressions)}]\"\n    ),\n    exp.JSONPathWildcard: lambda *_: \"*\",\n}\n\nALL_JSON_PATH_PARTS = set(JSON_PATH_PART_TRANSFORMS)\n"
  },
  {
    "path": "sqlglot/lineage.py",
    "content": "from __future__ import annotations\n\nimport json\nimport logging\nimport typing as t\nfrom dataclasses import dataclass, field\n\nfrom sqlglot import Schema, exp, maybe_parse\nfrom sqlglot.errors import SqlglotError\nfrom sqlglot.optimizer import Scope, build_scope, find_all_in_scope, normalize_identifiers, qualify\nfrom sqlglot.optimizer.scope import ScopeType\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n    from collections.abc import Iterator, Mapping, Sequence\n\nlogger = logging.getLogger(\"sqlglot\")\n\n\n@dataclass(frozen=True)\nclass Node:\n    name: str\n    expression: exp.Expr\n    source: exp.Expr\n    downstream: list[Node] = field(default_factory=list)\n    source_name: str = \"\"\n    reference_node_name: str = \"\"\n\n    def walk(self) -> Iterator[Node]:\n        visited: set[int] = set()\n        queue = [self]\n        while queue:\n            node = queue.pop()\n            node_id = id(node)\n            if node_id in visited:\n                continue\n            visited.add(node_id)\n            yield node\n            queue.extend(reversed(node.downstream))\n\n    def to_html(self, dialect: DialectType = None, **opts) -> GraphHTML:\n        nodes = {}\n        edges = []\n\n        for node in self.walk():\n            if isinstance(node.expression, exp.Table):\n                label = f\"FROM {node.expression.this}\"\n                title = f\"<pre>SELECT {node.name} FROM {node.expression.this}</pre>\"\n                group = 1\n            else:\n                label = node.expression.sql(pretty=True, dialect=dialect)\n                source = node.source.transform(\n                    lambda n: (\n                        exp.Tag(this=n, prefix=\"<b>\", postfix=\"</b>\") if n is node.expression else n\n                    ),\n                    copy=False,\n                ).sql(pretty=True, dialect=dialect)\n                title = f\"<pre>{source}</pre>\"\n                group = 0\n\n            node_id = id(node)\n\n            nodes[node_id] = {\n                \"id\": node_id,\n                \"label\": label,\n                \"title\": title,\n                \"group\": group,\n            }\n\n            for d in node.downstream:\n                edges.append({\"from\": node_id, \"to\": id(d)})\n        return GraphHTML(nodes, edges, **opts)\n\n\ndef lineage(\n    column: str | exp.Column,\n    sql: str | exp.Expr,\n    schema: t.Optional[dict | Schema] = None,\n    sources: t.Optional[Mapping[str, str | exp.Query]] = None,\n    dialect: DialectType = None,\n    scope: t.Optional[Scope] = None,\n    trim_selects: bool = True,\n    copy: bool = True,\n    **kwargs,\n) -> Node:\n    \"\"\"Build the lineage graph for a column of a SQL query.\n\n    Args:\n        column: The column to build the lineage for.\n        sql: The SQL string or expression.\n        schema: The schema of tables.\n        sources: A mapping of queries which will be used to continue building lineage.\n        dialect: The dialect of input SQL.\n        scope: A pre-created scope to use instead.\n        trim_selects: Whether to clean up selects by trimming to only relevant columns.\n        copy: Whether to copy the Expr arguments.\n        **kwargs: Qualification optimizer kwargs.\n\n    Returns:\n        A lineage node.\n    \"\"\"\n\n    expression = maybe_parse(sql, copy=copy, dialect=dialect)\n    column = normalize_identifiers.normalize_identifiers(column, dialect=dialect).name\n\n    if sources:\n        expression = exp.expand(\n            expression,\n            {\n                k: t.cast(exp.Query, maybe_parse(v, copy=copy, dialect=dialect))\n                for k, v in sources.items()\n            },\n            dialect=dialect,\n            copy=copy,\n        )\n\n    if not scope:\n        expression = qualify.qualify(\n            expression,\n            dialect=dialect,\n            schema=schema,\n            **{\"validate_qualify_columns\": False, \"identify\": False, **kwargs},  # type: ignore\n        )\n\n        scope = build_scope(expression)\n\n    if not scope:\n        raise SqlglotError(\"Cannot build lineage, sql must be SELECT\")\n\n    selectable = scope.expression\n    if not isinstance(selectable, exp.Selectable) or not any(\n        select.alias_or_name == column for select in selectable.selects\n    ):\n        raise SqlglotError(f\"Cannot find column '{column}' in query.\")\n\n    return to_node(column, scope, dialect, trim_selects=trim_selects, _cache={})\n\n\ndef to_node(\n    column: str | int,\n    scope: Scope,\n    dialect: DialectType,\n    scope_name: t.Optional[str] = None,\n    upstream: t.Optional[Node] = None,\n    source_name: t.Optional[str] = None,\n    reference_node_name: t.Optional[str] = None,\n    trim_selects: bool = True,\n    _cache: t.Optional[t.Dict[t.Tuple, Node]] = None,\n) -> Node:\n    cache_key = (column, id(scope), scope_name, source_name, reference_node_name)\n\n    if _cache is not None and cache_key in _cache:\n        cached_node = _cache[cache_key]\n        if upstream:\n            upstream.downstream.append(cached_node)\n        return cached_node\n\n    # Find the specific select clause that is the source of the column we want.\n    # This can either be a specific, named select or a generic `*` clause.\n    selectable = t.cast(exp.Selectable, scope.expression)\n    if isinstance(column, int):\n        if column >= len(selectable.selects):\n            raise SqlglotError(\n                f\"Cannot find column's source with index {column} in query: {selectable.sql(dialect=dialect)}\"\n            )\n        select = selectable.selects[column]\n    else:\n        select = next(\n            (select for select in selectable.selects if select.alias_or_name == column),\n            exp.Star() if selectable.is_star else scope.expression,\n        )\n\n    if isinstance(scope.expression, exp.Subquery):\n        for inner_scope in scope.subquery_scopes:\n            result = to_node(\n                column,\n                scope=inner_scope,\n                dialect=dialect,\n                upstream=upstream,\n                source_name=source_name,\n                reference_node_name=reference_node_name,\n                trim_selects=trim_selects,\n                _cache=_cache,\n            )\n            if _cache is not None:\n                _cache[cache_key] = result\n            return result\n    if isinstance(scope.expression, exp.SetOperation):\n        name = type(scope.expression).__name__.upper()\n        upstream = upstream or Node(name=name, source=scope.expression, expression=select)\n\n        index = (\n            column\n            if isinstance(column, int)\n            else next(\n                (\n                    i\n                    for i, select in enumerate(selectable.selects)\n                    if select.alias_or_name == column or select.is_star\n                ),\n                -1,  # mypy will not allow a None here, but a negative index should never be returned\n            )\n        )\n\n        if index == -1:\n            raise ValueError(f\"Could not find {column} in {scope.expression}\")\n\n        for s in scope.union_scopes:\n            to_node(\n                index,\n                scope=s,\n                dialect=dialect,\n                upstream=upstream,\n                source_name=source_name,\n                reference_node_name=reference_node_name,\n                trim_selects=trim_selects,\n                _cache=_cache,\n            )\n\n        if _cache is not None:\n            _cache[cache_key] = upstream\n        return upstream\n\n    if trim_selects and isinstance(scope.expression, exp.Select):\n        # For better ergonomics in our node labels, replace the full select with\n        # a version that has only the column we care about.\n        #   \"x\", SELECT x, y FROM foo\n        #     => \"x\", SELECT x FROM foo\n        source: exp.Expr = scope.expression.select(select, append=False)\n    else:\n        source = scope.expression\n\n    # Create the node for this step in the lineage chain, and attach it to the previous one.\n    node = Node(\n        name=f\"{scope_name}.{column}\" if scope_name else str(column),\n        source=source,\n        expression=select,\n        source_name=source_name or \"\",\n        reference_node_name=reference_node_name or \"\",\n    )\n\n    if upstream:\n        upstream.downstream.append(node)\n\n    subquery_scopes = {\n        id(subquery_scope.expression): subquery_scope for subquery_scope in scope.subquery_scopes\n    }\n\n    for subquery in find_all_in_scope(select, *exp.UNWRAPPED_QUERIES):\n        subquery_scope: t.Optional[Scope] = subquery_scopes.get(id(subquery))\n        if not subquery_scope:\n            logger.warning(f\"Unknown subquery scope: {subquery.sql(dialect=dialect)}\")\n            continue\n\n        for name in subquery.named_selects:\n            to_node(\n                name,\n                scope=subquery_scope,\n                dialect=dialect,\n                upstream=node,\n                trim_selects=trim_selects,\n                _cache=_cache,\n            )\n\n    # if the select is a star add all scope sources as downstreams\n    if isinstance(select, exp.Star):\n        for src in scope.sources.values():\n            src_expr = src.expression if isinstance(src, Scope) else src\n            node.downstream.append(\n                Node(name=select.sql(comments=False), source=src_expr, expression=src_expr)\n            )\n\n    # Find all columns that went into creating this one to list their lineage nodes.\n    source_columns = set(find_all_in_scope(select, exp.Column))\n\n    # If the source is a UDTF find columns used in the UDTF to generate the table\n    if isinstance(source, exp.UDTF):\n        source_columns |= set(source.find_all(exp.Column))\n        derived_tables: Sequence[exp.Expr] = [\n            src.expression.parent\n            for src in scope.sources.values()\n            if isinstance(src, Scope) and src.is_derived_table and src.expression.parent\n        ]\n    else:\n        derived_tables = scope.derived_tables\n\n    source_names = {\n        dt.alias: dt.comments[0].split()[1]\n        for dt in derived_tables\n        if dt.comments and dt.comments[0].startswith(\"source: \")\n    }\n\n    pivots = scope.pivots\n    pivot = pivots[0] if len(pivots) == 1 and not pivots[0].unpivot else None\n    if pivot:\n        # For each aggregation function, the pivot creates a new column for each field in category\n        # combined with the aggfunc. So the columns parsed have this order: cat_a_value_sum, cat_a,\n        # b_value_sum, b. Because of this step wise manner the aggfunc 'sum(value) as value_sum'\n        # belongs to the column indices 0, 2, and the aggfunc 'max(price)' without an alias belongs\n        # to the column indices 1, 3. Here, only the columns used in the aggregations are of interest\n        # in the lineage, so lookup the pivot column name by index and map that with the columns used\n        # in the aggregation.\n        #\n        # Example: PIVOT (SUM(value) AS value_sum, MAX(price)) FOR category IN ('a' AS cat_a, 'b')\n        pivot_columns = pivot.args[\"columns\"]\n        pivot_aggs_count = len(pivot.expressions)\n\n        pivot_column_mapping = {}\n        for i, agg in enumerate(pivot.expressions):\n            agg_cols = list(agg.find_all(exp.Column))\n            for col_index in range(i, len(pivot_columns), pivot_aggs_count):\n                pivot_column_mapping[pivot_columns[col_index].name] = agg_cols\n\n    for c in source_columns:\n        table = c.table\n        col_source: t.Optional[exp.Table | Scope] = scope.sources.get(table)\n\n        if isinstance(col_source, Scope):\n            reference_node_name = None\n            if col_source.scope_type == ScopeType.DERIVED_TABLE and table not in source_names:\n                reference_node_name = table\n            elif col_source.scope_type == ScopeType.CTE:\n                selected_node, _ = scope.selected_sources.get(table, (None, None))\n                reference_node_name = selected_node.name if selected_node else None\n\n            # The table itself came from a more specific scope. Recurse into that one using the unaliased column name.\n            to_node(\n                c.name,\n                scope=col_source,\n                dialect=dialect,\n                scope_name=table,\n                upstream=node,\n                source_name=source_names.get(table) or source_name,\n                reference_node_name=reference_node_name,\n                trim_selects=trim_selects,\n                _cache=_cache,\n            )\n        elif pivot and pivot.alias_or_name == c.table:\n            downstream_columns = []\n\n            column_name = c.name\n            if any(column_name == pivot_column.name for pivot_column in pivot_columns):\n                downstream_columns.extend(pivot_column_mapping[column_name])\n            else:\n                # The column is not in the pivot, so it must be an implicit column of the\n                # pivoted source -- adapt column to be from the implicit pivoted source.\n                pivot_parent = pivot.parent\n                downstream_columns.append(\n                    exp.column(c.this, table=pivot_parent.alias_or_name if pivot_parent else \"\")\n                )\n\n            for downstream_column in downstream_columns:\n                table = downstream_column.table\n                col_source = scope.sources.get(table)\n                if isinstance(col_source, Scope):\n                    to_node(\n                        downstream_column.name,\n                        scope=col_source,\n                        scope_name=table,\n                        dialect=dialect,\n                        upstream=node,\n                        source_name=source_names.get(table) or source_name,\n                        reference_node_name=reference_node_name,\n                        trim_selects=trim_selects,\n                        _cache=_cache,\n                    )\n                else:\n                    col_expr = col_source or exp.Placeholder()\n                    node.downstream.append(\n                        Node(\n                            name=downstream_column.sql(comments=False),\n                            source=col_expr,\n                            expression=col_expr,\n                        )\n                    )\n        else:\n            # The source is not a scope and the column is not in any pivot - we've reached the end\n            # of the line. At this point, if a source is not found it means this column's lineage\n            # is unknown. This can happen if the definition of a source used in a query is not\n            # passed into the `sources` map.\n            col_expr = col_source or exp.Placeholder()\n            node.downstream.append(\n                Node(name=c.sql(comments=False), source=col_expr, expression=col_expr)\n            )\n\n    if _cache is not None:\n        _cache[cache_key] = node\n\n    return node\n\n\nclass GraphHTML:\n    \"\"\"Node to HTML generator using vis.js.\n\n    https://visjs.github.io/vis-network/docs/network/\n    \"\"\"\n\n    def __init__(\n        self, nodes: t.Dict, edges: t.List, imports: bool = True, options: t.Optional[t.Dict] = None\n    ):\n        self.imports = imports\n\n        self.options = {\n            \"height\": \"500px\",\n            \"width\": \"100%\",\n            \"layout\": {\n                \"hierarchical\": {\n                    \"enabled\": True,\n                    \"nodeSpacing\": 200,\n                    \"sortMethod\": \"directed\",\n                },\n            },\n            \"interaction\": {\n                \"dragNodes\": False,\n                \"selectable\": False,\n            },\n            \"physics\": {\n                \"enabled\": False,\n            },\n            \"edges\": {\n                \"arrows\": \"to\",\n            },\n            \"nodes\": {\n                \"font\": \"20px monaco\",\n                \"shape\": \"box\",\n                \"widthConstraint\": {\n                    \"maximum\": 300,\n                },\n            },\n            **(options or {}),\n        }\n\n        self.nodes = nodes\n        self.edges = edges\n\n    def __str__(self):\n        nodes = json.dumps(list(self.nodes.values()))\n        edges = json.dumps(self.edges)\n        options = json.dumps(self.options)\n        imports = (\n            \"\"\"<script type=\"text/javascript\" src=\"https://unpkg.com/vis-data@latest/peer/umd/vis-data.min.js\"></script>\n  <script type=\"text/javascript\" src=\"https://unpkg.com/vis-network@latest/peer/umd/vis-network.min.js\"></script>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"https://unpkg.com/vis-network/styles/vis-network.min.css\" />\"\"\"\n            if self.imports\n            else \"\"\n        )\n\n        return f\"\"\"<div>\n  <div id=\"sqlglot-lineage\"></div>\n  {imports}\n  <script type=\"text/javascript\">\n    var nodes = new vis.DataSet({nodes})\n    nodes.forEach(row => row[\"title\"] = new DOMParser().parseFromString(row[\"title\"], \"text/html\").body.childNodes[0])\n\n    new vis.Network(\n        document.getElementById(\"sqlglot-lineage\"),\n        {{\n            nodes: nodes,\n            edges: new vis.DataSet({edges})\n        }},\n        {options},\n    )\n  </script>\n</div>\"\"\"\n\n    def _repr_html_(self) -> str:\n        return self.__str__()\n"
  },
  {
    "path": "sqlglot/optimizer/__init__.py",
    "content": "# ruff: noqa: F401\n\nfrom sqlglot.optimizer.optimizer import RULES as RULES, optimize as optimize\nfrom sqlglot.optimizer.scope import (\n    Scope as Scope,\n    build_scope as build_scope,\n    find_all_in_scope as find_all_in_scope,\n    find_in_scope as find_in_scope,\n    traverse_scope as traverse_scope,\n    walk_in_scope as walk_in_scope,\n)\n"
  },
  {
    "path": "sqlglot/optimizer/annotate_types.py",
    "content": "from __future__ import annotations\n\nimport functools\nimport logging\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect\nfrom sqlglot.helper import (\n    ensure_list,\n    is_date_unit,\n    is_iso_date,\n    is_iso_datetime,\n    seq_get,\n)\nfrom sqlglot.optimizer.scope import Scope, traverse_scope\nfrom sqlglot.schema import MappingSchema, Schema, ensure_schema\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import B, E\n\n    BinaryCoercionFunc = t.Callable[[exp.Expr, exp.Expr], exp.DType]\n    BinaryCoercions = t.Dict[\n        t.Tuple[exp.DType, exp.DType],\n        BinaryCoercionFunc,\n    ]\n\n    from sqlglot.dialects.dialect import DialectType\n    from sqlglot.typing import ExprMetadataType\n\nlogger = logging.getLogger(\"sqlglot\")\n\n# EXTRACT/DATE_PART specifiers that return BIGINT instead of INT\nBIGINT_EXTRACT_DATE_PARTS = {\n    \"EPOCH_SECOND\",\n    \"EPOCH_MILLISECOND\",\n    \"EPOCH_MICROSECOND\",\n    \"EPOCH_NANOSECOND\",\n    \"NANOSECOND\",\n}\n\n\ndef annotate_types(\n    expression: E,\n    schema: t.Optional[t.Dict | Schema] = None,\n    expression_metadata: t.Optional[ExprMetadataType] = None,\n    coerces_to: t.Optional[t.Dict[exp.DType, t.Set[exp.DType]]] = None,\n    dialect: DialectType = None,\n    overwrite_types: bool = True,\n) -> E:\n    \"\"\"\n    Infers the types of an expression, annotating its AST accordingly.\n\n    Example:\n        >>> import sqlglot\n        >>> schema = {\"y\": {\"cola\": \"SMALLINT\"}}\n        >>> sql = \"SELECT x.cola + 2.5 AS cola FROM (SELECT y.cola AS cola FROM y AS y) AS x\"\n        >>> annotated_expr = annotate_types(sqlglot.parse_one(sql), schema=schema)\n        >>> annotated_expr.expressions[0].type.this  # Get the type of \"x.cola + 2.5 AS cola\"\n        <DType.DOUBLE: 'DOUBLE'>\n\n    Args:\n        expression: Expr to annotate.\n        schema: Database schema.\n        expression_metadata: Maps expression type to corresponding annotation function.\n        coerces_to: Maps expression type to set of types that it can be coerced into.\n        overwrite_types: Re-annotate the existing AST types.\n\n    Returns:\n        The expression annotated with types.\n    \"\"\"\n\n    schema = ensure_schema(schema, dialect=dialect)\n\n    return TypeAnnotator(\n        schema=schema,\n        expression_metadata=expression_metadata,\n        coerces_to=coerces_to,\n        overwrite_types=overwrite_types,\n    ).annotate(expression)\n\n\ndef _coerce_date_literal(l: exp.Expr, unit: t.Optional[exp.Expr]) -> exp.DType:\n    date_text = l.name\n    is_iso_date_ = is_iso_date(date_text)\n\n    if is_iso_date_ and is_date_unit(unit):\n        return exp.DType.DATE\n\n    # An ISO date is also an ISO datetime, but not vice versa\n    if is_iso_date_ or is_iso_datetime(date_text):\n        return exp.DType.DATETIME\n\n    return exp.DType.UNKNOWN\n\n\ndef _coerce_date(l: exp.Expr, unit: t.Optional[exp.Expr]) -> exp.DType:\n    if not is_date_unit(unit):\n        return exp.DType.DATETIME\n    return l.type.this if l.type else exp.DType.UNKNOWN\n\n\ndef swap_args(func: BinaryCoercionFunc) -> BinaryCoercionFunc:\n    @functools.wraps(func)\n    def _swapped(l: exp.Expr, r: exp.Expr) -> exp.DType:\n        return func(r, l)\n\n    return _swapped\n\n\ndef swap_all(coercions: BinaryCoercions) -> BinaryCoercions:\n    return {**coercions, **{(b, a): swap_args(func) for (a, b), func in coercions.items()}}\n\n\nclass _TypeAnnotator(type):\n    def __new__(cls, clsname, bases, attrs):\n        klass = super().__new__(cls, clsname, bases, attrs)\n\n        # Highest-to-lowest type precedence, as specified in Spark's docs (ANSI):\n        # https://spark.apache.org/docs/3.2.0/sql-ref-ansi-compliance.html\n        text_precedence = (\n            exp.DType.TEXT,\n            exp.DType.NVARCHAR,\n            exp.DType.VARCHAR,\n            exp.DType.NCHAR,\n            exp.DType.CHAR,\n        )\n        numeric_precedence = (\n            exp.DType.DECFLOAT,\n            exp.DType.DOUBLE,\n            exp.DType.FLOAT,\n            exp.DType.BIGDECIMAL,\n            exp.DType.DECIMAL,\n            exp.DType.BIGINT,\n            exp.DType.INT,\n            exp.DType.SMALLINT,\n            exp.DType.TINYINT,\n        )\n        timelike_precedence = (\n            exp.DType.TIMESTAMPLTZ,\n            exp.DType.TIMESTAMPTZ,\n            exp.DType.TIMESTAMP,\n            exp.DType.DATETIME,\n            exp.DType.DATE,\n        )\n\n        for type_precedence in (text_precedence, numeric_precedence, timelike_precedence):\n            coerces_to = set()\n            for data_type in type_precedence:\n                klass.COERCES_TO[data_type] = coerces_to.copy()\n                coerces_to |= {data_type}\n        return klass\n\n\nclass TypeAnnotator(metaclass=_TypeAnnotator):\n    NESTED_TYPES = {\n        exp.DType.ARRAY,\n    }\n\n    # Specifies what types a given type can be coerced into (autofilled)\n    COERCES_TO: t.Dict[exp.DType, t.Set[exp.DType]] = {}\n\n    # Coercion functions for binary operations.\n    # Map of type pairs to a callable that takes both sides of the binary operation and returns the resulting type.\n    BINARY_COERCIONS: BinaryCoercions = {\n        **swap_all(\n            {\n                (t, exp.DType.INTERVAL): lambda l, r: _coerce_date_literal(l, r.args.get(\"unit\"))\n                for t in exp.DataType.TEXT_TYPES\n            }\n        ),\n        **swap_all(\n            {\n                # text + numeric will yield the numeric type to match most dialects' semantics\n                (text, numeric): lambda l, r: t.cast(\n                    exp.DType, l.type if l.type in exp.DataType.NUMERIC_TYPES else r.type\n                )\n                for text in exp.DataType.TEXT_TYPES\n                for numeric in exp.DataType.NUMERIC_TYPES\n            }\n        ),\n        **swap_all(\n            {\n                (exp.DType.DATE, exp.DType.INTERVAL): lambda l, r: _coerce_date(\n                    l, r.args.get(\"unit\")\n                ),\n            }\n        ),\n    }\n\n    def __init__(\n        self,\n        schema: Schema,\n        expression_metadata: t.Optional[ExprMetadataType] = None,\n        coerces_to: t.Optional[t.Dict[exp.DType, t.Set[exp.DType]]] = None,\n        binary_coercions: t.Optional[BinaryCoercions] = None,\n        overwrite_types: bool = True,\n    ) -> None:\n        self.schema = schema\n        dialect = schema.dialect or Dialect()\n        self.dialect = dialect\n        self.expression_metadata = expression_metadata or dialect.EXPRESSION_METADATA\n        self.coerces_to = coerces_to or dialect.COERCES_TO or self.COERCES_TO\n        self.binary_coercions = binary_coercions or self.BINARY_COERCIONS\n\n        # Caches the ids of annotated sub-Exprs, to ensure we only visit them once\n        self._visited: t.Set[int] = set()\n\n        # Caches NULL-annotated expressions to set them to UNKNOWN after type inference is completed\n        self._null_expressions: t.Dict[int, exp.Expr] = {}\n\n        # Databricks and Spark ≥v3 actually support NULL (i.e., VOID) as a type\n        self._supports_null_type = dialect.SUPPORTS_NULL_TYPE\n\n        # Maps an exp.SetOperation's id (e.g. UNION) to its projection types. This is computed if the\n        # exp.SetOperation is the expression of a scope source, as selecting from it multiple times\n        # would reprocess the entire subtree to coerce the types of its operands' projections\n        self._setop_column_types: t.Dict[int, t.Dict[str, exp.DataType | exp.DType]] = {}\n\n        # When set to False, this enables partial annotation by skipping already-annotated nodes\n        self._overwrite_types = overwrite_types\n\n        # Maps Scope to its corresponding selected sources\n        self._scope_selects: t.Dict[Scope, t.Dict[str, t.Dict[str, t.Any]]] = {}\n\n    def clear(self) -> None:\n        self._visited.clear()\n        self._null_expressions.clear()\n        self._setop_column_types.clear()\n        self._scope_selects.clear()\n\n    def _set_type(self, expression: E, target_type: t.Optional[exp.DataType | exp.DType]) -> E:\n        prev_type = expression.type\n        expression_id = id(expression)\n\n        expression.type = target_type or exp.DType.UNKNOWN  # type: ignore\n        self._visited.add(expression_id)\n\n        if (\n            not self._supports_null_type\n            and t.cast(exp.DataType, expression.type).this == exp.DType.NULL\n        ):\n            self._null_expressions[expression_id] = expression\n        elif prev_type and t.cast(exp.DataType, prev_type).this == exp.DType.NULL:\n            self._null_expressions.pop(expression_id, None)\n\n        return expression\n\n    def annotate(self, expression: E, annotate_scope: bool = True) -> E:\n        # This flag is used to avoid costly scope traversals when we only care about annotating\n        # non-column expressions (partial type inference), e.g., when simplifying in the optimizer\n        if annotate_scope:\n            for scope in traverse_scope(expression):\n                self.annotate_scope(scope)\n\n        # This takes care of non-traversable expressions\n        self._annotate_expression(expression)\n\n        # Replace NULL type with the default type of the targeted dialect, since the former is not an actual type;\n        # it is mostly used to aid type coercion, e.g. in query set operations.\n        for expr in self._null_expressions.values():\n            expr.type = self.dialect.DEFAULT_NULL_TYPE\n\n        return expression\n\n    def _get_scope_selects(self, scope: Scope) -> t.Dict[str, t.Dict[str, t.Any]]:\n        if scope not in self._scope_selects:\n            selects = {}\n            for name, source in scope.sources.items():\n                if not isinstance(source, Scope):\n                    continue\n\n                expression = source.expression\n                if isinstance(expression, exp.UDTF):\n                    values = []\n\n                    if isinstance(expression, exp.Lateral):\n                        if isinstance(expression.this, exp.Explode):\n                            values = [expression.this.this]\n                    elif isinstance(expression, exp.Unnest):\n                        values = [expression]\n                    elif not isinstance(expression, exp.TableFromRows):\n                        values = expression.expressions[0].expressions\n\n                    if not values:\n                        continue\n\n                    alias_column_names = expression.alias_column_names\n\n                    if (\n                        isinstance(expression, exp.Unnest)\n                        and expression.type\n                        and expression.type.is_type(exp.DType.STRUCT)\n                    ):\n                        selects[name] = {\n                            col_def.name: t.cast(t.Union[exp.DataType, exp.DType], col_def.kind)\n                            for col_def in expression.type.expressions\n                            if isinstance(col_def, exp.ColumnDef) and col_def.kind\n                        }\n                    else:\n                        selects[name] = {\n                            alias: column.type for alias, column in zip(alias_column_names, values)\n                        }\n                elif isinstance(expression, exp.SetOperation) and len(\n                    expression.left.selects\n                ) == len(expression.right.selects):\n                    selects[name] = self._get_setop_column_types(expression)\n                elif isinstance(expression, exp.Selectable):\n                    selects[name] = {s.alias_or_name: s.type for s in expression.selects if s.type}\n\n            self._scope_selects[scope] = selects\n\n        return self._scope_selects[scope]\n\n    def annotate_scope(self, scope: Scope) -> None:\n        if isinstance(self.schema, MappingSchema):\n            for table_column in scope.table_columns:\n                source = scope.sources.get(table_column.name)\n\n                if isinstance(source, exp.Table):\n                    schema = self.schema.find(\n                        source, raise_on_missing=False, ensure_data_types=True\n                    )\n                    if not isinstance(schema, dict):\n                        continue\n\n                    struct_type = exp.DataType(\n                        this=exp.DType.STRUCT,\n                        expressions=[\n                            exp.ColumnDef(this=exp.to_identifier(c), kind=kind)\n                            for c, kind in schema.items()\n                        ],\n                        nested=True,\n                    )\n                    self._set_type(table_column, struct_type)\n                elif (\n                    isinstance(source, Scope)\n                    and isinstance(source.expression, exp.Query)\n                    and (\n                        source.expression.meta.get(\"query_type\") or exp.DataType.build(\"UNKNOWN\")\n                    ).is_type(exp.DType.STRUCT)\n                ):\n                    self._set_type(table_column, source.expression.meta[\"query_type\"])\n\n        # Iterate through all the expressions of the current scope in post-order, and annotate\n        self._annotate_expression(scope.expression, scope)\n\n        if self.dialect.QUERY_RESULTS_ARE_STRUCTS and isinstance(scope.expression, exp.Query):\n            struct_type = exp.DataType(\n                this=exp.DType.STRUCT,\n                expressions=[\n                    exp.ColumnDef(\n                        this=exp.to_identifier(select.output_name),\n                        kind=select.type.copy() if select.type else None,\n                    )\n                    for select in scope.expression.selects\n                ],\n                nested=True,\n            )\n\n            if not any(\n                cd.kind.is_type(exp.DType.UNKNOWN) for cd in struct_type.expressions if cd.kind\n            ):\n                # We don't use `_set_type` on purpose here. If we annotated the query directly, then\n                # using it in other contexts (e.g., ARRAY(<query>)) could result in incorrect type\n                # annotations, i.e., it shouldn't be interpreted as a STRUCT value.\n                scope.expression.meta[\"query_type\"] = struct_type\n\n    def _annotate_expression(\n        self,\n        expression: exp.Expr,\n        scope: t.Optional[Scope] = None,\n    ) -> None:\n        stack = [(expression, False)]\n\n        while stack:\n            expr, children_annotated = stack.pop()\n\n            if id(expr) in self._visited or (\n                not self._overwrite_types and expr.type and not expr.is_type(exp.DType.UNKNOWN)\n            ):\n                continue  # We've already inferred the expression's type\n\n            if not children_annotated:\n                stack.append((expr, True))\n                for child_expr in expr.iter_expressions():\n                    stack.append((child_expr, False))\n                continue\n\n            if scope and isinstance(expr, exp.Column) and expr.table:\n                source = None\n                source_scope: t.Optional[Scope] = scope\n                while source_scope and not source:\n                    source = source_scope.sources.get(expr.table)\n                    if not source:\n                        source_scope = source_scope.parent\n\n                if isinstance(source, exp.Table):\n                    self._set_type(expr, self.schema.get_column_type(source, expr))\n                elif source and source_scope:\n                    col_type = (\n                        self._get_scope_selects(source_scope).get(expr.table, {}).get(expr.name)\n                    )\n                    if col_type:\n                        self._set_type(expr, col_type)\n                    elif isinstance(source.expression, exp.Unnest):\n                        self._set_type(expr, source.expression.type)\n                    else:\n                        self._set_type(expr, exp.DType.UNKNOWN)\n                else:\n                    self._set_type(expr, exp.DType.UNKNOWN)\n\n                if expr.is_type(exp.DType.JSON) and (dot_parts := expr.meta.get(\"dot_parts\")):\n                    # JSON dot access is case sensitive across all dialects, so we need to undo the normalization.\n                    i = iter(dot_parts)\n                    parent = expr.parent\n                    while isinstance(parent, exp.Dot):\n                        parent.expression.replace(exp.to_identifier(next(i), quoted=True))\n                        parent = parent.parent\n\n                    expr.meta.pop(\"dot_parts\", None)\n\n                if expr.type and expr.type.args.get(\"nullable\") is False:\n                    expr.meta[\"nonnull\"] = True\n                continue\n\n            spec = self.expression_metadata.get(expr.__class__)\n\n            if spec and (annotator := spec.get(\"annotator\")):\n                annotator(self, expr)\n            elif spec and (returns := spec.get(\"returns\")):\n                self._set_type(expr, t.cast(exp.DType, returns))\n            else:\n                self._set_type(expr, exp.DType.UNKNOWN)\n\n    def _maybe_coerce(\n        self,\n        type1: exp.DataType | exp.DType,\n        type2: exp.DataType | exp.DType,\n    ) -> exp.DataType | exp.DType:\n        \"\"\"\n        Returns type2 if type1 can be coerced into it, otherwise type1.\n\n        If either type is parameterized (e.g. DECIMAL(18, 2) contains two parameters),\n        we assume type1 does not coerce into type2, so we also return it in this case.\n        \"\"\"\n        if isinstance(type1, exp.DataType):\n            if type1.expressions:\n                return type1\n            type1_value = type1.this\n        else:\n            type1_value = type1\n\n        if isinstance(type2, exp.DataType):\n            if type2.expressions:\n                return type2\n            type2_value = type2.this\n        else:\n            type2_value = type2\n\n        # We propagate the UNKNOWN type upwards if found\n        if exp.DType.UNKNOWN in (type1_value, type2_value):\n            return exp.DType.UNKNOWN\n\n        if type1_value == exp.DType.NULL:\n            return type2_value\n        if type2_value == exp.DType.NULL:\n            return type1_value\n\n        return type2_value if type2_value in self.coerces_to.get(type1_value, {}) else type1_value\n\n    def _get_setop_column_types(\n        self, setop: exp.SetOperation\n    ) -> t.Dict[str, exp.DataType | exp.DType]:\n        \"\"\"\n        Computes and returns the coerced column types for a SetOperation.\n\n        This handles UNION, INTERSECT, EXCEPT, etc., coercing types across\n        left and right operands for all projections/columns.\n\n        Args:\n            setop: The SetOperation expression to analyze\n\n        Returns:\n            Dictionary mapping column names to their coerced types\n        \"\"\"\n        setop_id = id(setop)\n        if setop_id in self._setop_column_types:\n            return self._setop_column_types[setop_id]\n\n        col_types: t.Dict[str, exp.DataType | exp.DType] = {}\n\n        # Validate that left and right have same number of projections\n        if not (\n            isinstance(setop, exp.SetOperation)\n            and setop.left.selects\n            and setop.right.selects\n            and len(setop.left.selects) == len(setop.right.selects)\n        ):\n            return col_types\n\n        # Process a chain / sub-tree of set operations\n        for set_op in setop.walk(\n            prune=lambda n: not isinstance(n, (exp.SetOperation, exp.Subquery))\n        ):\n            if not isinstance(set_op, exp.SetOperation):\n                continue\n\n            if set_op.args.get(\"by_name\"):\n                r_type_by_select = {s.alias_or_name: s.type for s in set_op.right.selects}\n                setop_cols = {\n                    s.alias_or_name: self._maybe_coerce(\n                        t.cast(exp.DataType, s.type),\n                        r_type_by_select.get(s.alias_or_name) or exp.DType.UNKNOWN,\n                    )\n                    for s in set_op.left.selects\n                }\n            else:\n                setop_cols = {\n                    ls.alias_or_name: self._maybe_coerce(\n                        t.cast(exp.DataType, ls.type), t.cast(exp.DataType, rs.type)\n                    )\n                    for ls, rs in zip(set_op.left.selects, set_op.right.selects)\n                }\n\n            # Coerce intermediate results with the previously registered types, if they exist\n            for col_name, col_type in setop_cols.items():\n                col_types[col_name] = self._maybe_coerce(\n                    col_type, col_types.get(col_name, exp.DType.NULL)\n                )\n\n        self._setop_column_types[setop_id] = col_types\n        return col_types\n\n    def _annotate_binary(self, expression: B) -> B:\n        left, right = expression.left, expression.right\n        if not left or not right:\n            expression_sql = expression.sql(self.dialect)\n            logger.warning(f\"Failed to annotate badly formed binary expression: {expression_sql}\")\n            self._set_type(expression, None)\n            return expression\n\n        left_type, right_type = left.type.this, right.type.this  # type: ignore\n\n        if isinstance(expression, (exp.Connector, exp.Predicate)):\n            self._set_type(expression, exp.DType.BOOLEAN)\n        elif (left_type, right_type) in self.binary_coercions:\n            self._set_type(expression, self.binary_coercions[(left_type, right_type)](left, right))\n        else:\n            self._annotate_by_args(expression, left, right)\n\n        if isinstance(expression, exp.Is) or (\n            left.meta.get(\"nonnull\") is True and right.meta.get(\"nonnull\") is True\n        ):\n            expression.meta[\"nonnull\"] = True\n\n        return expression\n\n    def _annotate_unary(self, expression: E) -> E:\n        if isinstance(expression, exp.Not):\n            self._set_type(expression, exp.DType.BOOLEAN)\n        else:\n            self._set_type(expression, expression.this.type)\n\n        if expression.this.meta.get(\"nonnull\") is True:\n            expression.meta[\"nonnull\"] = True\n\n        return expression\n\n    def _annotate_literal(self, expression: exp.Literal) -> exp.Literal:\n        if expression.is_string:\n            self._set_type(expression, exp.DType.VARCHAR)\n        elif expression.is_int:\n            self._set_type(expression, exp.DType.INT)\n        else:\n            self._set_type(expression, exp.DType.DOUBLE)\n\n        expression.meta[\"nonnull\"] = True\n\n        return expression\n\n    @t.no_type_check\n    def _annotate_by_args(\n        self,\n        expression: E,\n        *args: str | exp.Expr,\n        promote: bool = False,\n        array: bool = False,\n    ) -> E:\n        literal_type = None\n        non_literal_type = None\n        nested_type = None\n\n        for arg in args:\n            if isinstance(arg, str):\n                expressions = expression.args.get(arg)\n            else:\n                expressions = arg\n\n            for expr in ensure_list(expressions):\n                expr_type = expr.type\n\n                # Stop at the first nested data type found - we don't want to _maybe_coerce nested types\n                if expr_type.args.get(\"nested\"):\n                    nested_type = expr_type\n                    break\n\n                if isinstance(expr, exp.Literal):\n                    literal_type = self._maybe_coerce(literal_type or expr_type, expr_type)\n                else:\n                    non_literal_type = self._maybe_coerce(non_literal_type or expr_type, expr_type)\n\n            if nested_type:\n                break\n\n        result_type = None\n\n        if nested_type:\n            result_type = nested_type\n        elif literal_type and non_literal_type:\n            if self.dialect.PRIORITIZE_NON_LITERAL_TYPES:\n                literal_this_type = (\n                    literal_type.this if isinstance(literal_type, exp.DataType) else literal_type\n                )\n                non_literal_this_type = (\n                    non_literal_type.this\n                    if isinstance(non_literal_type, exp.DataType)\n                    else non_literal_type\n                )\n                if (\n                    literal_this_type in exp.DataType.INTEGER_TYPES\n                    and non_literal_this_type in exp.DataType.INTEGER_TYPES\n                ) or (\n                    literal_this_type in exp.DataType.REAL_TYPES\n                    and non_literal_this_type in exp.DataType.REAL_TYPES\n                ):\n                    result_type = non_literal_type\n        else:\n            result_type = literal_type or non_literal_type or exp.DType.UNKNOWN\n\n        self._set_type(\n            expression, result_type or self._maybe_coerce(non_literal_type, literal_type)\n        )\n\n        if promote:\n            if expression.type.this in exp.DataType.INTEGER_TYPES:\n                self._set_type(expression, exp.DType.BIGINT)\n            elif expression.type.this in exp.DataType.FLOAT_TYPES:\n                self._set_type(expression, exp.DType.DOUBLE)\n\n        if array:\n            self._set_type(\n                expression,\n                exp.DataType(this=exp.DType.ARRAY, expressions=[expression.type], nested=True),\n            )\n\n        return expression\n\n    def _annotate_timeunit(\n        self, expression: exp.TimeUnit | exp.DateTrunc\n    ) -> exp.TimeUnit | exp.DateTrunc:\n        if expression.this.type.this in exp.DataType.TEXT_TYPES:\n            datatype = _coerce_date_literal(expression.this, expression.unit)\n        elif expression.this.type.this in exp.DataType.TEMPORAL_TYPES:\n            datatype = _coerce_date(expression.this, expression.unit)\n        else:\n            datatype = exp.DType.UNKNOWN\n\n        self._set_type(expression, datatype)\n        return expression\n\n    def _annotate_bracket(self, expression: exp.Bracket) -> exp.Bracket:\n        bracket_arg = expression.expressions[0]\n        this = expression.this\n\n        if isinstance(bracket_arg, exp.Slice):\n            self._set_type(expression, this.type)\n        elif this.type.is_type(exp.DType.ARRAY):\n            self._set_type(expression, seq_get(this.type.expressions, 0))\n        elif isinstance(this, (exp.Map, exp.VarMap)) and bracket_arg in this.keys:\n            index = this.keys.index(bracket_arg)\n            value = seq_get(this.values, index)\n            self._set_type(expression, value.type if value else None)\n        else:\n            self._set_type(expression, exp.DType.UNKNOWN)\n\n        return expression\n\n    def _annotate_div(self, expression: exp.Div) -> exp.Div:\n        left_type, right_type = expression.left.type.this, expression.right.type.this  # type: ignore\n\n        if (\n            expression.args.get(\"typed\")\n            and left_type in exp.DataType.INTEGER_TYPES\n            and right_type in exp.DataType.INTEGER_TYPES\n        ):\n            self._set_type(expression, exp.DType.BIGINT)\n        else:\n            self._set_type(expression, self._maybe_coerce(left_type, right_type))\n            if expression.type and expression.type.this not in exp.DataType.REAL_TYPES:\n                self._set_type(expression, self._maybe_coerce(expression.type, exp.DType.DOUBLE))\n\n        return expression\n\n    def _annotate_dot(self, expression: exp.Dot) -> exp.Dot:\n        self._set_type(expression, None)\n\n        # Propagate type from qualified UDF calls (e.g., db.my_udf(...))\n        if isinstance(expression.expression, exp.Anonymous):\n            self._set_type(expression, expression.expression.type)\n            return expression\n\n        this_type = expression.this.type\n\n        if this_type and this_type.is_type(exp.DType.STRUCT):\n            for e in this_type.expressions:\n                if e.name == expression.expression.name:\n                    self._set_type(expression, e.kind)\n                    break\n\n        return expression\n\n    def _annotate_explode(self, expression: exp.Explode) -> exp.Explode:\n        self._set_type(expression, seq_get(expression.this.type.expressions, 0))\n        return expression\n\n    def _annotate_unnest(self, expression: exp.Unnest) -> exp.Unnest:\n        child = seq_get(expression.expressions, 0)\n\n        if child and child.is_type(exp.DType.ARRAY):\n            expr_type = seq_get(child.type.expressions, 0)\n        else:\n            expr_type = None\n\n        self._set_type(expression, expr_type)\n        return expression\n\n    def _annotate_subquery(self, expression: exp.Subquery) -> exp.Subquery:\n        # For scalar subqueries (subqueries with a single projection), infer the type\n        # from that single projection. This allows type propagation in cases like:\n        # SELECT (SELECT 1 AS c) AS c\n        query = expression.unnest()\n\n        if isinstance(query, exp.Query):\n            selects = query.selects\n            if len(selects) == 1:\n                self._set_type(expression, selects[0].type)\n                return expression\n\n        self._set_type(expression, exp.DType.UNKNOWN)\n        return expression\n\n    def _annotate_struct_value(\n        self, expression: exp.Expr\n    ) -> t.Optional[exp.DataType] | exp.ColumnDef:\n        # Case: STRUCT(key AS value)\n        this: t.Optional[exp.Expr] = None\n        kind = expression.type\n\n        if alias := expression.args.get(\"alias\"):\n            this = alias.copy()\n        elif expression.expression:\n            # Case: STRUCT(key = value) or STRUCT(key := value)\n            this = expression.this.copy()\n            kind = expression.expression.type\n        elif isinstance(expression, exp.Column):\n            # Case: STRUCT(c)\n            this = expression.this.copy()\n\n        if kind and kind.is_type(exp.DType.UNKNOWN):\n            return None\n\n        if this:\n            return exp.ColumnDef(this=this, kind=kind)\n\n        return kind\n\n    def _annotate_struct(self, expression: exp.Struct) -> exp.Struct:\n        expressions = []\n        for expr in expression.expressions:\n            struct_field_type = self._annotate_struct_value(expr)\n            if struct_field_type is None:\n                self._set_type(expression, None)\n                return expression\n\n            expressions.append(struct_field_type)\n\n        self._set_type(\n            expression,\n            exp.DataType(this=exp.DType.STRUCT, expressions=expressions, nested=True),\n        )\n        return expression\n\n    @t.overload\n    def _annotate_map(self, expression: exp.Map) -> exp.Map: ...\n\n    @t.overload\n    def _annotate_map(self, expression: exp.VarMap) -> exp.VarMap: ...\n\n    def _annotate_map(self, expression):\n        keys = expression.args.get(\"keys\")\n        values = expression.args.get(\"values\")\n\n        map_type = exp.DataType(this=exp.DType.MAP)\n        if isinstance(keys, exp.Array) and isinstance(values, exp.Array):\n            key_type = seq_get(keys.type.expressions, 0) or exp.DType.UNKNOWN\n            value_type = seq_get(values.type.expressions, 0) or exp.DType.UNKNOWN\n\n            if key_type != exp.DType.UNKNOWN and value_type != exp.DType.UNKNOWN:\n                map_type.set(\"expressions\", [key_type, value_type])\n                map_type.set(\"nested\", True)\n\n        self._set_type(expression, map_type)\n        return expression\n\n    def _annotate_to_map(self, expression: exp.ToMap) -> exp.ToMap:\n        map_type = exp.DataType(this=exp.DType.MAP)\n        arg = expression.this\n        if arg.is_type(exp.DType.STRUCT):\n            for coldef in arg.type.expressions:\n                kind = coldef.kind\n                if kind != exp.DType.UNKNOWN:\n                    map_type.set(\"expressions\", [exp.DataType.build(\"varchar\"), kind])\n                    map_type.set(\"nested\", True)\n                    break\n\n        self._set_type(expression, map_type)\n        return expression\n\n    def _annotate_extract(self, expression: exp.Extract) -> exp.Extract:\n        part = expression.name\n        if part == \"TIME\":\n            self._set_type(expression, exp.DType.TIME)\n        elif part == \"DATE\":\n            self._set_type(expression, exp.DType.DATE)\n        elif part in BIGINT_EXTRACT_DATE_PARTS:\n            self._set_type(expression, exp.DType.BIGINT)\n        else:\n            self._set_type(expression, exp.DType.INT)\n        return expression\n\n    def _annotate_by_array_element(self, expression: exp.Expr) -> exp.Expr:\n        array_arg = expression.this\n        if array_arg.type.is_type(exp.DType.ARRAY):\n            element_type = seq_get(array_arg.type.expressions, 0) or exp.DType.UNKNOWN\n            self._set_type(expression, element_type)\n        else:\n            self._set_type(expression, exp.DType.UNKNOWN)\n\n        return expression\n"
  },
  {
    "path": "sqlglot/optimizer/canonicalize.py",
    "content": "from __future__ import annotations\n\nimport itertools\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect, DialectType\nfrom sqlglot.helper import is_date_unit, is_iso_date, is_iso_datetime\nfrom sqlglot.optimizer.annotate_types import TypeAnnotator\n\n\ndef canonicalize(expression: exp.Expr, dialect: DialectType = None) -> exp.Expr:\n    \"\"\"Converts a sql expression into a standard form.\n\n    This method relies on annotate_types because many of the\n    conversions rely on type inference.\n\n    Args:\n        expression: The expression to canonicalize.\n    \"\"\"\n\n    _dialect = Dialect.get_or_raise(dialect)\n\n    def _canonicalize(expression: exp.Expr) -> exp.Expr:\n        if not isinstance(expression, _CANONICALIZE_TYPES):\n            return expression\n        expression = add_text_to_concat(expression)\n        expression = replace_date_funcs(expression, dialect=_dialect)\n        expression = coerce_type(expression, _dialect.PROMOTE_TO_INFERRED_DATETIME_TYPE)\n        expression = remove_redundant_casts(expression)\n        expression = ensure_bools(expression, _replace_int_predicate)\n        expression = remove_ascending_order(expression)\n        return expression\n\n    return exp.replace_tree(expression, _canonicalize)\n\n\nCOERCIBLE_DATE_OPS = (\n    exp.Add,\n    exp.Sub,\n    exp.EQ,\n    exp.NEQ,\n    exp.GT,\n    exp.GTE,\n    exp.LT,\n    exp.LTE,\n    exp.NullSafeEQ,\n    exp.NullSafeNEQ,\n)\n\n\n# All expression types that any of the canonicalize functions can act on\n_CANONICALIZE_TYPES = tuple(\n    {\n        # add_text_to_concat\n        exp.Add,\n        # replace_date_funcs\n        exp.Date,\n        exp.TsOrDsToDate,\n        exp.Timestamp,\n        # coerce_type (COERCIBLE_DATE_OPS + Between, Extract, DateAdd, DateSub, DateTrunc, DateDiff)\n        *COERCIBLE_DATE_OPS,\n        exp.Between,\n        exp.Extract,\n        exp.DateAdd,\n        exp.DateSub,\n        exp.DateTrunc,\n        exp.DateDiff,\n        # remove_redundant_casts\n        exp.Cast,\n        # ensure_bools (Connector, Not, If, Where, Having)\n        exp.Connector,\n        exp.Not,\n        exp.If,\n        exp.Where,\n        exp.Having,\n        # remove_ascending_order\n        exp.Ordered,\n    }\n)\n\n\ndef add_text_to_concat(node: exp.Expr) -> exp.Expr:\n    if isinstance(node, exp.Add) and node.type and node.type.this in exp.DataType.TEXT_TYPES:\n        node = exp.Concat(\n            expressions=[node.left, node.right],\n            # All known dialects, i.e. Redshift and T-SQL, that support\n            # concatenating strings with the + operator do not coalesce NULLs.\n            coalesce=False,\n        )\n    return node\n\n\ndef replace_date_funcs(node: exp.Expr, dialect: DialectType) -> exp.Expr:\n    if (\n        isinstance(node, (exp.Date, exp.TsOrDsToDate))\n        and not node.expressions\n        and not node.args.get(\"zone\")\n        and node.this.is_string\n        and is_iso_date(node.this.name)\n    ):\n        return exp.cast(node.this, to=exp.DType.DATE)\n    if isinstance(node, exp.Timestamp) and not node.args.get(\"zone\"):\n        if not node.type:\n            from sqlglot.optimizer.annotate_types import annotate_types\n\n            node = annotate_types(node, dialect=dialect)\n        return exp.cast(node.this, to=node.type or exp.DType.TIMESTAMP)\n\n    return node\n\n\ndef coerce_type(node: exp.Expr, promote_to_inferred_datetime_type: bool) -> exp.Expr:\n    if isinstance(node, COERCIBLE_DATE_OPS):\n        _coerce_date(node.left, node.right, promote_to_inferred_datetime_type)\n    elif isinstance(node, exp.Between):\n        _coerce_date(node.this, node.args[\"low\"], promote_to_inferred_datetime_type)\n    elif isinstance(node, exp.Extract) and not node.expression.is_type(\n        *exp.DataType.TEMPORAL_TYPES\n    ):\n        _replace_cast(node.expression, exp.DType.DATETIME)\n    elif isinstance(node, (exp.DateAdd, exp.DateSub, exp.DateTrunc)):\n        _coerce_timeunit_arg(node.this, node.unit)\n    elif isinstance(node, exp.DateDiff):\n        _coerce_datediff_args(node)\n\n    return node\n\n\ndef remove_redundant_casts(expression: exp.Expr) -> exp.Expr:\n    if (\n        isinstance(expression, exp.Cast)\n        and expression.this.type\n        and expression.to == expression.this.type\n    ):\n        return expression.this\n\n    if (\n        isinstance(expression, (exp.Date, exp.TsOrDsToDate))\n        and expression.this.type\n        and expression.this.type.this == exp.DType.DATE\n        and not expression.this.type.expressions\n    ):\n        return expression.this\n\n    return expression\n\n\ndef ensure_bools(expression: exp.Expr, replace_func: t.Callable[[exp.Expr], None]) -> exp.Expr:\n    if isinstance(expression, exp.Connector):\n        replace_func(expression.left)\n        replace_func(expression.right)\n    elif isinstance(expression, exp.Not):\n        replace_func(expression.this)\n        # We can't replace num in CASE x WHEN num ..., because it's not the full predicate\n    elif isinstance(expression, exp.If) and not (\n        isinstance(expression.parent, exp.Case) and expression.parent.this\n    ):\n        replace_func(expression.this)\n    elif isinstance(expression, (exp.Where, exp.Having)):\n        replace_func(expression.this)\n\n    return expression\n\n\ndef remove_ascending_order(expression: exp.Expr) -> exp.Expr:\n    if isinstance(expression, exp.Ordered) and expression.args.get(\"desc\") is False:\n        # Convert ORDER BY a ASC to ORDER BY a\n        expression.set(\"desc\", None)\n\n    return expression\n\n\ndef _coerce_date(\n    a: exp.Expr,\n    b: exp.Expr,\n    promote_to_inferred_datetime_type: bool,\n) -> None:\n    for a, b in itertools.permutations([a, b]):\n        if isinstance(b, exp.Interval):\n            a = _coerce_timeunit_arg(a, b.unit)\n\n        a_type = a.type\n        if (\n            not a_type\n            or a_type.this not in exp.DataType.TEMPORAL_TYPES\n            or not b.type\n            or b.type.this not in exp.DataType.TEXT_TYPES\n        ):\n            continue\n\n        if promote_to_inferred_datetime_type:\n            if b.is_string:\n                date_text = b.name\n                if is_iso_date(date_text):\n                    b_type = exp.DType.DATE\n                elif is_iso_datetime(date_text):\n                    b_type = exp.DType.DATETIME\n                else:\n                    b_type = a_type.this\n            else:\n                # If b is not a datetime string, we conservatively promote it to a DATETIME,\n                # in order to ensure there are no surprising truncations due to downcasting\n                b_type = exp.DType.DATETIME\n\n            target_type = (\n                b_type if b_type in TypeAnnotator.COERCES_TO.get(a_type.this, {}) else a_type\n            )\n        else:\n            target_type = a_type\n\n        if target_type != a_type:\n            _replace_cast(a, target_type)\n\n        _replace_cast(b, target_type)\n\n\ndef _coerce_timeunit_arg(arg: exp.Expr, unit: t.Optional[exp.Expr]) -> exp.Expr:\n    if not arg.type:\n        return arg\n\n    if arg.type.this in exp.DataType.TEXT_TYPES:\n        date_text = arg.name\n        is_iso_date_ = is_iso_date(date_text)\n\n        if is_iso_date_ and is_date_unit(unit):\n            return arg.replace(exp.cast(arg.copy(), to=exp.DType.DATE))\n\n        # An ISO date is also an ISO datetime, but not vice versa\n        if is_iso_date_ or is_iso_datetime(date_text):\n            return arg.replace(exp.cast(arg.copy(), to=exp.DType.DATETIME))\n\n    elif arg.type.this == exp.DType.DATE and not is_date_unit(unit):\n        return arg.replace(exp.cast(arg.copy(), to=exp.DType.DATETIME))\n\n    return arg\n\n\ndef _coerce_datediff_args(node: exp.DateDiff) -> None:\n    for e in (node.this, node.expression):\n        if e.type.this not in exp.DataType.TEMPORAL_TYPES:\n            e.replace(exp.cast(e.copy(), to=exp.DType.DATETIME))\n\n\ndef _replace_cast(node: exp.Expr, to: exp.DATA_TYPE) -> None:\n    node.replace(exp.cast(node.copy(), to=to))\n\n\n# this was originally designed for presto, there is a similar transform for tsql\n# this is different in that it only operates on int types, this is because\n# presto has a boolean type whereas tsql doesn't (people use bits)\n# with y as (select true as x) select x = 0 FROM y -- illegal presto query\ndef _replace_int_predicate(expression: exp.Expr) -> None:\n    if isinstance(expression, exp.Coalesce):\n        for child in expression.iter_expressions():\n            _replace_int_predicate(child)\n    elif expression.type and expression.type.this in exp.DataType.INTEGER_TYPES:\n        expression.replace(expression.neq(0))\n"
  },
  {
    "path": "sqlglot/optimizer/eliminate_ctes.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.optimizer.scope import Scope, build_scope\n\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n\n\ndef eliminate_ctes(expression: E) -> E:\n    \"\"\"\n    Remove unused CTEs from an expression.\n\n    Example:\n        >>> import sqlglot\n        >>> sql = \"WITH y AS (SELECT a FROM x) SELECT a FROM z\"\n        >>> expression = sqlglot.parse_one(sql)\n        >>> eliminate_ctes(expression).sql()\n        'SELECT a FROM z'\n\n    Args:\n        expression (sqlglot.Expr): expression to optimize\n    Returns:\n        sqlglot.Expr: optimized expression\n    \"\"\"\n    root = build_scope(expression)\n\n    if root:\n        ref_count = root.ref_count()\n\n        # Traverse the scope tree in reverse so we can remove chains of unused CTEs\n        for scope in reversed(list(root.traverse())):\n            if scope.is_cte:\n                count = ref_count[id(scope)]\n                if count <= 0:\n                    cte_node = scope.expression.parent\n                    if not cte_node:\n                        continue\n                    with_node = cte_node.parent\n                    cte_node.pop()\n\n                    # Pop the entire WITH clause if this is the last CTE\n                    if with_node and len(with_node.expressions) <= 0:\n                        with_node.pop()\n\n                    # Decrement the ref count for all sources this CTE selects from\n                    for _, source in scope.selected_sources.values():\n                        if isinstance(source, Scope):\n                            ref_count[id(source)] -= 1\n\n    return expression\n"
  },
  {
    "path": "sqlglot/optimizer/eliminate_joins.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import expressions as exp\nfrom sqlglot.optimizer.normalize import normalized\nfrom sqlglot.optimizer.scope import Scope, traverse_scope\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n\n\ndef eliminate_joins(expression: E) -> E:\n    \"\"\"\n    Remove unused joins from an expression.\n\n    This only removes joins when we know that the join condition doesn't produce duplicate rows.\n\n    Example:\n        >>> import sqlglot\n        >>> sql = \"SELECT x.a FROM x LEFT JOIN (SELECT DISTINCT y.b FROM y) AS y ON x.b = y.b\"\n        >>> expression = sqlglot.parse_one(sql)\n        >>> eliminate_joins(expression).sql()\n        'SELECT x.a FROM x'\n\n    Args:\n        expression: expression to optimize\n\n    Returns:\n        The optimized expression\n    \"\"\"\n    for scope in traverse_scope(expression):\n        joins = scope.expression.args.get(\"joins\", [])\n        if not joins:\n            continue\n\n        # If any columns in this scope aren't qualified, it's hard to determine if a join isn't used.\n        # It's probably possible to infer this from the outputs of derived tables.\n        # But for now, let's just skip this rule.\n        if scope.unqualified_columns:\n            continue\n\n        # Reverse the joins so we can remove chains of unused joins\n        for join in reversed(joins):\n            if join.is_semi_or_anti_join:\n                continue\n\n            alias = join.alias_or_name\n            if _should_eliminate_join(scope, join, alias):\n                join.pop()\n                scope.remove_source(alias)\n\n    return expression\n\n\ndef _should_eliminate_join(scope, join, alias):\n    inner_source = scope.sources.get(alias)\n    return (\n        isinstance(inner_source, Scope)\n        and not _join_is_used(scope, join, alias)\n        and (\n            (join.side == \"LEFT\" and _is_joined_on_all_unique_outputs(inner_source, join))\n            or (not join.args.get(\"on\") and _has_single_output_row(inner_source))\n        )\n    )\n\n\ndef _join_is_used(scope, join, alias):\n    # We need to find all columns that reference this join.\n    # But columns in the ON clause shouldn't count.\n    on = join.args.get(\"on\")\n    if on:\n        on_clause_columns = {id(column) for column in on.find_all(exp.Column)}\n    else:\n        on_clause_columns = set()\n    return any(\n        column for column in scope.source_columns(alias) if id(column) not in on_clause_columns\n    )\n\n\ndef _is_joined_on_all_unique_outputs(scope, join):\n    unique_outputs = _unique_outputs(scope)\n    if not unique_outputs:\n        return False\n\n    _, join_keys, _ = join_condition(join)\n    remaining_unique_outputs = unique_outputs - {c.name for c in join_keys}\n    return not remaining_unique_outputs\n\n\ndef _unique_outputs(scope):\n    \"\"\"Determine output columns of `scope` that must have a unique combination per row\"\"\"\n    if scope.expression.args.get(\"distinct\"):\n        return set(scope.expression.named_selects)\n\n    group = scope.expression.args.get(\"group\")\n    if group:\n        grouped_expressions = set(group.expressions)\n        grouped_outputs = set()\n\n        unique_outputs = set()\n        for select in scope.expression.selects:\n            output = select.unalias()\n            if output in grouped_expressions:\n                grouped_outputs.add(output)\n                unique_outputs.add(select.alias_or_name)\n\n        # All the grouped expressions must be in the output\n        if not grouped_expressions.difference(grouped_outputs):\n            return unique_outputs\n        else:\n            return set()\n\n    if _has_single_output_row(scope):\n        return set(scope.expression.named_selects)\n\n    return set()\n\n\ndef _has_single_output_row(scope):\n    return isinstance(scope.expression, exp.Select) and (\n        all(isinstance(e.unalias(), exp.AggFunc) for e in scope.expression.selects)\n        or _is_limit_1(scope)\n        or not scope.expression.args.get(\"from_\")\n    )\n\n\ndef _is_limit_1(scope):\n    limit = scope.expression.args.get(\"limit\")\n    return limit and limit.expression.this == \"1\"\n\n\ndef join_condition(join):\n    \"\"\"\n    Extract the join condition from a join expression.\n\n    Args:\n        join (exp.Join)\n    Returns:\n        tuple[list[str], list[str], exp.Expr]:\n            Tuple of (source key, join key, remaining predicate)\n    \"\"\"\n    name = join.alias_or_name\n    on = (join.args.get(\"on\") or exp.true()).copy()\n    source_key = []\n    join_key = []\n\n    def extract_condition(condition):\n        left, right = condition.unnest_operands()\n        left_tables = exp.column_table_names(left)\n        right_tables = exp.column_table_names(right)\n\n        if name in left_tables and name not in right_tables:\n            join_key.append(left)\n            source_key.append(right)\n            condition.replace(exp.true())\n        elif name in right_tables and name not in left_tables:\n            join_key.append(right)\n            source_key.append(left)\n            condition.replace(exp.true())\n\n    # find the join keys\n    # SELECT\n    # FROM x\n    # JOIN y\n    #   ON x.a = y.b AND y.b > 1\n    #\n    # should pull y.b as the join key and x.a as the source key\n    if normalized(on):\n        on = on if isinstance(on, exp.And) else exp.and_(on, exp.true(), copy=False)\n\n        for condition in on.flatten():\n            if isinstance(condition, exp.EQ):\n                extract_condition(condition)\n    elif normalized(on, dnf=True):\n        conditions = None\n\n        for condition in on.flatten():\n            parts = [part for part in condition.flatten() if isinstance(part, exp.EQ)]\n            if conditions is None:\n                conditions = parts\n            else:\n                temp = []\n                for p in parts:\n                    cs = [c for c in conditions if p == c]\n\n                    if cs:\n                        temp.append(p)\n                        temp.extend(cs)\n                conditions = temp\n\n        for condition in conditions:\n            extract_condition(condition)\n\n    return source_key, join_key, on\n"
  },
  {
    "path": "sqlglot/optimizer/eliminate_subqueries.py",
    "content": "from __future__ import annotations\n\nimport itertools\nimport typing as t\n\nfrom sqlglot import expressions as exp\nfrom sqlglot.helper import find_new_name\nfrom sqlglot.optimizer.scope import Scope, build_scope\n\nif t.TYPE_CHECKING:\n    ExistingCTEsMapping = t.Dict[exp.Expr, str]\n    TakenNameMapping = t.Dict[str, t.Union[Scope, exp.Expr]]\n\n\ndef eliminate_subqueries(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Rewrite derived tables as CTES, deduplicating if possible.\n\n    Example:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one(\"SELECT a FROM (SELECT * FROM x) AS y\")\n        >>> eliminate_subqueries(expression).sql()\n        'WITH y AS (SELECT * FROM x) SELECT a FROM y AS y'\n\n    This also deduplicates common subqueries:\n        >>> expression = sqlglot.parse_one(\"SELECT a FROM (SELECT * FROM x) AS y CROSS JOIN (SELECT * FROM x) AS z\")\n        >>> eliminate_subqueries(expression).sql()\n        'WITH y AS (SELECT * FROM x) SELECT a FROM y AS y CROSS JOIN y AS z'\n\n    Args:\n        expression (sqlglot.Expr): expression\n    Returns:\n        sqlglot.Expr: expression\n    \"\"\"\n    if isinstance(expression, exp.Subquery):\n        # It's possible to have subqueries at the root, e.g. (SELECT * FROM x) LIMIT 1\n        eliminate_subqueries(expression.this)\n        return expression\n\n    root = build_scope(expression)\n\n    if not root:\n        return expression\n\n    # Map of alias->Scope|Table\n    # These are all aliases that are already used in the expression.\n    # We don't want to create new CTEs that conflict with these names.\n    taken: TakenNameMapping = {}\n\n    # All CTE aliases in the root scope are taken\n    for scope in root.cte_scopes:\n        parent = scope.expression.parent\n        if parent:\n            taken[parent.alias] = scope\n\n    # All table names are taken\n    for scope in root.traverse():\n        taken.update(\n            {\n                source.name: source\n                for _, source in scope.sources.items()\n                if isinstance(source, exp.Table)\n            }\n        )\n\n    # Map of Expr->alias\n    # Existing CTES in the root expression. We'll use this for deduplication.\n    existing_ctes: ExistingCTEsMapping = {}\n\n    with_ = root.expression.args.get(\"with_\")\n    recursive = False\n    if with_:\n        recursive = with_.args.get(\"recursive\")\n        for cte in with_.expressions:\n            existing_ctes[cte.this] = cte.alias\n    new_ctes = []\n\n    # We're adding more CTEs, but we want to maintain the DAG order.\n    # Derived tables within an existing CTE need to come before the existing CTE.\n    for cte_scope in root.cte_scopes:\n        # Append all the new CTEs from this existing CTE\n        for scope in cte_scope.traverse():\n            if scope is cte_scope:\n                # Don't try to eliminate this CTE itself\n                continue\n            new_cte = _eliminate(scope, existing_ctes, taken)\n            if new_cte:\n                new_ctes.append(new_cte)\n\n        # Append the existing CTE itself\n        cte_parent = cte_scope.expression.parent\n        if cte_parent:\n            new_ctes.append(cte_parent)\n\n    # Now append the rest\n    for scope in itertools.chain(root.union_scopes, root.subquery_scopes, root.table_scopes):\n        for child_scope in scope.traverse():\n            new_cte = _eliminate(child_scope, existing_ctes, taken)\n            if new_cte:\n                new_ctes.append(new_cte)\n\n    if new_ctes:\n        query = expression.expression if isinstance(expression, exp.DDL) else expression\n        query.set(\"with_\", exp.With(expressions=new_ctes, recursive=recursive))\n\n    return expression\n\n\ndef _eliminate(\n    scope: Scope, existing_ctes: ExistingCTEsMapping, taken: TakenNameMapping\n) -> t.Optional[exp.Expr]:\n    if scope.is_derived_table:\n        return _eliminate_derived_table(scope, existing_ctes, taken)\n\n    if scope.is_cte:\n        return _eliminate_cte(scope, existing_ctes, taken)\n\n    return None\n\n\ndef _eliminate_derived_table(\n    scope: Scope, existing_ctes: ExistingCTEsMapping, taken: TakenNameMapping\n) -> t.Optional[exp.Expr]:\n    # This makes sure that we don't:\n    # - drop the \"pivot\" arg from a pivoted subquery\n    # - eliminate a lateral correlated subquery\n    parent_scope = scope.parent\n    if not parent_scope or parent_scope.pivots or isinstance(parent_scope.expression, exp.Lateral):\n        return None\n\n    expr_parent = scope.expression.parent\n    if not isinstance(expr_parent, exp.Subquery):\n        return None\n\n    # Get rid of redundant exp.Subquery expressions, i.e. those that are just used as wrappers\n    to_replace = expr_parent.unwrap()\n    name, cte = _new_cte(scope, existing_ctes, taken)\n    table = exp.alias_(exp.table_(name), alias=to_replace.alias or name)\n    table.set(\"joins\", to_replace.args.get(\"joins\"))\n\n    to_replace.replace(table)\n\n    return cte\n\n\ndef _eliminate_cte(\n    scope: Scope, existing_ctes: ExistingCTEsMapping, taken: TakenNameMapping\n) -> t.Optional[exp.Expr]:\n    parent = scope.expression.parent\n    if not parent:\n        return None\n    name, cte = _new_cte(scope, existing_ctes, taken)\n\n    with_ = parent.parent\n    parent.pop()\n    if with_ and not with_.expressions:\n        with_.pop()\n\n    # Rename references to this CTE\n    if not scope.parent:\n        return cte\n    for child_scope in scope.parent.traverse():\n        for table, source in child_scope.selected_sources.values():\n            if source is scope:\n                new_table = exp.alias_(exp.table_(name), alias=table.alias_or_name, copy=False)\n                table.replace(new_table)\n\n    return cte\n\n\ndef _new_cte(\n    scope: Scope, existing_ctes: ExistingCTEsMapping, taken: TakenNameMapping\n) -> t.Tuple[str, t.Optional[exp.Expr]]:\n    \"\"\"\n    Returns:\n        tuple of (name, cte)\n        where `name` is a new name for this CTE in the root scope and `cte` is a new CTE instance.\n        If this CTE duplicates an existing CTE, `cte` will be None.\n    \"\"\"\n    duplicate_cte_alias = existing_ctes.get(scope.expression)\n    parent = scope.expression.parent\n    name = parent.alias if parent else \"\"\n\n    if not name:\n        name = find_new_name(taken=taken, base=\"cte\")\n\n    if duplicate_cte_alias:\n        name = duplicate_cte_alias\n    elif taken.get(name):\n        name = find_new_name(taken=taken, base=name)\n\n    taken[name] = scope\n\n    if not duplicate_cte_alias:\n        existing_ctes[scope.expression] = name\n        cte = exp.CTE(\n            this=scope.expression,\n            alias=exp.TableAlias(this=exp.to_identifier(name)),\n        )\n    else:\n        cte = None\n    return name, cte\n"
  },
  {
    "path": "sqlglot/optimizer/isolate_table_selects.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import alias, exp\nfrom sqlglot.errors import OptimizeError\nfrom sqlglot.optimizer.scope import traverse_scope\nfrom sqlglot.schema import ensure_schema\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from sqlglot.schema import Schema\n    from sqlglot.dialects.dialect import DialectType\n\n\ndef isolate_table_selects(\n    expression: E,\n    schema: t.Optional[t.Dict | Schema] = None,\n    dialect: DialectType = None,\n) -> E:\n    schema = ensure_schema(schema, dialect=dialect)\n\n    for scope in traverse_scope(expression):\n        if len(scope.selected_sources) == 1:\n            continue\n\n        for _, source in scope.selected_sources.values():\n            assert source.parent\n\n            if (\n                not isinstance(source, exp.Table)\n                or not schema.column_names(source)\n                or isinstance(source.parent, exp.Subquery)\n                or isinstance(source.parent.parent, exp.Table)\n            ):\n                continue\n\n            if not source.alias:\n                raise OptimizeError(\"Tables require an alias. Run qualify_tables optimization.\")\n\n            source.replace(\n                exp.select(\"*\")\n                .from_(\n                    alias(source, source.alias_or_name, table=True),\n                    copy=False,\n                )\n                .subquery(source.alias, copy=False)\n            )\n\n    return expression\n"
  },
  {
    "path": "sqlglot/optimizer/merge_subqueries.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom collections import defaultdict\n\nfrom sqlglot import expressions as exp\nfrom sqlglot.helper import find_new_name, seq_get\nfrom sqlglot.optimizer.scope import Scope, traverse_scope\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n\n    FromOrJoin = t.Union[exp.From, exp.Join]\n\n\ndef merge_subqueries(expression: E, leave_tables_isolated: bool = False) -> E:\n    \"\"\"\n    Rewrite sqlglot AST to merge derived tables into the outer query.\n\n    This also merges CTEs if they are selected from only once.\n\n    Example:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one(\"SELECT a FROM (SELECT x.a FROM x) CROSS JOIN y\")\n        >>> merge_subqueries(expression).sql()\n        'SELECT x.a FROM x CROSS JOIN y'\n\n    If `leave_tables_isolated` is True, this will not merge inner queries into outer\n    queries if it would result in multiple table selects in a single query:\n        >>> expression = sqlglot.parse_one(\"SELECT a FROM (SELECT x.a FROM x) CROSS JOIN y\")\n        >>> merge_subqueries(expression, leave_tables_isolated=True).sql()\n        'SELECT a FROM (SELECT x.a FROM x) CROSS JOIN y'\n\n    Inspired by https://dev.mysql.com/doc/refman/8.0/en/derived-table-optimization.html\n\n    Args:\n        expression (sqlglot.Expr): expression to optimize\n        leave_tables_isolated (bool):\n    Returns:\n        sqlglot.Expr: optimized expression\n    \"\"\"\n    expression = merge_ctes(expression, leave_tables_isolated)\n    expression = merge_derived_tables(expression, leave_tables_isolated)\n    return expression\n\n\n# If a derived table has these Select args, it can't be merged\nUNMERGABLE_ARGS = set(exp.Select.arg_types) - {\n    \"expressions\",\n    \"from_\",\n    \"joins\",\n    \"where\",\n    \"order\",\n    \"hint\",\n}\n\n\n# Projections in the outer query that are instances of these types can be replaced\n# without getting wrapped in parentheses, because the precedence won't be altered.\nSAFE_TO_REPLACE_UNWRAPPED = (\n    exp.Column,\n    exp.EQ,\n    exp.Func,\n    exp.NEQ,\n    exp.Paren,\n)\n\n\ndef merge_ctes(expression: E, leave_tables_isolated: bool = False) -> E:\n    scopes = traverse_scope(expression)\n\n    # All places where we select from CTEs.\n    # We key on the CTE scope so we can detect CTES that are selected from multiple times.\n    cte_selections = defaultdict(list)\n    for outer_scope in scopes:\n        for table, inner_scope in outer_scope.selected_sources.values():\n            if isinstance(inner_scope, Scope) and inner_scope.is_cte:\n                cte_selections[id(inner_scope)].append(\n                    (\n                        outer_scope,\n                        inner_scope,\n                        table,\n                    )\n                )\n\n    singular_cte_selections = [v[0] for k, v in cte_selections.items() if len(v) == 1]\n    for outer_scope, inner_scope, table in singular_cte_selections:\n        from_or_join = table.find_ancestor(exp.From, exp.Join)\n        if not isinstance(from_or_join, (exp.From, exp.Join)):\n            continue\n        if _mergeable(outer_scope, inner_scope, leave_tables_isolated, from_or_join):\n            alias = table.alias_or_name\n            _rename_inner_sources(outer_scope, inner_scope, alias)\n            _merge_from(\n                outer_scope, inner_scope, t.cast(t.Union[exp.Subquery, exp.Table], table), alias\n            )\n            _merge_expressions(outer_scope, inner_scope, alias)\n            _merge_order(outer_scope, inner_scope)\n            _merge_joins(outer_scope, inner_scope, from_or_join)\n            _merge_where(outer_scope, inner_scope, from_or_join)\n            _merge_hints(outer_scope, inner_scope)\n            _pop_cte(inner_scope)\n            outer_scope.clear_cache()\n    return expression\n\n\ndef merge_derived_tables(expression: E, leave_tables_isolated: bool = False) -> E:\n    for outer_scope in traverse_scope(expression):\n        for subquery in outer_scope.derived_tables:\n            from_or_join = subquery.find_ancestor(exp.From, exp.Join)\n            if not isinstance(from_or_join, (exp.From, exp.Join)):\n                continue\n            alias = subquery.alias_or_name\n            inner_scope = outer_scope.sources[alias]\n            if not isinstance(inner_scope, Scope):\n                continue\n            if _mergeable(outer_scope, inner_scope, leave_tables_isolated, from_or_join):\n                _rename_inner_sources(outer_scope, inner_scope, alias)\n                _merge_from(outer_scope, inner_scope, subquery, alias)\n                _merge_expressions(outer_scope, inner_scope, alias)\n                _merge_order(outer_scope, inner_scope)\n                _merge_joins(outer_scope, inner_scope, from_or_join)\n                _merge_where(outer_scope, inner_scope, from_or_join)\n                _merge_hints(outer_scope, inner_scope)\n                outer_scope.clear_cache()\n\n    return expression\n\n\ndef _mergeable(\n    outer_scope: Scope, inner_scope: Scope, leave_tables_isolated: bool, from_or_join: FromOrJoin\n) -> bool:\n    \"\"\"\n    Return True if `inner_select` can be merged into outer query.\n    \"\"\"\n    inner_select = inner_scope.expression.unnest()\n\n    def _is_a_window_expression_in_unmergable_operation():\n        window_aliases = {s.alias_or_name for s in inner_select.selects if s.find(exp.Window)}\n        if not window_aliases:\n            return False\n        inner_select_name = from_or_join.alias_or_name\n        unmergable_window_columns = [\n            column\n            for column in outer_scope.columns\n            if column.find_ancestor(\n                exp.Where, exp.Group, exp.Order, exp.Join, exp.Having, exp.AggFunc\n            )\n        ]\n        return any(\n            column.table == inner_select_name and column.name in window_aliases\n            for column in unmergable_window_columns\n        )\n\n    def _outer_select_joins_on_inner_select_join():\n        \"\"\"\n        All columns from the inner select in the ON clause must be from the first FROM table.\n\n        That is, this can be merged:\n            SELECT * FROM x JOIN (SELECT y.a AS a FROM y JOIN z) AS q ON x.a = q.a\n                                         ^^^           ^\n        But this can't:\n            SELECT * FROM x JOIN (SELECT z.a AS a FROM y JOIN z) AS q ON x.a = q.a\n                                         ^^^                  ^\n        \"\"\"\n        if not isinstance(from_or_join, exp.Join):\n            return False\n\n        alias = from_or_join.alias_or_name\n\n        on = from_or_join.args.get(\"on\")\n        if not on:\n            return False\n        selections = [c.name for c in on.find_all(exp.Column) if c.table == alias]\n        inner_from = inner_scope.expression.args.get(\"from_\")\n        if not inner_from:\n            return False\n        inner_from_table = inner_from.alias_or_name\n        inner_projections = {s.alias_or_name: s for s in inner_scope.expression.selects}\n        return any(\n            col.table != inner_from_table\n            for selection in selections\n            for col in inner_projections[selection].find_all(exp.Column)\n        )\n\n    def _is_recursive():\n        # Recursive CTEs look like this:\n        #     WITH RECURSIVE cte AS (\n        #       SELECT * FROM x  <-- inner scope\n        #       UNION ALL\n        #       SELECT * FROM cte  <-- outer scope\n        #     )\n        cte = inner_scope.expression.parent\n        node = outer_scope.expression.parent\n\n        while node:\n            if node is cte:\n                return True\n            node = node.parent\n        return False\n\n    return (\n        isinstance(outer_scope.expression, exp.Select)\n        and not outer_scope.expression.is_star\n        and isinstance(inner_select, exp.Select)\n        and not any(inner_select.args.get(arg) for arg in UNMERGABLE_ARGS)\n        and inner_select.args.get(\"from_\") is not None\n        and not outer_scope.pivots\n        and not any(e.find(exp.AggFunc, exp.Select, exp.Explode) for e in inner_select.expressions)\n        and not (leave_tables_isolated and len(outer_scope.selected_sources) > 1)\n        and not (isinstance(from_or_join, exp.Join) and inner_select.args.get(\"joins\"))\n        and not (\n            isinstance(from_or_join, exp.Join)\n            and inner_select.args.get(\"where\")\n            and from_or_join.side in (\"FULL\", \"LEFT\", \"RIGHT\")\n        )\n        and not (\n            isinstance(from_or_join, exp.From)\n            and inner_select.args.get(\"where\")\n            and any(\n                j.side in (\"FULL\", \"RIGHT\") for j in outer_scope.expression.args.get(\"joins\", [])\n            )\n        )\n        and not _outer_select_joins_on_inner_select_join()\n        and not _is_a_window_expression_in_unmergable_operation()\n        and not _is_recursive()\n        and not (inner_select.args.get(\"order\") and outer_scope.is_union)\n        and not isinstance(seq_get(inner_select.expressions, 0), exp.QueryTransform)\n    )\n\n\ndef _rename_inner_sources(outer_scope: Scope, inner_scope: Scope, alias: str) -> None:\n    \"\"\"\n    Renames any sources in the inner query that conflict with names in the outer query.\n    \"\"\"\n    inner_taken = set(inner_scope.selected_sources)\n    outer_taken = set(outer_scope.selected_sources)\n    conflicts = outer_taken.intersection(inner_taken)\n    conflicts -= {alias}\n\n    taken = outer_taken.union(inner_taken)\n\n    for conflict in conflicts:\n        new_name = find_new_name(taken, conflict)\n\n        source, _ = inner_scope.selected_sources[conflict]\n        new_alias = exp.to_identifier(new_name)\n\n        if isinstance(source, exp.Table) and source.alias:\n            source.set(\"alias\", new_alias)\n        elif isinstance(source, exp.Table):\n            source.replace(exp.alias_(source, new_alias))\n        elif isinstance(source.parent, exp.Subquery):\n            source.parent.set(\"alias\", exp.TableAlias(this=new_alias))\n\n        for column in inner_scope.source_columns(conflict):\n            column.set(\"table\", exp.to_identifier(new_name))\n\n        inner_scope.rename_source(conflict, new_name)\n\n\ndef _merge_from(\n    outer_scope: Scope,\n    inner_scope: Scope,\n    node_to_replace: t.Union[exp.Subquery, exp.Table],\n    alias: str,\n) -> None:\n    \"\"\"\n    Merge FROM clause of inner query into outer query.\n    \"\"\"\n    new_subquery = inner_scope.expression.args[\"from_\"].this\n    new_subquery.set(\"joins\", node_to_replace.args.get(\"joins\"))\n    node_to_replace.replace(new_subquery)\n    for join_hint in outer_scope.join_hints:\n        tables = join_hint.find_all(exp.Table)\n        for table in tables:\n            if table.alias_or_name == node_to_replace.alias_or_name:\n                table.set(\"this\", exp.to_identifier(new_subquery.alias_or_name))\n    outer_scope.remove_source(alias)\n    outer_scope.add_source(\n        new_subquery.alias_or_name, inner_scope.sources[new_subquery.alias_or_name]\n    )\n\n\ndef _merge_joins(outer_scope: Scope, inner_scope: Scope, from_or_join: FromOrJoin) -> None:\n    \"\"\"\n    Merge JOIN clauses of inner query into outer query.\n    \"\"\"\n\n    new_joins = []\n\n    joins = inner_scope.expression.args.get(\"joins\") or []\n\n    for join in joins:\n        new_joins.append(join)\n        outer_scope.add_source(join.alias_or_name, inner_scope.sources[join.alias_or_name])\n\n    if new_joins:\n        outer_joins = outer_scope.expression.args.get(\"joins\", [])\n\n        # Maintain the join order\n        if isinstance(from_or_join, exp.From):\n            position = 0\n        else:\n            position = outer_joins.index(from_or_join) + 1\n        outer_joins[position:position] = new_joins\n\n        outer_scope.expression.set(\"joins\", outer_joins)\n\n\ndef _merge_expressions(outer_scope: Scope, inner_scope: Scope, alias: str) -> None:\n    \"\"\"\n    Merge projections of inner query into outer query.\n\n    Args:\n        outer_scope (sqlglot.optimizer.scope.Scope)\n        inner_scope (sqlglot.optimizer.scope.Scope)\n        alias (str)\n    \"\"\"\n    # Collect all columns that reference the alias of the inner query\n    outer_columns = defaultdict(list)\n    for column in outer_scope.columns:\n        if column.table == alias:\n            outer_columns[column.name].append(column)\n\n    # Replace columns with the projection expression in the inner query\n    for expression in inner_scope.expression.expressions:\n        projection_name = expression.alias_or_name\n        if not projection_name:\n            continue\n        columns_to_replace = outer_columns.get(projection_name, [])\n        if not columns_to_replace:\n            continue\n\n        expression = expression.unalias()\n        must_wrap_expression = not isinstance(expression, SAFE_TO_REPLACE_UNWRAPPED)\n\n        is_number = expression.is_number\n        last = len(columns_to_replace) - 1\n\n        for i, column in enumerate(columns_to_replace):\n            parent = column.parent\n\n            # Ensures that we don't merge literal numbers in GROUP BY as they have positional context\n            # e.g don't trasform `SELECT a FROM (SELECT 6 AS a) GROUP BY a` to `SELECT 6 AS a GROUP BY 6`,\n            # as this would attempt to GROUP BY the 6th projection instead of the column `a`\n            if is_number and isinstance(parent, exp.Group):\n                column.replace(exp.to_identifier(column.name))\n                continue\n\n            # Ensures we don't alter the intended operator precedence if there's additional\n            # context surrounding the outer expression (i.e. it's not a simple projection).\n            if isinstance(parent, (exp.Unary, exp.Binary)) and must_wrap_expression:\n                expression = exp.paren(expression, copy=False)\n\n            # make sure we do not accidentally change the name of the column\n            if isinstance(parent, exp.Select) and column.name != expression.name:\n                expression = exp.alias_(expression, column.name)\n\n            # Skip the expensive deep copy for the last reference since the inner query\n            # is about to be removed, so we can move the expression directly\n            column.replace(expression.copy() if i < last else expression)\n\n\ndef _merge_where(outer_scope: Scope, inner_scope: Scope, from_or_join: FromOrJoin) -> None:\n    \"\"\"\n    Merge WHERE clause of inner query into outer query.\n\n    Args:\n        outer_scope (sqlglot.optimizer.scope.Scope)\n        inner_scope (sqlglot.optimizer.scope.Scope)\n        from_or_join (exp.From|exp.Join)\n    \"\"\"\n    where = inner_scope.expression.args.get(\"where\")\n    if not where or not where.this:\n        return\n\n    expression = outer_scope.expression\n\n    if isinstance(from_or_join, exp.Join):\n        # Merge predicates from an outer join to the ON clause\n        # if it only has columns that are already joined\n        from_ = expression.args.get(\"from_\")\n        sources = {from_.alias_or_name} if from_ else set()\n\n        for join in expression.args[\"joins\"]:\n            source = join.alias_or_name\n            sources.add(source)\n            if source == from_or_join.alias_or_name:\n                break\n\n        if exp.column_table_names(where.this) <= sources:\n            from_or_join.on(where.this, copy=False)\n            from_or_join.set(\"on\", from_or_join.args.get(\"on\"))\n            return\n\n    t.cast(exp.Select, expression).where(where.this, copy=False)\n\n\ndef _merge_order(outer_scope: Scope, inner_scope: Scope) -> None:\n    \"\"\"\n    Merge ORDER clause of inner query into outer query.\n\n    Args:\n        outer_scope (sqlglot.optimizer.scope.Scope)\n        inner_scope (sqlglot.optimizer.scope.Scope)\n    \"\"\"\n    inner_order = inner_scope.expression.args.get(\"order\")\n    if not inner_order:\n        return\n\n    if (\n        any(\n            outer_scope.expression.args.get(arg) for arg in [\"group\", \"distinct\", \"having\", \"order\"]\n        )\n        or len(outer_scope.selected_sources) != 1\n        or any(expression.find(exp.AggFunc) for expression in outer_scope.expression.expressions)\n    ):\n        return\n\n    outer_scope.expression.set(\"order\", inner_order)\n\n\ndef _merge_hints(outer_scope: Scope, inner_scope: Scope) -> None:\n    inner_scope_hint = inner_scope.expression.args.get(\"hint\")\n    if not inner_scope_hint:\n        return\n    outer_scope_hint = outer_scope.expression.args.get(\"hint\")\n    if outer_scope_hint:\n        for hint_expression in inner_scope_hint.expressions:\n            outer_scope_hint.append(\"expressions\", hint_expression)\n    else:\n        outer_scope.expression.set(\"hint\", inner_scope_hint)\n\n\ndef _pop_cte(inner_scope: Scope) -> None:\n    \"\"\"\n    Remove CTE from the AST.\n\n    Args:\n        inner_scope (sqlglot.optimizer.scope.Scope)\n    \"\"\"\n    cte = inner_scope.expression.parent\n    if not cte:\n        return\n    with_ = cte.parent\n    if not with_:\n        return\n    if len(with_.expressions) == 1:\n        with_.pop()\n    else:\n        cte.pop()\n"
  },
  {
    "path": "sqlglot/optimizer/normalize.py",
    "content": "from __future__ import annotations\n\nimport logging\n\nfrom sqlglot import exp\nfrom sqlglot.errors import OptimizeError\nfrom sqlglot.helper import while_changing\nfrom sqlglot.optimizer.scope import find_all_in_scope\nfrom sqlglot.optimizer.simplify import Simplifier, flatten\n\nlogger = logging.getLogger(\"sqlglot\")\n\n\ndef normalize(expression: exp.Expr, dnf: bool = False, max_distance: int = 128) -> exp.Expr:\n    \"\"\"\n    Rewrite sqlglot AST into conjunctive normal form or disjunctive normal form.\n\n    Example:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one(\"(x AND y) OR z\")\n        >>> normalize(expression, dnf=False).sql()\n        '(x OR z) AND (y OR z)'\n\n    Args:\n        expression: expression to normalize\n        dnf: rewrite in disjunctive normal form instead.\n        max_distance (int): the maximal estimated distance from cnf/dnf to attempt conversion\n    Returns:\n        sqlglot.Expr: normalized expression\n    \"\"\"\n    simplifier = Simplifier(annotate_new_expressions=False)\n\n    for node in tuple(expression.walk(prune=lambda e: isinstance(e, exp.Connector))):\n        if isinstance(node, exp.Connector):\n            if normalized(node, dnf=dnf):\n                continue\n            root = node is expression\n            original = node.copy()\n\n            node.transform(simplifier.rewrite_between, copy=False)\n            distance = normalization_distance(node, dnf=dnf, max_=max_distance)\n\n            if distance > max_distance:\n                logger.info(\n                    f\"Skipping normalization because distance {distance} exceeds max {max_distance}\"\n                )\n                return expression\n\n            try:\n                node = node.replace(\n                    while_changing(\n                        node,\n                        lambda e: distributive_law(e, dnf, max_distance, simplifier=simplifier),\n                    )\n                )\n            except OptimizeError as e:\n                logger.info(e)\n                node.replace(original)\n                if root:\n                    return original\n                return expression\n\n            if root:\n                expression = node\n\n    return expression\n\n\ndef normalized(expression: exp.Expr, dnf: bool = False) -> bool:\n    \"\"\"\n    Checks whether a given expression is in a normal form of interest.\n\n    Example:\n        >>> from sqlglot import parse_one\n        >>> normalized(parse_one(\"(a AND b) OR c OR (d AND e)\"), dnf=True)\n        True\n        >>> normalized(parse_one(\"(a OR b) AND c\"))  # Checks CNF by default\n        True\n        >>> normalized(parse_one(\"a AND (b OR c)\"), dnf=True)\n        False\n\n    Args:\n        expression: The expression to check if it's normalized.\n        dnf: Whether to check if the expression is in Disjunctive Normal Form (DNF).\n            Default: False, i.e. we check if it's in Conjunctive Normal Form (CNF).\n    \"\"\"\n    ancestor, root = (exp.And, exp.Or) if dnf else (exp.Or, exp.And)\n    return not any(\n        connector.find_ancestor(ancestor) for connector in find_all_in_scope(expression, root)\n    )\n\n\ndef normalization_distance(\n    expression: exp.Expr, dnf: bool = False, max_: float = float(\"inf\")\n) -> int:\n    \"\"\"\n    The difference in the number of predicates between a given expression and its normalized form.\n\n    This is used as an estimate of the cost of the conversion which is exponential in complexity.\n\n    Example:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one(\"(a AND b) OR (c AND d)\")\n        >>> normalization_distance(expression)\n        4\n\n    Args:\n        expression: The expression to compute the normalization distance for.\n        dnf: Whether to check if the expression is in Disjunctive Normal Form (DNF).\n            Default: False, i.e. we check if it's in Conjunctive Normal Form (CNF).\n        max_: stop early if count exceeds this.\n\n    Returns:\n        The normalization distance.\n    \"\"\"\n    total = -(sum(1 for _ in expression.find_all(exp.Connector)) + 1)\n\n    for length in _predicate_lengths(expression, dnf, max_):\n        total += length\n        if total > max_:\n            return total\n\n    return total\n\n\ndef _predicate_lengths(expression, dnf, max_=float(\"inf\"), depth=0):\n    \"\"\"\n    Returns a list of predicate lengths when expanded to normalized form.\n\n    (A AND B) OR C -> [2, 2] because len(A OR C), len(B OR C).\n    \"\"\"\n    if depth > max_:\n        yield depth\n        return\n\n    expression = expression.unnest()\n\n    if not isinstance(expression, exp.Connector):\n        yield 1\n        return\n\n    depth += 1\n    left, right = expression.args.values()\n\n    if isinstance(expression, exp.And if dnf else exp.Or):\n        for a in _predicate_lengths(left, dnf, max_, depth):\n            for b in _predicate_lengths(right, dnf, max_, depth):\n                yield a + b\n    else:\n        yield from _predicate_lengths(left, dnf, max_, depth)\n        yield from _predicate_lengths(right, dnf, max_, depth)\n\n\ndef distributive_law(expression, dnf, max_distance, simplifier=None):\n    \"\"\"\n    x OR (y AND z) -> (x OR y) AND (x OR z)\n    (x AND y) OR (y AND z) -> (x OR y) AND (x OR z) AND (y OR y) AND (y OR z)\n    \"\"\"\n    if normalized(expression, dnf=dnf):\n        return expression\n\n    distance = normalization_distance(expression, dnf=dnf, max_=max_distance)\n\n    if distance > max_distance:\n        raise OptimizeError(f\"Normalization distance {distance} exceeds max {max_distance}\")\n\n    exp.replace_children(expression, lambda e: distributive_law(e, dnf, max_distance))\n    to_exp, from_exp = (exp.Or, exp.And) if dnf else (exp.And, exp.Or)\n\n    if isinstance(expression, from_exp):\n        a, b = expression.unnest_operands()\n\n        from_func = exp.and_ if from_exp == exp.And else exp.or_\n        to_func = exp.and_ if to_exp == exp.And else exp.or_\n\n        simplifier = simplifier or Simplifier(annotate_new_expressions=False)\n\n        if isinstance(a, to_exp) and isinstance(b, to_exp):\n            if len(tuple(a.find_all(exp.Connector))) > len(tuple(b.find_all(exp.Connector))):\n                return _distribute(a, b, from_func, to_func, simplifier)\n            return _distribute(b, a, from_func, to_func, simplifier)\n        if isinstance(a, to_exp):\n            return _distribute(b, a, from_func, to_func, simplifier)\n        if isinstance(b, to_exp):\n            return _distribute(a, b, from_func, to_func, simplifier)\n\n    return expression\n\n\ndef _distribute(a, b, from_func, to_func, simplifier):\n    if isinstance(a, exp.Connector):\n        exp.replace_children(\n            a,\n            lambda c: to_func(\n                simplifier.uniq_sort(flatten(from_func(c, b.left))),\n                simplifier.uniq_sort(flatten(from_func(c, b.right))),\n                copy=False,\n            ),\n        )\n    else:\n        a = to_func(\n            simplifier.uniq_sort(flatten(from_func(a, b.left))),\n            simplifier.uniq_sort(flatten(from_func(a, b.right))),\n            copy=False,\n        )\n\n    return a\n"
  },
  {
    "path": "sqlglot/optimizer/normalize_identifiers.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect, DialectType\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n\n\n@t.overload\ndef normalize_identifiers(\n    expression: E, dialect: DialectType = None, store_original_column_identifiers: bool = False\n) -> E: ...\n\n\n@t.overload\ndef normalize_identifiers(\n    expression: str, dialect: DialectType = None, store_original_column_identifiers: bool = False\n) -> exp.Identifier: ...\n\n\ndef normalize_identifiers(expression, dialect=None, store_original_column_identifiers=False):\n    \"\"\"\n    Normalize identifiers by converting them to either lower or upper case,\n    ensuring the semantics are preserved in each case (e.g. by respecting\n    case-sensitivity).\n\n    This transformation reflects how identifiers would be resolved by the engine corresponding\n    to each SQL dialect, and plays a very important role in the standardization of the AST.\n\n    It's possible to make this a no-op by adding a special comment next to the\n    identifier of interest:\n\n        SELECT a /* sqlglot.meta case_sensitive */ FROM table\n\n    In this example, the identifier `a` will not be normalized.\n\n    Note:\n        Some dialects (e.g. DuckDB) treat all identifiers as case-insensitive even\n        when they're quoted, so in these cases all identifiers are normalized.\n\n    Example:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one('SELECT Bar.A AS A FROM \"Foo\".Bar')\n        >>> normalize_identifiers(expression).sql()\n        'SELECT bar.a AS a FROM \"Foo\".bar'\n        >>> normalize_identifiers(\"foo\", dialect=\"snowflake\").sql(dialect=\"snowflake\")\n        'FOO'\n\n    Args:\n        expression: The expression to transform.\n        dialect: The dialect to use in order to decide how to normalize identifiers.\n        store_original_column_identifiers: Whether to store the original column identifiers in\n            the meta data of the expression in case we want to undo the normalization at a later point.\n\n    Returns:\n        The transformed expression.\n    \"\"\"\n    dialect = Dialect.get_or_raise(dialect)\n\n    if isinstance(expression, str):\n        expression = exp.parse_identifier(expression, dialect=dialect)\n\n    for node in expression.walk(prune=lambda n: bool(n.meta.get(\"case_sensitive\"))):\n        if not node.meta.get(\"case_sensitive\"):\n            if store_original_column_identifiers and isinstance(node, exp.Column):\n                # TODO: This does not handle non-column cases, e.g PARSE_JSON(...).key\n                parent = node\n                while parent and isinstance(parent.parent, exp.Dot):\n                    parent = parent.parent\n\n                node.meta[\"dot_parts\"] = [p.name for p in parent.parts]\n\n            dialect.normalize_identifier(node)\n\n    return expression\n"
  },
  {
    "path": "sqlglot/optimizer/optimize_joins.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.helper import tsort\n\nJOIN_ATTRS = (\"on\", \"side\", \"kind\", \"using\", \"method\")\n\n\ndef optimize_joins(expression):\n    \"\"\"\n    Removes cross joins if possible and reorder joins based on predicate dependencies.\n\n    Example:\n        >>> from sqlglot import parse_one\n        >>> optimize_joins(parse_one(\"SELECT * FROM x CROSS JOIN y JOIN z ON x.a = z.a AND y.a = z.a\")).sql()\n        'SELECT * FROM x JOIN z ON x.a = z.a AND TRUE JOIN y ON y.a = z.a'\n    \"\"\"\n\n    for select in expression.find_all(exp.Select):\n        joins = select.args.get(\"joins\", [])\n\n        if not _is_reorderable(joins):\n            continue\n\n        references = {}\n        cross_joins = []\n\n        for join in joins:\n            tables = other_table_names(join)\n\n            if tables:\n                for table in tables:\n                    references[table] = references.get(table, []) + [join]\n            else:\n                cross_joins.append((join.alias_or_name, join))\n\n        for name, join in cross_joins:\n            for dep in references.get(name, []):\n                on = dep.args[\"on\"]\n\n                if isinstance(on, exp.Connector):\n                    if len(other_table_names(dep)) < 2:\n                        continue\n\n                    operator = type(on)\n                    for predicate in on.flatten():\n                        if name in exp.column_table_names(predicate):\n                            predicate.replace(exp.true())\n                            predicate = exp._combine(\n                                [join.args.get(\"on\"), predicate], operator, copy=False\n                            )\n                            join.on(predicate, append=False, copy=False)\n\n    expression = reorder_joins(expression)\n    expression = normalize(expression)\n    return expression\n\n\ndef reorder_joins(expression):\n    \"\"\"\n    Reorder joins by topological sort order based on predicate references.\n    \"\"\"\n    for from_ in expression.find_all(exp.From):\n        parent = from_.parent\n        joins = parent.args.get(\"joins\", [])\n\n        if not _is_reorderable(joins):\n            continue\n\n        joins_by_name = {join.alias_or_name: join for join in joins}\n        dag = {name: other_table_names(join) for name, join in joins_by_name.items()}\n        parent.set(\n            \"joins\",\n            [\n                joins_by_name[name]\n                for name in tsort(dag)\n                if name != from_.alias_or_name and name in joins_by_name\n            ],\n        )\n    return expression\n\n\ndef normalize(expression):\n    \"\"\"\n    Remove INNER and OUTER from joins as they are optional.\n    \"\"\"\n    for join in expression.find_all(exp.Join):\n        if not any(join.args.get(k) for k in JOIN_ATTRS):\n            join.set(\"kind\", \"CROSS\")\n\n        if join.kind == \"CROSS\":\n            join.set(\"on\", None)\n        else:\n            if join.kind in (\"INNER\", \"OUTER\"):\n                join.set(\"kind\", None)\n\n            if not join.args.get(\"on\") and not join.args.get(\"using\"):\n                join.set(\"on\", exp.true())\n    return expression\n\n\ndef other_table_names(join: exp.Join) -> t.Set[str]:\n    on = join.args.get(\"on\")\n    return exp.column_table_names(on, join.alias_or_name) if on else set()\n\n\ndef _is_reorderable(joins: t.List[exp.Join]) -> bool:\n    \"\"\"\n    Checks if joins can be reordered without changing query semantics.\n\n    Joins with a side (LEFT, RIGHT, FULL) cannot be reordered easily,\n    the order affects which rows are included in the result.\n\n    Example:\n        >>> from sqlglot import parse_one, exp\n        >>> from sqlglot.optimizer.optimize_joins import _is_reorderable\n        >>> ast = parse_one(\"SELECT * FROM x JOIN y ON x.id = y.id JOIN z ON y.id = z.id\")\n        >>> _is_reorderable(ast.find(exp.Select).args.get(\"joins\", []))\n        True\n        >>> ast = parse_one(\"SELECT * FROM x LEFT JOIN y ON x.id = y.id JOIN z ON y.id = z.id\")\n        >>> _is_reorderable(ast.find(exp.Select).args.get(\"joins\", []))\n        False\n    \"\"\"\n    return not any(join.side for join in joins)\n"
  },
  {
    "path": "sqlglot/optimizer/optimizer.py",
    "content": "from __future__ import annotations\n\nimport inspect\nimport typing as t\nfrom collections.abc import Sequence\nfrom sqlglot import Schema, exp\nfrom sqlglot.dialects.dialect import DialectType\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom sqlglot.optimizer.canonicalize import canonicalize\nfrom sqlglot.optimizer.eliminate_ctes import eliminate_ctes\nfrom sqlglot.optimizer.eliminate_joins import eliminate_joins\nfrom sqlglot.optimizer.eliminate_subqueries import eliminate_subqueries\nfrom sqlglot.optimizer.merge_subqueries import merge_subqueries\nfrom sqlglot.optimizer.normalize import normalize\nfrom sqlglot.optimizer.optimize_joins import optimize_joins\nfrom sqlglot.optimizer.pushdown_predicates import pushdown_predicates\nfrom sqlglot.optimizer.pushdown_projections import pushdown_projections\nfrom sqlglot.optimizer.qualify import qualify\nfrom sqlglot.optimizer.qualify_columns import quote_identifiers\nfrom sqlglot.optimizer.simplify import simplify\nfrom sqlglot.optimizer.unnest_subqueries import unnest_subqueries\nfrom sqlglot.schema import ensure_schema\n\nRULES = (\n    qualify,\n    pushdown_projections,\n    normalize,\n    unnest_subqueries,\n    pushdown_predicates,\n    optimize_joins,\n    eliminate_subqueries,\n    merge_subqueries,\n    eliminate_joins,\n    eliminate_ctes,\n    quote_identifiers,\n    annotate_types,\n    canonicalize,\n    simplify,\n)\n\n\ndef optimize(\n    expression: str | exp.Expr,\n    schema: t.Optional[dict | Schema] = None,\n    db: t.Optional[str | exp.Identifier] = None,\n    catalog: t.Optional[str | exp.Identifier] = None,\n    dialect: DialectType = None,\n    rules: Sequence[t.Callable] = RULES,\n    sql: t.Optional[str] = None,\n    **kwargs,\n) -> exp.Expr:\n    \"\"\"\n    Rewrite a sqlglot AST into an optimized form.\n\n    Args:\n        expression: expression to optimize\n        schema: database schema.\n            This can either be an instance of `sqlglot.optimizer.Schema` or a mapping in one of\n            the following forms:\n                1. {table: {col: type}}\n                2. {db: {table: {col: type}}}\n                3. {catalog: {db: {table: {col: type}}}}\n            If no schema is provided then the default schema defined at `sqlgot.schema` will be used\n        db: specify the default database, as might be set by a `USE DATABASE db` statement\n        catalog: specify the default catalog, as might be set by a `USE CATALOG c` statement\n        dialect: The dialect to parse the sql string.\n        rules: sequence of optimizer rules to use.\n            Many of the rules require tables and columns to be qualified.\n            Do not remove `qualify` from the sequence of rules unless you know what you're doing!\n        sql: Original SQL string for error highlighting. If not provided, errors will not include\n            highlighting. Requires that the expression has position metadata from parsing.\n        **kwargs: If a rule has a keyword argument with a same name in **kwargs, it will be passed in.\n\n    Returns:\n        The optimized expression.\n    \"\"\"\n    schema = ensure_schema(schema, dialect=dialect)\n    possible_kwargs = {\n        \"db\": db,\n        \"catalog\": catalog,\n        \"schema\": schema,\n        \"dialect\": dialect,\n        \"sql\": sql,\n        \"isolate_tables\": True,  # needed for other optimizations to perform well\n        \"quote_identifiers\": False,\n        **kwargs,\n    }\n\n    optimized = exp.maybe_parse(expression, dialect=dialect, copy=True)\n    for rule in rules:\n        # Find any additional rule parameters, beyond `expression`\n        rule_params = inspect.getfullargspec(rule).args\n        rule_kwargs = {\n            param: possible_kwargs[param] for param in rule_params if param in possible_kwargs\n        }\n        optimized = rule(optimized, **rule_kwargs)\n\n    return optimized\n"
  },
  {
    "path": "sqlglot/optimizer/pushdown_predicates.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.optimizer.normalize import normalized\nfrom sqlglot.optimizer.scope import build_scope, find_in_scope\nfrom sqlglot.optimizer.simplify import simplify\nfrom sqlglot import Dialect\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from sqlglot.dialects.dialect import DialectType\n\n\ndef pushdown_predicates(expression: E, dialect: DialectType = None) -> E:\n    \"\"\"\n    Rewrite sqlglot AST to pushdown predicates in FROMS and JOINS\n\n    Example:\n        >>> import sqlglot\n        >>> sql = \"SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y WHERE y.a = 1\"\n        >>> expression = sqlglot.parse_one(sql)\n        >>> pushdown_predicates(expression).sql()\n        'SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x WHERE x.a = 1) AS y WHERE TRUE'\n\n    Args:\n        expression (sqlglot.Expr): expression to optimize\n    Returns:\n        sqlglot.Expr: optimized expression\n    \"\"\"\n    from sqlglot.dialects.athena import Athena\n    from sqlglot.dialects.presto import Presto\n\n    root = build_scope(expression)\n\n    dialect = Dialect.get_or_raise(dialect)\n    unnest_requires_cross_join = isinstance(dialect, (Athena, Presto))\n\n    if root:\n        scope_ref_count = root.ref_count()\n\n        for scope in reversed(list(root.traverse())):\n            select = scope.expression\n            where = select.args.get(\"where\")\n            if where:\n                selected_sources = scope.selected_sources\n                join_index = {\n                    join.alias_or_name: i for i, join in enumerate(select.args.get(\"joins\") or [])\n                }\n\n                # a right join can only push down to itself and not the source FROM table\n                # presto, trino and athena don't support inner joins where the RHS is an UNNEST expression\n                pushdown_allowed = True\n                for k, (node, source) in selected_sources.items():\n                    parent = node.find_ancestor(exp.Join, exp.From)\n                    if isinstance(parent, exp.Join):\n                        if parent.side == \"RIGHT\":\n                            selected_sources = {k: (node, source)}\n                            break\n                        if isinstance(node, exp.Unnest) and unnest_requires_cross_join:\n                            pushdown_allowed = False\n                            break\n\n                if pushdown_allowed:\n                    pushdown(where.this, selected_sources, scope_ref_count, dialect, join_index)\n\n            # joins should only pushdown into itself, not to other joins\n            # so we limit the selected sources to only itself\n            for join in select.args.get(\"joins\") or []:\n                name = join.alias_or_name\n                if name in scope.selected_sources:\n                    pushdown(\n                        join.args.get(\"on\"),\n                        {name: scope.selected_sources[name]},\n                        scope_ref_count,\n                        dialect,\n                    )\n\n    return expression\n\n\ndef pushdown(condition, sources, scope_ref_count, dialect, join_index=None):\n    if not condition:\n        return\n\n    condition = condition.replace(simplify(condition, dialect=dialect))\n    cnf_like = normalized(condition) or not normalized(condition, dnf=True)\n\n    predicates = list(\n        condition.flatten()\n        if isinstance(condition, exp.And if cnf_like else exp.Or)\n        else [condition]\n    )\n\n    if cnf_like:\n        pushdown_cnf(predicates, sources, scope_ref_count, join_index=join_index)\n    else:\n        pushdown_dnf(predicates, sources, scope_ref_count, join_index=join_index)\n\n\ndef pushdown_cnf(predicates, sources, scope_ref_count, join_index=None):\n    \"\"\"\n    If the predicates are in CNF like form, we can simply replace each block in the parent.\n    \"\"\"\n    for predicate in predicates:\n        for node in nodes_for_predicate(predicate, sources, scope_ref_count).values():\n            if isinstance(node, exp.Join):\n                name = node.alias_or_name\n                predicate_tables = exp.column_table_names(predicate, name)\n\n                if join_index:\n                    # Don't push the predicate if it references tables that appear in later joins\n                    this_index = join_index[name]\n                    if all(join_index.get(table, -1) < this_index for table in predicate_tables):\n                        predicate.replace(exp.true())\n                        node.on(predicate, copy=False)\n                        break\n            if isinstance(node, exp.Select):\n                predicate.replace(exp.true())\n                inner_predicate = replace_aliases(node, predicate)\n                if find_in_scope(inner_predicate, exp.AggFunc):\n                    node.having(inner_predicate, copy=False)\n                else:\n                    node.where(inner_predicate, copy=False)\n\n\ndef pushdown_dnf(predicates, sources, scope_ref_count, join_index=None):\n    \"\"\"\n    If the predicates are in DNF form, we can only push down conditions that are in all blocks.\n    Additionally, we can't remove predicates from their original form.\n    \"\"\"\n    # find all the tables that can be pushdown too\n    # these are tables that are referenced in all blocks of a DNF\n    # (a.x AND b.x) OR (a.y AND c.y)\n    # only table a can be push down\n    pushdown_tables = set()\n\n    for a in predicates:\n        a_tables = exp.column_table_names(a)\n\n        for b in predicates:\n            a_tables &= exp.column_table_names(b)\n\n        pushdown_tables.update(a_tables)\n\n    conditions = {}\n\n    # pushdown all predicates to their respective nodes\n    for table in sorted(pushdown_tables):\n        for predicate in predicates:\n            nodes = nodes_for_predicate(predicate, sources, scope_ref_count)\n\n            if table not in nodes:\n                continue\n\n            conditions[table] = (\n                exp.or_(conditions[table], predicate) if table in conditions else predicate\n            )\n\n        for name, node in nodes.items():\n            if name not in conditions:\n                continue\n\n            predicate = conditions[name]\n\n            if isinstance(node, exp.Join):\n                if join_index:\n                    this_index = join_index[name]\n                    predicate_tables = exp.column_table_names(predicate, name)\n                    if not all(join_index.get(t, -1) < this_index for t in predicate_tables):\n                        continue\n                node.on(predicate, copy=False)\n            elif isinstance(node, exp.Select):\n                inner_predicate = replace_aliases(node, predicate)\n                if find_in_scope(inner_predicate, exp.AggFunc):\n                    node.having(inner_predicate, copy=False)\n                else:\n                    node.where(inner_predicate, copy=False)\n\n\ndef nodes_for_predicate(predicate, sources, scope_ref_count):\n    nodes = {}\n    tables = exp.column_table_names(predicate)\n    where_condition = isinstance(predicate.find_ancestor(exp.Join, exp.Where), exp.Where)\n\n    for table in sorted(tables):\n        node, source = sources.get(table) or (None, None)\n\n        # if the predicate is in a where statement we can try to push it down\n        # we want to find the root join or from statement\n        if node and where_condition:\n            node = node.find_ancestor(exp.Join, exp.From)\n\n        # a node can reference a CTE which should be pushed down\n        if isinstance(node, exp.From) and not isinstance(source, exp.Table):\n            with_ = source.parent.expression.args.get(\"with_\")\n            if with_ and with_.recursive:\n                return {}\n            node = source.expression\n\n        if isinstance(node, exp.Join):\n            if node.side and node.side != \"RIGHT\":\n                return {}\n            nodes[table] = node\n        elif isinstance(node, exp.Select) and len(tables) == 1:\n            # We can't push down window expressions\n            has_window_expression = any(\n                select for select in node.selects if select.find(exp.Window)\n            )\n            # we can't push down predicates to select statements if they are referenced in\n            # multiple places.\n            if (\n                not node.args.get(\"group\")\n                and scope_ref_count[id(source)] < 2\n                and not has_window_expression\n            ):\n                nodes[table] = node\n    return nodes\n\n\ndef replace_aliases(source, predicate):\n    aliases = {}\n\n    for select in source.selects:\n        if isinstance(select, exp.Alias):\n            aliases[select.alias] = select.this\n        else:\n            aliases[select.name] = select\n\n    def _replace_alias(column):\n        if isinstance(column, exp.Column) and column.name in aliases:\n            return aliases[column.name].copy()\n        return column\n\n    return predicate.transform(_replace_alias)\n"
  },
  {
    "path": "sqlglot/optimizer/pushdown_projections.py",
    "content": "from __future__ import annotations\n\nimport typing as t\nfrom collections import defaultdict\n\nfrom sqlglot import alias, exp\nfrom sqlglot.optimizer.qualify_columns import Resolver\nfrom sqlglot.optimizer.scope import Scope, traverse_scope\nfrom sqlglot.schema import ensure_schema\nfrom sqlglot.errors import OptimizeError\nfrom sqlglot.helper import seq_get\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from sqlglot.schema import Schema\n    from sqlglot.dialects.dialect import DialectType\n\n# Sentinel value that means an outer query selecting ALL columns\nSELECT_ALL = object()\n\n\n# Selection to use if selection list is empty\ndef default_selection(is_agg: bool) -> exp.Alias:\n    return alias(exp.Max(this=exp.Literal.number(1)) if is_agg else \"1\", \"_\").assert_is(exp.Alias)\n\n\ndef pushdown_projections(\n    expression: E,\n    schema: t.Optional[t.Dict | Schema] = None,\n    remove_unused_selections: bool = True,\n    dialect: DialectType = None,\n) -> E:\n    \"\"\"\n    Rewrite sqlglot AST to remove unused columns projections.\n\n    Example:\n        >>> import sqlglot\n        >>> sql = \"SELECT y.a AS a FROM (SELECT x.a AS a, x.b AS b FROM x) AS y\"\n        >>> expression = sqlglot.parse_one(sql)\n        >>> pushdown_projections(expression).sql()\n        'SELECT y.a AS a FROM (SELECT x.a AS a FROM x) AS y'\n\n    Args:\n        expression (sqlglot.Expr): expression to optimize\n        remove_unused_selections (bool): remove selects that are unused\n    Returns:\n        sqlglot.Expr: optimized expression\n    \"\"\"\n    # Map of Scope to all columns being selected by outer queries.\n    schema = ensure_schema(schema, dialect=dialect)\n    source_column_alias_count: t.Dict[exp.Expr | Scope, int] = {}\n    referenced_columns: t.DefaultDict[Scope, t.Set[str | object]] = defaultdict(set)\n\n    # We build the scope tree (which is traversed in DFS postorder), then iterate\n    # over the result in reverse order. This should ensure that the set of selected\n    # columns for a particular scope are completely build by the time we get to it.\n    for scope in reversed(traverse_scope(expression)):\n        parent_selections = referenced_columns.get(scope, {SELECT_ALL})\n        alias_count = source_column_alias_count.get(scope, 0)\n\n        # We can't remove columns SELECT DISTINCT nor UNION DISTINCT.\n        if scope.expression.args.get(\"distinct\"):\n            parent_selections = {SELECT_ALL}\n\n        if isinstance(scope.expression, exp.SetOperation):\n            set_op = scope.expression\n            if set_op.kind or set_op.side:\n                # Do not optimize this set operation if it's using the BigQuery specific\n                # kind / side syntax (e.g INNER UNION ALL BY NAME) which changes the semantics of the operation\n                continue\n\n            left, right = scope.union_scopes\n            le = left.expression\n            re = right.expression\n\n            if not (isinstance(le, exp.Selectable) and isinstance(re, exp.Selectable)):\n                continue\n\n            if len(le.selects) != len(re.selects):\n                scope_sql = scope.expression.sql(dialect=dialect)\n                raise OptimizeError(f\"Invalid set operation due to column mismatch: {scope_sql}.\")\n\n            referenced_columns[left] = parent_selections\n\n            if re.is_star:\n                referenced_columns[right] = parent_selections\n            elif not le.is_star:\n                if scope.expression.args.get(\"by_name\"):\n                    referenced_columns[right] = referenced_columns[left]\n                else:\n                    referenced_columns[right] = {\n                        re.selects[i].alias_or_name\n                        for i, select in enumerate(le.selects)\n                        if SELECT_ALL in parent_selections\n                        or select.alias_or_name in parent_selections\n                    }\n\n        if isinstance(scope.expression, exp.Select):\n            if remove_unused_selections:\n                _remove_unused_selections(scope, parent_selections, schema, alias_count)\n\n            if scope.expression.is_star:\n                continue\n\n            # Group columns by source name\n            selects: t.Dict[str, t.Set[object]] = defaultdict(set)\n            for col in scope.columns:\n                table_name = col.table\n                col_name = col.name\n                selects[table_name].add(col_name)\n\n            # Push the selected columns down to the next scope\n            for name, (node, source) in scope.selected_sources.items():\n                if isinstance(source, Scope) and isinstance(source.expression, exp.Selectable):\n                    select = seq_get(source.expression.selects, 0)\n\n                    if scope.pivots or isinstance(select, exp.QueryTransform):\n                        columns: t.Set[object] = {SELECT_ALL}\n                    else:\n                        columns = selects.get(name) or set()\n\n                    referenced_columns[source].update(columns)\n\n                column_aliases = node.alias_column_names\n                if column_aliases:\n                    source_column_alias_count[source] = len(column_aliases)\n\n    return expression\n\n\ndef _remove_unused_selections(scope, parent_selections, schema, alias_count):\n    order = scope.expression.args.get(\"order\")\n\n    if order:\n        # Assume columns without a qualified table are references to output columns\n        order_refs = {c.name for c in order.find_all(exp.Column) if not c.table}\n    else:\n        order_refs = set()\n\n    new_selections = []\n    removed = False\n    star = False\n    is_agg = False\n\n    select_all = SELECT_ALL in parent_selections\n\n    for selection in scope.expression.selects:\n        name = selection.alias_or_name\n\n        if select_all or name in parent_selections or name in order_refs or alias_count > 0:\n            new_selections.append(selection)\n            alias_count -= 1\n        else:\n            if selection.is_star:\n                star = True\n            removed = True\n\n        if not is_agg and selection.find(exp.AggFunc):\n            is_agg = True\n\n    if star:\n        resolver = Resolver(scope, schema)\n        names = {s.alias_or_name for s in new_selections}\n\n        for name in sorted(parent_selections):\n            if name not in names:\n                new_selections.append(\n                    alias(exp.column(name, table=resolver.get_table(name)), name, copy=False)\n                )\n\n    # If there are no remaining selections, just select a single constant\n    if not new_selections:\n        new_selections.append(default_selection(is_agg))\n\n    scope.expression.select(*new_selections, append=False, copy=False)\n\n    if removed:\n        scope.clear_cache()\n"
  },
  {
    "path": "sqlglot/optimizer/qualify.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect, DialectType\nfrom sqlglot.optimizer.isolate_table_selects import isolate_table_selects\nfrom sqlglot.optimizer.normalize_identifiers import normalize_identifiers\nfrom sqlglot.optimizer.qualify_columns import (\n    qualify_columns as qualify_columns_func,\n    quote_identifiers as quote_identifiers_func,\n    validate_qualify_columns as validate_qualify_columns_func,\n)\nfrom sqlglot.optimizer.qualify_tables import qualify_tables\nfrom sqlglot.schema import Schema, ensure_schema\n\n\ndef qualify(\n    expression: exp.Expr,\n    dialect: DialectType = None,\n    db: t.Optional[str] = None,\n    catalog: t.Optional[str] = None,\n    schema: t.Optional[dict | Schema] = None,\n    expand_alias_refs: bool = True,\n    expand_stars: bool = True,\n    infer_schema: t.Optional[bool] = None,\n    isolate_tables: bool = False,\n    qualify_columns: bool = True,\n    allow_partial_qualification: bool = False,\n    validate_qualify_columns: bool = True,\n    quote_identifiers: bool = True,\n    identify: bool = True,\n    canonicalize_table_aliases: bool = False,\n    on_qualify: t.Optional[t.Callable[[exp.Expr], None]] = None,\n    sql: t.Optional[str] = None,\n) -> exp.Expr:\n    \"\"\"\n    Rewrite sqlglot AST to have normalized and qualified tables and columns.\n\n    This step is necessary for all further SQLGlot optimizations.\n\n    Example:\n        >>> import sqlglot\n        >>> schema = {\"tbl\": {\"col\": \"INT\"}}\n        >>> expression = sqlglot.parse_one(\"SELECT col FROM tbl\")\n        >>> qualify(expression, schema=schema).sql()\n        'SELECT \"tbl\".\"col\" AS \"col\" FROM \"tbl\" AS \"tbl\"'\n\n    Args:\n        expression: Expr to qualify.\n        db: Default database name for tables.\n        catalog: Default catalog name for tables.\n        schema: Schema to infer column names and types.\n        expand_alias_refs: Whether to expand references to aliases.\n        expand_stars: Whether to expand star queries. This is a necessary step\n            for most of the optimizer's rules to work; do not set to False unless you\n            know what you're doing!\n        infer_schema: Whether to infer the schema if missing.\n        isolate_tables: Whether to isolate table selects.\n        qualify_columns: Whether to qualify columns.\n        allow_partial_qualification: Whether to allow partial qualification.\n        validate_qualify_columns: Whether to validate columns.\n        quote_identifiers: Whether to run the quote_identifiers step.\n            This step is necessary to ensure correctness for case sensitive queries.\n            But this flag is provided in case this step is performed at a later time.\n        identify: If True, quote all identifiers, else only necessary ones.\n        canonicalize_table_aliases: Whether to use canonical aliases (_0, _1, ...) for all sources\n            instead of preserving table names.\n        on_qualify: Callback after a table has been qualified.\n        sql: Original SQL string for error highlighting. If not provided, errors will not include\n            highlighting. Requires that the expression has position metadata from parsing.\n\n    Returns:\n        The qualified expression.\n    \"\"\"\n    schema = ensure_schema(schema, dialect=dialect)\n    dialect = Dialect.get_or_raise(dialect)\n\n    expression = normalize_identifiers(\n        expression,\n        dialect=dialect,\n        store_original_column_identifiers=True,\n    )\n    expression = qualify_tables(\n        expression,\n        db=db,\n        catalog=catalog,\n        dialect=dialect,\n        on_qualify=on_qualify,\n        canonicalize_table_aliases=canonicalize_table_aliases,\n    )\n\n    if isolate_tables:\n        expression = isolate_table_selects(expression, schema=schema)\n\n    if qualify_columns:\n        expression = qualify_columns_func(\n            expression,\n            schema,\n            expand_alias_refs=expand_alias_refs,\n            expand_stars=expand_stars,\n            infer_schema=infer_schema,\n            allow_partial_qualification=allow_partial_qualification,\n        )\n\n    if quote_identifiers:\n        expression = quote_identifiers_func(expression, dialect=dialect, identify=identify)\n\n    if validate_qualify_columns:\n        validate_qualify_columns_func(expression, sql=sql)\n\n    return expression\n"
  },
  {
    "path": "sqlglot/optimizer/qualify_columns.py",
    "content": "from __future__ import annotations\n\nimport itertools\nimport typing as t\n\nfrom sqlglot import alias, exp\nfrom sqlglot.dialects.dialect import Dialect, DialectType\nfrom sqlglot.errors import OptimizeError, highlight_sql\nfrom sqlglot.helper import seq_get\nfrom sqlglot.optimizer.annotate_types import TypeAnnotator\nfrom sqlglot.optimizer.resolver import Resolver\nfrom sqlglot.optimizer.scope import Scope, build_scope, traverse_scope, walk_in_scope\nfrom sqlglot.optimizer.simplify import simplify_parens\nfrom sqlglot.schema import Schema, ensure_schema\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from collections.abc import Iterator, Iterable\n\n\ndef qualify_columns(\n    expression: exp.Expr,\n    schema: dict | Schema,\n    expand_alias_refs: bool = True,\n    expand_stars: bool = True,\n    infer_schema: t.Optional[bool] = None,\n    allow_partial_qualification: bool = False,\n    dialect: DialectType = None,\n) -> exp.Expr:\n    \"\"\"\n    Rewrite sqlglot AST to have fully qualified columns.\n\n    Example:\n        >>> import sqlglot\n        >>> schema = {\"tbl\": {\"col\": \"INT\"}}\n        >>> expression = sqlglot.parse_one(\"SELECT col FROM tbl\")\n        >>> qualify_columns(expression, schema).sql()\n        'SELECT tbl.col AS col FROM tbl'\n\n    Args:\n        expression: Expr to qualify.\n        schema: Database schema.\n        expand_alias_refs: Whether to expand references to aliases.\n        expand_stars: Whether to expand star queries. This is a necessary step\n            for most of the optimizer's rules to work; do not set to False unless you\n            know what you're doing!\n        infer_schema: Whether to infer the schema if missing.\n        allow_partial_qualification: Whether to allow partial qualification.\n\n    Returns:\n        The qualified expression.\n\n    Notes:\n        - Currently only handles a single PIVOT or UNPIVOT operator\n    \"\"\"\n    schema = ensure_schema(schema, dialect=dialect)\n    annotator = TypeAnnotator(schema)\n    infer_schema = schema.empty if infer_schema is None else infer_schema\n    dialect = schema.dialect or Dialect()\n    pseudocolumns = dialect.PSEUDOCOLUMNS\n\n    for scope in traverse_scope(expression):\n        if dialect.PREFER_CTE_ALIAS_COLUMN:\n            pushdown_cte_alias_columns(scope)\n\n        scope_expression = scope.expression\n        is_select = isinstance(scope_expression, exp.Select)\n\n        _separate_pseudocolumns(scope, pseudocolumns)\n\n        resolver = Resolver(scope, schema, infer_schema=infer_schema)\n        _pop_table_column_aliases(scope.ctes)\n        _pop_table_column_aliases(scope.derived_tables)\n        using_column_tables = _expand_using(scope, resolver)\n\n        if (schema.empty or dialect.FORCE_EARLY_ALIAS_REF_EXPANSION) and expand_alias_refs:\n            _expand_alias_refs(\n                scope,\n                resolver,\n                dialect,\n                expand_only_groupby=dialect.EXPAND_ONLY_GROUP_ALIAS_REF,\n            )\n\n        _convert_columns_to_dots(scope, resolver)\n        _qualify_columns(\n            scope,\n            resolver,\n            allow_partial_qualification=allow_partial_qualification,\n        )\n\n        if not schema.empty and expand_alias_refs:\n            _expand_alias_refs(scope, resolver, dialect)\n\n        if is_select:\n            if expand_stars:\n                _expand_stars(\n                    scope,\n                    resolver,\n                    using_column_tables,\n                    pseudocolumns,\n                    annotator,\n                )\n            qualify_outputs(scope)\n\n        _expand_group_by(scope, dialect)\n\n        # DISTINCT ON and ORDER BY follow the same rules (tested in DuckDB, Postgres, ClickHouse)\n        # https://www.postgresql.org/docs/current/sql-select.html#SQL-DISTINCT\n        _expand_order_by_and_distinct_on(scope, resolver)\n\n        if dialect.ANNOTATE_ALL_SCOPES:\n            annotator.annotate_scope(scope)\n\n    return expression\n\n\ndef validate_qualify_columns(expression: E, sql: t.Optional[str] = None) -> E:\n    \"\"\"Raise an `OptimizeError` if any columns aren't qualified\"\"\"\n    all_unqualified_columns = []\n    for scope in traverse_scope(expression):\n        if isinstance(scope.expression, exp.Select):\n            unqualified_columns = scope.unqualified_columns\n\n            if scope.external_columns and not scope.is_correlated_subquery and not scope.pivots:\n                column = scope.external_columns[0]\n                for_table = f\" for table: '{column.table}'\" if column.table else \"\"\n                line = column.this.meta.get(\"line\")\n                col = column.this.meta.get(\"col\")\n                start = column.this.meta.get(\"start\")\n                end = column.this.meta.get(\"end\")\n\n                error_msg = f\"Column '{column.name}' could not be resolved{for_table}.\"\n                if line and col:\n                    error_msg += f\" Line: {line}, Col: {col}\"\n                if sql and start is not None and end is not None:\n                    formatted_sql = highlight_sql(sql, [(start, end)])[0]\n                    error_msg += f\"\\n  {formatted_sql}\"\n\n                raise OptimizeError(error_msg)\n\n            if unqualified_columns and scope.pivots and scope.pivots[0].unpivot:\n                # New columns produced by the UNPIVOT can't be qualified, but there may be columns\n                # under the UNPIVOT's IN clause that can and should be qualified. We recompute\n                # this list here to ensure those in the former category will be excluded.\n                unpivot_columns = set(_unpivot_columns(scope.pivots[0]))\n                unqualified_columns = [c for c in unqualified_columns if c not in unpivot_columns]\n\n            all_unqualified_columns.extend(unqualified_columns)\n\n    if all_unqualified_columns:\n        first_column = all_unqualified_columns[0]\n        line = first_column.this.meta.get(\"line\")\n        col = first_column.this.meta.get(\"col\")\n        start = first_column.this.meta.get(\"start\")\n        end = first_column.this.meta.get(\"end\")\n\n        error_msg = f\"Ambiguous column '{first_column.name}'\"\n        if line and col:\n            error_msg += f\" (Line: {line}, Col: {col})\"\n        if sql and start is not None and end is not None:\n            formatted_sql = highlight_sql(sql, [(start, end)])[0]\n            error_msg += f\"\\n  {formatted_sql}\"\n\n        raise OptimizeError(error_msg)\n\n    return expression\n\n\ndef _separate_pseudocolumns(scope: Scope, pseudocolumns: t.Set[str]) -> None:\n    if not pseudocolumns:\n        return\n\n    has_pseudocolumns = False\n    scope_expression = scope.expression\n\n    for column in scope.columns:\n        name = column.name.upper()\n        if name not in pseudocolumns:\n            continue\n\n        if name != \"LEVEL\" or (\n            isinstance(scope_expression, exp.Select) and scope_expression.args.get(\"connect\")\n        ):\n            column.replace(exp.Pseudocolumn(**column.args))\n            has_pseudocolumns = True\n\n    if has_pseudocolumns:\n        scope.clear_cache()\n\n\ndef _unpivot_columns(unpivot: exp.Pivot) -> Iterator[exp.Column]:\n    name_columns = [\n        field.this\n        for field in unpivot.fields\n        if isinstance(field, exp.In) and isinstance(field.this, exp.Column)\n    ]\n    value_columns = (c for e in unpivot.expressions for c in e.find_all(exp.Column))\n\n    return itertools.chain(name_columns, value_columns)\n\n\ndef _pop_table_column_aliases(derived_tables: Iterable[exp.Expr]) -> None:\n    \"\"\"\n    Remove table column aliases.\n\n    For example, `col1` and `col2` will be dropped in SELECT ... FROM (SELECT ...) AS foo(col1, col2)\n    \"\"\"\n    for derived_table in derived_tables:\n        if isinstance(derived_table.parent, exp.With) and derived_table.parent.recursive:\n            continue\n        table_alias = derived_table.args.get(\"alias\")\n        if table_alias:\n            table_alias.set(\"columns\", None)\n\n\ndef _expand_using(scope: Scope, resolver: Resolver) -> t.Dict[str, t.Any]:\n    columns = {}\n\n    def _update_source_columns(source_name: str) -> None:\n        for column_name in resolver.get_source_columns(source_name):\n            if column_name not in columns:\n                columns[column_name] = source_name\n\n    joins = list(scope.find_all(exp.Join))\n    names = {join.alias_or_name for join in joins}\n    ordered = [key for key in scope.selected_sources if key not in names]\n\n    if names and not ordered:\n        raise OptimizeError(f\"Joins {names} missing source table {scope.expression}\")\n\n    # Mapping of automatically joined column names to an ordered set of source names (dict).\n    column_tables: t.Dict[str, t.Dict[str, t.Any]] = {}\n\n    for source_name in ordered:\n        _update_source_columns(source_name)\n\n    for i, join in enumerate(joins):\n        source_table = ordered[-1]\n        if source_table:\n            _update_source_columns(source_table)\n\n        join_table = join.alias_or_name\n        ordered.append(join_table)\n\n        using = join.args.get(\"using\")\n        if not using:\n            continue\n\n        join_columns = resolver.get_source_columns(join_table)\n        conditions = []\n        using_identifier_count = len(using)\n        is_semi_or_anti_join = join.is_semi_or_anti_join\n\n        for identifier in using:\n            identifier = identifier.name\n            table = columns.get(identifier)\n\n            if not table or identifier not in join_columns:\n                if (columns and \"*\" not in columns) and join_columns:\n                    raise OptimizeError(f\"Cannot automatically join: {identifier}\")\n\n            table = table or source_table\n\n            if i == 0 or using_identifier_count == 1:\n                lhs: exp.Expr = exp.column(identifier, table=table)\n            else:\n                coalesce_columns = [\n                    exp.column(identifier, table=t)\n                    for t in ordered[:-1]\n                    if identifier in resolver.get_source_columns(t)\n                ]\n                if len(coalesce_columns) > 1:\n                    lhs = exp.func(\"coalesce\", *coalesce_columns)\n                else:\n                    lhs = exp.column(identifier, table=table)\n\n            conditions.append(lhs.eq(exp.column(identifier, table=join_table)))\n\n            # Set all values in the dict to None, because we only care about the key ordering\n            tables = column_tables.setdefault(identifier, {})\n\n            # Do not update the dict if this was a SEMI/ANTI join in\n            # order to avoid generating COALESCE columns for this join pair\n            if not is_semi_or_anti_join:\n                if table not in tables:\n                    tables[table] = None\n                if join_table not in tables:\n                    tables[join_table] = None\n\n        join.set(\"using\", None)\n        join.set(\"on\", exp.and_(*conditions, copy=False))\n\n    if column_tables:\n        for column in scope.columns:\n            if not column.table and column.name in column_tables:\n                tables = column_tables[column.name]\n                coalesce_args = [exp.column(column.name, table=table) for table in tables]\n                replacement: exp.Expr = exp.func(\"coalesce\", *coalesce_args)\n\n                if isinstance(column.parent, exp.Select):\n                    # Ensure the USING column keeps its name if it's projected\n                    replacement = alias(replacement, alias=column.name, copy=False)\n                elif isinstance(column.parent, exp.Struct):\n                    # Ensure the USING column keeps its name if it's an anonymous STRUCT field\n                    replacement = exp.PropertyEQ(\n                        this=exp.to_identifier(column.name), expression=replacement\n                    )\n\n                scope.replace(column, replacement)\n\n    return column_tables\n\n\ndef _expand_alias_refs(\n    scope: Scope, resolver: Resolver, dialect: Dialect, expand_only_groupby: bool = False\n) -> None:\n    \"\"\"\n    Expand references to aliases.\n    Example:\n        SELECT y.foo AS bar, bar * 2 AS baz FROM y\n     => SELECT y.foo AS bar, y.foo * 2 AS baz FROM y\n    \"\"\"\n    expression = scope.expression\n\n    if not isinstance(expression, exp.Select) or dialect.DISABLES_ALIAS_REF_EXPANSION:\n        return\n\n    alias_to_expression: t.Dict[str, t.Tuple[exp.Expr, int]] = {}\n    projections = {s.alias_or_name for s in expression.selects}\n    replaced = False\n\n    def replace_columns(\n        node: t.Optional[exp.Expr], resolve_table: bool = False, literal_index: bool = False\n    ) -> None:\n        nonlocal replaced\n        is_group_by = isinstance(node, exp.Group)\n        is_having = isinstance(node, exp.Having)\n        if not node or (expand_only_groupby and not is_group_by):\n            return\n\n        for column in walk_in_scope(node, prune=lambda node: node.is_star):\n            if not isinstance(column, exp.Column):\n                continue\n\n            # BigQuery's GROUP BY allows alias expansion only for standalone names, e.g:\n            #   SELECT FUNC(col) AS col FROM t GROUP BY col --> Can be expanded\n            #   SELECT FUNC(col) AS col FROM t GROUP BY FUNC(col)  --> Shouldn't be expanded, will result to FUNC(FUNC(col))\n            # This not required for the HAVING clause as it can evaluate expressions using both the alias & the table columns\n            if expand_only_groupby and is_group_by and column.parent is not node:\n                continue\n\n            skip_replace = False\n            table = resolver.get_table(column.name) if resolve_table and not column.table else None\n            alias_expr, i = alias_to_expression.get(column.name, (None, 1))\n\n            if alias_expr:\n                skip_replace = bool(\n                    alias_expr.find(exp.AggFunc)\n                    and column.find_ancestor(exp.AggFunc)\n                    and not isinstance(column.find_ancestor(exp.Window, exp.Select), exp.Window)\n                )\n\n                # BigQuery's having clause gets confused if an alias matches a source.\n                # SELECT x.a, max(x.b) as x FROM x GROUP BY 1 HAVING x > 1;\n                # If \"HAVING x\" is expanded to \"HAVING max(x.b)\", BQ would blindly replace the \"x\" reference with the projection MAX(x.b)\n                # i.e HAVING MAX(MAX(x.b).b), resulting in the error: \"Aggregations of aggregations are not allowed\"\n                if is_having and dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:\n                    skip_replace = skip_replace or any(\n                        node.parts[0].name in projections\n                        for node in alias_expr.find_all(exp.Column)\n                    )\n            elif dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES and (is_group_by or is_having):\n                column_table = table.name if table else column.table\n                if column_table in projections:\n                    # BigQuery's GROUP BY and HAVING clauses get confused if the column name\n                    # matches a source name and a projection. For instance:\n                    # SELECT id, ARRAY_AGG(col) AS custom_fields FROM custom_fields GROUP BY id HAVING id >= 1\n                    # We should not qualify \"id\" with \"custom_fields\" in either clause, since the aggregation shadows the actual table\n                    # and we'd get the error: \"Column custom_fields contains an aggregation function, which is not allowed in GROUP BY clause\"\n                    column.replace(exp.to_identifier(column.name))\n                    replaced = True\n                    return\n\n            if table and (not alias_expr or skip_replace):\n                column.set(\"table\", table)\n            elif not column.table and alias_expr and not skip_replace:\n                if (isinstance(alias_expr, exp.Literal) or alias_expr.is_number) and (\n                    literal_index or resolve_table\n                ):\n                    if literal_index:\n                        column.replace(exp.Literal.number(i))\n                        replaced = True\n                else:\n                    replaced = True\n                    column = column.replace(exp.paren(alias_expr))\n                    simplified = simplify_parens(column, dialect)\n                    if simplified is not column:\n                        column.replace(simplified)\n\n    for i, projection in enumerate(expression.selects):\n        replace_columns(projection)\n        if isinstance(projection, exp.Alias):\n            alias_to_expression[projection.alias] = (projection.this, i + 1)\n\n    parent_scope: t.Optional[Scope] = scope\n    on_right_sub_tree = False\n    while parent_scope and not parent_scope.is_cte:\n        if parent_scope := parent_scope.parent:\n            if isinstance(parent_scope.expression, exp.Union):\n                on_right_sub_tree = parent_scope.expression.right is parent_scope.expression\n\n    # We shouldn't expand aliases if they match the recursive CTE's columns\n    # and we are in the recursive part (right sub tree) of the CTE\n    if parent_scope and on_right_sub_tree:\n        if cte := parent_scope.expression.parent:\n            with_ = cte.find_ancestor(exp.With)\n            if with_ and with_.recursive:\n                for recursive_cte_column in cte.args[\"alias\"].columns or cte.this.selects:\n                    alias_to_expression.pop(recursive_cte_column.output_name, None)\n\n    replace_columns(expression.args.get(\"where\"))\n    replace_columns(expression.args.get(\"group\"), literal_index=True)\n    replace_columns(expression.args.get(\"having\"), resolve_table=True)\n    replace_columns(expression.args.get(\"qualify\"), resolve_table=True)\n\n    if dialect.SUPPORTS_ALIAS_REFS_IN_JOIN_CONDITIONS:\n        for join in expression.args.get(\"joins\") or []:\n            replace_columns(join)\n\n    if replaced:\n        scope.clear_cache()\n\n\ndef _expand_group_by(scope: Scope, dialect: Dialect) -> None:\n    expression = scope.expression\n    group = expression.args.get(\"group\")\n    if not group:\n        return\n\n    group.set(\"expressions\", _expand_positional_references(scope, group.expressions, dialect))\n    expression.set(\"group\", group)\n\n\ndef _expand_order_by_and_distinct_on(scope: Scope, resolver: Resolver) -> None:\n    expression = scope.expression\n\n    if not isinstance(expression, exp.Selectable):\n        return\n\n    # TODO (mypyc): rebind to exp.Expr to avoid Selectable trait vtable dispatch for .args\n    expr: exp.Expr = expression\n\n    for modifier_key in (\"order\", \"distinct\"):\n        modifier = expr.args.get(modifier_key)\n        if isinstance(modifier, exp.Distinct):\n            modifier = modifier.args.get(\"on\")\n\n        if not isinstance(modifier, exp.Expr):\n            continue\n\n        modifier_expressions = modifier.expressions\n        if modifier_key == \"order\":\n            modifier_expressions = [ordered.this for ordered in modifier_expressions]\n\n        for original, expanded in zip(\n            modifier_expressions,\n            _expand_positional_references(\n                scope, modifier_expressions, resolver.dialect, alias=True\n            ),\n        ):\n            for agg in original.find_all(exp.AggFunc):\n                for col in agg.find_all(exp.Column):\n                    if not col.table:\n                        col.set(\"table\", resolver.get_table(col.name))\n\n            original.replace(expanded)\n\n        if expr.args.get(\"group\"):\n            selects = {s.this: exp.column(s.alias_or_name) for s in expression.selects}\n\n            for node in modifier_expressions:\n                node.replace(\n                    exp.to_identifier(_select_by_pos(expression, node).alias)\n                    if node.is_int\n                    else selects.get(node, node)\n                )\n\n\ndef _expand_positional_references(\n    scope: Scope, expressions: Iterable[exp.Expr], dialect: Dialect, alias: bool = False\n) -> list[exp.Expr]:\n    new_nodes: list[exp.Expr] = []\n    ambiguous_projections = None\n\n    expression = scope.expression\n\n    if not isinstance(expression, exp.Selectable):\n        return new_nodes\n\n    for node in expressions:\n        if node.is_int and isinstance(node, exp.Literal):\n            select = _select_by_pos(expression, node)\n\n            if alias:\n                new_nodes.append(exp.column(select.args[\"alias\"].copy()))\n            else:\n                # TODO (mypyc): use a separate variable to avoid reusing `select` (Alias) with a different type\n                select_expr: exp.Expr = select.this\n\n                if dialect.PROJECTION_ALIASES_SHADOW_SOURCE_NAMES:\n                    if ambiguous_projections is None:\n                        # When a projection name is also a source name and it is referenced in the\n                        # GROUP BY clause, BQ can't understand what the identifier corresponds to\n                        ambiguous_projections = {\n                            s.alias_or_name\n                            for s in expression.selects\n                            if s.alias_or_name in scope.selected_sources\n                        }\n\n                    ambiguous = any(\n                        column.parts[0].name in ambiguous_projections\n                        for column in select_expr.find_all(exp.Column)\n                    )\n                else:\n                    ambiguous = False\n\n                if (\n                    isinstance(select_expr, exp.CONSTANTS)\n                    or select_expr.is_number\n                    or select_expr.find(exp.Explode, exp.Unnest)\n                    or ambiguous\n                ):\n                    new_nodes.append(node)\n                else:\n                    new_nodes.append(select_expr.copy())\n        else:\n            new_nodes.append(node)\n\n    return new_nodes\n\n\ndef _select_by_pos(expression: exp.Selectable, node: exp.Literal) -> exp.Alias:\n    try:\n        return expression.selects[int(node.this) - 1].assert_is(exp.Alias)\n    except IndexError:\n        raise OptimizeError(f\"Unknown output column: {node.name}\")\n\n\ndef _convert_columns_to_dots(scope: Scope, resolver: Resolver) -> None:\n    \"\"\"\n    Converts `Column` instances that represent STRUCT or JSON field lookup into chained `Dots`.\n\n    These lookups may be parsed as columns (e.g. \"col\".\"field\".\"field2\"), but they need to be\n    normalized to `Dot(Dot(...(<table>.<column>, field1), field2, ...))` to be qualified properly.\n    \"\"\"\n    converted = False\n    for column in itertools.chain(scope.columns, scope.stars):\n        if isinstance(column, exp.Dot):\n            continue\n\n        column_table: str | exp.Identifier | None = column.table\n        dot_parts = column.meta.pop(\"dot_parts\", [])\n        if (\n            column_table\n            and column_table not in scope.sources\n            and (\n                not scope.parent\n                or column_table not in scope.parent.sources\n                or not scope.is_correlated_subquery\n            )\n        ):\n            root, *parts = column.parts\n\n            if isinstance(root, exp.Identifier) and root.name in scope.sources:\n                # The struct is already qualified, but we still need to change the AST\n                column_table = root\n                root, *parts = parts\n                was_qualified = True\n            else:\n                column_table = resolver.get_table(root.name)\n                was_qualified = False\n\n            if column_table:\n                converted = True\n                new_column = exp.column(root, table=column_table)\n\n                if dot_parts:\n                    # Remove the actual column parts from the rest of dot parts\n                    new_column.meta[\"dot_parts\"] = dot_parts[2 if was_qualified else 1 :]\n\n                column.replace(exp.Dot.build([new_column, *parts]))\n\n    if converted:\n        # We want to re-aggregate the converted columns, otherwise they'd be skipped in\n        # a `for column in scope.columns` iteration, even though they shouldn't be\n        scope.clear_cache()\n\n\ndef _qualify_columns(\n    scope: Scope,\n    resolver: Resolver,\n    allow_partial_qualification: bool,\n) -> None:\n    \"\"\"Disambiguate columns, ensuring each column specifies a source\"\"\"\n    for column in scope.columns:\n        column_table = column.table\n        column_name = column.name\n\n        if column_table and column_table in scope.sources:\n            source_columns = resolver.get_source_columns(column_table)\n            if (\n                not allow_partial_qualification\n                and source_columns\n                and column_name not in source_columns\n                and \"*\" not in source_columns\n            ):\n                raise OptimizeError(f\"Unknown column: {column_name}\")\n\n        if not column_table:\n            if scope.pivots and not column.find_ancestor(exp.Pivot):\n                # If the column is under the Pivot expression, we need to qualify it\n                # using the name of the pivoted source instead of the pivot's alias\n                column.set(\"table\", exp.to_identifier(scope.pivots[0].alias))\n                continue\n\n            # column_table can be a '' because bigquery unnest has no table alias\n            table = resolver.get_table(column)\n\n            if (\n                table\n                and isinstance(source := scope.sources.get(table.name), Scope)\n                and id(column) in source.column_index\n            ):\n                continue\n\n            if table:\n                column.set(\"table\", table)\n            elif (\n                resolver.dialect.TABLES_REFERENCEABLE_AS_COLUMNS\n                and len(column.parts) == 1\n                and column_name in scope.selected_sources\n            ):\n                # BigQuery and Postgres allow tables to be referenced as columns, treating them as structs/records\n                scope.replace(column, exp.TableColumn(this=column.this))\n\n    for pivot in scope.pivots:\n        for column in pivot.find_all(exp.Column):\n            if not column.table and column.name in resolver.all_columns:\n                table = resolver.get_table(column.name)\n                if table:\n                    column.set(\"table\", table)\n\n\ndef _expand_struct_stars_no_parens(\n    expression: exp.Dot,\n) -> t.List[exp.Alias]:\n    \"\"\"[BigQuery] Expand/Flatten foo.bar.* where bar is a struct column\"\"\"\n\n    dot_column = expression.find(exp.Column)\n    if not isinstance(dot_column, exp.Column) or not dot_column.is_type(exp.DType.STRUCT):\n        return []\n\n    # All nested struct values are ColumnDefs, so normalize the first exp.Column in one\n    dot_column = dot_column.copy()\n    starting_struct = exp.ColumnDef(this=dot_column.this, kind=dot_column.type)\n\n    # First part is the table name and last part is the star so they can be dropped\n    dot_parts = expression.parts[1:-1]\n\n    # If we're expanding a nested struct eg. t.c.f1.f2.* find the last struct (f2 in this case)\n    for part in dot_parts[1:]:\n        for field in t.cast(exp.DataType, starting_struct.kind).expressions:\n            # Unable to expand star unless all fields are named\n            if not isinstance(field.this, exp.Identifier):\n                return []\n\n            if field.name == part.name and field.kind.is_type(exp.DType.STRUCT):\n                starting_struct = field\n                break\n        else:\n            # There is no matching field in the struct\n            return []\n\n    taken_names = set()\n    new_selections = []\n\n    for field in t.cast(exp.DataType, starting_struct.kind).expressions:\n        name = field.name\n\n        # Ambiguous or anonymous fields can't be expanded\n        if name in taken_names or not isinstance(field.this, exp.Identifier):\n            return []\n\n        taken_names.add(name)\n\n        this = field.this.copy()\n        root, *parts = [part.copy() for part in itertools.chain(dot_parts, [this])]\n        new_column = exp.column(\n            t.cast(exp.Identifier, root),\n            table=dot_column.args.get(\"table\"),\n            fields=t.cast(t.List[exp.Identifier], parts),\n        )\n        new_selections.append(alias(new_column, this, copy=False).assert_is(exp.Alias))\n\n    return new_selections\n\n\ndef _expand_struct_stars_with_parens(expression: exp.Dot) -> t.List[exp.Alias]:\n    \"\"\"[RisingWave] Expand/Flatten (<exp>.bar).*, where bar is a struct column\"\"\"\n\n    # it is not (<sub_exp>).* pattern, which means we can't expand\n    if not isinstance(expression.this, exp.Paren):\n        return []\n\n    # find column definition to get data-type\n    dot_column = expression.find(exp.Column)\n    if not isinstance(dot_column, exp.Column) or not dot_column.is_type(exp.DType.STRUCT):\n        return []\n\n    parent = dot_column.parent\n    starting_struct = dot_column.type\n\n    # walk up AST and down into struct definition in sync\n    while parent is not None:\n        if isinstance(parent, exp.Paren):\n            parent = parent.parent\n            continue\n\n        # if parent is not a dot, then something is wrong\n        if not isinstance(parent, exp.Dot):\n            return []\n\n        # if the rhs of the dot is star we are done\n        rhs = parent.right\n        if isinstance(rhs, exp.Star):\n            break\n\n        # if it is not identifier, then something is wrong\n        if not isinstance(rhs, exp.Identifier):\n            return []\n\n        # Check if current rhs identifier is in struct\n        matched = False\n        for struct_field_def in t.cast(exp.DataType, starting_struct).expressions:\n            if struct_field_def.name == rhs.name:\n                matched = True\n                starting_struct = struct_field_def.kind  # update struct\n                break\n\n        if not matched:\n            return []\n\n        parent = parent.parent\n\n    # build new aliases to expand star\n    new_selections = []\n\n    # fetch the outermost parentheses for new aliaes\n    outer_paren = expression.this\n\n    for struct_field_def in t.cast(exp.DataType, starting_struct).expressions:\n        new_identifier = struct_field_def.this.copy()\n        new_dot = exp.Dot.build([outer_paren.copy(), new_identifier])\n        new_alias = alias(new_dot, new_identifier, copy=False).assert_is(exp.Alias)\n        new_selections.append(new_alias)\n\n    return new_selections\n\n\ndef _expand_stars(\n    scope: Scope,\n    resolver: Resolver,\n    using_column_tables: t.Dict[str, t.Any],\n    pseudocolumns: t.Set[str],\n    annotator: TypeAnnotator,\n) -> None:\n    \"\"\"Expand stars to lists of column selections\"\"\"\n\n    new_selections: t.List[exp.Expr] = []\n    except_columns: t.Dict[int, t.Set[str]] = {}\n    replace_columns: t.Dict[int, t.Dict[str, exp.Alias]] = {}\n    rename_columns: t.Dict[int, t.Dict[str, str]] = {}\n\n    coalesced_columns = set()\n    dialect = resolver.dialect\n\n    pivot_output_columns = None\n    pivot_exclude_columns: t.Set[str] = set()\n\n    pivot = t.cast(t.Optional[exp.Pivot], seq_get(scope.pivots, 0))\n    if isinstance(pivot, exp.Pivot) and not pivot.alias_column_names:\n        if pivot.unpivot:\n            pivot_output_columns = [c.output_name for c in _unpivot_columns(pivot)]\n\n            for field in pivot.fields:\n                if isinstance(field, exp.In):\n                    pivot_exclude_columns.update(\n                        c.output_name for e in field.expressions for c in e.find_all(exp.Column)\n                    )\n\n        else:\n            pivot_exclude_columns = set(c.output_name for c in pivot.find_all(exp.Column))\n\n            pivot_output_columns = [c.output_name for c in pivot.args.get(\"columns\", [])]\n            if not pivot_output_columns:\n                pivot_output_columns = [c.alias_or_name for c in pivot.expressions]\n\n    if dialect.SUPPORTS_STRUCT_STAR_EXPANSION and any(\n        isinstance(col, exp.Dot) for col in scope.stars\n    ):\n        # Found struct expansion, annotate scope ahead of time\n        annotator.annotate_scope(scope)\n\n    scope_expression = scope.expression\n\n    if not isinstance(scope_expression, exp.Selectable):\n        return\n\n    for expression in scope_expression.selects:\n        tables: t.List[str] = []\n        if isinstance(expression, exp.Star):\n            tables.extend(scope.selected_sources)\n            _add_except_columns(expression, tables, except_columns)\n            _add_replace_columns(expression, tables, replace_columns)\n            _add_rename_columns(expression, tables, rename_columns)\n        elif expression.is_star:\n            if isinstance(expression, exp.Column):\n                tables.append(expression.table)\n                _add_except_columns(expression.this, tables, except_columns)\n                _add_replace_columns(expression.this, tables, replace_columns)\n                _add_rename_columns(expression.this, tables, rename_columns)\n            elif isinstance(expression, exp.Dot):\n                if (\n                    dialect.SUPPORTS_STRUCT_STAR_EXPANSION\n                    and not dialect.REQUIRES_PARENTHESIZED_STRUCT_ACCESS\n                ):\n                    struct_fields = _expand_struct_stars_no_parens(expression)\n                    if struct_fields:\n                        new_selections.extend(struct_fields)\n                        continue\n                elif dialect.REQUIRES_PARENTHESIZED_STRUCT_ACCESS:\n                    struct_fields = _expand_struct_stars_with_parens(expression)\n                    if struct_fields:\n                        new_selections.extend(struct_fields)\n                        continue\n\n        if not tables:\n            new_selections.append(expression)\n            continue\n\n        for table in tables:\n            if table not in scope.sources:\n                raise OptimizeError(f\"Unknown table: {table}\")\n\n            columns = resolver.get_source_columns(table, only_visible=True)\n            columns = columns or scope.outer_columns\n\n            if pseudocolumns and dialect.EXCLUDES_PSEUDOCOLUMNS_FROM_STAR:\n                columns = [name for name in columns if name.upper() not in pseudocolumns]\n\n            if not columns or \"*\" in columns:\n                return\n\n            table_id = id(table)\n            columns_to_exclude = except_columns.get(table_id) or set()\n            renamed_columns = rename_columns.get(table_id, {})\n            replaced_columns = replace_columns.get(table_id, {})\n\n            if pivot:\n                if pivot_output_columns and pivot_exclude_columns:\n                    pivot_columns = [c for c in columns if c not in pivot_exclude_columns]\n                    pivot_columns.extend(pivot_output_columns)\n                else:\n                    pivot_columns = pivot.alias_column_names\n\n                if pivot_columns:\n                    new_selections.extend(\n                        alias(exp.column(name, table=pivot.alias), name, copy=False)\n                        for name in pivot_columns\n                        if name not in columns_to_exclude\n                    )\n                    continue\n\n            for name in columns:\n                if name in columns_to_exclude or name in coalesced_columns:\n                    continue\n                if name in using_column_tables and table in using_column_tables[name]:\n                    coalesced_columns.add(name)\n                    # TODO (mypyc): use a separate variable to avoid reusing `tables` (list) with dict type\n                    using_tables = using_column_tables[name]\n                    coalesce_args = [exp.column(name, table=table) for table in using_tables]\n\n                    new_selections.append(\n                        alias(exp.func(\"coalesce\", *coalesce_args), alias=name, copy=False)\n                    )\n                else:\n                    alias_ = renamed_columns.get(name, name)\n                    selection_expr = replaced_columns.get(name) or exp.column(name, table=table)\n                    new_selections.append(\n                        alias(selection_expr, alias_, copy=False)\n                        if alias_ != name\n                        else selection_expr\n                    )\n\n    # Ensures we don't overwrite the initial selections with an empty list\n    if new_selections and isinstance(scope_expression, exp.Select):\n        scope_expression.set(\"expressions\", new_selections)\n\n\ndef _add_except_columns(\n    expression: exp.Expr, tables, except_columns: t.Dict[int, t.Set[str]]\n) -> None:\n    except_ = expression.args.get(\"except_\")\n\n    if not except_:\n        return\n\n    columns = {e.name for e in except_}\n\n    for table in tables:\n        except_columns[id(table)] = columns\n\n\ndef _add_rename_columns(\n    expression: exp.Expr, tables, rename_columns: t.Dict[int, t.Dict[str, str]]\n) -> None:\n    rename = expression.args.get(\"rename\")\n\n    if not rename:\n        return\n\n    columns = {e.this.name: e.alias for e in rename}\n\n    for table in tables:\n        rename_columns[id(table)] = columns\n\n\ndef _add_replace_columns(\n    expression: exp.Expr, tables, replace_columns: t.Dict[int, t.Dict[str, exp.Alias]]\n) -> None:\n    replace = expression.args.get(\"replace\")\n\n    if not replace:\n        return\n\n    columns = {e.alias: e for e in replace}\n\n    for table in tables:\n        replace_columns[id(table)] = columns\n\n\ndef qualify_outputs(scope_or_expression: Scope | exp.Expr) -> None:\n    \"\"\"Ensure all output columns are aliased\"\"\"\n    if isinstance(scope_or_expression, exp.Expr):\n        scope = build_scope(scope_or_expression)\n        if not isinstance(scope, Scope):\n            return\n    else:\n        scope = scope_or_expression\n\n    expression = scope.expression\n\n    if not isinstance(expression, exp.Selectable):\n        return\n\n    new_selections = []\n\n    for i, (selection, aliased_column) in enumerate(\n        itertools.zip_longest(expression.selects, scope.outer_columns)\n    ):\n        if selection is None or isinstance(selection, exp.QueryTransform):\n            break\n\n        if isinstance(selection, exp.Subquery):\n            if not selection.output_name:\n                selection.set(\"alias\", exp.TableAlias(this=exp.to_identifier(f\"_col_{i}\")))\n        elif not isinstance(selection, (exp.Alias, exp.Aliases)) and not selection.is_star:\n            selection = alias(\n                selection,\n                alias=selection.output_name or f\"_col_{i}\",\n                copy=False,\n            )\n        if aliased_column:\n            selection.set(\"alias\", exp.to_identifier(aliased_column))\n\n        new_selections.append(selection)\n\n    if new_selections and isinstance(expression, exp.Select):\n        expression.set(\"expressions\", new_selections)\n\n\ndef quote_identifiers(expression: E, dialect: DialectType = None, identify: bool = True) -> E:\n    \"\"\"Makes sure all identifiers that need to be quoted are quoted.\"\"\"\n    return expression.transform(\n        Dialect.get_or_raise(dialect).quote_identifier, identify=identify, copy=False\n    )  # type: ignore\n\n\ndef pushdown_cte_alias_columns(scope: Scope) -> None:\n    \"\"\"\n    Pushes down the CTE alias columns into the projection,\n\n    This step is useful in Snowflake where the CTE alias columns can be referenced in the HAVING.\n\n    Args:\n        scope: Scope to find ctes to pushdown aliases.\n    \"\"\"\n    for cte in scope.ctes:\n        if cte.alias_column_names and isinstance(cte.this, exp.Select):\n            new_expressions = []\n            for _alias, projection in zip(cte.alias_column_names, cte.this.expressions):\n                if isinstance(projection, exp.Alias):\n                    projection.set(\"alias\", exp.to_identifier(_alias))\n                else:\n                    projection = alias(projection, alias=_alias)\n                new_expressions.append(projection)\n            cte.this.set(\"expressions\", new_expressions)\n"
  },
  {
    "path": "sqlglot/optimizer/qualify_tables.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect, DialectType\nfrom sqlglot.helper import name_sequence, seq_get, ensure_list\nfrom sqlglot.optimizer.normalize_identifiers import normalize_identifiers\nfrom sqlglot.optimizer.scope import Scope, traverse_scope\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n\n\ndef qualify_tables(\n    expression: E,\n    db: t.Optional[str | exp.Identifier] = None,\n    catalog: t.Optional[str | exp.Identifier] = None,\n    on_qualify: t.Optional[t.Callable[[exp.Table], None]] = None,\n    dialect: DialectType = None,\n    canonicalize_table_aliases: bool = False,\n) -> E:\n    \"\"\"\n    Rewrite sqlglot AST to have fully qualified tables. Join constructs such as\n    (t1 JOIN t2) AS t will be expanded into (SELECT * FROM t1 AS t1, t2 AS t2) AS t.\n\n    Examples:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one(\"SELECT 1 FROM tbl\")\n        >>> qualify_tables(expression, db=\"db\").sql()\n        'SELECT 1 FROM db.tbl AS tbl'\n        >>>\n        >>> expression = sqlglot.parse_one(\"SELECT 1 FROM (t1 JOIN t2) AS t\")\n        >>> qualify_tables(expression).sql()\n        'SELECT 1 FROM (SELECT * FROM t1 AS t1, t2 AS t2) AS t'\n\n    Args:\n        expression: Expr to qualify\n        db: Database name\n        catalog: Catalog name\n        on_qualify: Callback after a table has been qualified.\n        dialect: The dialect to parse catalog and schema into.\n        canonicalize_table_aliases: Whether to use canonical aliases (_0, _1, ...) for all sources\n            instead of preserving table names. Defaults to False.\n\n    Returns:\n        The qualified expression.\n    \"\"\"\n    dialect = Dialect.get_or_raise(dialect)\n    next_alias_name = name_sequence(\"_\")\n\n    if db := db or None:\n        db = exp.parse_identifier(db, dialect=dialect)\n        db.meta[\"is_table\"] = True\n        db = normalize_identifiers(db, dialect=dialect)\n    if catalog := catalog or None:\n        catalog = exp.parse_identifier(catalog, dialect=dialect)\n        catalog.meta[\"is_table\"] = True\n        catalog = normalize_identifiers(catalog, dialect=dialect)\n\n    def _qualify(table: exp.Table) -> None:\n        if isinstance(table.this, exp.Identifier):\n            if db and not table.args.get(\"db\"):\n                table.set(\"db\", db.copy())\n            if catalog and not table.args.get(\"catalog\") and table.args.get(\"db\"):\n                table.set(\"catalog\", catalog.copy())\n\n    if (db or catalog) and not isinstance(expression, exp.Query):\n        with_ = expression.args.get(\"with_\") or exp.With()\n        cte_names = {cte.alias_or_name for cte in with_.expressions}\n\n        for node in expression.walk(prune=lambda n: isinstance(n, exp.Query)):\n            if isinstance(node, exp.Table) and node.name not in cte_names:\n                _qualify(node)\n\n    def _set_alias(\n        expression: exp.Expr,\n        canonical_aliases: t.Dict[str, str],\n        target_alias: t.Optional[str] = None,\n        scope: t.Optional[Scope] = None,\n        normalize: bool = False,\n        columns: t.Optional[t.List[t.Union[str, exp.Identifier]]] = None,\n    ) -> None:\n        alias = expression.args.get(\"alias\") or exp.TableAlias()\n\n        if canonicalize_table_aliases:\n            new_alias_name = next_alias_name()\n            canonical_aliases[alias.name or target_alias or \"\"] = new_alias_name\n        elif not alias.name:\n            new_alias_name = target_alias or next_alias_name()\n            if normalize and target_alias:\n                new_alias_name = normalize_identifiers(new_alias_name, dialect=dialect).name\n        else:\n            return\n\n        alias.set(\"this\", exp.to_identifier(new_alias_name))\n\n        if columns:\n            alias.set(\"columns\", [exp.to_identifier(c) for c in columns])\n\n        expression.set(\"alias\", alias)\n\n        if scope:\n            scope.rename_source(None, new_alias_name)\n\n    for scope in traverse_scope(expression):\n        local_columns = scope.local_columns\n        canonical_aliases: t.Dict[str, str] = {}\n\n        for query in scope.subqueries:\n            subquery = query.parent\n            if isinstance(subquery, exp.Subquery):\n                subquery.unwrap().replace(subquery)\n\n        for derived_table in scope.derived_tables:\n            unnested = derived_table.unnest()\n            if isinstance(unnested, exp.Table):\n                joins = unnested.args.get(\"joins\")\n                unnested.set(\"joins\", None)\n                derived_table.this.replace(exp.select(\"*\").from_(unnested.copy(), copy=False))\n                derived_table.this.set(\"joins\", joins)\n\n            _set_alias(derived_table, canonical_aliases, scope=scope)\n            if pivot := seq_get(derived_table.args.get(\"pivots\") or [], 0):\n                _set_alias(pivot, canonical_aliases)\n\n        table_aliases = {}\n\n        for name, source in scope.sources.items():\n            if isinstance(source, exp.Table):\n                # When the name is empty, it means that we have a non-table source, e.g. a pivoted cte\n                is_real_table_source = bool(name)\n\n                if pivot := seq_get(source.args.get(\"pivots\") or [], 0):\n                    name = source.name\n\n                table_this = source.this\n                table_alias = source.args.get(\"alias\")\n                function_columns: t.List[t.Union[str, exp.Identifier]] = []\n                if isinstance(table_this, exp.Func):\n                    if not table_alias:\n                        function_columns = ensure_list(\n                            dialect.DEFAULT_FUNCTIONS_COLUMN_NAMES.get(type(table_this))\n                        )\n                    elif columns := table_alias.columns:\n                        function_columns = columns\n                    elif type(table_this) in dialect.DEFAULT_FUNCTIONS_COLUMN_NAMES:\n                        function_columns = ensure_list(source.alias_or_name)\n                        source.set(\"alias\", None)\n                        name = \"\"\n\n                _set_alias(\n                    source,\n                    canonical_aliases,\n                    target_alias=name or source.name or None,\n                    normalize=True,\n                    columns=function_columns,\n                )\n\n                source_fqn = \".\".join(p.name for p in source.parts)\n                had_explicit_alias = table_alias and table_alias.name\n                if not had_explicit_alias or source_fqn not in table_aliases:\n                    table_aliases[source_fqn] = source.args[\"alias\"].this.copy()\n\n                if pivot:\n                    target_alias = source.alias if pivot.unpivot else None\n                    _set_alias(pivot, canonical_aliases, target_alias=target_alias, normalize=True)\n\n                    # This case corresponds to a pivoted CTE, we don't want to qualify that\n                    if isinstance(scope.sources.get(source.alias_or_name), Scope):\n                        continue\n\n                if is_real_table_source:\n                    _qualify(source)\n\n                    if on_qualify:\n                        on_qualify(source)\n            elif isinstance(source, Scope) and source.is_udtf:\n                _set_alias(udtf := source.expression, canonical_aliases)\n\n                table_alias = udtf.args[\"alias\"]\n\n                if isinstance(udtf, exp.Values) and not table_alias.columns:\n                    column_aliases = [\n                        normalize_identifiers(i, dialect=dialect)\n                        for i in dialect.generate_values_aliases(udtf)\n                    ]\n                    table_alias.set(\"columns\", column_aliases)\n\n        for table in scope.tables:\n            if not table.alias and isinstance(table.parent, (exp.From, exp.Join)):\n                _set_alias(table, canonical_aliases, target_alias=table.name)\n\n        for column in local_columns:\n            column_table = column.table\n\n            if column.db:\n                table_alias = table_aliases.get(\".\".join(p.name for p in column.parts[0:-1]))\n\n                if table_alias:\n                    for p in exp.COLUMN_PARTS[1:]:\n                        column.set(p, None)\n\n                    column.set(\"table\", table_alias.copy())\n            elif (\n                canonical_aliases\n                and column_table\n                and (canonical_table := canonical_aliases.get(column_table, \"\")) != column_table\n            ):\n                # Amend existing aliases, e.g. t.c -> _0.c if t is aliased to _0\n                column.set(\"table\", exp.to_identifier(canonical_table))\n\n    return expression\n"
  },
  {
    "path": "sqlglot/optimizer/resolver.py",
    "content": "from __future__ import annotations\n\nimport itertools\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import Dialect\nfrom sqlglot.errors import OptimizeError\nfrom sqlglot.helper import seq_get, SingleValuedMapping\nfrom sqlglot.optimizer.scope import Scope\n\nif t.TYPE_CHECKING:\n    from sqlglot.schema import Schema\n    from collections.abc import Sequence, Mapping\n\n\nclass Resolver:\n    \"\"\"\n    Helper for resolving columns.\n\n    This is a class so we can lazily load some things and easily share them across functions.\n    \"\"\"\n\n    def __init__(self, scope: Scope, schema: Schema, infer_schema: bool = True):\n        self.scope = scope\n        self.schema = schema\n        self.dialect = schema.dialect or Dialect()\n        self._source_columns: t.Optional[dict[str, Sequence[str]]] = None\n        self._unambiguous_columns: t.Optional[Mapping[str, str]] = None\n        self._all_columns: t.Optional[set[str]] = None\n        self._infer_schema = infer_schema\n        self._get_source_columns_cache: dict[tuple[str, bool], Sequence[str]] = {}\n\n    def get_table(self, column: str | exp.Column) -> t.Optional[exp.Identifier]:\n        \"\"\"\n        Get the table for a column name.\n\n        Args:\n            column: The column expression (or column name) to find the table for.\n        Returns:\n            The table name if it can be found/inferred.\n        \"\"\"\n        column_name = column if isinstance(column, str) else column.name\n\n        table_name = self._get_table_name_from_sources(column_name)\n\n        if not table_name and isinstance(column, exp.Column):\n            # Fall-back case: If we couldn't find the `table_name` from ALL of the sources,\n            # attempt to disambiguate the column based on other characteristics e.g if this column is in a join condition,\n            # we may be able to disambiguate based on the source order.\n            if join_context := self._get_column_join_context(column):\n                # In this case, the return value will be the join that _may_ be able to disambiguate the column\n                # and we can use the source columns available at that join to get the table name\n                # catch OptimizeError if column is still ambiguous and try to resolve with schema inference below\n                try:\n                    table_name = self._get_table_name_from_sources(\n                        column_name, self._get_available_source_columns(join_context)\n                    )\n                except OptimizeError:\n                    pass\n\n        if not table_name and self._infer_schema:\n            sources_without_schema = tuple(\n                source\n                for source, columns in self._get_all_source_columns().items()\n                if not columns or \"*\" in columns\n            )\n            if len(sources_without_schema) == 1:\n                table_name = sources_without_schema[0]\n\n        if table_name not in self.scope.selected_sources:\n            return exp.to_identifier(table_name)\n\n        node: exp.Expr = self.scope.selected_sources[table_name][0]\n\n        if isinstance(node, exp.Query):\n            while node and node.alias != table_name and node.parent:\n                node = node.parent\n\n        node_alias = node.args.get(\"alias\")\n        if node_alias:\n            return exp.to_identifier(node_alias.this)\n\n        return exp.to_identifier(table_name)\n\n    @property\n    def all_columns(self) -> t.Set[str]:\n        \"\"\"All available columns of all sources in this scope\"\"\"\n        if self._all_columns is None:\n            self._all_columns = {\n                column for columns in self._get_all_source_columns().values() for column in columns\n            }\n        return self._all_columns\n\n    def get_source_columns_from_set_op(self, expression: exp.Expr) -> t.List[str]:\n        if isinstance(expression, exp.Select):\n            return expression.named_selects\n        if isinstance(expression, exp.Subquery) and isinstance(expression.this, exp.SetOperation):\n            # Different types of SET modifiers can be chained together if they're explicitly grouped by nesting\n            return self.get_source_columns_from_set_op(expression.this)\n        if not isinstance(expression, exp.SetOperation):\n            raise OptimizeError(f\"Unknown set operation: {expression}\")\n\n        set_op = expression\n\n        # BigQuery specific set operations modifiers, e.g INNER UNION ALL BY NAME\n        on_column_list = set_op.args.get(\"on\")\n\n        if on_column_list:\n            # The resulting columns are the columns in the ON clause:\n            # {INNER | LEFT | FULL} UNION ALL BY NAME ON (col1, col2, ...)\n            columns = [col.name for col in on_column_list]\n        elif set_op.side or set_op.kind:\n            side = set_op.side\n            kind = set_op.kind\n\n            # Visit the children UNIONs (if any) in a post-order traversal\n            left = self.get_source_columns_from_set_op(set_op.left)\n            right = self.get_source_columns_from_set_op(set_op.right)\n\n            # We use dict.fromkeys to deduplicate keys and maintain insertion order\n            if side == \"LEFT\":\n                columns = left\n            elif side == \"FULL\":\n                columns = list(dict.fromkeys(left + right))\n            elif kind == \"INNER\":\n                columns = list(dict.fromkeys(left).keys() & dict.fromkeys(right).keys())\n        else:\n            columns = set_op.named_selects\n\n        return columns\n\n    def get_source_columns(self, name: str, only_visible: bool = False) -> Sequence[str]:\n        \"\"\"Resolve the source columns for a given source `name`.\"\"\"\n        cache_key = (name, only_visible)\n        if cache_key not in self._get_source_columns_cache:\n            if name not in self.scope.sources:\n                raise OptimizeError(f\"Unknown table: {name}\")\n\n            source = self.scope.sources[name]\n\n            if isinstance(source, exp.Table):\n                columns = self.schema.column_names(source, only_visible)\n            elif isinstance(source, Scope) and isinstance(\n                source.expression, (exp.Values, exp.Unnest)\n            ):\n                columns = source.expression.named_selects\n\n                # in bigquery, unnest structs are automatically scoped as tables, so you can\n                # directly select a struct field in a query.\n                # this handles the case where the unnest is statically defined.\n                if self.dialect.UNNEST_COLUMN_ONLY and isinstance(source.expression, exp.Unnest):\n                    unnest = source.expression\n\n                    # if type is not annotated yet, try to get it from the schema\n                    if not unnest.type or unnest.type.is_type(exp.DType.UNKNOWN):\n                        unnest_expr = seq_get(unnest.expressions, 0)\n                        if isinstance(unnest_expr, exp.Column) and self.scope.parent:\n                            col_type = self._get_unnest_column_type(unnest_expr)\n                            # extract element type if it's an ARRAY\n                            if col_type and col_type.is_type(exp.DType.ARRAY):\n                                element_types = col_type.expressions\n                                if element_types:\n                                    unnest.type = element_types[0].copy()\n                            else:\n                                if col_type:\n                                    unnest.type = col_type.copy()\n                    # check if the result type is a STRUCT - extract struct field names\n                    if unnest.is_type(exp.DType.STRUCT):\n                        for k in unnest.type.expressions:  # type: ignore\n                            columns.append(k.name)\n            elif isinstance(source, Scope) and isinstance(source.expression, exp.SetOperation):\n                columns = self.get_source_columns_from_set_op(source.expression)\n            else:\n                selectable = source.expression.assert_is(exp.Selectable)\n                select = seq_get(selectable.selects, 0)\n\n                if isinstance(select, exp.QueryTransform):\n                    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-qry-select-transform.html\n                    schema = select.args.get(\"schema\")\n                    columns = [c.name for c in schema.expressions] if schema else [\"key\", \"value\"]\n                else:\n                    columns = selectable.named_selects\n\n            node, _ = self.scope.selected_sources.get(name) or (None, None)\n            if isinstance(node, Scope):\n                column_aliases = node.expression.alias_column_names\n            elif isinstance(node, exp.Expr):\n                column_aliases = node.alias_column_names\n            else:\n                column_aliases = []\n\n            if column_aliases:\n                # If the source's columns are aliased, their aliases shadow the corresponding column names.\n                # This can be expensive if there are lots of columns, so only do this if column_aliases exist.\n                columns = [\n                    alias or name\n                    for (name, alias) in itertools.zip_longest(columns, column_aliases)\n                ]\n\n            self._get_source_columns_cache[cache_key] = columns\n\n        return self._get_source_columns_cache[cache_key]\n\n    def _get_all_source_columns(self) -> dict[str, Sequence[str]]:\n        if self._source_columns is None:\n            self._source_columns = {\n                source_name: self.get_source_columns(source_name)\n                for source_name, source in itertools.chain(\n                    self.scope.selected_sources.items(), self.scope.lateral_sources.items()\n                )\n            }\n        return self._source_columns\n\n    def _get_table_name_from_sources(\n        self, column_name: str, source_columns: t.Optional[dict[str, Sequence[str]]] = None\n    ) -> t.Optional[str]:\n        if not source_columns:\n            # If not supplied, get all sources to calculate unambiguous columns\n            if self._unambiguous_columns is None:\n                self._unambiguous_columns = self._get_unambiguous_columns(\n                    self._get_all_source_columns()\n                )\n\n            unambiguous_columns = self._unambiguous_columns\n        else:\n            unambiguous_columns = self._get_unambiguous_columns(source_columns)\n\n        return unambiguous_columns.get(column_name)\n\n    def _get_column_join_context(self, column: exp.Column) -> t.Optional[exp.Join]:\n        \"\"\"\n        Check if a column participating in a join can be qualified based on the source order.\n        \"\"\"\n        args = self.scope.expression.args\n        joins = args.get(\"joins\")\n\n        if not joins or args.get(\"laterals\") or args.get(\"pivots\"):\n            # Feature gap: We currently don't try to disambiguate columns if other sources\n            # (e.g laterals, pivots) exist alongside joins\n            return None\n\n        join_ancestor = column.find_ancestor(exp.Join, exp.Select)\n\n        if (\n            isinstance(join_ancestor, exp.Join)\n            and join_ancestor.alias_or_name in self.scope.selected_sources\n        ):\n            # Ensure that the found ancestor is a join that contains an actual source,\n            # e.g in Clickhouse `b` is an array expression in `a ARRAY JOIN b`\n            return join_ancestor\n\n        return None\n\n    def _get_available_source_columns(self, join_ancestor: exp.Join) -> dict[str, Sequence[str]]:\n        \"\"\"\n        Get the source columns that are available at the point where a column is referenced.\n\n        For columns in JOIN conditions, this only includes tables that have been joined\n        up to that point. Example:\n\n        ```\n        SELECT * FROM t_1 INNER JOIN ... INNER JOIN t_n ON t_1.a = c INNER JOIN t_n+1 ON ...\n        ```                                                        ^\n                                                                   |\n                                +----------------------------------+\n                                |\n                                ⌄\n        The unqualified column `c` is not ambiguous if no other sources up until that\n        join i.e t_1, ..., t_n, contain a column named `c`.\n\n        \"\"\"\n        args = self.scope.expression.args\n\n        # Collect tables in order: FROM clause tables + joined tables up to current join\n        from_name = args[\"from_\"].alias_or_name\n        available_sources = {from_name: self.get_source_columns(from_name)}\n\n        for join in args[\"joins\"][: t.cast(int, join_ancestor.index) + 1]:\n            available_sources[join.alias_or_name] = self.get_source_columns(join.alias_or_name)\n\n        return available_sources\n\n    def _get_unambiguous_columns(\n        self, source_columns: dict[str, Sequence[str]]\n    ) -> Mapping[str, str]:\n        \"\"\"\n        Find all the unambiguous columns in sources.\n\n        Args:\n            source_columns: Mapping of names to source columns.\n\n        Returns:\n            Mapping of column name to source name.\n        \"\"\"\n        if not source_columns:\n            return {}\n\n        source_columns_pairs = list(source_columns.items())\n\n        first_table, first_columns = source_columns_pairs[0]\n\n        if len(source_columns_pairs) == 1:\n            # Performance optimization - avoid copying first_columns if there is only one table.\n            return SingleValuedMapping(first_columns, first_table)\n\n        # For BigQuery UNNEST_COLUMN_ONLY, build a mapping of original UNNEST aliases\n        # from alias.columns[0] to their source names. This is used to resolve shadowing\n        # where an UNNEST alias shadows a column name from another table.\n        unnest_original_aliases: t.Dict[str, str] = {}\n        if self.dialect.UNNEST_COLUMN_ONLY:\n            unnest_original_aliases = {\n                alias_arg.columns[0].name: source_name\n                for source_name, source in self.scope.sources.items()\n                if (\n                    isinstance(source.expression, exp.Unnest)\n                    and (alias_arg := source.expression.args.get(\"alias\"))\n                    and alias_arg.columns\n                )\n            }\n\n        unambiguous_columns = {col: first_table for col in first_columns}\n        all_columns = set(unambiguous_columns)\n\n        for table, columns in source_columns_pairs[1:]:\n            unique = set(columns)\n            ambiguous = all_columns.intersection(unique)\n            all_columns.update(columns)\n\n            for column in ambiguous:\n                if column in unnest_original_aliases:\n                    unambiguous_columns[column] = unnest_original_aliases[column]\n                    continue\n\n                unambiguous_columns.pop(column, None)\n            for column in unique.difference(ambiguous):\n                unambiguous_columns[column] = table\n\n        return unambiguous_columns\n\n    def _get_unnest_column_type(self, column: exp.Column) -> t.Optional[exp.DataType]:\n        \"\"\"\n        Get the type of a column being unnested, tracing through CTEs/subqueries to find the base table.\n\n        Args:\n            column: The column expression being unnested.\n\n        Returns:\n            The DataType of the column, or None if not found.\n        \"\"\"\n        scope = self.scope.parent\n        assert scope\n\n        # if column is qualified, use that table, otherwise disambiguate using the resolver\n        if column.table:\n            table_name = column.table\n        else:\n            # use the parent scope's resolver to disambiguate the column\n            parent_resolver = Resolver(scope, self.schema, self._infer_schema)\n            table_identifier = parent_resolver.get_table(column)\n            if not table_identifier:\n                return None\n            table_name = table_identifier.name\n\n        source = scope.sources.get(table_name)\n        return self._get_column_type_from_scope(source, column) if source else None\n\n    def _get_column_type_from_scope(\n        self, source: t.Union[Scope, exp.Table], column: exp.Column\n    ) -> t.Optional[exp.DataType]:\n        \"\"\"\n        Get a column's type by tracing through scopes/tables to find the base table.\n\n        Args:\n            source: The source to search - can be a Scope (to iterate its sources) or a Table.\n            column: The column to find the type for.\n\n        Returns:\n            The DataType of the column, or None if not found.\n        \"\"\"\n        if isinstance(source, exp.Table):\n            # base table - get the column type from schema\n            col_type: t.Optional[exp.DataType] = self.schema.get_column_type(source, column)\n            if col_type and not col_type.is_type(exp.DType.UNKNOWN):\n                return col_type\n        elif isinstance(source, Scope):\n            # iterate over all sources in the scope\n            for source_name, nested_source in source.sources.items():\n                col_type = self._get_column_type_from_scope(nested_source, column)\n                if col_type and not col_type.is_type(exp.DType.UNKNOWN):\n                    return col_type\n\n        return None\n"
  },
  {
    "path": "sqlglot/optimizer/scope.py",
    "content": "from __future__ import annotations\n\nimport itertools\nimport logging\nimport typing as t\nfrom collections import defaultdict\nfrom enum import Enum, auto\n\nfrom sqlglot import exp\nfrom sqlglot.errors import OptimizeError\nfrom sqlglot.helper import find_new_name, mypyc_attr, seq_get\nfrom builtins import type as Type\n\nlogger = logging.getLogger(\"sqlglot\")\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from collections.abc import Iterator\n\nTRAVERSABLES = (exp.Query, exp.DDL, exp.DML)\n\n\nclass ScopeType(Enum):\n    ROOT = auto()\n    SUBQUERY = auto()\n    DERIVED_TABLE = auto()\n    CTE = auto()\n    UNION = auto()\n    UDTF = auto()\n\n\n@mypyc_attr(native_class=True)\nclass Scope:\n    \"\"\"\n    Selection scope.\n\n    Attributes:\n        expression: Root expression of this scope\n        sources: Mapping of source name to either\n            a Table expression or another Scope instance. For example:\n                SELECT * FROM x                     {\"x\": Table(this=\"x\")}\n                SELECT * FROM x AS y                {\"y\": Table(this=\"x\")}\n                SELECT * FROM (SELECT ...) AS y     {\"y\": Scope(...)}\n        lateral_sources: Sources from laterals\n            For example:\n                SELECT c FROM x LATERAL VIEW EXPLODE (a) AS c;\n            The LATERAL VIEW EXPLODE gets x as a source.\n        cte_sources: Sources from CTES\n        outer_columns: If this is a derived table or CTE, and the outer query\n            defines a column list for the alias of this scope, this is that list of columns.\n            For example:\n                SELECT * FROM (SELECT ...) AS y(col1, col2)\n            The inner query would have `[\"col1\", \"col2\"]` for its `outer_columns`\n        parent: Parent scope\n        scope_type: Type of this scope, relative to it's parent\n        subquery_scopes: List of all child scopes for subqueries\n        cte_scopes: List of all child scopes for CTEs\n        derived_table_scopes: List of all child scopes for derived_tables\n        udtf_scopes: List of all child scopes for user defined tabular functions\n        table_scopes: derived_table_scopes + udtf_scopes, in the order that they're defined\n        union_scopes: If this Scope is for a Union expression, this will be\n            a list of the left and right child scopes.\n    \"\"\"\n\n    _collected: bool\n    _raw_columns: t.List[exp.Column]\n    _table_columns: t.List[exp.TableColumn]\n    _stars: t.List[exp.Column | exp.Dot]\n    _derived_tables: t.List[exp.Subquery]\n    _udtfs: t.List[exp.UDTF]\n    _tables: t.List[exp.Table]\n    _ctes: t.List[exp.CTE]\n    _subqueries: t.List[exp.Select | exp.SetOperation]\n    _join_hints: t.List[exp.JoinHint]\n    _semi_anti_join_tables: t.Set[str]\n    _column_index: t.Set[int]\n    _selected_sources: t.Optional[t.Dict[str, t.Tuple[exp.Selectable, exp.Table | Scope]]]\n    _columns: t.Optional[t.List[exp.Column]]\n    _external_columns: t.Optional[t.List[exp.Column]]\n    _local_columns: t.Optional[t.List[exp.Column]]\n    _pivots: t.Optional[t.List[exp.Pivot]]\n    _references: t.Optional[t.List[t.Tuple[str, exp.Selectable]]]\n\n    def __init__(\n        self,\n        expression: exp.Expr,\n        sources: t.Optional[t.Dict[str, exp.Table | Scope]] = None,\n        outer_columns: t.Optional[t.List[str]] = None,\n        parent: t.Optional[Scope] = None,\n        scope_type: ScopeType = ScopeType.ROOT,\n        lateral_sources: t.Optional[t.Dict[str, exp.Table | Scope]] = None,\n        cte_sources: t.Optional[t.Dict[str, exp.Table | Scope]] = None,\n        can_be_correlated: t.Optional[bool] = None,\n    ) -> None:\n        self.expression = expression\n        self.sources = sources or {}\n        self.lateral_sources = lateral_sources or {}\n        self.cte_sources = cte_sources or {}\n        self.sources.update(self.lateral_sources)\n        self.sources.update(self.cte_sources)\n        self.outer_columns = outer_columns or []\n        self.parent = parent\n        self.scope_type = scope_type\n        self.subquery_scopes: t.List[Scope] = []\n        self.derived_table_scopes: t.List[Scope] = []\n        self.table_scopes: t.List[Scope] = []\n        self.cte_scopes: t.List[Scope] = []\n        self.union_scopes: t.List[Scope] = []\n        self.udtf_scopes: t.List[Scope] = []\n        self.can_be_correlated = can_be_correlated\n        self.clear_cache()\n\n    def clear_cache(self) -> None:\n        self._collected = False\n        self._raw_columns = []\n        self._table_columns = []\n        self._stars = []\n        self._derived_tables = []\n        self._udtfs = []\n        self._tables = []\n        self._ctes = []\n        self._subqueries = []\n        self._join_hints = []\n        self._semi_anti_join_tables = set()\n        self._column_index = set()\n        self._selected_sources = None\n        self._columns = None\n        self._external_columns = None\n        self._local_columns = None\n        self._pivots = None\n        self._references = None\n\n    def branch(\n        self,\n        expression: exp.Expr,\n        scope_type: ScopeType,\n        sources: t.Optional[t.Dict[str, exp.Table | Scope]] = None,\n        cte_sources: t.Optional[t.Dict[str, exp.Table | Scope]] = None,\n        lateral_sources: t.Optional[t.Dict[str, exp.Table | Scope]] = None,\n        outer_columns: t.Optional[t.List[str]] = None,\n    ) -> Scope:\n        \"\"\"Branch from the current scope to a new, inner scope\"\"\"\n        return Scope(\n            expression=expression.unnest(),\n            sources=sources.copy() if sources else None,\n            parent=self,\n            scope_type=scope_type,\n            cte_sources={**self.cte_sources, **(cte_sources or {})},\n            lateral_sources=lateral_sources.copy() if lateral_sources else None,\n            can_be_correlated=self.can_be_correlated\n            or scope_type in (ScopeType.SUBQUERY, ScopeType.UDTF),\n            outer_columns=outer_columns,\n        )\n\n    def _collect(self) -> None:\n        self._tables = []\n        self._ctes = []\n        self._subqueries = []\n        self._derived_tables = []\n        self._udtfs = []\n        self._raw_columns = []\n        self._table_columns = []\n        self._stars = []\n        self._join_hints = []\n        self._semi_anti_join_tables = set()\n        self._column_index = set()\n\n        for node in self.walk():\n            if node is self.expression:\n                continue\n\n            if isinstance(node, exp.Dot) and node.is_star:\n                self._stars.append(node)\n            elif type(node) is exp.Column:\n                self._column_index.add(id(node))\n\n                if isinstance(node.this, exp.Star):\n                    self._stars.append(node)\n                else:\n                    self._raw_columns.append(node)\n            elif isinstance(node, exp.Table) and not isinstance(node.parent, exp.JoinHint):\n                parent = node.parent\n                if isinstance(parent, exp.Join) and parent.is_semi_or_anti_join:\n                    self._semi_anti_join_tables.add(node.alias_or_name)\n\n                self._tables.append(node)\n            elif isinstance(node, exp.JoinHint):\n                self._join_hints.append(node)\n            elif isinstance(node, exp.UDTF):\n                self._udtfs.append(node)\n            elif isinstance(node, exp.CTE):\n                self._ctes.append(node)\n            elif _is_derived_table(node) and _is_from_or_join(node):\n                self._derived_tables.append(t.cast(exp.Subquery, node))\n            elif isinstance(node, exp.UNWRAPPED_QUERIES) and not _is_from_or_join(node):\n                self._subqueries.append(node)\n            elif isinstance(node, exp.TableColumn):\n                self._table_columns.append(node)\n\n        self._collected = True\n\n    def _ensure_collected(self) -> None:\n        if not self._collected:\n            self._collect()\n\n    def walk(self, prune: t.Optional[t.Callable[[exp.Expr], bool]] = None) -> Iterator[exp.Expr]:\n        return walk_in_scope(self.expression, prune=prune)\n\n    def find(self, *expression_types: Type[E]) -> t.Optional[E]:\n        return find_in_scope(self.expression, *expression_types)\n\n    def find_all(self, *expression_types: Type[E]) -> Iterator[E]:\n        return find_all_in_scope(self.expression, *expression_types)\n\n    def replace(self, old: exp.Expr, new: exp.Expr) -> None:\n        \"\"\"\n        Replace `old` with `new`.\n\n        This can be used instead of `exp.Expr.replace` to ensure the `Scope` is kept up-to-date.\n\n        Args:\n            old (exp.Expr): old node\n            new (exp.Expr): new node\n        \"\"\"\n        old.replace(new)\n        self.clear_cache()\n\n    @property\n    def tables(self) -> t.List[exp.Table]:\n        \"\"\"\n        List of tables in this scope.\n\n        Returns:\n            list[exp.Table]: tables\n        \"\"\"\n        self._ensure_collected()\n        return self._tables\n\n    @property\n    def ctes(self) -> t.List[exp.CTE]:\n        \"\"\"\n        List of CTEs in this scope.\n\n        Returns:\n            list[exp.CTE]: ctes\n        \"\"\"\n        self._ensure_collected()\n        return self._ctes\n\n    @property\n    def derived_tables(self) -> t.List[exp.Subquery]:\n        \"\"\"\n        List of derived tables in this scope.\n\n        For example:\n            SELECT * FROM (SELECT ...) <- that's a derived table\n\n        Returns:\n            list[exp.Subquery]: derived tables\n        \"\"\"\n        self._ensure_collected()\n        return self._derived_tables\n\n    @property\n    def udtfs(self) -> t.List[exp.UDTF]:\n        \"\"\"\n        List of \"User Defined Tabular Functions\" in this scope.\n\n        Returns:\n            list[exp.UDTF]: UDTFs\n        \"\"\"\n        self._ensure_collected()\n        return self._udtfs\n\n    @property\n    def subqueries(self) -> t.List[exp.Select | exp.SetOperation]:\n        \"\"\"\n        List of subqueries in this scope.\n\n        For example:\n            SELECT * FROM x WHERE a IN (SELECT ...) <- that's a subquery\n\n        Returns:\n            list[exp.Select | exp.SetOperation]: subqueries\n        \"\"\"\n        self._ensure_collected()\n        return self._subqueries\n\n    @property\n    def stars(self) -> t.List[exp.Column | exp.Dot]:\n        \"\"\"\n        List of star expressions (columns or dots) in this scope.\n        \"\"\"\n        self._ensure_collected()\n        return self._stars\n\n    @property\n    def column_index(self) -> t.Set[int]:\n        \"\"\"\n        Set of column object IDs that belong to this scope's expression.\n        \"\"\"\n        self._ensure_collected()\n        return self._column_index\n\n    @property\n    def columns(self) -> t.List[exp.Column]:\n        \"\"\"\n        List of columns in this scope.\n\n        Returns:\n            list[exp.Column]: Column instances in this scope, plus any\n                Columns that reference this scope from correlated subqueries.\n        \"\"\"\n        if self._columns is None:\n            self._ensure_collected()\n            columns = self._raw_columns\n\n            external_columns = [\n                column\n                for scope in itertools.chain(\n                    self.subquery_scopes,\n                    self.udtf_scopes,\n                    (dts for dts in self.derived_table_scopes if dts.can_be_correlated),\n                )\n                for column in scope.external_columns\n            ]\n\n            expr = self.expression\n            named_selects = set(expr.named_selects) if isinstance(expr, exp.Query) else set()\n\n            self._columns = []\n            for column in columns + external_columns:\n                ancestor = column.find_ancestor(\n                    exp.Select,\n                    exp.Qualify,\n                    exp.Order,\n                    exp.Having,\n                    exp.Hint,\n                    exp.Table,\n                    exp.Star,\n                    exp.Distinct,\n                )\n                if (\n                    not ancestor\n                    or column.text(\"table\")\n                    or isinstance(ancestor, exp.Select)\n                    or (isinstance(ancestor, exp.Table) and not isinstance(ancestor.this, exp.Func))\n                    or (\n                        isinstance(ancestor, (exp.Order, exp.Distinct))\n                        and (\n                            isinstance(ancestor.parent, (exp.Window, exp.WithinGroup))\n                            or not isinstance(ancestor.parent, exp.Select)\n                            or column.name not in named_selects\n                        )\n                    )\n                    or (isinstance(ancestor, exp.Star) and not column.arg_key == \"except_\")\n                ):\n                    self._columns.append(column)\n\n        return self._columns\n\n    @property\n    def table_columns(self) -> t.List[exp.TableColumn]:\n        self._ensure_collected()\n        return self._table_columns\n\n    @property\n    def selected_sources(self) -> t.Dict[str, t.Tuple[exp.Selectable, exp.Table | Scope]]:\n        \"\"\"\n        Mapping of nodes and sources that are actually selected from in this scope.\n\n        That is, all tables in a schema are selectable at any point. But a\n        table only becomes a selected source if it's included in a FROM or JOIN clause.\n\n        Returns:\n            dict[str, (exp.Table|exp.Select, exp.Table|Scope)]: selected sources and nodes\n        \"\"\"\n        if self._selected_sources is None:\n            result: t.Dict[str, t.Tuple[exp.Selectable, exp.Table | Scope]] = {}\n\n            for name, node in self.references:\n                if name in self._semi_anti_join_tables:\n                    # The RHS table of SEMI/ANTI joins shouldn't be collected as a\n                    # selected source\n                    continue\n\n                if name in result:\n                    raise OptimizeError(f\"Alias already used: {name}\")\n                if name in self.sources:\n                    result[name] = (node, self.sources[name])\n\n            self._selected_sources = result\n        return self._selected_sources\n\n    @property\n    def references(self) -> t.List[t.Tuple[str, exp.Selectable]]:\n        if self._references is None:\n            self._references = []\n\n            for table in self.tables:\n                self._references.append((table.alias_or_name, table))\n            for _expr in itertools.chain(self.derived_tables, self.udtfs):\n                # TODO (mypyc): rebind to exp.Expr to avoid DerivedTable trait vtable dispatch\n                expression: exp.Expr = _expr\n                self._references.append(\n                    (\n                        _get_source_alias(expression),\n                        (\n                            expression if expression.args.get(\"pivots\") else expression.unnest()\n                        ).assert_is(exp.Selectable),\n                    )\n                )\n\n        return self._references\n\n    @property\n    def external_columns(self) -> t.List[exp.Column]:\n        \"\"\"\n        Columns that appear to reference sources in outer scopes.\n\n        Returns:\n            list[exp.Column]: Column instances that don't reference sources in the current scope.\n        \"\"\"\n        if self._external_columns is None:\n            if isinstance(self.expression, exp.SetOperation):\n                left, right = self.union_scopes\n                self._external_columns = left.external_columns + right.external_columns\n            else:\n                self._external_columns = [\n                    c\n                    for c in self.columns\n                    if c.text(\"table\") not in self.sources\n                    and c.text(\"table\") not in self.semi_or_anti_join_tables\n                ]\n\n        return self._external_columns\n\n    @property\n    def local_columns(self) -> t.List[exp.Column]:\n        \"\"\"\n        Columns in this scope that are not external.\n\n        Returns:\n            list[exp.Column]: Column instances that reference sources in the current scope.\n        \"\"\"\n        if self._local_columns is None:\n            external_columns = set(self.external_columns)\n            self._local_columns = [c for c in self.columns if c not in external_columns]\n\n        return self._local_columns\n\n    @property\n    def unqualified_columns(self) -> t.List[exp.Column]:\n        \"\"\"\n        Unqualified columns in the current scope.\n\n        Returns:\n             list[exp.Column]: Unqualified columns\n        \"\"\"\n        return [c for c in self.columns if not c.text(\"table\")]\n\n    @property\n    def join_hints(self) -> t.List[exp.JoinHint]:\n        \"\"\"\n        Hints that exist in the scope that reference tables\n\n        Returns:\n            list[exp.JoinHint]: Join hints that are referenced within the scope\n        \"\"\"\n        self._ensure_collected()\n        return self._join_hints\n\n    @property\n    def pivots(self) -> t.List[exp.Pivot]:\n        if self._pivots is None:\n            self._pivots = [\n                pivot for _, node in self.references for pivot in node.args.get(\"pivots\") or []\n            ]\n\n        return self._pivots\n\n    @property\n    def semi_or_anti_join_tables(self) -> t.Set[str]:\n        self._ensure_collected()\n        return self._semi_anti_join_tables\n\n    def source_columns(self, source_name: str) -> t.List[exp.Column]:\n        \"\"\"\n        Get all columns in the current scope for a particular source.\n\n        Args:\n            source_name (str): Name of the source\n        Returns:\n            list[exp.Column]: Column instances that reference `source_name`\n        \"\"\"\n        return [column for column in self.columns if column.text(\"table\") == source_name]\n\n    @property\n    def is_subquery(self) -> bool:\n        \"\"\"Determine if this scope is a subquery\"\"\"\n        return self.scope_type == ScopeType.SUBQUERY\n\n    @property\n    def is_derived_table(self) -> bool:\n        \"\"\"Determine if this scope is a derived table\"\"\"\n        return self.scope_type == ScopeType.DERIVED_TABLE\n\n    @property\n    def is_union(self) -> bool:\n        \"\"\"Determine if this scope is a union\"\"\"\n        return self.scope_type == ScopeType.UNION\n\n    @property\n    def is_cte(self) -> bool:\n        \"\"\"Determine if this scope is a common table expression\"\"\"\n        return self.scope_type == ScopeType.CTE\n\n    @property\n    def is_root(self) -> bool:\n        \"\"\"Determine if this is the root scope\"\"\"\n        return self.scope_type == ScopeType.ROOT\n\n    @property\n    def is_udtf(self) -> bool:\n        \"\"\"Determine if this scope is a UDTF (User Defined Table Function)\"\"\"\n        return self.scope_type == ScopeType.UDTF\n\n    @property\n    def is_correlated_subquery(self) -> bool:\n        \"\"\"Determine if this scope is a correlated subquery\"\"\"\n        return bool(self.can_be_correlated and self.external_columns)\n\n    def rename_source(self, old_name: t.Optional[str], new_name: str) -> None:\n        \"\"\"Rename a source in this scope\"\"\"\n        old_name = old_name or \"\"\n        if old_name in self.sources:\n            self.sources[new_name] = self.sources.pop(old_name)\n\n    def add_source(self, name: str, source: exp.Table | Scope) -> None:\n        \"\"\"Add a source to this scope\"\"\"\n        self.sources[name] = source\n        self.clear_cache()\n\n    def remove_source(self, name: str) -> None:\n        \"\"\"Remove a source from this scope\"\"\"\n        self.sources.pop(name, None)\n        self.clear_cache()\n\n    def __repr__(self) -> str:\n        return f\"Scope<{self.expression.sql()}>\"\n\n    def traverse(self) -> Iterator[Scope]:\n        \"\"\"\n        Traverse the scope tree from this node.\n\n        Yields:\n            Scope: scope instances in depth-first-search post-order\n        \"\"\"\n        stack: list[Scope] = [self]\n        result: list[Scope] = []\n        while stack:\n            scope = stack.pop()\n            result.append(scope)\n            stack.extend(\n                itertools.chain(\n                    scope.cte_scopes,\n                    scope.union_scopes,\n                    scope.table_scopes,\n                    scope.subquery_scopes,\n                )\n            )\n\n        yield from reversed(result)\n\n    def ref_count(self) -> t.Dict[int, int]:\n        \"\"\"\n        Count the number of times each scope in this tree is referenced.\n\n        Returns:\n            dict[int, int]: Mapping of Scope instance ID to reference count\n        \"\"\"\n        scope_ref_count: t.Dict[int, int] = defaultdict(int)\n\n        for scope in self.traverse():\n            for _, source in scope.selected_sources.values():\n                scope_ref_count[id(source)] += 1\n\n            for name in scope._semi_anti_join_tables:\n                # semi/anti join sources are not actually selected but we still need to\n                # increment their ref count to avoid them being optimized away\n                if name in scope.sources:\n                    scope_ref_count[id(scope.sources[name])] += 1\n\n        return scope_ref_count\n\n\ndef traverse_scope(expression: exp.Expr) -> t.List[Scope]:\n    \"\"\"\n    Traverse an expression by its \"scopes\".\n\n    \"Scope\" represents the current context of a Select statement.\n\n    This is helpful for optimizing queries, where we need more information than\n    the expression tree itself. For example, we might care about the source\n    names within a subquery. Returns a list because a generator could result in\n    incomplete properties which is confusing.\n\n    Examples:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one(\"SELECT a FROM (SELECT a FROM x) AS y\")\n        >>> scopes = traverse_scope(expression)\n        >>> scopes[0].expression.sql(), list(scopes[0].sources)\n        ('SELECT a FROM x', ['x'])\n        >>> scopes[1].expression.sql(), list(scopes[1].sources)\n        ('SELECT a FROM (SELECT a FROM x) AS y', ['y'])\n\n    Args:\n        expression: Expr to traverse\n\n    Returns:\n        A list of the created scope instances\n    \"\"\"\n    if isinstance(expression, TRAVERSABLES):\n        return list(_traverse_scope(Scope(expression)))\n    return []\n\n\ndef build_scope(expression: exp.Expr) -> t.Optional[Scope]:\n    \"\"\"\n    Build a scope tree.\n\n    Args:\n        expression: Expr to build the scope tree for.\n\n    Returns:\n        The root scope\n    \"\"\"\n    return seq_get(traverse_scope(expression), -1)\n\n\ndef _traverse_scope(scope: Scope) -> Iterator[Scope]:\n    expression = scope.expression\n\n    if isinstance(expression, exp.Select):\n        yield from _traverse_select(scope)\n    elif isinstance(expression, exp.SetOperation):\n        yield from _traverse_ctes(scope)\n        yield from _traverse_union(scope)\n        return\n    elif isinstance(expression, exp.Subquery):\n        if scope.is_root:\n            yield from _traverse_select(scope)\n        else:\n            yield from _traverse_subqueries(scope)\n    elif isinstance(expression, exp.Table):\n        yield from _traverse_tables(scope)\n    elif isinstance(expression, exp.UDTF):\n        yield from _traverse_udtfs(scope)\n    elif isinstance(expression, exp.DDL):\n        # TODO (mypyc): change to ddl_expression = expression.expression\n        ddl_expression = expression.args.get(\"expression\")\n        if isinstance(ddl_expression, exp.Query):\n            yield from _traverse_ctes(scope)\n            yield from _traverse_scope(Scope(ddl_expression, cte_sources=scope.cte_sources))\n        return\n    elif isinstance(expression, exp.DML):\n        yield from _traverse_ctes(scope)\n        for query in find_all_in_scope(expression, exp.Query):\n            # This check ensures we don't yield the CTE/nested queries twice\n            if not isinstance(query.parent, (exp.CTE, exp.Subquery)):\n                yield from _traverse_scope(Scope(query, cte_sources=scope.cte_sources))\n        return\n    else:\n        logger.warning(\"Cannot traverse scope %s with type '%s'\", expression, type(expression))\n        return\n\n    yield scope\n\n\ndef _traverse_select(scope: Scope) -> Iterator[Scope]:\n    yield from _traverse_ctes(scope)\n    yield from _traverse_tables(scope)\n    yield from _traverse_subqueries(scope)\n\n\ndef _traverse_union(scope: Scope) -> Iterator[Scope]:\n    prev_scope: t.Optional[Scope] = None\n    union_scope_stack: t.List[Scope] = [scope]\n\n    set_op = scope.expression\n    assert isinstance(set_op, exp.SetOperation)\n    expression_stack: t.List[exp.Expr] = [set_op.right, set_op.left]\n\n    while expression_stack:\n        expression = expression_stack.pop()\n        union_scope = union_scope_stack[-1]\n\n        new_scope = union_scope.branch(\n            expression,\n            outer_columns=union_scope.outer_columns,\n            scope_type=ScopeType.UNION,\n        )\n\n        if isinstance(expression, exp.SetOperation):\n            yield from _traverse_ctes(new_scope)\n\n            union_scope_stack.append(new_scope)\n            expression_stack.extend([expression.right, expression.left])\n            continue\n\n        for scope in _traverse_scope(new_scope):\n            yield scope\n\n        if prev_scope:\n            union_scope_stack.pop()\n            union_scope.union_scopes = [prev_scope, scope]\n            prev_scope = union_scope\n\n            yield union_scope\n        else:\n            prev_scope = scope\n\n\ndef _traverse_ctes(scope: Scope) -> Iterator[Scope]:\n    sources: dict[str, exp.Table | Scope] = {}\n\n    for cte in scope.ctes:\n        cte_name = cte.alias\n\n        # if the scope is a recursive cte, it must be in the form of base_case UNION recursive.\n        # thus the recursive scope is the first section of the union.\n        with_ = scope.expression.args.get(\"with_\")\n        if with_ and with_.recursive:\n            union = cte.this\n\n            if isinstance(union, exp.SetOperation):\n                sources[cte_name] = scope.branch(union.this, scope_type=ScopeType.CTE)\n\n        child_scope: t.Optional[Scope] = None\n\n        for child_scope in _traverse_scope(\n            scope.branch(\n                cte.this,\n                cte_sources=sources,\n                outer_columns=cte.alias_column_names,\n                scope_type=ScopeType.CTE,\n            )\n        ):\n            yield child_scope\n\n        # append the final child_scope yielded\n        if child_scope:\n            sources[cte_name] = child_scope\n            scope.cte_scopes.append(child_scope)\n\n    scope.sources.update(sources)\n    scope.cte_sources.update(sources)\n\n\ndef _is_derived_table(expression: exp.Expr) -> bool:\n    \"\"\"\n    We represent (tbl1 JOIN tbl2) as a Subquery, but it's not really a \"derived table\",\n    as it doesn't introduce a new scope. If an alias is present, it shadows all names\n    under the Subquery, so that's one exception to this rule.\n    \"\"\"\n    return isinstance(expression, exp.Subquery) and bool(\n        expression.alias or isinstance(expression.this, exp.UNWRAPPED_QUERIES)\n    )\n\n\ndef _is_from_or_join(expression: exp.Expr) -> bool:\n    \"\"\"\n    Determine if `expression` is the FROM or JOIN clause of a SELECT statement.\n    \"\"\"\n    parent = expression.parent\n\n    # Subqueries can be arbitrarily nested\n    while type(parent) is exp.Subquery:\n        parent = parent.parent\n\n    return type(parent) in (exp.From, exp.Join)\n\n\ndef _traverse_tables(scope: Scope) -> Iterator[Scope]:\n    sources: dict[str, exp.Table | Scope] = {}\n\n    # Traverse FROMs, JOINs, and LATERALs in the order they are defined\n    expressions: list[exp.Expr] = []\n    from_ = scope.expression.args.get(\"from_\")\n    if from_:\n        expressions.append(from_.this)\n\n    for join in scope.expression.args.get(\"joins\") or []:\n        expressions.append(join.this)\n\n    if isinstance(scope.expression, exp.Table):\n        expressions.append(scope.expression)\n\n    expressions.extend(scope.expression.args.get(\"laterals\") or [])\n\n    for expression in expressions:\n        if isinstance(expression, exp.Final):\n            expression = expression.this\n        if isinstance(expression, exp.Table):\n            table_name = expression.name\n            source_name = expression.alias_or_name\n\n            if table_name in scope.sources and not expression.db:\n                # This is a reference to a parent source (e.g. a CTE), not an actual table, unless\n                # it is pivoted, because then we get back a new table and hence a new source.\n                pivots = expression.args.get(\"pivots\")\n                if pivots:\n                    sources[pivots[0].alias] = expression\n                else:\n                    sources[source_name] = scope.sources[table_name]\n            elif source_name in sources:\n                sources[find_new_name(sources, table_name)] = expression\n            else:\n                sources[source_name] = expression\n\n            # Make sure to not include the joins twice\n            if expression is not scope.expression:\n                expressions.extend(join.this for join in expression.args.get(\"joins\") or [])\n\n            continue\n\n        if not isinstance(expression, exp.DerivedTable):\n            continue\n\n        # TODO (mypyc): rebind to exp.Expr to avoid DerivedTable/UDTF trait vtable dispatch\n        node: exp.Expr = expression\n\n        if isinstance(expression, exp.UDTF):\n            lateral_sources = sources\n            scope_type = ScopeType.UDTF\n            scopes = scope.udtf_scopes\n        elif _is_derived_table(expression):\n            lateral_sources = None\n            scope_type = ScopeType.DERIVED_TABLE\n            scopes = scope.derived_table_scopes\n            expressions.extend(join.this for join in node.args.get(\"joins\") or [])\n        else:\n            # Makes sure we check for possible sources in nested table constructs\n            expressions.append(node.this)\n            expressions.extend(join.this for join in node.args.get(\"joins\") or [])\n            continue\n\n        child_scope: t.Optional[Scope] = None\n\n        for child_scope in _traverse_scope(\n            scope.branch(\n                node,\n                lateral_sources=lateral_sources,\n                outer_columns=node.alias_column_names,\n                scope_type=scope_type,\n            )\n        ):\n            yield child_scope\n\n            # Tables without aliases will be set as \"\"\n            # This shouldn't be a problem once qualify_columns runs, as it adds aliases on everything.\n            # Until then, this means that only a single, unaliased derived table is allowed (rather,\n            # the latest one wins.\n            sources[_get_source_alias(node)] = child_scope\n\n        # append the final child_scope yielded\n        if child_scope:\n            scopes.append(child_scope)\n            scope.table_scopes.append(child_scope)\n\n    scope.sources.update(sources)\n\n\ndef _traverse_subqueries(scope: Scope) -> Iterator[Scope]:\n    for subquery in scope.subqueries:\n        top: t.Optional[Scope] = None\n        for child_scope in _traverse_scope(scope.branch(subquery, scope_type=ScopeType.SUBQUERY)):\n            yield child_scope\n            top = child_scope\n        if top is not None:\n            scope.subquery_scopes.append(top)\n\n\ndef _traverse_udtfs(scope: Scope) -> Iterator[Scope]:\n    if isinstance(scope.expression, exp.Unnest):\n        udtf_expressions = scope.expression.expressions\n    elif isinstance(scope.expression, exp.Lateral):\n        udtf_expressions = [scope.expression.this]\n    else:\n        udtf_expressions = []\n\n    sources: t.Dict[str, exp.Table | Scope] = {}\n    for expression in udtf_expressions:\n        if isinstance(expression, exp.Subquery):\n            top: t.Optional[Scope] = None\n            for child_scope in _traverse_scope(\n                scope.branch(\n                    expression,\n                    scope_type=ScopeType.SUBQUERY,\n                    outer_columns=expression.alias_column_names,\n                )\n            ):\n                yield child_scope\n                top = child_scope\n                sources[_get_source_alias(expression)] = child_scope\n\n            if top is not None:\n                scope.subquery_scopes.append(top)\n\n    scope.sources.update(sources)\n\n\ndef walk_in_scope(\n    expression: exp.Expr,\n    prune: t.Optional[t.Callable[[exp.Expr], bool]] = None,\n) -> Iterator[exp.Expr]:\n    \"\"\"\n    Returns a generator object which visits all nodes in the syntrax tree, stopping at\n    nodes that start child scopes. This does a custom DFS traversal rather than using\n    expression.walk() because the nested generators aren't optimized in mypyc.\n\n    Args:\n        expression:\n        prune: callable that returns True if\n            the generator should stop traversing this branch of the tree.\n\n    Yields:\n        exp.Expr: each node in scope\n    \"\"\"\n    stack: t.List[exp.Expr] = [expression]\n\n    while stack:\n        node = stack.pop()\n\n        yield node\n\n        if node is not expression and (\n            isinstance(node, exp.CTE)\n            or (isinstance(node.parent, (exp.From, exp.Join)) and _is_derived_table(node))\n            or (isinstance(node.parent, exp.UDTF) and isinstance(node, exp.Query))\n            or isinstance(node, exp.UNWRAPPED_QUERIES)\n        ):\n            if isinstance(node, (exp.Subquery, exp.UDTF)):\n                for key in (\"joins\", \"laterals\", \"pivots\"):\n                    for arg in node.args.get(key) or []:\n                        yield from walk_in_scope(arg)\n            continue\n\n        if prune and prune(node):\n            continue\n\n        for vs in reversed(node.args.values()):\n            if isinstance(vs, list):\n                for v in reversed(vs):\n                    if isinstance(v, exp.Expr):\n                        stack.append(v)\n            elif isinstance(vs, exp.Expr):\n                stack.append(vs)\n\n\ndef find_all_in_scope(\n    expression: exp.Expr,\n    *expression_types: Type[E],\n) -> Iterator[E]:\n    \"\"\"\n    Returns a generator object which visits all nodes in this scope and only yields those that\n    match at least one of the specified expression types.\n\n    This does NOT traverse into subscopes.\n\n    Args:\n        expression: the expression to search.\n        expression_types: the expression type(s) to match.\n\n    Yields:\n        The matching nodes.\n    \"\"\"\n    for node in walk_in_scope(expression):\n        if isinstance(node, expression_types):\n            yield node\n\n\ndef find_in_scope(\n    expression: exp.Expr,\n    *expression_types: Type[E],\n) -> t.Optional[E]:\n    \"\"\"\n    Returns the first node in this scope which matches at least one of the specified types.\n\n    This does NOT traverse into subscopes.\n\n    Args:\n        expression: the expression to search.\n        expression_types: the expression type(s) to match.\n\n    Returns:\n        The node which matches the criteria or None if no node matching\n        the criteria was found.\n    \"\"\"\n    return next(find_all_in_scope(expression, *expression_types), None)\n\n\ndef _get_source_alias(expression: exp.Expr) -> str:\n    alias_arg = expression.args.get(\"alias\")\n    alias_name = expression.alias\n\n    if not alias_name and isinstance(alias_arg, exp.TableAlias) and len(alias_arg.columns) == 1:\n        alias_name = alias_arg.columns[0].name\n\n    return alias_name\n"
  },
  {
    "path": "sqlglot/optimizer/simplify.py",
    "content": "from __future__ import annotations\n\nimport datetime\nimport logging\nimport functools\nimport itertools\nimport typing as t\nfrom collections import deque, defaultdict\nfrom functools import reduce, wraps\n\nimport sqlglot\nfrom sqlglot import Dialect, exp\nfrom sqlglot.helper import first, merge_ranges, while_changing\nfrom sqlglot.optimizer.annotate_types import TypeAnnotator\nfrom sqlglot.optimizer.scope import find_all_in_scope, walk_in_scope\nfrom sqlglot.schema import ensure_schema\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n\n    DateRange = t.Tuple[datetime.date, datetime.date]\n    DateTruncBinaryTransform = t.Callable[\n        [exp.Expr, datetime.date, str, Dialect, exp.DataType], t.Optional[exp.Expr]\n    ]\n\n\nlogger = logging.getLogger(\"sqlglot\")\n\n\n# Final means that an expression should not be simplified\nFINAL = \"final\"\n\nSIMPLIFIABLE = (\n    exp.Binary,\n    exp.Func,\n    exp.Lambda,\n    exp.Predicate,\n    exp.Unary,\n)\n\n\ndef simplify(\n    expression: exp.Expr,\n    constant_propagation: bool = False,\n    coalesce_simplification: bool = False,\n    dialect: DialectType = None,\n):\n    \"\"\"\n    Rewrite sqlglot AST to simplify expressions.\n\n    Example:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one(\"TRUE AND TRUE\")\n        >>> simplify(expression).sql()\n        'TRUE'\n\n    Args:\n        expression: expression to simplify\n        constant_propagation: whether the constant propagation rule should be used\n        coalesce_simplification: whether the simplify coalesce rule should be used.\n            This rule tries to remove coalesce functions, which can be useful in certain analyses but\n            can leave the query more verbose.\n    Returns:\n        sqlglot.Expr: simplified expression\n    \"\"\"\n    return Simplifier(dialect=dialect).simplify(\n        expression,\n        constant_propagation=constant_propagation,\n        coalesce_simplification=coalesce_simplification,\n    )\n\n\nclass UnsupportedUnit(Exception):\n    pass\n\n\ndef catch(*exceptions):\n    \"\"\"Decorator that ignores a simplification function if any of `exceptions` are raised\"\"\"\n\n    def decorator(func):\n        def wrapped(expression, *args, **kwargs):\n            try:\n                return func(expression, *args, **kwargs)\n            except exceptions:\n                return expression\n\n        return wrapped\n\n    return decorator\n\n\ndef annotate_types_on_change(func):\n    @wraps(func)\n    def _func(self, expression: exp.Expr, *args, **kwargs) -> t.Optional[exp.Expr]:\n        new_expression = func(self, expression, *args, **kwargs)\n\n        if new_expression is None:\n            return new_expression\n\n        if self.annotate_new_expressions and expression != new_expression:\n            self._annotator.clear()\n\n            # We annotate this to ensure new children nodes are also annotated\n            new_expression = self._annotator.annotate(\n                expression=new_expression,\n                annotate_scope=False,\n            )\n\n            # Whatever expression the original expression is transformed into needs to preserve\n            # the original type, otherwise the simplification could result in a different schema\n            new_expression.type = expression.type\n\n        return new_expression\n\n    return _func\n\n\ndef flatten(expression):\n    \"\"\"\n    A AND (B AND C) -> A AND B AND C\n    A OR (B OR C) -> A OR B OR C\n    \"\"\"\n    if isinstance(expression, exp.Connector):\n        for node in expression.args.values():\n            child = node.unnest()\n            if isinstance(child, expression.__class__):\n                node.replace(child)\n    return expression\n\n\ndef simplify_parens(expression: exp.Expr, dialect: DialectType) -> exp.Expr:\n    if not isinstance(expression, exp.Paren):\n        return expression\n\n    this = expression.this\n    parent = expression.parent\n    parent_is_predicate = isinstance(parent, exp.Predicate)\n\n    if isinstance(this, exp.Select):\n        return expression\n\n    if isinstance(parent, (exp.SubqueryPredicate, exp.Bracket)):\n        return expression\n\n    if (\n        Dialect.get_or_raise(dialect).REQUIRES_PARENTHESIZED_STRUCT_ACCESS\n        and isinstance(parent, exp.Dot)\n        and (isinstance(parent.right, (exp.Identifier, exp.Star)))\n    ):\n        return expression\n\n    if isinstance(this, exp.Predicate) and (\n        not (\n            parent_is_predicate\n            or isinstance(parent, exp.Neg)\n            or (isinstance(parent, exp.Binary) and not isinstance(parent, exp.Connector))\n        )\n    ):\n        return this\n\n    if (\n        not isinstance(parent, (exp.Condition, exp.Binary))\n        or isinstance(parent, exp.Paren)\n        or (\n            not isinstance(this, exp.Binary)\n            and not (isinstance(this, (exp.Not, exp.Is)) and parent_is_predicate)\n        )\n        or (isinstance(this, exp.Add) and isinstance(parent, exp.Add))\n        or (isinstance(this, exp.Mul) and isinstance(parent, exp.Mul))\n        or (isinstance(this, exp.Mul) and isinstance(parent, (exp.Add, exp.Sub)))\n    ):\n        return this\n\n    return expression\n\n\ndef propagate_constants(expression, root=True):\n    \"\"\"\n    Propagate constants for conjunctions in DNF:\n\n    SELECT * FROM t WHERE a = b AND b = 5 becomes\n    SELECT * FROM t WHERE a = 5 AND b = 5\n\n    Reference: https://www.sqlite.org/optoverview.html\n    \"\"\"\n\n    if (\n        isinstance(expression, exp.And)\n        and (root or not expression.same_parent)\n        and sqlglot.optimizer.normalize.normalized(expression, dnf=True)\n    ):\n        constant_mapping = {}\n        for expr in walk_in_scope(expression, prune=lambda node: isinstance(node, exp.If)):\n            if isinstance(expr, exp.EQ):\n                l, r = expr.left, expr.right\n\n                # TODO: create a helper that can be used to detect nested literal expressions such\n                # as CAST(123456 AS BIGINT), since we usually want to treat those as literals too\n                if isinstance(l, exp.Column) and isinstance(r, exp.Literal):\n                    constant_mapping[l] = (id(l), r)\n\n        if constant_mapping:\n            for column in find_all_in_scope(expression, exp.Column):\n                parent = column.parent\n                column_id, constant = constant_mapping.get(column) or (None, None)\n                if (\n                    column_id is not None\n                    and id(column) != column_id\n                    and not (isinstance(parent, exp.Is) and isinstance(parent.expression, exp.Null))\n                ):\n                    column.replace(constant.copy())\n\n    return expression\n\n\ndef _is_number(expression: exp.Expr) -> bool:\n    return expression.is_number\n\n\ndef _is_interval(expression: exp.Expr) -> bool:\n    return isinstance(expression, exp.Interval) and extract_interval(expression) is not None\n\n\ndef _is_nonnull_constant(expression: exp.Expr) -> bool:\n    return isinstance(expression, exp.NONNULL_CONSTANTS) or _is_date_literal(expression)\n\n\ndef _is_constant(expression: exp.Expr) -> bool:\n    return isinstance(expression, exp.CONSTANTS) or _is_date_literal(expression)\n\n\ndef _datetrunc_range(date: datetime.date, unit: str, dialect: Dialect) -> t.Optional[DateRange]:\n    \"\"\"\n    Get the date range for a DATE_TRUNC equality comparison:\n\n    Example:\n        _datetrunc_range(date(2021-01-01), 'year') == (date(2021-01-01), date(2022-01-01))\n    Returns:\n        tuple of [min, max) or None if a value can never be equal to `date` for `unit`\n    \"\"\"\n    floor = date_floor(date, unit, dialect)\n\n    if date != floor:\n        # This will always be False, except for NULL values.\n        return None\n\n    return floor, floor + interval(unit)\n\n\ndef _datetrunc_eq_expression(\n    left: exp.Expr, drange: DateRange, target_type: t.Optional[exp.DataType]\n) -> exp.Expr:\n    \"\"\"Get the logical expression for a date range\"\"\"\n    return exp.and_(\n        left >= date_literal(drange[0], target_type),\n        left < date_literal(drange[1], target_type),\n        copy=False,\n    )\n\n\ndef _datetrunc_eq(\n    left: exp.Expr,\n    date: datetime.date,\n    unit: str,\n    dialect: Dialect,\n    target_type: t.Optional[exp.DataType],\n) -> t.Optional[exp.Expr]:\n    drange = _datetrunc_range(date, unit, dialect)\n    if not drange:\n        return None\n\n    return _datetrunc_eq_expression(left, drange, target_type)\n\n\ndef _datetrunc_neq(\n    left: exp.Expr,\n    date: datetime.date,\n    unit: str,\n    dialect: Dialect,\n    target_type: t.Optional[exp.DataType],\n) -> t.Optional[exp.Expr]:\n    drange = _datetrunc_range(date, unit, dialect)\n    if not drange:\n        return None\n\n    return exp.and_(\n        left < date_literal(drange[0], target_type),\n        left >= date_literal(drange[1], target_type),\n        copy=False,\n    )\n\n\ndef always_true(expression):\n    return (isinstance(expression, exp.Boolean) and expression.this) or (\n        isinstance(expression, exp.Literal) and expression.is_number and not is_zero(expression)\n    )\n\n\ndef always_false(expression):\n    return is_false(expression) or is_null(expression) or is_zero(expression)\n\n\ndef is_zero(expression):\n    return isinstance(expression, exp.Literal) and expression.to_py() == 0\n\n\ndef is_complement(a, b):\n    return isinstance(b, exp.Not) and b.this == a\n\n\ndef is_false(a: exp.Expr) -> bool:\n    return type(a) is exp.Boolean and not a.this\n\n\ndef is_null(a: exp.Expr) -> bool:\n    return type(a) is exp.Null\n\n\ndef eval_boolean(expression, a, b):\n    if isinstance(expression, (exp.EQ, exp.Is)):\n        return boolean_literal(a == b)\n    if isinstance(expression, exp.NEQ):\n        return boolean_literal(a != b)\n    if isinstance(expression, exp.GT):\n        return boolean_literal(a > b)\n    if isinstance(expression, exp.GTE):\n        return boolean_literal(a >= b)\n    if isinstance(expression, exp.LT):\n        return boolean_literal(a < b)\n    if isinstance(expression, exp.LTE):\n        return boolean_literal(a <= b)\n    return None\n\n\ndef cast_as_date(value: t.Any) -> t.Optional[datetime.date]:\n    if isinstance(value, datetime.datetime):\n        return value.date()\n    if isinstance(value, datetime.date):\n        return value\n    try:\n        return datetime.datetime.fromisoformat(value).date()\n    except ValueError:\n        return None\n\n\ndef cast_as_datetime(value: t.Any) -> t.Optional[datetime.datetime]:\n    if isinstance(value, datetime.datetime):\n        return value\n    if isinstance(value, datetime.date):\n        return datetime.datetime(year=value.year, month=value.month, day=value.day)\n    try:\n        return datetime.datetime.fromisoformat(value)\n    except ValueError:\n        return None\n\n\ndef cast_value(value: t.Any, to: exp.DataType) -> t.Optional[t.Union[datetime.date, datetime.date]]:\n    if not value:\n        return None\n    if to.is_type(exp.DType.DATE):\n        return cast_as_date(value)\n    if to.is_type(*exp.DataType.TEMPORAL_TYPES):\n        return cast_as_datetime(value)\n    return None\n\n\ndef extract_date(cast: exp.Expr) -> t.Optional[t.Union[datetime.date, datetime.date]]:\n    if isinstance(cast, exp.Cast):\n        to = cast.to\n    elif isinstance(cast, exp.TsOrDsToDate) and not cast.args.get(\"format\"):\n        to = exp.DataType.build(exp.DType.DATE)\n    else:\n        return None\n\n    if isinstance(cast.this, exp.Literal):\n        value: t.Any = cast.this.name\n    elif isinstance(cast.this, (exp.Cast, exp.TsOrDsToDate)):\n        value = extract_date(cast.this)\n    else:\n        return None\n    return cast_value(value, to)\n\n\ndef _is_date_literal(expression: exp.Expr) -> bool:\n    return extract_date(expression) is not None\n\n\ndef extract_interval(expression):\n    try:\n        n = int(expression.this.to_py())\n        unit = expression.text(\"unit\").lower()\n        return interval(unit, n)\n    except (UnsupportedUnit, ModuleNotFoundError, ValueError):\n        return None\n\n\ndef extract_type(*expressions):\n    target_type = None\n    for expression in expressions:\n        target_type = expression.to if isinstance(expression, exp.Cast) else expression.type\n        if target_type:\n            break\n\n    return target_type\n\n\ndef date_literal(date, target_type=None):\n    if not target_type or not target_type.is_type(*exp.DataType.TEMPORAL_TYPES):\n        target_type = exp.DType.DATETIME if isinstance(date, datetime.datetime) else exp.DType.DATE\n\n    return exp.cast(exp.Literal.string(date), target_type)\n\n\ndef interval(unit: str, n: int = 1):\n    from dateutil.relativedelta import relativedelta\n\n    if unit == \"year\":\n        return relativedelta(years=1 * n)\n    if unit == \"quarter\":\n        return relativedelta(months=3 * n)\n    if unit == \"month\":\n        return relativedelta(months=1 * n)\n    if unit == \"week\":\n        return relativedelta(weeks=1 * n)\n    if unit == \"day\":\n        return relativedelta(days=1 * n)\n    if unit == \"hour\":\n        return relativedelta(hours=1 * n)\n    if unit == \"minute\":\n        return relativedelta(minutes=1 * n)\n    if unit == \"second\":\n        return relativedelta(seconds=1 * n)\n\n    raise UnsupportedUnit(f\"Unsupported unit: {unit}\")\n\n\ndef date_floor(d: datetime.date, unit: str, dialect: Dialect) -> datetime.date:\n    if unit == \"year\":\n        return d.replace(month=1, day=1)\n    if unit == \"quarter\":\n        if d.month <= 3:\n            return d.replace(month=1, day=1)\n        elif d.month <= 6:\n            return d.replace(month=4, day=1)\n        elif d.month <= 9:\n            return d.replace(month=7, day=1)\n        else:\n            return d.replace(month=10, day=1)\n    if unit == \"month\":\n        return d.replace(month=d.month, day=1)\n    if unit == \"week\":\n        # Assuming week starts on Monday (0) and ends on Sunday (6)\n        return d - datetime.timedelta(days=d.weekday() - dialect.WEEK_OFFSET)\n    if unit == \"day\":\n        return d\n\n    raise UnsupportedUnit(f\"Unsupported unit: {unit}\")\n\n\ndef date_ceil(d: datetime.date, unit: str, dialect: Dialect) -> datetime.date:\n    floor = date_floor(d, unit, dialect)\n\n    if floor == d:\n        return d\n\n    return floor + interval(unit)\n\n\ndef boolean_literal(condition):\n    return exp.true() if condition else exp.false()\n\n\nclass Simplifier:\n    def __init__(self, dialect: DialectType = None, annotate_new_expressions: bool = True):\n        self.dialect = Dialect.get_or_raise(dialect)\n        self.annotate_new_expressions = annotate_new_expressions\n\n        self._annotator: TypeAnnotator = TypeAnnotator(\n            schema=ensure_schema(None, dialect=self.dialect), overwrite_types=False\n        )\n\n    # Value ranges for byte-sized signed/unsigned integers\n    TINYINT_MIN = -128\n    TINYINT_MAX = 127\n    UTINYINT_MIN = 0\n    UTINYINT_MAX = 255\n\n    COMPLEMENT_COMPARISONS = {\n        exp.LT: exp.GTE,\n        exp.GT: exp.LTE,\n        exp.LTE: exp.GT,\n        exp.GTE: exp.LT,\n        exp.EQ: exp.NEQ,\n        exp.NEQ: exp.EQ,\n    }\n\n    COMPLEMENT_SUBQUERY_PREDICATES = {\n        exp.All: exp.Any,\n        exp.Any: exp.All,\n    }\n\n    LT_LTE = (exp.LT, exp.LTE)\n    GT_GTE = (exp.GT, exp.GTE)\n\n    COMPARISONS = (\n        *LT_LTE,\n        *GT_GTE,\n        exp.EQ,\n        exp.NEQ,\n        exp.Is,\n    )\n\n    INVERSE_COMPARISONS: t.Dict[t.Type[exp.Expr], t.Type[exp.Expr]] = {\n        exp.LT: exp.GT,\n        exp.GT: exp.LT,\n        exp.LTE: exp.GTE,\n        exp.GTE: exp.LTE,\n    }\n\n    NONDETERMINISTIC = (exp.Rand, exp.Randn)\n    AND_OR = (exp.And, exp.Or)\n\n    INVERSE_DATE_OPS: t.Dict[t.Type[exp.Expr], t.Type[exp.Expr]] = {\n        exp.DateAdd: exp.Sub,\n        exp.DateSub: exp.Add,\n        exp.DatetimeAdd: exp.Sub,\n        exp.DatetimeSub: exp.Add,\n    }\n\n    INVERSE_OPS: t.Dict[t.Type[exp.Expr], t.Type[exp.Expr]] = {\n        **INVERSE_DATE_OPS,\n        exp.Add: exp.Sub,\n        exp.Sub: exp.Add,\n    }\n\n    NULL_OK = (exp.NullSafeEQ, exp.NullSafeNEQ, exp.PropertyEQ)\n\n    CONCATS = (exp.Concat, exp.DPipe)\n\n    DATETRUNC_BINARY_COMPARISONS: t.Dict[t.Type[exp.Expr], DateTruncBinaryTransform] = {\n        exp.LT: lambda l, dt, u, d, t: (\n            l\n            < date_literal(\n                dt if dt == date_floor(dt, u, d) else date_floor(dt, u, d) + interval(u), t\n            )\n        ),\n        exp.GT: lambda l, dt, u, d, t: l >= date_literal(date_floor(dt, u, d) + interval(u), t),\n        exp.LTE: lambda l, dt, u, d, t: l < date_literal(date_floor(dt, u, d) + interval(u), t),\n        exp.GTE: lambda l, dt, u, d, t: l >= date_literal(date_ceil(dt, u, d), t),\n        exp.EQ: _datetrunc_eq,\n        exp.NEQ: _datetrunc_neq,\n    }\n\n    DATETRUNC_COMPARISONS = {exp.In, *DATETRUNC_BINARY_COMPARISONS}\n    DATETRUNCS = (exp.DateTrunc, exp.TimestampTrunc)\n\n    SAFE_CONNECTOR_ELIMINATION_RESULT = (exp.Connector, exp.Boolean)\n\n    # CROSS joins result in an empty table if the right table is empty.\n    # So we can only simplify certain types of joins to CROSS.\n    # Or in other words, LEFT JOIN x ON TRUE != CROSS JOIN x\n    JOINS = {\n        (\"\", \"\"),\n        (\"\", \"INNER\"),\n        (\"RIGHT\", \"\"),\n        (\"RIGHT\", \"OUTER\"),\n    }\n\n    def simplify(\n        self,\n        expression: exp.Expr,\n        constant_propagation: bool = False,\n        coalesce_simplification: bool = False,\n    ):\n        wheres = []\n        joins = []\n\n        for node in expression.walk(\n            prune=lambda n: bool(isinstance(n, exp.Condition) or n.meta.get(FINAL))\n        ):\n            if node.meta.get(FINAL):\n                continue\n\n            # group by expressions cannot be simplified, for example\n            # select x + 1 + 1 FROM y GROUP BY x + 1 + 1\n            # the projection must exactly match the group by key\n            group = node.args.get(\"group\")\n\n            if group and hasattr(node, \"selects\"):\n                groups = set(group.expressions)\n                group.meta[FINAL] = True\n\n                for s in node.selects:\n                    for n in s.walk():\n                        if n in groups:\n                            s.meta[FINAL] = True\n                            break\n\n                having = node.args.get(\"having\")\n\n                if having:\n                    for n in having.walk():\n                        if n in groups:\n                            having.meta[FINAL] = True\n                            break\n\n            if isinstance(node, exp.Condition):\n                simplified = while_changing(\n                    node, lambda e: self._simplify(e, constant_propagation, coalesce_simplification)\n                )\n\n                if node is expression:\n                    expression = simplified\n            elif isinstance(node, exp.Where):\n                wheres.append(node)\n            elif isinstance(node, exp.Join):\n                # snowflake match_conditions have very strict ordering rules\n                if match := node.args.get(\"match_condition\"):\n                    match.meta[FINAL] = True\n\n                joins.append(node)\n\n        for where in wheres:\n            if always_true(where.this):\n                where.pop()\n        for join in joins:\n            if (\n                always_true(join.args.get(\"on\"))\n                and not join.args.get(\"using\")\n                and not join.args.get(\"method\")\n                and (join.side, join.kind) in self.JOINS\n            ):\n                join.args[\"on\"].pop()\n                join.set(\"side\", None)\n                join.set(\"kind\", \"CROSS\")\n\n        return expression\n\n    def _simplify(\n        self, expression: exp.Expr, constant_propagation: bool, coalesce_simplification: bool\n    ):\n        pre_transformation_stack = [expression]\n        post_transformation_stack = []\n\n        while pre_transformation_stack:\n            original = pre_transformation_stack.pop()\n            node = original\n\n            if not isinstance(node, SIMPLIFIABLE):\n                if isinstance(node, exp.Query):\n                    self.simplify(node, constant_propagation, coalesce_simplification)\n                continue\n\n            parent = node.parent\n            root = node is expression\n\n            node = self.rewrite_between(node)\n            node = self.uniq_sort(node, root)\n            node = self.absorb_and_eliminate(node, root)\n            node = self.simplify_concat(node)\n            node = self.simplify_conditionals(node)\n\n            if constant_propagation:\n                node = propagate_constants(node, root)\n\n            if node is not original:\n                original.replace(node)\n\n            for n in node.iter_expressions(reverse=True):\n                if n.meta.get(FINAL):\n                    raise\n            pre_transformation_stack.extend(\n                n for n in node.iter_expressions(reverse=True) if not n.meta.get(FINAL)\n            )\n            post_transformation_stack.append((node, parent))\n\n        while post_transformation_stack:\n            original, parent = post_transformation_stack.pop()\n            root = original is expression\n\n            # Resets parent, arg_key, index pointers– this is needed because some of the\n            # previous transformations mutate the AST, leading to an inconsistent state\n            for k, v in tuple(original.args.items()):\n                original.set(k, v)\n\n            # Post-order transformations\n            node = self.simplify_not(original)\n            node = flatten(node)\n            node = self.simplify_connectors(node, root)\n            node = self.remove_complements(node, root)\n\n            if coalesce_simplification:\n                node = self.simplify_coalesce(node)\n            node.parent = parent\n\n            node = self.simplify_literals(node, root)\n            node = self.simplify_equality(node)\n            node = simplify_parens(node, dialect=self.dialect)\n            node = self.simplify_datetrunc(node)\n            node = self.sort_comparison(node)\n            node = self.simplify_startswith(node)\n\n            if node is not original:\n                original.replace(node)\n\n        return node\n\n    @annotate_types_on_change\n    def rewrite_between(self, expression: exp.Expr) -> exp.Expr:\n        \"\"\"Rewrite x between y and z to x >= y AND x <= z.\n\n        This is done because comparison simplification is only done on lt/lte/gt/gte.\n        \"\"\"\n        if isinstance(expression, exp.Between):\n            negate = isinstance(expression.parent, exp.Not)\n\n            expression = exp.and_(\n                exp.GTE(this=expression.this.copy(), expression=expression.args[\"low\"]),\n                exp.LTE(this=expression.this.copy(), expression=expression.args[\"high\"]),\n                copy=False,\n            )\n\n            if negate:\n                expression = exp.paren(expression, copy=False)\n\n        return expression\n\n    @annotate_types_on_change\n    def simplify_not(self, expression: exp.Expr) -> exp.Expr:\n        \"\"\"\n        Demorgan's Law\n        NOT (x OR y) -> NOT x AND NOT y\n        NOT (x AND y) -> NOT x OR NOT y\n        \"\"\"\n        if isinstance(expression, exp.Not):\n            this = expression.this\n            if is_null(this):\n                return exp.and_(exp.null(), exp.true(), copy=False)\n            if this.__class__ in self.COMPLEMENT_COMPARISONS:\n                right = this.expression\n                complement_subquery_predicate = self.COMPLEMENT_SUBQUERY_PREDICATES.get(\n                    right.__class__\n                )\n                if complement_subquery_predicate:\n                    right = complement_subquery_predicate(this=right.this)\n\n                return self.COMPLEMENT_COMPARISONS[this.__class__](this=this.this, expression=right)\n            if isinstance(this, exp.Paren):\n                condition = this.unnest()\n                if isinstance(condition, exp.And):\n                    return exp.paren(\n                        exp.or_(\n                            exp.not_(condition.left, copy=False),\n                            exp.not_(condition.right, copy=False),\n                            copy=False,\n                        ),\n                        copy=False,\n                    )\n                if isinstance(condition, exp.Or):\n                    return exp.paren(\n                        exp.and_(\n                            exp.not_(condition.left, copy=False),\n                            exp.not_(condition.right, copy=False),\n                            copy=False,\n                        ),\n                        copy=False,\n                    )\n                if is_null(condition):\n                    return exp.and_(exp.null(), exp.true(), copy=False)\n            if always_true(this):\n                return exp.false()\n            if is_false(this):\n                return exp.true()\n            if isinstance(this, exp.Not) and self.dialect.SAFE_TO_ELIMINATE_DOUBLE_NEGATION:\n                inner = this.this\n                if inner.is_type(exp.DType.BOOLEAN):\n                    # double negation\n                    # NOT NOT x -> x, if x is BOOLEAN type\n                    return inner\n        return expression\n\n    @annotate_types_on_change\n    def simplify_connectors(self, expression, root=True):\n        def _simplify_connectors(expression, left, right):\n            if isinstance(expression, exp.And):\n                if is_false(left) or is_false(right):\n                    return exp.false()\n                if is_zero(left) or is_zero(right):\n                    return exp.false()\n                if (\n                    (is_null(left) and is_null(right))\n                    or (is_null(left) and always_true(right))\n                    or (always_true(left) and is_null(right))\n                ):\n                    return exp.null()\n                if always_true(left) and always_true(right):\n                    return exp.true()\n                if always_true(left):\n                    return right\n                if always_true(right):\n                    return left\n                return self._simplify_comparison(expression, left, right)\n            elif isinstance(expression, exp.Or):\n                if always_true(left) or always_true(right):\n                    return exp.true()\n                if (\n                    (is_null(left) and is_null(right))\n                    or (is_null(left) and always_false(right))\n                    or (always_false(left) and is_null(right))\n                ):\n                    return exp.null()\n                if is_false(left):\n                    return right\n                if is_false(right):\n                    return left\n                return self._simplify_comparison(expression, left, right, or_=True)\n\n        if isinstance(expression, exp.Connector):\n            original_parent = expression.parent\n            expression = self._flat_simplify(expression, _simplify_connectors, root)\n\n            # If we reduced a connector to, e.g., a column (t1 AND ... AND tn -> Tk), then we need\n            # to ensure that the resulting type is boolean. We know this is true only for connectors,\n            # boolean values and columns that are essentially operands to a connector:\n            #\n            # A AND (((B)))\n            #          ~ this is safe to keep because it will eventually be part of another connector\n            if not isinstance(\n                expression, self.SAFE_CONNECTOR_ELIMINATION_RESULT\n            ) and not expression.is_type(exp.DType.BOOLEAN):\n                while True:\n                    if isinstance(original_parent, exp.Connector):\n                        break\n                    if not isinstance(original_parent, exp.Paren):\n                        expression = expression.and_(exp.true(), copy=False)\n                        break\n\n                    original_parent = original_parent.parent\n\n        return expression\n\n    @annotate_types_on_change\n    def _simplify_comparison(self, expression, left, right, or_=False):\n        if isinstance(left, self.COMPARISONS) and isinstance(right, self.COMPARISONS):\n            ll, lr = left.args.values()\n            rl, rr = right.args.values()\n\n            largs = {ll, lr}\n            rargs = {rl, rr}\n\n            matching = largs & rargs\n            columns = {\n                m for m in matching if not _is_constant(m) and not m.find(*self.NONDETERMINISTIC)\n            }\n\n            if matching and columns:\n                try:\n                    l = first(largs - columns)\n                    r = first(rargs - columns)\n                except StopIteration:\n                    return expression\n\n                if l.is_number and r.is_number:\n                    l = l.to_py()\n                    r = r.to_py()\n                elif l.is_string and r.is_string:\n                    l = l.name\n                    r = r.name\n                else:\n                    l = extract_date(l)\n                    if not l:\n                        return None\n                    r = extract_date(r)\n                    if not r:\n                        return None\n                    # python won't compare date and datetime, but many engines will upcast\n                    l, r = cast_as_datetime(l), cast_as_datetime(r)\n\n                for (a, av), (b, bv) in itertools.permutations(((left, l), (right, r))):\n                    if isinstance(a, self.LT_LTE) and isinstance(b, self.LT_LTE):\n                        return left if (av > bv if or_ else av <= bv) else right\n                    if isinstance(a, self.GT_GTE) and isinstance(b, self.GT_GTE):\n                        return left if (av < bv if or_ else av >= bv) else right\n\n                    # we can't ever shortcut to true because the column could be null\n                    if not or_:\n                        if isinstance(a, exp.LT) and isinstance(b, self.GT_GTE):\n                            if av <= bv:\n                                return exp.false()\n                        elif isinstance(a, exp.GT) and isinstance(b, self.LT_LTE):\n                            if av >= bv:\n                                return exp.false()\n                        elif isinstance(a, exp.EQ):\n                            if isinstance(b, exp.LT):\n                                return exp.false() if av >= bv else a\n                            if isinstance(b, exp.LTE):\n                                return exp.false() if av > bv else a\n                            if isinstance(b, exp.GT):\n                                return exp.false() if av <= bv else a\n                            if isinstance(b, exp.GTE):\n                                return exp.false() if av < bv else a\n                            if isinstance(b, exp.NEQ):\n                                return exp.false() if av == bv else a\n        return None\n\n    @annotate_types_on_change\n    def remove_complements(self, expression, root=True):\n        \"\"\"\n        Removing complements.\n\n        A AND NOT A -> FALSE (only for non-NULL A)\n        A OR NOT A -> TRUE (only for non-NULL A)\n        \"\"\"\n        if isinstance(expression, self.AND_OR) and (root or not expression.same_parent):\n            ops = set(expression.flatten())\n            for op in ops:\n                if isinstance(op, exp.Not) and op.this in ops:\n                    if expression.meta.get(\"nonnull\") is True:\n                        return exp.false() if isinstance(expression, exp.And) else exp.true()\n\n        return expression\n\n    @annotate_types_on_change\n    def uniq_sort(self, expression, root=True):\n        \"\"\"\n        Uniq and sort a connector.\n\n        C AND A AND B AND B -> A AND B AND C\n        \"\"\"\n        if isinstance(expression, exp.Connector) and (root or not expression.same_parent):\n            flattened = tuple(expression.flatten())\n\n            if isinstance(expression, exp.Xor):\n                result_func = exp.xor\n                # Do not deduplicate XOR as A XOR A != A if A == True\n                deduped = None\n                arr = tuple((gen(e), e) for e in flattened)\n            else:\n                result_func = exp.and_ if isinstance(expression, exp.And) else exp.or_\n                deduped = {gen(e): e for e in flattened}\n                arr = tuple(deduped.items())\n\n            # check if the operands are already sorted, if not sort them\n            # A AND C AND B -> A AND B AND C\n            for i, (sql, e) in enumerate(arr[1:]):\n                if sql < arr[i][0]:\n                    expression = result_func(*(e for _, e in sorted(arr)), copy=False)\n                    break\n            else:\n                # we didn't have to sort but maybe we need to dedup\n                if deduped and len(deduped) < len(flattened):\n                    unique_operand = flattened[0]\n                    if len(deduped) == 1:\n                        expression = unique_operand.and_(exp.true(), copy=False)\n                    else:\n                        expression = result_func(*deduped.values(), copy=False)\n\n        return expression\n\n    @annotate_types_on_change\n    def absorb_and_eliminate(self, expression, root=True):\n        \"\"\"\n        absorption:\n            A AND (A OR B) -> A\n            A OR (A AND B) -> A\n            A AND (NOT A OR B) -> A AND B\n            A OR (NOT A AND B) -> A OR B\n        elimination:\n            (A AND B) OR (A AND NOT B) -> A\n            (A OR B) AND (A OR NOT B) -> A\n        \"\"\"\n        if isinstance(expression, self.AND_OR) and (root or not expression.same_parent):\n            kind = exp.Or if isinstance(expression, exp.And) else exp.And\n\n            ops = tuple(expression.flatten())\n\n            # Initialize lookup tables:\n            # Set of all operands, used to find complements for absorption.\n            op_set = set()\n            # Sub-operands, used to find subsets for absorption.\n            subops = defaultdict(list)\n            # Pairs of complements, used for elimination.\n            pairs = defaultdict(list)\n\n            # Populate the lookup tables\n            for op in ops:\n                op_set.add(op)\n\n                if not isinstance(op, kind):\n                    # In cases like: A OR (A AND B)\n                    # Subop will be: ^\n                    subops[op].append({op})\n                    continue\n\n                # In cases like: (A AND B) OR (A AND B AND C)\n                # Subops will be: ^     ^\n                subset = set(op.flatten())\n                for i in subset:\n                    subops[i].append(subset)\n\n                a, b = op.unnest_operands()\n                if isinstance(a, exp.Not):\n                    pairs[frozenset((a.this, b))].append((op, b))\n                if isinstance(b, exp.Not):\n                    pairs[frozenset((a, b.this))].append((op, a))\n\n            for op in ops:\n                if not isinstance(op, kind):\n                    continue\n\n                a, b = op.unnest_operands()\n\n                # Absorb\n                if isinstance(a, exp.Not) and a.this in op_set:\n                    a.replace(exp.true() if kind == exp.And else exp.false())\n                    continue\n                if isinstance(b, exp.Not) and b.this in op_set:\n                    b.replace(exp.true() if kind == exp.And else exp.false())\n                    continue\n                superset = set(op.flatten())\n                if any(any(subset < superset for subset in subops[i]) for i in superset):\n                    op.replace(exp.false() if kind == exp.And else exp.true())\n                    continue\n\n                # Eliminate\n                for other, complement in pairs[frozenset((a, b))]:\n                    op.replace(complement)\n                    other.replace(complement)\n\n        return expression\n\n    @annotate_types_on_change\n    @catch(ModuleNotFoundError, UnsupportedUnit)\n    def simplify_equality(self, expression: exp.Expr) -> exp.Expr:\n        \"\"\"\n        Use the subtraction and addition properties of equality to simplify expressions:\n\n            x + 1 = 3 becomes x = 2\n\n        There are two binary operations in the above expression: + and =\n        Here's how we reference all the operands in the code below:\n\n            l     r\n            x + 1 = 3\n            a   b\n        \"\"\"\n        if isinstance(expression, self.COMPARISONS):\n            l, r = expression.left, expression.right\n\n            if l.__class__ not in self.INVERSE_OPS:\n                return expression\n\n            if r.is_number:\n                a_predicate = _is_number\n                b_predicate = _is_number\n            elif _is_date_literal(r):\n                a_predicate = _is_date_literal\n                b_predicate = _is_interval\n            else:\n                return expression\n\n            if l.__class__ in self.INVERSE_DATE_OPS:\n                l = t.cast(exp.IntervalOp, l)\n                a: exp.Expr = l.this\n                b: exp.Expr = l.interval()\n            else:\n                l = t.cast(exp.Binary, l)\n                a, b = l.left, l.right\n\n            if not a_predicate(a) and b_predicate(b):\n                pass\n            elif not a_predicate(b) and b_predicate(a):\n                a, b = b, a\n            else:\n                return expression\n\n            return expression.__class__(\n                this=a, expression=self.INVERSE_OPS[l.__class__](this=r, expression=b)\n            )\n        return expression\n\n    @annotate_types_on_change\n    def simplify_literals(self, expression, root=True):\n        if isinstance(expression, exp.Binary) and not isinstance(expression, exp.Connector):\n            return self._flat_simplify(expression, self._simplify_binary, root)\n\n        if isinstance(expression, exp.Neg) and isinstance(expression.this, exp.Neg):\n            return expression.this.this\n\n        if type(expression) in self.INVERSE_DATE_OPS:\n            return (\n                self._simplify_binary(expression, expression.this, expression.interval())\n                or expression\n            )\n\n        return expression\n\n    def _simplify_integer_cast(self, expr: exp.Expr) -> exp.Expr:\n        if isinstance(expr, exp.Cast) and isinstance(expr.this, exp.Cast):\n            this = self._simplify_integer_cast(expr.this)\n        else:\n            this = expr.this\n\n        if isinstance(expr, exp.Cast) and this.is_int:\n            num = this.to_py()\n\n            # Remove the (up)cast from small (byte-sized) integers in predicates which is side-effect free. Downcasts on any\n            # integer type might cause overflow, thus the cast cannot be eliminated and the behavior is\n            # engine-dependent\n            if (\n                self.TINYINT_MIN <= num <= self.TINYINT_MAX\n                and expr.to.this in exp.DataType.SIGNED_INTEGER_TYPES\n            ) or (\n                self.UTINYINT_MIN <= num <= self.UTINYINT_MAX\n                and expr.to.this in exp.DataType.UNSIGNED_INTEGER_TYPES\n            ):\n                return this\n\n        return expr\n\n    def _simplify_binary(self, expression, a, b):\n        if isinstance(expression, self.COMPARISONS):\n            a = self._simplify_integer_cast(a)\n            b = self._simplify_integer_cast(b)\n\n        if isinstance(expression, exp.Is):\n            if isinstance(b, exp.Not):\n                c = b.this\n                not_ = True\n            else:\n                c = b\n                not_ = False\n\n            if is_null(c):\n                if isinstance(a, exp.Literal):\n                    return exp.true() if not_ else exp.false()\n                if is_null(a):\n                    return exp.false() if not_ else exp.true()\n        elif isinstance(expression, self.NULL_OK):\n            return None\n        elif (is_null(a) or is_null(b)) and isinstance(expression.parent, exp.If):\n            return exp.null()\n\n        if a.is_number and b.is_number:\n            num_a = a.to_py()\n            num_b = b.to_py()\n\n            if isinstance(expression, exp.Add):\n                return exp.Literal.number(num_a + num_b)\n            if isinstance(expression, exp.Mul):\n                return exp.Literal.number(num_a * num_b)\n\n            # We only simplify Sub, Div if a and b have the same parent because they're not associative\n            if isinstance(expression, exp.Sub):\n                return exp.Literal.number(num_a - num_b) if a.parent is b.parent else None\n            if isinstance(expression, exp.Div):\n                # engines have differing int div behavior so intdiv is not safe\n                if (isinstance(num_a, int) and isinstance(num_b, int)) or a.parent is not b.parent:\n                    return None\n                return exp.Literal.number(num_a / num_b)\n\n            boolean = eval_boolean(expression, num_a, num_b)\n\n            if boolean:\n                return boolean\n        elif a.is_string and b.is_string:\n            boolean = eval_boolean(expression, a.this, b.this)\n\n            if boolean:\n                return boolean\n        elif _is_date_literal(a) and isinstance(b, exp.Interval):\n            date, b = extract_date(a), extract_interval(b)\n            if date and b:\n                if isinstance(expression, (exp.Add, exp.DateAdd, exp.DatetimeAdd)):\n                    return date_literal(date + b, extract_type(a))\n                if isinstance(expression, (exp.Sub, exp.DateSub, exp.DatetimeSub)):\n                    return date_literal(date - b, extract_type(a))\n        elif isinstance(a, exp.Interval) and _is_date_literal(b):\n            a, date = extract_interval(a), extract_date(b)\n            # you cannot subtract a date from an interval\n            if a and b and isinstance(expression, exp.Add):\n                return date_literal(a + date, extract_type(b))\n        elif _is_date_literal(a) and _is_date_literal(b):\n            if isinstance(expression, exp.Predicate):\n                a, b = extract_date(a), extract_date(b)\n                boolean = eval_boolean(expression, a, b)\n                if boolean:\n                    return boolean\n\n        return None\n\n    @annotate_types_on_change\n    def simplify_coalesce(self, expression: exp.Expr) -> exp.Expr:\n        # COALESCE(x) -> x\n        if (\n            isinstance(expression, exp.Coalesce)\n            and (not expression.expressions or _is_nonnull_constant(expression.this))\n            # COALESCE is also used as a Spark partitioning hint\n            and not isinstance(expression.parent, exp.Hint)\n        ):\n            return expression.this\n\n        if self.dialect.COALESCE_COMPARISON_NON_STANDARD:\n            return expression\n\n        if not isinstance(expression, self.COMPARISONS):\n            return expression\n\n        if isinstance(expression.left, exp.Coalesce):\n            coalesce = expression.left\n            other = expression.right\n        elif isinstance(expression.right, exp.Coalesce):\n            coalesce = expression.right\n            other = expression.left\n        else:\n            return expression\n\n        # This transformation is valid for non-constants,\n        # but it really only does anything if they are both constants.\n        if not _is_constant(other):\n            return expression\n\n        # Find the first constant arg\n        for arg_index, arg in enumerate(coalesce.expressions):\n            if _is_constant(arg):\n                break\n        else:\n            return expression\n\n        coalesce.set(\"expressions\", coalesce.expressions[:arg_index])\n\n        # Remove the COALESCE function. This is an optimization, skipping a simplify iteration,\n        # since we already remove COALESCE at the top of this function.\n        coalesce = coalesce if coalesce.expressions else coalesce.this\n\n        # This expression is more complex than when we started, but it will get simplified further\n        return exp.paren(\n            exp.or_(\n                exp.and_(\n                    coalesce.is_(exp.null()).not_(copy=False),\n                    expression.copy(),\n                    copy=False,\n                ),\n                exp.and_(\n                    coalesce.is_(exp.null()),\n                    type(expression)(this=arg.copy(), expression=other.copy()),\n                    copy=False,\n                ),\n                copy=False,\n            ),\n            copy=False,\n        )\n\n    @annotate_types_on_change\n    def simplify_concat(self, expression):\n        \"\"\"Reduces all groups that contain string literals by concatenating them.\"\"\"\n        if not isinstance(expression, self.CONCATS) or (\n            # We can't reduce a CONCAT_WS call if we don't statically know the separator\n            isinstance(expression, exp.ConcatWs) and not expression.expressions[0].is_string\n        ):\n            return expression\n\n        if isinstance(expression, exp.ConcatWs):\n            sep_expr, *expressions = expression.expressions\n            sep = sep_expr.name\n            concat_type = exp.ConcatWs\n            args = {}\n        else:\n            expressions = expression.expressions\n            sep = \"\"\n            concat_type = exp.Concat\n            args = {\n                \"safe\": expression.args.get(\"safe\"),\n                \"coalesce\": expression.args.get(\"coalesce\"),\n            }\n\n        new_args = []\n        for is_string_group, group in itertools.groupby(\n            expressions or expression.flatten(), lambda e: e.is_string\n        ):\n            if is_string_group:\n                new_args.append(exp.Literal.string(sep.join(string.name for string in group)))\n            else:\n                new_args.extend(group)\n\n        if len(new_args) == 1 and new_args[0].is_string:\n            return new_args[0]\n\n        if concat_type is exp.ConcatWs:\n            new_args = [sep_expr] + new_args\n        elif isinstance(expression, exp.DPipe):\n            return reduce(lambda x, y: exp.DPipe(this=x, expression=y), new_args)\n\n        return concat_type(expressions=new_args, **args)\n\n    @annotate_types_on_change\n    def simplify_conditionals(self, expression):\n        \"\"\"Simplifies expressions like IF, CASE if their condition is statically known.\"\"\"\n        if isinstance(expression, exp.Case):\n            this = expression.this\n            for case in expression.args[\"ifs\"]:\n                cond = case.this\n                if this:\n                    # Convert CASE x WHEN matching_value ... to CASE WHEN x = matching_value ...\n                    cond = cond.replace(this.pop().eq(cond))\n\n                if always_true(cond):\n                    return case.args[\"true\"]\n\n                if always_false(cond):\n                    case.pop()\n                    if not expression.args[\"ifs\"]:\n                        return expression.args.get(\"default\") or exp.null()\n        elif isinstance(expression, exp.If) and not isinstance(expression.parent, exp.Case):\n            if always_true(expression.this):\n                return expression.args[\"true\"]\n            if always_false(expression.this):\n                return expression.args.get(\"false\") or exp.null()\n\n        return expression\n\n    @annotate_types_on_change\n    def simplify_startswith(self, expression: exp.Expr) -> exp.Expr:\n        \"\"\"\n        Reduces a prefix check to either TRUE or FALSE if both the string and the\n        prefix are statically known.\n\n        Example:\n            >>> from sqlglot import parse_one\n            >>> Simplifier().simplify_startswith(parse_one(\"STARTSWITH('foo', 'f')\")).sql()\n            'TRUE'\n        \"\"\"\n        if (\n            isinstance(expression, exp.StartsWith)\n            and expression.this.is_string\n            and expression.expression.is_string\n        ):\n            return exp.convert(expression.name.startswith(expression.expression.name))\n\n        return expression\n\n    def _is_datetrunc_predicate(self, left: exp.Expr, right: exp.Expr) -> bool:\n        return isinstance(left, self.DATETRUNCS) and _is_date_literal(right)\n\n    @annotate_types_on_change\n    @catch(ModuleNotFoundError, UnsupportedUnit)\n    def simplify_datetrunc(self, expression: exp.Expr) -> exp.Expr:\n        \"\"\"Simplify expressions like `DATE_TRUNC('year', x) >= CAST('2021-01-01' AS DATE)`\"\"\"\n        comparison = expression.__class__\n\n        if isinstance(expression, self.DATETRUNCS):\n            this = expression.this\n            trunc_type = extract_type(this)\n            date = extract_date(this)\n            if date and expression.unit:\n                return date_literal(\n                    date_floor(date, expression.unit.name.lower(), self.dialect), trunc_type\n                )\n        elif comparison not in self.DATETRUNC_COMPARISONS:\n            return expression\n\n        if isinstance(expression, exp.Binary):\n            l, r = expression.left, expression.right\n\n            if not self._is_datetrunc_predicate(l, r):\n                return expression\n\n            l = t.cast(exp.DateTrunc, l)\n            trunc_arg = l.this\n            unit = l.unit.name.lower()\n            date = extract_date(r)\n\n            if not date:\n                return expression\n\n            return (\n                self.DATETRUNC_BINARY_COMPARISONS[comparison](\n                    trunc_arg, date, unit, self.dialect, extract_type(r)\n                )\n                or expression\n            )\n\n        if isinstance(expression, exp.In):\n            l = expression.this\n            rs = expression.expressions\n\n            if rs and all(self._is_datetrunc_predicate(l, r) for r in rs):\n                l = t.cast(exp.DateTrunc, l)\n                unit = l.unit.name.lower()\n\n                ranges = []\n                for r in rs:\n                    date = extract_date(r)\n                    if not date:\n                        return expression\n                    drange = _datetrunc_range(date, unit, self.dialect)\n                    if drange:\n                        ranges.append(drange)\n\n                if not ranges:\n                    return expression\n\n                ranges = merge_ranges(ranges)\n                target_type = extract_type(*rs)\n\n                return exp.or_(\n                    *[_datetrunc_eq_expression(l, drange, target_type) for drange in ranges],\n                    copy=False,\n                )\n\n        return expression\n\n    @annotate_types_on_change\n    def sort_comparison(self, expression: exp.Expr) -> exp.Expr:\n        if expression.__class__ in self.COMPLEMENT_COMPARISONS:\n            l, r = expression.this, expression.expression\n            l_column = isinstance(l, exp.Column)\n            r_column = isinstance(r, exp.Column)\n            l_const = _is_constant(l)\n            r_const = _is_constant(r)\n\n            if (\n                (l_column and not r_column)\n                or (r_const and not l_const)\n                or isinstance(r, exp.SubqueryPredicate)\n            ):\n                return expression\n            if (r_column and not l_column) or (l_const and not r_const) or (gen(l) > gen(r)):\n                return self.INVERSE_COMPARISONS.get(expression.__class__, expression.__class__)(\n                    this=r, expression=l\n                )\n        return expression\n\n    def _flat_simplify(self, expression, simplifier, root=True):\n        if root or not expression.same_parent:\n            operands = []\n            queue = deque(expression.flatten(unnest=False))\n            size = len(queue)\n\n            while queue:\n                a = queue.popleft()\n\n                for b in queue:\n                    result = simplifier(expression, a, b)\n\n                    if result and result is not expression:\n                        queue.remove(b)\n                        queue.appendleft(result)\n                        break\n                else:\n                    operands.append(a)\n\n            if len(operands) < size:\n                return functools.reduce(\n                    lambda a, b: expression.__class__(this=a, expression=b), operands\n                )\n        return expression\n\n\ndef gen(expression: t.Any, comments: bool = False) -> str:\n    \"\"\"Simple pseudo sql generator for quickly generating sortable and uniq strings.\n\n    Sorting and deduping sql is a necessary step for optimization. Calling the actual\n    generator is expensive so we have a bare minimum sql generator here.\n\n    Args:\n        expression: the expression to convert into a SQL string.\n        comments: whether to include the expression's comments.\n    \"\"\"\n    return Gen().gen(expression, comments=comments)\n\n\nclass Gen:\n    def __init__(self):\n        self.stack = []\n        self.sqls = []\n\n    def gen(self, expression: exp.Expr, comments: bool = False) -> str:\n        self.stack = [expression]\n        self.sqls.clear()\n\n        while self.stack:\n            node = self.stack.pop()\n\n            if isinstance(node, exp.Expr):\n                if comments and node.comments:\n                    self.stack.append(f\" /*{','.join(node.comments)}*/\")\n\n                exp_handler_name = f\"{node.key}_sql\"\n\n                if hasattr(self, exp_handler_name):\n                    getattr(self, exp_handler_name)(node)\n                elif isinstance(node, exp.Func):\n                    self._function(node)\n                else:\n                    key = node.key.upper()\n                    self.stack.append(f\"{key} \" if self._args(node) else key)\n            elif type(node) is list:\n                for n in reversed(node):\n                    if n is not None:\n                        self.stack.extend((n, \",\"))\n                if node:\n                    self.stack.pop()\n            else:\n                if node is not None:\n                    self.sqls.append(str(node))\n\n        return \"\".join(self.sqls)\n\n    def add_sql(self, e: exp.Add) -> None:\n        self._binary(e, \" + \")\n\n    def alias_sql(self, e: exp.Alias) -> None:\n        self.stack.extend(\n            (\n                e.args.get(\"alias\"),\n                \" AS \",\n                e.args.get(\"this\"),\n            )\n        )\n\n    def and_sql(self, e: exp.And) -> None:\n        self._binary(e, \" AND \")\n\n    def anonymous_sql(self, e: exp.Anonymous) -> None:\n        this = e.this\n        if isinstance(this, str):\n            name = this.upper()\n        elif isinstance(this, exp.Identifier):\n            name = this.this\n            name = f'\"{name}\"' if this.quoted else name.upper()\n        else:\n            raise ValueError(\n                f\"Anonymous.this expects a str or an Identifier, got '{this.__class__.__name__}'.\"\n            )\n\n        self.stack.extend(\n            (\n                \")\",\n                e.expressions,\n                \"(\",\n                name,\n            )\n        )\n\n    def between_sql(self, e: exp.Between) -> None:\n        self.stack.extend(\n            (\n                e.args.get(\"high\"),\n                \" AND \",\n                e.args.get(\"low\"),\n                \" BETWEEN \",\n                e.this,\n            )\n        )\n\n    def boolean_sql(self, e: exp.Boolean) -> None:\n        self.stack.append(\"TRUE\" if e.this else \"FALSE\")\n\n    def bracket_sql(self, e: exp.Bracket) -> None:\n        self.stack.extend(\n            (\n                \"]\",\n                e.expressions,\n                \"[\",\n                e.this,\n            )\n        )\n\n    def column_sql(self, e: exp.Column) -> None:\n        for p in reversed(e.parts):\n            self.stack.extend((p, \".\"))\n        self.stack.pop()\n\n    def datatype_sql(self, e: exp.DataType) -> None:\n        self._args(e, 1)\n        self.stack.append(f\"{e.this.name} \")\n\n    def div_sql(self, e: exp.Div) -> None:\n        self._binary(e, \" / \")\n\n    def dot_sql(self, e: exp.Dot) -> None:\n        self._binary(e, \".\")\n\n    def eq_sql(self, e: exp.EQ) -> None:\n        self._binary(e, \" = \")\n\n    def from_sql(self, e: exp.From) -> None:\n        self.stack.extend((e.this, \"FROM \"))\n\n    def gt_sql(self, e: exp.GT) -> None:\n        self._binary(e, \" > \")\n\n    def gte_sql(self, e: exp.GTE) -> None:\n        self._binary(e, \" >= \")\n\n    def identifier_sql(self, e: exp.Identifier) -> None:\n        self.stack.append(f'\"{e.this}\"' if e.quoted else e.this)\n\n    def ilike_sql(self, e: exp.ILike) -> None:\n        self._binary(e, \" ILIKE \")\n\n    def in_sql(self, e: exp.In) -> None:\n        self.stack.append(\")\")\n        self._args(e, 1)\n        self.stack.extend(\n            (\n                \"(\",\n                \" IN \",\n                e.this,\n            )\n        )\n\n    def intdiv_sql(self, e: exp.IntDiv) -> None:\n        self._binary(e, \" DIV \")\n\n    def is_sql(self, e: exp.Is) -> None:\n        self._binary(e, \" IS \")\n\n    def like_sql(self, e: exp.Like) -> None:\n        self._binary(e, \" Like \")\n\n    def literal_sql(self, e: exp.Literal) -> None:\n        self.stack.append(f\"'{e.this}'\" if e.is_string else e.this)\n\n    def lt_sql(self, e: exp.LT) -> None:\n        self._binary(e, \" < \")\n\n    def lte_sql(self, e: exp.LTE) -> None:\n        self._binary(e, \" <= \")\n\n    def mod_sql(self, e: exp.Mod) -> None:\n        self._binary(e, \" % \")\n\n    def mul_sql(self, e: exp.Mul) -> None:\n        self._binary(e, \" * \")\n\n    def neg_sql(self, e: exp.Neg) -> None:\n        self._unary(e, \"-\")\n\n    def neq_sql(self, e: exp.NEQ) -> None:\n        self._binary(e, \" <> \")\n\n    def not_sql(self, e: exp.Not) -> None:\n        self._unary(e, \"NOT \")\n\n    def null_sql(self, e: exp.Null) -> None:\n        self.stack.append(\"NULL\")\n\n    def or_sql(self, e: exp.Or) -> None:\n        self._binary(e, \" OR \")\n\n    def paren_sql(self, e: exp.Paren) -> None:\n        self.stack.extend(\n            (\n                \")\",\n                e.this,\n                \"(\",\n            )\n        )\n\n    def sub_sql(self, e: exp.Sub) -> None:\n        self._binary(e, \" - \")\n\n    def subquery_sql(self, e: exp.Subquery) -> None:\n        self._args(e, 2)\n        alias = e.args.get(\"alias\")\n        if alias:\n            self.stack.append(alias)\n        self.stack.extend((\")\", e.this, \"(\"))\n\n    def table_sql(self, e: exp.Table) -> None:\n        self._args(e, 4)\n        alias = e.args.get(\"alias\")\n        if alias:\n            self.stack.append(alias)\n        for p in reversed(e.parts):\n            self.stack.extend((p, \".\"))\n        self.stack.pop()\n\n    def tablealias_sql(self, e: exp.TableAlias) -> None:\n        columns = e.columns\n\n        if columns:\n            self.stack.extend((\")\", columns, \"(\"))\n\n        self.stack.extend((e.this, \" AS \"))\n\n    def var_sql(self, e: exp.Var) -> None:\n        self.stack.append(e.this)\n\n    def _binary(self, e: exp.Binary, op: str) -> None:\n        self.stack.extend((e.expression, op, e.this))\n\n    def _unary(self, e: exp.Unary, op: str) -> None:\n        self.stack.extend((e.this, op))\n\n    def _function(self, e: exp.Func) -> None:\n        self.stack.extend(\n            (\n                \")\",\n                list(e.args.values()),\n                \"(\",\n                e.sql_name(),\n            )\n        )\n\n    def _args(self, node: exp.Expr, arg_index: int = 0) -> bool:\n        kvs = []\n        arg_types = list(node.arg_types)[arg_index:] if arg_index else node.arg_types\n\n        for k in arg_types:\n            v = node.args.get(k)\n\n            if v is not None:\n                kvs.append([f\":{k}\", v])\n        if kvs:\n            self.stack.append(kvs)\n            return True\n        return False\n"
  },
  {
    "path": "sqlglot/optimizer/unnest_subqueries.py",
    "content": "from sqlglot import exp\nfrom sqlglot.helper import name_sequence\nfrom sqlglot.optimizer.scope import ScopeType, find_in_scope, traverse_scope\n\n\ndef unnest_subqueries(expression):\n    \"\"\"\n    Rewrite sqlglot AST to convert some predicates with subqueries into joins.\n\n    Convert scalar subqueries into cross joins.\n    Convert correlated or vectorized subqueries into a group by so it is not a many to many left join.\n\n    Example:\n        >>> import sqlglot\n        >>> expression = sqlglot.parse_one(\"SELECT * FROM x AS x WHERE (SELECT y.a AS a FROM y AS y WHERE x.a = y.a) = 1 \")\n        >>> unnest_subqueries(expression).sql()\n        'SELECT * FROM x AS x LEFT JOIN (SELECT y.a AS a FROM y AS y WHERE TRUE GROUP BY y.a) AS _u_0 ON x.a = _u_0.a WHERE _u_0.a = 1'\n\n    Args:\n        expression (sqlglot.Expr): expression to unnest\n    Returns:\n        sqlglot.Expr: unnested expression\n    \"\"\"\n    next_alias_name = name_sequence(\"_u_\")\n\n    for scope in traverse_scope(expression):\n        select = scope.expression\n        parent = select.parent_select\n        if not parent:\n            continue\n        if scope.external_columns:\n            decorrelate(select, parent, scope.external_columns, next_alias_name)\n        elif scope.scope_type == ScopeType.SUBQUERY:\n            unnest(select, parent, next_alias_name)\n\n    return expression\n\n\ndef unnest(select, parent_select, next_alias_name):\n    if len(select.selects) > 1:\n        return\n\n    predicate = select.find_ancestor(exp.Condition)\n    if (\n        not predicate\n        # Do not unnest subqueries inside table-valued functions such as\n        # FROM GENERATE_SERIES(...), FROM UNNEST(...) etc in order to preserve join order\n        or (\n            isinstance(predicate, exp.Func)\n            and isinstance(predicate.parent, (exp.Table, exp.From, exp.Join))\n        )\n        or parent_select is not predicate.parent_select\n        or not parent_select.args.get(\"from_\")\n    ):\n        return\n\n    if isinstance(select, exp.SetOperation):\n        select = exp.select(*select.selects).from_(select.subquery(next_alias_name()))\n\n    alias = next_alias_name()\n    clause = predicate.find_ancestor(exp.Having, exp.Where, exp.Join)\n\n    # This subquery returns a scalar and can just be converted to a cross join\n    if not isinstance(predicate, (exp.In, exp.Any)):\n        column = exp.column(select.selects[0].alias_or_name, alias)\n\n        clause_parent_select = clause.parent_select if clause else None\n\n        if (isinstance(clause, exp.Having) and clause_parent_select is parent_select) or (\n            (not clause or clause_parent_select is not parent_select)\n            and (\n                parent_select.args.get(\"group\")\n                or any(find_in_scope(select, exp.AggFunc) for select in parent_select.selects)\n            )\n        ):\n            column = exp.Max(this=column)\n        elif not isinstance(select.parent, exp.Subquery):\n            return\n\n        join_type = \"CROSS\"\n        on_clause = None\n        if isinstance(predicate, exp.Exists):\n            # If a subquery returns no rows, cross-joining against it incorrectly eliminates all rows\n            # from the parent query. Therefore, we use a LEFT JOIN that always matches (ON TRUE), then\n            # check for non-NULL column values to determine whether the subquery contained rows.\n            column = column.is_(exp.null()).not_()\n            join_type = \"LEFT\"\n            on_clause = exp.true()\n\n        _replace(select.parent, column)\n        parent_select.join(select, on=on_clause, join_type=join_type, join_alias=alias, copy=False)\n\n        return\n\n    if select.find(exp.Limit, exp.Offset):\n        return\n\n    if isinstance(predicate, exp.Any):\n        predicate = predicate.find_ancestor(exp.EQ)\n\n        if not predicate or parent_select is not predicate.parent_select:\n            return\n\n    column = _other_operand(predicate)\n    value = select.selects[0]\n\n    join_key = exp.column(value.alias, alias)\n    join_key_not_null = join_key.is_(exp.null()).not_()\n\n    if isinstance(clause, exp.Join):\n        _replace(predicate, exp.true())\n        parent_select.where(join_key_not_null, copy=False)\n    else:\n        _replace(predicate, join_key_not_null)\n\n    group = select.args.get(\"group\")\n\n    if group:\n        if {value.this} != set(group.expressions):\n            select = (\n                exp.select(exp.alias_(exp.column(value.alias, \"_q\"), value.alias))\n                .from_(select.subquery(\"_q\", copy=False), copy=False)\n                .group_by(exp.column(value.alias, \"_q\"), copy=False)\n            )\n    elif not find_in_scope(value.this, exp.AggFunc):\n        select = select.group_by(value.this, copy=False)\n\n    parent_select.join(\n        select,\n        on=column.eq(join_key),\n        join_type=\"LEFT\",\n        join_alias=alias,\n        copy=False,\n    )\n\n\ndef decorrelate(select, parent_select, external_columns, next_alias_name):\n    where = select.args.get(\"where\")\n\n    if not where or where.find(exp.Or) or select.find(exp.Limit, exp.Offset):\n        return\n\n    table_alias = next_alias_name()\n    keys = []\n\n    # for all external columns in the where statement, find the relevant predicate\n    # keys to convert it into a join\n    for column in external_columns:\n        if column.find_ancestor(exp.Where) is not where:\n            return\n\n        predicate = column.find_ancestor(exp.Predicate)\n\n        if not predicate or predicate.find_ancestor(exp.Where) is not where:\n            return\n\n        if isinstance(predicate, exp.Binary):\n            key = (\n                predicate.right\n                if any(node is column for node in predicate.left.walk())\n                else predicate.left\n            )\n        else:\n            return\n\n        keys.append((key, column, predicate))\n\n    if not any(isinstance(predicate, exp.EQ) for *_, predicate in keys):\n        return\n\n    is_subquery_projection = any(\n        node is select.parent\n        for node in map(lambda s: s.unalias(), parent_select.selects)\n        if isinstance(node, exp.Subquery)\n    )\n\n    value = select.selects[0]\n    key_aliases = {}\n    group_by = []\n\n    for key, _, predicate in keys:\n        # if we filter on the value of the subquery, it needs to be unique\n        if key == value.this:\n            key_aliases[key] = value.alias\n            group_by.append(key)\n        else:\n            if key not in key_aliases:\n                key_aliases[key] = next_alias_name()\n            # all predicates that are equalities must also be in the unique\n            # so that we don't do a many to many join\n            if isinstance(predicate, exp.EQ) and key not in group_by:\n                group_by.append(key)\n\n    parent_predicate = select.find_ancestor(exp.Predicate)\n\n    # When the subquery is embedded inside a function (e.g. COALESCE, TRIM) in the SELECT list,\n    # the ancestor chain contains no Predicate node AND the subquery is not a direct projection.\n    if parent_predicate is None and not is_subquery_projection:\n        return\n\n    # if the value of the subquery is not an agg or a key, we need to collect it into an array\n    # so that it can be grouped. For subquery projections, we use a MAX aggregation instead.\n    agg_func = exp.Max if is_subquery_projection else exp.ArrayAgg\n    if not value.find(exp.AggFunc) and value.this not in group_by:\n        select.select(\n            exp.alias_(agg_func(this=value.this), value.alias, quoted=False),\n            append=False,\n            copy=False,\n        )\n\n    # exists queries should not have any selects as it only checks if there are any rows\n    # all selects will be added by the optimizer and only used for join keys\n    if isinstance(parent_predicate, exp.Exists):\n        select.set(\"expressions\", [])\n\n    for key, alias in key_aliases.items():\n        if key in group_by:\n            # add all keys to the projections of the subquery\n            # so that we can use it as a join key\n            if isinstance(parent_predicate, exp.Exists) or key != value.this:\n                select.select(f\"{key} AS {alias}\", copy=False)\n        else:\n            select.select(exp.alias_(agg_func(this=key.copy()), alias, quoted=False), copy=False)\n\n    alias = exp.column(value.alias, table_alias)\n    other = _other_operand(parent_predicate)\n    op_type = type(parent_predicate.parent) if parent_predicate else None\n\n    if isinstance(parent_predicate, exp.Exists):\n        alias = exp.column(list(key_aliases.values())[0], table_alias)\n        parent_predicate = _replace(parent_predicate, f\"NOT {alias} IS NULL\")\n    elif isinstance(parent_predicate, exp.All):\n        assert issubclass(op_type, exp.Binary)\n        predicate = op_type(this=other, expression=exp.column(\"_x\"))\n        parent_predicate = _replace(\n            parent_predicate.parent, f\"ARRAY_ALL({alias}, _x -> {predicate})\"\n        )\n    elif isinstance(parent_predicate, exp.Any):\n        assert issubclass(op_type, exp.Binary)\n        if value.this in group_by:\n            predicate = op_type(this=other, expression=alias)\n            parent_predicate = _replace(parent_predicate.parent, predicate)\n        else:\n            predicate = op_type(this=other, expression=exp.column(\"_x\"))\n            parent_predicate = _replace(parent_predicate, f\"ARRAY_ANY({alias}, _x -> {predicate})\")\n    elif isinstance(parent_predicate, exp.In):\n        if value.this in group_by:\n            parent_predicate = _replace(parent_predicate, f\"{other} = {alias}\")\n        else:\n            parent_predicate = _replace(\n                parent_predicate,\n                f\"ARRAY_ANY({alias}, _x -> _x = {parent_predicate.this})\",\n            )\n    else:\n        if is_subquery_projection and select.parent.alias:\n            alias = exp.alias_(alias, select.parent.alias)\n\n        # COUNT always returns 0 on empty datasets, so we need take that into consideration here\n        # by transforming all counts into 0 and using that as the coalesced value\n        if value.find(exp.Count):\n\n            def remove_aggs(node):\n                if isinstance(node, exp.Count):\n                    return exp.Literal.number(0)\n                elif isinstance(node, exp.AggFunc):\n                    return exp.null()\n                return node\n\n            alias = exp.Coalesce(this=alias, expressions=[value.this.transform(remove_aggs)])\n\n        select.parent.replace(alias)\n\n    for key, column, predicate in keys:\n        predicate.replace(exp.true())\n        nested = exp.column(key_aliases[key], table_alias)\n\n        if is_subquery_projection:\n            key.replace(nested)\n            if not isinstance(predicate, exp.EQ):\n                parent_select.where(predicate, copy=False)\n            continue\n\n        if key in group_by:\n            key.replace(nested)\n        elif isinstance(predicate, exp.EQ):\n            parent_predicate = _replace(\n                parent_predicate,\n                f\"({parent_predicate} AND ARRAY_CONTAINS({nested}, {column}))\",\n            )\n        else:\n            key.replace(exp.to_identifier(\"_x\"))\n            parent_predicate = _replace(\n                parent_predicate,\n                f\"({parent_predicate} AND ARRAY_ANY({nested}, _x -> {predicate}))\",\n            )\n\n    parent_select.join(\n        select.group_by(*group_by, copy=False),\n        on=[predicate for *_, predicate in keys if isinstance(predicate, exp.EQ)],\n        join_type=\"LEFT\",\n        join_alias=table_alias,\n        copy=False,\n    )\n\n\ndef _replace(expression, condition):\n    return expression.replace(exp.condition(condition))\n\n\ndef _other_operand(expression):\n    if isinstance(expression, exp.In):\n        return expression.this\n\n    if isinstance(expression, (exp.Any, exp.All)):\n        return _other_operand(expression.parent)\n\n    if isinstance(expression, exp.Binary):\n        return (\n            expression.right\n            if isinstance(expression.left, (exp.Subquery, exp.Any, exp.Exists, exp.All))\n            else expression.left\n        )\n\n    return None\n"
  },
  {
    "path": "sqlglot/parser.py",
    "content": "from __future__ import annotations\n\nimport itertools\nimport logging\nimport re\nimport typing as t\nfrom collections import defaultdict\n\nfrom sqlglot import exp\nfrom sqlglot.errors import (\n    ErrorLevel,\n    ParseError,\n    TokenError,\n    concat_messages,\n    highlight_sql,\n    merge_errors,\n)\nfrom sqlglot.expressions import apply_index_offset\nfrom sqlglot.helper import ensure_list, i64, seq_get\nfrom sqlglot.trie import new_trie\nfrom sqlglot.time import format_time\nfrom sqlglot.tokens import Token, Tokenizer, TokenType\nfrom sqlglot.trie import TrieResult, in_trie\nfrom collections.abc import Sequence\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from sqlglot.dialects.dialect import Dialect, DialectType\n\n    from re import Pattern\n\n    T = t.TypeVar(\"T\")\n    TCeilFloor = t.TypeVar(\"TCeilFloor\", exp.Ceil, exp.Floor)\n\nlogger = logging.getLogger(\"sqlglot\")\n\nOPTIONS_TYPE = dict[str, Sequence[t.Union[Sequence[str], str]]]\n\n# Used to detect alphabetical characters and +/- in timestamp literals\nTIME_ZONE_RE: Pattern[str] = re.compile(r\":.*?[a-zA-Z\\+\\-]\")\n\n\ndef build_var_map(args: list) -> exp.StarMap | exp.VarMap:\n    if len(args) == 1 and args[0].is_star:\n        return exp.StarMap(this=args[0])\n\n    keys = []\n    values = []\n    for i in range(0, len(args), 2):\n        keys.append(args[i])\n        values.append(args[i + 1])\n\n    return exp.VarMap(keys=exp.array(*keys, copy=False), values=exp.array(*values, copy=False))\n\n\ndef build_like(args: t.List) -> exp.Escape | exp.Like:\n    like = exp.Like(this=seq_get(args, 1), expression=seq_get(args, 0))\n    return exp.Escape(this=like, expression=seq_get(args, 2)) if len(args) > 2 else like\n\n\ndef binary_range_parser(\n    expr_type: t.Type[exp.Expr], reverse_args: bool = False\n) -> t.Callable[[Parser, t.Optional[exp.Expr]], t.Optional[exp.Expr]]:\n    def _parse_binary_range(self: Parser, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        expression = self._parse_bitwise()\n        if reverse_args:\n            this, expression = expression, this\n        return self._parse_escape(self.expression(expr_type(this=this, expression=expression)))\n\n    return _parse_binary_range\n\n\ndef build_logarithm(args: t.List, dialect: Dialect) -> exp.Func:\n    # Default argument order is base, expression\n    this = seq_get(args, 0)\n    expression = seq_get(args, 1)\n\n    if expression:\n        if not dialect.LOG_BASE_FIRST:\n            this, expression = expression, this\n        return exp.Log(this=this, expression=expression)\n\n    return (exp.Ln if dialect.parser_class.LOG_DEFAULTS_TO_LN else exp.Log)(this=this)\n\n\ndef build_hex(args: t.List, dialect: Dialect) -> exp.Hex | exp.LowerHex:\n    arg = seq_get(args, 0)\n    return exp.LowerHex(this=arg) if dialect.HEX_LOWERCASE else exp.Hex(this=arg)\n\n\ndef build_lower(args: t.List) -> exp.Lower | exp.Hex:\n    # LOWER(HEX(..)) can be simplified to LowerHex to simplify its transpilation\n    arg = seq_get(args, 0)\n    return exp.LowerHex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Lower(this=arg)\n\n\ndef build_upper(args: t.List) -> exp.Upper | exp.Hex:\n    # UPPER(HEX(..)) can be simplified to Hex to simplify its transpilation\n    arg = seq_get(args, 0)\n    return exp.Hex(this=arg.this) if isinstance(arg, exp.Hex) else exp.Upper(this=arg)\n\n\ndef build_extract_json_with_path(expr_type: t.Type[E]) -> t.Callable[[t.List, Dialect], E]:\n    def _builder(args: t.List, dialect: Dialect) -> E:\n        expression = expr_type(\n            this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1))\n        )\n        if len(args) > 2 and expr_type is exp.JSONExtract:\n            expression.set(\"expressions\", args[2:])\n        if expr_type is exp.JSONExtractScalar:\n            expression.set(\"scalar_only\", dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY)\n\n        return expression\n\n    return _builder\n\n\ndef build_mod(args: t.List) -> exp.Mod:\n    this = seq_get(args, 0)\n    expression = seq_get(args, 1)\n\n    # Wrap the operands if they are binary nodes, e.g. MOD(a + 1, 7) -> (a + 1) % 7\n    this = exp.Paren(this=this) if isinstance(this, exp.Binary) else this\n    expression = exp.Paren(this=expression) if isinstance(expression, exp.Binary) else expression\n\n    return exp.Mod(this=this, expression=expression)\n\n\ndef build_pad(args: t.List, is_left: bool = True):\n    return exp.Pad(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        fill_pattern=seq_get(args, 2),\n        is_left=is_left,\n    )\n\n\ndef build_array_constructor(\n    exp_class: t.Type[E], args: t.List, bracket_kind: TokenType, dialect: Dialect\n) -> exp.Expr:\n    array_exp = exp_class(expressions=args)\n\n    if exp_class == exp.Array and dialect.HAS_DISTINCT_ARRAY_CONSTRUCTORS:\n        array_exp.set(\"bracket_notation\", bracket_kind == TokenType.L_BRACKET)\n\n    return array_exp\n\n\ndef build_convert_timezone(\n    args: t.List, default_source_tz: t.Optional[str] = None\n) -> t.Union[exp.ConvertTimezone, exp.Anonymous]:\n    if len(args) == 2:\n        source_tz = exp.Literal.string(default_source_tz) if default_source_tz else None\n        return exp.ConvertTimezone(\n            source_tz=source_tz, target_tz=seq_get(args, 0), timestamp=seq_get(args, 1)\n        )\n\n    return exp.ConvertTimezone.from_arg_list(args)\n\n\ndef build_trim(args: t.List, is_left: bool = True, reverse_args: bool = False):\n    this, expression = seq_get(args, 0), seq_get(args, 1)\n\n    if expression and reverse_args:\n        this, expression = expression, this\n\n    return exp.Trim(this=this, expression=expression, position=\"LEADING\" if is_left else \"TRAILING\")\n\n\ndef build_coalesce(\n    args: t.List, is_nvl: t.Optional[bool] = None, is_null: t.Optional[bool] = None\n) -> exp.Coalesce:\n    return exp.Coalesce(this=seq_get(args, 0), expressions=args[1:], is_nvl=is_nvl, is_null=is_null)\n\n\ndef build_locate_strposition(args: t.List):\n    return exp.StrPosition(\n        this=seq_get(args, 1),\n        substr=seq_get(args, 0),\n        position=seq_get(args, 2),\n    )\n\n\ndef build_array_append(args: t.List, dialect: Dialect) -> exp.ArrayAppend:\n    \"\"\"\n    Builds ArrayAppend with NULL propagation semantics based on the dialect configuration.\n\n    Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.\n    Others (DuckDB, PostgreSQL) create a new single-element array instead.\n\n    Args:\n        args: Function arguments [array, element]\n        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from\n\n    Returns:\n        ArrayAppend expression with appropriate null_propagation flag\n    \"\"\"\n    return exp.ArrayAppend(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,\n    )\n\n\ndef build_array_prepend(args: t.List, dialect: Dialect) -> exp.ArrayPrepend:\n    \"\"\"\n    Builds ArrayPrepend with NULL propagation semantics based on the dialect configuration.\n\n    Some dialects (Databricks, Spark, Snowflake) return NULL when the input array is NULL.\n    Others (DuckDB, PostgreSQL) create a new single-element array instead.\n\n    Args:\n        args: Function arguments [array, element]\n        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from\n\n    Returns:\n        ArrayPrepend expression with appropriate null_propagation flag\n    \"\"\"\n    return exp.ArrayPrepend(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,\n    )\n\n\ndef build_array_concat(args: t.List, dialect: Dialect) -> exp.ArrayConcat:\n    \"\"\"\n    Builds ArrayConcat with NULL propagation semantics based on the dialect configuration.\n\n    Some dialects (Redshift, Snowflake) return NULL when any input array is NULL.\n    Others (DuckDB, PostgreSQL) skip NULL arrays and continue concatenation.\n\n    Args:\n        args: Function arguments [array1, array2, ...] (variadic)\n        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from\n\n    Returns:\n        ArrayConcat expression with appropriate null_propagation flag\n    \"\"\"\n    return exp.ArrayConcat(\n        this=seq_get(args, 0),\n        expressions=args[1:],\n        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,\n    )\n\n\ndef build_array_remove(args: t.List, dialect: Dialect) -> exp.ArrayRemove:\n    \"\"\"\n    Builds ArrayRemove with NULL propagation semantics based on the dialect configuration.\n\n    Some dialects (Snowflake) return NULL when the removal value is NULL.\n    Others (DuckDB) may return empty array due to NULL comparison semantics.\n\n    Args:\n        args: Function arguments [array, value_to_remove]\n        dialect: The dialect to read ARRAY_FUNCS_PROPAGATES_NULLS from\n\n    Returns:\n        ArrayRemove expression with appropriate null_propagation flag\n    \"\"\"\n    return exp.ArrayRemove(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        null_propagation=dialect.ARRAY_FUNCS_PROPAGATES_NULLS,\n    )\n\n\ndef _resolve_dialect(dialect: t.Any) -> t.Any:\n    from sqlglot.dialects.dialect import Dialect\n\n    return Dialect.get_or_raise(dialect)\n\n\nSENTINEL_NONE: Token = Token(TokenType.SENTINEL, \"SENTINEL\")\n\n\nclass Parser:\n    \"\"\"\n    Parser consumes a list of tokens produced by the Tokenizer and produces a parsed syntax tree.\n\n    Args:\n        error_level: The desired error level.\n            Default: ErrorLevel.IMMEDIATE\n        error_message_context: The amount of context to capture from a query string when displaying\n            the error message (in number of characters).\n            Default: 100\n        max_errors: Maximum number of error messages to include in a raised ParseError.\n            This is only relevant if error_level is ErrorLevel.RAISE.\n            Default: 3\n    \"\"\"\n\n    __slots__ = (\n        \"error_level\",\n        \"error_message_context\",\n        \"max_errors\",\n        \"dialect\",\n        \"sql\",\n        \"errors\",\n        \"_tokens\",\n        \"_index\",\n        \"_curr\",\n        \"_next\",\n        \"_prev\",\n        \"_prev_comments\",\n        \"_pipe_cte_counter\",\n        \"_chunks\",\n        \"_chunk_index\",\n        \"_tokens_size\",\n    )\n\n    FUNCTIONS: t.ClassVar[t.Dict[str, t.Callable]] = {\n        **{name: func.from_arg_list for name, func in exp.FUNCTION_BY_NAME.items()},\n        **dict.fromkeys((\"COALESCE\", \"IFNULL\", \"NVL\"), build_coalesce),\n        \"ARRAY\": lambda args, dialect: exp.Array(expressions=args),\n        \"ARRAYAGG\": lambda args, dialect: exp.ArrayAgg(\n            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None\n        ),\n        \"ARRAY_AGG\": lambda args, dialect: exp.ArrayAgg(\n            this=seq_get(args, 0), nulls_excluded=dialect.ARRAY_AGG_INCLUDES_NULLS is None or None\n        ),\n        \"ARRAY_APPEND\": build_array_append,\n        \"ARRAY_CAT\": build_array_concat,\n        \"ARRAY_CONCAT\": build_array_concat,\n        \"ARRAY_INTERSECT\": lambda args: exp.ArrayIntersect(expressions=args),\n        \"ARRAY_INTERSECTION\": lambda args: exp.ArrayIntersect(expressions=args),\n        \"ARRAY_PREPEND\": build_array_prepend,\n        \"ARRAY_REMOVE\": build_array_remove,\n        \"COUNT\": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=True),\n        \"CONCAT\": lambda args, dialect: exp.Concat(\n            expressions=args,\n            safe=not dialect.STRICT_STRING_CONCAT,\n            coalesce=dialect.CONCAT_COALESCE,\n        ),\n        \"CONCAT_WS\": lambda args, dialect: exp.ConcatWs(\n            expressions=args,\n            safe=not dialect.STRICT_STRING_CONCAT,\n            coalesce=dialect.CONCAT_COALESCE,\n        ),\n        \"CONVERT_TIMEZONE\": build_convert_timezone,\n        \"DATE_TO_DATE_STR\": lambda args: exp.Cast(\n            this=seq_get(args, 0),\n            to=exp.DataType(this=exp.DType.TEXT),\n        ),\n        \"GENERATE_DATE_ARRAY\": lambda args: exp.GenerateDateArray(\n            start=seq_get(args, 0),\n            end=seq_get(args, 1),\n            step=seq_get(args, 2) or exp.Interval(this=exp.Literal.string(1), unit=exp.var(\"DAY\")),\n        ),\n        \"GENERATE_UUID\": lambda args, dialect: exp.Uuid(\n            is_string=dialect.UUID_IS_STRING_TYPE or None\n        ),\n        \"GLOB\": lambda args: exp.Glob(this=seq_get(args, 1), expression=seq_get(args, 0)),\n        \"GREATEST\": lambda args, dialect: exp.Greatest(\n            this=seq_get(args, 0),\n            expressions=args[1:],\n            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,\n        ),\n        \"LEAST\": lambda args, dialect: exp.Least(\n            this=seq_get(args, 0),\n            expressions=args[1:],\n            ignore_nulls=dialect.LEAST_GREATEST_IGNORES_NULLS,\n        ),\n        \"HEX\": build_hex,\n        \"JSON_EXTRACT\": build_extract_json_with_path(exp.JSONExtract),\n        \"JSON_EXTRACT_SCALAR\": build_extract_json_with_path(exp.JSONExtractScalar),\n        \"JSON_EXTRACT_PATH_TEXT\": build_extract_json_with_path(exp.JSONExtractScalar),\n        \"JSON_KEYS\": lambda args, dialect: exp.JSONKeys(\n            this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1))\n        ),\n        \"LIKE\": build_like,\n        \"LOG\": build_logarithm,\n        \"LOG2\": lambda args: exp.Log(this=exp.Literal.number(2), expression=seq_get(args, 0)),\n        \"LOG10\": lambda args: exp.Log(this=exp.Literal.number(10), expression=seq_get(args, 0)),\n        \"LOWER\": build_lower,\n        \"LPAD\": lambda args: build_pad(args),\n        \"LEFTPAD\": lambda args: build_pad(args),\n        \"LTRIM\": lambda args: build_trim(args),\n        \"MOD\": build_mod,\n        \"RIGHTPAD\": lambda args: build_pad(args, is_left=False),\n        \"RPAD\": lambda args: build_pad(args, is_left=False),\n        \"RTRIM\": lambda args: build_trim(args, is_left=False),\n        \"SCOPE_RESOLUTION\": lambda args: (\n            exp.ScopeResolution(expression=seq_get(args, 0))\n            if len(args) != 2\n            else exp.ScopeResolution(this=seq_get(args, 0), expression=seq_get(args, 1))\n        ),\n        \"STRPOS\": exp.StrPosition.from_arg_list,\n        \"CHARINDEX\": lambda args: build_locate_strposition(args),\n        \"INSTR\": exp.StrPosition.from_arg_list,\n        \"LOCATE\": lambda args: build_locate_strposition(args),\n        \"TIME_TO_TIME_STR\": lambda args: exp.Cast(\n            this=seq_get(args, 0),\n            to=exp.DataType(this=exp.DType.TEXT),\n        ),\n        \"TO_HEX\": build_hex,\n        \"TS_OR_DS_TO_DATE_STR\": lambda args: exp.Substring(\n            this=exp.Cast(\n                this=seq_get(args, 0),\n                to=exp.DataType(this=exp.DType.TEXT),\n            ),\n            start=exp.Literal.number(1),\n            length=exp.Literal.number(10),\n        ),\n        \"UNNEST\": lambda args: exp.Unnest(expressions=ensure_list(seq_get(args, 0))),\n        \"UPPER\": build_upper,\n        \"UUID\": lambda args, dialect: exp.Uuid(is_string=dialect.UUID_IS_STRING_TYPE or None),\n        \"VAR_MAP\": build_var_map,\n    }\n\n    NO_PAREN_FUNCTIONS: t.ClassVar[t.Dict] = {\n        TokenType.CURRENT_DATE: exp.CurrentDate,\n        TokenType.CURRENT_DATETIME: exp.CurrentDate,\n        TokenType.CURRENT_TIME: exp.CurrentTime,\n        TokenType.CURRENT_TIMESTAMP: exp.CurrentTimestamp,\n        TokenType.CURRENT_USER: exp.CurrentUser,\n        TokenType.CURRENT_ROLE: exp.CurrentRole,\n    }\n\n    STRUCT_TYPE_TOKENS: t.ClassVar = {\n        TokenType.NESTED,\n        TokenType.OBJECT,\n        TokenType.STRUCT,\n        TokenType.UNION,\n    }\n\n    NESTED_TYPE_TOKENS: t.ClassVar = {\n        TokenType.ARRAY,\n        TokenType.LIST,\n        TokenType.LOWCARDINALITY,\n        TokenType.MAP,\n        TokenType.NULLABLE,\n        TokenType.RANGE,\n        *STRUCT_TYPE_TOKENS,\n    }\n\n    ENUM_TYPE_TOKENS: t.ClassVar = {\n        TokenType.DYNAMIC,\n        TokenType.ENUM,\n        TokenType.ENUM8,\n        TokenType.ENUM16,\n    }\n\n    AGGREGATE_TYPE_TOKENS: t.ClassVar = {\n        TokenType.AGGREGATEFUNCTION,\n        TokenType.SIMPLEAGGREGATEFUNCTION,\n    }\n\n    TYPE_TOKENS: t.ClassVar = {\n        TokenType.BIT,\n        TokenType.BOOLEAN,\n        TokenType.TINYINT,\n        TokenType.UTINYINT,\n        TokenType.SMALLINT,\n        TokenType.USMALLINT,\n        TokenType.INT,\n        TokenType.UINT,\n        TokenType.BIGINT,\n        TokenType.UBIGINT,\n        TokenType.BIGNUM,\n        TokenType.INT128,\n        TokenType.UINT128,\n        TokenType.INT256,\n        TokenType.UINT256,\n        TokenType.MEDIUMINT,\n        TokenType.UMEDIUMINT,\n        TokenType.FIXEDSTRING,\n        TokenType.FLOAT,\n        TokenType.DOUBLE,\n        TokenType.UDOUBLE,\n        TokenType.CHAR,\n        TokenType.NCHAR,\n        TokenType.VARCHAR,\n        TokenType.NVARCHAR,\n        TokenType.BPCHAR,\n        TokenType.TEXT,\n        TokenType.MEDIUMTEXT,\n        TokenType.LONGTEXT,\n        TokenType.BLOB,\n        TokenType.MEDIUMBLOB,\n        TokenType.LONGBLOB,\n        TokenType.BINARY,\n        TokenType.VARBINARY,\n        TokenType.JSON,\n        TokenType.JSONB,\n        TokenType.INTERVAL,\n        TokenType.TINYBLOB,\n        TokenType.TINYTEXT,\n        TokenType.TIME,\n        TokenType.TIMETZ,\n        TokenType.TIME_NS,\n        TokenType.TIMESTAMP,\n        TokenType.TIMESTAMP_S,\n        TokenType.TIMESTAMP_MS,\n        TokenType.TIMESTAMP_NS,\n        TokenType.TIMESTAMPTZ,\n        TokenType.TIMESTAMPLTZ,\n        TokenType.TIMESTAMPNTZ,\n        TokenType.DATETIME,\n        TokenType.DATETIME2,\n        TokenType.DATETIME64,\n        TokenType.SMALLDATETIME,\n        TokenType.DATE,\n        TokenType.DATE32,\n        TokenType.INT4RANGE,\n        TokenType.INT4MULTIRANGE,\n        TokenType.INT8RANGE,\n        TokenType.INT8MULTIRANGE,\n        TokenType.NUMRANGE,\n        TokenType.NUMMULTIRANGE,\n        TokenType.TSRANGE,\n        TokenType.TSMULTIRANGE,\n        TokenType.TSTZRANGE,\n        TokenType.TSTZMULTIRANGE,\n        TokenType.DATERANGE,\n        TokenType.DATEMULTIRANGE,\n        TokenType.DECIMAL,\n        TokenType.DECIMAL32,\n        TokenType.DECIMAL64,\n        TokenType.DECIMAL128,\n        TokenType.DECIMAL256,\n        TokenType.DECFLOAT,\n        TokenType.UDECIMAL,\n        TokenType.BIGDECIMAL,\n        TokenType.UUID,\n        TokenType.GEOGRAPHY,\n        TokenType.GEOGRAPHYPOINT,\n        TokenType.GEOMETRY,\n        TokenType.POINT,\n        TokenType.RING,\n        TokenType.LINESTRING,\n        TokenType.MULTILINESTRING,\n        TokenType.POLYGON,\n        TokenType.MULTIPOLYGON,\n        TokenType.HLLSKETCH,\n        TokenType.HSTORE,\n        TokenType.PSEUDO_TYPE,\n        TokenType.SUPER,\n        TokenType.SERIAL,\n        TokenType.SMALLSERIAL,\n        TokenType.BIGSERIAL,\n        TokenType.XML,\n        TokenType.YEAR,\n        TokenType.USERDEFINED,\n        TokenType.MONEY,\n        TokenType.SMALLMONEY,\n        TokenType.ROWVERSION,\n        TokenType.IMAGE,\n        TokenType.VARIANT,\n        TokenType.VECTOR,\n        TokenType.VOID,\n        TokenType.OBJECT,\n        TokenType.OBJECT_IDENTIFIER,\n        TokenType.INET,\n        TokenType.IPADDRESS,\n        TokenType.IPPREFIX,\n        TokenType.IPV4,\n        TokenType.IPV6,\n        TokenType.UNKNOWN,\n        TokenType.NOTHING,\n        TokenType.NULL,\n        TokenType.NAME,\n        TokenType.TDIGEST,\n        TokenType.DYNAMIC,\n        *ENUM_TYPE_TOKENS,\n        *NESTED_TYPE_TOKENS,\n        *AGGREGATE_TYPE_TOKENS,\n    }\n\n    SIGNED_TO_UNSIGNED_TYPE_TOKEN: t.ClassVar = {\n        TokenType.BIGINT: TokenType.UBIGINT,\n        TokenType.INT: TokenType.UINT,\n        TokenType.MEDIUMINT: TokenType.UMEDIUMINT,\n        TokenType.SMALLINT: TokenType.USMALLINT,\n        TokenType.TINYINT: TokenType.UTINYINT,\n        TokenType.DECIMAL: TokenType.UDECIMAL,\n        TokenType.DOUBLE: TokenType.UDOUBLE,\n    }\n\n    SUBQUERY_PREDICATES: t.ClassVar = {\n        TokenType.ANY: exp.Any,\n        TokenType.ALL: exp.All,\n        TokenType.EXISTS: exp.Exists,\n        TokenType.SOME: exp.Any,\n    }\n\n    SUBQUERY_TOKENS: t.ClassVar = {\n        TokenType.SELECT,\n        TokenType.WITH,\n        TokenType.FROM,\n    }\n\n    RESERVED_TOKENS: t.ClassVar = {\n        *Tokenizer.SINGLE_TOKENS.values(),\n        TokenType.SELECT,\n    } - {TokenType.IDENTIFIER}\n\n    DB_CREATABLES: t.ClassVar = {\n        TokenType.DATABASE,\n        TokenType.DICTIONARY,\n        TokenType.FILE_FORMAT,\n        TokenType.MODEL,\n        TokenType.NAMESPACE,\n        TokenType.SCHEMA,\n        TokenType.SEMANTIC_VIEW,\n        TokenType.SEQUENCE,\n        TokenType.SINK,\n        TokenType.SOURCE,\n        TokenType.STAGE,\n        TokenType.STORAGE_INTEGRATION,\n        TokenType.STREAMLIT,\n        TokenType.TABLE,\n        TokenType.TAG,\n        TokenType.VIEW,\n        TokenType.WAREHOUSE,\n    }\n\n    CREATABLES: t.ClassVar = {\n        TokenType.COLUMN,\n        TokenType.CONSTRAINT,\n        TokenType.FOREIGN_KEY,\n        TokenType.FUNCTION,\n        TokenType.INDEX,\n        TokenType.PROCEDURE,\n        TokenType.TRIGGER,\n        *DB_CREATABLES,\n    }\n\n    TRIGGER_EVENTS: t.ClassVar = {\n        TokenType.INSERT,\n        TokenType.UPDATE,\n        TokenType.DELETE,\n        TokenType.TRUNCATE,\n    }\n\n    ALTERABLES: t.ClassVar = {\n        TokenType.INDEX,\n        TokenType.TABLE,\n        TokenType.VIEW,\n        TokenType.SESSION,\n    }\n\n    # Tokens that can represent identifiers\n    ID_VAR_TOKENS: t.ClassVar[t.Set] = {\n        TokenType.ALL,\n        TokenType.ANALYZE,\n        TokenType.ATTACH,\n        TokenType.VAR,\n        TokenType.ANTI,\n        TokenType.APPLY,\n        TokenType.ASC,\n        TokenType.ASOF,\n        TokenType.AUTO_INCREMENT,\n        TokenType.BEGIN,\n        TokenType.BPCHAR,\n        TokenType.CACHE,\n        TokenType.CASE,\n        TokenType.COLLATE,\n        TokenType.COMMAND,\n        TokenType.COMMENT,\n        TokenType.COMMIT,\n        TokenType.CONSTRAINT,\n        TokenType.COPY,\n        TokenType.CUBE,\n        TokenType.CURRENT_SCHEMA,\n        TokenType.DEFAULT,\n        TokenType.DELETE,\n        TokenType.DESC,\n        TokenType.DESCRIBE,\n        TokenType.DETACH,\n        TokenType.DICTIONARY,\n        TokenType.DIV,\n        TokenType.END,\n        TokenType.EXECUTE,\n        TokenType.EXPORT,\n        TokenType.ESCAPE,\n        TokenType.FALSE,\n        TokenType.FIRST,\n        TokenType.FILE,\n        TokenType.FILTER,\n        TokenType.FINAL,\n        TokenType.FORMAT,\n        TokenType.FULL,\n        TokenType.GET,\n        TokenType.IDENTIFIER,\n        TokenType.INOUT,\n        TokenType.IS,\n        TokenType.ISNULL,\n        TokenType.INTERVAL,\n        TokenType.KEEP,\n        TokenType.KILL,\n        TokenType.LEFT,\n        TokenType.LIMIT,\n        TokenType.LOAD,\n        TokenType.LOCK,\n        TokenType.MATCH,\n        TokenType.MERGE,\n        TokenType.NATURAL,\n        TokenType.NEXT,\n        TokenType.OFFSET,\n        TokenType.OPERATOR,\n        TokenType.ORDINALITY,\n        TokenType.OVER,\n        TokenType.OVERLAPS,\n        TokenType.OVERWRITE,\n        TokenType.PARTITION,\n        TokenType.PERCENT,\n        TokenType.PIVOT,\n        TokenType.PRAGMA,\n        TokenType.PUT,\n        TokenType.RANGE,\n        TokenType.RECURSIVE,\n        TokenType.REFERENCES,\n        TokenType.REFRESH,\n        TokenType.RENAME,\n        TokenType.REPLACE,\n        TokenType.RIGHT,\n        TokenType.ROLLUP,\n        TokenType.ROW,\n        TokenType.ROWS,\n        TokenType.SEMI,\n        TokenType.SET,\n        TokenType.SETTINGS,\n        TokenType.SHOW,\n        TokenType.STREAM,\n        TokenType.STREAMLIT,\n        TokenType.TEMPORARY,\n        TokenType.TOP,\n        TokenType.TRUE,\n        TokenType.TRUNCATE,\n        TokenType.UNIQUE,\n        TokenType.UNNEST,\n        TokenType.UNPIVOT,\n        TokenType.UPDATE,\n        TokenType.USE,\n        TokenType.VOLATILE,\n        TokenType.WINDOW,\n        TokenType.CURRENT_CATALOG,\n        TokenType.LOCALTIME,\n        TokenType.LOCALTIMESTAMP,\n        TokenType.SESSION_USER,\n        TokenType.STRAIGHT_JOIN,\n        *ALTERABLES,\n        *CREATABLES,\n        *SUBQUERY_PREDICATES,\n        *TYPE_TOKENS,\n        *NO_PAREN_FUNCTIONS,\n    } - {TokenType.UNION}\n\n    TABLE_ALIAS_TOKENS: t.ClassVar[t.Set] = ID_VAR_TOKENS - {\n        TokenType.ANTI,\n        TokenType.ASOF,\n        TokenType.FULL,\n        TokenType.LEFT,\n        TokenType.LOCK,\n        TokenType.NATURAL,\n        TokenType.RIGHT,\n        TokenType.SEMI,\n        TokenType.WINDOW,\n    }\n\n    ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS\n\n    COLON_PLACEHOLDER_TOKENS: t.ClassVar = ID_VAR_TOKENS\n\n    ARRAY_CONSTRUCTORS: t.ClassVar = {\n        \"ARRAY\": exp.Array,\n        \"LIST\": exp.List,\n    }\n\n    COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.IS}\n\n    UPDATE_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - {TokenType.SET}\n\n    TRIM_TYPES: t.ClassVar = {\"LEADING\", \"TRAILING\", \"BOTH\"}\n\n    # Tokens that indicate a simple column reference\n    IDENTIFIER_TOKENS: t.ClassVar[t.FrozenSet] = frozenset({TokenType.VAR, TokenType.IDENTIFIER})\n\n    BRACKETS: t.ClassVar[t.FrozenSet] = frozenset({TokenType.L_BRACKET, TokenType.L_BRACE})\n\n    # Postfix tokens that prevent the bare column fast path\n    COLUMN_POSTFIX_TOKENS: t.ClassVar[t.FrozenSet] = frozenset(\n        {\n            TokenType.L_PAREN,\n            TokenType.L_BRACKET,\n            TokenType.L_BRACE,\n            TokenType.COLON,\n            TokenType.JOIN_MARKER,\n        }\n    )\n\n    TABLE_POSTFIX_TOKENS: t.ClassVar[t.FrozenSet] = frozenset(\n        {\n            TokenType.L_PAREN,\n            TokenType.L_BRACKET,\n            TokenType.L_BRACE,\n            TokenType.PIVOT,\n            TokenType.UNPIVOT,\n            TokenType.TABLE_SAMPLE,\n        }\n    )\n\n    FUNC_TOKENS: t.ClassVar = {\n        TokenType.COLLATE,\n        TokenType.COMMAND,\n        TokenType.CURRENT_DATE,\n        TokenType.CURRENT_DATETIME,\n        TokenType.CURRENT_SCHEMA,\n        TokenType.CURRENT_TIMESTAMP,\n        TokenType.CURRENT_TIME,\n        TokenType.CURRENT_USER,\n        TokenType.CURRENT_CATALOG,\n        TokenType.FILTER,\n        TokenType.FIRST,\n        TokenType.FORMAT,\n        TokenType.GET,\n        TokenType.GLOB,\n        TokenType.IDENTIFIER,\n        TokenType.INDEX,\n        TokenType.ISNULL,\n        TokenType.ILIKE,\n        TokenType.INSERT,\n        TokenType.LIKE,\n        TokenType.LOCALTIME,\n        TokenType.LOCALTIMESTAMP,\n        TokenType.MERGE,\n        TokenType.NEXT,\n        TokenType.OFFSET,\n        TokenType.PRIMARY_KEY,\n        TokenType.RANGE,\n        TokenType.REPLACE,\n        TokenType.RLIKE,\n        TokenType.ROW,\n        TokenType.SESSION_USER,\n        TokenType.UNNEST,\n        TokenType.VAR,\n        TokenType.LEFT,\n        TokenType.RIGHT,\n        TokenType.SEQUENCE,\n        TokenType.DATE,\n        TokenType.DATETIME,\n        TokenType.TABLE,\n        TokenType.TIMESTAMP,\n        TokenType.TIMESTAMPTZ,\n        TokenType.TRUNCATE,\n        TokenType.UTC_DATE,\n        TokenType.UTC_TIME,\n        TokenType.UTC_TIMESTAMP,\n        TokenType.WINDOW,\n        TokenType.XOR,\n        *TYPE_TOKENS,\n        *SUBQUERY_PREDICATES,\n    }\n\n    CONJUNCTION: t.ClassVar[t.Dict[TokenType, t.Type[exp.Expr]]] = {\n        TokenType.AND: exp.And,\n    }\n\n    ASSIGNMENT: t.ClassVar[t.Dict[TokenType, t.Type[exp.Expr]]] = {\n        TokenType.COLON_EQ: exp.PropertyEQ,\n    }\n\n    DISJUNCTION: t.ClassVar[t.Dict[TokenType, t.Type[exp.Expr]]] = {\n        TokenType.OR: exp.Or,\n    }\n\n    EQUALITY: t.ClassVar = {\n        TokenType.EQ: exp.EQ,\n        TokenType.NEQ: exp.NEQ,\n        TokenType.NULLSAFE_EQ: exp.NullSafeEQ,\n    }\n\n    COMPARISON: t.ClassVar = {\n        TokenType.GT: exp.GT,\n        TokenType.GTE: exp.GTE,\n        TokenType.LT: exp.LT,\n        TokenType.LTE: exp.LTE,\n    }\n\n    BITWISE: t.ClassVar = {\n        TokenType.AMP: exp.BitwiseAnd,\n        TokenType.CARET: exp.BitwiseXor,\n        TokenType.PIPE: exp.BitwiseOr,\n    }\n\n    TERM: t.ClassVar = {\n        TokenType.DASH: exp.Sub,\n        TokenType.PLUS: exp.Add,\n        TokenType.MOD: exp.Mod,\n        TokenType.COLLATE: exp.Collate,\n    }\n\n    FACTOR: t.ClassVar = {\n        TokenType.DIV: exp.IntDiv,\n        TokenType.LR_ARROW: exp.Distance,\n        TokenType.SLASH: exp.Div,\n        TokenType.STAR: exp.Mul,\n    }\n\n    EXPONENT: t.ClassVar[t.Dict[TokenType, t.Type[exp.Expr]]] = {}\n\n    TIMES: t.ClassVar = {\n        TokenType.TIME,\n        TokenType.TIMETZ,\n    }\n\n    TIMESTAMPS: t.ClassVar = {\n        TokenType.TIMESTAMP,\n        TokenType.TIMESTAMPNTZ,\n        TokenType.TIMESTAMPTZ,\n        TokenType.TIMESTAMPLTZ,\n        *TIMES,\n    }\n\n    SET_OPERATIONS: t.ClassVar = {\n        TokenType.UNION,\n        TokenType.INTERSECT,\n        TokenType.EXCEPT,\n    }\n\n    JOIN_METHODS: t.ClassVar = {\n        TokenType.ASOF,\n        TokenType.NATURAL,\n        TokenType.POSITIONAL,\n    }\n\n    JOIN_SIDES: t.ClassVar = {\n        TokenType.LEFT,\n        TokenType.RIGHT,\n        TokenType.FULL,\n    }\n\n    JOIN_KINDS: t.ClassVar = {\n        TokenType.ANTI,\n        TokenType.CROSS,\n        TokenType.INNER,\n        TokenType.OUTER,\n        TokenType.SEMI,\n        TokenType.STRAIGHT_JOIN,\n    }\n\n    JOIN_HINTS: t.ClassVar[t.Set[str]] = set()\n\n    # Tokens that unambiguously end a table reference on the fast path\n    TABLE_TERMINATORS: t.ClassVar[t.FrozenSet] = frozenset(\n        {\n            TokenType.COMMA,\n            TokenType.GROUP_BY,\n            TokenType.HAVING,\n            TokenType.JOIN,\n            TokenType.LIMIT,\n            TokenType.ON,\n            TokenType.ORDER_BY,\n            TokenType.R_PAREN,\n            TokenType.SEMICOLON,\n            TokenType.SENTINEL,\n            TokenType.WHERE,\n            *SET_OPERATIONS,\n            *JOIN_KINDS,\n            *JOIN_METHODS,\n            *JOIN_SIDES,\n        }\n    )\n\n    LAMBDAS: t.ClassVar = {\n        TokenType.ARROW: lambda self, expressions: self.expression(\n            exp.Lambda(\n                this=self._replace_lambda(\n                    self._parse_disjunction(),\n                    expressions,\n                ),\n                expressions=expressions,\n            )\n        ),\n        TokenType.FARROW: lambda self, expressions: self.expression(\n            exp.Kwarg(this=exp.var(expressions[0].name), expression=self._parse_disjunction())\n        ),\n    }\n\n    # Whether lambda args include type annotations, e.g. TRANSFORM(arr, x INT -> x + 1) in Snowflake\n    TYPED_LAMBDA_ARGS: t.ClassVar[bool] = False\n\n    LAMBDA_ARG_TERMINATORS: t.ClassVar[t.FrozenSet] = frozenset(\n        {TokenType.COMMA, TokenType.R_PAREN}\n    )\n\n    COLUMN_OPERATORS: t.ClassVar = {\n        TokenType.DOT: None,\n        TokenType.DOTCOLON: lambda self, this, to: self.expression(exp.JSONCast(this=this, to=to)),\n        TokenType.DCOLON: lambda self, this, to: self.build_cast(\n            strict=self.STRICT_CAST, this=this, to=to\n        ),\n        TokenType.ARROW: lambda self, this, path: self.expression(\n            exp.JSONExtract(\n                this=this,\n                expression=self.dialect.to_json_path(path),\n                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,\n            )\n        ),\n        TokenType.DARROW: lambda self, this, path: self.expression(\n            exp.JSONExtractScalar(\n                this=this,\n                expression=self.dialect.to_json_path(path),\n                only_json_types=self.JSON_ARROWS_REQUIRE_JSON_TYPE,\n                scalar_only=self.dialect.JSON_EXTRACT_SCALAR_SCALAR_ONLY,\n            )\n        ),\n        TokenType.HASH_ARROW: lambda self, this, path: self.expression(\n            exp.JSONBExtract(this=this, expression=path)\n        ),\n        TokenType.DHASH_ARROW: lambda self, this, path: self.expression(\n            exp.JSONBExtractScalar(this=this, expression=path)\n        ),\n        TokenType.PLACEHOLDER: lambda self, this, key: self.expression(\n            exp.JSONBContains(this=this, expression=key)\n        ),\n    }\n\n    CAST_COLUMN_OPERATORS: t.ClassVar = {\n        TokenType.DOTCOLON,\n        TokenType.DCOLON,\n    }\n\n    EXPRESSION_PARSERS: t.ClassVar = {\n        exp.Cluster: lambda self: self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),\n        exp.Column: lambda self: self._parse_column(),\n        exp.ColumnDef: lambda self: self._parse_column_def(self._parse_column()),\n        exp.Condition: lambda self: self._parse_disjunction(),\n        exp.DataType: lambda self: self._parse_types(allow_identifiers=False, schema=True),\n        exp.Expr: lambda self: self._parse_expression(),\n        exp.From: lambda self: self._parse_from(joins=True),\n        exp.GrantPrincipal: lambda self: self._parse_grant_principal(),\n        exp.GrantPrivilege: lambda self: self._parse_grant_privilege(),\n        exp.Group: lambda self: self._parse_group(),\n        exp.Having: lambda self: self._parse_having(),\n        exp.Hint: lambda self: self._parse_hint_body(),\n        exp.Identifier: lambda self: self._parse_id_var(),\n        exp.Join: lambda self: self._parse_join(),\n        exp.Lambda: lambda self: self._parse_lambda(),\n        exp.Lateral: lambda self: self._parse_lateral(),\n        exp.Limit: lambda self: self._parse_limit(),\n        exp.Offset: lambda self: self._parse_offset(),\n        exp.Order: lambda self: self._parse_order(),\n        exp.Ordered: lambda self: self._parse_ordered(),\n        exp.Properties: lambda self: self._parse_properties(),\n        exp.PartitionedByProperty: lambda self: self._parse_partitioned_by(),\n        exp.Qualify: lambda self: self._parse_qualify(),\n        exp.Returning: lambda self: self._parse_returning(),\n        exp.Select: lambda self: self._parse_select(),\n        exp.Sort: lambda self: self._parse_sort(exp.Sort, TokenType.SORT_BY),\n        exp.Table: lambda self: self._parse_table_parts(),\n        exp.TableAlias: lambda self: self._parse_table_alias(),\n        exp.Tuple: lambda self: self._parse_value(values=False),\n        exp.Whens: lambda self: self._parse_when_matched(),\n        exp.Where: lambda self: self._parse_where(),\n        exp.Window: lambda self: self._parse_named_window(),\n        exp.With: lambda self: self._parse_with(),\n    }\n\n    STATEMENT_PARSERS: t.ClassVar = {\n        TokenType.ALTER: lambda self: self._parse_alter(),\n        TokenType.ANALYZE: lambda self: self._parse_analyze(),\n        TokenType.BEGIN: lambda self: self._parse_transaction(),\n        TokenType.CACHE: lambda self: self._parse_cache(),\n        TokenType.COMMENT: lambda self: self._parse_comment(),\n        TokenType.COMMIT: lambda self: self._parse_commit_or_rollback(),\n        TokenType.COPY: lambda self: self._parse_copy(),\n        TokenType.CREATE: lambda self: self._parse_create(),\n        TokenType.DELETE: lambda self: self._parse_delete(),\n        TokenType.DESC: lambda self: self._parse_describe(),\n        TokenType.DESCRIBE: lambda self: self._parse_describe(),\n        TokenType.DROP: lambda self: self._parse_drop(),\n        TokenType.GRANT: lambda self: self._parse_grant(),\n        TokenType.REVOKE: lambda self: self._parse_revoke(),\n        TokenType.INSERT: lambda self: self._parse_insert(),\n        TokenType.KILL: lambda self: self._parse_kill(),\n        TokenType.LOAD: lambda self: self._parse_load(),\n        TokenType.MERGE: lambda self: self._parse_merge(),\n        TokenType.PIVOT: lambda self: self._parse_simplified_pivot(),\n        TokenType.PRAGMA: lambda self: self.expression(exp.Pragma(this=self._parse_expression())),\n        TokenType.REFRESH: lambda self: self._parse_refresh(),\n        TokenType.ROLLBACK: lambda self: self._parse_commit_or_rollback(),\n        TokenType.SET: lambda self: self._parse_set(),\n        TokenType.TRUNCATE: lambda self: self._parse_truncate_table(),\n        TokenType.UNCACHE: lambda self: self._parse_uncache(),\n        TokenType.UNPIVOT: lambda self: self._parse_simplified_pivot(is_unpivot=True),\n        TokenType.UPDATE: lambda self: self._parse_update(),\n        TokenType.USE: lambda self: self._parse_use(),\n        TokenType.SEMICOLON: lambda self: exp.Semicolon(),\n    }\n\n    UNARY_PARSERS: t.ClassVar = {\n        TokenType.PLUS: lambda self: self._parse_unary(),  # Unary + is handled as a no-op\n        TokenType.NOT: lambda self: self.expression(exp.Not(this=self._parse_equality())),\n        TokenType.TILDE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())),\n        TokenType.DASH: lambda self: self.expression(exp.Neg(this=self._parse_unary())),\n        TokenType.PIPE_SLASH: lambda self: self.expression(exp.Sqrt(this=self._parse_unary())),\n        TokenType.DPIPE_SLASH: lambda self: self.expression(exp.Cbrt(this=self._parse_unary())),\n    }\n\n    STRING_PARSERS: t.ClassVar = {\n        TokenType.HEREDOC_STRING: lambda self, token: self.expression(\n            exp.RawString(this=token.text), token\n        ),\n        TokenType.NATIONAL_STRING: lambda self, token: self.expression(\n            exp.National(this=token.text), token\n        ),\n        TokenType.RAW_STRING: lambda self, token: self.expression(\n            exp.RawString(this=token.text), token\n        ),\n        TokenType.STRING: lambda self, token: self.expression(\n            exp.Literal(this=token.text, is_string=True), token\n        ),\n        TokenType.UNICODE_STRING: lambda self, token: self.expression(\n            exp.UnicodeString(\n                this=token.text, escape=self._match_text_seq(\"UESCAPE\") and self._parse_string()\n            ),\n            token,\n        ),\n    }\n\n    NUMERIC_PARSERS: t.ClassVar = {\n        TokenType.BIT_STRING: lambda self, token: self.expression(\n            exp.BitString(this=token.text), token\n        ),\n        TokenType.BYTE_STRING: lambda self, token: self.expression(\n            exp.ByteString(\n                this=token.text, is_bytes=self.dialect.BYTE_STRING_IS_BYTES_TYPE or None\n            ),\n            token,\n        ),\n        TokenType.HEX_STRING: lambda self, token: self.expression(\n            exp.HexString(\n                this=token.text, is_integer=self.dialect.HEX_STRING_IS_INTEGER_TYPE or None\n            ),\n            token,\n        ),\n        TokenType.NUMBER: lambda self, token: self.expression(\n            exp.Literal(this=token.text, is_string=False), token\n        ),\n    }\n\n    PRIMARY_PARSERS: t.ClassVar = {\n        **STRING_PARSERS,\n        **NUMERIC_PARSERS,\n        TokenType.INTRODUCER: lambda self, token: self._parse_introducer(token),\n        TokenType.NULL: lambda self, _: self.expression(exp.Null()),\n        TokenType.TRUE: lambda self, _: self.expression(exp.Boolean(this=True)),\n        TokenType.FALSE: lambda self, _: self.expression(exp.Boolean(this=False)),\n        TokenType.SESSION_PARAMETER: lambda self, _: self._parse_session_parameter(),\n        TokenType.STAR: lambda self, _: self._parse_star_ops(),\n    }\n\n    PLACEHOLDER_PARSERS: t.ClassVar = {\n        TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder()),\n        TokenType.PARAMETER: lambda self: self._parse_parameter(),\n        TokenType.COLON: lambda self: (\n            self.expression(exp.Placeholder(this=self._prev.text))\n            if self._match_set(self.COLON_PLACEHOLDER_TOKENS)\n            else None\n        ),\n    }\n\n    RANGE_PARSERS: t.ClassVar = {\n        TokenType.AT_GT: binary_range_parser(exp.ArrayContainsAll),\n        TokenType.BETWEEN: lambda self, this: self._parse_between(this),\n        TokenType.GLOB: binary_range_parser(exp.Glob),\n        TokenType.ILIKE: binary_range_parser(exp.ILike),\n        TokenType.IN: lambda self, this: self._parse_in(this),\n        TokenType.IRLIKE: binary_range_parser(exp.RegexpILike),\n        TokenType.IS: lambda self, this: self._parse_is(this),\n        TokenType.LIKE: binary_range_parser(exp.Like),\n        TokenType.LT_AT: binary_range_parser(exp.ArrayContainsAll, reverse_args=True),\n        TokenType.OVERLAPS: binary_range_parser(exp.Overlaps),\n        TokenType.RLIKE: binary_range_parser(exp.RegexpLike),\n        TokenType.SIMILAR_TO: binary_range_parser(exp.SimilarTo),\n        TokenType.FOR: lambda self, this: self._parse_comprehension(this),\n        TokenType.QMARK_AMP: binary_range_parser(exp.JSONBContainsAllTopKeys),\n        TokenType.QMARK_PIPE: binary_range_parser(exp.JSONBContainsAnyTopKeys),\n        TokenType.HASH_DASH: binary_range_parser(exp.JSONBDeleteAtPath),\n        TokenType.ADJACENT: binary_range_parser(exp.Adjacent),\n        TokenType.OPERATOR: lambda self, this: self._parse_operator(this),\n        TokenType.AMP_LT: binary_range_parser(exp.ExtendsLeft),\n        TokenType.AMP_GT: binary_range_parser(exp.ExtendsRight),\n    }\n\n    PIPE_SYNTAX_TRANSFORM_PARSERS: t.ClassVar = {\n        \"AGGREGATE\": lambda self, query: self._parse_pipe_syntax_aggregate(query),\n        \"AS\": lambda self, query: self._build_pipe_cte(\n            query, [exp.Star()], self._parse_table_alias()\n        ),\n        \"EXTEND\": lambda self, query: self._parse_pipe_syntax_extend(query),\n        \"LIMIT\": lambda self, query: self._parse_pipe_syntax_limit(query),\n        \"ORDER BY\": lambda self, query: query.order_by(\n            self._parse_order(), append=False, copy=False\n        ),\n        \"PIVOT\": lambda self, query: self._parse_pipe_syntax_pivot(query),\n        \"SELECT\": lambda self, query: self._parse_pipe_syntax_select(query),\n        \"TABLESAMPLE\": lambda self, query: self._parse_pipe_syntax_tablesample(query),\n        \"UNPIVOT\": lambda self, query: self._parse_pipe_syntax_pivot(query),\n        \"WHERE\": lambda self, query: query.where(self._parse_where(), copy=False),\n    }\n\n    PROPERTY_PARSERS: t.ClassVar[t.Dict[str, t.Callable]] = {\n        \"ALLOWED_VALUES\": lambda self: self.expression(\n            exp.AllowedValuesProperty(expressions=self._parse_csv(self._parse_primary))\n        ),\n        \"ALGORITHM\": lambda self: self._parse_property_assignment(exp.AlgorithmProperty),\n        \"AUTO\": lambda self: self._parse_auto_property(),\n        \"AUTO_INCREMENT\": lambda self: self._parse_property_assignment(exp.AutoIncrementProperty),\n        \"BACKUP\": lambda self: self.expression(\n            exp.BackupProperty(this=self._parse_var(any_token=True))\n        ),\n        \"BLOCKCOMPRESSION\": lambda self: self._parse_blockcompression(),\n        \"CHARSET\": lambda self, **kwargs: self._parse_character_set(**kwargs),\n        \"CHARACTER SET\": lambda self, **kwargs: self._parse_character_set(**kwargs),\n        \"CHECKSUM\": lambda self: self._parse_checksum(),\n        \"CLUSTER BY\": lambda self: self._parse_cluster(),\n        \"CLUSTERED\": lambda self: self._parse_clustered_by(),\n        \"COLLATE\": lambda self, **kwargs: self._parse_property_assignment(\n            exp.CollateProperty, **kwargs\n        ),\n        \"COMMENT\": lambda self: self._parse_property_assignment(exp.SchemaCommentProperty),\n        \"CONTAINS\": lambda self: self._parse_contains_property(),\n        \"COPY\": lambda self: self._parse_copy_property(),\n        \"DATABLOCKSIZE\": lambda self, **kwargs: self._parse_datablocksize(**kwargs),\n        \"DATA_DELETION\": lambda self: self._parse_data_deletion_property(),\n        \"DEFINER\": lambda self: self._parse_definer(),\n        \"DETERMINISTIC\": lambda self: self.expression(\n            exp.StabilityProperty(this=exp.Literal.string(\"IMMUTABLE\"))\n        ),\n        \"DISTRIBUTED\": lambda self: self._parse_distributed_property(),\n        \"DUPLICATE\": lambda self: self._parse_composite_key_property(exp.DuplicateKeyProperty),\n        \"DYNAMIC\": lambda self: self.expression(exp.DynamicProperty()),\n        \"DISTKEY\": lambda self: self._parse_distkey(),\n        \"DISTSTYLE\": lambda self: self._parse_property_assignment(exp.DistStyleProperty),\n        \"EMPTY\": lambda self: self.expression(exp.EmptyProperty()),\n        \"ENGINE\": lambda self: self._parse_property_assignment(exp.EngineProperty),\n        \"ENVIRONMENT\": lambda self: self.expression(\n            exp.EnviromentProperty(expressions=self._parse_wrapped_csv(self._parse_assignment))\n        ),\n        \"HANDLER\": lambda self: self._parse_property_assignment(exp.HandlerProperty),\n        \"EXECUTE\": lambda self: self._parse_property_assignment(exp.ExecuteAsProperty),\n        \"EXTERNAL\": lambda self: self.expression(exp.ExternalProperty()),\n        \"FALLBACK\": lambda self, **kwargs: self._parse_fallback(**kwargs),\n        \"FORMAT\": lambda self: self._parse_property_assignment(exp.FileFormatProperty),\n        \"FREESPACE\": lambda self: self._parse_freespace(),\n        \"GLOBAL\": lambda self: self.expression(exp.GlobalProperty()),\n        \"HEAP\": lambda self: self.expression(exp.HeapProperty()),\n        \"ICEBERG\": lambda self: self.expression(exp.IcebergProperty()),\n        \"IMMUTABLE\": lambda self: self.expression(\n            exp.StabilityProperty(this=exp.Literal.string(\"IMMUTABLE\"))\n        ),\n        \"INHERITS\": lambda self: self.expression(\n            exp.InheritsProperty(expressions=self._parse_wrapped_csv(self._parse_table))\n        ),\n        \"INPUT\": lambda self: self.expression(exp.InputModelProperty(this=self._parse_schema())),\n        \"JOURNAL\": lambda self, **kwargs: self._parse_journal(**kwargs),\n        \"LANGUAGE\": lambda self: self._parse_property_assignment(exp.LanguageProperty),\n        \"LAYOUT\": lambda self: self._parse_dict_property(this=\"LAYOUT\"),\n        \"LIFETIME\": lambda self: self._parse_dict_range(this=\"LIFETIME\"),\n        \"LIKE\": lambda self: self._parse_create_like(),\n        \"LOCATION\": lambda self: self._parse_property_assignment(exp.LocationProperty),\n        \"LOCK\": lambda self: self._parse_locking(),\n        \"LOCKING\": lambda self: self._parse_locking(),\n        \"LOG\": lambda self, **kwargs: self._parse_log(**kwargs),\n        \"MATERIALIZED\": lambda self: self.expression(exp.MaterializedProperty()),\n        \"MERGEBLOCKRATIO\": lambda self, **kwargs: self._parse_mergeblockratio(**kwargs),\n        \"MODIFIES\": lambda self: self._parse_modifies_property(),\n        \"MULTISET\": lambda self: self.expression(exp.SetProperty(multi=True)),\n        \"NO\": lambda self: self._parse_no_property(),\n        \"ON\": lambda self: self._parse_on_property(),\n        \"ORDER BY\": lambda self: self._parse_order(skip_order_token=True),\n        \"OUTPUT\": lambda self: self.expression(exp.OutputModelProperty(this=self._parse_schema())),\n        \"PARTITION\": lambda self: self._parse_partitioned_of(),\n        \"PARTITION BY\": lambda self: self._parse_partitioned_by(),\n        \"PARTITIONED BY\": lambda self: self._parse_partitioned_by(),\n        \"PARTITIONED_BY\": lambda self: self._parse_partitioned_by(),\n        \"PRIMARY KEY\": lambda self: self._parse_primary_key(in_props=True),\n        \"RANGE\": lambda self: self._parse_dict_range(this=\"RANGE\"),\n        \"READS\": lambda self: self._parse_reads_property(),\n        \"REMOTE\": lambda self: self._parse_remote_with_connection(),\n        \"RETURNS\": lambda self: self._parse_returns(),\n        \"STRICT\": lambda self: self.expression(exp.StrictProperty()),\n        \"STREAMING\": lambda self: self.expression(exp.StreamingTableProperty()),\n        \"ROW\": lambda self: self._parse_row(),\n        \"ROW_FORMAT\": lambda self: self._parse_property_assignment(exp.RowFormatProperty),\n        \"SAMPLE\": lambda self: self.expression(\n            exp.SampleProperty(this=self._match_text_seq(\"BY\") and self._parse_bitwise())\n        ),\n        \"SECURE\": lambda self: self.expression(exp.SecureProperty()),\n        \"SECURITY\": lambda self: self._parse_sql_security(),\n        \"SQL SECURITY\": lambda self: self._parse_sql_security(),\n        \"SET\": lambda self: self.expression(exp.SetProperty(multi=False)),\n        \"SETTINGS\": lambda self: self._parse_settings_property(),\n        \"SHARING\": lambda self: self._parse_property_assignment(exp.SharingProperty),\n        \"SORTKEY\": lambda self: self._parse_sortkey(),\n        \"SOURCE\": lambda self: self._parse_dict_property(this=\"SOURCE\"),\n        \"STABLE\": lambda self: self.expression(\n            exp.StabilityProperty(this=exp.Literal.string(\"STABLE\"))\n        ),\n        \"STORED\": lambda self: self._parse_stored(),\n        \"SYSTEM_VERSIONING\": lambda self: self._parse_system_versioning_property(),\n        \"TBLPROPERTIES\": lambda self: self._parse_wrapped_properties(),\n        \"TEMP\": lambda self: self.expression(exp.TemporaryProperty()),\n        \"TEMPORARY\": lambda self: self.expression(exp.TemporaryProperty()),\n        \"TO\": lambda self: self._parse_to_table(),\n        \"TRANSIENT\": lambda self: self.expression(exp.TransientProperty()),\n        \"TRANSFORM\": lambda self: self.expression(\n            exp.TransformModelProperty(expressions=self._parse_wrapped_csv(self._parse_expression))\n        ),\n        \"TTL\": lambda self: self._parse_ttl(),\n        \"USING\": lambda self: self._parse_property_assignment(exp.FileFormatProperty),\n        \"UNLOGGED\": lambda self: self.expression(exp.UnloggedProperty()),\n        \"VOLATILE\": lambda self: self._parse_volatile_property(),\n        \"WITH\": lambda self: self._parse_with_property(),\n    }\n\n    CONSTRAINT_PARSERS: t.ClassVar = {\n        \"AUTOINCREMENT\": lambda self: self._parse_auto_increment(),\n        \"AUTO_INCREMENT\": lambda self: self._parse_auto_increment(),\n        \"CASESPECIFIC\": lambda self: self.expression(exp.CaseSpecificColumnConstraint(not_=False)),\n        \"CHARACTER SET\": lambda self: self.expression(\n            exp.CharacterSetColumnConstraint(this=self._parse_var_or_string())\n        ),\n        \"CHECK\": lambda self: self._parse_check_constraint(),\n        \"COLLATE\": lambda self: self.expression(\n            exp.CollateColumnConstraint(this=self._parse_identifier() or self._parse_column())\n        ),\n        \"COMMENT\": lambda self: self.expression(\n            exp.CommentColumnConstraint(this=self._parse_string())\n        ),\n        \"COMPRESS\": lambda self: self._parse_compress(),\n        \"CLUSTERED\": lambda self: self.expression(\n            exp.ClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))\n        ),\n        \"NONCLUSTERED\": lambda self: self.expression(\n            exp.NonClusteredColumnConstraint(this=self._parse_wrapped_csv(self._parse_ordered))\n        ),\n        \"DEFAULT\": lambda self: self.expression(\n            exp.DefaultColumnConstraint(this=self._parse_bitwise())\n        ),\n        \"ENCODE\": lambda self: self.expression(exp.EncodeColumnConstraint(this=self._parse_var())),\n        \"EPHEMERAL\": lambda self: self.expression(\n            exp.EphemeralColumnConstraint(this=self._parse_bitwise())\n        ),\n        \"EXCLUDE\": lambda self: self.expression(\n            exp.ExcludeColumnConstraint(this=self._parse_index_params())\n        ),\n        \"FOREIGN KEY\": lambda self: self._parse_foreign_key(),\n        \"FORMAT\": lambda self: self.expression(\n            exp.DateFormatColumnConstraint(this=self._parse_var_or_string())\n        ),\n        \"GENERATED\": lambda self: self._parse_generated_as_identity(),\n        \"IDENTITY\": lambda self: self._parse_auto_increment(),\n        \"INLINE\": lambda self: self._parse_inline(),\n        \"LIKE\": lambda self: self._parse_create_like(),\n        \"NOT\": lambda self: self._parse_not_constraint(),\n        \"NULL\": lambda self: self.expression(exp.NotNullColumnConstraint(allow_null=True)),\n        \"ON\": lambda self: (\n            (\n                self._match(TokenType.UPDATE)\n                and self.expression(exp.OnUpdateColumnConstraint(this=self._parse_function()))\n            )\n            or self.expression(exp.OnProperty(this=self._parse_id_var()))\n        ),\n        \"PATH\": lambda self: self.expression(exp.PathColumnConstraint(this=self._parse_string())),\n        \"PERIOD\": lambda self: self._parse_period_for_system_time(),\n        \"PRIMARY KEY\": lambda self: self._parse_primary_key(),\n        \"REFERENCES\": lambda self: self._parse_references(match=False),\n        \"TITLE\": lambda self: self.expression(\n            exp.TitleColumnConstraint(this=self._parse_var_or_string())\n        ),\n        \"TTL\": lambda self: self.expression(exp.MergeTreeTTL(expressions=[self._parse_bitwise()])),\n        \"UNIQUE\": lambda self: self._parse_unique(),\n        \"UPPERCASE\": lambda self: self.expression(exp.UppercaseColumnConstraint()),\n        \"WITH\": lambda self: self.expression(\n            exp.Properties(expressions=self._parse_wrapped_properties())\n        ),\n        \"BUCKET\": lambda self: self._parse_partitioned_by_bucket_or_truncate(),\n        \"TRUNCATE\": lambda self: self._parse_partitioned_by_bucket_or_truncate(),\n    }\n\n    def _parse_partitioned_by_bucket_or_truncate(self) -> t.Optional[exp.Expr]:\n        if not self._match(TokenType.L_PAREN, advance=False):\n            # Partitioning by bucket or truncate follows the syntax:\n            # PARTITION BY (BUCKET(..) | TRUNCATE(..))\n            # If we don't have parenthesis after each keyword, we should instead parse this as an identifier\n            self._retreat(self._index - 1)\n            return None\n\n        klass = (\n            exp.PartitionedByBucket\n            if self._prev.text.upper() == \"BUCKET\"\n            else exp.PartitionByTruncate\n        )\n\n        args = self._parse_wrapped_csv(lambda: self._parse_primary() or self._parse_column())\n        this, expression = seq_get(args, 0), seq_get(args, 1)\n\n        if isinstance(this, exp.Literal):\n            # Check for Iceberg partition transforms (bucket / truncate) and ensure their arguments are in the right order\n            #  - For Hive, it's `bucket(<num buckets>, <col name>)` or `truncate(<num_chars>, <col_name>)`\n            #  - For Trino, it's reversed - `bucket(<col name>, <num buckets>)` or `truncate(<col_name>, <num_chars>)`\n            # Both variants are canonicalized in the latter i.e `bucket(<col name>, <num buckets>)`\n            #\n            # Hive ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning\n            # Trino ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties\n            this, expression = expression, this\n\n        return self.expression(klass(this=this, expression=expression))\n\n    ALTER_PARSERS: t.ClassVar = {\n        \"ADD\": lambda self: self._parse_alter_table_add(),\n        \"AS\": lambda self: self._parse_select(),\n        \"ALTER\": lambda self: self._parse_alter_table_alter(),\n        \"CLUSTER BY\": lambda self: self._parse_cluster(wrapped=True),\n        \"DELETE\": lambda self: self.expression(exp.Delete(where=self._parse_where())),\n        \"DROP\": lambda self: self._parse_alter_table_drop(),\n        \"RENAME\": lambda self: self._parse_alter_table_rename(),\n        \"SET\": lambda self: self._parse_alter_table_set(),\n        \"SWAP\": lambda self: self.expression(\n            exp.SwapTable(this=self._match(TokenType.WITH) and self._parse_table(schema=True))\n        ),\n    }\n\n    ALTER_ALTER_PARSERS: t.ClassVar = {\n        \"DISTKEY\": lambda self: self._parse_alter_diststyle(),\n        \"DISTSTYLE\": lambda self: self._parse_alter_diststyle(),\n        \"SORTKEY\": lambda self: self._parse_alter_sortkey(),\n        \"COMPOUND\": lambda self: self._parse_alter_sortkey(compound=True),\n    }\n\n    SCHEMA_UNNAMED_CONSTRAINTS: t.ClassVar = {\n        \"CHECK\",\n        \"EXCLUDE\",\n        \"FOREIGN KEY\",\n        \"LIKE\",\n        \"PERIOD\",\n        \"PRIMARY KEY\",\n        \"UNIQUE\",\n        \"BUCKET\",\n        \"TRUNCATE\",\n    }\n\n    NO_PAREN_FUNCTION_PARSERS: t.ClassVar = {\n        \"ANY\": lambda self: self.expression(exp.Any(this=self._parse_bitwise())),\n        \"CASE\": lambda self: self._parse_case(),\n        \"CONNECT_BY_ROOT\": lambda self: self.expression(\n            exp.ConnectByRoot(this=self._parse_column())\n        ),\n        \"IF\": lambda self: self._parse_if(),\n    }\n\n    INVALID_FUNC_NAME_TOKENS: t.ClassVar = {\n        TokenType.IDENTIFIER,\n        TokenType.STRING,\n    }\n\n    FUNCTIONS_WITH_ALIASED_ARGS: t.ClassVar = {\"STRUCT\"}\n\n    KEY_VALUE_DEFINITIONS: t.ClassVar = (exp.Alias, exp.EQ, exp.PropertyEQ, exp.Slice)\n\n    FUNCTION_PARSERS: t.ClassVar[t.Dict[str, t.Callable]] = {\n        **{\n            name: lambda self: self._parse_max_min_by(exp.ArgMax) for name in exp.ArgMax.sql_names()\n        },\n        **{\n            name: lambda self: self._parse_max_min_by(exp.ArgMin) for name in exp.ArgMin.sql_names()\n        },\n        \"CAST\": lambda self: self._parse_cast(self.STRICT_CAST),\n        \"CEIL\": lambda self: self._parse_ceil_floor(exp.Ceil),\n        \"CONVERT\": lambda self: self._parse_convert(self.STRICT_CAST),\n        \"CHAR\": lambda self: self._parse_char(),\n        \"CHR\": lambda self: self._parse_char(),\n        \"DECODE\": lambda self: self._parse_decode(),\n        \"EXTRACT\": lambda self: self._parse_extract(),\n        \"FLOOR\": lambda self: self._parse_ceil_floor(exp.Floor),\n        \"GAP_FILL\": lambda self: self._parse_gap_fill(),\n        \"INITCAP\": lambda self: self._parse_initcap(),\n        \"JSON_OBJECT\": lambda self: self._parse_json_object(),\n        \"JSON_OBJECTAGG\": lambda self: self._parse_json_object(agg=True),\n        \"JSON_TABLE\": lambda self: self._parse_json_table(),\n        \"MATCH\": lambda self: self._parse_match_against(),\n        \"NORMALIZE\": lambda self: self._parse_normalize(),\n        \"OPENJSON\": lambda self: self._parse_open_json(),\n        \"OVERLAY\": lambda self: self._parse_overlay(),\n        \"POSITION\": lambda self: self._parse_position(),\n        \"SAFE_CAST\": lambda self: self._parse_cast(False, safe=True),\n        \"STRING_AGG\": lambda self: self._parse_string_agg(),\n        \"SUBSTRING\": lambda self: self._parse_substring(),\n        \"TRIM\": lambda self: self._parse_trim(),\n        \"TRY_CAST\": lambda self: self._parse_cast(False, safe=True),\n        \"TRY_CONVERT\": lambda self: self._parse_convert(False, safe=True),\n        \"XMLELEMENT\": lambda self: self._parse_xml_element(),\n        \"XMLTABLE\": lambda self: self._parse_xml_table(),\n    }\n\n    QUERY_MODIFIER_PARSERS: t.ClassVar = {\n        TokenType.MATCH_RECOGNIZE: lambda self: (\"match\", self._parse_match_recognize()),\n        TokenType.PREWHERE: lambda self: (\"prewhere\", self._parse_prewhere()),\n        TokenType.WHERE: lambda self: (\"where\", self._parse_where()),\n        TokenType.GROUP_BY: lambda self: (\"group\", self._parse_group()),\n        TokenType.HAVING: lambda self: (\"having\", self._parse_having()),\n        TokenType.QUALIFY: lambda self: (\"qualify\", self._parse_qualify()),\n        TokenType.WINDOW: lambda self: (\"windows\", self._parse_window_clause()),\n        TokenType.ORDER_BY: lambda self: (\"order\", self._parse_order()),\n        TokenType.LIMIT: lambda self: (\"limit\", self._parse_limit()),\n        TokenType.FETCH: lambda self: (\"limit\", self._parse_limit()),\n        TokenType.OFFSET: lambda self: (\"offset\", self._parse_offset()),\n        TokenType.FOR: lambda self: (\"locks\", self._parse_locks()),\n        TokenType.LOCK: lambda self: (\"locks\", self._parse_locks()),\n        TokenType.TABLE_SAMPLE: lambda self: (\"sample\", self._parse_table_sample(as_modifier=True)),\n        TokenType.USING: lambda self: (\"sample\", self._parse_table_sample(as_modifier=True)),\n        TokenType.CLUSTER_BY: lambda self: (\n            \"cluster\",\n            self._parse_sort(exp.Cluster, TokenType.CLUSTER_BY),\n        ),\n        TokenType.DISTRIBUTE_BY: lambda self: (\n            \"distribute\",\n            self._parse_sort(exp.Distribute, TokenType.DISTRIBUTE_BY),\n        ),\n        TokenType.SORT_BY: lambda self: (\"sort\", self._parse_sort(exp.Sort, TokenType.SORT_BY)),\n        TokenType.CONNECT_BY: lambda self: (\"connect\", self._parse_connect(skip_start_token=True)),\n        TokenType.START_WITH: lambda self: (\"connect\", self._parse_connect()),\n    }\n    QUERY_MODIFIER_TOKENS: t.ClassVar = set(QUERY_MODIFIER_PARSERS)\n\n    SET_PARSERS: t.ClassVar = {\n        \"GLOBAL\": lambda self: self._parse_set_item_assignment(\"GLOBAL\"),\n        \"LOCAL\": lambda self: self._parse_set_item_assignment(\"LOCAL\"),\n        \"SESSION\": lambda self: self._parse_set_item_assignment(\"SESSION\"),\n        \"TRANSACTION\": lambda self: self._parse_set_transaction(),\n    }\n\n    SHOW_PARSERS: t.ClassVar[t.Dict[str, t.Callable]] = {}\n\n    TYPE_LITERAL_PARSERS: t.ClassVar = {\n        exp.DType.JSON: lambda self, this, _: self.expression(exp.ParseJSON(this=this)),\n    }\n\n    TYPE_CONVERTERS: t.ClassVar[t.Dict[exp.DType, t.Callable[[exp.DataType], exp.DataType]]] = {}\n\n    DDL_SELECT_TOKENS: t.ClassVar = {TokenType.SELECT, TokenType.WITH, TokenType.L_PAREN}\n\n    PRE_VOLATILE_TOKENS: t.ClassVar = {TokenType.CREATE, TokenType.REPLACE, TokenType.UNIQUE}\n\n    TRANSACTION_KIND: t.ClassVar = {\"DEFERRED\", \"IMMEDIATE\", \"EXCLUSIVE\"}\n    TRANSACTION_CHARACTERISTICS: t.ClassVar[OPTIONS_TYPE] = {\n        \"ISOLATION\": (\n            (\"LEVEL\", \"REPEATABLE\", \"READ\"),\n            (\"LEVEL\", \"READ\", \"COMMITTED\"),\n            (\"LEVEL\", \"READ\", \"UNCOMITTED\"),\n            (\"LEVEL\", \"SERIALIZABLE\"),\n        ),\n        \"READ\": (\"WRITE\", \"ONLY\"),\n    }\n\n    CONFLICT_ACTIONS: t.ClassVar[OPTIONS_TYPE] = {\n        **dict.fromkeys((\"ABORT\", \"FAIL\", \"IGNORE\", \"REPLACE\", \"ROLLBACK\", \"UPDATE\"), tuple()),\n        \"DO\": (\"NOTHING\", \"UPDATE\"),\n    }\n\n    TRIGGER_TIMING: t.ClassVar[OPTIONS_TYPE] = {\n        \"INSTEAD\": ((\"OF\",),),\n        \"BEFORE\": tuple(),\n        \"AFTER\": tuple(),\n    }\n\n    TRIGGER_DEFERRABLE: t.ClassVar[OPTIONS_TYPE] = {\n        \"NOT\": ((\"DEFERRABLE\",),),\n        \"DEFERRABLE\": tuple(),\n    }\n\n    CREATE_SEQUENCE: t.ClassVar[OPTIONS_TYPE] = {\n        \"SCALE\": (\"EXTEND\", \"NOEXTEND\"),\n        \"SHARD\": (\"EXTEND\", \"NOEXTEND\"),\n        \"NO\": (\"CYCLE\", \"CACHE\", \"MAXVALUE\", \"MINVALUE\"),\n        **dict.fromkeys(\n            (\n                \"SESSION\",\n                \"GLOBAL\",\n                \"KEEP\",\n                \"NOKEEP\",\n                \"ORDER\",\n                \"NOORDER\",\n                \"NOCACHE\",\n                \"CYCLE\",\n                \"NOCYCLE\",\n                \"NOMINVALUE\",\n                \"NOMAXVALUE\",\n                \"NOSCALE\",\n                \"NOSHARD\",\n            ),\n            tuple(),\n        ),\n    }\n\n    ISOLATED_LOADING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {\"FOR\": (\"ALL\", \"INSERT\", \"NONE\")}\n\n    USABLES: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(\n        (\"ROLE\", \"WAREHOUSE\", \"DATABASE\", \"SCHEMA\", \"CATALOG\"), tuple()\n    )\n\n    CAST_ACTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys((\"RENAME\", \"ADD\"), (\"FIELDS\",))\n\n    SCHEMA_BINDING_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {\n        \"TYPE\": (\"EVOLUTION\",),\n        **dict.fromkeys((\"BINDING\", \"COMPENSATION\", \"EVOLUTION\"), tuple()),\n    }\n\n    PROCEDURE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {}\n\n    EXECUTE_AS_OPTIONS: t.ClassVar[OPTIONS_TYPE] = dict.fromkeys(\n        (\"CALLER\", \"SELF\", \"OWNER\"), tuple()\n    )\n\n    KEY_CONSTRAINT_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {\n        \"NOT\": (\"ENFORCED\",),\n        \"MATCH\": (\n            \"FULL\",\n            \"PARTIAL\",\n            \"SIMPLE\",\n        ),\n        \"INITIALLY\": (\"DEFERRED\", \"IMMEDIATE\"),\n        \"USING\": (\n            \"BTREE\",\n            \"HASH\",\n        ),\n        **dict.fromkeys((\"DEFERRABLE\", \"NORELY\", \"RELY\"), tuple()),\n    }\n\n    WINDOW_EXCLUDE_OPTIONS: t.ClassVar[OPTIONS_TYPE] = {\n        \"NO\": (\"OTHERS\",),\n        \"CURRENT\": (\"ROW\",),\n        **dict.fromkeys((\"GROUP\", \"TIES\"), tuple()),\n    }\n\n    INSERT_ALTERNATIVES: t.ClassVar = {\"ABORT\", \"FAIL\", \"IGNORE\", \"REPLACE\", \"ROLLBACK\"}\n\n    CLONE_KEYWORDS: t.ClassVar = {\"CLONE\", \"COPY\"}\n    HISTORICAL_DATA_PREFIX: t.ClassVar = {\"AT\", \"BEFORE\", \"END\"}\n    HISTORICAL_DATA_KIND: t.ClassVar = {\"OFFSET\", \"STATEMENT\", \"STREAM\", \"TIMESTAMP\", \"VERSION\"}\n\n    OPCLASS_FOLLOW_KEYWORDS: t.ClassVar = {\"ASC\", \"DESC\", \"NULLS\", \"WITH\"}\n\n    OPTYPE_FOLLOW_TOKENS: t.ClassVar = {TokenType.COMMA, TokenType.R_PAREN}\n\n    TABLE_INDEX_HINT_TOKENS: t.ClassVar = {TokenType.FORCE, TokenType.IGNORE, TokenType.USE}\n\n    VIEW_ATTRIBUTES: t.ClassVar = {\"ENCRYPTION\", \"SCHEMABINDING\", \"VIEW_METADATA\"}\n\n    WINDOW_ALIAS_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.RANGE, TokenType.ROWS}\n    WINDOW_BEFORE_PAREN_TOKENS: t.ClassVar = {TokenType.OVER}\n    WINDOW_SIDES: t.ClassVar = {\"FOLLOWING\", \"PRECEDING\"}\n\n    JSON_KEY_VALUE_SEPARATOR_TOKENS: t.ClassVar = {TokenType.COLON, TokenType.COMMA, TokenType.IS}\n\n    FETCH_TOKENS: t.ClassVar = ID_VAR_TOKENS - {TokenType.ROW, TokenType.ROWS, TokenType.PERCENT}\n\n    ADD_CONSTRAINT_TOKENS: t.ClassVar = {\n        TokenType.CONSTRAINT,\n        TokenType.FOREIGN_KEY,\n        TokenType.INDEX,\n        TokenType.KEY,\n        TokenType.PRIMARY_KEY,\n        TokenType.UNIQUE,\n    }\n\n    DISTINCT_TOKENS: t.ClassVar = {TokenType.DISTINCT}\n\n    UNNEST_OFFSET_ALIAS_TOKENS: t.ClassVar = TABLE_ALIAS_TOKENS - SET_OPERATIONS\n\n    SELECT_START_TOKENS: t.ClassVar = {TokenType.L_PAREN, TokenType.WITH, TokenType.SELECT}\n\n    COPY_INTO_VARLEN_OPTIONS: t.ClassVar = {\n        \"FILE_FORMAT\",\n        \"COPY_OPTIONS\",\n        \"FORMAT_OPTIONS\",\n        \"CREDENTIAL\",\n    }\n\n    IS_JSON_PREDICATE_KIND: t.ClassVar = {\"VALUE\", \"SCALAR\", \"ARRAY\", \"OBJECT\"}\n\n    ODBC_DATETIME_LITERALS: t.ClassVar[t.Dict[str, t.Type[exp.Expr]]] = {}\n\n    ON_CONDITION_TOKENS: t.ClassVar = {\"ERROR\", \"NULL\", \"TRUE\", \"FALSE\", \"EMPTY\"}\n\n    PRIVILEGE_FOLLOW_TOKENS: t.ClassVar = {TokenType.ON, TokenType.COMMA, TokenType.L_PAREN}\n\n    # The style options for the DESCRIBE statement\n    DESCRIBE_STYLES: t.ClassVar = {\"ANALYZE\", \"EXTENDED\", \"FORMATTED\", \"HISTORY\"}\n\n    SET_ASSIGNMENT_DELIMITERS: t.ClassVar = {\"=\", \":=\", \"TO\"}\n\n    # The style options for the ANALYZE statement\n    ANALYZE_STYLES: t.ClassVar = {\n        \"BUFFER_USAGE_LIMIT\",\n        \"FULL\",\n        \"LOCAL\",\n        \"NO_WRITE_TO_BINLOG\",\n        \"SAMPLE\",\n        \"SKIP_LOCKED\",\n        \"VERBOSE\",\n    }\n\n    ANALYZE_EXPRESSION_PARSERS: t.ClassVar = {\n        \"ALL\": lambda self: self._parse_analyze_columns(),\n        \"COMPUTE\": lambda self: self._parse_analyze_statistics(),\n        \"DELETE\": lambda self: self._parse_analyze_delete(),\n        \"DROP\": lambda self: self._parse_analyze_histogram(),\n        \"ESTIMATE\": lambda self: self._parse_analyze_statistics(),\n        \"LIST\": lambda self: self._parse_analyze_list(),\n        \"PREDICATE\": lambda self: self._parse_analyze_columns(),\n        \"UPDATE\": lambda self: self._parse_analyze_histogram(),\n        \"VALIDATE\": lambda self: self._parse_analyze_validate(),\n    }\n\n    PARTITION_KEYWORDS: t.ClassVar = {\"PARTITION\", \"SUBPARTITION\"}\n\n    AMBIGUOUS_ALIAS_TOKENS: t.ClassVar = (TokenType.LIMIT, TokenType.OFFSET)\n\n    OPERATION_MODIFIERS: t.ClassVar[t.Set[str]] = set()\n\n    RECURSIVE_CTE_SEARCH_KIND: t.ClassVar = {\"BREADTH\", \"DEPTH\", \"CYCLE\"}\n\n    SECURITY_PROPERTY_KEYWORDS: t.ClassVar = {\"DEFINER\", \"INVOKER\", \"NONE\"}\n\n    MODIFIABLES: t.ClassVar = (exp.Query, exp.Table, exp.TableFromRows, exp.Values)\n\n    STRICT_CAST: t.ClassVar = True\n\n    PREFIXED_PIVOT_COLUMNS: t.ClassVar = False\n    IDENTIFY_PIVOT_STRINGS: t.ClassVar = False\n\n    LOG_DEFAULTS_TO_LN: t.ClassVar = False\n\n    # Whether the table sample clause expects CSV syntax\n    TABLESAMPLE_CSV: t.ClassVar = False\n\n    # The default method used for table sampling\n    DEFAULT_SAMPLING_METHOD: t.ClassVar[t.Optional[str]] = None\n\n    # Whether the SET command needs a delimiter (e.g. \"=\") for assignments\n    SET_REQUIRES_ASSIGNMENT_DELIMITER: t.ClassVar = True\n\n    # Whether the TRIM function expects the characters to trim as its first argument\n    TRIM_PATTERN_FIRST: t.ClassVar = False\n\n    # Whether string aliases are supported `SELECT COUNT(*) 'count'`\n    STRING_ALIASES: t.ClassVar = False\n\n    # Whether query modifiers such as LIMIT are attached to the UNION node (vs its right operand)\n    MODIFIERS_ATTACHED_TO_SET_OP: t.ClassVar = True\n    SET_OP_MODIFIERS: t.ClassVar = {\"order\", \"limit\", \"offset\"}\n\n    # Whether to parse IF statements that aren't followed by a left parenthesis as commands\n    NO_PAREN_IF_COMMANDS: t.ClassVar = True\n\n    # Whether the -> and ->> operators expect documents of type JSON (e.g. Postgres)\n    JSON_ARROWS_REQUIRE_JSON_TYPE: t.ClassVar = False\n\n    # Whether the `:` operator is used to extract a value from a VARIANT column\n    COLON_IS_VARIANT_EXTRACT: t.ClassVar = False\n\n    # Whether or not a VALUES keyword needs to be followed by '(' to form a VALUES clause.\n    # If this is True and '(' is not found, the keyword will be treated as an identifier\n    VALUES_FOLLOWED_BY_PAREN: t.ClassVar = True\n\n    # Whether implicit unnesting is supported, e.g. SELECT 1 FROM y.z AS z, z.a (Redshift)\n    SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = False\n\n    # Whether or not interval spans are supported, INTERVAL 1 YEAR TO MONTHS\n    INTERVAL_SPANS: t.ClassVar = True\n\n    # Whether a PARTITION clause can follow a table reference\n    SUPPORTS_PARTITION_SELECTION: t.ClassVar = False\n\n    # Whether the `name AS expr` schema/column constraint requires parentheses around `expr`\n    WRAPPED_TRANSFORM_COLUMN_CONSTRAINT: t.ClassVar = True\n\n    # Whether the 'AS' keyword is optional in the CTE definition syntax\n    OPTIONAL_ALIAS_TOKEN_CTE: t.ClassVar = True\n\n    # Whether renaming a column with an ALTER statement requires the presence of the COLUMN keyword\n    ALTER_RENAME_REQUIRES_COLUMN: t.ClassVar = True\n\n    # Whether Alter statements are allowed to contain Partition specifications\n    ALTER_TABLE_PARTITIONS: t.ClassVar = False\n\n    # Whether all join types have the same precedence, i.e., they \"naturally\" produce a left-deep tree.\n    # In standard SQL, joins that use the JOIN keyword take higher precedence than comma-joins. That is\n    # to say, JOIN operators happen before comma operators. This is not the case in some dialects, such\n    # as BigQuery, where all joins have the same precedence.\n    JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = False\n\n    # Whether TIMESTAMP <literal> can produce a zone-aware timestamp\n    ZONE_AWARE_TIMESTAMP_CONSTRUCTOR: t.ClassVar = False\n\n    # Whether map literals support arbitrary expressions as keys.\n    # When True, allows complex keys like arrays or literals: {[1, 2]: 3}, {1: 2} (e.g. DuckDB).\n    # When False, keys are typically restricted to identifiers.\n    MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS: t.ClassVar = False\n\n    # Whether JSON_EXTRACT requires a JSON expression as the first argument, e.g this\n    # is true for Snowflake but not for BigQuery which can also process strings\n    JSON_EXTRACT_REQUIRES_JSON_EXPRESSION: t.ClassVar = False\n\n    # Dialects like Databricks support JOINS without join criteria\n    # Adding an ON TRUE, makes transpilation semantically correct for other dialects\n    ADD_JOIN_ON_TRUE: t.ClassVar = False\n\n    # Whether INTERVAL spans with literal format '\\d+ hh:[mm:[ss[.ff]]]'\n    # can omit the span unit `DAY TO MINUTE` or `DAY TO SECOND`\n    SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT: t.ClassVar = False\n\n    SHOW_TRIE: t.ClassVar[t.Dict] = new_trie(key.split(\" \") for key in SHOW_PARSERS)\n    SET_TRIE: t.ClassVar[t.Dict] = new_trie(key.split(\" \") for key in SET_PARSERS)\n\n    def __init__(\n        self,\n        error_level: t.Optional[ErrorLevel] = None,\n        error_message_context: int = 100,\n        max_errors: int = 3,\n        dialect: DialectType = None,\n    ):\n        self.error_level: ErrorLevel = error_level or ErrorLevel.IMMEDIATE\n        self.error_message_context: int = error_message_context\n        self.max_errors: int = max_errors\n        self.dialect: t.Any = _resolve_dialect(dialect)\n        self.sql: str = \"\"\n        self.errors: t.List[ParseError] = []\n        self._tokens: t.List[Token] = []\n        self._tokens_size: i64 = 0\n        self._index: i64 = 0\n        self._curr: Token = SENTINEL_NONE\n        self._next: Token = SENTINEL_NONE\n        self._prev: Token = SENTINEL_NONE\n        self._prev_comments: t.List[str] = []\n        self._pipe_cte_counter: int = 0\n        self._chunks: t.List[t.List[Token]] = []\n        self._chunk_index: i64 = 0\n\n    def reset(self) -> None:\n        self.sql = \"\"\n        self.errors = []\n        self._tokens = []\n        self._tokens_size = 0\n        self._index = 0\n        self._curr = SENTINEL_NONE\n        self._next = SENTINEL_NONE\n        self._prev = SENTINEL_NONE\n        self._prev_comments = []\n        self._pipe_cte_counter = 0\n        self._chunks = []\n        self._chunk_index = 0\n\n    def _advance(self, times: i64 = 1) -> None:\n        index = self._index + times\n        self._index = index\n        tokens = self._tokens\n        size = self._tokens_size\n        self._curr = tokens[index] if index < size else SENTINEL_NONE\n        self._next = tokens[index + 1] if index + 1 < size else SENTINEL_NONE\n\n        if index > 0:\n            prev = tokens[index - 1]\n            self._prev = prev\n            self._prev_comments = prev.comments\n        else:\n            self._prev = SENTINEL_NONE\n            self._prev_comments = []\n\n    def _advance_chunk(self) -> None:\n        self._index = -1\n        self._tokens = self._chunks[self._chunk_index]\n        self._tokens_size = i64(len(self._tokens))\n        self._chunk_index += 1\n        self._advance()\n\n    def _retreat(self, index: i64) -> None:\n        if index != self._index:\n            self._advance(index - self._index)\n\n    def _add_comments(self, expression: t.Optional[exp.Expr]) -> None:\n        if expression and self._prev_comments:\n            expression.add_comments(self._prev_comments)\n            self._prev_comments = []\n\n    def _match(\n        self, token_type: TokenType, advance: bool = True, expression: t.Optional[exp.Expr] = None\n    ) -> bool:\n        if self._curr.token_type == token_type:\n            if advance:\n                self._advance()\n            self._add_comments(expression)\n            return True\n        return False\n\n    def _match_set(self, types: t.Collection[TokenType], advance: bool = True) -> bool:\n        if self._curr.token_type in types:\n            if advance:\n                self._advance()\n            return True\n        return False\n\n    def _match_pair(\n        self, token_type_a: TokenType, token_type_b: TokenType, advance: bool = True\n    ) -> bool:\n        if self._curr.token_type == token_type_a and self._next.token_type == token_type_b:\n            if advance:\n                self._advance(2)\n            return True\n        return False\n\n    def _match_texts(self, texts: t.Collection[str], advance: bool = True) -> bool:\n        if self._curr.token_type != TokenType.STRING and self._curr.text.upper() in texts:\n            if advance:\n                self._advance()\n            return True\n        return False\n\n    def _match_text_seq(self, *texts: str, advance: bool = True) -> bool:\n        index = self._index\n        string_type = TokenType.STRING\n        for text in texts:\n            if self._curr.token_type != string_type and self._curr.text.upper() == text:\n                self._advance()\n            else:\n                self._retreat(index)\n                return False\n\n        if not advance:\n            self._retreat(index)\n\n        return True\n\n    def _is_connected(self) -> bool:\n        prev = self._prev\n        curr = self._curr\n        return bool(prev and curr and prev.end + 1 == curr.start)\n\n    def _find_sql(self, start: Token, end: Token) -> str:\n        return self.sql[start.start : end.end + 1]\n\n    def raise_error(self, message: str, token: Token = SENTINEL_NONE) -> None:\n        token = token or self._curr or self._prev or Token.string(\"\")\n        formatted_sql, start_context, highlight, end_context = highlight_sql(\n            sql=self.sql,\n            positions=[(token.start, token.end)],\n            context_length=self.error_message_context,\n        )\n        formatted_message = f\"{message}. Line {token.line}, Col: {token.col}.\\n  {formatted_sql}\"\n\n        error = ParseError.new(\n            formatted_message,\n            description=message,\n            line=token.line,\n            col=token.col,\n            start_context=start_context,\n            highlight=highlight,\n            end_context=end_context,\n        )\n\n        if self.error_level == ErrorLevel.IMMEDIATE:\n            raise error\n\n        self.errors.append(error)\n\n    def validate_expression(self, expression: E, args: t.Optional[t.List] = None) -> E:\n        if self.error_level != ErrorLevel.IGNORE:\n            for error_message in expression.error_messages(args):\n                self.raise_error(error_message)\n        return expression\n\n    def _try_parse(self, parse_method: t.Callable[[], T], retreat: bool = False) -> t.Optional[T]:\n        index = self._index\n        error_level = self.error_level\n        this: t.Optional[T] = None\n\n        self.error_level = ErrorLevel.IMMEDIATE\n        try:\n            this = parse_method()\n        except ParseError:\n            this = None\n        finally:\n            if not this or retreat:\n                self._retreat(index)\n            self.error_level = error_level\n\n        return this\n\n    def parse(self, raw_tokens: t.List[Token], sql: str) -> t.List[t.Optional[exp.Expr]]:\n        \"\"\"\n        Parses a list of tokens and returns a list of syntax trees, one tree\n        per parsed SQL statement.\n\n        Args:\n            raw_tokens: The list of tokens.\n            sql: The original SQL string.\n\n        Returns:\n            The list of the produced syntax trees.\n        \"\"\"\n        return self._parse(\n            parse_method=self.__class__._parse_statement, raw_tokens=raw_tokens, sql=sql\n        )\n\n    def parse_into(\n        self,\n        expression_types: exp.IntoType,\n        raw_tokens: t.List[Token],\n        sql: t.Optional[str] = None,\n    ) -> t.List[t.Optional[exp.Expr]]:\n        \"\"\"\n        Parses a list of tokens into a given Expr type. If a collection of Expr\n        types is given instead, this method will try to parse the token list into each one\n        of them, stopping at the first for which the parsing succeeds.\n\n        Args:\n            expression_types: The expression type(s) to try and parse the token list into.\n            raw_tokens: The list of tokens.\n            sql: The original SQL string, used to produce helpful debug messages.\n\n        Returns:\n            The target Expr.\n        \"\"\"\n        errors = []\n        for expression_type in ensure_list(expression_types):\n            parser = self.EXPRESSION_PARSERS.get(t.cast(t.Type[exp.Expr], expression_type))\n            if not parser:\n                raise TypeError(f\"No parser registered for {expression_type}\")\n\n            try:\n                return self._parse(parser, raw_tokens, sql)\n            except ParseError as e:\n                e.errors[0][\"into_expression\"] = expression_type\n                errors.append(e)\n\n        raise ParseError(\n            f\"Failed to parse '{sql or raw_tokens}' into {expression_types}\",\n            errors=merge_errors(errors),\n        ) from errors[-1]\n\n    def check_errors(self) -> None:\n        \"\"\"Logs or raises any found errors, depending on the chosen error level setting.\"\"\"\n        if self.error_level == ErrorLevel.WARN:\n            for error in self.errors:\n                logger.error(str(error))\n        elif self.error_level == ErrorLevel.RAISE and self.errors:\n            raise ParseError(\n                concat_messages(self.errors, self.max_errors),\n                errors=merge_errors(self.errors),\n            )\n\n    def expression(\n        self,\n        instance: E,\n        token: t.Optional[Token] = None,\n        comments: t.Optional[t.List[str]] = None,\n    ) -> E:\n        if token:\n            instance.update_positions(token)\n        instance.add_comments(comments) if comments else self._add_comments(instance)\n        if not instance.is_primitive:\n            instance = self.validate_expression(instance)\n        return instance\n\n    def _parse_batch_statements(\n        self,\n        parse_method: t.Callable[[Parser], t.Optional[exp.Expr]],\n        sep_first_statement: bool = True,\n    ) -> t.List[t.Optional[exp.Expr]]:\n        expressions = []\n\n        # Chunkification binds if/while statements with the first statement of the body\n        if sep_first_statement:\n            self._match(TokenType.BEGIN)\n            expressions.append(parse_method(self))\n\n        chunks_length = len(self._chunks)\n        while self._chunk_index < chunks_length:\n            self._advance_chunk()\n\n            if self._match(TokenType.ELSE, advance=False):\n                return expressions\n\n            if not self._next and self._match(TokenType.END):\n                expressions.append(exp.EndStatement())\n                continue\n\n            expressions.append(parse_method(self))\n\n            if self._index < self._tokens_size:\n                self.raise_error(\"Invalid expression / Unexpected token\")\n\n            self.check_errors()\n\n        return expressions\n\n    def _parse(\n        self,\n        parse_method: t.Callable[[Parser], t.Optional[exp.Expr]],\n        raw_tokens: t.List[Token],\n        sql: t.Optional[str] = None,\n    ) -> t.List[t.Optional[exp.Expr]]:\n        self.reset()\n        self.sql = sql or \"\"\n\n        total = len(raw_tokens)\n        chunks: t.List[t.List[Token]] = [[]]\n\n        for i, token in enumerate(raw_tokens):\n            if token.token_type == TokenType.SEMICOLON:\n                if token.comments:\n                    chunks.append([token])\n\n                if i < total - 1:\n                    chunks.append([])\n            else:\n                chunks[-1].append(token)\n\n        self._chunks = chunks\n\n        return self._parse_batch_statements(parse_method=parse_method, sep_first_statement=False)\n\n    def _warn_unsupported(self) -> None:\n        if self._tokens_size <= 1:\n            return\n\n        # We use _find_sql because self.sql may comprise multiple chunks, and we're only\n        # interested in emitting a warning for the one being currently processed.\n        sql = self._find_sql(self._tokens[0], self._tokens[-1])[: self.error_message_context]\n\n        logger.warning(\n            f\"'{sql}' contains unsupported syntax. Falling back to parsing as a 'Command'.\"\n        )\n\n    def _parse_command(self) -> exp.Command:\n        self._warn_unsupported()\n        comments = self._prev_comments\n        return self.expression(\n            exp.Command(this=self._prev.text.upper(), expression=self._parse_string()),\n            comments=comments,\n        )\n\n    def _parse_comment(self, allow_exists: bool = True) -> exp.Expr:\n        start = self._prev\n        exists = self._parse_exists() if allow_exists else None\n\n        self._match(TokenType.ON)\n\n        materialized = self._match_text_seq(\"MATERIALIZED\")\n        kind = self._match_set(self.CREATABLES) and self._prev\n        if not kind:\n            return self._parse_as_command(start)\n\n        if kind.token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):\n            this = self._parse_user_defined_function(kind=kind.token_type)\n        elif kind.token_type == TokenType.TABLE:\n            this = self._parse_table(alias_tokens=self.COMMENT_TABLE_ALIAS_TOKENS)\n        elif kind.token_type == TokenType.COLUMN:\n            this = self._parse_column()\n        else:\n            this = self._parse_id_var()\n\n        self._match(TokenType.IS)\n\n        return self.expression(\n            exp.Comment(\n                this=this,\n                kind=kind.text,\n                expression=self._parse_string(),\n                exists=exists,\n                materialized=materialized,\n            )\n        )\n\n    def _parse_to_table(\n        self,\n    ) -> exp.ToTableProperty:\n        table = self._parse_table_parts(schema=True)\n        return self.expression(exp.ToTableProperty(this=table))\n\n    # https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#mergetree-table-ttl\n    def _parse_ttl(self) -> exp.Expr:\n        def _parse_ttl_action() -> t.Optional[exp.Expr]:\n            this = self._parse_bitwise()\n\n            if self._match_text_seq(\"DELETE\"):\n                return self.expression(exp.MergeTreeTTLAction(this=this, delete=True))\n            if self._match_text_seq(\"RECOMPRESS\"):\n                return self.expression(\n                    exp.MergeTreeTTLAction(this=this, recompress=self._parse_bitwise())\n                )\n            if self._match_text_seq(\"TO\", \"DISK\"):\n                return self.expression(\n                    exp.MergeTreeTTLAction(this=this, to_disk=self._parse_string())\n                )\n            if self._match_text_seq(\"TO\", \"VOLUME\"):\n                return self.expression(\n                    exp.MergeTreeTTLAction(this=this, to_volume=self._parse_string())\n                )\n\n            return this\n\n        expressions = self._parse_csv(_parse_ttl_action)\n        where = self._parse_where()\n        group = self._parse_group()\n\n        aggregates = None\n        if group and self._match(TokenType.SET):\n            aggregates = self._parse_csv(self._parse_set_item)\n\n        return self.expression(\n            exp.MergeTreeTTL(\n                expressions=expressions, where=where, group=group, aggregates=aggregates\n            )\n        )\n\n    def _parse_condition(self) -> t.Optional[exp.Expr]:\n        return self._parse_wrapped(parse_method=self._parse_expression, optional=True)\n\n    def _parse_block(self) -> exp.Block:\n        return self.expression(\n            exp.Block(\n                expressions=self._parse_batch_statements(\n                    parse_method=lambda self: self._parse_statement()\n                )\n            )\n        )\n\n    def _parse_whileblock(self) -> exp.WhileBlock:\n        return self.expression(\n            exp.WhileBlock(this=self._parse_condition(), body=self._parse_block())\n        )\n\n    def _parse_statement(self) -> t.Optional[exp.Expr]:\n        if not self._curr:\n            return None\n\n        if self._match_set(self.STATEMENT_PARSERS):\n            comments = self._prev_comments\n            stmt = self.STATEMENT_PARSERS[self._prev.token_type](self)\n            stmt.add_comments(comments, prepend=True)\n            return stmt\n\n        if self._match_set(self.dialect.tokenizer_class.COMMANDS):\n            return self._parse_command()\n\n        if self._match_text_seq(\"WHILE\"):\n            return self._parse_whileblock()\n\n        expression = self._parse_expression()\n        expression = self._parse_set_operations(expression) if expression else self._parse_select()\n        return self._parse_query_modifiers(expression)\n\n    def _parse_drop(self, exists: bool = False) -> exp.Drop | exp.Command:\n        start = self._prev\n        temporary = self._match(TokenType.TEMPORARY)\n        materialized = self._match_text_seq(\"MATERIALIZED\")\n\n        kind = self._match_set(self.CREATABLES) and self._prev.text.upper()\n        if not kind:\n            return self._parse_as_command(start)\n\n        concurrently = self._match_text_seq(\"CONCURRENTLY\")\n        if_exists = exists or self._parse_exists()\n\n        if kind == \"COLUMN\":\n            this = self._parse_column()\n        else:\n            this = self._parse_table_parts(schema=True, is_db_reference=kind == \"SCHEMA\")\n\n        cluster = self._parse_on_property() if self._match(TokenType.ON) else None\n\n        if self._match(TokenType.L_PAREN, advance=False):\n            expressions = self._parse_wrapped_csv(self._parse_types)\n        else:\n            expressions = None\n\n        return self.expression(\n            exp.Drop(\n                exists=if_exists,\n                this=this,\n                expressions=expressions,\n                kind=self.dialect.CREATABLE_KIND_MAPPING.get(kind) or kind,\n                temporary=temporary,\n                materialized=materialized,\n                cascade=self._match_text_seq(\"CASCADE\"),\n                constraints=self._match_text_seq(\"CONSTRAINTS\"),\n                purge=self._match_text_seq(\"PURGE\"),\n                cluster=cluster,\n                concurrently=concurrently,\n                sync=self._match_text_seq(\"SYNC\"),\n            )\n        )\n\n    def _parse_exists(self, not_: bool = False) -> t.Optional[bool]:\n        return (\n            self._match_text_seq(\"IF\")\n            and (not not_ or self._match(TokenType.NOT))\n            and self._match(TokenType.EXISTS)\n        )\n\n    def _parse_create(self) -> exp.Create | exp.Command:\n        # Note: this can't be None because we've matched a statement parser\n        start = self._prev\n\n        replace = (\n            start.token_type == TokenType.REPLACE\n            or self._match_pair(TokenType.OR, TokenType.REPLACE)\n            or self._match_pair(TokenType.OR, TokenType.ALTER)\n        )\n        refresh = self._match_pair(TokenType.OR, TokenType.REFRESH)\n\n        unique = self._match(TokenType.UNIQUE)\n\n        if self._match_text_seq(\"CLUSTERED\", \"COLUMNSTORE\"):\n            clustered = True\n        elif self._match_text_seq(\"NONCLUSTERED\", \"COLUMNSTORE\") or self._match_text_seq(\n            \"COLUMNSTORE\"\n        ):\n            clustered = False\n        else:\n            clustered = None\n\n        if self._match_pair(TokenType.TABLE, TokenType.FUNCTION, advance=False):\n            self._advance()\n\n        properties = None\n        create_token = self._match_set(self.CREATABLES) and self._prev\n\n        if not create_token:\n            # exp.Properties.Location.POST_CREATE\n            properties = self._parse_properties()\n            create_token = self._match_set(self.CREATABLES) and self._prev\n\n            if not properties or not create_token:\n                return self._parse_as_command(start)\n\n        create_token_type = t.cast(Token, create_token).token_type\n\n        concurrently = self._match_text_seq(\"CONCURRENTLY\")\n        exists = self._parse_exists(not_=True)\n        this = None\n        expression: t.Optional[exp.Expr] = None\n        indexes = None\n        no_schema_binding = None\n        begin = None\n        clone = None\n\n        def extend_props(temp_props: t.Optional[exp.Properties]) -> None:\n            nonlocal properties\n            if properties and temp_props:\n                properties.expressions.extend(temp_props.expressions)\n            elif temp_props:\n                properties = temp_props\n\n        if create_token_type in (TokenType.FUNCTION, TokenType.PROCEDURE):\n            this = self._parse_user_defined_function(kind=create_token_type)\n\n            # exp.Properties.Location.POST_SCHEMA (\"schema\" here is the UDF's type signature)\n            extend_props(self._parse_properties())\n\n            expression = self._parse_heredoc() if self._match(TokenType.ALIAS) else None\n            extend_props(self._parse_properties())\n\n            if not expression:\n                if self._match(TokenType.COMMAND):\n                    expression = self._parse_as_command(self._prev)\n                else:\n                    begin = self._match(TokenType.BEGIN)\n                    return_ = self._match_text_seq(\"RETURN\")\n\n                    if self._match(TokenType.STRING, advance=False):\n                        # Takes care of BigQuery's JavaScript UDF definitions that end in an OPTIONS property\n                        # # https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_function_statement\n                        expression = self._parse_string()\n                        extend_props(self._parse_properties())\n                    else:\n                        expression = (\n                            self._parse_user_defined_function_expression()\n                            if create_token_type == TokenType.FUNCTION\n                            else self._parse_block()\n                        )\n\n                    if return_:\n                        expression = self.expression(exp.Return(this=expression))\n        elif create_token_type == TokenType.INDEX:\n            # Postgres allows anonymous indexes, eg. CREATE INDEX IF NOT EXISTS ON t(c)\n            if not self._match(TokenType.ON):\n                index = self._parse_id_var()\n                anonymous = False\n            else:\n                index = None\n                anonymous = True\n\n            this = self._parse_index(index=index, anonymous=anonymous)\n        elif (\n            create_token_type == TokenType.CONSTRAINT and self._match(TokenType.TRIGGER)\n        ) or create_token_type == TokenType.TRIGGER:\n            if is_constraint := (create_token_type == TokenType.CONSTRAINT):\n                create_token = self._prev\n\n            trigger_name = self._parse_id_var()\n            if not trigger_name:\n                return self._parse_as_command(start)\n\n            timing_var = self._parse_var_from_options(self.TRIGGER_TIMING, raise_unmatched=False)\n            timing = timing_var.this if timing_var else None\n            if not timing:\n                return self._parse_as_command(start)\n\n            events = self._parse_trigger_events()\n            if not self._match(TokenType.ON):\n                self.raise_error(\"Expected ON in trigger definition\")\n\n            table = self._parse_table_parts()\n            referenced_table = self._parse_table_parts() if self._match(TokenType.FROM) else None\n            deferrable, initially = self._parse_trigger_deferrable()\n            referencing = self._parse_trigger_referencing()\n            for_each = self._parse_trigger_for_each()\n            when = self._match_text_seq(\"WHEN\") and self._parse_wrapped(\n                self._parse_disjunction, optional=True\n            )\n            execute = self._parse_trigger_execute()\n\n            if execute is None:\n                return self._parse_as_command(start)\n\n            trigger_props = self.expression(\n                exp.TriggerProperties(\n                    table=table,\n                    timing=timing,\n                    events=events,\n                    execute=execute,\n                    constraint=is_constraint,\n                    referenced_table=referenced_table,\n                    deferrable=deferrable,\n                    initially=initially,\n                    referencing=referencing,\n                    for_each=for_each,\n                    when=when,\n                )\n            )\n\n            this = trigger_name\n            extend_props(exp.Properties(expressions=[trigger_props] if trigger_props else []))\n        elif create_token_type in self.DB_CREATABLES:\n            table_parts = self._parse_table_parts(\n                schema=True, is_db_reference=create_token_type == TokenType.SCHEMA\n            )\n\n            # exp.Properties.Location.POST_NAME\n            self._match(TokenType.COMMA)\n            extend_props(self._parse_properties(before=True))\n\n            this = self._parse_schema(this=table_parts)\n\n            # exp.Properties.Location.POST_SCHEMA and POST_WITH\n            extend_props(self._parse_properties())\n\n            has_alias = self._match(TokenType.ALIAS)\n            if not self._match_set(self.DDL_SELECT_TOKENS, advance=False):\n                # exp.Properties.Location.POST_ALIAS\n                extend_props(self._parse_properties())\n\n            if create_token_type == TokenType.SEQUENCE:\n                expression = self._parse_types()\n                props = self._parse_properties()\n                if props:\n                    sequence_props = exp.SequenceProperties()\n                    options = []\n                    for prop in props:\n                        if isinstance(prop, exp.SequenceProperties):\n                            for arg, value in prop.args.items():\n                                if arg == \"options\":\n                                    options.extend(value)\n                                else:\n                                    sequence_props.set(arg, value)\n                            prop.pop()\n\n                    if options:\n                        sequence_props.set(\"options\", options)\n\n                    props.append(\"expressions\", sequence_props)\n                    extend_props(props)\n            else:\n                expression = self._parse_ddl_select()\n\n                # Some dialects also support using a table as an alias instead of a SELECT.\n                # Here we fallback to this as an alternative.\n                if not expression and has_alias:\n                    expression = self._try_parse(self._parse_table_parts)\n\n            if create_token_type == TokenType.TABLE:\n                # exp.Properties.Location.POST_EXPRESSION\n                extend_props(self._parse_properties())\n\n                indexes = []\n                while True:\n                    index = self._parse_index()\n\n                    # exp.Properties.Location.POST_INDEX\n                    extend_props(self._parse_properties())\n                    if not index:\n                        break\n                    else:\n                        self._match(TokenType.COMMA)\n                        indexes.append(index)\n            elif create_token_type == TokenType.VIEW:\n                if self._match_text_seq(\"WITH\", \"NO\", \"SCHEMA\", \"BINDING\"):\n                    no_schema_binding = True\n            elif create_token_type in (TokenType.SINK, TokenType.SOURCE):\n                extend_props(self._parse_properties())\n\n            shallow = self._match_text_seq(\"SHALLOW\")\n\n            if self._match_texts(self.CLONE_KEYWORDS):\n                copy = self._prev.text.lower() == \"copy\"\n                clone = self.expression(\n                    exp.Clone(this=self._parse_table(schema=True), shallow=shallow, copy=copy)\n                )\n\n        if self._curr and not self._match_set((TokenType.R_PAREN, TokenType.COMMA), advance=False):\n            return self._parse_as_command(start)\n\n        create_kind_text = create_token.text.upper()\n        return self.expression(\n            exp.Create(\n                this=this,\n                kind=self.dialect.CREATABLE_KIND_MAPPING.get(create_kind_text) or create_kind_text,\n                replace=replace,\n                refresh=refresh,\n                unique=unique,\n                expression=expression,\n                exists=exists,\n                properties=properties,\n                indexes=indexes,\n                no_schema_binding=no_schema_binding,\n                begin=begin,\n                clone=clone,\n                concurrently=concurrently,\n                clustered=clustered,\n            )\n        )\n\n    def _parse_sequence_properties(self) -> t.Optional[exp.SequenceProperties]:\n        seq = exp.SequenceProperties()\n\n        options = []\n        index = self._index\n\n        while self._curr:\n            self._match(TokenType.COMMA)\n            if self._match_text_seq(\"INCREMENT\"):\n                self._match_text_seq(\"BY\")\n                self._match_text_seq(\"=\")\n                seq.set(\"increment\", self._parse_term())\n            elif self._match_text_seq(\"MINVALUE\"):\n                seq.set(\"minvalue\", self._parse_term())\n            elif self._match_text_seq(\"MAXVALUE\"):\n                seq.set(\"maxvalue\", self._parse_term())\n            elif self._match(TokenType.START_WITH) or self._match_text_seq(\"START\"):\n                self._match_text_seq(\"=\")\n                seq.set(\"start\", self._parse_term())\n            elif self._match_text_seq(\"CACHE\"):\n                # T-SQL allows empty CACHE which is initialized dynamically\n                seq.set(\"cache\", self._parse_number() or True)\n            elif self._match_text_seq(\"OWNED\", \"BY\"):\n                # \"OWNED BY NONE\" is the default\n                seq.set(\"owned\", None if self._match_text_seq(\"NONE\") else self._parse_column())\n            else:\n                opt = self._parse_var_from_options(self.CREATE_SEQUENCE, raise_unmatched=False)\n                if opt:\n                    options.append(opt)\n                else:\n                    break\n\n        seq.set(\"options\", options if options else None)\n        return None if self._index == index else seq\n\n    def _parse_trigger_events(self) -> t.List[exp.TriggerEvent]:\n        events = []\n\n        while True:\n            event_type = self._match_set(self.TRIGGER_EVENTS) and self._prev.text.upper()\n\n            if not event_type:\n                self.raise_error(\"Expected trigger event (INSERT, UPDATE, DELETE, TRUNCATE)\")\n\n            columns = (\n                self._parse_csv(self._parse_column)\n                if event_type == \"UPDATE\" and self._match_text_seq(\"OF\")\n                else None\n            )\n\n            events.append(self.expression(exp.TriggerEvent(this=event_type, columns=columns)))\n\n            if not self._match(TokenType.OR):\n                break\n\n        return events\n\n    def _parse_trigger_deferrable(\n        self,\n    ) -> t.Tuple[t.Optional[str], t.Optional[str]]:\n        deferrable_var = self._parse_var_from_options(\n            self.TRIGGER_DEFERRABLE, raise_unmatched=False\n        )\n        deferrable = deferrable_var.this if deferrable_var else None\n\n        initially = None\n        if deferrable and self._match_text_seq(\"INITIALLY\"):\n            initially = (\n                self._prev.text.upper() if self._match_texts((\"IMMEDIATE\", \"DEFERRED\")) else None\n            )\n\n        return deferrable, initially\n\n    def _parse_trigger_referencing_clause(self, keyword: str) -> t.Optional[exp.Expr]:\n        if not self._match_text_seq(keyword):\n            return None\n        if not self._match_text_seq(\"TABLE\"):\n            self.raise_error(f\"Expected TABLE after {keyword} in REFERENCING clause\")\n        self._match_text_seq(\"AS\")\n        return self._parse_id_var()\n\n    def _parse_trigger_referencing(self) -> t.Optional[exp.TriggerReferencing]:\n        if not self._match_text_seq(\"REFERENCING\"):\n            return None\n\n        old_alias = None\n        new_alias = None\n\n        while True:\n            if alias := self._parse_trigger_referencing_clause(\"OLD\"):\n                if old_alias is not None:\n                    self.raise_error(\"Duplicate OLD clause in REFERENCING\")\n                old_alias = alias\n            elif alias := self._parse_trigger_referencing_clause(\"NEW\"):\n                if new_alias is not None:\n                    self.raise_error(\"Duplicate NEW clause in REFERENCING\")\n                new_alias = alias\n            else:\n                break\n\n        if old_alias is None and new_alias is None:\n            self.raise_error(\"REFERENCING clause requires at least OLD TABLE or NEW TABLE\")\n\n        return self.expression(exp.TriggerReferencing(old=old_alias, new=new_alias))\n\n    def _parse_trigger_for_each(self) -> t.Optional[str]:\n        if not self._match_text_seq(\"FOR\", \"EACH\"):\n            return None\n\n        return self._prev.text.upper() if self._match_texts((\"ROW\", \"STATEMENT\")) else None\n\n    def _parse_trigger_execute(self) -> t.Optional[exp.TriggerExecute]:\n        if not self._match(TokenType.EXECUTE):\n            return None\n\n        if not self._match_set((TokenType.FUNCTION, TokenType.PROCEDURE)):\n            self.raise_error(\"Expected FUNCTION or PROCEDURE after EXECUTE\")\n\n        func_call = self._parse_function(anonymous=True, optional_parens=False)\n        return self.expression(exp.TriggerExecute(this=func_call))\n\n    def _parse_property_before(self) -> exp.Expr | t.List[exp.Expr] | None:\n        # only used for teradata currently\n        self._match(TokenType.COMMA)\n\n        kwargs = {\n            \"no\": self._match_text_seq(\"NO\"),\n            \"dual\": self._match_text_seq(\"DUAL\"),\n            \"before\": self._match_text_seq(\"BEFORE\"),\n            \"default\": self._match_text_seq(\"DEFAULT\"),\n            \"local\": (self._match_text_seq(\"LOCAL\") and \"LOCAL\")\n            or (self._match_text_seq(\"NOT\", \"LOCAL\") and \"NOT LOCAL\"),\n            \"after\": self._match_text_seq(\"AFTER\"),\n            \"minimum\": self._match_texts((\"MIN\", \"MINIMUM\")),\n            \"maximum\": self._match_texts((\"MAX\", \"MAXIMUM\")),\n        }\n\n        if self._match_texts(self.PROPERTY_PARSERS):\n            parser = self.PROPERTY_PARSERS[self._prev.text.upper()]\n            try:\n                return parser(self, **{k: v for k, v in kwargs.items() if v})\n            except TypeError:\n                self.raise_error(f\"Cannot parse property '{self._prev.text}'\")\n\n        return None\n\n    def _parse_wrapped_properties(self) -> t.List[exp.Expr | t.List[exp.Expr]]:\n        return self._parse_wrapped_csv(self._parse_property)\n\n    def _parse_property(self) -> exp.Expr | t.List[exp.Expr] | None:\n        if self._match_texts(self.PROPERTY_PARSERS):\n            return self.PROPERTY_PARSERS[self._prev.text.upper()](self)\n\n        if self._match(TokenType.DEFAULT) and self._match_texts(self.PROPERTY_PARSERS):\n            return self.PROPERTY_PARSERS[self._prev.text.upper()](self, default=True)\n\n        if self._match_text_seq(\"COMPOUND\", \"SORTKEY\"):\n            return self._parse_sortkey(compound=True)\n\n        if self._match_text_seq(\"PARAMETER\", \"STYLE\", \"PANDAS\"):\n            return self.expression(exp.ParameterStyleProperty(this=\"PANDAS\"))\n\n        index = self._index\n\n        seq_props = self._parse_sequence_properties()\n        if seq_props:\n            return seq_props\n\n        self._retreat(index)\n        key = self._parse_column()\n\n        if not self._match(TokenType.EQ):\n            self._retreat(index)\n            return None\n\n        # Transform the key to exp.Dot if it's dotted identifiers wrapped in exp.Column or to exp.Var otherwise\n        if isinstance(key, exp.Column):\n            key = key.to_dot() if len(key.parts) > 1 else exp.var(key.name)\n\n        value = self._parse_bitwise() or self._parse_var(any_token=True)\n\n        # Transform the value to exp.Var if it was parsed as exp.Column(exp.Identifier())\n        if isinstance(value, exp.Column):\n            value = exp.var(value.name)\n\n        return self.expression(exp.Property(this=key, value=value))\n\n    def _parse_stored(self) -> t.Union[exp.FileFormatProperty, exp.StorageHandlerProperty]:\n        if self._match_text_seq(\"BY\"):\n            return self.expression(exp.StorageHandlerProperty(this=self._parse_var_or_string()))\n\n        self._match(TokenType.ALIAS)\n        input_format = self._parse_string() if self._match_text_seq(\"INPUTFORMAT\") else None\n        output_format = self._parse_string() if self._match_text_seq(\"OUTPUTFORMAT\") else None\n\n        return self.expression(\n            exp.FileFormatProperty(\n                this=(\n                    self.expression(\n                        exp.InputOutputFormat(\n                            input_format=input_format, output_format=output_format\n                        )\n                    )\n                    if input_format or output_format\n                    else self._parse_var_or_string() or self._parse_number() or self._parse_id_var()\n                ),\n                hive_format=True,\n            )\n        )\n\n    def _parse_unquoted_field(self) -> t.Optional[exp.Expr]:\n        field = self._parse_field()\n        if isinstance(field, exp.Identifier) and not field.quoted:\n            field = exp.var(field)\n\n        return field\n\n    def _parse_property_assignment(self, exp_class: t.Type[E], **kwargs: t.Any) -> E:\n        self._match(TokenType.EQ)\n        self._match(TokenType.ALIAS)\n\n        return self.expression(exp_class(this=self._parse_unquoted_field(), **kwargs))\n\n    def _parse_properties(self, before: t.Optional[bool] = None) -> t.Optional[exp.Properties]:\n        properties = []\n        while True:\n            if before:\n                prop = self._parse_property_before()\n            else:\n                prop = self._parse_property()\n            if not prop:\n                break\n            for p in ensure_list(prop):\n                properties.append(p)\n\n        if properties:\n            return self.expression(exp.Properties(expressions=properties))\n\n        return None\n\n    def _parse_fallback(self, no: bool = False) -> exp.FallbackProperty:\n        return self.expression(\n            exp.FallbackProperty(no=no, protection=self._match_text_seq(\"PROTECTION\"))\n        )\n\n    def _parse_sql_security(self) -> exp.SqlSecurityProperty:\n        return self.expression(\n            exp.SqlSecurityProperty(\n                this=self._match_texts(self.SECURITY_PROPERTY_KEYWORDS) and self._prev.text.upper()\n            )\n        )\n\n    def _parse_settings_property(self) -> exp.SettingsProperty:\n        return self.expression(\n            exp.SettingsProperty(expressions=self._parse_csv(self._parse_assignment))\n        )\n\n    def _parse_volatile_property(self) -> exp.VolatileProperty | exp.StabilityProperty:\n        if self._index >= 2:\n            pre_volatile_token = self._tokens[self._index - 2]\n        else:\n            pre_volatile_token = None\n\n        if pre_volatile_token and pre_volatile_token.token_type in self.PRE_VOLATILE_TOKENS:\n            return exp.VolatileProperty()\n\n        return self.expression(exp.StabilityProperty(this=exp.Literal.string(\"VOLATILE\")))\n\n    def _parse_retention_period(self) -> exp.Var:\n        # Parse TSQL's HISTORY_RETENTION_PERIOD: {INFINITE | <number> DAY | DAYS | MONTH ...}\n        number = self._parse_number()\n        number_str = f\"{number} \" if number else \"\"\n        unit = self._parse_var(any_token=True)\n        return exp.var(f\"{number_str}{unit}\")\n\n    def _parse_system_versioning_property(\n        self, with_: bool = False\n    ) -> exp.WithSystemVersioningProperty:\n        self._match(TokenType.EQ)\n        prop = self.expression(exp.WithSystemVersioningProperty(on=True, with_=with_))\n\n        if self._match_text_seq(\"OFF\"):\n            prop.set(\"on\", False)\n            return prop\n\n        self._match(TokenType.ON)\n        if self._match(TokenType.L_PAREN):\n            while self._curr and not self._match(TokenType.R_PAREN):\n                if self._match_text_seq(\"HISTORY_TABLE\", \"=\"):\n                    prop.set(\"this\", self._parse_table_parts())\n                elif self._match_text_seq(\"DATA_CONSISTENCY_CHECK\", \"=\"):\n                    prop.set(\"data_consistency\", self._advance_any() and self._prev.text.upper())\n                elif self._match_text_seq(\"HISTORY_RETENTION_PERIOD\", \"=\"):\n                    prop.set(\"retention_period\", self._parse_retention_period())\n\n                self._match(TokenType.COMMA)\n\n        return prop\n\n    def _parse_data_deletion_property(self) -> exp.DataDeletionProperty:\n        self._match(TokenType.EQ)\n        on = self._match_text_seq(\"ON\") or not self._match_text_seq(\"OFF\")\n        prop = self.expression(exp.DataDeletionProperty(on=on))\n\n        if self._match(TokenType.L_PAREN):\n            while self._curr and not self._match(TokenType.R_PAREN):\n                if self._match_text_seq(\"FILTER_COLUMN\", \"=\"):\n                    prop.set(\"filter_column\", self._parse_column())\n                elif self._match_text_seq(\"RETENTION_PERIOD\", \"=\"):\n                    prop.set(\"retention_period\", self._parse_retention_period())\n\n                self._match(TokenType.COMMA)\n\n        return prop\n\n    def _parse_distributed_property(self) -> exp.DistributedByProperty:\n        kind = \"HASH\"\n        expressions: t.Optional[t.List[exp.Expr]] = None\n        if self._match_text_seq(\"BY\", \"HASH\"):\n            expressions = self._parse_wrapped_csv(self._parse_id_var)\n        elif self._match_text_seq(\"BY\", \"RANDOM\"):\n            kind = \"RANDOM\"\n\n        # If the BUCKETS keyword is not present, the number of buckets is AUTO\n        buckets: t.Optional[exp.Expr] = None\n        if self._match_text_seq(\"BUCKETS\") and not self._match_text_seq(\"AUTO\"):\n            buckets = self._parse_number()\n\n        return self.expression(\n            exp.DistributedByProperty(\n                expressions=expressions, kind=kind, buckets=buckets, order=self._parse_order()\n            )\n        )\n\n    def _parse_composite_key_property(self, expr_type: t.Type[E]) -> E:\n        self._match_text_seq(\"KEY\")\n        expressions = self._parse_wrapped_id_vars()\n        return self.expression(expr_type(expressions=expressions))\n\n    def _parse_with_property(self) -> t.Optional[exp.Expr] | t.List[exp.Expr]:\n        if self._match_text_seq(\"(\", \"SYSTEM_VERSIONING\"):\n            prop = self._parse_system_versioning_property(with_=True)\n            self._match_r_paren()\n            return prop\n\n        if self._match(TokenType.L_PAREN, advance=False):\n            result: t.List[exp.Expr] = []\n            for i in self._parse_wrapped_properties():\n                result.extend(i) if isinstance(i, list) else result.append(i)\n            return result\n\n        if self._match_text_seq(\"JOURNAL\"):\n            return self._parse_withjournaltable()\n\n        if self._match_texts(self.VIEW_ATTRIBUTES):\n            return self.expression(exp.ViewAttributeProperty(this=self._prev.text.upper()))\n\n        if self._match_text_seq(\"DATA\"):\n            return self._parse_withdata(no=False)\n        elif self._match_text_seq(\"NO\", \"DATA\"):\n            return self._parse_withdata(no=True)\n\n        if self._match(TokenType.SERDE_PROPERTIES, advance=False):\n            return self._parse_serde_properties(with_=True)\n\n        if self._match(TokenType.SCHEMA):\n            return self.expression(\n                exp.WithSchemaBindingProperty(\n                    this=self._parse_var_from_options(self.SCHEMA_BINDING_OPTIONS)\n                )\n            )\n\n        if self._match_texts(self.PROCEDURE_OPTIONS, advance=False):\n            return self.expression(\n                exp.WithProcedureOptions(expressions=self._parse_csv(self._parse_procedure_option))\n            )\n\n        if not self._next:\n            return None\n\n        return self._parse_withisolatedloading()\n\n    def _parse_procedure_option(self) -> exp.Expr | None:\n        if self._match_text_seq(\"EXECUTE\", \"AS\"):\n            return self.expression(\n                exp.ExecuteAsProperty(\n                    this=self._parse_var_from_options(\n                        self.EXECUTE_AS_OPTIONS, raise_unmatched=False\n                    )\n                    or self._parse_string()\n                )\n            )\n\n        return self._parse_var_from_options(self.PROCEDURE_OPTIONS)\n\n    # https://dev.mysql.com/doc/refman/8.0/en/create-view.html\n    def _parse_definer(self) -> t.Optional[exp.DefinerProperty]:\n        self._match(TokenType.EQ)\n\n        user = self._parse_id_var()\n        self._match(TokenType.PARAMETER)\n        host = self._parse_id_var() or (self._match(TokenType.MOD) and self._prev.text)\n\n        if not user or not host:\n            return None\n\n        return exp.DefinerProperty(this=f\"{user}@{host}\")\n\n    def _parse_withjournaltable(self) -> exp.WithJournalTableProperty:\n        self._match(TokenType.TABLE)\n        self._match(TokenType.EQ)\n        return self.expression(exp.WithJournalTableProperty(this=self._parse_table_parts()))\n\n    def _parse_log(self, no: bool = False) -> exp.LogProperty:\n        return self.expression(exp.LogProperty(no=no))\n\n    def _parse_journal(self, **kwargs) -> exp.JournalProperty:\n        return self.expression(exp.JournalProperty(**kwargs))\n\n    def _parse_checksum(self) -> exp.ChecksumProperty:\n        self._match(TokenType.EQ)\n\n        on = None\n        if self._match(TokenType.ON):\n            on = True\n        elif self._match_text_seq(\"OFF\"):\n            on = False\n\n        return self.expression(exp.ChecksumProperty(on=on, default=self._match(TokenType.DEFAULT)))\n\n    def _parse_cluster(self, wrapped: bool = False) -> exp.Cluster:\n        return self.expression(\n            exp.Cluster(\n                expressions=(\n                    self._parse_wrapped_csv(self._parse_ordered)\n                    if wrapped\n                    else self._parse_csv(self._parse_ordered)\n                )\n            )\n        )\n\n    def _parse_clustered_by(self) -> exp.ClusteredByProperty:\n        self._match_text_seq(\"BY\")\n\n        self._match_l_paren()\n        expressions = self._parse_csv(self._parse_column)\n        self._match_r_paren()\n\n        if self._match_text_seq(\"SORTED\", \"BY\"):\n            self._match_l_paren()\n            sorted_by = self._parse_csv(self._parse_ordered)\n            self._match_r_paren()\n        else:\n            sorted_by = None\n\n        self._match(TokenType.INTO)\n        buckets = self._parse_number()\n        self._match_text_seq(\"BUCKETS\")\n\n        return self.expression(\n            exp.ClusteredByProperty(expressions=expressions, sorted_by=sorted_by, buckets=buckets)\n        )\n\n    def _parse_copy_property(self) -> t.Optional[exp.CopyGrantsProperty]:\n        if not self._match_text_seq(\"GRANTS\"):\n            self._retreat(self._index - 1)\n            return None\n\n        return self.expression(exp.CopyGrantsProperty())\n\n    def _parse_freespace(self) -> exp.FreespaceProperty:\n        self._match(TokenType.EQ)\n        return self.expression(\n            exp.FreespaceProperty(this=self._parse_number(), percent=self._match(TokenType.PERCENT))\n        )\n\n    def _parse_mergeblockratio(\n        self, no: bool = False, default: bool = False\n    ) -> exp.MergeBlockRatioProperty:\n        if self._match(TokenType.EQ):\n            return self.expression(\n                exp.MergeBlockRatioProperty(\n                    this=self._parse_number(), percent=self._match(TokenType.PERCENT)\n                )\n            )\n\n        return self.expression(exp.MergeBlockRatioProperty(no=no, default=default))\n\n    def _parse_datablocksize(\n        self,\n        default: t.Optional[bool] = None,\n        minimum: t.Optional[bool] = None,\n        maximum: t.Optional[bool] = None,\n    ) -> exp.DataBlocksizeProperty:\n        self._match(TokenType.EQ)\n        size = self._parse_number()\n\n        units = None\n        if self._match_texts((\"BYTES\", \"KBYTES\", \"KILOBYTES\")):\n            units = self._prev.text\n\n        return self.expression(\n            exp.DataBlocksizeProperty(\n                size=size, units=units, default=default, minimum=minimum, maximum=maximum\n            )\n        )\n\n    def _parse_blockcompression(self) -> exp.BlockCompressionProperty:\n        self._match(TokenType.EQ)\n        always = self._match_text_seq(\"ALWAYS\")\n        manual = self._match_text_seq(\"MANUAL\")\n        never = self._match_text_seq(\"NEVER\")\n        default = self._match_text_seq(\"DEFAULT\")\n\n        autotemp = None\n        if self._match_text_seq(\"AUTOTEMP\"):\n            autotemp = self._parse_schema()\n\n        return self.expression(\n            exp.BlockCompressionProperty(\n                always=always, manual=manual, never=never, default=default, autotemp=autotemp\n            )\n        )\n\n    def _parse_withisolatedloading(self) -> t.Optional[exp.IsolatedLoadingProperty]:\n        index = self._index\n        no = self._match_text_seq(\"NO\")\n        concurrent = self._match_text_seq(\"CONCURRENT\")\n\n        if not self._match_text_seq(\"ISOLATED\", \"LOADING\"):\n            self._retreat(index)\n            return None\n\n        target = self._parse_var_from_options(self.ISOLATED_LOADING_OPTIONS, raise_unmatched=False)\n        return self.expression(\n            exp.IsolatedLoadingProperty(no=no, concurrent=concurrent, target=target)\n        )\n\n    def _parse_locking(self) -> exp.LockingProperty:\n        if self._match(TokenType.TABLE):\n            kind = \"TABLE\"\n        elif self._match(TokenType.VIEW):\n            kind = \"VIEW\"\n        elif self._match(TokenType.ROW):\n            kind = \"ROW\"\n        elif self._match_text_seq(\"DATABASE\"):\n            kind = \"DATABASE\"\n        else:\n            kind = None\n\n        if kind in (\"DATABASE\", \"TABLE\", \"VIEW\"):\n            this = self._parse_table_parts()\n        else:\n            this = None\n\n        if self._match(TokenType.FOR):\n            for_or_in = \"FOR\"\n        elif self._match(TokenType.IN):\n            for_or_in = \"IN\"\n        else:\n            for_or_in = None\n\n        if self._match_text_seq(\"ACCESS\"):\n            lock_type = \"ACCESS\"\n        elif self._match_texts((\"EXCL\", \"EXCLUSIVE\")):\n            lock_type = \"EXCLUSIVE\"\n        elif self._match_text_seq(\"SHARE\"):\n            lock_type = \"SHARE\"\n        elif self._match_text_seq(\"READ\"):\n            lock_type = \"READ\"\n        elif self._match_text_seq(\"WRITE\"):\n            lock_type = \"WRITE\"\n        elif self._match_text_seq(\"CHECKSUM\"):\n            lock_type = \"CHECKSUM\"\n        else:\n            lock_type = None\n\n        override = self._match_text_seq(\"OVERRIDE\")\n\n        return self.expression(\n            exp.LockingProperty(\n                this=this, kind=kind, for_or_in=for_or_in, lock_type=lock_type, override=override\n            )\n        )\n\n    def _parse_partition_by(self) -> t.List[exp.Expr]:\n        if self._match(TokenType.PARTITION_BY):\n            return self._parse_csv(self._parse_disjunction)\n        return []\n\n    def _parse_partition_bound_spec(self) -> exp.PartitionBoundSpec:\n        def _parse_partition_bound_expr() -> t.Optional[exp.Expr]:\n            if self._match_text_seq(\"MINVALUE\"):\n                return exp.var(\"MINVALUE\")\n            if self._match_text_seq(\"MAXVALUE\"):\n                return exp.var(\"MAXVALUE\")\n            return self._parse_bitwise()\n\n        this: t.Optional[exp.Expr | t.List[exp.Expr]] = None\n        expression = None\n        from_expressions = None\n        to_expressions = None\n\n        if self._match(TokenType.IN):\n            this = self._parse_wrapped_csv(self._parse_bitwise)\n        elif self._match(TokenType.FROM):\n            from_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)\n            self._match_text_seq(\"TO\")\n            to_expressions = self._parse_wrapped_csv(_parse_partition_bound_expr)\n        elif self._match_text_seq(\"WITH\", \"(\", \"MODULUS\"):\n            this = self._parse_number()\n            self._match_text_seq(\",\", \"REMAINDER\")\n            expression = self._parse_number()\n            self._match_r_paren()\n        else:\n            self.raise_error(\"Failed to parse partition bound spec.\")\n\n        return self.expression(\n            exp.PartitionBoundSpec(\n                this=this,\n                expression=expression,\n                from_expressions=from_expressions,\n                to_expressions=to_expressions,\n            )\n        )\n\n    # https://www.postgresql.org/docs/current/sql-createtable.html\n    def _parse_partitioned_of(self) -> t.Optional[exp.PartitionedOfProperty]:\n        if not self._match_text_seq(\"OF\"):\n            self._retreat(self._index - 1)\n            return None\n\n        this = self._parse_table(schema=True)\n\n        if self._match(TokenType.DEFAULT):\n            expression: exp.Var | exp.PartitionBoundSpec = exp.var(\"DEFAULT\")\n        elif self._match_text_seq(\"FOR\", \"VALUES\"):\n            expression = self._parse_partition_bound_spec()\n        else:\n            self.raise_error(\"Expecting either DEFAULT or FOR VALUES clause.\")\n\n        return self.expression(exp.PartitionedOfProperty(this=this, expression=expression))\n\n    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:\n        self._match(TokenType.EQ)\n        return self.expression(\n            exp.PartitionedByProperty(\n                this=self._parse_schema() or self._parse_bracket(self._parse_field())\n            )\n        )\n\n    def _parse_withdata(self, no: bool = False) -> exp.WithDataProperty:\n        if self._match_text_seq(\"AND\", \"STATISTICS\"):\n            statistics = True\n        elif self._match_text_seq(\"AND\", \"NO\", \"STATISTICS\"):\n            statistics = False\n        else:\n            statistics = None\n\n        return self.expression(exp.WithDataProperty(no=no, statistics=statistics))\n\n    def _parse_contains_property(self) -> t.Optional[exp.SqlReadWriteProperty]:\n        if self._match_text_seq(\"SQL\"):\n            return self.expression(exp.SqlReadWriteProperty(this=\"CONTAINS SQL\"))\n        return None\n\n    def _parse_modifies_property(self) -> t.Optional[exp.SqlReadWriteProperty]:\n        if self._match_text_seq(\"SQL\", \"DATA\"):\n            return self.expression(exp.SqlReadWriteProperty(this=\"MODIFIES SQL DATA\"))\n        return None\n\n    def _parse_no_property(self) -> t.Optional[exp.Expr]:\n        if self._match_text_seq(\"PRIMARY\", \"INDEX\"):\n            return exp.NoPrimaryIndexProperty()\n        if self._match_text_seq(\"SQL\"):\n            return self.expression(exp.SqlReadWriteProperty(this=\"NO SQL\"))\n        return None\n\n    def _parse_on_property(self) -> t.Optional[exp.Expr]:\n        if self._match_text_seq(\"COMMIT\", \"PRESERVE\", \"ROWS\"):\n            return exp.OnCommitProperty()\n        if self._match_text_seq(\"COMMIT\", \"DELETE\", \"ROWS\"):\n            return exp.OnCommitProperty(delete=True)\n        return self.expression(exp.OnProperty(this=self._parse_schema(self._parse_id_var())))\n\n    def _parse_reads_property(self) -> t.Optional[exp.SqlReadWriteProperty]:\n        if self._match_text_seq(\"SQL\", \"DATA\"):\n            return self.expression(exp.SqlReadWriteProperty(this=\"READS SQL DATA\"))\n        return None\n\n    def _parse_distkey(self) -> exp.DistKeyProperty:\n        return self.expression(exp.DistKeyProperty(this=self._parse_wrapped(self._parse_id_var)))\n\n    def _parse_create_like(self) -> t.Optional[exp.LikeProperty]:\n        table = self._parse_table(schema=True)\n\n        options = []\n        while self._match_texts((\"INCLUDING\", \"EXCLUDING\")):\n            this = self._prev.text.upper()\n\n            id_var = self._parse_id_var()\n            if not id_var:\n                return None\n\n            options.append(\n                self.expression(exp.Property(this=this, value=exp.var(id_var.this.upper())))\n            )\n\n        return self.expression(exp.LikeProperty(this=table, expressions=options))\n\n    def _parse_sortkey(self, compound: bool = False) -> exp.SortKeyProperty:\n        return self.expression(\n            exp.SortKeyProperty(this=self._parse_wrapped_id_vars(), compound=compound)\n        )\n\n    def _parse_character_set(self, default: bool = False) -> exp.CharacterSetProperty:\n        self._match(TokenType.EQ)\n        return self.expression(\n            exp.CharacterSetProperty(this=self._parse_var_or_string(), default=default)\n        )\n\n    def _parse_remote_with_connection(self) -> exp.RemoteWithConnectionModelProperty:\n        self._match_text_seq(\"WITH\", \"CONNECTION\")\n        return self.expression(\n            exp.RemoteWithConnectionModelProperty(this=self._parse_table_parts())\n        )\n\n    def _parse_returns(self) -> exp.ReturnsProperty:\n        value: t.Optional[exp.Expr]\n        null = None\n        is_table = self._match(TokenType.TABLE)\n\n        if is_table:\n            if self._match(TokenType.LT):\n                value = self.expression(\n                    exp.Schema(this=\"TABLE\", expressions=self._parse_csv(self._parse_struct_types))\n                )\n                if not self._match(TokenType.GT):\n                    self.raise_error(\"Expecting >\")\n            else:\n                value = self._parse_schema(exp.var(\"TABLE\"))\n        elif self._match_text_seq(\"NULL\", \"ON\", \"NULL\", \"INPUT\"):\n            null = True\n            value = None\n        else:\n            value = self._parse_types()\n\n        return self.expression(exp.ReturnsProperty(this=value, is_table=is_table, null=null))\n\n    def _parse_describe(self) -> exp.Describe:\n        kind = self._prev.text if self._match_set(self.CREATABLES) else None\n        style: t.Optional[str] = (\n            self._prev.text.upper() if self._match_texts(self.DESCRIBE_STYLES) else None\n        )\n        if self._match(TokenType.DOT):\n            style = None\n            self._retreat(self._index - 2)\n\n        format = self._parse_property() if self._match(TokenType.FORMAT, advance=False) else None\n\n        if self._match_set(self.STATEMENT_PARSERS, advance=False):\n            this = self._parse_statement()\n        else:\n            this = self._parse_table(schema=True)\n\n        properties = self._parse_properties()\n        expressions = properties.expressions if properties else None\n        partition = self._parse_partition()\n        return self.expression(\n            exp.Describe(\n                this=this,\n                style=style,\n                kind=kind,\n                expressions=expressions,\n                partition=partition,\n                format=format,\n                as_json=self._match_text_seq(\"AS\", \"JSON\"),\n            )\n        )\n\n    def _parse_multitable_inserts(self, comments: t.Optional[t.List[str]]) -> exp.MultitableInserts:\n        kind = self._prev.text.upper()\n        expressions = []\n\n        def parse_conditional_insert() -> t.Optional[exp.ConditionalInsert]:\n            if self._match(TokenType.WHEN):\n                expression = self._parse_disjunction()\n                self._match(TokenType.THEN)\n            else:\n                expression = None\n\n            else_ = self._match(TokenType.ELSE)\n\n            if not self._match(TokenType.INTO):\n                return None\n\n            return self.expression(\n                exp.ConditionalInsert(\n                    this=self.expression(\n                        exp.Insert(\n                            this=self._parse_table(schema=True),\n                            expression=self._parse_derived_table_values(),\n                        )\n                    ),\n                    expression=expression,\n                    else_=else_,\n                )\n            )\n\n        expression = parse_conditional_insert()\n        while expression is not None:\n            expressions.append(expression)\n            expression = parse_conditional_insert()\n\n        return self.expression(\n            exp.MultitableInserts(kind=kind, expressions=expressions, source=self._parse_table()),\n            comments=comments,\n        )\n\n    def _parse_insert(self) -> t.Union[exp.Insert, exp.MultitableInserts]:\n        comments = []\n        hint = self._parse_hint()\n        overwrite = self._match(TokenType.OVERWRITE)\n        ignore = self._match(TokenType.IGNORE)\n        local = self._match_text_seq(\"LOCAL\")\n        alternative = None\n        is_function = None\n\n        if self._match_text_seq(\"DIRECTORY\"):\n            this: t.Optional[exp.Expr] = self.expression(\n                exp.Directory(\n                    this=self._parse_var_or_string(),\n                    local=local,\n                    row_format=self._parse_row_format(match_row=True),\n                )\n            )\n        else:\n            if self._match_set((TokenType.FIRST, TokenType.ALL)):\n                comments += ensure_list(self._prev_comments)\n                return self._parse_multitable_inserts(comments)\n\n            if self._match(TokenType.OR):\n                alternative = self._match_texts(self.INSERT_ALTERNATIVES) and self._prev.text\n\n            self._match(TokenType.INTO)\n            comments += ensure_list(self._prev_comments)\n            self._match(TokenType.TABLE)\n            is_function = self._match(TokenType.FUNCTION)\n\n            this = self._parse_function() if is_function else self._parse_insert_table()\n\n        returning = self._parse_returning()  # TSQL allows RETURNING before source\n\n        return self.expression(\n            exp.Insert(\n                hint=hint,\n                is_function=is_function,\n                this=this,\n                stored=self._match_text_seq(\"STORED\") and self._parse_stored(),\n                by_name=self._match_text_seq(\"BY\", \"NAME\"),\n                exists=self._parse_exists(),\n                where=self._match_pair(TokenType.REPLACE, TokenType.WHERE)\n                and self._parse_disjunction(),\n                partition=self._match(TokenType.PARTITION_BY) and self._parse_partitioned_by(),\n                settings=self._match_text_seq(\"SETTINGS\") and self._parse_settings_property(),\n                default=self._match_text_seq(\"DEFAULT\", \"VALUES\"),\n                expression=self._parse_derived_table_values() or self._parse_ddl_select(),\n                conflict=self._parse_on_conflict(),\n                returning=returning or self._parse_returning(),\n                overwrite=overwrite,\n                alternative=alternative,\n                ignore=ignore,\n                source=self._match(TokenType.TABLE) and self._parse_table(),\n            ),\n            comments=comments,\n        )\n\n    def _parse_insert_table(self) -> t.Optional[exp.Expr]:\n        this = self._parse_table(schema=True, parse_partition=True)\n        if isinstance(this, exp.Table) and self._match(TokenType.ALIAS, advance=False):\n            this.set(\"alias\", self._parse_table_alias())\n        return this\n\n    def _parse_kill(self) -> exp.Kill:\n        kind = exp.var(self._prev.text) if self._match_texts((\"CONNECTION\", \"QUERY\")) else None\n\n        return self.expression(exp.Kill(this=self._parse_primary(), kind=kind))\n\n    def _parse_on_conflict(self) -> t.Optional[exp.OnConflict]:\n        conflict = self._match_text_seq(\"ON\", \"CONFLICT\")\n        duplicate = self._match_text_seq(\"ON\", \"DUPLICATE\", \"KEY\")\n\n        if not conflict and not duplicate:\n            return None\n\n        conflict_keys = None\n        constraint = None\n\n        if conflict:\n            if self._match_text_seq(\"ON\", \"CONSTRAINT\"):\n                constraint = self._parse_id_var()\n            elif self._match(TokenType.L_PAREN):\n                conflict_keys = self._parse_csv(self._parse_id_var)\n                self._match_r_paren()\n\n        index_predicate = self._parse_where()\n\n        action = self._parse_var_from_options(self.CONFLICT_ACTIONS)\n        if self._prev.token_type == TokenType.UPDATE:\n            self._match(TokenType.SET)\n            expressions = self._parse_csv(self._parse_equality)\n        else:\n            expressions = None\n\n        return self.expression(\n            exp.OnConflict(\n                duplicate=duplicate,\n                expressions=expressions,\n                action=action,\n                conflict_keys=conflict_keys,\n                index_predicate=index_predicate,\n                constraint=constraint,\n                where=self._parse_where(),\n            )\n        )\n\n    def _parse_returning(self) -> t.Optional[exp.Returning]:\n        if not self._match(TokenType.RETURNING):\n            return None\n        return self.expression(\n            exp.Returning(\n                expressions=self._parse_csv(self._parse_expression),\n                into=self._match(TokenType.INTO) and self._parse_table_part(),\n            )\n        )\n\n    def _parse_row(self) -> t.Optional[exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty]:\n        if not self._match(TokenType.FORMAT):\n            return None\n        return self._parse_row_format()\n\n    def _parse_serde_properties(self, with_: bool = False) -> t.Optional[exp.SerdeProperties]:\n        index = self._index\n        with_ = with_ or self._match_text_seq(\"WITH\")\n\n        if not self._match(TokenType.SERDE_PROPERTIES):\n            self._retreat(index)\n            return None\n        return self.expression(\n            exp.SerdeProperties(expressions=self._parse_wrapped_properties(), with_=with_)\n        )\n\n    def _parse_row_format(\n        self, match_row: bool = False\n    ) -> t.Optional[exp.RowFormatSerdeProperty | exp.RowFormatDelimitedProperty]:\n        if match_row and not self._match_pair(TokenType.ROW, TokenType.FORMAT):\n            return None\n\n        if self._match_text_seq(\"SERDE\"):\n            this = self._parse_string()\n\n            serde_properties = self._parse_serde_properties()\n\n            return self.expression(\n                exp.RowFormatSerdeProperty(this=this, serde_properties=serde_properties)\n            )\n\n        self._match_text_seq(\"DELIMITED\")\n\n        kwargs = {}\n\n        if self._match_text_seq(\"FIELDS\", \"TERMINATED\", \"BY\"):\n            kwargs[\"fields\"] = self._parse_string()\n            if self._match_text_seq(\"ESCAPED\", \"BY\"):\n                kwargs[\"escaped\"] = self._parse_string()\n        if self._match_text_seq(\"COLLECTION\", \"ITEMS\", \"TERMINATED\", \"BY\"):\n            kwargs[\"collection_items\"] = self._parse_string()\n        if self._match_text_seq(\"MAP\", \"KEYS\", \"TERMINATED\", \"BY\"):\n            kwargs[\"map_keys\"] = self._parse_string()\n        if self._match_text_seq(\"LINES\", \"TERMINATED\", \"BY\"):\n            kwargs[\"lines\"] = self._parse_string()\n        if self._match_text_seq(\"NULL\", \"DEFINED\", \"AS\"):\n            kwargs[\"null\"] = self._parse_string()\n\n        return self.expression(exp.RowFormatDelimitedProperty(**kwargs))  # type: ignore\n\n    def _parse_load(self) -> exp.LoadData | exp.Command:\n        if self._match_text_seq(\"DATA\"):\n            local = self._match_text_seq(\"LOCAL\")\n            self._match_text_seq(\"INPATH\")\n            inpath = self._parse_string()\n            overwrite = self._match(TokenType.OVERWRITE)\n            self._match_pair(TokenType.INTO, TokenType.TABLE)\n\n            return self.expression(\n                exp.LoadData(\n                    this=self._parse_table(schema=True),\n                    local=local,\n                    overwrite=overwrite,\n                    inpath=inpath,\n                    partition=self._parse_partition(),\n                    input_format=self._match_text_seq(\"INPUTFORMAT\") and self._parse_string(),\n                    serde=self._match_text_seq(\"SERDE\") and self._parse_string(),\n                )\n            )\n        return self._parse_as_command(self._prev)\n\n    def _parse_delete(self) -> exp.Delete:\n        # This handles MySQL's \"Multiple-Table Syntax\"\n        # https://dev.mysql.com/doc/refman/8.0/en/delete.html\n        tables = None\n        if not self._match(TokenType.FROM, advance=False):\n            tables = self._parse_csv(self._parse_table) or None\n\n        returning = self._parse_returning()\n\n        return self.expression(\n            exp.Delete(\n                tables=tables,\n                this=self._match(TokenType.FROM) and self._parse_table(joins=True),\n                using=self._match(TokenType.USING)\n                and self._parse_csv(lambda: self._parse_table(joins=True)),\n                cluster=self._match(TokenType.ON) and self._parse_on_property(),\n                where=self._parse_where(),\n                returning=returning or self._parse_returning(),\n                order=self._parse_order(),\n                limit=self._parse_limit(),\n            )\n        )\n\n    def _parse_update(self) -> exp.Update:\n        kwargs: t.Dict[str, object] = {\n            \"this\": self._parse_table(joins=True, alias_tokens=self.UPDATE_ALIAS_TOKENS),\n        }\n        while self._curr:\n            if self._match(TokenType.SET):\n                kwargs[\"expressions\"] = self._parse_csv(self._parse_equality)\n            elif self._match(TokenType.RETURNING, advance=False):\n                kwargs[\"returning\"] = self._parse_returning()\n            elif self._match(TokenType.FROM, advance=False):\n                from_ = self._parse_from(joins=True)\n                table = from_.this if from_ else None\n                if isinstance(table, exp.Subquery) and self._match(TokenType.JOIN, advance=False):\n                    table.set(\"joins\", list(self._parse_joins()) or None)\n\n                kwargs[\"from_\"] = from_\n            elif self._match(TokenType.WHERE, advance=False):\n                kwargs[\"where\"] = self._parse_where()\n            elif self._match(TokenType.ORDER_BY, advance=False):\n                kwargs[\"order\"] = self._parse_order()\n            elif self._match(TokenType.LIMIT, advance=False):\n                kwargs[\"limit\"] = self._parse_limit()\n            else:\n                break\n\n        return self.expression(exp.Update(**kwargs))\n\n    def _parse_use(self) -> exp.Use:\n        return self.expression(\n            exp.Use(\n                kind=self._parse_var_from_options(self.USABLES, raise_unmatched=False),\n                this=self._parse_table(schema=False),\n            )\n        )\n\n    def _parse_uncache(self) -> exp.Uncache:\n        if not self._match(TokenType.TABLE):\n            self.raise_error(\"Expecting TABLE after UNCACHE\")\n\n        return self.expression(\n            exp.Uncache(exists=self._parse_exists(), this=self._parse_table(schema=True))\n        )\n\n    def _parse_cache(self) -> exp.Cache:\n        lazy = self._match_text_seq(\"LAZY\")\n        self._match(TokenType.TABLE)\n        table = self._parse_table(schema=True)\n\n        options = []\n        if self._match_text_seq(\"OPTIONS\"):\n            self._match_l_paren()\n            k = self._parse_string()\n            self._match(TokenType.EQ)\n            v = self._parse_string()\n            options = [k, v]\n            self._match_r_paren()\n\n        self._match(TokenType.ALIAS)\n        return self.expression(\n            exp.Cache(\n                this=table, lazy=lazy, options=options, expression=self._parse_select(nested=True)\n            )\n        )\n\n    def _parse_partition(self) -> t.Optional[exp.Partition]:\n        if not self._match_texts(self.PARTITION_KEYWORDS):\n            return None\n\n        return self.expression(\n            exp.Partition(\n                subpartition=self._prev.text.upper() == \"SUBPARTITION\",\n                expressions=self._parse_wrapped_csv(self._parse_disjunction),\n            )\n        )\n\n    def _parse_value(self, values: bool = True) -> t.Optional[exp.Tuple]:\n        def _parse_value_expression() -> t.Optional[exp.Expr]:\n            if self.dialect.SUPPORTS_VALUES_DEFAULT and self._match(TokenType.DEFAULT):\n                return exp.var(self._prev.text.upper())\n            return self._parse_expression()\n\n        if self._match(TokenType.L_PAREN):\n            expressions = self._parse_csv(_parse_value_expression)\n            self._match_r_paren()\n            return self.expression(exp.Tuple(expressions=expressions))\n\n        # In some dialects we can have VALUES 1, 2 which results in 1 column & 2 rows.\n        expression = self._parse_expression()\n        if expression:\n            return self.expression(exp.Tuple(expressions=[expression]))\n        return None\n\n    def _parse_projections(\n        self,\n    ) -> t.Tuple[t.List[exp.Expr], t.Optional[t.List[exp.Expr]]]:\n        return self._parse_expressions(), None\n\n    def _parse_wrapped_select(self, table: bool = False) -> t.Optional[exp.Expr]:\n        if self._match_set((TokenType.PIVOT, TokenType.UNPIVOT)):\n            this: t.Optional[exp.Expr] = self._parse_simplified_pivot(\n                is_unpivot=self._prev.token_type == TokenType.UNPIVOT\n            )\n        elif self._match(TokenType.FROM):\n            from_ = self._parse_from(skip_from_token=True, consume_pipe=True)\n            # Support parentheses for duckdb FROM-first syntax\n            select = self._parse_select(from_=from_)\n            if select:\n                if not select.args.get(\"from_\"):\n                    select.set(\"from_\", from_)\n                this = select\n            else:\n                this = exp.select(\"*\").from_(t.cast(exp.From, from_))\n                this = self._parse_query_modifiers(self._parse_set_operations(this))\n        else:\n            this = (\n                self._parse_table(consume_pipe=True)\n                if table\n                else self._parse_select(nested=True, parse_set_operation=False)\n            )\n\n            # Transform exp.Values into a exp.Table to pass through parse_query_modifiers\n            # in case a modifier (e.g. join) is following\n            if table and isinstance(this, exp.Values) and this.alias:\n                alias = this.args[\"alias\"].pop()\n                this = exp.Table(this=this, alias=alias)\n\n            this = self._parse_query_modifiers(self._parse_set_operations(this))\n\n        return this\n\n    def _parse_select(\n        self,\n        nested: bool = False,\n        table: bool = False,\n        parse_subquery_alias: bool = True,\n        parse_set_operation: bool = True,\n        consume_pipe: bool = True,\n        from_: t.Optional[exp.From] = None,\n    ) -> t.Optional[exp.Expr]:\n        query = self._parse_select_query(\n            nested=nested,\n            table=table,\n            parse_subquery_alias=parse_subquery_alias,\n            parse_set_operation=parse_set_operation,\n        )\n\n        if consume_pipe and self._match(TokenType.PIPE_GT, advance=False):\n            if not query and from_:\n                query = exp.select(\"*\").from_(from_)\n            if isinstance(query, exp.Query):\n                query = self._parse_pipe_syntax_query(query)\n                query = query.subquery(copy=False) if query and table else query\n\n        return query\n\n    def _parse_select_query(\n        self,\n        nested: bool = False,\n        table: bool = False,\n        parse_subquery_alias: bool = True,\n        parse_set_operation: bool = True,\n    ) -> t.Optional[exp.Expr]:\n        cte = self._parse_with()\n\n        if cte:\n            this = self._parse_statement()\n\n            if not this:\n                self.raise_error(\"Failed to parse any statement following CTE\")\n                return cte\n\n            while isinstance(this, exp.Subquery) and this.is_wrapper:\n                this = this.this\n\n            assert this is not None\n            if \"with_\" in this.arg_types:\n                this.set(\"with_\", cte)\n            else:\n                self.raise_error(f\"{this.key} does not support CTE\")\n                this = cte\n\n            return this\n\n        # duckdb supports leading with FROM x\n        from_ = (\n            self._parse_from(joins=True, consume_pipe=True)\n            if self._match(TokenType.FROM, advance=False)\n            else None\n        )\n\n        if self._match(TokenType.SELECT):\n            comments = self._prev_comments\n\n            hint = self._parse_hint()\n\n            if self._next and not self._next.token_type == TokenType.DOT:\n                all_ = self._match(TokenType.ALL)\n                matched_distinct = self._match_set(self.DISTINCT_TOKENS)\n            else:\n                all_, matched_distinct = None, False\n\n            kind = (\n                self._prev.text.upper()\n                if self._match(TokenType.ALIAS) and self._match_texts((\"STRUCT\", \"VALUE\"))\n                else None\n            )\n\n            distinct: t.Optional[exp.Expr] = (\n                self.expression(\n                    exp.Distinct(\n                        on=self._parse_value(values=False) if self._match(TokenType.ON) else None\n                    )\n                )\n                if matched_distinct\n                else None\n            )\n\n            if all_ and distinct:\n                self.raise_error(\"Cannot specify both ALL and DISTINCT after SELECT\")\n\n            operation_modifiers = []\n            while self._curr and self._match_texts(self.OPERATION_MODIFIERS):\n                operation_modifiers.append(exp.var(self._prev.text.upper()))\n\n            limit = self._parse_limit(top=True)\n            projections, exclude = self._parse_projections()\n\n            this = self.expression(\n                exp.Select(\n                    kind=kind,\n                    hint=hint,\n                    distinct=distinct,\n                    expressions=projections,\n                    limit=limit,\n                    exclude=exclude,\n                    operation_modifiers=operation_modifiers or None,\n                )\n            )\n            this.comments = comments\n\n            into = self._parse_into()\n            if into:\n                this.set(\"into\", into)\n\n            if not from_:\n                from_ = self._parse_from()\n\n            if from_:\n                this.set(\"from_\", from_)\n\n            this = self._parse_query_modifiers(this)\n        elif (table or nested) and self._match(TokenType.L_PAREN):\n            comments = self._prev_comments\n            this = self._parse_wrapped_select(table=table)\n\n            if this:\n                this.add_comments(comments, prepend=True)\n\n            # We return early here so that the UNION isn't attached to the subquery by the\n            # following call to _parse_set_operations, but instead becomes the parent node\n            self._match_r_paren()\n            return self._parse_subquery(this, parse_alias=parse_subquery_alias)\n        elif self._match(TokenType.VALUES, advance=False):\n            this = self._parse_derived_table_values()\n        elif from_:\n            this = exp.select(\"*\").from_(from_.this, copy=False)\n            this = self._parse_query_modifiers(this)\n        elif self._match(TokenType.SUMMARIZE):\n            table = self._match(TokenType.TABLE)\n            this = self._parse_select() or self._parse_string() or self._parse_table()\n            return self.expression(exp.Summarize(this=this, table=table))\n        elif self._match(TokenType.DESCRIBE):\n            this = self._parse_describe()\n        else:\n            this = None\n\n        return self._parse_set_operations(this) if parse_set_operation else this\n\n    def _parse_recursive_with_search(self) -> t.Optional[exp.RecursiveWithSearch]:\n        self._match_text_seq(\"SEARCH\")\n\n        kind = self._match_texts(self.RECURSIVE_CTE_SEARCH_KIND) and self._prev.text.upper()\n\n        if not kind:\n            return None\n\n        self._match_text_seq(\"FIRST\", \"BY\")\n\n        return self.expression(\n            exp.RecursiveWithSearch(\n                kind=kind,\n                this=self._parse_id_var(),\n                expression=self._match_text_seq(\"SET\") and self._parse_id_var(),\n                using=self._match_text_seq(\"USING\") and self._parse_id_var(),\n            )\n        )\n\n    def _parse_with(self, skip_with_token: bool = False) -> t.Optional[exp.With]:\n        if not skip_with_token and not self._match(TokenType.WITH):\n            return None\n\n        comments = self._prev_comments\n        recursive = self._match(TokenType.RECURSIVE)\n\n        last_comments = None\n        expressions = []\n        while True:\n            cte = self._parse_cte()\n            if isinstance(cte, exp.CTE):\n                expressions.append(cte)\n                if last_comments:\n                    cte.add_comments(last_comments)\n\n            if not self._match(TokenType.COMMA) and not self._match(TokenType.WITH):\n                break\n            else:\n                self._match(TokenType.WITH)\n\n            last_comments = self._prev_comments\n\n        return self.expression(\n            exp.With(\n                expressions=expressions,\n                recursive=recursive or None,\n                search=self._parse_recursive_with_search(),\n            ),\n            comments=comments,\n        )\n\n    def _parse_cte(self) -> t.Optional[exp.CTE]:\n        index = self._index\n\n        alias = self._parse_table_alias(self.ID_VAR_TOKENS)\n        if not alias or not alias.this:\n            self.raise_error(\"Expected CTE to have alias\")\n\n        key_expressions = (\n            self._parse_wrapped_id_vars() if self._match_text_seq(\"USING\", \"KEY\") else None\n        )\n\n        if not self._match(TokenType.ALIAS) and not self.OPTIONAL_ALIAS_TOKEN_CTE:\n            self._retreat(index)\n            return None\n\n        comments = self._prev_comments\n\n        if self._match_text_seq(\"NOT\", \"MATERIALIZED\"):\n            materialized = False\n        elif self._match_text_seq(\"MATERIALIZED\"):\n            materialized = True\n        else:\n            materialized = None\n\n        cte = self.expression(\n            exp.CTE(\n                this=self._parse_wrapped(self._parse_statement),\n                alias=alias,\n                materialized=materialized,\n                key_expressions=key_expressions,\n            ),\n            comments=comments,\n        )\n\n        values = cte.this\n        if isinstance(values, exp.Values):\n            if values.alias:\n                cte.set(\"this\", exp.select(\"*\").from_(values))\n            else:\n                cte.set(\"this\", exp.select(\"*\").from_(exp.alias_(values, \"_values\", table=True)))\n\n        return cte\n\n    def _parse_table_alias(\n        self, alias_tokens: t.Optional[t.Collection[TokenType]] = None\n    ) -> t.Optional[exp.TableAlias]:\n        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)\n        # so this section tries to parse the clause version and if it fails, it treats the token\n        # as an identifier (alias)\n        if self._can_parse_limit_or_offset():\n            return None\n\n        any_token = self._match(TokenType.ALIAS)\n        alias = (\n            self._parse_id_var(any_token=any_token, tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)\n            or self._parse_string_as_identifier()\n        )\n\n        index = self._index\n        if self._match(TokenType.L_PAREN):\n            columns = self._parse_csv(self._parse_function_parameter)\n            self._match_r_paren() if columns else self._retreat(index)\n        else:\n            columns = None\n\n        if not alias and not columns:\n            return None\n\n        table_alias = self.expression(exp.TableAlias(this=alias, columns=columns))\n\n        # We bubble up comments from the Identifier to the TableAlias\n        if isinstance(alias, exp.Identifier):\n            table_alias.add_comments(alias.pop_comments())\n\n        return table_alias\n\n    def _parse_subquery(\n        self, this: t.Optional[exp.Expr], parse_alias: bool = True\n    ) -> t.Optional[exp.Subquery]:\n        if not this:\n            return None\n\n        return self.expression(\n            exp.Subquery(\n                this=this,\n                pivots=self._parse_pivots(),\n                alias=self._parse_table_alias() if parse_alias else None,\n                sample=self._parse_table_sample(),\n            )\n        )\n\n    def _implicit_unnests_to_explicit(self, this: E) -> E:\n        from sqlglot.optimizer.normalize_identifiers import normalize_identifiers as _norm\n\n        refs = {_norm(this.args[\"from_\"].this.copy(), dialect=self.dialect).alias_or_name}\n        for i, join in enumerate(this.args.get(\"joins\") or []):\n            table = join.this\n            normalized_table = table.copy()\n            normalized_table.meta[\"maybe_column\"] = True\n            normalized_table = _norm(normalized_table, dialect=self.dialect)\n\n            if isinstance(table, exp.Table) and not join.args.get(\"on\"):\n                if normalized_table.parts[0].name in refs:\n                    table_as_column = table.to_column()\n                    unnest = exp.Unnest(expressions=[table_as_column])\n\n                    # Table.to_column creates a parent Alias node that we want to convert to\n                    # a TableAlias and attach to the Unnest, so it matches the parser's output\n                    if isinstance(table.args.get(\"alias\"), exp.TableAlias):\n                        table_as_column.replace(table_as_column.this)\n                        exp.alias_(unnest, None, table=[table.args[\"alias\"].this], copy=False)\n\n                    table.replace(unnest)\n\n            refs.add(normalized_table.alias_or_name)\n\n        return this\n\n    @t.overload\n    def _parse_query_modifiers(self, this: E) -> E: ...\n\n    @t.overload\n    def _parse_query_modifiers(self, this: None) -> None: ...\n\n    def _parse_query_modifiers(self, this):\n        if isinstance(this, self.MODIFIABLES):\n            for join in self._parse_joins():\n                this.append(\"joins\", join)\n            for lateral in iter(self._parse_lateral, None):\n                this.append(\"laterals\", lateral)\n\n            while True:\n                if self._match_set(self.QUERY_MODIFIER_PARSERS, advance=False):\n                    modifier_token = self._curr\n                    parser = self.QUERY_MODIFIER_PARSERS[modifier_token.token_type]\n                    key, expression = parser(self)\n\n                    if expression:\n                        if this.args.get(key):\n                            self.raise_error(\n                                f\"Found multiple '{modifier_token.text.upper()}' clauses\",\n                                token=modifier_token,\n                            )\n\n                        this.set(key, expression)\n                        if key == \"limit\":\n                            offset = expression.args.get(\"offset\")\n                            expression.set(\"offset\", None)\n\n                            if offset:\n                                offset = exp.Offset(expression=offset)\n                                this.set(\"offset\", offset)\n\n                                limit_by_expressions = expression.expressions\n                                expression.set(\"expressions\", None)\n                                offset.set(\"expressions\", limit_by_expressions)\n                        continue\n                break\n\n        if self.SUPPORTS_IMPLICIT_UNNEST and this and this.args.get(\"from_\"):\n            this = self._implicit_unnests_to_explicit(this)\n\n        return this\n\n    def _parse_hint_fallback_to_string(self) -> t.Optional[exp.Hint]:\n        start = self._curr\n        while self._curr:\n            self._advance()\n\n        end = self._tokens[self._index - 1]\n        return exp.Hint(expressions=[self._find_sql(start, end)])\n\n    def _parse_hint_function_call(self) -> t.Optional[exp.Expr]:\n        return self._parse_function_call()\n\n    def _parse_hint_body(self) -> t.Optional[exp.Hint]:\n        start_index = self._index\n        should_fallback_to_string = False\n\n        hints = []\n        try:\n            for hint in iter(\n                lambda: self._parse_csv(\n                    lambda: self._parse_hint_function_call() or self._parse_var(upper=True),\n                ),\n                [],\n            ):\n                hints.extend(hint)\n        except ParseError:\n            should_fallback_to_string = True\n\n        if should_fallback_to_string or self._curr:\n            self._retreat(start_index)\n            return self._parse_hint_fallback_to_string()\n\n        return self.expression(exp.Hint(expressions=hints))\n\n    def _parse_hint(self) -> t.Optional[exp.Hint]:\n        if self._match(TokenType.HINT) and self._prev_comments:\n            return exp.maybe_parse(self._prev_comments[0], into=exp.Hint, dialect=self.dialect)\n\n        return None\n\n    def _parse_into(self) -> t.Optional[exp.Into]:\n        if not self._match(TokenType.INTO):\n            return None\n\n        temp = self._match(TokenType.TEMPORARY)\n        unlogged = self._match_text_seq(\"UNLOGGED\")\n        self._match(TokenType.TABLE)\n\n        return self.expression(\n            exp.Into(this=self._parse_table(schema=True), temporary=temp, unlogged=unlogged)\n        )\n\n    def _parse_from(\n        self,\n        joins: bool = False,\n        skip_from_token: bool = False,\n        consume_pipe: bool = False,\n    ) -> t.Optional[exp.From]:\n        if not skip_from_token and not self._match(TokenType.FROM):\n            return None\n\n        comments = self._prev_comments\n        return self.expression(\n            exp.From(this=self._parse_table(joins=joins, consume_pipe=consume_pipe)),\n            comments=comments,\n        )\n\n    def _parse_match_recognize_measure(self) -> exp.MatchRecognizeMeasure:\n        return self.expression(\n            exp.MatchRecognizeMeasure(\n                window_frame=self._match_texts((\"FINAL\", \"RUNNING\")) and self._prev.text.upper(),\n                this=self._parse_expression(),\n            )\n        )\n\n    def _parse_match_recognize(self) -> t.Optional[exp.MatchRecognize]:\n        if not self._match(TokenType.MATCH_RECOGNIZE):\n            return None\n\n        self._match_l_paren()\n\n        partition = self._parse_partition_by()\n        order = self._parse_order()\n\n        measures = (\n            self._parse_csv(self._parse_match_recognize_measure)\n            if self._match_text_seq(\"MEASURES\")\n            else None\n        )\n\n        if self._match_text_seq(\"ONE\", \"ROW\", \"PER\", \"MATCH\"):\n            rows = exp.var(\"ONE ROW PER MATCH\")\n        elif self._match_text_seq(\"ALL\", \"ROWS\", \"PER\", \"MATCH\"):\n            text = \"ALL ROWS PER MATCH\"\n            if self._match_text_seq(\"SHOW\", \"EMPTY\", \"MATCHES\"):\n                text += \" SHOW EMPTY MATCHES\"\n            elif self._match_text_seq(\"OMIT\", \"EMPTY\", \"MATCHES\"):\n                text += \" OMIT EMPTY MATCHES\"\n            elif self._match_text_seq(\"WITH\", \"UNMATCHED\", \"ROWS\"):\n                text += \" WITH UNMATCHED ROWS\"\n            rows = exp.var(text)\n        else:\n            rows = None\n\n        if self._match_text_seq(\"AFTER\", \"MATCH\", \"SKIP\"):\n            text = \"AFTER MATCH SKIP\"\n            if self._match_text_seq(\"PAST\", \"LAST\", \"ROW\"):\n                text += \" PAST LAST ROW\"\n            elif self._match_text_seq(\"TO\", \"NEXT\", \"ROW\"):\n                text += \" TO NEXT ROW\"\n            elif self._match_text_seq(\"TO\", \"FIRST\"):\n                text += f\" TO FIRST {self._advance_any().text}\"  # type: ignore\n            elif self._match_text_seq(\"TO\", \"LAST\"):\n                text += f\" TO LAST {self._advance_any().text}\"  # type: ignore\n            after = exp.var(text)\n        else:\n            after = None\n\n        if self._match_text_seq(\"PATTERN\"):\n            self._match_l_paren()\n\n            if not self._curr:\n                self.raise_error(\"Expecting )\", self._curr)\n\n            paren = 1\n            start = self._curr\n\n            while self._curr and paren > 0:\n                if self._curr.token_type == TokenType.L_PAREN:\n                    paren += 1\n                if self._curr.token_type == TokenType.R_PAREN:\n                    paren -= 1\n\n                end = self._prev\n                self._advance()\n\n            if paren > 0:\n                self.raise_error(\"Expecting )\", self._curr)\n\n            pattern = exp.var(self._find_sql(start, end))\n        else:\n            pattern = None\n\n        define = (\n            self._parse_csv(self._parse_name_as_expression)\n            if self._match_text_seq(\"DEFINE\")\n            else None\n        )\n\n        self._match_r_paren()\n\n        return self.expression(\n            exp.MatchRecognize(\n                partition_by=partition,\n                order=order,\n                measures=measures,\n                rows=rows,\n                after=after,\n                pattern=pattern,\n                define=define,\n                alias=self._parse_table_alias(),\n            )\n        )\n\n    def _parse_lateral(self) -> t.Optional[exp.Lateral]:\n        cross_apply: t.Optional[bool] = None\n        if self._match_pair(TokenType.CROSS, TokenType.APPLY):\n            cross_apply = True\n        elif self._match_pair(TokenType.OUTER, TokenType.APPLY):\n            cross_apply = False\n\n        if cross_apply is not None:\n            this = self._parse_select(table=True)\n            view = None\n            outer = None\n        elif self._match(TokenType.LATERAL):\n            this = self._parse_select(table=True)\n            view = self._match(TokenType.VIEW)\n            outer = self._match(TokenType.OUTER)\n        else:\n            return None\n\n        if not this:\n            this = (\n                self._parse_unnest()\n                or self._parse_function()\n                or self._parse_id_var(any_token=False)\n            )\n\n            while self._match(TokenType.DOT):\n                this = exp.Dot(\n                    this=this,\n                    expression=self._parse_function() or self._parse_id_var(any_token=False),\n                )\n\n        ordinality: t.Optional[bool] = None\n\n        if view:\n            table = self._parse_id_var(any_token=False)\n            columns = self._parse_csv(self._parse_id_var) if self._match(TokenType.ALIAS) else []\n            table_alias: t.Optional[exp.TableAlias] = self.expression(\n                exp.TableAlias(this=table, columns=columns)\n            )\n        elif isinstance(this, (exp.Subquery, exp.Unnest)) and this.alias:\n            # We move the alias from the lateral's child node to the lateral itself\n            table_alias = this.args[\"alias\"].pop()\n        else:\n            ordinality = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)\n            table_alias = self._parse_table_alias()\n\n        return self.expression(\n            exp.Lateral(\n                this=this,\n                view=view,\n                outer=outer,\n                alias=table_alias,\n                cross_apply=cross_apply,\n                ordinality=ordinality,\n            )\n        )\n\n    def _parse_stream(self) -> t.Optional[exp.Stream]:\n        index = self._index\n        if self._match(TokenType.STREAM):\n            if this := self._try_parse(self._parse_table):\n                return self.expression(exp.Stream(this=this))\n            self._retreat(index)\n        return None\n\n    def _parse_join_parts(\n        self,\n    ) -> t.Tuple[t.Optional[Token], t.Optional[Token], t.Optional[Token]]:\n        return (\n            self._prev if self._match_set(self.JOIN_METHODS) else None,\n            self._prev if self._match_set(self.JOIN_SIDES) else None,\n            self._prev if self._match_set(self.JOIN_KINDS) else None,\n        )\n\n    def _parse_using_identifiers(self) -> t.List[exp.Expr]:\n        def _parse_column_as_identifier() -> t.Optional[exp.Expr]:\n            this = self._parse_column()\n            if isinstance(this, exp.Column):\n                return this.this\n            return this\n\n        return self._parse_wrapped_csv(_parse_column_as_identifier, optional=True)\n\n    def _parse_join(\n        self, skip_join_token: bool = False, parse_bracket: bool = False\n    ) -> t.Optional[exp.Join]:\n        if self._match(TokenType.COMMA):\n            table = self._try_parse(self._parse_table)\n            cross_join = self.expression(exp.Join(this=table)) if table else None\n\n            if cross_join and self.JOINS_HAVE_EQUAL_PRECEDENCE:\n                cross_join.set(\"kind\", \"CROSS\")\n\n            return cross_join\n\n        index = self._index\n        method, side, kind = self._parse_join_parts()\n        directed = self._match_text_seq(\"DIRECTED\")\n        hint = self._prev.text if self._match_texts(self.JOIN_HINTS) else None\n        join = self._match(TokenType.JOIN) or (kind and kind.token_type == TokenType.STRAIGHT_JOIN)\n        join_comments = self._prev_comments\n\n        if not skip_join_token and not join:\n            self._retreat(index)\n            kind = None\n            method = None\n            side = None\n\n        outer_apply = self._match_pair(TokenType.OUTER, TokenType.APPLY, False)\n        cross_apply = self._match_pair(TokenType.CROSS, TokenType.APPLY, False)\n\n        if not skip_join_token and not join and not outer_apply and not cross_apply:\n            return None\n\n        kwargs: t.Dict[str, t.Any] = {\"this\": self._parse_table(parse_bracket=parse_bracket)}\n        if kind and kind.token_type == TokenType.ARRAY and self._match(TokenType.COMMA):\n            kwargs[\"expressions\"] = self._parse_csv(\n                lambda: self._parse_table(parse_bracket=parse_bracket)\n            )\n\n        if method:\n            kwargs[\"method\"] = method.text.upper()\n        if side:\n            kwargs[\"side\"] = side.text.upper()\n        if kind:\n            kwargs[\"kind\"] = kind.text.upper()\n        if hint:\n            kwargs[\"hint\"] = hint\n\n        if self._match(TokenType.MATCH_CONDITION):\n            kwargs[\"match_condition\"] = self._parse_wrapped(self._parse_comparison)\n\n        if self._match(TokenType.ON):\n            kwargs[\"on\"] = self._parse_disjunction()\n        elif self._match(TokenType.USING):\n            kwargs[\"using\"] = self._parse_using_identifiers()\n        elif (\n            not method\n            and not (outer_apply or cross_apply)\n            and not isinstance(kwargs[\"this\"], exp.Unnest)\n            and not (kind and kind.token_type in (TokenType.CROSS, TokenType.ARRAY))\n        ):\n            index = self._index\n            joins: t.Optional[list] = list(self._parse_joins())\n\n            if joins and self._match(TokenType.ON):\n                kwargs[\"on\"] = self._parse_disjunction()\n            elif joins and self._match(TokenType.USING):\n                kwargs[\"using\"] = self._parse_using_identifiers()\n            else:\n                joins = None\n                self._retreat(index)\n\n            kwargs[\"this\"].set(\"joins\", joins if joins else None)\n\n        kwargs[\"pivots\"] = self._parse_pivots()\n\n        comments = [c for token in (method, side, kind) if token for c in token.comments]\n        comments = (join_comments or []) + comments\n\n        if (\n            self.ADD_JOIN_ON_TRUE\n            and not kwargs.get(\"on\")\n            and not kwargs.get(\"using\")\n            and not kwargs.get(\"method\")\n            and kwargs.get(\"kind\") in (None, \"INNER\", \"OUTER\")\n        ):\n            kwargs[\"on\"] = exp.true()\n\n        if directed:\n            kwargs[\"directed\"] = directed\n\n        return self.expression(exp.Join(**kwargs), comments=comments)\n\n    def _parse_opclass(self) -> t.Optional[exp.Expr]:\n        this = self._parse_disjunction()\n\n        if self._match_texts(self.OPCLASS_FOLLOW_KEYWORDS, advance=False):\n            return this\n\n        if not self._match_set(self.OPTYPE_FOLLOW_TOKENS, advance=False):\n            return self.expression(exp.Opclass(this=this, expression=self._parse_table_parts()))\n\n        return this\n\n    def _parse_index_params(self) -> exp.IndexParameters:\n        using = self._parse_var(any_token=True) if self._match(TokenType.USING) else None\n\n        if self._match(TokenType.L_PAREN, advance=False):\n            columns = self._parse_wrapped_csv(self._parse_with_operator)\n        else:\n            columns = None\n\n        include = self._parse_wrapped_id_vars() if self._match_text_seq(\"INCLUDE\") else None\n        partition_by = self._parse_partition_by()\n        with_storage = self._match(TokenType.WITH) and self._parse_wrapped_properties()\n        tablespace = (\n            self._parse_var(any_token=True)\n            if self._match_text_seq(\"USING\", \"INDEX\", \"TABLESPACE\")\n            else None\n        )\n        where = self._parse_where()\n\n        on = self._parse_field() if self._match(TokenType.ON) else None\n\n        return self.expression(\n            exp.IndexParameters(\n                using=using,\n                columns=columns,\n                include=include,\n                partition_by=partition_by,\n                where=where,\n                with_storage=with_storage,\n                tablespace=tablespace,\n                on=on,\n            )\n        )\n\n    def _parse_index(\n        self, index: t.Optional[exp.Expr] = None, anonymous: bool = False\n    ) -> t.Optional[exp.Index]:\n        if index or anonymous:\n            unique = None\n            primary = None\n            amp = None\n\n            self._match(TokenType.ON)\n            self._match(TokenType.TABLE)  # hive\n            table = self._parse_table_parts(schema=True)\n        else:\n            unique = self._match(TokenType.UNIQUE)\n            primary = self._match_text_seq(\"PRIMARY\")\n            amp = self._match_text_seq(\"AMP\")\n\n            if not self._match(TokenType.INDEX):\n                return None\n\n            index = self._parse_id_var()\n            table = None\n\n        params = self._parse_index_params()\n\n        return self.expression(\n            exp.Index(\n                this=index, table=table, unique=unique, primary=primary, amp=amp, params=params\n            )\n        )\n\n    def _parse_table_hints(self) -> t.Optional[t.List[exp.Expr]]:\n        hints: t.List[exp.Expr] = []\n        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):\n            # https://learn.microsoft.com/en-us/sql/t-sql/queries/hints-transact-sql-table?view=sql-server-ver16\n            hints.append(\n                self.expression(\n                    exp.WithTableHint(\n                        expressions=self._parse_csv(\n                            lambda: self._parse_function() or self._parse_var(any_token=True)\n                        )\n                    )\n                )\n            )\n            self._match_r_paren()\n        else:\n            # https://dev.mysql.com/doc/refman/8.0/en/index-hints.html\n            while self._match_set(self.TABLE_INDEX_HINT_TOKENS):\n                hint = exp.IndexTableHint(this=self._prev.text.upper())\n\n                self._match_set((TokenType.INDEX, TokenType.KEY))\n                if self._match(TokenType.FOR):\n                    hint.set(\"target\", self._advance_any() and self._prev.text.upper())\n\n                hint.set(\"expressions\", self._parse_wrapped_id_vars())\n                hints.append(hint)\n\n        return hints or None\n\n    def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expr]:\n        return (\n            (not schema and self._parse_function(optional_parens=False))\n            or self._parse_id_var(any_token=False)\n            or self._parse_string_as_identifier()\n            or self._parse_placeholder()\n        )\n\n    def _parse_table_parts_fast(self) -> t.Optional[exp.Table]:\n        index = self._index\n        parts: t.Optional[t.List[exp.Identifier]] = None\n        all_comments: t.Optional[t.List[str]] = None\n\n        while self._match_set(self.IDENTIFIER_TOKENS):\n            token = self._prev\n            comments = self._prev_comments\n\n            has_dot = self._match(TokenType.DOT)\n            curr_tt = self._curr.token_type\n\n            if not has_dot:\n                if curr_tt in self.TABLE_POSTFIX_TOKENS:\n                    self._retreat(index)\n                    return None\n            elif curr_tt not in self.IDENTIFIER_TOKENS:\n                self._retreat(index)\n                return None\n\n            if parts is None:\n                parts = []\n\n            if comments:\n                if all_comments is None:\n                    all_comments = []\n                all_comments.extend(comments)\n                self._prev_comments = []\n\n            parts.append(\n                self.expression(\n                    exp.Identifier(\n                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER\n                    ),\n                    token,\n                )\n            )\n\n            if not has_dot:\n                break\n\n        if parts is None:\n            return None\n\n        n = len(parts)\n\n        if n == 1:\n            table: exp.Table = exp.Table(this=parts[0])\n        elif n == 2:\n            table = exp.Table(this=parts[1], db=parts[0])\n        elif n >= 3:\n            this: exp.Identifier | exp.Dot = parts[2]\n            for i in range(3, n):\n                this = exp.Dot(this=this, expression=parts[i])\n\n            table = exp.Table(this=this, db=parts[1], catalog=parts[0])\n\n        if table is None:\n            self._retreat(index)\n        elif all_comments:\n            table.add_comments(all_comments)\n        return table\n\n    def _parse_table_parts(\n        self,\n        schema: bool = False,\n        is_db_reference: bool = False,\n        wildcard: bool = False,\n        fast: bool = False,\n    ) -> t.Optional[exp.Table | exp.Dot]:\n        if fast:\n            return self._parse_table_parts_fast()\n\n        catalog: t.Optional[exp.Expr | str] = None\n        db: t.Optional[exp.Expr | str] = None\n        table: t.Optional[exp.Expr | str] = self._parse_table_part(schema=schema)\n\n        while self._match(TokenType.DOT):\n            if catalog:\n                # This allows nesting the table in arbitrarily many dot expressions if needed\n                table = self.expression(\n                    exp.Dot(this=table, expression=self._parse_table_part(schema=schema))\n                )\n            else:\n                catalog = db\n                db = table\n                # \"\" used for tsql FROM a..b case\n                table = self._parse_table_part(schema=schema) or \"\"\n\n        if (\n            wildcard\n            and self._is_connected()\n            and (isinstance(table, exp.Identifier) or not table)\n            and self._match(TokenType.STAR)\n        ):\n            if isinstance(table, exp.Identifier):\n                table.args[\"this\"] += \"*\"\n            else:\n                table = exp.Identifier(this=\"*\")\n\n        if is_db_reference:\n            catalog = db\n            db = table\n            table = None\n\n        if not table and not is_db_reference:\n            self.raise_error(f\"Expected table name but got {self._curr}\")\n        if not db and is_db_reference:\n            self.raise_error(f\"Expected database name but got {self._curr}\")\n\n        table = self.expression(exp.Table(this=table, db=db, catalog=catalog))\n\n        # Bubble up comments from identifier parts to the Table\n        comments = []\n        for part in table.parts:\n            if part_comments := part.pop_comments():\n                comments.extend(part_comments)\n        if comments:\n            table.add_comments(comments)\n\n        changes = self._parse_changes()\n        if changes:\n            table.set(\"changes\", changes)\n\n        at_before = self._parse_historical_data()\n        if at_before:\n            table.set(\"when\", at_before)\n\n        pivots = self._parse_pivots()\n        if pivots:\n            table.set(\"pivots\", pivots)\n\n        return table\n\n    def _parse_table(\n        self,\n        schema: bool = False,\n        joins: bool = False,\n        alias_tokens: t.Optional[t.Collection[TokenType]] = None,\n        parse_bracket: bool = False,\n        is_db_reference: bool = False,\n        parse_partition: bool = False,\n        consume_pipe: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        if not schema and not is_db_reference and not consume_pipe and not joins:\n            index = self._index\n            table = self._parse_table_parts(fast=True)\n\n            if table is not None:\n                curr_tt = self._curr.token_type\n                next_tt = self._next.token_type\n\n                fast_terminators = self.TABLE_TERMINATORS\n\n                # only return the table if we're sure there are no other operators\n                # MATCH_CONDITION is a special case because it accepts any alias before it like LIMIT\n                if curr_tt in fast_terminators and next_tt != TokenType.MATCH_CONDITION:\n                    return table\n\n                postfix_tokens = self.TABLE_POSTFIX_TOKENS\n\n                if curr_tt not in postfix_tokens and next_tt not in postfix_tokens:\n                    if alias := self._parse_table_alias(\n                        alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS\n                    ):\n                        table.set(\"alias\", alias)\n\n                    if self._curr.token_type in fast_terminators:\n                        return table\n\n                self._retreat(index)\n\n        if stream := self._parse_stream():\n            return stream\n\n        if lateral := self._parse_lateral():\n            return lateral\n\n        if unnest := self._parse_unnest():\n            return unnest\n\n        if values := self._parse_derived_table_values():\n            return values\n\n        if subquery := self._parse_select(table=True, consume_pipe=consume_pipe):\n            if not subquery.args.get(\"pivots\"):\n                subquery.set(\"pivots\", self._parse_pivots())\n            return subquery\n\n        bracket = parse_bracket and self._parse_bracket(None)\n        bracket = self.expression(exp.Table(this=bracket)) if bracket else None\n\n        rows_from_tables = (\n            self._parse_wrapped_csv(self._parse_table)\n            if self._match_text_seq(\"ROWS\", \"FROM\")\n            else None\n        )\n        rows_from = (\n            self.expression(exp.Table(rows_from=rows_from_tables)) if rows_from_tables else None\n        )\n\n        only = self._match(TokenType.ONLY)\n\n        this = t.cast(\n            exp.Expr,\n            bracket\n            or rows_from\n            or self._parse_bracket(\n                self._parse_table_parts(schema=schema, is_db_reference=is_db_reference)\n            ),\n        )\n\n        if only:\n            this.set(\"only\", only)\n\n        # Postgres supports a wildcard (table) suffix operator, which is a no-op in this context\n        self._match(TokenType.STAR)\n\n        parse_partition = parse_partition or self.SUPPORTS_PARTITION_SELECTION\n        if parse_partition and self._match(TokenType.PARTITION, advance=False):\n            this.set(\"partition\", self._parse_partition())\n\n        if schema:\n            return self._parse_schema(this=this)\n\n        if self.dialect.ALIAS_POST_VERSION:\n            this.set(\"version\", self._parse_version())\n\n        if self.dialect.ALIAS_POST_TABLESAMPLE:\n            this.set(\"sample\", self._parse_table_sample())\n\n        alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)\n        if alias:\n            this.set(\"alias\", alias)\n\n        if self._match(TokenType.INDEXED_BY):\n            this.set(\"indexed\", self._parse_table_parts())\n        elif self._match_text_seq(\"NOT\", \"INDEXED\"):\n            this.set(\"indexed\", False)\n\n        if isinstance(this, exp.Table) and self._match_text_seq(\"AT\"):\n            return self.expression(\n                exp.AtIndex(this=this.to_column(copy=False), expression=self._parse_id_var())\n            )\n\n        this.set(\"hints\", self._parse_table_hints())\n\n        if not this.args.get(\"pivots\"):\n            this.set(\"pivots\", self._parse_pivots())\n\n        if not self.dialect.ALIAS_POST_TABLESAMPLE:\n            this.set(\"sample\", self._parse_table_sample())\n\n        if not self.dialect.ALIAS_POST_VERSION:\n            this.set(\"version\", self._parse_version())\n\n        if joins:\n            for join in self._parse_joins():\n                this.append(\"joins\", join)\n\n        if self._match_pair(TokenType.WITH, TokenType.ORDINALITY):\n            this.set(\"ordinality\", True)\n            this.set(\"alias\", self._parse_table_alias())\n\n        return this\n\n    def _parse_version(self) -> t.Optional[exp.Version]:\n        if self._match(TokenType.TIMESTAMP_SNAPSHOT):\n            this = \"TIMESTAMP\"\n        elif self._match(TokenType.VERSION_SNAPSHOT):\n            this = \"VERSION\"\n        else:\n            return None\n\n        if self._match_set((TokenType.FROM, TokenType.BETWEEN)):\n            kind = self._prev.text.upper()\n            start = self._parse_bitwise()\n            self._match_texts((\"TO\", \"AND\"))\n            end = self._parse_bitwise()\n            expression: t.Optional[exp.Expr] = self.expression(exp.Tuple(expressions=[start, end]))\n        elif self._match_text_seq(\"CONTAINED\", \"IN\"):\n            kind = \"CONTAINED IN\"\n            expression = self.expression(\n                exp.Tuple(expressions=self._parse_wrapped_csv(self._parse_bitwise))\n            )\n        elif self._match(TokenType.ALL):\n            kind = \"ALL\"\n            expression = None\n        else:\n            self._match_text_seq(\"AS\", \"OF\")\n            kind = \"AS OF\"\n            expression = self._parse_type()\n\n        return self.expression(exp.Version(this=this, expression=expression, kind=kind))\n\n    def _parse_historical_data(self) -> t.Optional[exp.HistoricalData]:\n        # https://docs.snowflake.com/en/sql-reference/constructs/at-before\n        index = self._index\n        historical_data = None\n        if self._match_texts(self.HISTORICAL_DATA_PREFIX):\n            this = self._prev.text.upper()\n            kind = (\n                self._match(TokenType.L_PAREN)\n                and self._match_texts(self.HISTORICAL_DATA_KIND)\n                and self._prev.text.upper()\n            )\n            expression = self._match(TokenType.FARROW) and self._parse_bitwise()\n\n            if expression:\n                self._match_r_paren()\n                historical_data = self.expression(\n                    exp.HistoricalData(this=this, kind=kind, expression=expression)\n                )\n            else:\n                self._retreat(index)\n\n        return historical_data\n\n    def _parse_changes(self) -> t.Optional[exp.Changes]:\n        if not self._match_text_seq(\"CHANGES\", \"(\", \"INFORMATION\", \"=>\"):\n            return None\n\n        information = self._parse_var(any_token=True)\n        self._match_r_paren()\n\n        return self.expression(\n            exp.Changes(\n                information=information,\n                at_before=self._parse_historical_data(),\n                end=self._parse_historical_data(),\n            )\n        )\n\n    def _parse_unnest(self, with_alias: bool = True) -> t.Optional[exp.Unnest]:\n        if not self._match_pair(TokenType.UNNEST, TokenType.L_PAREN, advance=False):\n            return None\n\n        self._advance()\n\n        expressions = self._parse_wrapped_csv(self._parse_equality)\n        offset: t.Union[bool, exp.Expr] = self._match_pair(TokenType.WITH, TokenType.ORDINALITY)\n\n        alias = self._parse_table_alias() if with_alias else None\n\n        if alias:\n            if self.dialect.UNNEST_COLUMN_ONLY:\n                if alias.args.get(\"columns\"):\n                    self.raise_error(\"Unexpected extra column alias in unnest.\")\n\n                alias.set(\"columns\", [alias.this])\n                alias.set(\"this\", None)\n\n            columns = alias.args.get(\"columns\") or []\n            if offset and len(expressions) < len(columns):\n                offset = columns.pop()\n\n        if not offset and self._match_pair(TokenType.WITH, TokenType.OFFSET):\n            self._match(TokenType.ALIAS)\n            offset = self._parse_id_var(\n                any_token=False, tokens=self.UNNEST_OFFSET_ALIAS_TOKENS\n            ) or exp.to_identifier(\"offset\")\n\n        return self.expression(exp.Unnest(expressions=expressions, alias=alias, offset=offset))\n\n    def _parse_derived_table_values(self) -> t.Optional[exp.Values]:\n        is_derived = self._match_pair(TokenType.L_PAREN, TokenType.VALUES)\n        if not is_derived and not (\n            # ClickHouse's `FORMAT Values` is equivalent to `VALUES`\n            self._match_text_seq(\"VALUES\") or self._match_text_seq(\"FORMAT\", \"VALUES\")\n        ):\n            return None\n\n        expressions = self._parse_csv(self._parse_value)\n        alias = self._parse_table_alias()\n\n        if is_derived:\n            self._match_r_paren()\n\n        return self.expression(\n            exp.Values(expressions=expressions, alias=alias or self._parse_table_alias())\n        )\n\n    def _parse_table_sample(self, as_modifier: bool = False) -> t.Optional[exp.TableSample]:\n        if not self._match(TokenType.TABLE_SAMPLE) and not (\n            as_modifier and self._match_text_seq(\"USING\", \"SAMPLE\")\n        ):\n            return None\n\n        bucket_numerator = None\n        bucket_denominator = None\n        bucket_field = None\n        percent = None\n        size = None\n        seed = None\n\n        method = self._parse_var(tokens=(TokenType.ROW,), upper=True)\n        matched_l_paren = self._match(TokenType.L_PAREN)\n\n        if self.TABLESAMPLE_CSV:\n            num = None\n            expressions = self._parse_csv(self._parse_primary)\n        else:\n            expressions = None\n            num = (\n                self._parse_factor()\n                if self._match(TokenType.NUMBER, advance=False)\n                else self._parse_primary() or self._parse_placeholder()\n            )\n\n        if self._match_text_seq(\"BUCKET\"):\n            bucket_numerator = self._parse_number()\n            self._match_text_seq(\"OUT\", \"OF\")\n            bucket_denominator = bucket_denominator = self._parse_number()\n            self._match(TokenType.ON)\n            bucket_field = self._parse_field()\n        elif self._match_set((TokenType.PERCENT, TokenType.MOD)):\n            percent = num\n        elif self._match(TokenType.ROWS) or not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT:\n            size = num\n        else:\n            percent = num\n\n        if matched_l_paren:\n            self._match_r_paren()\n\n        if self._match(TokenType.L_PAREN):\n            method = self._parse_var(upper=True)\n            seed = self._match(TokenType.COMMA) and self._parse_number()\n            self._match_r_paren()\n        elif self._match_texts((\"SEED\", \"REPEATABLE\")):\n            seed = self._parse_wrapped(self._parse_number)\n\n        if not method and self.DEFAULT_SAMPLING_METHOD:\n            method = exp.var(self.DEFAULT_SAMPLING_METHOD)\n\n        return self.expression(\n            exp.TableSample(\n                expressions=expressions,\n                method=method,\n                bucket_numerator=bucket_numerator,\n                bucket_denominator=bucket_denominator,\n                bucket_field=bucket_field,\n                percent=percent,\n                size=size,\n                seed=seed,\n            )\n        )\n\n    def _parse_pivots(self) -> t.Optional[t.List[exp.Pivot]]:\n        return list(iter(self._parse_pivot, None)) or None\n\n    def _parse_joins(self) -> t.Iterator[exp.Join]:\n        return iter(self._parse_join, None)\n\n    def _parse_unpivot_columns(self) -> t.Optional[exp.UnpivotColumns]:\n        if not self._match(TokenType.INTO):\n            return None\n\n        return self.expression(\n            exp.UnpivotColumns(\n                this=self._match_text_seq(\"NAME\") and self._parse_column(),\n                expressions=self._match_text_seq(\"VALUE\") and self._parse_csv(self._parse_column),\n            )\n        )\n\n    # https://duckdb.org/docs/sql/statements/pivot\n    def _parse_simplified_pivot(self, is_unpivot: t.Optional[bool] = None) -> exp.Pivot:\n        def _parse_on() -> t.Optional[exp.Expr]:\n            this = self._parse_bitwise()\n\n            if self._match(TokenType.IN):\n                # PIVOT ... ON col IN (row_val1, row_val2)\n                return self._parse_in(this)\n            if self._match(TokenType.ALIAS, advance=False):\n                # UNPIVOT ... ON (col1, col2, col3) AS row_val\n                return self._parse_alias(this)\n\n            return this\n\n        this = self._parse_table()\n        expressions = self._match(TokenType.ON) and self._parse_csv(_parse_on)\n        into = self._parse_unpivot_columns()\n        using = self._match(TokenType.USING) and self._parse_csv(\n            lambda: self._parse_alias(self._parse_column())\n        )\n        group = self._parse_group()\n\n        return self.expression(\n            exp.Pivot(\n                this=this,\n                expressions=expressions,\n                using=using,\n                group=group,\n                unpivot=is_unpivot,\n                into=into,\n            )\n        )\n\n    def _parse_pivot_in(self) -> exp.In:\n        def _parse_aliased_expression() -> t.Optional[exp.Expr]:\n            this = self._parse_select_or_expression()\n\n            self._match(TokenType.ALIAS)\n            alias = self._parse_bitwise()\n            if alias:\n                if isinstance(alias, exp.Column) and not alias.db:\n                    alias = alias.this\n                return self.expression(exp.PivotAlias(this=this, alias=alias))\n\n            return this\n\n        value = self._parse_column()\n\n        if not self._match(TokenType.IN):\n            self.raise_error(\"Expecting IN\")\n\n        if self._match(TokenType.L_PAREN):\n            if self._match(TokenType.ANY):\n                exprs: t.List[exp.Expr] = ensure_list(exp.PivotAny(this=self._parse_order()))\n            else:\n                exprs = self._parse_csv(_parse_aliased_expression)\n            self._match_r_paren()\n            return self.expression(exp.In(this=value, expressions=exprs))\n\n        return self.expression(exp.In(this=value, field=self._parse_id_var()))\n\n    def _parse_pivot_aggregation(self) -> t.Optional[exp.Expr]:\n        func = self._parse_function()\n        if not func:\n            if self._prev.token_type == TokenType.COMMA:\n                return None\n            self.raise_error(\"Expecting an aggregation function in PIVOT\")\n\n        return self._parse_alias(func)\n\n    def _parse_pivot(self) -> t.Optional[exp.Pivot]:\n        index = self._index\n        include_nulls = None\n\n        if self._match(TokenType.PIVOT):\n            unpivot = False\n        elif self._match(TokenType.UNPIVOT):\n            unpivot = True\n\n            # https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot.html#syntax\n            if self._match_text_seq(\"INCLUDE\", \"NULLS\"):\n                include_nulls = True\n            elif self._match_text_seq(\"EXCLUDE\", \"NULLS\"):\n                include_nulls = False\n        else:\n            return None\n\n        expressions = []\n\n        if not self._match(TokenType.L_PAREN):\n            self._retreat(index)\n            return None\n\n        if unpivot:\n            expressions = self._parse_csv(self._parse_column)\n        else:\n            expressions = self._parse_csv(self._parse_pivot_aggregation)\n\n        if not expressions:\n            self.raise_error(\"Failed to parse PIVOT's aggregation list\")\n\n        if not self._match(TokenType.FOR):\n            self.raise_error(\"Expecting FOR\")\n\n        fields = []\n        while True:\n            field = self._try_parse(self._parse_pivot_in)\n            if not field:\n                break\n            fields.append(field)\n\n        default_on_null = self._match_text_seq(\"DEFAULT\", \"ON\", \"NULL\") and self._parse_wrapped(\n            self._parse_bitwise\n        )\n\n        group = self._parse_group()\n\n        self._match_r_paren()\n\n        pivot = self.expression(\n            exp.Pivot(\n                expressions=expressions,\n                fields=fields,\n                unpivot=unpivot,\n                include_nulls=include_nulls,\n                default_on_null=default_on_null,\n                group=group,\n            )\n        )\n\n        if not self._match_set((TokenType.PIVOT, TokenType.UNPIVOT), advance=False):\n            pivot.set(\"alias\", self._parse_table_alias())\n\n        if not unpivot:\n            names = self._pivot_column_names(t.cast(t.List[exp.Expr], expressions))\n\n            columns: t.List[exp.Expr] = []\n            all_fields = []\n            for pivot_field in pivot.fields:\n                pivot_field_expressions = pivot_field.expressions\n\n                # The `PivotAny` expression corresponds to `ANY ORDER BY <column>`; we can't infer in this case.\n                if isinstance(seq_get(pivot_field_expressions, 0), exp.PivotAny):\n                    continue\n\n                all_fields.append(\n                    [\n                        fld.sql() if self.IDENTIFY_PIVOT_STRINGS else fld.alias_or_name\n                        for fld in pivot_field_expressions\n                    ]\n                )\n\n            if all_fields:\n                if names:\n                    all_fields.append(names)\n\n                # Generate all possible combinations of the pivot columns\n                # e.g PIVOT(sum(...) as total FOR year IN (2000, 2010) FOR country IN ('NL', 'US'))\n                # generates the product between [[2000, 2010], ['NL', 'US'], ['total']]\n                for fld_parts_tuple in itertools.product(*all_fields):\n                    fld_parts = list(fld_parts_tuple)\n\n                    if names and self.PREFIXED_PIVOT_COLUMNS:\n                        # Move the \"name\" to the front of the list\n                        fld_parts.insert(0, fld_parts.pop(-1))\n\n                    columns.append(exp.to_identifier(\"_\".join(fld_parts)))\n\n            pivot.set(\"columns\", columns)\n\n        return pivot\n\n    def _pivot_column_names(self, aggregations: t.List[exp.Expr]) -> t.List[str]:\n        return [agg.alias for agg in aggregations if agg.alias]\n\n    def _parse_prewhere(self, skip_where_token: bool = False) -> t.Optional[exp.PreWhere]:\n        if not skip_where_token and not self._match(TokenType.PREWHERE):\n            return None\n\n        comments = self._prev_comments\n        return self.expression(\n            exp.PreWhere(this=self._parse_disjunction()),\n            comments=comments,\n        )\n\n    def _parse_where(self, skip_where_token: bool = False) -> t.Optional[exp.Where]:\n        if not skip_where_token and not self._match(TokenType.WHERE):\n            return None\n\n        comments = self._prev_comments\n        return self.expression(\n            exp.Where(this=self._parse_disjunction()),\n            comments=comments,\n        )\n\n    def _parse_group(self, skip_group_by_token: bool = False) -> t.Optional[exp.Group]:\n        if not skip_group_by_token and not self._match(TokenType.GROUP_BY):\n            return None\n        comments = self._prev_comments\n\n        elements: t.Dict[str, t.Any] = defaultdict(list)\n\n        if self._match(TokenType.ALL):\n            elements[\"all\"] = True\n        elif self._match(TokenType.DISTINCT):\n            elements[\"all\"] = False\n\n        if self._match_set(self.QUERY_MODIFIER_TOKENS, advance=False):\n            return self.expression(exp.Group(**elements), comments=comments)  # type: ignore\n\n        while True:\n            index = self._index\n\n            elements[\"expressions\"].extend(\n                self._parse_csv(\n                    lambda: (\n                        None\n                        if self._match_set((TokenType.CUBE, TokenType.ROLLUP), advance=False)\n                        else self._parse_disjunction()\n                    )\n                )\n            )\n\n            before_with_index = self._index\n            with_prefix = self._match(TokenType.WITH)\n\n            if cube_or_rollup := self._parse_cube_or_rollup(with_prefix=with_prefix):\n                key = \"rollup\" if isinstance(cube_or_rollup, exp.Rollup) else \"cube\"\n                elements[key].append(cube_or_rollup)\n            elif grouping_sets := self._parse_grouping_sets():\n                elements[\"grouping_sets\"].append(grouping_sets)\n            elif self._match_text_seq(\"TOTALS\"):\n                elements[\"totals\"] = True  # type: ignore\n\n            if before_with_index <= self._index <= before_with_index + 1:\n                self._retreat(before_with_index)\n                break\n\n            if index == self._index:\n                break\n\n        return self.expression(exp.Group(**elements), comments=comments)  # type: ignore\n\n    def _parse_cube_or_rollup(self, with_prefix: bool = False) -> t.Optional[exp.Cube | exp.Rollup]:\n        if self._match(TokenType.CUBE):\n            kind: t.Type[exp.Cube | exp.Rollup] = exp.Cube\n        elif self._match(TokenType.ROLLUP):\n            kind = exp.Rollup\n        else:\n            return None\n\n        return self.expression(\n            kind(expressions=[] if with_prefix else self._parse_wrapped_csv(self._parse_bitwise))\n        )\n\n    def _parse_grouping_sets(self) -> t.Optional[exp.GroupingSets]:\n        if self._match(TokenType.GROUPING_SETS):\n            return self.expression(\n                exp.GroupingSets(expressions=self._parse_wrapped_csv(self._parse_grouping_set))\n            )\n        return None\n\n    def _parse_grouping_set(self) -> t.Optional[exp.Expr]:\n        return self._parse_grouping_sets() or self._parse_cube_or_rollup() or self._parse_bitwise()\n\n    def _parse_having(self, skip_having_token: bool = False) -> t.Optional[exp.Having]:\n        if not skip_having_token and not self._match(TokenType.HAVING):\n            return None\n        comments = self._prev_comments\n        return self.expression(\n            exp.Having(this=self._parse_disjunction()),\n            comments=comments,\n        )\n\n    def _parse_qualify(self) -> t.Optional[exp.Qualify]:\n        if not self._match(TokenType.QUALIFY):\n            return None\n        return self.expression(exp.Qualify(this=self._parse_disjunction()))\n\n    def _parse_connect_with_prior(self) -> t.Optional[exp.Expr]:\n        self.NO_PAREN_FUNCTION_PARSERS[\"PRIOR\"] = lambda self: self.expression(\n            exp.Prior(this=self._parse_bitwise())\n        )\n        connect = self._parse_disjunction()\n        self.NO_PAREN_FUNCTION_PARSERS.pop(\"PRIOR\")\n        return connect\n\n    def _parse_connect(self, skip_start_token: bool = False) -> t.Optional[exp.Connect]:\n        if skip_start_token:\n            start = None\n        elif self._match(TokenType.START_WITH):\n            start = self._parse_disjunction()\n        else:\n            return None\n\n        self._match(TokenType.CONNECT_BY)\n        nocycle = self._match_text_seq(\"NOCYCLE\")\n        connect = self._parse_connect_with_prior()\n\n        if not start and self._match(TokenType.START_WITH):\n            start = self._parse_disjunction()\n\n        return self.expression(exp.Connect(start=start, connect=connect, nocycle=nocycle))\n\n    def _parse_name_as_expression(self) -> t.Optional[exp.Expr]:\n        this = self._parse_id_var(any_token=True)\n        if self._match(TokenType.ALIAS):\n            this = self.expression(exp.Alias(alias=this, this=self._parse_disjunction()))\n        return this\n\n    def _parse_interpolate(self) -> t.Optional[t.List[exp.Expr]]:\n        if self._match_text_seq(\"INTERPOLATE\"):\n            return self._parse_wrapped_csv(self._parse_name_as_expression)\n        return None\n\n    def _parse_order(\n        self, this: t.Optional[exp.Expr] = None, skip_order_token: bool = False\n    ) -> t.Optional[exp.Expr]:\n        siblings = None\n        if not skip_order_token and not self._match(TokenType.ORDER_BY):\n            if not self._match(TokenType.ORDER_SIBLINGS_BY):\n                return this\n\n            siblings = True\n\n        comments = self._prev_comments\n        return self.expression(\n            exp.Order(\n                this=this,\n                expressions=self._parse_csv(self._parse_ordered),\n                siblings=siblings,\n            ),\n            comments=comments,\n        )\n\n    def _parse_sort(self, exp_class: t.Type[E], token: TokenType) -> t.Optional[E]:\n        if not self._match(token):\n            return None\n        return self.expression(exp_class(expressions=self._parse_csv(self._parse_ordered)))\n\n    def _parse_ordered(\n        self, parse_method: t.Optional[t.Callable[[], t.Optional[exp.Expr]]] = None\n    ) -> t.Optional[exp.Ordered]:\n        this = parse_method() if parse_method else self._parse_disjunction()\n        if not this:\n            return None\n\n        if this.name.upper() == \"ALL\" and self.dialect.SUPPORTS_ORDER_BY_ALL:\n            this = exp.var(\"ALL\")\n\n        asc = self._match(TokenType.ASC)\n        desc: t.Optional[bool] = True if self._match(TokenType.DESC) else (False if asc else None)\n\n        is_nulls_first = self._match_text_seq(\"NULLS\", \"FIRST\")\n        is_nulls_last = self._match_text_seq(\"NULLS\", \"LAST\")\n\n        nulls_first = is_nulls_first or False\n        explicitly_null_ordered = is_nulls_first or is_nulls_last\n\n        if (\n            not explicitly_null_ordered\n            and (\n                (not desc and self.dialect.NULL_ORDERING == \"nulls_are_small\")\n                or (desc and self.dialect.NULL_ORDERING != \"nulls_are_small\")\n            )\n            and self.dialect.NULL_ORDERING != \"nulls_are_last\"\n        ):\n            nulls_first = True\n\n        if self._match_text_seq(\"WITH\", \"FILL\"):\n            with_fill = self.expression(\n                exp.WithFill(\n                    from_=self._match(TokenType.FROM) and self._parse_bitwise(),\n                    to=self._match_text_seq(\"TO\") and self._parse_bitwise(),\n                    step=self._match_text_seq(\"STEP\") and self._parse_bitwise(),\n                    interpolate=self._parse_interpolate(),\n                )\n            )\n        else:\n            with_fill = None\n\n        return self.expression(\n            exp.Ordered(this=this, desc=desc, nulls_first=nulls_first, with_fill=with_fill)\n        )\n\n    def _parse_limit_options(self) -> t.Optional[exp.LimitOptions]:\n        percent = self._match_set((TokenType.PERCENT, TokenType.MOD))\n        rows = self._match_set((TokenType.ROW, TokenType.ROWS))\n        self._match_text_seq(\"ONLY\")\n        with_ties = self._match_text_seq(\"WITH\", \"TIES\")\n\n        if not (percent or rows or with_ties):\n            return None\n\n        return self.expression(exp.LimitOptions(percent=percent, rows=rows, with_ties=with_ties))\n\n    def _parse_limit(\n        self,\n        this: t.Optional[exp.Expr] = None,\n        top: bool = False,\n        skip_limit_token: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        if skip_limit_token or self._match(TokenType.TOP if top else TokenType.LIMIT):\n            comments = self._prev_comments\n            if top:\n                limit_paren = self._match(TokenType.L_PAREN)\n                expression = self._parse_term() if limit_paren else self._parse_number()\n\n                if limit_paren:\n                    self._match_r_paren()\n\n            else:\n                # Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since\n                # we try to build an exp.Mod expr. For that matter, we backtrack and instead\n                # consume the factor plus parse the percentage separately\n                index = self._index\n                expression = self._try_parse(self._parse_term)\n                if isinstance(expression, exp.Mod):\n                    self._retreat(index)\n                    expression = self._parse_factor()\n                elif not expression:\n                    expression = self._parse_factor()\n            limit_options = self._parse_limit_options()\n\n            if self._match(TokenType.COMMA):\n                offset = expression\n                expression = self._parse_term()\n            else:\n                offset = None\n\n            limit_exp = self.expression(\n                exp.Limit(\n                    this=this,\n                    expression=expression,\n                    offset=offset,\n                    limit_options=limit_options,\n                    expressions=self._parse_limit_by(),\n                ),\n                comments=comments,\n            )\n\n            return limit_exp\n\n        if self._match(TokenType.FETCH):\n            direction = (\n                self._prev.text.upper()\n                if self._match_set((TokenType.FIRST, TokenType.NEXT))\n                else \"FIRST\"\n            )\n\n            count = self._parse_field(tokens=self.FETCH_TOKENS)\n\n            return self.expression(\n                exp.Fetch(\n                    direction=direction, count=count, limit_options=self._parse_limit_options()\n                )\n            )\n\n        return this\n\n    def _parse_offset(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        if not self._match(TokenType.OFFSET):\n            return this\n\n        count = self._parse_term()\n        self._match_set((TokenType.ROW, TokenType.ROWS))\n\n        return self.expression(\n            exp.Offset(this=this, expression=count, expressions=self._parse_limit_by())\n        )\n\n    def _can_parse_limit_or_offset(self) -> bool:\n        if not self._match_set(self.AMBIGUOUS_ALIAS_TOKENS, advance=False):\n            return False\n\n        index = self._index\n        result = bool(\n            self._try_parse(self._parse_limit, retreat=True)\n            or self._try_parse(self._parse_offset, retreat=True)\n        )\n        self._retreat(index)\n\n        # MATCH_CONDITION (...) is a special construct that should not be consumed by limit/offset\n        if self._next.token_type == TokenType.MATCH_CONDITION:\n            result = False\n\n        return result\n\n    def _parse_limit_by(self) -> t.Optional[t.List[exp.Expr]]:\n        return self._parse_csv(self._parse_bitwise) if self._match_text_seq(\"BY\") else None\n\n    def _parse_locks(self) -> t.List[exp.Lock]:\n        locks = []\n        while True:\n            update, key = None, None\n            if self._match_text_seq(\"FOR\", \"UPDATE\"):\n                update = True\n            elif self._match_text_seq(\"FOR\", \"SHARE\") or self._match_text_seq(\n                \"LOCK\", \"IN\", \"SHARE\", \"MODE\"\n            ):\n                update = False\n            elif self._match_text_seq(\"FOR\", \"KEY\", \"SHARE\"):\n                update, key = False, True\n            elif self._match_text_seq(\"FOR\", \"NO\", \"KEY\", \"UPDATE\"):\n                update, key = True, True\n            else:\n                break\n\n            expressions = None\n            if self._match_text_seq(\"OF\"):\n                expressions = self._parse_csv(lambda: self._parse_table(schema=True))\n\n            wait: t.Optional[bool | exp.Expr] = None\n            if self._match_text_seq(\"NOWAIT\"):\n                wait = True\n            elif self._match_text_seq(\"WAIT\"):\n                wait = self._parse_primary()\n            elif self._match_text_seq(\"SKIP\", \"LOCKED\"):\n                wait = False\n\n            locks.append(\n                self.expression(\n                    exp.Lock(update=update, expressions=expressions, wait=wait, key=key)\n                )\n            )\n\n        return locks\n\n    def parse_set_operation(\n        self, this: t.Optional[exp.Expr], consume_pipe: bool = False\n    ) -> t.Optional[exp.Expr]:\n        start = self._index\n        _, side_token, kind_token = self._parse_join_parts()\n\n        side = side_token.text if side_token else None\n        kind = kind_token.text if kind_token else None\n\n        if not self._match_set(self.SET_OPERATIONS):\n            self._retreat(start)\n            return None\n\n        token_type = self._prev.token_type\n\n        if token_type == TokenType.UNION:\n            operation: t.Type[exp.SetOperation] = exp.Union\n        elif token_type == TokenType.EXCEPT:\n            operation = exp.Except\n        else:\n            operation = exp.Intersect\n\n        comments = self._prev.comments\n\n        if self._match(TokenType.DISTINCT):\n            distinct: t.Optional[bool] = True\n        elif self._match(TokenType.ALL):\n            distinct = False\n        else:\n            distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[operation]\n            if distinct is None:\n                self.raise_error(f\"Expected DISTINCT or ALL for {operation.__name__}\")\n\n        by_name = (\n            self._match_text_seq(\"BY\", \"NAME\")\n            or self._match_text_seq(\"STRICT\", \"CORRESPONDING\")\n            or None\n        )\n        if self._match_text_seq(\"CORRESPONDING\"):\n            by_name = True\n            if not side and not kind:\n                kind = \"INNER\"\n\n        on_column_list = None\n        if by_name and self._match_texts((\"ON\", \"BY\")):\n            on_column_list = self._parse_wrapped_csv(self._parse_column)\n\n        expression = self._parse_select(\n            nested=True, parse_set_operation=False, consume_pipe=consume_pipe\n        )\n\n        return self.expression(\n            operation(\n                this=this,\n                distinct=distinct,\n                by_name=by_name,\n                expression=expression,\n                side=side,\n                kind=kind,\n                on=on_column_list,\n            ),\n            comments=comments,\n        )\n\n    def _parse_set_operations(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        while this:\n            setop = self.parse_set_operation(this)\n            if not setop:\n                break\n            this = setop\n\n        if isinstance(this, exp.SetOperation) and self.MODIFIERS_ATTACHED_TO_SET_OP:\n            expression = this.expression\n\n            if expression:\n                for arg in self.SET_OP_MODIFIERS:\n                    expr = expression.args.get(arg)\n                    if expr:\n                        this.set(arg, expr.pop())\n\n        return this\n\n    def _parse_expression(self) -> t.Optional[exp.Expr]:\n        return self._parse_alias(self._parse_assignment())\n\n    def _parse_assignment(self) -> t.Optional[exp.Expr]:\n        this = self._parse_disjunction()\n        if not this and self._next.token_type in self.ASSIGNMENT:\n            # This allows us to parse <non-identifier token> := <expr>\n            this = exp.column(\n                t.cast(str, self._advance_any(ignore_reserved=True) and self._prev.text)\n            )\n\n        while self._match_set(self.ASSIGNMENT):\n            if isinstance(this, exp.Column) and len(this.parts) == 1:\n                this = this.this\n\n            comments = self._prev_comments\n            this = self.expression(\n                self.ASSIGNMENT[self._prev.token_type](\n                    this=this, expression=self._parse_assignment()\n                ),\n                comments=comments,\n            )\n\n        return this\n\n    def _parse_disjunction(self) -> t.Optional[exp.Expr]:\n        this = self._parse_conjunction()\n        while self._match_set(self.DISJUNCTION):\n            comments = self._prev_comments\n            this = self.expression(\n                self.DISJUNCTION[self._prev.token_type](\n                    this=this, expression=self._parse_conjunction()\n                ),\n                comments=comments,\n            )\n        return this\n\n    def _parse_conjunction(self) -> t.Optional[exp.Expr]:\n        this = self._parse_equality()\n        while self._match_set(self.CONJUNCTION):\n            comments = self._prev_comments\n            this = self.expression(\n                self.CONJUNCTION[self._prev.token_type](\n                    this=this, expression=self._parse_equality()\n                ),\n                comments=comments,\n            )\n        return this\n\n    def _parse_equality(self) -> t.Optional[exp.Expr]:\n        this = self._parse_comparison()\n        while self._match_set(self.EQUALITY):\n            comments = self._prev_comments\n            this = self.expression(\n                self.EQUALITY[self._prev.token_type](\n                    this=this, expression=self._parse_comparison()\n                ),\n                comments=comments,\n            )\n        return this\n\n    def _parse_comparison(self) -> t.Optional[exp.Expr]:\n        this = self._parse_range()\n        while self._match_set(self.COMPARISON):\n            comments = self._prev_comments\n            this = self.expression(\n                self.COMPARISON[self._prev.token_type](this=this, expression=self._parse_range()),\n                comments=comments,\n            )\n        return this\n\n    def _parse_range(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        this = this or self._parse_bitwise()\n        negate = self._match(TokenType.NOT)\n\n        if self._match_set(self.RANGE_PARSERS):\n            expression = self.RANGE_PARSERS[self._prev.token_type](self, this)\n            if not expression:\n                return this\n\n            this = expression\n        elif self._match(TokenType.ISNULL) or (negate and self._match(TokenType.NULL)):\n            this = self.expression(exp.Is(this=this, expression=exp.Null()))\n\n        # Postgres supports ISNULL and NOTNULL for conditions.\n        # https://blog.andreiavram.ro/postgresql-null-composite-type/\n        if self._match(TokenType.NOTNULL):\n            this = self.expression(exp.Is(this=this, expression=exp.Null()))\n            this = self.expression(exp.Not(this=this))\n\n        if negate:\n            this = self._negate_range(this)\n\n        if self._match(TokenType.IS):\n            this = self._parse_is(this)\n\n        return this\n\n    def _negate_range(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        if not this:\n            return this\n\n        return self.expression(exp.Not(this=this))\n\n    def _parse_is(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        index = self._index - 1\n        negate = self._match(TokenType.NOT)\n\n        if self._match_text_seq(\"DISTINCT\", \"FROM\"):\n            klass = exp.NullSafeEQ if negate else exp.NullSafeNEQ\n            return self.expression(klass(this=this, expression=self._parse_bitwise()))\n\n        if self._match(TokenType.JSON):\n            kind = self._match_texts(self.IS_JSON_PREDICATE_KIND) and self._prev.text.upper()\n\n            if self._match_text_seq(\"WITH\"):\n                _with = True\n            elif self._match_text_seq(\"WITHOUT\"):\n                _with = False\n            else:\n                _with = None\n\n            unique = self._match(TokenType.UNIQUE)\n            self._match_text_seq(\"KEYS\")\n            expression: t.Optional[exp.Expr] = self.expression(\n                exp.JSON(this=kind, with_=_with, unique=unique)\n            )\n        else:\n            expression = self._parse_null() or self._parse_bitwise()\n            if not expression:\n                self._retreat(index)\n                return None\n\n        this = self.expression(exp.Is(this=this, expression=expression))\n        this = self.expression(exp.Not(this=this)) if negate else this\n        return self._parse_column_ops(this)\n\n    def _parse_in(self, this: t.Optional[exp.Expr], alias: bool = False) -> exp.In:\n        unnest = self._parse_unnest(with_alias=False)\n        if unnest:\n            this = self.expression(exp.In(this=this, unnest=unnest))\n        elif self._match_set((TokenType.L_PAREN, TokenType.L_BRACKET)):\n            matched_l_paren = self._prev.token_type == TokenType.L_PAREN\n            expressions = self._parse_csv(lambda: self._parse_select_or_expression(alias=alias))\n\n            if len(expressions) == 1 and isinstance(query := expressions[0], exp.Query):\n                this = self.expression(\n                    exp.In(this=this, query=self._parse_query_modifiers(query).subquery(copy=False))\n                )\n            else:\n                this = self.expression(exp.In(this=this, expressions=expressions))\n\n            if matched_l_paren:\n                self._match_r_paren(this)\n            elif not self._match(TokenType.R_BRACKET, expression=this):\n                self.raise_error(\"Expecting ]\")\n        else:\n            this = self.expression(exp.In(this=this, field=self._parse_column()))\n\n        return this\n\n    def _parse_between(self, this: t.Optional[exp.Expr]) -> exp.Between:\n        symmetric = None\n        if self._match_text_seq(\"SYMMETRIC\"):\n            symmetric = True\n        elif self._match_text_seq(\"ASYMMETRIC\"):\n            symmetric = False\n\n        low = self._parse_bitwise()\n        self._match(TokenType.AND)\n        high = self._parse_bitwise()\n\n        return self.expression(exp.Between(this=this, low=low, high=high, symmetric=symmetric))\n\n    def _parse_escape(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        if not self._match(TokenType.ESCAPE):\n            return this\n        return self.expression(\n            exp.Escape(this=this, expression=self._parse_string() or self._parse_null())\n        )\n\n    def _parse_interval_span(self, this: exp.Expr) -> exp.Interval:\n        # handle day-time format interval span with omitted units:\n        #   INTERVAL '<number days> hh[:][mm[:ss[.ff]]]' <maybe `unit TO unit`>\n        interval_span_units_omitted = None\n        if (\n            this\n            and this.is_string\n            and self.SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT\n            and exp.INTERVAL_DAY_TIME_RE.match(this.name)\n        ):\n            index = self._index\n\n            # Var \"TO\" Var\n            first_unit = self._parse_var(any_token=True, upper=True)\n            second_unit = None\n            if first_unit and self._match_text_seq(\"TO\"):\n                second_unit = self._parse_var(any_token=True, upper=True)\n\n            interval_span_units_omitted = not (first_unit and second_unit)\n\n            self._retreat(index)\n\n        unit = (\n            None\n            if interval_span_units_omitted\n            else (\n                self._parse_function()\n                or (\n                    not self._match_set((TokenType.ALIAS, TokenType.DCOLON), advance=False)\n                    and self._parse_var(any_token=True, upper=True)\n                )\n            )\n        )\n\n        # Most dialects support, e.g., the form INTERVAL '5' day, thus we try to parse\n        # each INTERVAL expression into this canonical form so it's easy to transpile\n        if this and this.is_number:\n            this = exp.Literal.string(this.to_py())\n        elif this and this.is_string:\n            parts = exp.INTERVAL_STRING_RE.findall(this.name)\n            if parts and unit:\n                # Unconsume the eagerly-parsed unit, since the real unit was part of the string\n                unit = None\n                self._retreat(self._index - 1)\n\n            if len(parts) == 1:\n                this = exp.Literal.string(parts[0][0])\n                unit = self.expression(exp.Var(this=parts[0][1].upper()))\n\n        if self.INTERVAL_SPANS and self._match_text_seq(\"TO\"):\n            unit = self.expression(\n                exp.IntervalSpan(\n                    this=unit,\n                    expression=self._parse_function()\n                    or self._parse_var(any_token=True, upper=True),\n                )\n            )\n\n        return self.expression(exp.Interval(this=this, unit=unit))\n\n    def _parse_interval(self, require_interval: bool = True) -> t.Optional[exp.Add | exp.Interval]:\n        index = self._index\n\n        if not self._match(TokenType.INTERVAL) and require_interval:\n            return None\n\n        if self._match(TokenType.STRING, advance=False):\n            this = self._parse_primary()\n        else:\n            this = self._parse_term()\n\n        if not this or (\n            isinstance(this, exp.Column)\n            and not this.table\n            and not this.this.quoted\n            and self._curr\n            and self._curr.text.upper() not in self.dialect.VALID_INTERVAL_UNITS\n        ):\n            self._retreat(index)\n            return None\n\n        interval = self._parse_interval_span(this)\n\n        index = self._index\n        self._match(TokenType.PLUS)\n\n        # Convert INTERVAL 'val_1' unit_1 [+] ... [+] 'val_n' unit_n into a sum of intervals\n        if self._match_set((TokenType.STRING, TokenType.NUMBER), advance=False):\n            return self.expression(exp.Add(this=interval, expression=self._parse_interval(False)))\n\n        self._retreat(index)\n        return interval\n\n    def _parse_bitwise(self) -> t.Optional[exp.Expr]:\n        this = self._parse_term()\n\n        while True:\n            if self._match_set(self.BITWISE):\n                this = self.expression(\n                    self.BITWISE[self._prev.token_type](this=this, expression=self._parse_term())\n                )\n            elif self.dialect.DPIPE_IS_STRING_CONCAT and self._match(TokenType.DPIPE):\n                this = self.expression(\n                    exp.DPipe(\n                        this=this,\n                        expression=self._parse_term(),\n                        safe=not self.dialect.STRICT_STRING_CONCAT,\n                    )\n                )\n            elif self._match(TokenType.DQMARK):\n                this = self.expression(\n                    exp.Coalesce(this=this, expressions=ensure_list(self._parse_term()))\n                )\n            elif self._match_pair(TokenType.LT, TokenType.LT):\n                this = self.expression(\n                    exp.BitwiseLeftShift(this=this, expression=self._parse_term())\n                )\n            elif self._match_pair(TokenType.GT, TokenType.GT):\n                this = self.expression(\n                    exp.BitwiseRightShift(this=this, expression=self._parse_term())\n                )\n            else:\n                break\n\n        return this\n\n    def _parse_term(self) -> t.Optional[exp.Expr]:\n        this = self._parse_factor()\n\n        while self._match_set(self.TERM):\n            klass = self.TERM[self._prev.token_type]\n            comments = self._prev_comments\n            expression = self._parse_factor()\n\n            this = self.expression(klass(this=this, expression=expression), comments=comments)\n\n            if isinstance(this, exp.Collate):\n                expr = this.expression\n\n                # Preserve collations such as pg_catalog.\"default\" (Postgres) as columns, otherwise\n                # fallback to Identifier / Var\n                if isinstance(expr, exp.Column) and len(expr.parts) == 1:\n                    ident = expr.this\n                    if isinstance(ident, exp.Identifier):\n                        this.set(\"expression\", ident if ident.quoted else exp.var(ident.name))\n\n        return this\n\n    def _parse_factor(self) -> t.Optional[exp.Expr]:\n        parse_method = self._parse_exponent if self.EXPONENT else self._parse_unary\n        this = self._parse_at_time_zone(parse_method())\n\n        while self._match_set(self.FACTOR):\n            klass = self.FACTOR[self._prev.token_type]\n            comments = self._prev_comments\n            expression = parse_method()\n\n            if not expression and klass is exp.IntDiv and self._prev.text.isalpha():\n                self._retreat(self._index - 1)\n                return this\n\n            this = self.expression(klass(this=this, expression=expression), comments=comments)\n\n            if isinstance(this, exp.Div):\n                this.set(\"typed\", self.dialect.TYPED_DIVISION)\n                this.set(\"safe\", self.dialect.SAFE_DIVISION)\n\n        return this\n\n    def _parse_exponent(self) -> t.Optional[exp.Expr]:\n        this = self._parse_unary()\n        while self._match_set(self.EXPONENT):\n            comments = self._prev_comments\n            this = self.expression(\n                self.EXPONENT[self._prev.token_type](this=this, expression=self._parse_unary()),\n                comments=comments,\n            )\n        return this\n\n    def _parse_unary(self) -> t.Optional[exp.Expr]:\n        if self._match_set(self.UNARY_PARSERS):\n            return self.UNARY_PARSERS[self._prev.token_type](self)\n        return self._parse_type()\n\n    def _parse_type(\n        self, parse_interval: bool = True, fallback_to_identifier: bool = False\n    ) -> t.Optional[exp.Expr]:\n        if not fallback_to_identifier and (atom := self._parse_atom()) is not None:\n            return atom\n\n        if interval := parse_interval and self._parse_interval():\n            return self._parse_column_ops(interval)\n\n        index = self._index\n        data_type = self._parse_types(check_func=True, allow_identifiers=False)\n\n        # parse_types() returns a Cast if we parsed BQ's inline constructor <type>(<values>) e.g.\n        # STRUCT<a INT, b STRING>(1, 'foo'), which is canonicalized to CAST(<values> AS <type>)\n        if isinstance(data_type, exp.Cast):\n            # This constructor can contain ops directly after it, for instance struct unnesting:\n            # STRUCT<a INT, b STRING>(1, 'foo').* --> CAST(STRUCT(1, 'foo') AS STRUCT<a iNT, b STRING).*\n            return self._parse_column_ops(data_type)\n\n        if data_type:\n            index2 = self._index\n            this = self._parse_primary()\n\n            if isinstance(this, exp.Literal):\n                literal = this.name\n                this = self._parse_column_ops(this)\n\n                parser = self.TYPE_LITERAL_PARSERS.get(data_type.this)\n                if parser:\n                    return parser(self, this, data_type)\n\n                if (\n                    self.ZONE_AWARE_TIMESTAMP_CONSTRUCTOR\n                    and data_type.is_type(exp.DType.TIMESTAMP)\n                    and TIME_ZONE_RE.search(literal)\n                ):\n                    data_type = exp.DataType.build(\"TIMESTAMPTZ\")\n\n                return self.expression(exp.Cast(this=this, to=data_type))\n\n            # The expressions arg gets set by the parser when we have something like DECIMAL(38, 0)\n            # in the input SQL. In that case, we'll produce these tokens: DECIMAL ( 38 , 0 )\n            #\n            # If the index difference here is greater than 1, that means the parser itself must have\n            # consumed additional tokens such as the DECIMAL scale and precision in the above example.\n            #\n            # If it's not greater than 1, then it must be 1, because we've consumed at least the type\n            # keyword, meaning that the expressions arg of the DataType must have gotten set by a\n            # callable in the TYPE_CONVERTERS mapping. For example, Snowflake converts DECIMAL to\n            # DECIMAL(38, 0)) in order to facilitate the data type's transpilation.\n            #\n            # In these cases, we don't really want to return the converted type, but instead retreat\n            # and try to parse a Column or Identifier in the section below.\n            if data_type.expressions and index2 - index > 1:\n                self._retreat(index2)\n                return self._parse_column_ops(data_type)\n\n            self._retreat(index)\n\n        if fallback_to_identifier:\n            return self._parse_id_var()\n\n        return self._parse_column()\n\n    def _parse_type_size(self) -> t.Optional[exp.DataTypeParam]:\n        this = self._parse_type()\n        if not this:\n            return None\n\n        if isinstance(this, exp.Column) and not this.table:\n            this = exp.var(this.name.upper())\n\n        return self.expression(\n            exp.DataTypeParam(this=this, expression=self._parse_var(any_token=True))\n        )\n\n    def _parse_user_defined_type(self, identifier: exp.Identifier) -> t.Optional[exp.Expr]:\n        type_name = identifier.name\n\n        while self._match(TokenType.DOT):\n            type_name = f\"{type_name}.{self._advance_any() and self._prev.text}\"\n\n        return exp.DataType.build(type_name, dialect=self.dialect, udt=True)\n\n    def _parse_types(\n        self, check_func: bool = False, schema: bool = False, allow_identifiers: bool = True\n    ) -> t.Optional[exp.Expr]:\n        index = self._index\n        this: t.Optional[exp.Expr] = None\n\n        if self._match_set(self.TYPE_TOKENS):\n            type_token = self._prev.token_type\n        else:\n            type_token = None\n            identifier = allow_identifiers and self._parse_id_var(\n                any_token=False, tokens=(TokenType.VAR,)\n            )\n            if isinstance(identifier, exp.Identifier):\n                try:\n                    tokens = self.dialect.tokenize(identifier.name)\n                except TokenError:\n                    tokens = None\n\n                if tokens and (type_token := tokens[0].token_type) in self.TYPE_TOKENS:\n                    if len(tokens) > 1:\n                        return exp.DataType.build(identifier.name, dialect=self.dialect)\n                elif self.dialect.SUPPORTS_USER_DEFINED_TYPES:\n                    this = self._parse_user_defined_type(identifier)\n                else:\n                    self._retreat(self._index - 1)\n                    return None\n            else:\n                return None\n\n        if type_token == TokenType.PSEUDO_TYPE:\n            return self.expression(exp.PseudoType(this=self._prev.text.upper()))\n\n        if type_token == TokenType.OBJECT_IDENTIFIER:\n            return self.expression(exp.ObjectIdentifier(this=self._prev.text.upper()))\n\n        # https://materialize.com/docs/sql/types/map/\n        if type_token == TokenType.MAP and self._match(TokenType.L_BRACKET):\n            key_type = self._parse_types(\n                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers\n            )\n            if not self._match(TokenType.FARROW):\n                self._retreat(index)\n                return None\n\n            value_type = self._parse_types(\n                check_func=check_func, schema=schema, allow_identifiers=allow_identifiers\n            )\n            if not self._match(TokenType.R_BRACKET):\n                self._retreat(index)\n                return None\n\n            return exp.DataType(\n                this=exp.DType.MAP,\n                expressions=[key_type, value_type],\n                nested=True,\n            )\n\n        nested = type_token in self.NESTED_TYPE_TOKENS\n        is_struct = type_token in self.STRUCT_TYPE_TOKENS\n        is_aggregate = type_token in self.AGGREGATE_TYPE_TOKENS\n        expressions = None\n        maybe_func = False\n\n        if self._match(TokenType.L_PAREN):\n            if is_struct:\n                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))\n            elif nested:\n                expressions = self._parse_csv(\n                    lambda: self._parse_types(\n                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers\n                    )\n                )\n                if type_token == TokenType.NULLABLE and len(expressions) == 1:\n                    this = expressions[0]\n                    this.set(\"nullable\", True)\n                    self._match_r_paren()\n                    return this\n            elif type_token in self.ENUM_TYPE_TOKENS:\n                expressions = self._parse_csv(self._parse_equality)\n            elif type_token == TokenType.JSON:\n                # ClickHouse JSON type supports arguments: JSON(col Type, SKIP col, param=value)\n                # https://clickhouse.com/docs/sql-reference/data-types/newjson\n                expressions = self._parse_csv(self._parse_json_type_arg)\n            elif is_aggregate:\n                func_or_ident = self._parse_function(anonymous=True) or self._parse_id_var(\n                    any_token=False, tokens=(TokenType.VAR, TokenType.ANY)\n                )\n                if not func_or_ident:\n                    return None\n                expressions = [func_or_ident]\n                if self._match(TokenType.COMMA):\n                    expressions.extend(\n                        self._parse_csv(\n                            lambda: self._parse_types(\n                                check_func=check_func,\n                                schema=schema,\n                                allow_identifiers=allow_identifiers,\n                            )\n                        )\n                    )\n            else:\n                expressions = self._parse_csv(self._parse_type_size)\n\n                # https://docs.snowflake.com/en/sql-reference/data-types-vector\n                if type_token == TokenType.VECTOR and len(expressions) == 2:\n                    expressions = self._parse_vector_expressions(expressions)\n\n            if not self._match(TokenType.R_PAREN):\n                self._retreat(index)\n                return None\n\n            maybe_func = True\n\n        values: t.Optional[t.List[exp.Expr]] = None\n\n        if nested and self._match(TokenType.LT):\n            if is_struct:\n                expressions = self._parse_csv(lambda: self._parse_struct_types(type_required=True))\n            else:\n                expressions = self._parse_csv(\n                    lambda: self._parse_types(\n                        check_func=check_func, schema=schema, allow_identifiers=allow_identifiers\n                    )\n                )\n\n            if not self._match(TokenType.GT):\n                self.raise_error(\"Expecting >\")\n\n            if self._match_set((TokenType.L_BRACKET, TokenType.L_PAREN)):\n                values = self._parse_csv(self._parse_disjunction)\n                if not values and is_struct:\n                    values = None\n                    self._retreat(self._index - 1)\n                else:\n                    self._match_set((TokenType.R_BRACKET, TokenType.R_PAREN))\n\n        if type_token in self.TIMESTAMPS:\n            if self._match_text_seq(\"WITH\", \"TIME\", \"ZONE\"):\n                maybe_func = False\n                tz_type = exp.DType.TIMETZ if type_token in self.TIMES else exp.DType.TIMESTAMPTZ\n                this = exp.DataType(this=tz_type, expressions=expressions)\n            elif self._match_text_seq(\"WITH\", \"LOCAL\", \"TIME\", \"ZONE\"):\n                maybe_func = False\n                this = exp.DataType(this=exp.DType.TIMESTAMPLTZ, expressions=expressions)\n            elif self._match_text_seq(\"WITHOUT\", \"TIME\", \"ZONE\"):\n                maybe_func = False\n        elif type_token == TokenType.INTERVAL:\n            if self._curr.text.upper() in self.dialect.VALID_INTERVAL_UNITS:\n                unit = self._parse_var(upper=True)\n                if self._match_text_seq(\"TO\"):\n                    unit = exp.IntervalSpan(this=unit, expression=self._parse_var(upper=True))\n\n                this = self.expression(exp.DataType(this=self.expression(exp.Interval(unit=unit))))\n            else:\n                this = self.expression(exp.DataType(this=exp.DType.INTERVAL))\n        elif type_token == TokenType.VOID:\n            this = exp.DataType(this=exp.DType.NULL)\n\n        if maybe_func and check_func:\n            index2 = self._index\n            peek = self._parse_string()\n\n            if not peek:\n                self._retreat(index)\n                return None\n\n            self._retreat(index2)\n\n        if not this:\n            assert type_token is not None\n            if self._match_text_seq(\"UNSIGNED\"):\n                unsigned_type_token = self.SIGNED_TO_UNSIGNED_TYPE_TOKEN.get(type_token)\n                if not unsigned_type_token:\n                    self.raise_error(f\"Cannot convert {type_token.name} to unsigned.\")\n\n                type_token = unsigned_type_token or type_token\n\n            # NULLABLE without parentheses can be a column (Presto/Trino)\n            if type_token == TokenType.NULLABLE and not expressions:\n                self._retreat(index)\n                return None\n\n            this = exp.DataType(\n                this=exp.DType[type_token.name],\n                expressions=expressions,\n                nested=nested,\n            )\n\n            # Empty arrays/structs are allowed\n            if values is not None:\n                cls = exp.Struct if is_struct else exp.Array\n                this = exp.cast(cls(expressions=values), this, copy=False)\n\n        elif expressions:\n            this.set(\"expressions\", expressions)\n\n        # https://materialize.com/docs/sql/types/list/#type-name\n        while self._match(TokenType.LIST):\n            this = exp.DataType(this=exp.DType.LIST, expressions=[this], nested=True)\n\n        index = self._index\n\n        # Postgres supports the INT ARRAY[3] syntax as a synonym for INT[3]\n        matched_array = self._match(TokenType.ARRAY)\n\n        while self._curr:\n            datatype_token = self._prev.token_type\n            matched_l_bracket = self._match(TokenType.L_BRACKET)\n\n            if (not matched_l_bracket and not matched_array) or (\n                datatype_token == TokenType.ARRAY and self._match(TokenType.R_BRACKET)\n            ):\n                # Postgres allows casting empty arrays such as ARRAY[]::INT[],\n                # not to be confused with the fixed size array parsing\n                break\n\n            matched_array = False\n            values = self._parse_csv(self._parse_disjunction) or None\n            if (\n                values\n                and not schema\n                and (\n                    not self.dialect.SUPPORTS_FIXED_SIZE_ARRAYS\n                    or datatype_token == TokenType.ARRAY\n                    or not self._match(TokenType.R_BRACKET, advance=False)\n                )\n            ):\n                # Retreating here means that we should not parse the following values as part of the data type, e.g. in DuckDB\n                # ARRAY[1] should retreat and instead be parsed into exp.Array in contrast to INT[x][y] which denotes a fixed-size array data type\n                self._retreat(index)\n                break\n\n            this = exp.DataType(\n                this=exp.DType.ARRAY, expressions=[this], values=values, nested=True\n            )\n            self._match(TokenType.R_BRACKET)\n\n        if self.TYPE_CONVERTERS and isinstance(this.this, exp.DType):\n            converter = self.TYPE_CONVERTERS.get(this.this)\n            if converter:\n                this = converter(t.cast(exp.DataType, this))\n\n        return this\n\n    def _parse_json_type_arg(self) -> t.Optional[exp.Expr]:\n        \"\"\"Parse a single argument to ClickHouse's JSON type.\"\"\"\n\n        # SKIP col or SKIP REGEXP 'pattern'\n        if self._match_text_seq(\"SKIP\"):\n            regexp = self._match(TokenType.RLIKE)\n            arg = self._parse_column()\n            if isinstance(arg, exp.Column):\n                arg = arg.to_dot()\n            return self.expression(exp.SkipJSONColumn(regexp=regexp, expression=arg))\n\n        param_or_col = self._parse_column()\n        if not isinstance(param_or_col, exp.Column):\n            return None\n\n        # Parameter: name=value (e.g., max_dynamic_paths=2)\n        if len(param_or_col.parts) == 1 and self._match(TokenType.EQ):\n            param = param_or_col.name\n            value = self._parse_primary()\n            return self.expression(exp.EQ(this=exp.var(param), expression=value))\n\n        # Column type hint: col_name Type\n        col = param_or_col.to_dot()\n        kind = self._parse_types(check_func=False, allow_identifiers=False)\n        return self.expression(exp.ColumnDef(this=col, kind=kind))\n\n    def _parse_vector_expressions(self, expressions: t.List[exp.Expr]) -> t.List[exp.Expr]:\n        return [exp.DataType.build(expressions[0].name, dialect=self.dialect), *expressions[1:]]\n\n    def _parse_struct_types(self, type_required: bool = False) -> t.Optional[exp.Expr]:\n        index = self._index\n\n        if (\n            self._curr\n            and self._next\n            and self._curr.token_type in self.TYPE_TOKENS\n            and self._next.token_type in self.TYPE_TOKENS\n        ):\n            # Takes care of special cases like `STRUCT<list ARRAY<...>>` where the identifier is also a\n            # type token. Without this, the list will be parsed as a type and we'll eventually crash\n            this = self._parse_id_var()\n        else:\n            this = (\n                self._parse_type(parse_interval=False, fallback_to_identifier=True)\n                or self._parse_id_var()\n            )\n\n        self._match(TokenType.COLON)\n\n        if (\n            type_required\n            and not isinstance(this, exp.DataType)\n            and not self._match_set(self.TYPE_TOKENS, advance=False)\n        ):\n            self._retreat(index)\n            return self._parse_types()\n\n        return self._parse_column_def(this)\n\n    def _parse_at_time_zone(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        if not self._match_text_seq(\"AT\", \"TIME\", \"ZONE\"):\n            return this\n        return self._parse_at_time_zone(\n            self.expression(exp.AtTimeZone(this=this, zone=self._parse_unary()))\n        )\n\n    def _parse_atom(self) -> t.Optional[exp.Expr]:\n        if (\n            self._curr.token_type in self.IDENTIFIER_TOKENS\n            and (column := self._parse_column()) is not None\n        ):\n            return column\n\n        token = self._curr\n        token_type = token.token_type\n\n        if not (primary_parser := self.PRIMARY_PARSERS.get(token_type)):\n            return None\n\n        next_type = self._next.token_type\n\n        if (\n            next_type in self.COLUMN_OPERATORS\n            or next_type in self.COLUMN_POSTFIX_TOKENS\n            or (token_type == TokenType.STRING and next_type == TokenType.STRING)\n        ):\n            return None\n\n        self._advance()\n        return primary_parser(self, token)\n\n    def _parse_column(self) -> t.Optional[exp.Expr]:\n        column: t.Optional[exp.Expr] = self._parse_column_parts_fast()\n        if column is None:\n            this = self._parse_column_reference()\n            if not this:\n                this = self._parse_bracket(this)\n            column = self._parse_column_ops(this) if this else this\n\n        if column:\n            if self.dialect.SUPPORTS_COLUMN_JOIN_MARKS:\n                column.set(\"join_mark\", self._match(TokenType.JOIN_MARKER))\n            if self.COLON_IS_VARIANT_EXTRACT:\n                column = self._parse_colon_as_variant_extract(column)\n\n        return column\n\n    def _parse_column_parts_fast(self) -> t.Optional[exp.Column | exp.Dot]:\n        \"\"\"Fast path for simple column and dot references (a, a.b, ...).\n\n        Greedily consumes VAR/IDENTIFIER tokens separated by DOTs, then checks\n        that nothing complex follows. If it does, retreats and returns None so\n        the slow path can handle it. For >4 parts, wraps in exp.Dot nodes.\n        \"\"\"\n        index = self._index\n        parts: t.Optional[t.List[exp.Identifier]] = None\n        all_comments: t.Optional[t.List[str]] = None\n\n        while self._match_set(self.IDENTIFIER_TOKENS):\n            token = self._prev\n            comments = self._prev_comments\n\n            if parts is None and token.text.upper() in self.NO_PAREN_FUNCTION_PARSERS:\n                self._retreat(index)\n                return None\n\n            has_dot = self._match(TokenType.DOT)\n            curr_tt = self._curr.token_type\n\n            if not has_dot:\n                if curr_tt in self.COLUMN_OPERATORS or curr_tt in self.COLUMN_POSTFIX_TOKENS:\n                    self._retreat(index)\n                    return None\n            elif curr_tt not in self.IDENTIFIER_TOKENS:\n                self._retreat(index)\n                return None\n\n            if parts is None:\n                parts = []\n\n            if comments:\n                if all_comments is None:\n                    all_comments = []\n                all_comments.extend(comments)\n                self._prev_comments = []\n\n            parts.append(\n                self.expression(\n                    exp.Identifier(\n                        this=token.text, quoted=token.token_type == TokenType.IDENTIFIER\n                    ),\n                    token,\n                )\n            )\n\n            if not has_dot:\n                break\n\n        if parts is None:\n            return None\n\n        n = len(parts)\n\n        if n == 1:\n            column: exp.Column | exp.Dot = exp.Column(this=parts[0])\n        elif n == 2:\n            column = exp.Column(this=parts[1], table=parts[0])\n        elif n == 3:\n            column = exp.Column(this=parts[2], table=parts[1], db=parts[0])\n        else:\n            column = exp.Column(this=parts[3], table=parts[2], db=parts[1], catalog=parts[0])\n\n            for i in range(4, n):\n                column = exp.Dot(this=column, expression=parts[i])\n\n        if all_comments:\n            column.add_comments(all_comments)\n\n        return column\n\n    def _parse_column_reference(self) -> t.Optional[exp.Expr]:\n        this = self._parse_field()\n        if (\n            not this\n            and self._match(TokenType.VALUES, advance=False)\n            and self.VALUES_FOLLOWED_BY_PAREN\n            and (not self._next or self._next.token_type != TokenType.L_PAREN)\n        ):\n            this = self._parse_id_var()\n\n        if isinstance(this, exp.Identifier):\n            # We bubble up comments from the Identifier to the Column\n            this = self.expression(exp.Column(this=this), comments=this.pop_comments())\n\n        return this\n\n    def _build_json_extract(\n        self,\n        this: t.Optional[exp.Expr],\n        json_path: t.List[str],\n        escape: t.Optional[bool],\n    ) -> exp.JSONExtract:\n        json_path_expr = self.dialect.to_json_path(exp.Literal.string(\".\".join(json_path)))\n\n        if json_path_expr:\n            json_path_expr.set(\"escape\", escape)\n\n        return self.expression(\n            exp.JSONExtract(\n                this=this,\n                expression=json_path_expr,\n                variant_extract=True,\n                requires_json=self.JSON_EXTRACT_REQUIRES_JSON_EXPRESSION,\n            )\n        )\n\n    def _parse_colon_as_variant_extract(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        casts = []\n        json_path = []\n        escape = None\n\n        while self._match(TokenType.COLON):\n            start_index = self._index\n\n            # Snowflake allows reserved keywords as json keys but advance_any() excludes TokenType.SELECT from any_tokens=True\n            path = self._parse_column_ops(\n                self._parse_field(any_token=True, tokens=(TokenType.SELECT,))\n            )\n\n            # The cast :: operator has a lower precedence than the extraction operator :, so\n            # we rearrange the AST appropriately to avoid casting the JSON path\n            while isinstance(path, exp.Cast):\n                casts.append(path.to)\n                path = path.this\n\n            if casts:\n                dcolon_offset = next(\n                    i\n                    for i, t in enumerate(self._tokens[start_index:])\n                    if t.token_type == TokenType.DCOLON\n                )\n                end_token = self._tokens[start_index + dcolon_offset - 1]\n            else:\n                end_token = self._prev\n\n            if path:\n                # Escape single quotes from Snowflake's colon extraction (e.g. col:\"a'b\") as\n                # it'll roundtrip to a string literal in GET_PATH\n                if isinstance(path, exp.Identifier) and path.quoted:\n                    escape = True\n\n                # Dynamic brackets (e.g. value:a[s.x].b.c or value:a[s.x].r.d[s.y])\n                # can't be in the JSON path string since the index is a column reference.\n                # We traverse Dot/Bracket layers from outside in, collecting segments, then\n                # process them inside out.\n                segments: t.List[t.Tuple[exp.Bracket, t.List[str]]] = []\n                node = path\n                while True:\n                    suffixes = []\n                    while isinstance(node, exp.Dot):\n                        suffixes.append(node.expression.sql(dialect=self.dialect))\n                        node = node.this\n\n                    if isinstance(node, exp.Bracket) and any(\n                        e.find(exp.Column) for e in node.expressions\n                    ):\n                        suffixes.reverse()\n                        segments.append((node, suffixes))\n                        node = node.this\n                    else:\n                        break\n\n                if segments:\n                    json_path.append(segments[-1][0].this.sql(dialect=self.dialect))\n                    for bracket, suffixes in reversed(segments):\n                        this = self._build_json_extract(this, json_path, escape)\n                        this = exp.Bracket(this=this, expressions=bracket.expressions)\n                        json_path = suffixes\n\n                    if json_path:\n                        this = self._build_json_extract(this, json_path, None)\n\n                    json_path = []\n                    continue\n\n                json_path.append(self._find_sql(self._tokens[start_index], end_token))\n\n        # The VARIANT extract in Snowflake/Databricks is parsed as a JSONExtract; Snowflake uses the json_path in GET_PATH() while\n        # Databricks transforms it back to the colon/dot notation\n        if json_path:\n            this = self._build_json_extract(this, json_path, escape)\n\n            while casts:\n                this = self.expression(exp.Cast(this=this, to=casts.pop()))\n\n        return this\n\n    def _parse_dcolon(self) -> t.Optional[exp.Expr]:\n        return self._parse_types()\n\n    def _parse_column_ops(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        while self._curr.token_type in self.BRACKETS:\n            this = self._parse_bracket(this)\n\n        column_operators = self.COLUMN_OPERATORS\n        cast_column_operators = self.CAST_COLUMN_OPERATORS\n        while self._curr:\n            op_token = self._curr.token_type\n\n            if op_token not in column_operators:\n                break\n            op = column_operators[op_token]\n            self._advance()\n\n            if op_token in cast_column_operators:\n                field = self._parse_dcolon()\n                if not field:\n                    self.raise_error(\"Expected type\")\n            elif op and self._curr:\n                field = self._parse_column_reference() or self._parse_bitwise()\n                if isinstance(field, exp.Column) and self._match(TokenType.DOT, advance=False):\n                    field = self._parse_column_ops(field)\n            else:\n                field = self._parse_field(any_token=True, anonymous_func=True)\n\n            # Function calls can be qualified, e.g., x.y.FOO()\n            # This converts the final AST to a series of Dots leading to the function call\n            # https://cloud.google.com/bigquery/docs/reference/standard-sql/functions-reference#function_call_rules\n            if isinstance(field, (exp.Func, exp.Window)) and this:\n                this = this.transform(\n                    lambda n: n.to_dot(include_dots=False) if isinstance(n, exp.Column) else n\n                )\n\n            if op:\n                this = op(self, this, field)\n            elif isinstance(this, exp.Column) and not this.args.get(\"catalog\"):\n                this = self.expression(\n                    exp.Column(\n                        this=field,\n                        table=this.this,\n                        db=this.args.get(\"table\"),\n                        catalog=this.args.get(\"db\"),\n                    ),\n                    comments=this.comments,\n                )\n            elif isinstance(field, exp.Window):\n                # Move the exp.Dot's to the window's function\n                window_func = self.expression(exp.Dot(this=this, expression=field.this))\n                field.set(\"this\", window_func)\n                this = field\n            else:\n                this = self.expression(exp.Dot(this=this, expression=field))\n\n            if field and field.comments:\n                t.cast(exp.Expr, this).add_comments(field.pop_comments())\n\n            this = self._parse_bracket(this)\n\n        return this\n\n    def _parse_paren(self) -> t.Optional[exp.Expr]:\n        if not self._match(TokenType.L_PAREN):\n            return None\n\n        comments = self._prev_comments\n        query = self._parse_select()\n\n        if query:\n            expressions = [query]\n        else:\n            expressions = self._parse_expressions()\n\n        this = seq_get(expressions, 0)\n\n        if not this and self._match(TokenType.R_PAREN, advance=False):\n            this = self.expression(exp.Tuple())\n        elif isinstance(this, exp.UNWRAPPED_QUERIES):\n            this = self._parse_subquery(this=this, parse_alias=False)\n        elif isinstance(this, (exp.Subquery, exp.Values)):\n            this = self._parse_subquery(\n                this=self._parse_query_modifiers(self._parse_set_operations(this)),\n                parse_alias=False,\n            )\n        elif len(expressions) > 1 or self._prev.token_type == TokenType.COMMA:\n            this = self.expression(exp.Tuple(expressions=expressions))\n        else:\n            this = self.expression(exp.Paren(this=this))\n\n        if this:\n            this.add_comments(comments)\n\n        self._match_r_paren(expression=this)\n\n        if isinstance(this, exp.Paren) and isinstance(this.this, exp.AggFunc):\n            return self._parse_window(this)\n\n        return this\n\n    def _parse_primary(self) -> t.Optional[exp.Expr]:\n        if self._match_set(self.PRIMARY_PARSERS):\n            token_type = self._prev.token_type\n            primary = self.PRIMARY_PARSERS[token_type](self, self._prev)\n\n            if token_type == TokenType.STRING:\n                expressions = [primary]\n                while self._match(TokenType.STRING):\n                    expressions.append(exp.Literal.string(self._prev.text))\n\n                if len(expressions) > 1:\n                    return self.expression(\n                        exp.Concat(expressions=expressions, coalesce=self.dialect.CONCAT_COALESCE)\n                    )\n\n            return primary\n\n        if self._match_pair(TokenType.DOT, TokenType.NUMBER):\n            return exp.Literal.number(f\"0.{self._prev.text}\")\n\n        return self._parse_paren()\n\n    def _parse_field(\n        self,\n        any_token: bool = False,\n        tokens: t.Optional[t.Collection[TokenType]] = None,\n        anonymous_func: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        if anonymous_func:\n            field = (\n                self._parse_function(anonymous=anonymous_func, any_token=any_token)\n                or self._parse_primary()\n            )\n        else:\n            field = self._parse_primary() or self._parse_function(\n                anonymous=anonymous_func, any_token=any_token\n            )\n        return field or self._parse_id_var(any_token=any_token, tokens=tokens)\n\n    def _parse_function(\n        self,\n        functions: t.Optional[t.Dict[str, t.Callable]] = None,\n        anonymous: bool = False,\n        optional_parens: bool = True,\n        any_token: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        # This allows us to also parse {fn <function>} syntax (Snowflake, MySQL support this)\n        # See: https://community.snowflake.com/s/article/SQL-Escape-Sequences\n        fn_syntax = False\n        if (\n            self._match(TokenType.L_BRACE, advance=False)\n            and self._next\n            and self._next.text.upper() == \"FN\"\n        ):\n            self._advance(2)\n            fn_syntax = True\n\n        func = self._parse_function_call(\n            functions=functions,\n            anonymous=anonymous,\n            optional_parens=optional_parens,\n            any_token=any_token,\n        )\n\n        if fn_syntax:\n            self._match(TokenType.R_BRACE)\n\n        return func\n\n    def _parse_function_args(self, alias: bool = False) -> t.List[exp.Expr]:\n        return self._parse_csv(lambda: self._parse_lambda(alias=alias))\n\n    def _parse_function_call(\n        self,\n        functions: t.Optional[t.Dict[str, t.Callable]] = None,\n        anonymous: bool = False,\n        optional_parens: bool = True,\n        any_token: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        if not self._curr:\n            return None\n\n        comments = self._curr.comments\n        prev = self._prev\n        token = self._curr\n        token_type = self._curr.token_type\n        this: str | exp.Expr = self._curr.text\n        upper = self._curr.text.upper()\n\n        parser = self.NO_PAREN_FUNCTION_PARSERS.get(upper)\n        if optional_parens and parser and token_type not in self.INVALID_FUNC_NAME_TOKENS:\n            self._advance()\n            return self._parse_window(parser(self))\n\n        if self._next.token_type != TokenType.L_PAREN:\n            if optional_parens and token_type in self.NO_PAREN_FUNCTIONS:\n                self._advance()\n                return self.expression(self.NO_PAREN_FUNCTIONS[token_type]())\n\n            return None\n\n        if any_token:\n            if token_type in self.RESERVED_TOKENS:\n                return None\n        elif token_type not in self.FUNC_TOKENS:\n            return None\n\n        self._advance(2)\n\n        parser = self.FUNCTION_PARSERS.get(upper)\n        if parser and not anonymous:\n            result = parser(self)\n        else:\n            subquery_predicate = self.SUBQUERY_PREDICATES.get(token_type)\n\n            if subquery_predicate:\n                expr = None\n                if self._curr.token_type in self.SUBQUERY_TOKENS:\n                    expr = self._parse_select()\n                    self._match_r_paren()\n                elif prev and prev.token_type in (TokenType.LIKE, TokenType.ILIKE):\n                    # Backtrack one token since we've consumed the L_PAREN here. Instead, we'd like\n                    # to parse \"LIKE [ANY | ALL] (...)\" as a whole into an exp.Tuple or exp.Paren\n                    self._advance(-1)\n                    expr = self._parse_bitwise()\n\n                if expr:\n                    return self.expression(subquery_predicate(this=expr), comments=comments)\n\n            if functions is None:\n                functions = self.FUNCTIONS\n\n            function = functions.get(upper)\n            known_function = function and not anonymous\n\n            alias = not known_function or upper in self.FUNCTIONS_WITH_ALIASED_ARGS\n            args = self._parse_function_args(alias)\n\n            post_func_comments = self._curr.comments if self._curr else None\n            if known_function and post_func_comments:\n                # If the user-inputted comment \"/* sqlglot.anonymous */\" is following the function\n                # call we'll construct it as exp.Anonymous, even if it's \"known\"\n                if any(\n                    comment.lstrip().startswith(exp.SQLGLOT_ANONYMOUS)\n                    for comment in post_func_comments\n                ):\n                    known_function = False\n\n            if alias and known_function:\n                args = self._kv_to_prop_eq(args)\n\n            if known_function:\n                func_builder = t.cast(t.Callable, function)\n\n                # mypyc compiled functions don't have __code__, so we use\n                # try/except to check if func_builder accepts 'dialect'.\n                try:\n                    func = func_builder(args)\n                except TypeError:\n                    func = func_builder(args, dialect=self.dialect)\n\n                func = self.validate_expression(func, args)\n                if self.dialect.PRESERVE_ORIGINAL_NAMES:\n                    func.meta[\"name\"] = this\n\n                result = func\n            else:\n                if token_type == TokenType.IDENTIFIER:\n                    this = exp.Identifier(this=this, quoted=True).update_positions(token)\n\n                result = self.expression(exp.Anonymous(this=this, expressions=args))\n\n            result = result.update_positions(token)\n\n        if isinstance(result, exp.Expr):\n            result.add_comments(comments)\n\n        if parser:\n            self._match(TokenType.R_PAREN, expression=result)\n        else:\n            self._match_r_paren(result)\n        return self._parse_window(result)\n\n    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:\n        return expression\n\n    def _kv_to_prop_eq(\n        self, expressions: t.List[exp.Expr], parse_map: bool = False\n    ) -> t.List[exp.Expr]:\n        transformed = []\n\n        for index, e in enumerate(expressions):\n            if isinstance(e, self.KEY_VALUE_DEFINITIONS):\n                if isinstance(e, exp.Alias):\n                    e = self.expression(exp.PropertyEQ(this=e.args.get(\"alias\"), expression=e.this))\n\n                if not isinstance(e, exp.PropertyEQ):\n                    e = self.expression(\n                        exp.PropertyEQ(\n                            this=e.this if parse_map else exp.to_identifier(e.this.name),\n                            expression=e.expression,\n                        )\n                    )\n\n                if isinstance(e.this, exp.Column):\n                    e.this.replace(e.this.this)\n            else:\n                e = self._to_prop_eq(e, index)\n\n            transformed.append(e)\n\n        return transformed\n\n    def _parse_user_defined_function_expression(self) -> t.Optional[exp.Expr]:\n        return self._parse_statement()\n\n    def _parse_function_parameter(self) -> t.Optional[exp.Expr]:\n        return self._parse_column_def(this=self._parse_id_var(), computed_column=False)\n\n    def _parse_user_defined_function(\n        self, kind: t.Optional[TokenType] = None\n    ) -> t.Optional[exp.Expr]:\n        this = self._parse_table_parts(schema=True)\n\n        if not self._match(TokenType.L_PAREN):\n            return this\n\n        expressions = self._parse_csv(self._parse_function_parameter)\n        self._match_r_paren()\n        return self.expression(\n            exp.UserDefinedFunction(this=this, expressions=expressions, wrapped=True)\n        )\n\n    def _parse_introducer(self, token: Token) -> exp.Introducer | exp.Identifier:\n        literal = self._parse_primary()\n        if literal:\n            return self.expression(exp.Introducer(this=token.text, expression=literal), token)\n\n        return self._identifier_expression(token)\n\n    def _parse_session_parameter(self) -> exp.SessionParameter:\n        kind = None\n        this = self._parse_id_var() or self._parse_primary()\n\n        if this and self._match(TokenType.DOT):\n            kind = this.name\n            this = self._parse_var() or self._parse_primary()\n\n        return self.expression(exp.SessionParameter(this=this, kind=kind))\n\n    def _parse_lambda_arg(self) -> t.Optional[exp.Expr]:\n        return self._parse_id_var()\n\n    def _parse_lambda(self, alias: bool = False) -> t.Optional[exp.Expr]:\n        next_token_type = self._next.token_type\n\n        # Fast path: simple atom (column, literal, null, bool) followed by , or )\n        if (\n            next_token_type in self.LAMBDA_ARG_TERMINATORS\n            and (atom := self._parse_atom()) is not None\n        ):\n            return atom\n\n        index = self._index\n\n        if self._match(TokenType.L_PAREN):\n            expressions = t.cast(\n                t.List[t.Optional[exp.Expr]], self._parse_csv(self._parse_lambda_arg)\n            )\n\n            if not self._match(TokenType.R_PAREN):\n                self._retreat(index)\n            elif self._match_set(self.LAMBDAS):\n                return self.LAMBDAS[self._prev.token_type](self, expressions)\n            else:\n                self._retreat(index)\n        elif self.TYPED_LAMBDA_ARGS or next_token_type in self.LAMBDAS:\n            expressions = [self._parse_lambda_arg()]\n\n            if self._match_set(self.LAMBDAS):\n                return self.LAMBDAS[self._prev.token_type](self, expressions)\n\n            self._retreat(index)\n\n        this: t.Optional[exp.Expr]\n\n        if self._match(TokenType.DISTINCT):\n            this = self.expression(\n                exp.Distinct(expressions=self._parse_csv(self._parse_disjunction))\n            )\n        else:\n            this = self._parse_select_or_expression(alias=alias)\n\n        return self._parse_limit(\n            self._parse_respect_or_ignore_nulls(\n                self._parse_order(self._parse_having_max(self._parse_respect_or_ignore_nulls(this)))\n            )\n        )\n\n    def _parse_schema(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        index = self._index\n        if not self._match(TokenType.L_PAREN):\n            return this\n\n        # Disambiguate between schema and subquery/CTE, e.g. in INSERT INTO table (<expr>),\n        # expr can be of both types\n        if self._match_set(self.SELECT_START_TOKENS):\n            self._retreat(index)\n            return this\n        args = self._parse_csv(lambda: self._parse_constraint() or self._parse_field_def())\n        self._match_r_paren()\n        return self.expression(exp.Schema(this=this, expressions=args))\n\n    def _parse_field_def(self) -> t.Optional[exp.Expr]:\n        return self._parse_column_def(self._parse_field(any_token=True))\n\n    def _parse_column_def(\n        self, this: t.Optional[exp.Expr], computed_column: bool = True\n    ) -> t.Optional[exp.Expr]:\n        # column defs are not really columns, they're identifiers\n        if isinstance(this, exp.Column):\n            this = this.this\n\n        if not computed_column:\n            self._match(TokenType.ALIAS)\n\n        kind = self._parse_types(schema=True)\n\n        if self._match_text_seq(\"FOR\", \"ORDINALITY\"):\n            return self.expression(exp.ColumnDef(this=this, ordinality=True))\n\n        constraints: t.List[exp.Expr] = []\n\n        if (not kind and self._match(TokenType.ALIAS)) or self._match_texts(\n            (\"ALIAS\", \"MATERIALIZED\")\n        ):\n            persisted = self._prev.text.upper() == \"MATERIALIZED\"\n            constraint_kind = exp.ComputedColumnConstraint(\n                this=self._parse_disjunction(),\n                persisted=persisted or self._match_text_seq(\"PERSISTED\"),\n                data_type=exp.Var(this=\"AUTO\")\n                if self._match_text_seq(\"AUTO\")\n                else self._parse_types(),\n                not_null=self._match_pair(TokenType.NOT, TokenType.NULL),\n            )\n            constraints.append(self.expression(exp.ColumnConstraint(kind=constraint_kind)))\n        elif not kind and self._match_set({TokenType.IN, TokenType.OUT}, advance=False):\n            in_out_constraint = self.expression(\n                exp.InOutColumnConstraint(\n                    input_=self._match(TokenType.IN), output=self._match(TokenType.OUT)\n                )\n            )\n            constraints.append(in_out_constraint)\n            kind = self._parse_types()\n        elif (\n            kind\n            and self._match(TokenType.ALIAS, advance=False)\n            and (\n                not self.WRAPPED_TRANSFORM_COLUMN_CONSTRAINT\n                or self._next.token_type == TokenType.L_PAREN\n            )\n        ):\n            self._advance()\n            constraints.append(\n                self.expression(\n                    exp.ColumnConstraint(\n                        kind=exp.ComputedColumnConstraint(\n                            this=self._parse_disjunction(),\n                            persisted=self._match_texts((\"STORED\", \"VIRTUAL\"))\n                            and self._prev.text.upper() == \"STORED\",\n                        )\n                    )\n                )\n            )\n\n        while True:\n            constraint = self._parse_column_constraint()\n            if not constraint:\n                break\n            constraints.append(constraint)\n\n        if not kind and not constraints:\n            return this\n\n        return self.expression(exp.ColumnDef(this=this, kind=kind, constraints=constraints))\n\n    def _parse_auto_increment(\n        self,\n    ) -> exp.GeneratedAsIdentityColumnConstraint | exp.AutoIncrementColumnConstraint:\n        start = None\n        increment = None\n        order = None\n\n        if self._match(TokenType.L_PAREN, advance=False):\n            args = self._parse_wrapped_csv(self._parse_bitwise)\n            start = seq_get(args, 0)\n            increment = seq_get(args, 1)\n        elif self._match_text_seq(\"START\"):\n            start = self._parse_bitwise()\n            self._match_text_seq(\"INCREMENT\")\n            increment = self._parse_bitwise()\n            if self._match_text_seq(\"ORDER\"):\n                order = True\n            elif self._match_text_seq(\"NOORDER\"):\n                order = False\n\n        if start and increment:\n            return exp.GeneratedAsIdentityColumnConstraint(\n                start=start, increment=increment, this=False, order=order\n            )\n\n        return exp.AutoIncrementColumnConstraint()\n\n    def _parse_check_constraint(self) -> t.Optional[exp.CheckColumnConstraint]:\n        if not self._match(TokenType.L_PAREN, advance=False):\n            return None\n\n        return self.expression(\n            exp.CheckColumnConstraint(\n                this=self._parse_wrapped(self._parse_assignment),\n                enforced=self._match_text_seq(\"ENFORCED\"),\n            )\n        )\n\n    def _parse_auto_property(self) -> t.Optional[exp.AutoRefreshProperty]:\n        if not self._match_text_seq(\"REFRESH\"):\n            self._retreat(self._index - 1)\n            return None\n        return self.expression(exp.AutoRefreshProperty(this=self._parse_var(upper=True)))\n\n    def _parse_compress(self) -> exp.CompressColumnConstraint:\n        if self._match(TokenType.L_PAREN, advance=False):\n            return self.expression(\n                exp.CompressColumnConstraint(this=self._parse_wrapped_csv(self._parse_bitwise))\n            )\n\n        return self.expression(exp.CompressColumnConstraint(this=self._parse_bitwise()))\n\n    def _parse_generated_as_identity(\n        self,\n    ) -> (\n        exp.GeneratedAsIdentityColumnConstraint\n        | exp.ComputedColumnConstraint\n        | exp.GeneratedAsRowColumnConstraint\n    ):\n        if self._match_text_seq(\"BY\", \"DEFAULT\"):\n            on_null = self._match_pair(TokenType.ON, TokenType.NULL)\n            this = self.expression(\n                exp.GeneratedAsIdentityColumnConstraint(this=False, on_null=on_null)\n            )\n        else:\n            self._match_text_seq(\"ALWAYS\")\n            this = self.expression(exp.GeneratedAsIdentityColumnConstraint(this=True))\n\n        self._match(TokenType.ALIAS)\n\n        if self._match_text_seq(\"ROW\"):\n            start = self._match_text_seq(\"START\")\n            if not start:\n                self._match(TokenType.END)\n            hidden = self._match_text_seq(\"HIDDEN\")\n            return self.expression(exp.GeneratedAsRowColumnConstraint(start=start, hidden=hidden))\n\n        identity = self._match_text_seq(\"IDENTITY\")\n\n        if self._match(TokenType.L_PAREN):\n            if self._match(TokenType.START_WITH):\n                this.set(\"start\", self._parse_bitwise())\n            if self._match_text_seq(\"INCREMENT\", \"BY\"):\n                this.set(\"increment\", self._parse_bitwise())\n            if self._match_text_seq(\"MINVALUE\"):\n                this.set(\"minvalue\", self._parse_bitwise())\n            if self._match_text_seq(\"MAXVALUE\"):\n                this.set(\"maxvalue\", self._parse_bitwise())\n\n            if self._match_text_seq(\"CYCLE\"):\n                this.set(\"cycle\", True)\n            elif self._match_text_seq(\"NO\", \"CYCLE\"):\n                this.set(\"cycle\", False)\n\n            if not identity:\n                this.set(\"expression\", self._parse_range())\n            elif not this.args.get(\"start\") and self._match(TokenType.NUMBER, advance=False):\n                args = self._parse_csv(self._parse_bitwise)\n                this.set(\"start\", seq_get(args, 0))\n                this.set(\"increment\", seq_get(args, 1))\n\n            self._match_r_paren()\n\n        return this\n\n    def _parse_inline(self) -> exp.InlineLengthColumnConstraint:\n        self._match_text_seq(\"LENGTH\")\n        return self.expression(exp.InlineLengthColumnConstraint(this=self._parse_bitwise()))\n\n    def _parse_not_constraint(self) -> t.Optional[exp.Expr]:\n        if self._match_text_seq(\"NULL\"):\n            return self.expression(exp.NotNullColumnConstraint())\n        if self._match_text_seq(\"CASESPECIFIC\"):\n            return self.expression(exp.CaseSpecificColumnConstraint(not_=True))\n        if self._match_text_seq(\"FOR\", \"REPLICATION\"):\n            return self.expression(exp.NotForReplicationColumnConstraint())\n\n        # Unconsume the `NOT` token\n        self._retreat(self._index - 1)\n        return None\n\n    def _parse_column_constraint(self) -> t.Optional[exp.Expr]:\n        this = self._parse_id_var() if self._match(TokenType.CONSTRAINT) else None\n\n        procedure_option_follows = (\n            self._match(TokenType.WITH, advance=False)\n            and self._next\n            and self._next.text.upper() in self.PROCEDURE_OPTIONS\n        )\n\n        if not procedure_option_follows and self._match_texts(self.CONSTRAINT_PARSERS):\n            constraint = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)\n            if not constraint:\n                self._retreat(self._index - 1)\n                return None\n\n            return self.expression(exp.ColumnConstraint(this=this, kind=constraint))\n\n        return this\n\n    def _parse_constraint(self) -> t.Optional[exp.Expr]:\n        if not self._match(TokenType.CONSTRAINT):\n            return self._parse_unnamed_constraint(constraints=self.SCHEMA_UNNAMED_CONSTRAINTS)\n\n        return self.expression(\n            exp.Constraint(this=self._parse_id_var(), expressions=self._parse_unnamed_constraints())\n        )\n\n    def _parse_unnamed_constraints(self) -> t.List[exp.Expr]:\n        constraints = []\n        while True:\n            constraint = self._parse_unnamed_constraint() or self._parse_function()\n            if not constraint:\n                break\n            constraints.append(constraint)\n\n        return constraints\n\n    def _parse_unnamed_constraint(\n        self, constraints: t.Optional[t.Collection[str]] = None\n    ) -> t.Optional[exp.Expr]:\n        index = self._index\n\n        if self._match(TokenType.IDENTIFIER, advance=False) or not self._match_texts(\n            constraints or self.CONSTRAINT_PARSERS\n        ):\n            return None\n\n        constraint_key = self._prev.text.upper()\n        if constraint_key not in self.CONSTRAINT_PARSERS:\n            self.raise_error(f\"No parser found for schema constraint {constraint_key}.\")\n\n        result = self.CONSTRAINT_PARSERS[constraint_key](self)\n        if not result:\n            self._retreat(index)\n\n        return result\n\n    def _parse_unique_key(self) -> t.Optional[exp.Expr]:\n        if self._curr and self._curr.text.upper() in self.CONSTRAINT_PARSERS:\n            return None\n        return self._parse_id_var(any_token=False)\n\n    def _parse_unique(self) -> exp.UniqueColumnConstraint:\n        self._match_texts((\"KEY\", \"INDEX\"))\n        return self.expression(\n            exp.UniqueColumnConstraint(\n                nulls=self._match_text_seq(\"NULLS\", \"NOT\", \"DISTINCT\"),\n                this=self._parse_schema(self._parse_unique_key()),\n                index_type=self._match(TokenType.USING) and self._advance_any() and self._prev.text,\n                on_conflict=self._parse_on_conflict(),\n                options=self._parse_key_constraint_options(),\n            )\n        )\n\n    def _parse_key_constraint_options(self) -> t.List[str]:\n        options = []\n        while True:\n            if not self._curr:\n                break\n\n            if self._match(TokenType.ON):\n                action = None\n                on = self._advance_any() and self._prev.text\n\n                if self._match_text_seq(\"NO\", \"ACTION\"):\n                    action = \"NO ACTION\"\n                elif self._match_text_seq(\"CASCADE\"):\n                    action = \"CASCADE\"\n                elif self._match_text_seq(\"RESTRICT\"):\n                    action = \"RESTRICT\"\n                elif self._match_pair(TokenType.SET, TokenType.NULL):\n                    action = \"SET NULL\"\n                elif self._match_pair(TokenType.SET, TokenType.DEFAULT):\n                    action = \"SET DEFAULT\"\n                else:\n                    self.raise_error(\"Invalid key constraint\")\n\n                options.append(f\"ON {on} {action}\")\n            else:\n                var = self._parse_var_from_options(\n                    self.KEY_CONSTRAINT_OPTIONS, raise_unmatched=False\n                )\n                if not var:\n                    break\n                options.append(var.name)\n\n        return options\n\n    def _parse_references(self, match: bool = True) -> t.Optional[exp.Reference]:\n        if match and not self._match(TokenType.REFERENCES):\n            return None\n\n        expressions: t.Optional[t.List] = None\n        this = self._parse_table(schema=True)\n        options = self._parse_key_constraint_options()\n        return self.expression(exp.Reference(this=this, expressions=expressions, options=options))\n\n    def _parse_foreign_key(self) -> exp.ForeignKey:\n        expressions = (\n            self._parse_wrapped_id_vars()\n            if not self._match(TokenType.REFERENCES, advance=False)\n            else None\n        )\n        reference = self._parse_references()\n        on_options = {}\n\n        while self._match(TokenType.ON):\n            if not self._match_set((TokenType.DELETE, TokenType.UPDATE)):\n                self.raise_error(\"Expected DELETE or UPDATE\")\n\n            kind = self._prev.text.lower()\n\n            if self._match_text_seq(\"NO\", \"ACTION\"):\n                action = \"NO ACTION\"\n            elif self._match(TokenType.SET):\n                self._match_set((TokenType.NULL, TokenType.DEFAULT))\n                action = \"SET \" + self._prev.text.upper()\n            else:\n                self._advance()\n                action = self._prev.text.upper()\n\n            on_options[kind] = action\n\n        return self.expression(\n            exp.ForeignKey(\n                expressions=expressions,\n                reference=reference,\n                options=self._parse_key_constraint_options(),\n                **on_options,\n            )\n        )\n\n    def _parse_primary_key_part(self) -> t.Optional[exp.Expr]:\n        return self._parse_field()\n\n    def _parse_period_for_system_time(self) -> t.Optional[exp.PeriodForSystemTimeConstraint]:\n        if not self._match(TokenType.TIMESTAMP_SNAPSHOT):\n            self._retreat(self._index - 1)\n            return None\n\n        id_vars = self._parse_wrapped_id_vars()\n        return self.expression(\n            exp.PeriodForSystemTimeConstraint(\n                this=seq_get(id_vars, 0), expression=seq_get(id_vars, 1)\n            )\n        )\n\n    def _parse_primary_key(\n        self,\n        wrapped_optional: bool = False,\n        in_props: bool = False,\n        named_primary_key: bool = False,\n    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:\n        desc = (\n            self._prev.token_type == TokenType.DESC\n            if self._match_set((TokenType.ASC, TokenType.DESC))\n            else None\n        )\n\n        this = None\n        if (\n            named_primary_key\n            and self._curr.text.upper() not in self.CONSTRAINT_PARSERS\n            and self._next\n            and self._next.token_type == TokenType.L_PAREN\n        ):\n            this = self._parse_id_var()\n\n        if not in_props and not self._match(TokenType.L_PAREN, advance=False):\n            return self.expression(\n                exp.PrimaryKeyColumnConstraint(\n                    desc=desc, options=self._parse_key_constraint_options()\n                )\n            )\n\n        expressions = self._parse_wrapped_csv(\n            self._parse_primary_key_part, optional=wrapped_optional\n        )\n\n        return self.expression(\n            exp.PrimaryKey(\n                this=this,\n                expressions=expressions,\n                include=self._parse_index_params(),\n                options=self._parse_key_constraint_options(),\n            )\n        )\n\n    def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expr]:\n        return self._parse_slice(self._parse_alias(self._parse_disjunction(), explicit=True))\n\n    def _parse_odbc_datetime_literal(self) -> exp.Expr:\n        \"\"\"\n        Parses a datetime column in ODBC format. We parse the column into the corresponding\n        types, for example `{d'yyyy-mm-dd'}` will be parsed as a `Date` column, exactly the\n        same as we did for `DATE('yyyy-mm-dd')`.\n\n        Reference:\n        https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/date-time-and-timestamp-literals\n        \"\"\"\n        self._match(TokenType.VAR)\n        exp_class = self.ODBC_DATETIME_LITERALS[self._prev.text.lower()]\n        expression = self.expression(exp_class(this=self._parse_string()))\n        if not self._match(TokenType.R_BRACE):\n            self.raise_error(\"Expected }\")\n        return expression\n\n    def _parse_bracket(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        if not self._match_set(self.BRACKETS):\n            return this\n\n        if self.MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS:\n            map_token = seq_get(self._tokens, self._index - 2)\n            parse_map = map_token is not None and map_token.text.upper() == \"MAP\"\n        else:\n            parse_map = False\n\n        bracket_kind = self._prev.token_type\n        if (\n            bracket_kind == TokenType.L_BRACE\n            and self._curr\n            and self._curr.token_type == TokenType.VAR\n            and self._curr.text.lower() in self.ODBC_DATETIME_LITERALS\n        ):\n            return self._parse_odbc_datetime_literal()\n\n        expressions = self._parse_csv(\n            lambda: self._parse_bracket_key_value(is_map=bracket_kind == TokenType.L_BRACE)\n        )\n\n        if bracket_kind == TokenType.L_BRACKET and not self._match(TokenType.R_BRACKET):\n            self.raise_error(\"Expected ]\")\n        elif bracket_kind == TokenType.L_BRACE and not self._match(TokenType.R_BRACE):\n            self.raise_error(\"Expected }\")\n\n        # https://duckdb.org/docs/sql/data_types/struct.html#creating-structs\n        if bracket_kind == TokenType.L_BRACE:\n            this = self.expression(\n                exp.Struct(\n                    expressions=self._kv_to_prop_eq(expressions=expressions, parse_map=parse_map)\n                )\n            )\n        elif not this:\n            this = build_array_constructor(\n                exp.Array, args=expressions, bracket_kind=bracket_kind, dialect=self.dialect\n            )\n        else:\n            constructor_type = self.ARRAY_CONSTRUCTORS.get(this.name.upper())\n            if constructor_type:\n                return build_array_constructor(\n                    constructor_type,\n                    args=expressions,\n                    bracket_kind=bracket_kind,\n                    dialect=self.dialect,\n                )\n\n            expressions = apply_index_offset(\n                this, expressions, -self.dialect.INDEX_OFFSET, dialect=self.dialect\n            )\n            this = self.expression(\n                exp.Bracket(this=this, expressions=expressions), comments=this.pop_comments()\n            )\n\n        self._add_comments(this)\n        return self._parse_bracket(this)\n\n    def _parse_slice(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        if not self._match(TokenType.COLON):\n            return this\n\n        if self._match_pair(TokenType.DASH, TokenType.COLON, advance=False):\n            self._advance()\n            end: t.Optional[exp.Expr] = -exp.Literal.number(\"1\")\n        else:\n            end = self._parse_assignment()\n        step = self._parse_unary() if self._match(TokenType.COLON) else None\n        return self.expression(exp.Slice(this=this, expression=end, step=step))\n\n    def _parse_case(self) -> t.Optional[exp.Expr]:\n        if self._match(TokenType.DOT, advance=False):\n            # Avoid raising on valid expressions like case.*, supported by, e.g., spark & snowflake\n            self._retreat(self._index - 1)\n            return None\n\n        ifs = []\n        default = None\n\n        comments = self._prev_comments\n        expression = self._parse_disjunction()\n\n        while self._match(TokenType.WHEN):\n            this = self._parse_disjunction()\n            self._match(TokenType.THEN)\n            then = self._parse_disjunction()\n            ifs.append(self.expression(exp.If(this=this, true=then)))\n\n        if self._match(TokenType.ELSE):\n            default = self._parse_disjunction()\n\n        if not self._match(TokenType.END):\n            if isinstance(default, exp.Interval) and default.this.sql().upper() == \"END\":\n                default = exp.column(\"interval\")\n            else:\n                self.raise_error(\"Expected END after CASE\", self._prev)\n\n        return self.expression(\n            exp.Case(this=expression, ifs=ifs, default=default), comments=comments\n        )\n\n    def _parse_if(self) -> t.Optional[exp.Expr]:\n        if self._match(TokenType.L_PAREN):\n            args = self._parse_csv(\n                lambda: self._parse_alias(self._parse_assignment(), explicit=True)\n            )\n            this = self.validate_expression(exp.If.from_arg_list(args), args)\n            self._match_r_paren()\n        else:\n            index = self._index - 1\n\n            if self.NO_PAREN_IF_COMMANDS and index == 0:\n                return self._parse_as_command(self._prev)\n\n            condition = self._parse_disjunction()\n\n            if not condition:\n                self._retreat(index)\n                return None\n\n            self._match(TokenType.THEN)\n            true = self._parse_disjunction()\n            false = self._parse_disjunction() if self._match(TokenType.ELSE) else None\n            self._match(TokenType.END)\n            this = self.expression(exp.If(this=condition, true=true, false=false))\n\n        return this\n\n    def _parse_next_value_for(self) -> t.Optional[exp.Expr]:\n        if not self._match_text_seq(\"VALUE\", \"FOR\"):\n            self._retreat(self._index - 1)\n            return None\n\n        return self.expression(\n            exp.NextValueFor(\n                this=self._parse_column(),\n                order=self._match(TokenType.OVER) and self._parse_wrapped(self._parse_order),\n            )\n        )\n\n    def _parse_extract(self) -> exp.Extract | exp.Anonymous:\n        this = self._parse_function() or self._parse_var_or_string(upper=True)\n\n        if self._match(TokenType.FROM):\n            return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))\n\n        if not self._match(TokenType.COMMA):\n            self.raise_error(\"Expected FROM or comma after EXTRACT\", self._prev)\n\n        return self.expression(exp.Extract(this=this, expression=self._parse_bitwise()))\n\n    def _parse_gap_fill(self) -> exp.GapFill:\n        self._match(TokenType.TABLE)\n        this = self._parse_table()\n\n        self._match(TokenType.COMMA)\n        args = [this, *self._parse_csv(self._parse_lambda)]\n\n        gap_fill = exp.GapFill.from_arg_list(args)\n        return self.validate_expression(gap_fill, args)\n\n    def _parse_char(self) -> exp.Chr:\n        return self.expression(\n            exp.Chr(\n                expressions=self._parse_csv(self._parse_assignment),\n                charset=self._match(TokenType.USING) and self._parse_var(),\n            )\n        )\n\n    def _parse_cast(self, strict: bool, safe: t.Optional[bool] = None) -> exp.Expr:\n        this = self._parse_assignment()\n\n        if not self._match(TokenType.ALIAS):\n            if self._match(TokenType.COMMA):\n                return self.expression(exp.CastToStrType(this=this, to=self._parse_string()))\n\n            self.raise_error(\"Expected AS after CAST\")\n\n        fmt = None\n        to = self._parse_types()\n\n        default = None\n        if self._match(TokenType.DEFAULT):\n            default = self._parse_bitwise()\n            self._match_text_seq(\"ON\", \"CONVERSION\", \"ERROR\")\n\n        if self._match_set((TokenType.FORMAT, TokenType.COMMA)):\n            fmt_string = self._parse_string()\n            fmt = self._parse_at_time_zone(fmt_string)\n\n            if not to:\n                to = exp.DataType.build(exp.DType.UNKNOWN)\n            if to.this in exp.DataType.TEMPORAL_TYPES:\n                this = self.expression(\n                    (exp.StrToDate if to.this == exp.DType.DATE else exp.StrToTime)(\n                        this=this,\n                        format=exp.Literal.string(\n                            format_time(\n                                fmt_string.this if fmt_string else \"\",\n                                self.dialect.FORMAT_MAPPING or self.dialect.TIME_MAPPING,\n                                self.dialect.FORMAT_TRIE or self.dialect.TIME_TRIE,\n                            )\n                        ),\n                        safe=safe,\n                    )\n                )\n\n                if isinstance(fmt, exp.AtTimeZone) and isinstance(this, exp.StrToTime):\n                    this.set(\"zone\", fmt.args[\"zone\"])\n                return this\n        elif not to:\n            self.raise_error(\"Expected TYPE after CAST\")\n        elif isinstance(to, exp.Identifier):\n            to = exp.DataType.build(to.name, dialect=self.dialect, udt=True)\n        elif to.this == exp.DType.CHAR and self._match(TokenType.CHARACTER_SET):\n            to = exp.DataType.build(exp.DType.CHARACTER_SET, kind=self._parse_var_or_string())\n\n        return self.build_cast(\n            strict=strict,\n            this=this,\n            to=to,\n            format=fmt,\n            safe=safe,\n            action=self._parse_var_from_options(self.CAST_ACTIONS, raise_unmatched=False),\n            default=default,\n        )\n\n    def _parse_string_agg(self) -> exp.GroupConcat:\n        if self._match(TokenType.DISTINCT):\n            args: t.List[t.Optional[exp.Expr]] = [\n                self.expression(exp.Distinct(expressions=[self._parse_disjunction()]))\n            ]\n            if self._match(TokenType.COMMA):\n                args.extend(self._parse_csv(self._parse_disjunction))\n        else:\n            args = self._parse_csv(self._parse_disjunction)  # type: ignore\n\n        if self._match_text_seq(\"ON\", \"OVERFLOW\"):\n            # trino: LISTAGG(expression [, separator] [ON OVERFLOW overflow_behavior])\n            if self._match_text_seq(\"ERROR\"):\n                on_overflow: t.Optional[exp.Expr] = exp.var(\"ERROR\")\n            else:\n                self._match_text_seq(\"TRUNCATE\")\n                on_overflow = self.expression(\n                    exp.OverflowTruncateBehavior(\n                        this=self._parse_string(),\n                        with_count=(\n                            self._match_text_seq(\"WITH\", \"COUNT\")\n                            or not self._match_text_seq(\"WITHOUT\", \"COUNT\")\n                        ),\n                    )\n                )\n        else:\n            on_overflow = None\n\n        index = self._index\n        if not self._match(TokenType.R_PAREN) and args:\n            # postgres: STRING_AGG([DISTINCT] expression, separator [ORDER BY expression1 {ASC | DESC} [, ...]])\n            # bigquery: STRING_AGG([DISTINCT] expression [, separator] [ORDER BY key [{ASC | DESC}] [, ... ]] [LIMIT n])\n            # The order is parsed through `this` as a canonicalization for WITHIN GROUPs\n            args[0] = self._parse_limit(this=self._parse_order(this=args[0]))\n            return self.expression(exp.GroupConcat(this=args[0], separator=seq_get(args, 1)))\n\n        # Checks if we can parse an order clause: WITHIN GROUP (ORDER BY <order_by_expression_list> [ASC | DESC]).\n        # This is done \"manually\", instead of letting _parse_window parse it into an exp.WithinGroup node, so that\n        # the STRING_AGG call is parsed like in MySQL / SQLite and can thus be transpiled more easily to them.\n        if not self._match_text_seq(\"WITHIN\", \"GROUP\"):\n            self._retreat(index)\n            return self.validate_expression(exp.GroupConcat.from_arg_list(args), args)\n\n        # The corresponding match_r_paren will be called in parse_function (caller)\n        self._match_l_paren()\n\n        return self.expression(\n            exp.GroupConcat(\n                this=self._parse_order(this=seq_get(args, 0)),\n                separator=seq_get(args, 1),\n                on_overflow=on_overflow,\n            )\n        )\n\n    def _parse_convert(self, strict: bool, safe: t.Optional[bool] = None) -> t.Optional[exp.Expr]:\n        this = self._parse_bitwise()\n\n        if self._match(TokenType.USING):\n            to: t.Optional[exp.Expr] = exp.DataType.build(\n                exp.DType.CHARACTER_SET,\n                kind=self._parse_var(tokens={TokenType.BINARY}),\n            )\n        elif self._match(TokenType.COMMA):\n            to = self._parse_types()\n        else:\n            to = None\n\n        return self.build_cast(strict=strict, this=this, to=to, safe=safe)\n\n    def _parse_xml_element(self) -> exp.XMLElement:\n        if self._match_text_seq(\"EVALNAME\"):\n            evalname = True\n            this = self._parse_bitwise()\n        else:\n            evalname = None\n            self._match_text_seq(\"NAME\")\n            this = self._parse_id_var()\n\n        return self.expression(\n            exp.XMLElement(\n                this=this,\n                expressions=self._match(TokenType.COMMA) and self._parse_csv(self._parse_bitwise),\n                evalname=evalname,\n            )\n        )\n\n    def _parse_xml_table(self) -> exp.XMLTable:\n        namespaces = None\n        passing = None\n        columns = None\n\n        if self._match_text_seq(\"XMLNAMESPACES\", \"(\"):\n            namespaces = self._parse_xml_namespace()\n            self._match_text_seq(\")\", \",\")\n\n        this = self._parse_string()\n\n        if self._match_text_seq(\"PASSING\"):\n            # The BY VALUE keywords are optional and are provided for semantic clarity\n            self._match_text_seq(\"BY\", \"VALUE\")\n            passing = self._parse_csv(self._parse_column)\n\n        by_ref = self._match_text_seq(\"RETURNING\", \"SEQUENCE\", \"BY\", \"REF\")\n\n        if self._match_text_seq(\"COLUMNS\"):\n            columns = self._parse_csv(self._parse_field_def)\n\n        return self.expression(\n            exp.XMLTable(\n                this=this, namespaces=namespaces, passing=passing, columns=columns, by_ref=by_ref\n            )\n        )\n\n    def _parse_xml_namespace(self) -> t.List[exp.XMLNamespace]:\n        namespaces = []\n\n        while True:\n            if self._match(TokenType.DEFAULT):\n                uri = self._parse_string()\n            else:\n                uri = self._parse_alias(self._parse_string())\n            namespaces.append(self.expression(exp.XMLNamespace(this=uri)))\n            if not self._match(TokenType.COMMA):\n                break\n\n        return namespaces\n\n    def _parse_decode(self) -> t.Optional[exp.Decode | exp.DecodeCase]:\n        args = self._parse_csv(self._parse_disjunction)\n\n        if len(args) < 3:\n            return self.expression(exp.Decode(this=seq_get(args, 0), charset=seq_get(args, 1)))\n\n        return self.expression(exp.DecodeCase(expressions=args))\n\n    def _parse_json_key_value(self) -> t.Optional[exp.JSONKeyValue]:\n        self._match_text_seq(\"KEY\")\n        key = self._parse_column()\n        self._match_set(self.JSON_KEY_VALUE_SEPARATOR_TOKENS)\n        self._match_text_seq(\"VALUE\")\n        value = self._parse_bitwise()\n\n        if not key and not value:\n            return None\n        return self.expression(exp.JSONKeyValue(this=key, expression=value))\n\n    def _parse_format_json(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        if not this or not self._match_text_seq(\"FORMAT\", \"JSON\"):\n            return this\n\n        return self.expression(exp.FormatJson(this=this))\n\n    def _parse_on_condition(self) -> t.Optional[exp.OnCondition]:\n        # MySQL uses \"X ON EMPTY Y ON ERROR\" (e.g. JSON_VALUE) while Oracle uses the opposite (e.g. JSON_EXISTS)\n        if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR:\n            empty = self._parse_on_handling(\"EMPTY\", *self.ON_CONDITION_TOKENS)\n            error = self._parse_on_handling(\"ERROR\", *self.ON_CONDITION_TOKENS)\n        else:\n            error = self._parse_on_handling(\"ERROR\", *self.ON_CONDITION_TOKENS)\n            empty = self._parse_on_handling(\"EMPTY\", *self.ON_CONDITION_TOKENS)\n\n        null = self._parse_on_handling(\"NULL\", *self.ON_CONDITION_TOKENS)\n\n        if not empty and not error and not null:\n            return None\n\n        return self.expression(exp.OnCondition(empty=empty, error=error, null=null))\n\n    def _parse_on_handling(self, on: str, *values: str) -> t.Optional[str] | t.Optional[exp.Expr]:\n        # Parses the \"X ON Y\" or \"DEFAULT <expr> ON Y syntax, e.g. NULL ON NULL (Oracle, T-SQL, MySQL)\n        for value in values:\n            if self._match_text_seq(value, \"ON\", on):\n                return f\"{value} ON {on}\"\n\n        index = self._index\n        if self._match(TokenType.DEFAULT):\n            default_value = self._parse_bitwise()\n            if self._match_text_seq(\"ON\", on):\n                return default_value\n\n            self._retreat(index)\n\n        return None\n\n    @t.overload\n    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...\n\n    @t.overload\n    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...\n\n    def _parse_json_object(self, agg=False):\n        star = self._parse_star()\n        expressions = (\n            [star]\n            if star\n            else self._parse_csv(lambda: self._parse_format_json(self._parse_json_key_value()))\n        )\n        null_handling = self._parse_on_handling(\"NULL\", \"NULL\", \"ABSENT\")\n\n        unique_keys = None\n        if self._match_text_seq(\"WITH\", \"UNIQUE\"):\n            unique_keys = True\n        elif self._match_text_seq(\"WITHOUT\", \"UNIQUE\"):\n            unique_keys = False\n\n        self._match_text_seq(\"KEYS\")\n\n        return_type = self._match_text_seq(\"RETURNING\") and self._parse_format_json(\n            self._parse_type()\n        )\n        encoding = self._match_text_seq(\"ENCODING\") and self._parse_var()\n\n        return self.expression(\n            (exp.JSONObjectAgg if agg else exp.JSONObject)(\n                expressions=expressions,\n                null_handling=null_handling,\n                unique_keys=unique_keys,\n                return_type=return_type,\n                encoding=encoding,\n            )\n        )\n\n    # Note: this is currently incomplete; it only implements the \"JSON_value_column\" part\n    def _parse_json_column_def(self) -> exp.JSONColumnDef:\n        if not self._match_text_seq(\"NESTED\"):\n            this = self._parse_id_var()\n            ordinality = self._match_pair(TokenType.FOR, TokenType.ORDINALITY)\n            kind = self._parse_types(allow_identifiers=False)\n            nested = None\n        else:\n            this = None\n            ordinality = None\n            kind = None\n            nested = True\n\n        path = self._match_text_seq(\"PATH\") and self._parse_string()\n        nested_schema = nested and self._parse_json_schema()\n\n        return self.expression(\n            exp.JSONColumnDef(\n                this=this, kind=kind, path=path, nested_schema=nested_schema, ordinality=ordinality\n            )\n        )\n\n    def _parse_json_schema(self) -> exp.JSONSchema:\n        self._match_text_seq(\"COLUMNS\")\n        return self.expression(\n            exp.JSONSchema(\n                expressions=self._parse_wrapped_csv(self._parse_json_column_def, optional=True)\n            )\n        )\n\n    def _parse_json_table(self) -> exp.JSONTable:\n        this = self._parse_format_json(self._parse_bitwise())\n        path = self._match(TokenType.COMMA) and self._parse_string()\n        error_handling = self._parse_on_handling(\"ERROR\", \"ERROR\", \"NULL\")\n        empty_handling = self._parse_on_handling(\"EMPTY\", \"ERROR\", \"NULL\")\n        schema = self._parse_json_schema()\n\n        return exp.JSONTable(\n            this=this,\n            schema=schema,\n            path=path,\n            error_handling=error_handling,\n            empty_handling=empty_handling,\n        )\n\n    def _parse_match_against(self) -> exp.MatchAgainst:\n        if self._match_text_seq(\"TABLE\"):\n            # parse SingleStore MATCH(TABLE ...) syntax\n            # https://docs.singlestore.com/cloud/reference/sql-reference/full-text-search-functions/match/\n            expressions = []\n            table = self._parse_table()\n            if table:\n                expressions = [table]\n        else:\n            expressions = self._parse_csv(self._parse_column)\n\n        self._match_text_seq(\")\", \"AGAINST\", \"(\")\n\n        this = self._parse_string()\n\n        if self._match_text_seq(\"IN\", \"NATURAL\", \"LANGUAGE\", \"MODE\"):\n            modifier = \"IN NATURAL LANGUAGE MODE\"\n            if self._match_text_seq(\"WITH\", \"QUERY\", \"EXPANSION\"):\n                modifier = f\"{modifier} WITH QUERY EXPANSION\"\n        elif self._match_text_seq(\"IN\", \"BOOLEAN\", \"MODE\"):\n            modifier = \"IN BOOLEAN MODE\"\n        elif self._match_text_seq(\"WITH\", \"QUERY\", \"EXPANSION\"):\n            modifier = \"WITH QUERY EXPANSION\"\n        else:\n            modifier = None\n\n        return self.expression(\n            exp.MatchAgainst(this=this, expressions=expressions, modifier=modifier)\n        )\n\n    # https://learn.microsoft.com/en-us/sql/t-sql/functions/openjson-transact-sql?view=sql-server-ver16\n    def _parse_open_json(self) -> exp.OpenJSON:\n        this = self._parse_bitwise()\n        path = self._match(TokenType.COMMA) and self._parse_string()\n\n        def _parse_open_json_column_def() -> exp.OpenJSONColumnDef:\n            this = self._parse_field(any_token=True)\n            kind = self._parse_types()\n            path = self._parse_string()\n            as_json = self._match_pair(TokenType.ALIAS, TokenType.JSON)\n\n            return self.expression(\n                exp.OpenJSONColumnDef(this=this, kind=kind, path=path, as_json=as_json)\n            )\n\n        expressions = None\n        if self._match_pair(TokenType.R_PAREN, TokenType.WITH):\n            self._match_l_paren()\n            expressions = self._parse_csv(_parse_open_json_column_def)\n\n        return self.expression(exp.OpenJSON(this=this, path=path, expressions=expressions))\n\n    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:\n        args = self._parse_csv(self._parse_bitwise)\n\n        if self._match(TokenType.IN):\n            return self.expression(\n                exp.StrPosition(this=self._parse_bitwise(), substr=seq_get(args, 0))\n            )\n\n        if haystack_first:\n            haystack = seq_get(args, 0)\n            needle = seq_get(args, 1)\n        else:\n            haystack = seq_get(args, 1)\n            needle = seq_get(args, 0)\n\n        return self.expression(\n            exp.StrPosition(this=haystack, substr=needle, position=seq_get(args, 2))\n        )\n\n    def _parse_join_hint(self, func_name: str) -> exp.JoinHint:\n        args = self._parse_csv(self._parse_table)\n        return exp.JoinHint(this=func_name.upper(), expressions=args)\n\n    def _parse_substring(self) -> exp.Substring:\n        # Postgres supports the form: substring(string [from int] [for int])\n        # (despite being undocumented, the reverse order also works)\n        # https://www.postgresql.org/docs/9.1/functions-string.html @ Table 9-6\n\n        args = t.cast(t.List[t.Optional[exp.Expr]], self._parse_csv(self._parse_bitwise))\n\n        start, length = None, None\n\n        while self._curr:\n            if self._match(TokenType.FROM):\n                start = self._parse_bitwise()\n            elif self._match(TokenType.FOR):\n                if not start:\n                    start = exp.Literal.number(1)\n                length = self._parse_bitwise()\n            else:\n                break\n\n        if start:\n            args.append(start)\n        if length:\n            args.append(length)\n\n        return self.validate_expression(exp.Substring.from_arg_list(args), args)\n\n    def _parse_trim(self) -> exp.Trim:\n        # https://www.w3resource.com/sql/character-functions/trim.php\n        # https://docs.oracle.com/javadb/10.8.3.0/ref/rreftrimfunc.html\n\n        position = None\n        collation = None\n        expression = None\n\n        if self._match_texts(self.TRIM_TYPES):\n            position = self._prev.text.upper()\n\n        this = self._parse_bitwise()\n        if self._match_set((TokenType.FROM, TokenType.COMMA)):\n            invert_order = self._prev.token_type == TokenType.FROM or self.TRIM_PATTERN_FIRST\n            expression = self._parse_bitwise()\n\n            if invert_order:\n                this, expression = expression, this\n\n        if self._match(TokenType.COLLATE):\n            collation = self._parse_bitwise()\n\n        return self.expression(\n            exp.Trim(this=this, position=position, expression=expression, collation=collation)\n        )\n\n    def _parse_window_clause(self) -> t.Optional[t.List[exp.Expr]]:\n        return self._parse_csv(self._parse_named_window) if self._match(TokenType.WINDOW) else None\n\n    def _parse_named_window(self) -> t.Optional[exp.Expr]:\n        return self._parse_window(self._parse_id_var(), alias=True)\n\n    def _parse_respect_or_ignore_nulls(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        if self._curr.token_type == TokenType.VAR:\n            if self._match_text_seq(\"IGNORE\", \"NULLS\"):\n                return self.expression(exp.IgnoreNulls(this=this))\n            if self._match_text_seq(\"RESPECT\", \"NULLS\"):\n                return self.expression(exp.RespectNulls(this=this))\n        return this\n\n    def _parse_having_max(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        if self._match(TokenType.HAVING):\n            self._match_texts((\"MAX\", \"MIN\"))\n            max = self._prev.text.upper() != \"MIN\"\n            return self.expression(\n                exp.HavingMax(this=this, expression=self._parse_column(), max=max)\n            )\n\n        return this\n\n    def _parse_window(\n        self, this: t.Optional[exp.Expr], alias: bool = False\n    ) -> t.Optional[exp.Expr]:\n        func = this\n        comments = func.comments if isinstance(func, exp.Expr) else None\n\n        # T-SQL allows the OVER (...) syntax after WITHIN GROUP.\n        # https://learn.microsoft.com/en-us/sql/t-sql/functions/percentile-disc-transact-sql?view=sql-server-ver16\n        if self._match_text_seq(\"WITHIN\", \"GROUP\"):\n            order = self._parse_wrapped(self._parse_order)\n            this = self.expression(exp.WithinGroup(this=this, expression=order))\n\n        if self._match_pair(TokenType.FILTER, TokenType.L_PAREN):\n            self._match(TokenType.WHERE)\n            this = self.expression(\n                exp.Filter(this=this, expression=self._parse_where(skip_where_token=True))\n            )\n            self._match_r_paren()\n\n        # SQL spec defines an optional [ { IGNORE | RESPECT } NULLS ] OVER\n        # Some dialects choose to implement and some do not.\n        # https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html\n\n        # There is some code above in _parse_lambda that handles\n        #   SELECT FIRST_VALUE(TABLE.COLUMN IGNORE|RESPECT NULLS) OVER ...\n\n        # The below changes handle\n        #   SELECT FIRST_VALUE(TABLE.COLUMN) IGNORE|RESPECT NULLS OVER ...\n\n        # Oracle allows both formats\n        #   (https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/img_text/first_value.html)\n        #   and Snowflake chose to do the same for familiarity\n        #   https://docs.snowflake.com/en/sql-reference/functions/first_value.html#usage-notes\n        if isinstance(this, exp.AggFunc):\n            ignore_respect = this.find(exp.IgnoreNulls, exp.RespectNulls)\n\n            if ignore_respect and ignore_respect is not this:\n                ignore_respect.replace(ignore_respect.this)\n                this = self.expression(ignore_respect.__class__(this=this))\n\n        this = self._parse_respect_or_ignore_nulls(this)\n\n        # bigquery select from window x AS (partition by ...)\n        if alias:\n            over = None\n            self._match(TokenType.ALIAS)\n        elif not self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS):\n            return this\n        else:\n            over = self._prev.text.upper()\n\n        if comments and isinstance(func, exp.Expr):\n            func.pop_comments()\n\n        if not self._match(TokenType.L_PAREN):\n            return self.expression(\n                exp.Window(this=this, alias=self._parse_id_var(False), over=over), comments=comments\n            )\n\n        window_alias = self._parse_id_var(any_token=False, tokens=self.WINDOW_ALIAS_TOKENS)\n\n        first: t.Optional[bool] = True if self._match(TokenType.FIRST) else None\n        if self._match_text_seq(\"LAST\"):\n            first = False\n\n        partition, order = self._parse_partition_and_order()\n        kind = (\n            self._match_set((TokenType.ROWS, TokenType.RANGE)) or self._match_text_seq(\"GROUPS\")\n        ) and self._prev.text\n\n        if kind:\n            self._match(TokenType.BETWEEN)\n            start = self._parse_window_spec()\n\n            end = self._parse_window_spec() if self._match(TokenType.AND) else {}\n            exclude = (\n                self._parse_var_from_options(self.WINDOW_EXCLUDE_OPTIONS)\n                if self._match_text_seq(\"EXCLUDE\")\n                else None\n            )\n\n            spec = self.expression(\n                exp.WindowSpec(\n                    kind=kind,\n                    start=start[\"value\"],\n                    start_side=start[\"side\"],\n                    end=end.get(\"value\"),\n                    end_side=end.get(\"side\"),\n                    exclude=exclude,\n                )\n            )\n        else:\n            spec = None\n\n        self._match_r_paren()\n\n        window = self.expression(\n            exp.Window(\n                this=this,\n                partition_by=partition,\n                order=order,\n                spec=spec,\n                alias=window_alias,\n                over=over,\n                first=first,\n            ),\n            comments=comments,\n        )\n\n        # This covers Oracle's FIRST/LAST syntax: aggregate KEEP (...) OVER (...)\n        if self._match_set(self.WINDOW_BEFORE_PAREN_TOKENS, advance=False):\n            return self._parse_window(window, alias=alias)\n\n        return window\n\n    def _parse_partition_and_order(\n        self,\n    ) -> t.Tuple[t.List[exp.Expr], t.Optional[exp.Expr]]:\n        return self._parse_partition_by(), self._parse_order()\n\n    def _parse_window_spec(self) -> t.Dict[str, t.Optional[str | exp.Expr]]:\n        self._match(TokenType.BETWEEN)\n\n        return {\n            \"value\": (\n                (self._match_text_seq(\"UNBOUNDED\") and \"UNBOUNDED\")\n                or (self._match_text_seq(\"CURRENT\", \"ROW\") and \"CURRENT ROW\")\n                or self._parse_bitwise()\n            ),\n            \"side\": self._prev.text if self._match_texts(self.WINDOW_SIDES) else None,\n        }\n\n    def _parse_alias(\n        self, this: t.Optional[exp.Expr], explicit: bool = False\n    ) -> t.Optional[exp.Expr]:\n        # In some dialects, LIMIT and OFFSET can act as both identifiers and keywords (clauses)\n        # so this section tries to parse the clause version and if it fails, it treats the token\n        # as an identifier (alias)\n        if self._can_parse_limit_or_offset():\n            return this\n\n        any_token = self._match(TokenType.ALIAS)\n        comments = self._prev_comments\n\n        if explicit and not any_token:\n            return this\n\n        if self._match(TokenType.L_PAREN):\n            aliases = self.expression(\n                exp.Aliases(\n                    this=this, expressions=self._parse_csv(lambda: self._parse_id_var(any_token))\n                ),\n                comments=comments,\n            )\n            self._match_r_paren(aliases)\n            return aliases\n\n        alias = self._parse_id_var(any_token, tokens=self.ALIAS_TOKENS) or (\n            self.STRING_ALIASES and self._parse_string_as_identifier()\n        )\n\n        if alias:\n            comments.extend(alias.pop_comments())\n            this = self.expression(exp.Alias(this=this, alias=alias), comments=comments)\n            column = this.this\n\n            # Moves the comment next to the alias in `expr /* comment */ AS alias`\n            if not this.comments and column and column.comments:\n                this.comments = column.pop_comments()\n\n        return this\n\n    def _parse_id_var(\n        self,\n        any_token: bool = True,\n        tokens: t.Optional[t.Collection[TokenType]] = None,\n    ) -> t.Optional[exp.Expr]:\n        expression = self._parse_identifier()\n        if not expression and (\n            (any_token and self._advance_any()) or self._match_set(tokens or self.ID_VAR_TOKENS)\n        ):\n            quoted = self._prev.token_type == TokenType.STRING\n            expression = self._identifier_expression(quoted=quoted)\n\n        return expression\n\n    def _parse_string(self) -> t.Optional[exp.Expr]:\n        if self._match_set(self.STRING_PARSERS):\n            return self.STRING_PARSERS[self._prev.token_type](self, self._prev)\n        return self._parse_placeholder()\n\n    def _parse_string_as_identifier(self) -> exp.Identifier | None:\n        if not self._match(TokenType.STRING):\n            return None\n        output = exp.to_identifier(self._prev.text, quoted=True)\n        output.update_positions(self._prev)\n        return output\n\n    def _parse_number(self) -> t.Optional[exp.Expr]:\n        if self._match_set(self.NUMERIC_PARSERS):\n            return self.NUMERIC_PARSERS[self._prev.token_type](self, self._prev)\n        return self._parse_placeholder()\n\n    def _parse_identifier(self) -> t.Optional[exp.Expr]:\n        if self._match(TokenType.IDENTIFIER):\n            return self._identifier_expression(quoted=True)\n        return self._parse_placeholder()\n\n    def _parse_var(\n        self,\n        any_token: bool = False,\n        tokens: t.Optional[t.Collection[TokenType]] = None,\n        upper: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        if (\n            (any_token and self._advance_any())\n            or self._match(TokenType.VAR)\n            or (self._match_set(tokens) if tokens else False)\n        ):\n            return self.expression(\n                exp.Var(this=self._prev.text.upper() if upper else self._prev.text)\n            )\n        return self._parse_placeholder()\n\n    def _advance_any(self, ignore_reserved: bool = False) -> t.Optional[Token]:\n        if self._curr and (ignore_reserved or self._curr.token_type not in self.RESERVED_TOKENS):\n            self._advance()\n            return self._prev\n        return None\n\n    def _parse_var_or_string(self, upper: bool = False) -> t.Optional[exp.Expr]:\n        return self._parse_string() or self._parse_var(any_token=True, upper=upper)\n\n    def _parse_primary_or_var(self) -> t.Optional[exp.Expr]:\n        return self._parse_primary() or self._parse_var(any_token=True)\n\n    def _parse_null(self) -> t.Optional[exp.Expr]:\n        if self._match_set((TokenType.NULL, TokenType.UNKNOWN)):\n            return self.PRIMARY_PARSERS[TokenType.NULL](self, self._prev)\n        return self._parse_placeholder()\n\n    def _parse_boolean(self) -> t.Optional[exp.Expr]:\n        if self._match(TokenType.TRUE):\n            return self.PRIMARY_PARSERS[TokenType.TRUE](self, self._prev)\n        if self._match(TokenType.FALSE):\n            return self.PRIMARY_PARSERS[TokenType.FALSE](self, self._prev)\n        return self._parse_placeholder()\n\n    def _parse_star(self) -> t.Optional[exp.Expr]:\n        if self._match(TokenType.STAR):\n            return self.PRIMARY_PARSERS[TokenType.STAR](self, self._prev)\n        return self._parse_placeholder()\n\n    def _parse_parameter(self) -> exp.Parameter:\n        this = self._parse_identifier() or self._parse_primary_or_var()\n        return self.expression(exp.Parameter(this=this))\n\n    def _parse_placeholder(self) -> t.Optional[exp.Expr]:\n        if self._match_set(self.PLACEHOLDER_PARSERS):\n            placeholder = self.PLACEHOLDER_PARSERS[self._prev.token_type](self)\n            if placeholder:\n                return placeholder\n            self._advance(-1)\n        return None\n\n    def _parse_star_op(self, *keywords: str) -> t.Optional[t.List[exp.Expr]]:\n        if not self._match_texts(keywords):\n            return None\n        if self._match(TokenType.L_PAREN, advance=False):\n            return self._parse_wrapped_csv(self._parse_expression)\n\n        expression = self._parse_alias(self._parse_disjunction(), explicit=True)\n        return [expression] if expression else None\n\n    def _parse_csv(\n        self, parse_method: t.Callable[[], t.Optional[T]], sep: TokenType = TokenType.COMMA\n    ) -> t.List[T]:\n        parse_result = parse_method()\n        items = [parse_result] if parse_result is not None else []\n\n        while self._match(sep):\n            if isinstance(parse_result, exp.Expr):\n                self._add_comments(parse_result)\n            parse_result = parse_method()\n            if parse_result is not None:\n                items.append(parse_result)\n\n        return items\n\n    def _parse_wrapped_id_vars(self, optional: bool = False) -> t.List[exp.Expr]:\n        return self._parse_wrapped_csv(self._parse_id_var, optional=optional)\n\n    def _parse_wrapped_csv(\n        self,\n        parse_method: t.Callable[[], t.Optional[T]],\n        sep: TokenType = TokenType.COMMA,\n        optional: bool = False,\n    ) -> t.List[T]:\n        return self._parse_wrapped(\n            lambda: self._parse_csv(parse_method, sep=sep), optional=optional\n        )\n\n    def _parse_wrapped(self, parse_method: t.Callable[[], T], optional: bool = False) -> T:\n        wrapped = self._match(TokenType.L_PAREN)\n        if not wrapped and not optional:\n            self.raise_error(\"Expecting (\")\n        parse_result = parse_method()\n        if wrapped:\n            self._match_r_paren()\n        return parse_result\n\n    def _parse_expressions(self) -> t.List[exp.Expr]:\n        return self._parse_csv(self._parse_expression)\n\n    def _parse_select_or_expression(self, alias: bool = False) -> t.Optional[exp.Expr]:\n        return (\n            self._parse_set_operations(\n                self._parse_alias(self._parse_assignment(), explicit=True)\n                if alias\n                else self._parse_assignment()\n            )\n            or self._parse_select()\n        )\n\n    def _parse_ddl_select(self) -> t.Optional[exp.Expr]:\n        return self._parse_query_modifiers(\n            self._parse_set_operations(self._parse_select(nested=True, parse_subquery_alias=False))\n        )\n\n    def _parse_transaction(self) -> exp.Transaction | exp.Command:\n        this = None\n        if self._match_texts(self.TRANSACTION_KIND):\n            this = self._prev.text\n\n        self._match_texts((\"TRANSACTION\", \"WORK\"))\n\n        modes = []\n        while True:\n            mode = []\n            while self._match(TokenType.VAR) or self._match(TokenType.NOT):\n                mode.append(self._prev.text)\n\n            if mode:\n                modes.append(\" \".join(mode))\n            if not self._match(TokenType.COMMA):\n                break\n\n        return self.expression(exp.Transaction(this=this, modes=modes))\n\n    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:\n        chain = None\n        savepoint = None\n        is_rollback = self._prev.token_type == TokenType.ROLLBACK\n\n        self._match_texts((\"TRANSACTION\", \"WORK\"))\n\n        if self._match_text_seq(\"TO\"):\n            self._match_text_seq(\"SAVEPOINT\")\n            savepoint = self._parse_id_var()\n\n        if self._match(TokenType.AND):\n            chain = not self._match_text_seq(\"NO\")\n            self._match_text_seq(\"CHAIN\")\n\n        if is_rollback:\n            return self.expression(exp.Rollback(savepoint=savepoint))\n\n        return self.expression(exp.Commit(chain=chain))\n\n    def _parse_refresh(self) -> exp.Refresh | exp.Command:\n        if self._match(TokenType.TABLE):\n            kind = \"TABLE\"\n        elif self._match_text_seq(\"MATERIALIZED\", \"VIEW\"):\n            kind = \"MATERIALIZED VIEW\"\n        else:\n            kind = \"\"\n\n        this = self._parse_string() or self._parse_table()\n        if not kind and not isinstance(this, exp.Literal):\n            return self._parse_as_command(self._prev)\n\n        return self.expression(exp.Refresh(this=this, kind=kind))\n\n    def _parse_column_def_with_exists(self):\n        start = self._index\n        self._match(TokenType.COLUMN)\n\n        exists_column = self._parse_exists(not_=True)\n        expression = self._parse_field_def()\n\n        if not isinstance(expression, exp.ColumnDef):\n            self._retreat(start)\n            return None\n\n        expression.set(\"exists\", exists_column)\n\n        return expression\n\n    def _parse_add_column(self) -> t.Optional[exp.ColumnDef]:\n        if not self._prev.text.upper() == \"ADD\":\n            return None\n\n        expression = self._parse_column_def_with_exists()\n        if not expression:\n            return None\n\n        # https://docs.databricks.com/delta/update-schema.html#explicitly-update-schema-to-add-columns\n        if self._match_texts((\"FIRST\", \"AFTER\")):\n            position = self._prev.text\n            column_position = self.expression(\n                exp.ColumnPosition(this=self._parse_column(), position=position)\n            )\n            expression.set(\"position\", column_position)\n\n        return expression\n\n    def _parse_drop_column(self) -> t.Optional[exp.Drop | exp.Command]:\n        drop = self._parse_drop() if self._match(TokenType.DROP) else None\n        if drop and not isinstance(drop, exp.Command):\n            drop.set(\"kind\", drop.args.get(\"kind\", \"COLUMN\"))\n        return drop\n\n    # https://docs.aws.amazon.com/athena/latest/ug/alter-table-drop-partition.html\n    def _parse_drop_partition(self, exists: t.Optional[bool] = None) -> exp.DropPartition:\n        return self.expression(\n            exp.DropPartition(expressions=self._parse_csv(self._parse_partition), exists=exists)\n        )\n\n    def _parse_alter_table_add(self) -> t.List[exp.Expr]:\n        def _parse_add_alteration() -> t.Optional[exp.Expr]:\n            self._match_text_seq(\"ADD\")\n            if self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False):\n                return self.expression(\n                    exp.AddConstraint(expressions=self._parse_csv(self._parse_constraint))\n                )\n\n            column_def = self._parse_add_column()\n            if isinstance(column_def, exp.ColumnDef):\n                return column_def\n\n            exists = self._parse_exists(not_=True)\n            if self._match_pair(TokenType.PARTITION, TokenType.L_PAREN, advance=False):\n                return self.expression(\n                    exp.AddPartition(\n                        exists=exists,\n                        this=self._parse_field(any_token=True),\n                        location=self._match_text_seq(\"LOCATION\", advance=False)\n                        and self._parse_property(),\n                    )\n                )\n\n            return None\n\n        if not self._match_set(self.ADD_CONSTRAINT_TOKENS, advance=False) and (\n            not self.dialect.ALTER_TABLE_ADD_REQUIRED_FOR_EACH_COLUMN\n            or self._match_text_seq(\"COLUMNS\")\n        ):\n            schema = self._parse_schema()\n\n            return (\n                ensure_list(schema)\n                if schema\n                else self._parse_csv(self._parse_column_def_with_exists)\n            )\n\n        return self._parse_csv(_parse_add_alteration)\n\n    def _parse_alter_table_alter(self) -> t.Optional[exp.Expr]:\n        if self._match_texts(self.ALTER_ALTER_PARSERS):\n            return self.ALTER_ALTER_PARSERS[self._prev.text.upper()](self)\n\n        # Many dialects support the ALTER [COLUMN] syntax, so if there is no\n        # keyword after ALTER we default to parsing this statement\n        self._match(TokenType.COLUMN)\n        column = self._parse_field(any_token=True)\n\n        if self._match_pair(TokenType.DROP, TokenType.DEFAULT):\n            return self.expression(exp.AlterColumn(this=column, drop=True))\n        if self._match_pair(TokenType.SET, TokenType.DEFAULT):\n            return self.expression(exp.AlterColumn(this=column, default=self._parse_disjunction()))\n        if self._match(TokenType.COMMENT):\n            return self.expression(exp.AlterColumn(this=column, comment=self._parse_string()))\n        if self._match_text_seq(\"DROP\", \"NOT\", \"NULL\"):\n            return self.expression(exp.AlterColumn(this=column, drop=True, allow_null=True))\n        if self._match_text_seq(\"SET\", \"NOT\", \"NULL\"):\n            return self.expression(exp.AlterColumn(this=column, allow_null=False))\n\n        if self._match_text_seq(\"SET\", \"VISIBLE\"):\n            return self.expression(exp.AlterColumn(this=column, visible=\"VISIBLE\"))\n        if self._match_text_seq(\"SET\", \"INVISIBLE\"):\n            return self.expression(exp.AlterColumn(this=column, visible=\"INVISIBLE\"))\n\n        self._match_text_seq(\"SET\", \"DATA\")\n        self._match_text_seq(\"TYPE\")\n        return self.expression(\n            exp.AlterColumn(\n                this=column,\n                dtype=self._parse_types(),\n                collate=self._match(TokenType.COLLATE) and self._parse_term(),\n                using=self._match(TokenType.USING) and self._parse_disjunction(),\n            )\n        )\n\n    def _parse_alter_diststyle(self) -> exp.AlterDistStyle:\n        if self._match_texts((\"ALL\", \"EVEN\", \"AUTO\")):\n            return self.expression(exp.AlterDistStyle(this=exp.var(self._prev.text.upper())))\n\n        self._match_text_seq(\"KEY\", \"DISTKEY\")\n        return self.expression(exp.AlterDistStyle(this=self._parse_column()))\n\n    def _parse_alter_sortkey(self, compound: t.Optional[bool] = None) -> exp.AlterSortKey:\n        if compound:\n            self._match_text_seq(\"SORTKEY\")\n\n        if self._match(TokenType.L_PAREN, advance=False):\n            return self.expression(\n                exp.AlterSortKey(expressions=self._parse_wrapped_id_vars(), compound=compound)\n            )\n\n        self._match_texts((\"AUTO\", \"NONE\"))\n        return self.expression(\n            exp.AlterSortKey(this=exp.var(self._prev.text.upper()), compound=compound)\n        )\n\n    def _parse_alter_table_drop(self) -> t.List[exp.Expr]:\n        index = self._index - 1\n\n        partition_exists = self._parse_exists()\n        if self._match(TokenType.PARTITION, advance=False):\n            return self._parse_csv(lambda: self._parse_drop_partition(exists=partition_exists))\n\n        self._retreat(index)\n        return self._parse_csv(self._parse_drop_column)\n\n    def _parse_alter_table_rename(self) -> t.Optional[exp.AlterRename | exp.RenameColumn]:\n        if self._match(TokenType.COLUMN) or not self.ALTER_RENAME_REQUIRES_COLUMN:\n            exists = self._parse_exists()\n            old_column = self._parse_column()\n            to = self._match_text_seq(\"TO\")\n            new_column = self._parse_column()\n\n            if old_column is None or not to or new_column is None:\n                return None\n\n            return self.expression(exp.RenameColumn(this=old_column, to=new_column, exists=exists))\n\n        self._match_text_seq(\"TO\")\n        return self.expression(exp.AlterRename(this=self._parse_table(schema=True)))\n\n    def _parse_alter_table_set(self) -> exp.AlterSet:\n        alter_set = self.expression(exp.AlterSet())\n\n        if self._match(TokenType.L_PAREN, advance=False) or self._match_text_seq(\n            \"TABLE\", \"PROPERTIES\"\n        ):\n            alter_set.set(\"expressions\", self._parse_wrapped_csv(self._parse_assignment))\n        elif self._match_text_seq(\"FILESTREAM_ON\", advance=False):\n            alter_set.set(\"expressions\", [self._parse_assignment()])\n        elif self._match_texts((\"LOGGED\", \"UNLOGGED\")):\n            alter_set.set(\"option\", exp.var(self._prev.text.upper()))\n        elif self._match_text_seq(\"WITHOUT\") and self._match_texts((\"CLUSTER\", \"OIDS\")):\n            alter_set.set(\"option\", exp.var(f\"WITHOUT {self._prev.text.upper()}\"))\n        elif self._match_text_seq(\"LOCATION\"):\n            alter_set.set(\"location\", self._parse_field())\n        elif self._match_text_seq(\"ACCESS\", \"METHOD\"):\n            alter_set.set(\"access_method\", self._parse_field())\n        elif self._match_text_seq(\"TABLESPACE\"):\n            alter_set.set(\"tablespace\", self._parse_field())\n        elif self._match_text_seq(\"FILE\", \"FORMAT\") or self._match_text_seq(\"FILEFORMAT\"):\n            alter_set.set(\"file_format\", [self._parse_field()])\n        elif self._match_text_seq(\"STAGE_FILE_FORMAT\"):\n            alter_set.set(\"file_format\", self._parse_wrapped_options())\n        elif self._match_text_seq(\"STAGE_COPY_OPTIONS\"):\n            alter_set.set(\"copy_options\", self._parse_wrapped_options())\n        elif self._match_text_seq(\"TAG\") or self._match_text_seq(\"TAGS\"):\n            alter_set.set(\"tag\", self._parse_csv(self._parse_assignment))\n        else:\n            if self._match_text_seq(\"SERDE\"):\n                alter_set.set(\"serde\", self._parse_field())\n\n            properties = self._parse_wrapped(self._parse_properties, optional=True)\n            alter_set.set(\"expressions\", [properties])\n\n        return alter_set\n\n    def _parse_alter_session(self) -> exp.AlterSession:\n        \"\"\"Parse ALTER SESSION SET/UNSET statements.\"\"\"\n        if self._match(TokenType.SET):\n            expressions = self._parse_csv(lambda: self._parse_set_item_assignment())\n            return self.expression(exp.AlterSession(expressions=expressions, unset=False))\n\n        self._match_text_seq(\"UNSET\")\n        expressions = self._parse_csv(\n            lambda: self.expression(exp.SetItem(this=self._parse_id_var(any_token=True)))\n        )\n        return self.expression(exp.AlterSession(expressions=expressions, unset=True))\n\n    def _parse_alter(self) -> exp.Alter | exp.Command:\n        start = self._prev\n\n        alter_token = self._match_set(self.ALTERABLES) and self._prev\n        if not alter_token:\n            return self._parse_as_command(start)\n\n        exists = self._parse_exists()\n        only = self._match_text_seq(\"ONLY\")\n\n        if alter_token.token_type == TokenType.SESSION:\n            this = None\n            check = None\n            cluster = None\n        else:\n            this = self._parse_table(schema=True, parse_partition=self.ALTER_TABLE_PARTITIONS)\n            check = self._match_text_seq(\"WITH\", \"CHECK\")\n            cluster = self._parse_on_property() if self._match(TokenType.ON) else None\n\n            if self._next:\n                self._advance()\n\n        parser = self.ALTER_PARSERS.get(self._prev.text.upper()) if self._prev else None\n        if parser:\n            actions = ensure_list(parser(self))\n            not_valid = self._match_text_seq(\"NOT\", \"VALID\")\n            options = self._parse_csv(self._parse_property)\n            cascade = self.dialect.ALTER_TABLE_SUPPORTS_CASCADE and self._match_text_seq(\"CASCADE\")\n\n            if not self._curr and actions:\n                return self.expression(\n                    exp.Alter(\n                        this=this,\n                        kind=alter_token.text.upper(),\n                        exists=exists,\n                        actions=actions,\n                        only=only,\n                        options=options,\n                        cluster=cluster,\n                        not_valid=not_valid,\n                        check=check,\n                        cascade=cascade,\n                    )\n                )\n\n        return self._parse_as_command(start)\n\n    def _parse_analyze(self) -> exp.Analyze | exp.Command:\n        start = self._prev\n        # https://duckdb.org/docs/sql/statements/analyze\n        if not self._curr:\n            return self.expression(exp.Analyze())\n\n        options = []\n        while self._match_texts(self.ANALYZE_STYLES):\n            if self._prev.text.upper() == \"BUFFER_USAGE_LIMIT\":\n                options.append(f\"BUFFER_USAGE_LIMIT {self._parse_number()}\")\n            else:\n                options.append(self._prev.text.upper())\n\n        this: t.Optional[exp.Expr] = None\n        inner_expression: t.Optional[exp.Expr] = None\n\n        kind = self._curr.text.upper() if self._curr else None\n\n        if self._match(TokenType.TABLE) or self._match(TokenType.INDEX):\n            this = self._parse_table_parts()\n        elif self._match_text_seq(\"TABLES\"):\n            if self._match_set((TokenType.FROM, TokenType.IN)):\n                kind = f\"{kind} {self._prev.text.upper()}\"\n                this = self._parse_table(schema=True, is_db_reference=True)\n        elif self._match_text_seq(\"DATABASE\"):\n            this = self._parse_table(schema=True, is_db_reference=True)\n        elif self._match_text_seq(\"CLUSTER\"):\n            this = self._parse_table()\n        # Try matching inner expr keywords before fallback to parse table.\n        elif self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):\n            kind = None\n            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)\n        else:\n            # Empty kind  https://prestodb.io/docs/current/sql/analyze.html\n            kind = None\n            this = self._parse_table_parts()\n\n        partition = self._try_parse(self._parse_partition)\n        if not partition and self._match_texts(self.PARTITION_KEYWORDS):\n            return self._parse_as_command(start)\n\n        # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/\n        if self._match_text_seq(\"WITH\", \"SYNC\", \"MODE\") or self._match_text_seq(\n            \"WITH\", \"ASYNC\", \"MODE\"\n        ):\n            mode = f\"WITH {self._tokens[self._index - 2].text.upper()} MODE\"\n        else:\n            mode = None\n\n        if self._match_texts(self.ANALYZE_EXPRESSION_PARSERS):\n            inner_expression = self.ANALYZE_EXPRESSION_PARSERS[self._prev.text.upper()](self)\n\n        properties = self._parse_properties()\n        return self.expression(\n            exp.Analyze(\n                kind=kind,\n                this=this,\n                mode=mode,\n                partition=partition,\n                properties=properties,\n                expression=inner_expression,\n                options=options,\n            )\n        )\n\n    # https://spark.apache.org/docs/3.5.1/sql-ref-syntax-aux-analyze-table.html\n    def _parse_analyze_statistics(self) -> exp.AnalyzeStatistics:\n        this = None\n        kind = self._prev.text.upper()\n        option = self._prev.text.upper() if self._match_text_seq(\"DELTA\") else None\n        expressions = []\n\n        if not self._match_text_seq(\"STATISTICS\"):\n            self.raise_error(\"Expecting token STATISTICS\")\n\n        if self._match_text_seq(\"NOSCAN\"):\n            this = \"NOSCAN\"\n        elif self._match(TokenType.FOR):\n            if self._match_text_seq(\"ALL\", \"COLUMNS\"):\n                this = \"FOR ALL COLUMNS\"\n            if self._match_texts(\"COLUMNS\"):\n                this = \"FOR COLUMNS\"\n                expressions = self._parse_csv(self._parse_column_reference)\n        elif self._match_text_seq(\"SAMPLE\"):\n            sample = self._parse_number()\n            expressions = [\n                self.expression(\n                    exp.AnalyzeSample(\n                        sample=sample,\n                        kind=self._prev.text.upper() if self._match(TokenType.PERCENT) else None,\n                    )\n                )\n            ]\n\n        return self.expression(\n            exp.AnalyzeStatistics(kind=kind, option=option, this=this, expressions=expressions)\n        )\n\n    # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ANALYZE.html\n    def _parse_analyze_validate(self) -> exp.AnalyzeValidate:\n        kind = None\n        this = None\n        expression: t.Optional[exp.Expr] = None\n        if self._match_text_seq(\"REF\", \"UPDATE\"):\n            kind = \"REF\"\n            this = \"UPDATE\"\n            if self._match_text_seq(\"SET\", \"DANGLING\", \"TO\", \"NULL\"):\n                this = \"UPDATE SET DANGLING TO NULL\"\n        elif self._match_text_seq(\"STRUCTURE\"):\n            kind = \"STRUCTURE\"\n            if self._match_text_seq(\"CASCADE\", \"FAST\"):\n                this = \"CASCADE FAST\"\n            elif self._match_text_seq(\"CASCADE\", \"COMPLETE\") and self._match_texts(\n                (\"ONLINE\", \"OFFLINE\")\n            ):\n                this = f\"CASCADE COMPLETE {self._prev.text.upper()}\"\n                expression = self._parse_into()\n\n        return self.expression(exp.AnalyzeValidate(kind=kind, this=this, expression=expression))\n\n    def _parse_analyze_columns(self) -> t.Optional[exp.AnalyzeColumns]:\n        this = self._prev.text.upper()\n        if self._match_text_seq(\"COLUMNS\"):\n            return self.expression(exp.AnalyzeColumns(this=f\"{this} {self._prev.text.upper()}\"))\n        return None\n\n    def _parse_analyze_delete(self) -> t.Optional[exp.AnalyzeDelete]:\n        kind = self._prev.text.upper() if self._match_text_seq(\"SYSTEM\") else None\n        if self._match_text_seq(\"STATISTICS\"):\n            return self.expression(exp.AnalyzeDelete(kind=kind))\n        return None\n\n    def _parse_analyze_list(self) -> t.Optional[exp.AnalyzeListChainedRows]:\n        if self._match_text_seq(\"CHAINED\", \"ROWS\"):\n            return self.expression(exp.AnalyzeListChainedRows(expression=self._parse_into()))\n        return None\n\n    # https://dev.mysql.com/doc/refman/8.4/en/analyze-table.html\n    def _parse_analyze_histogram(self) -> exp.AnalyzeHistogram:\n        this = self._prev.text.upper()\n        expression: t.Optional[exp.Expr] = None\n        expressions = []\n        update_options = None\n\n        if self._match_text_seq(\"HISTOGRAM\", \"ON\"):\n            expressions = self._parse_csv(self._parse_column_reference)\n            with_expressions = []\n            while self._match(TokenType.WITH):\n                # https://docs.starrocks.io/docs/sql-reference/sql-statements/cbo_stats/ANALYZE_TABLE/\n                if self._match_texts((\"SYNC\", \"ASYNC\")):\n                    if self._match_text_seq(\"MODE\", advance=False):\n                        with_expressions.append(f\"{self._prev.text.upper()} MODE\")\n                        self._advance()\n                else:\n                    buckets = self._parse_number()\n                    if self._match_text_seq(\"BUCKETS\"):\n                        with_expressions.append(f\"{buckets} BUCKETS\")\n            if with_expressions:\n                expression = self.expression(exp.AnalyzeWith(expressions=with_expressions))\n\n            if self._match_texts((\"MANUAL\", \"AUTO\")) and self._match(\n                TokenType.UPDATE, advance=False\n            ):\n                update_options = self._prev.text.upper()\n                self._advance()\n            elif self._match_text_seq(\"USING\", \"DATA\"):\n                expression = self.expression(exp.UsingData(this=self._parse_string()))\n\n        return self.expression(\n            exp.AnalyzeHistogram(\n                this=this,\n                expressions=expressions,\n                expression=expression,\n                update_options=update_options,\n            )\n        )\n\n    def _parse_merge(self) -> exp.Merge:\n        self._match(TokenType.INTO)\n        target = self._parse_table()\n\n        if target and self._match(TokenType.ALIAS, advance=False):\n            target.set(\"alias\", self._parse_table_alias())\n\n        self._match(TokenType.USING)\n        using = self._parse_table()\n\n        return self.expression(\n            exp.Merge(\n                this=target,\n                using=using,\n                on=self._match(TokenType.ON) and self._parse_disjunction(),\n                using_cond=self._match(TokenType.USING) and self._parse_using_identifiers(),\n                whens=self._parse_when_matched(),\n                returning=self._parse_returning(),\n            )\n        )\n\n    def _parse_when_matched(self) -> exp.Whens:\n        whens = []\n\n        while self._match(TokenType.WHEN):\n            matched = not self._match(TokenType.NOT)\n            self._match_text_seq(\"MATCHED\")\n            source = (\n                False\n                if self._match_text_seq(\"BY\", \"TARGET\")\n                else self._match_text_seq(\"BY\", \"SOURCE\")\n            )\n            condition = self._parse_disjunction() if self._match(TokenType.AND) else None\n\n            self._match(TokenType.THEN)\n\n            if self._match(TokenType.INSERT):\n                this = self._parse_star()\n                if this:\n                    then: t.Optional[exp.Expr] = self.expression(exp.Insert(this=this))\n                else:\n                    then = self.expression(\n                        exp.Insert(\n                            this=exp.var(\"ROW\")\n                            if self._match_text_seq(\"ROW\")\n                            else self._parse_value(values=False),\n                            expression=self._match_text_seq(\"VALUES\") and self._parse_value(),\n                        )\n                    )\n            elif self._match(TokenType.UPDATE):\n                expressions = self._parse_star()\n                if expressions:\n                    then = self.expression(exp.Update(expressions=expressions))\n                else:\n                    then = self.expression(\n                        exp.Update(\n                            expressions=self._match(TokenType.SET)\n                            and self._parse_csv(self._parse_equality)\n                        )\n                    )\n            elif self._match(TokenType.DELETE):\n                then = self.expression(exp.Var(this=self._prev.text))\n            else:\n                then = self._parse_var_from_options(self.CONFLICT_ACTIONS)\n\n            whens.append(\n                self.expression(\n                    exp.When(matched=matched, source=source, condition=condition, then=then)\n                )\n            )\n        return self.expression(exp.Whens(expressions=whens))\n\n    def _parse_show(self) -> t.Optional[exp.Expr]:\n        parser = self._find_parser(self.SHOW_PARSERS, self.SHOW_TRIE)\n        if parser:\n            return parser(self)\n        return self._parse_as_command(self._prev)\n\n    def _parse_set_item_assignment(self, kind: t.Optional[str] = None) -> t.Optional[exp.Expr]:\n        index = self._index\n\n        if kind in (\"GLOBAL\", \"SESSION\") and self._match_text_seq(\"TRANSACTION\"):\n            return self._parse_set_transaction(global_=kind == \"GLOBAL\")\n\n        left = self._parse_primary() or self._parse_column()\n        assignment_delimiter = self._match_texts(self.SET_ASSIGNMENT_DELIMITERS)\n\n        if not left or (self.SET_REQUIRES_ASSIGNMENT_DELIMITER and not assignment_delimiter):\n            self._retreat(index)\n            return None\n\n        right = self._parse_statement() or self._parse_id_var()\n        if isinstance(right, (exp.Column, exp.Identifier)):\n            right = exp.var(right.name)\n\n        this = self.expression(exp.EQ(this=left, expression=right))\n        return self.expression(exp.SetItem(this=this, kind=kind))\n\n    def _parse_set_transaction(self, global_: bool = False) -> exp.Expr:\n        self._match_text_seq(\"TRANSACTION\")\n        characteristics = self._parse_csv(\n            lambda: self._parse_var_from_options(self.TRANSACTION_CHARACTERISTICS)\n        )\n        return self.expression(\n            exp.SetItem(expressions=characteristics, kind=\"TRANSACTION\", global_=global_)\n        )\n\n    def _parse_set_item(self) -> t.Optional[exp.Expr]:\n        parser = self._find_parser(self.SET_PARSERS, self.SET_TRIE)\n        return parser(self) if parser else self._parse_set_item_assignment(kind=None)\n\n    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:\n        index = self._index\n        set_ = self.expression(\n            exp.Set(expressions=self._parse_csv(self._parse_set_item), unset=unset, tag=tag)\n        )\n\n        if self._curr:\n            self._retreat(index)\n            return self._parse_as_command(self._prev)\n\n        return set_\n\n    def _parse_var_from_options(\n        self, options: OPTIONS_TYPE, raise_unmatched: bool = True\n    ) -> t.Optional[exp.Var]:\n        start = self._curr\n        if not start:\n            return None\n\n        option = start.text.upper()\n        continuations = options.get(option)\n\n        index = self._index\n        self._advance()\n        for keywords in continuations or []:\n            if isinstance(keywords, str):\n                keywords = (keywords,)\n\n            if self._match_text_seq(*keywords):\n                option = f\"{option} {' '.join(keywords)}\"\n                break\n        else:\n            if continuations or continuations is None:\n                if raise_unmatched:\n                    self.raise_error(f\"Unknown option {option}\")\n\n                self._retreat(index)\n                return None\n\n        return exp.var(option)\n\n    def _parse_as_command(self, start: Token) -> exp.Command:\n        while self._curr:\n            self._advance()\n        text = self._find_sql(start, self._prev)\n        size = len(start.text)\n        self._warn_unsupported()\n        return exp.Command(this=text[:size], expression=text[size:])\n\n    def _parse_dict_property(self, this: str) -> exp.DictProperty:\n        settings = []\n\n        self._match_l_paren()\n        kind = self._parse_id_var()\n\n        if self._match(TokenType.L_PAREN):\n            while True:\n                key = self._parse_id_var()\n                value = self._parse_function() or self._parse_primary_or_var()\n                if not key and value is None:\n                    break\n                settings.append(self.expression(exp.DictSubProperty(this=key, value=value)))\n            self._match(TokenType.R_PAREN)\n\n        self._match_r_paren()\n\n        return self.expression(\n            exp.DictProperty(this=this, kind=kind.this if kind else None, settings=settings)\n        )\n\n    def _parse_dict_range(self, this: str) -> exp.DictRange:\n        self._match_l_paren()\n        has_min = self._match_text_seq(\"MIN\")\n        if has_min:\n            min = self._parse_var() or self._parse_primary()\n            self._match_text_seq(\"MAX\")\n            max = self._parse_var() or self._parse_primary()\n        else:\n            max = self._parse_var() or self._parse_primary()\n            min = exp.Literal.number(0)\n        self._match_r_paren()\n        return self.expression(exp.DictRange(this=this, min=min, max=max))\n\n    def _parse_comprehension(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Comprehension]:\n        index = self._index\n        expression = self._parse_column()\n        position = self._match(TokenType.COMMA) and self._parse_column()\n\n        if not self._match(TokenType.IN):\n            self._retreat(index - 1)\n            return None\n        iterator = self._parse_column()\n        condition = self._parse_disjunction() if self._match_text_seq(\"IF\") else None\n        return self.expression(\n            exp.Comprehension(\n                this=this,\n                expression=expression,\n                position=position,\n                iterator=iterator,\n                condition=condition,\n            )\n        )\n\n    def _parse_heredoc(self) -> t.Optional[exp.Heredoc]:\n        if self._match(TokenType.HEREDOC_STRING):\n            return self.expression(exp.Heredoc(this=self._prev.text))\n\n        if not self._match_text_seq(\"$\"):\n            return None\n\n        tags = [\"$\"]\n        tag_text = None\n\n        if self._is_connected():\n            self._advance()\n            tags.append(self._prev.text.upper())\n        else:\n            self.raise_error(\"No closing $ found\")\n\n        if tags[-1] != \"$\":\n            if self._is_connected() and self._match_text_seq(\"$\"):\n                tag_text = tags[-1]\n                tags.append(\"$\")\n            else:\n                self.raise_error(\"No closing $ found\")\n\n        heredoc_start = self._curr\n\n        while self._curr:\n            if self._match_text_seq(*tags, advance=False):\n                this = self._find_sql(heredoc_start, self._prev)\n                self._advance(len(tags))\n                return self.expression(exp.Heredoc(this=this, tag=tag_text))\n\n            self._advance()\n\n        self.raise_error(f\"No closing {''.join(tags)} found\")\n        return None\n\n    def _find_parser(\n        self, parsers: t.Dict[str, t.Callable], trie: t.Dict\n    ) -> t.Optional[t.Callable]:\n        if not self._curr:\n            return None\n\n        index = self._index\n        this = []\n        while True:\n            # The current token might be multiple words\n            curr = self._curr.text.upper()\n            key = curr.split(\" \")\n            this.append(curr)\n\n            self._advance()\n            result, trie = in_trie(trie, key)\n            if result == TrieResult.FAILED:\n                break\n\n            if result == TrieResult.EXISTS:\n                subparser = parsers[\" \".join(this)]\n                return subparser\n\n        self._retreat(index)\n        return None\n\n    def _match_l_paren(self, expression: t.Optional[exp.Expr] = None) -> None:\n        if not self._match(TokenType.L_PAREN, expression=expression):\n            self.raise_error(\"Expecting (\")\n\n    def _match_r_paren(self, expression: t.Optional[exp.Expr] = None) -> None:\n        if not self._match(TokenType.R_PAREN, expression=expression):\n            self.raise_error(\"Expecting )\")\n\n    def _replace_lambda(\n        self, node: t.Optional[exp.Expr], expressions: t.List[exp.Expr]\n    ) -> t.Optional[exp.Expr]:\n        if not node:\n            return node\n\n        lambda_types = {e.name: e.args.get(\"to\") or False for e in expressions}\n\n        for column in node.find_all(exp.Column):\n            typ = lambda_types.get(column.parts[0].name)\n            if typ is not None:\n                dot_or_id = column.to_dot() if column.table else column.this\n\n                if typ:\n                    dot_or_id = self.expression(exp.Cast(this=dot_or_id, to=typ))\n\n                parent = column.parent\n\n                while isinstance(parent, exp.Dot):\n                    if not isinstance(parent.parent, exp.Dot):\n                        parent.replace(dot_or_id)\n                        break\n                    parent = parent.parent\n                else:\n                    if column is node:\n                        node = dot_or_id\n                    else:\n                        column.replace(dot_or_id)\n        return node\n\n    def _parse_truncate_table(self) -> t.Optional[exp.TruncateTable] | exp.Expr:\n        start = self._prev\n\n        # Not to be confused with TRUNCATE(number, decimals) function call\n        if self._match(TokenType.L_PAREN):\n            self._retreat(self._index - 2)\n            return self._parse_function()\n\n        # Clickhouse supports TRUNCATE DATABASE as well\n        is_database = self._match(TokenType.DATABASE)\n\n        self._match(TokenType.TABLE)\n\n        exists = self._parse_exists(not_=False)\n\n        expressions = self._parse_csv(\n            lambda: self._parse_table(schema=True, is_db_reference=is_database)\n        )\n\n        cluster = self._parse_on_property() if self._match(TokenType.ON) else None\n\n        if self._match_text_seq(\"RESTART\", \"IDENTITY\"):\n            identity = \"RESTART\"\n        elif self._match_text_seq(\"CONTINUE\", \"IDENTITY\"):\n            identity = \"CONTINUE\"\n        else:\n            identity = None\n\n        if self._match_text_seq(\"CASCADE\") or self._match_text_seq(\"RESTRICT\"):\n            option = self._prev.text\n        else:\n            option = None\n\n        partition = self._parse_partition()\n\n        # Fallback case\n        if self._curr:\n            return self._parse_as_command(start)\n\n        return self.expression(\n            exp.TruncateTable(\n                expressions=expressions,\n                is_database=is_database,\n                exists=exists,\n                cluster=cluster,\n                identity=identity,\n                option=option,\n                partition=partition,\n            )\n        )\n\n    def _parse_with_operator(self) -> t.Optional[exp.Expr]:\n        this = self._parse_ordered(self._parse_opclass)\n\n        if not self._match(TokenType.WITH):\n            return this\n\n        op = self._parse_var(any_token=True, tokens=self.RESERVED_TOKENS)\n\n        return self.expression(exp.WithOperator(this=this, op=op))\n\n    def _parse_wrapped_options(self) -> t.List[t.Optional[exp.Expr]]:\n        self._match(TokenType.EQ)\n        self._match(TokenType.L_PAREN)\n\n        opts: t.List[t.Optional[exp.Expr]] = []\n        option: exp.Expr | t.List[exp.Expr] | None\n        while self._curr and not self._match(TokenType.R_PAREN):\n            if self._match_text_seq(\"FORMAT_NAME\", \"=\"):\n                # The FORMAT_NAME can be set to an identifier for Snowflake and T-SQL\n                option = self._parse_format_name()\n            else:\n                option = self._parse_property()\n\n            if option is None:\n                self.raise_error(\"Unable to parse option\")\n                break\n\n            opts.extend(ensure_list(option))\n\n        return opts\n\n    def _parse_copy_parameters(self) -> t.List[exp.CopyParameter]:\n        sep = TokenType.COMMA if self.dialect.COPY_PARAMS_ARE_CSV else None\n\n        options = []\n        while self._curr and not self._match(TokenType.R_PAREN, advance=False):\n            option = self._parse_var(any_token=True)\n            prev = self._prev.text.upper()\n\n            # Different dialects might separate options and values by white space, \"=\" and \"AS\"\n            self._match(TokenType.EQ)\n            self._match(TokenType.ALIAS)\n\n            param = self.expression(exp.CopyParameter(this=option))\n\n            if prev in self.COPY_INTO_VARLEN_OPTIONS and self._match(\n                TokenType.L_PAREN, advance=False\n            ):\n                # Snowflake FILE_FORMAT case, Databricks COPY & FORMAT options\n                param.set(\"expressions\", self._parse_wrapped_options())\n            elif prev == \"FILE_FORMAT\":\n                # T-SQL's external file format case\n                param.set(\"expression\", self._parse_field())\n            elif (\n                prev == \"FORMAT\"\n                and self._prev.token_type == TokenType.ALIAS\n                and self._match_texts((\"AVRO\", \"JSON\"))\n            ):\n                param.set(\"this\", exp.var(f\"FORMAT AS {self._prev.text.upper()}\"))\n                param.set(\"expression\", self._parse_field())\n            else:\n                param.set(\"expression\", self._parse_unquoted_field() or self._parse_bracket())\n\n            options.append(param)\n\n            if sep:\n                self._match(sep)\n\n        return options\n\n    def _parse_credentials(self) -> t.Optional[exp.Credentials]:\n        expr = self.expression(exp.Credentials())\n\n        if self._match_text_seq(\"STORAGE_INTEGRATION\", \"=\"):\n            expr.set(\"storage\", self._parse_field())\n        if self._match_text_seq(\"CREDENTIALS\"):\n            # Snowflake case: CREDENTIALS = (...), Redshift case: CREDENTIALS <string>\n            creds = (\n                self._parse_wrapped_options() if self._match(TokenType.EQ) else self._parse_field()\n            )\n            expr.set(\"credentials\", creds)\n        if self._match_text_seq(\"ENCRYPTION\"):\n            expr.set(\"encryption\", self._parse_wrapped_options())\n        if self._match_text_seq(\"IAM_ROLE\"):\n            expr.set(\n                \"iam_role\",\n                exp.var(self._prev.text) if self._match(TokenType.DEFAULT) else self._parse_field(),\n            )\n        if self._match_text_seq(\"REGION\"):\n            expr.set(\"region\", self._parse_field())\n\n        return expr\n\n    def _parse_file_location(self) -> t.Optional[exp.Expr]:\n        return self._parse_field()\n\n    def _parse_copy(self) -> exp.Copy | exp.Command:\n        start = self._prev\n\n        self._match(TokenType.INTO)\n\n        this = (\n            self._parse_select(nested=True, parse_subquery_alias=False)\n            if self._match(TokenType.L_PAREN, advance=False)\n            else self._parse_table(schema=True)\n        )\n\n        kind = self._match(TokenType.FROM) or not self._match_text_seq(\"TO\")\n\n        files = self._parse_csv(self._parse_file_location)\n        if self._match(TokenType.EQ, advance=False):\n            # Backtrack one token since we've consumed the lhs of a parameter assignment here.\n            # This can happen for Snowflake dialect. Instead, we'd like to parse the parameter\n            # list via `_parse_wrapped(..)` below.\n            self._advance(-1)\n            files = []\n\n        credentials = self._parse_credentials()\n\n        self._match_text_seq(\"WITH\")\n\n        params = self._parse_wrapped(self._parse_copy_parameters, optional=True)\n\n        # Fallback case\n        if self._curr:\n            return self._parse_as_command(start)\n\n        return self.expression(\n            exp.Copy(this=this, kind=kind, credentials=credentials, files=files, params=params)\n        )\n\n    def _parse_normalize(self) -> exp.Normalize:\n        return self.expression(\n            exp.Normalize(\n                this=self._parse_bitwise(), form=self._match(TokenType.COMMA) and self._parse_var()\n            )\n        )\n\n    def _parse_ceil_floor(self, expr_type: t.Type[TCeilFloor]) -> TCeilFloor:\n        args = self._parse_csv(lambda: self._parse_lambda())\n\n        this = seq_get(args, 0)\n        decimals = seq_get(args, 1)\n\n        return expr_type(\n            this=this,\n            decimals=decimals,\n            to=self._parse_var() if self._match_text_seq(\"TO\") else None,\n        )\n\n    def _parse_star_ops(self) -> t.Optional[exp.Expr]:\n        star_token = self._prev\n\n        if self._match_text_seq(\"COLUMNS\", \"(\", advance=False):\n            this = self._parse_function()\n            if isinstance(this, exp.Columns):\n                this.set(\"unpack\", True)\n            return this\n\n        return self.expression(\n            exp.Star(\n                except_=self._parse_star_op(\"EXCEPT\", \"EXCLUDE\"),\n                replace=self._parse_star_op(\"REPLACE\"),\n                rename=self._parse_star_op(\"RENAME\"),\n            )\n        ).update_positions(star_token)\n\n    def _parse_grant_privilege(self) -> t.Optional[exp.GrantPrivilege]:\n        privilege_parts = []\n\n        # Keep consuming consecutive keywords until comma (end of this privilege) or ON\n        # (end of privilege list) or L_PAREN (start of column list) are met\n        while self._curr and not self._match_set(self.PRIVILEGE_FOLLOW_TOKENS, advance=False):\n            privilege_parts.append(self._curr.text.upper())\n            self._advance()\n\n        this = exp.var(\" \".join(privilege_parts))\n        expressions = (\n            self._parse_wrapped_csv(self._parse_column)\n            if self._match(TokenType.L_PAREN, advance=False)\n            else None\n        )\n\n        return self.expression(exp.GrantPrivilege(this=this, expressions=expressions))\n\n    def _parse_grant_principal(self) -> t.Optional[exp.GrantPrincipal]:\n        kind = self._match_texts((\"ROLE\", \"GROUP\")) and self._prev.text.upper()\n        principal = self._parse_id_var()\n\n        if not principal:\n            return None\n\n        return self.expression(exp.GrantPrincipal(this=principal, kind=kind))\n\n    def _parse_grant_revoke_common(\n        self,\n    ) -> t.Tuple[t.Optional[t.List], t.Optional[str], t.Optional[exp.Expr]]:\n        privileges = self._parse_csv(self._parse_grant_privilege)\n\n        self._match(TokenType.ON)\n        kind = self._prev.text.upper() if self._match_set(self.CREATABLES) else None\n\n        # Attempt to parse the securable e.g. MySQL allows names\n        # such as \"foo.*\", \"*.*\" which are not easily parseable yet\n        securable = self._try_parse(self._parse_table_parts)\n\n        return privileges, kind, securable\n\n    def _parse_grant(self) -> exp.Grant | exp.Command:\n        start = self._prev\n\n        privileges, kind, securable = self._parse_grant_revoke_common()\n\n        if not securable or not self._match_text_seq(\"TO\"):\n            return self._parse_as_command(start)\n\n        principals = self._parse_csv(self._parse_grant_principal)\n\n        grant_option = self._match_text_seq(\"WITH\", \"GRANT\", \"OPTION\")\n\n        if self._curr:\n            return self._parse_as_command(start)\n\n        return self.expression(\n            exp.Grant(\n                privileges=privileges,\n                kind=kind,\n                securable=securable,\n                principals=principals,\n                grant_option=grant_option,\n            )\n        )\n\n    def _parse_revoke(self) -> exp.Revoke | exp.Command:\n        start = self._prev\n\n        grant_option = self._match_text_seq(\"GRANT\", \"OPTION\", \"FOR\")\n\n        privileges, kind, securable = self._parse_grant_revoke_common()\n\n        if not securable or not self._match_text_seq(\"FROM\"):\n            return self._parse_as_command(start)\n\n        principals = self._parse_csv(self._parse_grant_principal)\n\n        cascade = None\n        if self._match_texts((\"CASCADE\", \"RESTRICT\")):\n            cascade = self._prev.text.upper()\n\n        if self._curr:\n            return self._parse_as_command(start)\n\n        return self.expression(\n            exp.Revoke(\n                privileges=privileges,\n                kind=kind,\n                securable=securable,\n                principals=principals,\n                grant_option=grant_option,\n                cascade=cascade,\n            )\n        )\n\n    def _parse_overlay(self) -> exp.Overlay:\n        def _parse_overlay_arg(text: str) -> t.Optional[exp.Expr]:\n            return (\n                self._parse_bitwise()\n                if self._match(TokenType.COMMA) or self._match_text_seq(text)\n                else None\n            )\n\n        return self.expression(\n            exp.Overlay(\n                this=self._parse_bitwise(),\n                expression=_parse_overlay_arg(\"PLACING\"),\n                from_=_parse_overlay_arg(\"FROM\"),\n                for_=_parse_overlay_arg(\"FOR\"),\n            )\n        )\n\n    def _parse_format_name(self) -> exp.Property:\n        # Note: Although not specified in the docs, Snowflake does accept a string/identifier\n        # for FILE_FORMAT = <format_name>\n        return self.expression(\n            exp.Property(\n                this=exp.var(\"FORMAT_NAME\"), value=self._parse_string() or self._parse_table_parts()\n            )\n        )\n\n    def _parse_max_min_by(self, expr_type: t.Type[exp.AggFunc]) -> exp.AggFunc:\n        args: t.List[exp.Expr] = []\n\n        if self._match(TokenType.DISTINCT):\n            args.append(self.expression(exp.Distinct(expressions=[self._parse_lambda()])))\n            self._match(TokenType.COMMA)\n\n        args.extend(self._parse_function_args())\n\n        return self.expression(\n            expr_type(this=seq_get(args, 0), expression=seq_get(args, 1), count=seq_get(args, 2))\n        )\n\n    def _identifier_expression(\n        self, token: t.Optional[Token] = None, quoted: t.Optional[bool] = None\n    ) -> exp.Identifier:\n        token = token or self._prev\n        return self.expression(exp.Identifier(this=token.text, quoted=quoted), token)\n\n    def _build_pipe_cte(\n        self,\n        query: exp.Query,\n        expressions: t.List[exp.Expr],\n        alias_cte: t.Optional[exp.TableAlias] = None,\n    ) -> exp.Select:\n        new_cte: t.Optional[t.Union[str, exp.TableAlias]]\n        if alias_cte:\n            new_cte = alias_cte\n        else:\n            self._pipe_cte_counter += 1\n            new_cte = f\"__tmp{self._pipe_cte_counter}\"\n\n        with_ = query.args.get(\"with_\")\n        ctes = with_.pop() if with_ else None\n\n        new_select = exp.select(*expressions, copy=False).from_(new_cte, copy=False)\n        if ctes:\n            new_select.set(\"with_\", ctes)\n\n        return new_select.with_(new_cte, as_=query, copy=False)\n\n    def _parse_pipe_syntax_select(self, query: exp.Select) -> exp.Select:\n        select = self._parse_select(consume_pipe=False)\n        if not select:\n            return query\n\n        return self._build_pipe_cte(\n            query=query.select(*select.expressions, append=False), expressions=[exp.Star()]\n        )\n\n    def _parse_pipe_syntax_limit(self, query: exp.Select) -> exp.Select:\n        limit = self._parse_limit()\n        offset = self._parse_offset()\n        if limit:\n            curr_limit = query.args.get(\"limit\", limit)\n            if curr_limit.expression.to_py() >= limit.expression.to_py():\n                query.limit(limit, copy=False)\n        if offset:\n            curr_offset = query.args.get(\"offset\")\n            curr_offset = curr_offset.expression.to_py() if curr_offset else 0\n            query.offset(exp.Literal.number(curr_offset + offset.expression.to_py()), copy=False)\n\n        return query\n\n    def _parse_pipe_syntax_aggregate_fields(self) -> t.Optional[exp.Expr]:\n        this = self._parse_disjunction()\n        if self._match_text_seq(\"GROUP\", \"AND\", advance=False):\n            return this\n\n        this = self._parse_alias(this)\n\n        if self._match_set((TokenType.ASC, TokenType.DESC), advance=False):\n            return self._parse_ordered(lambda: this)\n\n        return this\n\n    def _parse_pipe_syntax_aggregate_group_order_by(\n        self, query: exp.Select, group_by_exists: bool = True\n    ) -> exp.Select:\n        expr = self._parse_csv(self._parse_pipe_syntax_aggregate_fields)\n        aggregates_or_groups, orders = [], []\n        for element in expr:\n            if isinstance(element, exp.Ordered):\n                this = element.this\n                if isinstance(this, exp.Alias):\n                    element.set(\"this\", this.args[\"alias\"])\n                orders.append(element)\n            else:\n                this = element\n            aggregates_or_groups.append(this)\n\n        if group_by_exists:\n            query.select(*aggregates_or_groups, copy=False).group_by(\n                *[projection.args.get(\"alias\", projection) for projection in aggregates_or_groups],\n                copy=False,\n            )\n        else:\n            query.select(*aggregates_or_groups, append=False, copy=False)\n\n        if orders:\n            return query.order_by(*orders, append=False, copy=False)\n\n        return query\n\n    def _parse_pipe_syntax_aggregate(self, query: exp.Select) -> exp.Select:\n        self._match_text_seq(\"AGGREGATE\")\n        query = self._parse_pipe_syntax_aggregate_group_order_by(query, group_by_exists=False)\n\n        if self._match(TokenType.GROUP_BY) or (\n            self._match_text_seq(\"GROUP\", \"AND\") and self._match(TokenType.ORDER_BY)\n        ):\n            query = self._parse_pipe_syntax_aggregate_group_order_by(query)\n\n        return self._build_pipe_cte(query=query, expressions=[exp.Star()])\n\n    def _parse_pipe_syntax_set_operator(self, query: exp.Query) -> t.Optional[exp.Query]:\n        first_setop = self.parse_set_operation(this=query)\n        if not first_setop:\n            return None\n\n        def _parse_and_unwrap_query() -> t.Optional[exp.Expr]:\n            expr = self._parse_paren()\n            return expr.assert_is(exp.Subquery).unnest() if expr else None\n\n        first_setop.this.pop()\n\n        setops = [\n            first_setop.expression.pop().assert_is(exp.Subquery).unnest(),\n            *self._parse_csv(_parse_and_unwrap_query),\n        ]\n\n        query = self._build_pipe_cte(query=query, expressions=[exp.Star()])\n        with_ = query.args.get(\"with_\")\n        ctes = with_.pop() if with_ else None\n\n        if isinstance(first_setop, exp.Union):\n            query = query.union(*setops, copy=False, **first_setop.args)\n        elif isinstance(first_setop, exp.Except):\n            query = query.except_(*setops, copy=False, **first_setop.args)\n        else:\n            query = query.intersect(*setops, copy=False, **first_setop.args)\n\n        query.set(\"with_\", ctes)\n\n        return self._build_pipe_cte(query=query, expressions=[exp.Star()])\n\n    def _parse_pipe_syntax_join(self, query: exp.Query) -> t.Optional[exp.Query]:\n        join = self._parse_join()\n        if not join:\n            return None\n\n        if isinstance(query, exp.Select):\n            return query.join(join, copy=False)\n\n        return query\n\n    def _parse_pipe_syntax_pivot(self, query: exp.Select) -> exp.Select:\n        pivots = self._parse_pivots()\n        if not pivots:\n            return query\n\n        from_ = query.args.get(\"from_\")\n        if from_:\n            from_.this.set(\"pivots\", pivots)\n\n        return self._build_pipe_cte(query=query, expressions=[exp.Star()])\n\n    def _parse_pipe_syntax_extend(self, query: exp.Select) -> exp.Select:\n        self._match_text_seq(\"EXTEND\")\n        query.select(*[exp.Star(), *self._parse_expressions()], append=False, copy=False)\n        return self._build_pipe_cte(query=query, expressions=[exp.Star()])\n\n    def _parse_pipe_syntax_tablesample(self, query: exp.Select) -> exp.Select:\n        sample = self._parse_table_sample()\n\n        with_ = query.args.get(\"with_\")\n        if with_:\n            with_.expressions[-1].this.set(\"sample\", sample)\n        else:\n            query.set(\"sample\", sample)\n\n        return query\n\n    def _parse_pipe_syntax_query(self, query: exp.Query) -> t.Optional[exp.Query]:\n        if isinstance(query, exp.Subquery):\n            query = exp.select(\"*\").from_(query, copy=False)\n\n        if not query.args.get(\"from_\"):\n            query = exp.select(\"*\").from_(query.subquery(copy=False), copy=False)\n\n        while self._match(TokenType.PIPE_GT):\n            start_index = self._index\n            start_text = self._curr.text.upper()\n            parser = self.PIPE_SYNTAX_TRANSFORM_PARSERS.get(start_text)\n            if not parser:\n                # The set operators (UNION, etc) and the JOIN operator have a few common starting\n                # keywords, making it tricky to disambiguate them without lookahead. The approach\n                # here is to try and parse a set operation and if that fails, then try to parse a\n                # join operator. If that fails as well, then the operator is not supported.\n                parsed_query = self._parse_pipe_syntax_set_operator(query)\n                parsed_query = parsed_query or self._parse_pipe_syntax_join(query)\n                if not parsed_query:\n                    self._retreat(start_index)\n                    self.raise_error(f\"Unsupported pipe syntax operator: '{start_text}'.\")\n                    break\n                query = parsed_query\n            else:\n                query = parser(self, query)\n\n        return query\n\n    def _parse_declareitem(self) -> t.Optional[exp.DeclareItem]:\n        self._match_texts((\"VAR\", \"VARIABLE\"))\n\n        vars = self._parse_csv(self._parse_id_var)\n        if not vars:\n            return None\n\n        self._match(TokenType.ALIAS)\n        kind = self._parse_schema() if self._match(TokenType.TABLE) else self._parse_types()\n        default = (\n            self._match(TokenType.DEFAULT) or self._match(TokenType.EQ)\n        ) and self._parse_bitwise()\n\n        return self.expression(exp.DeclareItem(this=vars, kind=kind, default=default))\n\n    def _parse_declare(self) -> exp.Declare | exp.Command:\n        start = self._prev\n        replace = self._match_text_seq(\"OR\", \"REPLACE\")\n        expressions = self._try_parse(lambda: self._parse_csv(self._parse_declareitem))\n\n        if not expressions or self._curr:\n            return self._parse_as_command(start)\n\n        return self.expression(exp.Declare(expressions=expressions, replace=replace))\n\n    def build_cast(self, strict: bool, **kwargs) -> exp.Cast:\n        exp_class = exp.Cast if strict else exp.TryCast\n\n        if exp_class == exp.TryCast:\n            kwargs[\"requires_string\"] = self.dialect.TRY_CAST_REQUIRES_STRING\n\n        return self.expression(exp_class(**kwargs))\n\n    def _parse_json_value(self) -> exp.JSONValue:\n        this = self._parse_bitwise()\n        self._match(TokenType.COMMA)\n        path = self._parse_bitwise()\n\n        returning = self._match(TokenType.RETURNING) and self._parse_type()\n\n        return self.expression(\n            exp.JSONValue(\n                this=this,\n                path=self.dialect.to_json_path(path),\n                returning=returning,\n                on_condition=self._parse_on_condition(),\n            )\n        )\n\n    def _parse_group_concat(self) -> t.Optional[exp.Expr]:\n        def concat_exprs(node: t.Optional[exp.Expr], exprs: t.List[exp.Expr]) -> exp.Expr:\n            if isinstance(node, exp.Distinct) and len(node.expressions) > 1:\n                concat_exprs = [\n                    self.expression(\n                        exp.Concat(\n                            expressions=node.expressions,\n                            safe=True,\n                            coalesce=self.dialect.CONCAT_COALESCE,\n                        )\n                    )\n                ]\n                node.set(\"expressions\", concat_exprs)\n                return node\n            if len(exprs) == 1:\n                return exprs[0]\n            return self.expression(\n                exp.Concat(expressions=args, safe=True, coalesce=self.dialect.CONCAT_COALESCE)\n            )\n\n        args = self._parse_csv(self._parse_lambda)\n\n        if args:\n            order = args[-1] if isinstance(args[-1], exp.Order) else None\n\n            if order:\n                # Order By is the last (or only) expression in the list and has consumed the 'expr' before it,\n                # remove 'expr' from exp.Order and add it back to args\n                args[-1] = order.this\n                order.set(\"this\", concat_exprs(order.this, args))\n\n            this = order or concat_exprs(args[0], args)\n        else:\n            this = None\n\n        separator = self._parse_field() if self._match(TokenType.SEPARATOR) else None\n\n        return self.expression(exp.GroupConcat(this=this, separator=separator))\n\n    def _parse_initcap(self) -> exp.Initcap:\n        expr = exp.Initcap.from_arg_list(self._parse_function_args())\n\n        # attach dialect's default delimiters\n        if expr.args.get(\"expression\") is None:\n            expr.set(\"expression\", exp.Literal.string(self.dialect.INITCAP_DEFAULT_DELIMITER_CHARS))\n\n        return expr\n\n    def _parse_operator(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        while True:\n            if not self._match(TokenType.L_PAREN):\n                break\n\n            op = \"\"\n            while self._curr and not self._match(TokenType.R_PAREN):\n                op += self._curr.text\n                self._advance()\n\n            comments = self._prev_comments\n            this = self.expression(\n                exp.Operator(this=this, operator=op, expression=self._parse_bitwise()),\n                comments=comments,\n            )\n\n            if not self._match(TokenType.OPERATOR):\n                break\n\n        return this\n"
  },
  {
    "path": "sqlglot/parsers/__init__.py",
    "content": "\n"
  },
  {
    "path": "sqlglot/parsers/athena.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.errors import ErrorLevel\nfrom sqlglot.parser import Parser\nfrom sqlglot.parsers.trino import TrinoParser\nfrom sqlglot.tokens import TokenType, Token\n\n\nclass AthenaTrinoParser(TrinoParser):\n    STATEMENT_PARSERS = {\n        **TrinoParser.STATEMENT_PARSERS,\n        TokenType.USING: lambda self: self._parse_as_command(self._prev),\n    }\n\n\nclass AthenaParser(Parser):\n    def __init__(\n        self,\n        error_level: t.Optional[ErrorLevel] = None,\n        error_message_context: int = 100,\n        max_errors: int = 3,\n        dialect: t.Any = None,\n        hive: t.Any = None,\n        trino: t.Any = None,\n        **kwargs: t.Any,\n    ) -> None:\n        from sqlglot.dialects import Hive, Trino\n\n        hive = hive or Hive()\n        trino = trino or Trino()\n\n        super().__init__(\n            error_level=error_level,\n            error_message_context=error_message_context,\n            max_errors=max_errors,\n            dialect=dialect,\n        )\n\n        self._hive_parser = hive.parser(\n            error_level=error_level,\n            error_message_context=error_message_context,\n            max_errors=max_errors,\n        )\n        self._trino_parser = AthenaTrinoParser(\n            error_level=error_level,\n            error_message_context=error_message_context,\n            max_errors=max_errors,\n            dialect=trino,\n        )\n\n    def parse(self, raw_tokens: t.List[Token], sql: str) -> t.List[t.Optional[exp.Expr]]:\n        if raw_tokens and raw_tokens[0].token_type == TokenType.HIVE_TOKEN_STREAM:\n            return self._hive_parser.parse(raw_tokens[1:], sql)\n\n        return self._trino_parser.parse(raw_tokens, sql)\n\n    def parse_into(\n        self,\n        expression_types: exp.IntoType,\n        raw_tokens: t.List[Token],\n        sql: t.Optional[str] = None,\n    ) -> t.List[t.Optional[exp.Expr]]:\n        if raw_tokens and raw_tokens[0].token_type == TokenType.HIVE_TOKEN_STREAM:\n            return self._hive_parser.parse_into(expression_types, raw_tokens[1:], sql)\n\n        return self._trino_parser.parse_into(expression_types, raw_tokens, sql)\n"
  },
  {
    "path": "sqlglot/parsers/base.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp, parser\nfrom sqlglot.tokens import TokenType\n\n\nclass BaseParser(parser.Parser):\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.LOCALTIME: exp.Localtime,\n        TokenType.LOCALTIMESTAMP: exp.Localtimestamp,\n        TokenType.CURRENT_CATALOG: exp.CurrentCatalog,\n        TokenType.SESSION_USER: exp.SessionUser,\n    }\n\n    ID_VAR_TOKENS = parser.Parser.ID_VAR_TOKENS - {TokenType.STRAIGHT_JOIN}\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS - {TokenType.STRAIGHT_JOIN}\n"
  },
  {
    "path": "sqlglot/parsers/bigquery.py",
    "content": "from __future__ import annotations\n\nimport re\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import (\n    binary_from_function,\n    build_date_delta_with_interval,\n    build_formatted_time,\n)\nfrom sqlglot.helper import seq_get, split_num_words\nfrom sqlglot.tokens import TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n\n\ndef _build_contains_substring(args: t.List) -> exp.Contains:\n    this = exp.Lower(this=seq_get(args, 0))\n    expr = exp.Lower(this=seq_get(args, 1))\n    return exp.Contains(this=this, expression=expr, json_scope=seq_get(args, 2))\n\n\ndef _build_date(args: t.List) -> exp.Date | exp.DateFromParts:\n    expr_type = exp.DateFromParts if len(args) == 3 else exp.Date\n    return expr_type.from_arg_list(args)\n\n\ndef build_date_diff(args: t.List) -> exp.Expr:\n    expr = exp.DateDiff(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        unit=seq_get(args, 2),\n        date_part_boundary=True,\n    )\n\n    unit = expr.args.get(\"unit\")\n    if isinstance(unit, exp.Var) and unit.name.upper() == \"WEEK\":\n        expr.set(\"unit\", exp.WeekStart(this=exp.var(\"SUNDAY\")))\n\n    return expr\n\n\ndef _build_datetime(args: t.List) -> exp.Func:\n    if len(args) == 1:\n        return exp.TsOrDsToDatetime.from_arg_list(args)\n    if len(args) == 2:\n        return exp.Datetime.from_arg_list(args)\n    return exp.TimestampFromParts.from_arg_list(args)\n\n\ndef _build_extract_json_with_default_path(\n    expr_type: t.Type[E],\n) -> t.Callable:\n    def _builder(args: t.List, dialect: t.Any) -> E:\n        if len(args) == 1:\n            args.append(exp.Literal.string(\"$\"))\n        return parser.build_extract_json_with_path(expr_type)(args, dialect)\n\n    return _builder\n\n\ndef _build_format_time(expr_type: t.Type[exp.Expr]) -> t.Callable[[t.List], exp.TimeToStr]:\n    def _builder(args: t.List) -> exp.TimeToStr:\n        formatted_time = build_formatted_time(exp.TimeToStr, \"bigquery\")(\n            [expr_type(this=seq_get(args, 1)), seq_get(args, 0)]\n        )\n        formatted_time.set(\"zone\", seq_get(args, 2))\n        return formatted_time\n\n    return _builder\n\n\ndef _build_json_strip_nulls(args: t.List) -> exp.JSONStripNulls:\n    expression = exp.JSONStripNulls(this=seq_get(args, 0))\n    for arg in args[1:]:\n        if isinstance(arg, exp.Kwarg):\n            expression.set(arg.this.name.lower(), arg)\n        else:\n            expression.set(\"expression\", arg)\n    return expression\n\n\ndef _build_levenshtein(args: t.List) -> exp.Levenshtein:\n    max_dist = seq_get(args, 2)\n    return exp.Levenshtein(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        max_dist=max_dist.expression if max_dist else None,\n    )\n\n\ndef _build_parse_timestamp(args: t.List) -> exp.StrToTime:\n    this = build_formatted_time(exp.StrToTime, \"bigquery\")([seq_get(args, 1), seq_get(args, 0)])\n    this.set(\"zone\", seq_get(args, 2))\n    return this\n\n\ndef _build_regexp_extract(\n    expr_type: t.Type[E], default_group: t.Optional[exp.Expr] = None\n) -> t.Callable:\n    def _builder(args: t.List, dialect: t.Any) -> E:\n        try:\n            group = re.compile(args[1].name).groups == 1\n        except re.error:\n            group = False\n\n        return expr_type(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            position=seq_get(args, 2),\n            occurrence=seq_get(args, 3),\n            group=exp.Literal.number(1) if group else default_group,\n            **(\n                {\"null_if_pos_overflow\": dialect.REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL}\n                if expr_type is exp.RegexpExtract\n                else {}\n            ),\n        )\n\n    return _builder\n\n\ndef _build_time(args: t.List) -> exp.Func:\n    if len(args) == 1:\n        return exp.TsOrDsToTime(this=args[0])\n    if len(args) == 2:\n        return exp.Time.from_arg_list(args)\n    return exp.TimeFromParts.from_arg_list(args)\n\n\ndef _build_timestamp(args: t.List) -> exp.Timestamp:\n    timestamp = exp.Timestamp.from_arg_list(args)\n    timestamp.set(\"with_tz\", True)\n    return timestamp\n\n\ndef _build_to_hex(args: t.List) -> exp.Hex | exp.MD5:\n    arg = seq_get(args, 0)\n    return exp.MD5(this=arg.this) if isinstance(arg, exp.MD5Digest) else exp.LowerHex(this=arg)\n\n\nMAKE_INTERVAL_KWARGS = [\"year\", \"month\", \"day\", \"hour\", \"minute\", \"second\"]\n\n\nclass BigQueryParser(parser.Parser):\n    PREFIXED_PIVOT_COLUMNS: t.ClassVar = True\n    LOG_DEFAULTS_TO_LN: t.ClassVar = True\n    SUPPORTS_IMPLICIT_UNNEST: t.ClassVar = True\n    JOINS_HAVE_EQUAL_PRECEDENCE: t.ClassVar = True\n\n    # BigQuery does not allow ASC/DESC to be used as an identifier, allows GRANT as an identifier\n    ID_VAR_TOKENS: t.ClassVar = {\n        *parser.Parser.ID_VAR_TOKENS,\n        TokenType.GRANT,\n    } - {TokenType.ASC, TokenType.DESC}\n\n    ALIAS_TOKENS: t.ClassVar = {\n        *parser.Parser.ALIAS_TOKENS,\n        TokenType.GRANT,\n    } - {TokenType.ASC, TokenType.DESC}\n\n    TABLE_ALIAS_TOKENS: t.ClassVar = {\n        *parser.Parser.TABLE_ALIAS_TOKENS,\n        TokenType.ANTI,\n        TokenType.GRANT,\n        TokenType.SEMI,\n    } - {TokenType.ASC, TokenType.DESC}\n\n    COMMENT_TABLE_ALIAS_TOKENS: t.ClassVar = {\n        *parser.Parser.COMMENT_TABLE_ALIAS_TOKENS,\n        TokenType.GRANT,\n    } - {TokenType.ASC, TokenType.DESC}\n\n    UPDATE_ALIAS_TOKENS: t.ClassVar = {\n        *parser.Parser.UPDATE_ALIAS_TOKENS,\n        TokenType.GRANT,\n    } - {TokenType.ASC, TokenType.DESC}\n\n    FUNCTIONS: t.ClassVar[t.Dict[str, t.Callable]] = {\n        **{k: v for k, v in parser.Parser.FUNCTIONS.items() if k != \"SEARCH\"},\n        \"APPROX_TOP_COUNT\": exp.ApproxTopK.from_arg_list,\n        \"BIT_AND\": exp.BitwiseAndAgg.from_arg_list,\n        \"BIT_OR\": exp.BitwiseOrAgg.from_arg_list,\n        \"BIT_XOR\": exp.BitwiseXorAgg.from_arg_list,\n        \"BIT_COUNT\": exp.BitwiseCount.from_arg_list,\n        \"BOOL\": exp.JSONBool.from_arg_list,\n        \"CONTAINS_SUBSTR\": _build_contains_substring,\n        \"DATE\": _build_date,\n        \"DATE_ADD\": build_date_delta_with_interval(exp.DateAdd),\n        \"DATE_DIFF\": build_date_diff,\n        \"DATE_SUB\": build_date_delta_with_interval(exp.DateSub),\n        \"DATE_TRUNC\": lambda args: exp.DateTrunc(\n            unit=seq_get(args, 1),\n            this=seq_get(args, 0),\n            zone=seq_get(args, 2),\n        ),\n        \"DATETIME\": _build_datetime,\n        \"DATETIME_ADD\": build_date_delta_with_interval(exp.DatetimeAdd),\n        \"DATETIME_SUB\": build_date_delta_with_interval(exp.DatetimeSub),\n        \"DIV\": binary_from_function(exp.IntDiv),\n        \"EDIT_DISTANCE\": _build_levenshtein,\n        \"FORMAT_DATE\": _build_format_time(exp.TsOrDsToDate),\n        \"GENERATE_ARRAY\": exp.GenerateSeries.from_arg_list,\n        \"JSON_EXTRACT_SCALAR\": _build_extract_json_with_default_path(exp.JSONExtractScalar),\n        \"JSON_EXTRACT_ARRAY\": _build_extract_json_with_default_path(exp.JSONExtractArray),\n        \"JSON_EXTRACT_STRING_ARRAY\": _build_extract_json_with_default_path(exp.JSONValueArray),\n        \"JSON_KEYS\": exp.JSONKeysAtDepth.from_arg_list,\n        \"JSON_QUERY\": parser.build_extract_json_with_path(exp.JSONExtract),\n        \"JSON_QUERY_ARRAY\": _build_extract_json_with_default_path(exp.JSONExtractArray),\n        \"JSON_STRIP_NULLS\": _build_json_strip_nulls,\n        \"JSON_VALUE\": _build_extract_json_with_default_path(exp.JSONExtractScalar),\n        \"JSON_VALUE_ARRAY\": _build_extract_json_with_default_path(exp.JSONValueArray),\n        \"LENGTH\": lambda args: exp.Length(this=seq_get(args, 0), binary=True),\n        \"MD5\": exp.MD5Digest.from_arg_list,\n        \"SHA1\": exp.SHA1Digest.from_arg_list,\n        \"NORMALIZE_AND_CASEFOLD\": lambda args: exp.Normalize(\n            this=seq_get(args, 0), form=seq_get(args, 1), is_casefold=True\n        ),\n        \"OCTET_LENGTH\": exp.ByteLength.from_arg_list,\n        \"TO_HEX\": _build_to_hex,\n        \"PARSE_DATE\": lambda args: build_formatted_time(exp.StrToDate, \"bigquery\")(\n            [seq_get(args, 1), seq_get(args, 0)]\n        ),\n        \"PARSE_TIME\": lambda args: build_formatted_time(exp.ParseTime, \"bigquery\")(\n            [seq_get(args, 1), seq_get(args, 0)]\n        ),\n        \"PARSE_TIMESTAMP\": _build_parse_timestamp,\n        \"PARSE_DATETIME\": lambda args: build_formatted_time(exp.ParseDatetime, \"bigquery\")(\n            [seq_get(args, 1), seq_get(args, 0)]\n        ),\n        \"REGEXP_CONTAINS\": exp.RegexpLike.from_arg_list,\n        \"REGEXP_EXTRACT\": _build_regexp_extract(exp.RegexpExtract),\n        \"REGEXP_SUBSTR\": _build_regexp_extract(exp.RegexpExtract),\n        \"REGEXP_EXTRACT_ALL\": _build_regexp_extract(\n            exp.RegexpExtractAll, default_group=exp.Literal.number(0)\n        ),\n        \"SHA256\": lambda args: exp.SHA2Digest(\n            this=seq_get(args, 0), length=exp.Literal.number(256)\n        ),\n        \"SHA512\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),\n        \"SPLIT\": lambda args: exp.Split(\n            # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#split\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1) or exp.Literal.string(\",\"),\n        ),\n        \"STRPOS\": exp.StrPosition.from_arg_list,\n        \"TIME\": _build_time,\n        \"TIME_ADD\": build_date_delta_with_interval(exp.TimeAdd),\n        \"TIME_SUB\": build_date_delta_with_interval(exp.TimeSub),\n        \"TIMESTAMP\": _build_timestamp,\n        \"TIMESTAMP_ADD\": build_date_delta_with_interval(exp.TimestampAdd),\n        \"TIMESTAMP_SUB\": build_date_delta_with_interval(exp.TimestampSub),\n        \"TIMESTAMP_MICROS\": lambda args: exp.UnixToTime(\n            this=seq_get(args, 0), scale=exp.UnixToTime.MICROS\n        ),\n        \"TIMESTAMP_MILLIS\": lambda args: exp.UnixToTime(\n            this=seq_get(args, 0), scale=exp.UnixToTime.MILLIS\n        ),\n        \"TIMESTAMP_SECONDS\": lambda args: exp.UnixToTime(this=seq_get(args, 0)),\n        \"TO_JSON\": lambda args: exp.JSONFormat(\n            this=seq_get(args, 0), options=seq_get(args, 1), to_json=True\n        ),\n        \"TO_JSON_STRING\": exp.JSONFormat.from_arg_list,\n        \"FORMAT_DATETIME\": _build_format_time(exp.TsOrDsToDatetime),\n        \"FORMAT_TIMESTAMP\": _build_format_time(exp.TsOrDsToTimestamp),\n        \"FORMAT_TIME\": _build_format_time(exp.TsOrDsToTime),\n        \"FROM_HEX\": exp.Unhex.from_arg_list,\n        \"WEEK\": lambda args: exp.WeekStart(this=exp.var(seq_get(args, 0))),\n    }\n\n    FUNCTION_PARSERS = {\n        **{k: v for k, v in parser.Parser.FUNCTION_PARSERS.items() if k != \"TRIM\"},\n        \"ARRAY\": lambda self: self.expression(\n            exp.Array(expressions=[self._parse_statement()], struct_name_inheritance=True)\n        ),\n        \"JSON_ARRAY\": lambda self: self.expression(\n            exp.JSONArray(expressions=self._parse_csv(self._parse_bitwise))\n        ),\n        \"MAKE_INTERVAL\": lambda self: self._parse_make_interval(),\n        \"PREDICT\": lambda self: self._parse_ml(exp.Predict),\n        \"TRANSLATE\": lambda self: self._parse_translate(),\n        \"FEATURES_AT_TIME\": lambda self: self._parse_features_at_time(),\n        \"GENERATE_EMBEDDING\": lambda self: self._parse_ml(exp.GenerateEmbedding),\n        \"GENERATE_TEXT_EMBEDDING\": lambda self: self._parse_ml(exp.GenerateEmbedding, is_text=True),\n        \"VECTOR_SEARCH\": lambda self: self._parse_vector_search(),\n        \"FORECAST\": lambda self: self._parse_ml(exp.MLForecast),\n    }\n\n    NO_PAREN_FUNCTIONS: t.ClassVar = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.CURRENT_DATETIME: exp.CurrentDatetime,\n    }\n\n    NESTED_TYPE_TOKENS: t.ClassVar = {\n        *parser.Parser.NESTED_TYPE_TOKENS,\n        TokenType.TABLE,\n    }\n\n    PROPERTY_PARSERS: t.ClassVar = {\n        **parser.Parser.PROPERTY_PARSERS,\n        \"NOT DETERMINISTIC\": lambda self: self.expression(\n            exp.StabilityProperty(this=exp.Literal.string(\"VOLATILE\"))\n        ),\n        \"OPTIONS\": lambda self: self._parse_with_property(),\n    }\n\n    CONSTRAINT_PARSERS: t.ClassVar = {\n        **parser.Parser.CONSTRAINT_PARSERS,\n        \"OPTIONS\": lambda self: exp.Properties(expressions=self._parse_with_property()),\n    }\n\n    RANGE_PARSERS: t.ClassVar = {\n        k: v for k, v in parser.Parser.RANGE_PARSERS.items() if k != TokenType.OVERLAPS\n    }\n\n    DASHED_TABLE_PART_FOLLOW_TOKENS: t.ClassVar = {\n        TokenType.DOT,\n        TokenType.L_PAREN,\n        TokenType.R_PAREN,\n    }\n\n    STATEMENT_PARSERS: t.ClassVar = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.ELSE: lambda self: self._parse_as_command(self._prev),\n        TokenType.END: lambda self: self._parse_as_command(self._prev),\n        TokenType.FOR: lambda self: self._parse_for_in(),\n        TokenType.EXPORT: lambda self: self._parse_export_data(),\n        TokenType.DECLARE: lambda self: self._parse_declare(),\n    }\n\n    BRACKET_OFFSETS: t.ClassVar = {\n        \"OFFSET\": (0, False),\n        \"ORDINAL\": (1, False),\n        \"SAFE_OFFSET\": (0, True),\n        \"SAFE_ORDINAL\": (1, True),\n    }\n\n    def _parse_for_in(self) -> t.Union[exp.ForIn, exp.Command]:\n        index = self._index\n        this = self._parse_range()\n        self._match_text_seq(\"DO\")\n        if self._match(TokenType.COMMAND):\n            self._retreat(index)\n            return self._parse_as_command(self._prev)\n        return self.expression(exp.ForIn(this=this, expression=self._parse_statement()))\n\n    def _parse_table_part(self, schema: bool = False) -> t.Optional[exp.Expr]:\n        this = super()._parse_table_part(schema=schema) or self._parse_number()\n\n        # https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#table_names\n        if isinstance(this, exp.Identifier):\n            table_name = this.name\n            while self._match(TokenType.DASH, advance=False) and self._next:\n                start = self._curr\n                while self._is_connected() and not self._match_set(\n                    self.DASHED_TABLE_PART_FOLLOW_TOKENS, advance=False\n                ):\n                    self._advance()\n\n                if start == self._curr:\n                    break\n\n                table_name += self._find_sql(start, self._prev)\n\n            this = exp.Identifier(this=table_name, quoted=this.args.get(\"quoted\")).update_positions(\n                this\n            )\n        elif isinstance(this, exp.Literal):\n            table_name = this.name\n\n            if self._is_connected() and self._parse_var(any_token=True):\n                table_name += self._prev.text\n\n            this = exp.Identifier(this=table_name, quoted=True).update_positions(this)\n\n        return this\n\n    def _parse_table_parts(\n        self,\n        schema: bool = False,\n        is_db_reference: bool = False,\n        wildcard: bool = False,\n        fast: bool = False,\n    ) -> t.Optional[exp.Table | exp.Dot]:\n        table = super()._parse_table_parts(\n            schema=schema, is_db_reference=is_db_reference, wildcard=True, fast=fast\n        )\n\n        if not isinstance(table, exp.Table):\n            return table\n\n        # proj-1.db.tbl -- `1.` is tokenized as a float so we need to unravel it here\n        if not table.catalog:\n            if table.db:\n                previous_db = table.args[\"db\"]\n                parts = table.db.split(\".\")\n                if len(parts) == 2 and not table.args[\"db\"].quoted:\n                    table.set(\n                        \"catalog\", exp.Identifier(this=parts[0]).update_positions(previous_db)\n                    )\n                    table.set(\"db\", exp.Identifier(this=parts[1]).update_positions(previous_db))\n            else:\n                previous_this = table.this\n                parts = table.name.split(\".\")\n                if len(parts) == 2 and not table.this.quoted:\n                    table.set(\"db\", exp.Identifier(this=parts[0]).update_positions(previous_this))\n                    table.set(\"this\", exp.Identifier(this=parts[1]).update_positions(previous_this))\n\n        if isinstance(table.this, exp.Identifier) and any(\".\" in p.name for p in table.parts):\n            alias = table.this\n            catalog, db, this_id, *rest = (\n                exp.to_identifier(p, quoted=True)\n                for p in split_num_words(\".\".join(p.name for p in table.parts), \".\", 3)\n            )\n\n            for part in (catalog, db, this_id):\n                if part:\n                    part.update_positions(table.this)\n\n            this: t.Optional[exp.Expr] = this_id\n            if rest and this:\n                this = exp.Dot.build([this, *rest])  # type: ignore[list-item]\n\n            table = exp.Table(this=this, db=db, catalog=catalog, pivots=table.args.get(\"pivots\"))\n            table.meta[\"quoted_table\"] = True\n        else:\n            alias = None\n\n        # The `INFORMATION_SCHEMA` views in BigQuery need to be qualified by a region or\n        # dataset, so if the project identifier is omitted we need to fix the ast so that\n        # the `INFORMATION_SCHEMA.X` bit is represented as a single (quoted) Identifier.\n        # Otherwise, we wouldn't correctly qualify a `Table` node that references these\n        # views, because it would seem like the \"catalog\" part is set, when it'd actually\n        # be the region/dataset. Merging the two identifiers into a single one is done to\n        # avoid producing a 4-part Table reference, which would cause issues in the schema\n        # module, when there are 3-part table names mixed with information schema views.\n        #\n        # See: https://cloud.google.com/bigquery/docs/information-schema-intro#syntax\n        table_parts = table.parts\n        if len(table_parts) > 1 and table_parts[-2].name.upper() == \"INFORMATION_SCHEMA\":\n            # We need to alias the table here to avoid breaking existing qualified columns.\n            # This is expected to be safe, because if there's an actual alias coming up in\n            # the token stream, it will overwrite this one. If there isn't one, we are only\n            # exposing the name that can be used to reference the view explicitly (a no-op).\n            exp.alias_(\n                table,\n                t.cast(exp.Identifier, alias or table_parts[-1]),\n                table=True,\n                copy=False,\n            )\n\n            info_schema_view = f\"{table_parts[-2].name}.{table_parts[-1].name}\"\n            new_this = exp.Identifier(this=info_schema_view, quoted=True).update_positions(\n                line=table_parts[-2].meta.get(\"line\"),\n                col=table_parts[-1].meta.get(\"col\"),\n                start=table_parts[-2].meta.get(\"start\"),\n                end=table_parts[-1].meta.get(\"end\"),\n            )\n            table.set(\"this\", new_this)\n            table.set(\"db\", seq_get(table_parts, -3))\n            table.set(\"catalog\", seq_get(table_parts, -4))\n\n        return table\n\n    def _parse_column(self) -> t.Optional[exp.Expr]:\n        column = super()._parse_column()\n        if isinstance(column, exp.Column):\n            parts = column.parts\n            if any(\".\" in p.name for p in parts):\n                catalog, db, table, this, *rest = (\n                    exp.to_identifier(p, quoted=True)\n                    for p in split_num_words(\".\".join(p.name for p in parts), \".\", 4)\n                )\n\n                if rest and this:\n                    this = exp.Dot.build([this, *rest])  # type: ignore\n\n                column = exp.Column(this=this, table=table, db=db, catalog=catalog)\n                column.meta[\"quoted_column\"] = True\n\n        return column\n\n    @t.overload\n    def _parse_json_object(self, agg: t.Literal[False]) -> exp.JSONObject: ...\n\n    @t.overload\n    def _parse_json_object(self, agg: t.Literal[True]) -> exp.JSONObjectAgg: ...\n\n    def _parse_json_object(self, agg=False):\n        json_object = super()._parse_json_object()\n        array_kv_pair = seq_get(json_object.expressions, 0)\n\n        # Converts BQ's \"signature 2\" of JSON_OBJECT into SQLGlot's canonical representation\n        # https://cloud.google.com/bigquery/docs/reference/standard-sql/json_functions#json_object_signature2\n        if (\n            array_kv_pair\n            and isinstance(array_kv_pair.this, exp.Array)\n            and isinstance(array_kv_pair.expression, exp.Array)\n        ):\n            keys = array_kv_pair.this.expressions\n            values = array_kv_pair.expression.expressions\n\n            json_object.set(\n                \"expressions\",\n                [exp.JSONKeyValue(this=k, expression=v) for k, v in zip(keys, values)],\n            )\n\n        return json_object\n\n    def _parse_bracket(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        bracket = super()._parse_bracket(this)\n\n        if isinstance(bracket, exp.Array):\n            bracket.set(\"struct_name_inheritance\", True)\n\n        if this is bracket:\n            return bracket\n\n        if isinstance(bracket, exp.Bracket):\n            for expression in bracket.expressions:\n                name = expression.name.upper()\n\n                if name not in self.BRACKET_OFFSETS:\n                    break\n\n                offset, safe = self.BRACKET_OFFSETS[name]\n                bracket.set(\"offset\", offset)\n                bracket.set(\"safe\", safe)\n                expression.replace(expression.expressions[0])\n\n        return bracket\n\n    def _parse_unnest(self, with_alias: bool = True) -> t.Optional[exp.Unnest]:\n        unnest = super()._parse_unnest(with_alias=with_alias)\n\n        if not unnest:\n            return None\n\n        unnest_expr = seq_get(unnest.expressions, 0)\n        if unnest_expr:\n            from sqlglot.optimizer.annotate_types import annotate_types\n\n            unnest_expr = annotate_types(unnest_expr, dialect=self.dialect)\n\n            # Unnesting a nested array (i.e array of structs) explodes the top-level struct fields,\n            # in contrast to other dialects such as DuckDB which flattens only the array by default\n            if unnest_expr.is_type(exp.DType.ARRAY) and any(\n                array_elem.is_type(exp.DType.STRUCT) for array_elem in unnest_expr._type.expressions\n            ):\n                unnest.set(\"explode_array\", True)\n\n        return unnest\n\n    def _parse_make_interval(self) -> exp.MakeInterval:\n        expr = exp.MakeInterval()\n\n        for arg_key in MAKE_INTERVAL_KWARGS:\n            value = self._parse_lambda()\n\n            if not value:\n                break\n\n            # Non-named arguments are filled sequentially, (optionally) followed by named arguments\n            # that can appear in any order e.g MAKE_INTERVAL(1, minute => 5, day => 2)\n            if isinstance(value, exp.Kwarg):\n                arg_key = value.this.name\n\n            expr.set(arg_key, value)\n\n            self._match(TokenType.COMMA)\n\n        return expr\n\n    def _parse_ml(self, expr_type: t.Type[E], **kwargs: t.Any) -> E:\n        self._match_text_seq(\"MODEL\")\n        this = self._parse_table()\n\n        self._match(TokenType.COMMA)\n        self._match_text_seq(\"TABLE\")\n\n        # Certain functions like ML.FORECAST require a STRUCT argument but not a TABLE/SELECT one\n        expression = (\n            self._parse_table() if not self._match(TokenType.STRUCT, advance=False) else None\n        )\n\n        self._match(TokenType.COMMA)\n\n        return self.expression(\n            expr_type(\n                this=this, expression=expression, params_struct=self._parse_bitwise(), **kwargs\n            )\n        )\n\n    def _parse_translate(self) -> exp.Translate | exp.MLTranslate:\n        # Check if this is ML.TRANSLATE by looking at previous tokens\n        token = seq_get(self._tokens, self._index - 4)\n        if token and token.text.upper() == \"ML\":\n            return self._parse_ml(exp.MLTranslate)\n\n        return exp.Translate.from_arg_list(self._parse_function_args())\n\n    def _parse_features_at_time(self) -> exp.FeaturesAtTime:\n        self._match(TokenType.TABLE)\n        this = self._parse_table()\n\n        expr = self.expression(exp.FeaturesAtTime(this=this))\n\n        while self._match(TokenType.COMMA):\n            arg = self._parse_lambda()\n\n            # Get the LHS of the Kwarg and set the arg to that value, e.g\n            # \"num_rows => 1\" sets the expr's `num_rows` arg\n            if arg:\n                expr.set(arg.this.name, arg)\n\n        return expr\n\n    def _parse_vector_search(self) -> exp.VectorSearch:\n        self._match(TokenType.TABLE)\n        base_table = self._parse_table()\n\n        self._match(TokenType.COMMA)\n\n        column_to_search = self._parse_bitwise()\n        self._match(TokenType.COMMA)\n\n        self._match(TokenType.TABLE)\n        query_table = self._parse_table()\n\n        expr = self.expression(\n            exp.VectorSearch(\n                this=base_table, column_to_search=column_to_search, query_table=query_table\n            )\n        )\n\n        while self._match(TokenType.COMMA):\n            # query_column_to_search can be named argument or positional\n            if self._match(TokenType.STRING, advance=False):\n                query_column = self._parse_string()\n                expr.set(\"query_column_to_search\", query_column)\n            else:\n                arg = self._parse_lambda()\n                if arg:\n                    expr.set(arg.this.name, arg)\n\n        return expr\n\n    def _parse_export_data(self) -> exp.Export:\n        self._match_text_seq(\"DATA\")\n\n        return self.expression(\n            exp.Export(\n                connection=self._match_text_seq(\"WITH\", \"CONNECTION\") and self._parse_table_parts(),\n                options=self._parse_properties(),\n                this=self._match_text_seq(\"AS\") and self._parse_select(),\n            )\n        )\n\n    def _parse_column_ops(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        func_index = self._index + 1\n        this = super()._parse_column_ops(this)\n\n        if isinstance(this, exp.Dot) and isinstance(this.expression, exp.Func):\n            prefix = this.this.name.upper()\n\n            func: t.Optional[t.Type[exp.Func]] = None\n            if prefix == \"NET\":\n                func = exp.NetFunc\n            elif prefix == \"SAFE\":\n                func = exp.SafeFunc\n\n            if func:\n                # Retreat to try and parse a known function instead of an anonymous one,\n                # which is parsed by the base column ops parser due to anonymous_func=true\n                self._retreat(func_index)\n                this = func(this=self._parse_function(any_token=True))\n\n        return this\n"
  },
  {
    "path": "sqlglot/parsers/clickhouse.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom collections import deque\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import (\n    build_date_delta,\n    build_formatted_time,\n    build_json_extract_path,\n    build_like,\n)\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import Token, TokenType\nfrom builtins import type as Type\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from collections.abc import Mapping, Sequence, Collection\n\n\ndef _build_datetime_format(\n    expr_type: Type[E],\n) -> t.Callable[[list], E]:\n    def _builder(args: list) -> E:\n        expr = build_formatted_time(expr_type, \"clickhouse\")(args)\n\n        timezone = seq_get(args, 2)\n        if timezone:\n            expr.set(\"zone\", timezone)\n\n        return expr\n\n    return _builder\n\n\ndef _build_count_if(args: t.List) -> exp.CountIf | exp.CombinedAggFunc:\n    if len(args) == 1:\n        return exp.CountIf(this=seq_get(args, 0))\n\n    return exp.CombinedAggFunc(this=\"countIf\", expressions=args)\n\n\ndef _build_str_to_date(args: t.List) -> exp.Cast | exp.Anonymous:\n    if len(args) == 3:\n        return exp.Anonymous(this=\"STR_TO_DATE\", expressions=args)\n\n    strtodate = exp.StrToDate.from_arg_list(args)\n    return exp.cast(strtodate, exp.DataType.build(exp.DType.DATETIME))\n\n\ndef _build_timestamp_trunc(unit: str) -> t.Callable[[t.List], exp.TimestampTrunc]:\n    return lambda args: exp.TimestampTrunc(\n        this=seq_get(args, 0), unit=exp.var(unit), zone=seq_get(args, 1)\n    )\n\n\ndef _build_split_by_char(args: t.List) -> exp.Split | exp.Anonymous:\n    sep = seq_get(args, 0)\n    if isinstance(sep, exp.Literal):\n        sep_value = sep.to_py()\n        if isinstance(sep_value, str) and len(sep_value.encode(\"utf-8\")) == 1:\n            return _build_split(exp.Split)(args)\n\n    return exp.Anonymous(this=\"splitByChar\", expressions=args)\n\n\ndef _build_split(exp_class: Type[E]) -> t.Callable[[t.List], E]:\n    return lambda args: exp_class(\n        this=seq_get(args, 1), expression=seq_get(args, 0), limit=seq_get(args, 2)\n    )\n\n\n# Skip the 'week' unit since ClickHouse's toStartOfWeek\n# uses an extra mode argument to specify the first day of the week\nTIMESTAMP_TRUNC_UNITS = {\n    \"MICROSECOND\",\n    \"MILLISECOND\",\n    \"SECOND\",\n    \"MINUTE\",\n    \"HOUR\",\n    \"DAY\",\n    \"MONTH\",\n    \"QUARTER\",\n    \"YEAR\",\n}\n\n\nAGG_FUNCTIONS = {\n    \"count\",\n    \"min\",\n    \"max\",\n    \"sum\",\n    \"avg\",\n    \"any\",\n    \"stddevPop\",\n    \"stddevSamp\",\n    \"varPop\",\n    \"varSamp\",\n    \"corr\",\n    \"covarPop\",\n    \"covarSamp\",\n    \"entropy\",\n    \"exponentialMovingAverage\",\n    \"intervalLengthSum\",\n    \"kolmogorovSmirnovTest\",\n    \"mannWhitneyUTest\",\n    \"median\",\n    \"rankCorr\",\n    \"sumKahan\",\n    \"studentTTest\",\n    \"welchTTest\",\n    \"anyHeavy\",\n    \"anyLast\",\n    \"boundingRatio\",\n    \"first_value\",\n    \"last_value\",\n    \"argMin\",\n    \"argMax\",\n    \"avgWeighted\",\n    \"topK\",\n    \"approx_top_sum\",\n    \"topKWeighted\",\n    \"deltaSum\",\n    \"deltaSumTimestamp\",\n    \"groupArray\",\n    \"groupArrayLast\",\n    \"groupUniqArray\",\n    \"groupArrayInsertAt\",\n    \"groupArrayMovingAvg\",\n    \"groupArrayMovingSum\",\n    \"groupArraySample\",\n    \"groupBitAnd\",\n    \"groupBitOr\",\n    \"groupBitXor\",\n    \"groupBitmap\",\n    \"groupBitmapAnd\",\n    \"groupBitmapOr\",\n    \"groupBitmapXor\",\n    \"sumWithOverflow\",\n    \"sumMap\",\n    \"minMap\",\n    \"maxMap\",\n    \"skewSamp\",\n    \"skewPop\",\n    \"kurtSamp\",\n    \"kurtPop\",\n    \"uniq\",\n    \"uniqExact\",\n    \"uniqCombined\",\n    \"uniqCombined64\",\n    \"uniqHLL12\",\n    \"uniqTheta\",\n    \"quantile\",\n    \"quantiles\",\n    \"quantileExact\",\n    \"quantilesExact\",\n    \"quantilesExactExclusive\",\n    \"quantileExactLow\",\n    \"quantilesExactLow\",\n    \"quantileExactHigh\",\n    \"quantilesExactHigh\",\n    \"quantileExactWeighted\",\n    \"quantilesExactWeighted\",\n    \"quantileTiming\",\n    \"quantilesTiming\",\n    \"quantileTimingWeighted\",\n    \"quantilesTimingWeighted\",\n    \"quantileDeterministic\",\n    \"quantilesDeterministic\",\n    \"quantileTDigest\",\n    \"quantilesTDigest\",\n    \"quantileTDigestWeighted\",\n    \"quantilesTDigestWeighted\",\n    \"quantileBFloat16\",\n    \"quantilesBFloat16\",\n    \"quantileBFloat16Weighted\",\n    \"quantilesBFloat16Weighted\",\n    \"simpleLinearRegression\",\n    \"stochasticLinearRegression\",\n    \"stochasticLogisticRegression\",\n    \"categoricalInformationValue\",\n    \"contingency\",\n    \"cramersV\",\n    \"cramersVBiasCorrected\",\n    \"theilsU\",\n    \"maxIntersections\",\n    \"maxIntersectionsPosition\",\n    \"meanZTest\",\n    \"quantileInterpolatedWeighted\",\n    \"quantilesInterpolatedWeighted\",\n    \"quantileGK\",\n    \"quantilesGK\",\n    \"sparkBar\",\n    \"sumCount\",\n    \"largestTriangleThreeBuckets\",\n    \"histogram\",\n    \"sequenceMatch\",\n    \"sequenceCount\",\n    \"windowFunnel\",\n    \"retention\",\n    \"uniqUpTo\",\n    \"sequenceNextNode\",\n    \"exponentialTimeDecayedAvg\",\n}\n\n# Sorted longest-first so that compound suffixes (e.g. \"SimpleState\") are matched\n# before their sub-suffixes (e.g. \"State\") when resolving multi-combinator functions.\nAGG_FUNCTIONS_SUFFIXES: t.List[str] = sorted(\n    [\n        \"If\",\n        \"Array\",\n        \"ArrayIf\",\n        \"Map\",\n        \"SimpleState\",\n        \"State\",\n        \"Merge\",\n        \"MergeState\",\n        \"ForEach\",\n        \"Distinct\",\n        \"OrDefault\",\n        \"OrNull\",\n        \"Resample\",\n        \"ArgMin\",\n        \"ArgMax\",\n    ],\n    key=len,\n    reverse=True,\n)\n\n# Memoized examples of all 0- and 1-suffix aggregate function names\nAGG_FUNC_MAPPING: Mapping[str, tuple[str, str | None]] = {\n    f\"{f}{sfx}\": (f, sfx) for sfx in AGG_FUNCTIONS_SUFFIXES for f in AGG_FUNCTIONS\n} | {f: (f, None) for f in AGG_FUNCTIONS}\n\n\nclass ClickHouseParser(parser.Parser):\n    # Tested in ClickHouse's playground, it seems that the following two queries do the same thing\n    # * select x from t1 union all select x from t2 limit 1;\n    # * select x from t1 union all (select x from t2 limit 1);\n    MODIFIERS_ATTACHED_TO_SET_OP = False\n    INTERVAL_SPANS = False\n    OPTIONAL_ALIAS_TOKEN_CTE = False\n    JOINS_HAVE_EQUAL_PRECEDENCE = True\n\n    FUNCTIONS = {\n        **{\n            k: v\n            for k, v in parser.Parser.FUNCTIONS.items()\n            if k not in (\"TRANSFORM\", \"APPROX_TOP_SUM\")\n        },\n        **{f\"TOSTARTOF{unit}\": _build_timestamp_trunc(unit=unit) for unit in TIMESTAMP_TRUNC_UNITS},\n        \"ANY\": exp.AnyValue.from_arg_list,\n        \"ARRAYCOMPACT\": exp.ArrayCompact.from_arg_list,\n        \"ARRAYCONCAT\": exp.ArrayConcat.from_arg_list,\n        \"ARRAYDISTINCT\": exp.ArrayDistinct.from_arg_list,\n        \"ARRAYEXCEPT\": exp.ArrayExcept.from_arg_list,\n        \"ARRAYSUM\": exp.ArraySum.from_arg_list,\n        \"ARRAYMAX\": exp.ArrayMax.from_arg_list,\n        \"ARRAYMIN\": exp.ArrayMin.from_arg_list,\n        \"ARRAYREVERSE\": exp.ArrayReverse.from_arg_list,\n        \"ARRAYSLICE\": exp.ArraySlice.from_arg_list,\n        \"CURRENTDATABASE\": exp.CurrentDatabase.from_arg_list,\n        \"CURRENTSCHEMAS\": exp.CurrentSchemas.from_arg_list,\n        \"COUNTIF\": _build_count_if,\n        \"CITYHASH64\": exp.CityHash64.from_arg_list,\n        \"COSINEDISTANCE\": exp.CosineDistance.from_arg_list,\n        \"VERSION\": exp.CurrentVersion.from_arg_list,\n        \"DATE_ADD\": build_date_delta(exp.DateAdd, default_unit=None),\n        \"DATEADD\": build_date_delta(exp.DateAdd, default_unit=None),\n        \"DATE_DIFF\": build_date_delta(exp.DateDiff, default_unit=None, supports_timezone=True),\n        \"DATEDIFF\": build_date_delta(exp.DateDiff, default_unit=None, supports_timezone=True),\n        \"DATE_FORMAT\": _build_datetime_format(exp.TimeToStr),\n        \"DATE_SUB\": build_date_delta(exp.DateSub, default_unit=None),\n        \"DATESUB\": build_date_delta(exp.DateSub, default_unit=None),\n        \"FORMATDATETIME\": _build_datetime_format(exp.TimeToStr),\n        \"HAS\": exp.ArrayContains.from_arg_list,\n        \"ILIKE\": build_like(exp.ILike),\n        \"JSONEXTRACTSTRING\": build_json_extract_path(\n            exp.JSONExtractScalar, zero_based_indexing=False\n        ),\n        \"LENGTH\": lambda args: exp.Length(this=seq_get(args, 0), binary=True),\n        \"LIKE\": build_like(exp.Like),\n        \"L2Distance\": exp.EuclideanDistance.from_arg_list,\n        \"MAP\": parser.build_var_map,\n        \"MATCH\": exp.RegexpLike.from_arg_list,\n        \"NOTLIKE\": build_like(exp.Like, not_like=True),\n        \"PARSEDATETIME\": _build_datetime_format(exp.ParseDatetime),\n        \"RANDCANONICAL\": exp.Rand.from_arg_list,\n        \"STR_TO_DATE\": _build_str_to_date,\n        \"TIMESTAMP_SUB\": build_date_delta(exp.TimestampSub, default_unit=None),\n        \"TIMESTAMPSUB\": build_date_delta(exp.TimestampSub, default_unit=None),\n        \"TIMESTAMP_ADD\": build_date_delta(exp.TimestampAdd, default_unit=None),\n        \"TIMESTAMPADD\": build_date_delta(exp.TimestampAdd, default_unit=None),\n        \"TOMONDAY\": _build_timestamp_trunc(\"WEEK\"),\n        \"UNIQ\": exp.ApproxDistinct.from_arg_list,\n        \"XOR\": lambda args: exp.Xor(expressions=args),\n        \"MD5\": exp.MD5Digest.from_arg_list,\n        \"SHA256\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),\n        \"SHA512\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),\n        \"SPLITBYCHAR\": _build_split_by_char,\n        \"SPLITBYREGEXP\": _build_split(exp.RegexpSplit),\n        \"SPLITBYSTRING\": _build_split(exp.Split),\n        \"SUBSTRINGINDEX\": exp.SubstringIndex.from_arg_list,\n        \"TOTYPENAME\": exp.Typeof.from_arg_list,\n        \"EDITDISTANCE\": exp.Levenshtein.from_arg_list,\n        \"JAROWINKLERSIMILARITY\": exp.JarowinklerSimilarity.from_arg_list,\n        \"LEVENSHTEINDISTANCE\": exp.Levenshtein.from_arg_list,\n        \"UTCTIMESTAMP\": exp.UtcTimestamp.from_arg_list,\n    }\n\n    AGG_FUNCTIONS = AGG_FUNCTIONS\n    AGG_FUNCTIONS_SUFFIXES = AGG_FUNCTIONS_SUFFIXES\n\n    FUNC_TOKENS = {\n        *parser.Parser.FUNC_TOKENS,\n        TokenType.AND,\n        TokenType.OR,\n        TokenType.SET,\n    }\n\n    RESERVED_TOKENS = parser.Parser.RESERVED_TOKENS - {TokenType.SELECT}\n\n    ID_VAR_TOKENS = {\n        *parser.Parser.ID_VAR_TOKENS,\n        TokenType.LIKE,\n    }\n\n    AGG_FUNC_MAPPING = AGG_FUNC_MAPPING\n\n    @classmethod\n    def _resolve_clickhouse_agg(cls, name: str) -> t.Optional[tuple[str, Sequence[str]]]:\n        # ClickHouse allows chaining multiple combinators on aggregate functions.\n        # See https://clickhouse.com/docs/sql-reference/aggregate-functions/combinators\n        # N.B. this resolution allows any suffix stack, including ones that ClickHouse rejects\n        # syntactically such as sumMergeMerge (due to repeated adjacent suffixes)\n\n        # Until we are able to identify a 1- or 0-suffix aggregate function by name,\n        # repeatedly strip and queue suffixes (checking longer suffixes first, see comment on\n        # AGG_FUNCTIONS_SUFFIXES_SORTED). This loop only runs for 2 or more suffixes,\n        # as AGG_FUNC_MAPPING memoizes all 0- and 1-suffix\n        accumulated_suffixes: t.Deque[str] = deque()\n        while (parts := AGG_FUNC_MAPPING.get(name)) is None:\n            for suffix in AGG_FUNCTIONS_SUFFIXES:\n                if name.endswith(suffix) and len(name) != len(suffix):\n                    accumulated_suffixes.appendleft(suffix)\n                    name = name[: -len(suffix)]\n                    break\n            else:\n                return None\n\n        # We now have a 0- or 1-suffix aggregate\n        agg_func_name, inner_suffix = parts\n        if inner_suffix:\n            # this is a 1-suffix aggregate (either naturally or via repeated suffix\n            # stripping). prepend the innermost suffix.\n            accumulated_suffixes.appendleft(inner_suffix)\n\n        return (agg_func_name, accumulated_suffixes)\n\n    FUNCTION_PARSERS = {\n        **{k: v for k, v in parser.Parser.FUNCTION_PARSERS.items() if k != \"MATCH\"},\n        \"ARRAYJOIN\": lambda self: self.expression(exp.Explode(this=self._parse_expression())),\n        \"QUANTILE\": lambda self: self._parse_quantile(),\n        \"MEDIAN\": lambda self: self._parse_quantile(),\n        \"COLUMNS\": lambda self: self._parse_columns(),\n        \"TUPLE\": lambda self: exp.Struct.from_arg_list(self._parse_function_args(alias=True)),\n        \"AND\": lambda self: exp.and_(*self._parse_function_args(alias=False)),\n        \"OR\": lambda self: exp.or_(*self._parse_function_args(alias=False)),\n    }\n\n    PROPERTY_PARSERS = {\n        **{k: v for k, v in parser.Parser.PROPERTY_PARSERS.items() if k != \"DYNAMIC\"},\n        \"ENGINE\": lambda self: self._parse_engine_property(),\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        k: v for k, v in parser.Parser.NO_PAREN_FUNCTION_PARSERS.items() if k != \"ANY\"\n    }\n\n    NO_PAREN_FUNCTIONS = {\n        k: v\n        for k, v in parser.Parser.NO_PAREN_FUNCTIONS.items()\n        if k != TokenType.CURRENT_TIMESTAMP\n    }\n\n    RANGE_PARSERS = {\n        **parser.Parser.RANGE_PARSERS,\n        TokenType.GLOBAL: lambda self, this: self._parse_global_in(this),\n    }\n\n    COLUMN_OPERATORS = {\n        **{k: v for k, v in parser.Parser.COLUMN_OPERATORS.items() if k != TokenType.PLACEHOLDER},\n        TokenType.DOTCARET: lambda self, this, field: self.expression(\n            exp.NestedJSONSelect(this=this, expression=field)\n        ),\n    }\n\n    JOIN_KINDS = {\n        *parser.Parser.JOIN_KINDS,\n        TokenType.ALL,\n        TokenType.ANY,\n        TokenType.ASOF,\n        TokenType.ARRAY,\n    }\n\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS - {\n        TokenType.ALL,\n        TokenType.ANY,\n        TokenType.ARRAY,\n        TokenType.ASOF,\n        TokenType.FINAL,\n        TokenType.FORMAT,\n        TokenType.SETTINGS,\n    }\n\n    ALIAS_TOKENS = parser.Parser.ALIAS_TOKENS - {\n        TokenType.FORMAT,\n    }\n\n    LOG_DEFAULTS_TO_LN = True\n\n    QUERY_MODIFIER_PARSERS = {\n        **parser.Parser.QUERY_MODIFIER_PARSERS,\n        TokenType.SETTINGS: lambda self: (\n            \"settings\",\n            self._advance() or self._parse_csv(self._parse_assignment),\n        ),\n        TokenType.FORMAT: lambda self: (\"format\", self._advance() or self._parse_id_var()),\n    }\n\n    CONSTRAINT_PARSERS = {\n        **parser.Parser.CONSTRAINT_PARSERS,\n        \"INDEX\": lambda self: self._parse_index_constraint(),\n        \"CODEC\": lambda self: self._parse_compress(),\n        \"ASSUME\": lambda self: self._parse_assume_constraint(),\n    }\n\n    ALTER_PARSERS = {\n        **parser.Parser.ALTER_PARSERS,\n        \"MODIFY\": lambda self: self._parse_alter_table_modify(),\n        \"REPLACE\": lambda self: self._parse_alter_table_replace(),\n    }\n\n    SCHEMA_UNNAMED_CONSTRAINTS = {\n        *parser.Parser.SCHEMA_UNNAMED_CONSTRAINTS,\n        \"INDEX\",\n    } - {\"CHECK\"}\n\n    PLACEHOLDER_PARSERS = {\n        **parser.Parser.PLACEHOLDER_PARSERS,\n        TokenType.L_BRACE: lambda self: self._parse_query_parameter(),\n    }\n\n    STATEMENT_PARSERS = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.DETACH: lambda self: self._parse_detach(),\n    }\n\n    def _parse_wrapped_select_or_assignment(self) -> t.Optional[exp.Expr]:\n        return self._parse_wrapped(\n            lambda: self._parse_select() or self._parse_assignment(), optional=True\n        )\n\n    def _parse_check_constraint(self) -> t.Optional[exp.CheckColumnConstraint]:\n        return self.expression(\n            exp.CheckColumnConstraint(this=self._parse_wrapped_select_or_assignment())\n        )\n\n    def _parse_assume_constraint(self) -> t.Optional[exp.AssumeColumnConstraint]:\n        return self.expression(\n            exp.AssumeColumnConstraint(this=self._parse_wrapped_select_or_assignment())\n        )\n\n    def _parse_engine_property(self) -> exp.EngineProperty:\n        self._match(TokenType.EQ)\n        return self.expression(\n            exp.EngineProperty(this=self._parse_field(any_token=True, anonymous_func=True))\n        )\n\n    # https://clickhouse.com/docs/en/sql-reference/statements/create/function\n    def _parse_user_defined_function_expression(self) -> t.Optional[exp.Expr]:\n        return self._parse_lambda()\n\n    def _parse_types(\n        self, check_func: bool = False, schema: bool = False, allow_identifiers: bool = True\n    ) -> t.Optional[exp.Expr]:\n        dtype = super()._parse_types(\n            check_func=check_func, schema=schema, allow_identifiers=allow_identifiers\n        )\n        if isinstance(dtype, exp.DataType) and dtype.args.get(\"nullable\") is not True:\n            # Mark every type as non-nullable which is ClickHouse's default, unless it's\n            # already marked as nullable. This marker helps us transpile types from other\n            # dialects to ClickHouse, so that we can e.g. produce `CAST(x AS Nullable(String))`\n            # from `CAST(x AS TEXT)`. If there is a `NULL` value in `x`, the former would\n            # fail in ClickHouse without the `Nullable` type constructor.\n            dtype.set(\"nullable\", False)\n\n        return dtype\n\n    def _parse_extract(self) -> exp.Extract | exp.Anonymous:\n        index = self._index\n        this = self._parse_bitwise()\n        if self._match(TokenType.FROM):\n            self._retreat(index)\n            return super()._parse_extract()\n\n        # We return Anonymous here because extract and regexpExtract have different semantics,\n        # so parsing extract(foo, bar) into RegexpExtract can potentially break queries. E.g.,\n        # `extract('foobar', 'b')` works, but ClickHouse crashes for `regexpExtract('foobar', 'b')`.\n        #\n        # TODO: can we somehow convert the former into an equivalent `regexpExtract` call?\n        self._match(TokenType.COMMA)\n        return self.expression(\n            exp.Anonymous(this=\"extract\", expressions=[this, self._parse_bitwise()])\n        )\n\n    def _parse_assignment(self) -> t.Optional[exp.Expr]:\n        this = super()._parse_assignment()\n\n        if self._match(TokenType.PLACEHOLDER):\n            return self.expression(\n                exp.If(\n                    this=this,\n                    true=self._parse_assignment(),\n                    false=self._match(TokenType.COLON) and self._parse_assignment(),\n                )\n            )\n\n        return this\n\n    def _parse_query_parameter(self) -> t.Optional[exp.Expr]:\n        \"\"\"\n        Parse a placeholder expression like SELECT {abc: UInt32} or FROM {table: Identifier}\n        https://clickhouse.com/docs/en/sql-reference/syntax#defining-and-using-query-parameters\n        \"\"\"\n        index = self._index\n\n        this = self._parse_id_var()\n        self._match(TokenType.COLON)\n        kind = self._parse_types(check_func=False, allow_identifiers=False) or (\n            self._match_text_seq(\"IDENTIFIER\") and \"Identifier\"\n        )\n\n        if not kind:\n            self._retreat(index)\n            return None\n        elif not self._match(TokenType.R_BRACE):\n            self.raise_error(\"Expecting }\")\n\n        if isinstance(this, exp.Identifier) and not this.quoted:\n            this = exp.var(this.name)\n\n        return self.expression(exp.Placeholder(this=this, kind=kind))\n\n    def _parse_bracket(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        if this:\n            bracket_json_type = None\n\n            while self._match_pair(TokenType.L_BRACKET, TokenType.R_BRACKET):\n                bracket_json_type = exp.DataType(\n                    this=exp.DType.ARRAY,\n                    expressions=[\n                        bracket_json_type\n                        or exp.DataType.build(\n                            dtype=exp.DType.JSON, dialect=self.dialect, nullable=False\n                        )\n                    ],\n                    nested=True,\n                )\n\n            if bracket_json_type:\n                return self.expression(exp.JSONCast(this=this, to=bracket_json_type))\n\n        l_brace = self._match(TokenType.L_BRACE, advance=False)\n        bracket = super()._parse_bracket(this)\n\n        if l_brace and isinstance(bracket, exp.Struct):\n            varmap = exp.VarMap(keys=exp.Array(), values=exp.Array())\n            for expression in bracket.expressions:\n                if not isinstance(expression, exp.PropertyEQ):\n                    break\n\n                varmap.args[\"keys\"].append(\"expressions\", exp.Literal.string(expression.name))\n                varmap.args[\"values\"].append(\"expressions\", expression.expression)\n\n            return varmap\n\n        return bracket\n\n    def _parse_global_in(self, this: t.Optional[exp.Expr]) -> exp.Not | exp.In:\n        is_negated = self._match(TokenType.NOT)\n        in_expr: t.Optional[exp.In] = None\n        if self._match(TokenType.IN):\n            in_expr = self._parse_in(this)\n            in_expr.set(\"is_global\", True)\n        return self.expression(exp.Not(this=in_expr)) if is_negated else t.cast(exp.In, in_expr)\n\n    def _parse_table(\n        self,\n        schema: bool = False,\n        joins: bool = False,\n        alias_tokens: t.Optional[Collection[TokenType]] = None,\n        parse_bracket: bool = False,\n        is_db_reference: bool = False,\n        parse_partition: bool = False,\n        consume_pipe: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        this = super()._parse_table(\n            schema=schema,\n            joins=joins,\n            alias_tokens=alias_tokens,\n            parse_bracket=parse_bracket,\n            is_db_reference=is_db_reference,\n        )\n\n        if isinstance(this, exp.Table):\n            inner = this.this\n            alias = this.args.get(\"alias\")\n\n            if isinstance(inner, exp.GenerateSeries) and alias and not alias.columns:\n                alias.set(\"columns\", [exp.to_identifier(\"generate_series\")])\n\n        if self._match(TokenType.FINAL):\n            this = self.expression(exp.Final(this=this))\n\n        return this\n\n    def _parse_position(self, haystack_first: bool = False) -> exp.StrPosition:\n        return super()._parse_position(haystack_first=True)\n\n    # https://clickhouse.com/docs/en/sql-reference/statements/select/with/\n    def _parse_cte(self) -> t.Optional[exp.CTE]:\n        # WITH <identifier> AS <subquery expression>\n        cte: t.Optional[exp.CTE] = self._try_parse(super()._parse_cte)\n\n        if not cte:\n            # WITH <expression> AS <identifier>\n            cte = self.expression(\n                exp.CTE(this=self._parse_assignment(), alias=self._parse_table_alias(), scalar=True)\n            )\n\n        return cte\n\n    def _parse_join_parts(\n        self,\n    ) -> t.Tuple[t.Optional[Token], t.Optional[Token], t.Optional[Token]]:\n        is_global = self._prev if self._match(TokenType.GLOBAL) else None\n\n        kind_pre = self._prev if self._match_set(self.JOIN_KINDS) else None\n        side = self._prev if self._match_set(self.JOIN_SIDES) else None\n        kind = self._prev if self._match_set(self.JOIN_KINDS) else None\n\n        return is_global, side or kind, kind_pre or kind\n\n    def _parse_join(\n        self, skip_join_token: bool = False, parse_bracket: bool = False\n    ) -> t.Optional[exp.Join]:\n        join = super()._parse_join(skip_join_token=skip_join_token, parse_bracket=True)\n        if join:\n            method = join.args.get(\"method\")\n            join.set(\"method\", None)\n            join.set(\"global_\", method)\n\n            # tbl ARRAY JOIN arr <-- this should be a `Column` reference, not a `Table`\n            # https://clickhouse.com/docs/en/sql-reference/statements/select/array-join\n            if join.kind == \"ARRAY\":\n                for table in join.find_all(exp.Table):\n                    table.replace(table.to_column())\n\n        return join\n\n    def _parse_function(\n        self,\n        functions: t.Optional[t.Dict[str, t.Callable]] = None,\n        anonymous: bool = False,\n        optional_parens: bool = True,\n        any_token: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        expr = super()._parse_function(\n            functions=functions,\n            anonymous=anonymous,\n            optional_parens=optional_parens,\n            any_token=any_token,\n        )\n\n        func = expr.this if isinstance(expr, exp.Window) else expr\n\n        # Aggregate functions can be split in 2 parts: <func_name><suffix[es]>\n        parts = self._resolve_clickhouse_agg(func.this) if isinstance(func, exp.Anonymous) else None\n\n        if parts:\n            anon_func: exp.Anonymous = t.cast(exp.Anonymous, func)\n            params = self._parse_func_params(anon_func)\n\n            if len(parts[1]) > 0:\n                exp_class: Type[exp.Expr] = (\n                    exp.CombinedParameterizedAgg if params else exp.CombinedAggFunc\n                )\n            else:\n                exp_class = exp.ParameterizedAgg if params else exp.AnonymousAggFunc\n\n            instance = exp_class(this=anon_func.this, expressions=anon_func.expressions)\n            if params:\n                instance.set(\"params\", params)\n            func = self.expression(instance)\n\n            if isinstance(expr, exp.Window):\n                # The window's func was parsed as Anonymous in base parser, fix its\n                # type to be ClickHouse style CombinedAnonymousAggFunc / AnonymousAggFunc\n                expr.set(\"this\", func)\n            elif params:\n                # Params have blocked super()._parse_function() from parsing the following window\n                # (if that exists) as they're standing between the function call and the window spec\n                expr = self._parse_window(func)\n            else:\n                expr = func\n\n        return expr\n\n    def _parse_func_params(self, this: t.Optional[exp.Func] = None) -> t.Optional[t.List[exp.Expr]]:\n        if self._match_pair(TokenType.R_PAREN, TokenType.L_PAREN):\n            return self._parse_csv(self._parse_lambda)\n\n        if self._match(TokenType.L_PAREN):\n            params = self._parse_csv(self._parse_lambda)\n            self._match_r_paren(this)\n            return params\n\n        return None\n\n    def _parse_quantile(self) -> exp.Quantile:\n        this = self._parse_lambda()\n        params = self._parse_func_params()\n        if params:\n            return self.expression(exp.Quantile(this=params[0], quantile=this))\n        return self.expression(exp.Quantile(this=this, quantile=exp.Literal.number(0.5)))\n\n    def _parse_wrapped_id_vars(self, optional: bool = False) -> t.List[exp.Expr]:\n        return super()._parse_wrapped_id_vars(optional=True)\n\n    def _parse_column_def(\n        self, this: t.Optional[exp.Expr], computed_column: bool = True\n    ) -> t.Optional[exp.Expr]:\n        if self._match(TokenType.DOT):\n            return exp.Dot(this=this, expression=self._parse_id_var())\n\n        return super()._parse_column_def(this, computed_column=computed_column)\n\n    def _parse_primary_key(\n        self,\n        wrapped_optional: bool = False,\n        in_props: bool = False,\n        named_primary_key: bool = False,\n    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:\n        return super()._parse_primary_key(\n            wrapped_optional=wrapped_optional or in_props,\n            in_props=in_props,\n            named_primary_key=named_primary_key,\n        )\n\n    def _parse_on_property(self) -> t.Optional[exp.Expr]:\n        index = self._index\n        if self._match_text_seq(\"CLUSTER\"):\n            this = self._parse_string() or self._parse_id_var()\n            if this:\n                return self.expression(exp.OnCluster(this=this))\n            else:\n                self._retreat(index)\n        return None\n\n    def _parse_index_constraint(self, kind: t.Optional[str] = None) -> exp.IndexColumnConstraint:\n        # INDEX name1 expr TYPE type1(args) GRANULARITY value\n        this = self._parse_id_var()\n        expression = self._parse_assignment()\n\n        index_type = self._match_text_seq(\"TYPE\") and (self._parse_function() or self._parse_var())\n\n        granularity = self._match_text_seq(\"GRANULARITY\") and self._parse_term()\n\n        return self.expression(\n            exp.IndexColumnConstraint(\n                this=this, expression=expression, index_type=index_type, granularity=granularity\n            )\n        )\n\n    def _parse_partition(self) -> t.Optional[exp.Partition]:\n        # https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#how-to-set-partition-expression\n        if not self._match(TokenType.PARTITION):\n            return None\n\n        if self._match_text_seq(\"ID\"):\n            # Corresponds to the PARTITION ID <string_value> syntax\n            expressions: t.List[exp.Expr] = [\n                self.expression(exp.PartitionId(this=self._parse_string()))\n            ]\n        else:\n            expressions = self._parse_expressions()\n\n        return self.expression(exp.Partition(expressions=expressions))\n\n    def _parse_alter_table_replace(self) -> t.Optional[exp.Expr]:\n        partition = self._parse_partition()\n\n        if not partition or not self._match(TokenType.FROM):\n            return None\n\n        return self.expression(\n            exp.ReplacePartition(expression=partition, source=self._parse_table_parts())\n        )\n\n    def _parse_alter_table_modify(self) -> t.Optional[exp.Expr]:\n        if properties := self._parse_properties():\n            return self.expression(exp.AlterModifySqlSecurity(expressions=properties.expressions))\n        return None\n\n    def _parse_definer(self) -> t.Optional[exp.DefinerProperty]:\n        self._match(TokenType.EQ)\n        if self._match(TokenType.CURRENT_USER):\n            return exp.DefinerProperty(this=exp.Var(this=self._prev.text.upper()))\n        return exp.DefinerProperty(this=self._parse_string())\n\n    def _parse_projection_def(self) -> t.Optional[exp.ProjectionDef]:\n        if not self._match_text_seq(\"PROJECTION\"):\n            return None\n\n        return self.expression(\n            exp.ProjectionDef(\n                this=self._parse_id_var(), expression=self._parse_wrapped(self._parse_statement)\n            )\n        )\n\n    def _parse_constraint(self) -> t.Optional[exp.Expr]:\n        return super()._parse_constraint() or self._parse_projection_def()\n\n    def _parse_alias(\n        self, this: t.Optional[exp.Expr], explicit: bool = False\n    ) -> t.Optional[exp.Expr]:\n        # In clickhouse \"SELECT <expr> APPLY(...)\" is a query modifier,\n        # so \"APPLY\" shouldn't be parsed as <expr>'s alias. However, \"SELECT <expr> apply\" is a valid alias\n        if self._match_pair(TokenType.APPLY, TokenType.L_PAREN, advance=False):\n            return this\n\n        return super()._parse_alias(this=this, explicit=explicit)\n\n    def _parse_expression(self) -> t.Optional[exp.Expr]:\n        this = super()._parse_expression()\n\n        # Clickhouse allows \"SELECT <expr> [APPLY(func)] [...]]\" modifier\n        while self._match_pair(TokenType.APPLY, TokenType.L_PAREN):\n            this = exp.Apply(this=this, expression=self._parse_var(any_token=True))\n            self._match(TokenType.R_PAREN)\n\n        return this\n\n    def _parse_columns(self) -> exp.Expr:\n        this: exp.Expr = self.expression(exp.Columns(this=self._parse_lambda()))\n\n        while self._next and self._match_text_seq(\")\", \"APPLY\", \"(\"):\n            self._match(TokenType.R_PAREN)\n            this = exp.Apply(this=this, expression=self._parse_var(any_token=True))\n        return this\n\n    def _parse_value(self, values: bool = True) -> t.Optional[exp.Tuple]:\n        value = super()._parse_value(values=values)\n        if not value:\n            return None\n\n        # In Clickhouse \"SELECT * FROM VALUES (1, 2, 3)\" generates a table with a single column, in contrast\n        # to other dialects. For this case, we canonicalize the values into a tuple-of-tuples AST if it's not already one.\n        # In INSERT INTO statements the same clause actually references multiple columns (opposite semantics),\n        # but the final result is not altered by the extra parentheses.\n        # Note: Clickhouse allows VALUES([structure], value, ...) so the branch checks for the last expression\n        expressions = value.expressions\n        if values and not isinstance(expressions[-1], exp.Tuple):\n            value.set(\n                \"expressions\",\n                [self.expression(exp.Tuple(expressions=[expr])) for expr in expressions],\n            )\n\n        return value\n\n    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:\n        # ClickHouse allows custom expressions as partition key\n        # https://clickhouse.com/docs/engines/table-engines/mergetree-family/custom-partitioning-key\n        return self.expression(exp.PartitionedByProperty(this=self._parse_assignment()))\n\n    def _parse_detach(self) -> exp.Detach:\n        kind = self._match_set(self.DB_CREATABLES) and self._prev.text.upper()\n        exists = self._parse_exists()\n        this = self._parse_table_parts()\n\n        return self.expression(\n            exp.Detach(\n                this=this,\n                kind=kind,\n                exists=exists,\n                cluster=self._parse_on_property() if self._match(TokenType.ON) else None,\n                permanent=self._match_text_seq(\"PERMANENTLY\"),\n                sync=self._match_text_seq(\"SYNC\"),\n            )\n        )\n"
  },
  {
    "path": "sqlglot/parsers/databricks.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import build_date_delta, build_formatted_time\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.spark import SparkParser\nfrom sqlglot.tokens import TokenType\n\n\nclass DatabricksParser(SparkParser):\n    LOG_DEFAULTS_TO_LN = True\n    STRICT_CAST = True\n    COLON_IS_VARIANT_EXTRACT = True\n\n    FUNCTIONS = {\n        **SparkParser.FUNCTIONS,\n        \"GETDATE\": exp.CurrentTimestamp.from_arg_list,\n        \"DATEADD\": build_date_delta(exp.DateAdd),\n        \"DATE_ADD\": build_date_delta(exp.DateAdd),\n        \"DATEDIFF\": build_date_delta(exp.DateDiff),\n        \"DATE_DIFF\": build_date_delta(exp.DateDiff),\n        \"NOW\": exp.CurrentTimestamp.from_arg_list,\n        \"TO_DATE\": build_formatted_time(exp.TsOrDsToDate, \"databricks\"),\n        \"UNIFORM\": lambda args: exp.Uniform(\n            this=seq_get(args, 0), expression=seq_get(args, 1), seed=seq_get(args, 2)\n        ),\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **SparkParser.NO_PAREN_FUNCTION_PARSERS,\n        \"CURDATE\": lambda self: self._parse_curdate(),\n    }\n\n    FACTOR = {\n        **SparkParser.FACTOR,\n        TokenType.COLON: exp.JSONExtract,\n    }\n\n    COLUMN_OPERATORS = {\n        **parser.Parser.COLUMN_OPERATORS,\n        TokenType.QDCOLON: lambda self, this, to: self.build_cast(\n            False,\n            this=this,\n            to=to,\n        ),\n    }\n    CAST_COLUMN_OPERATORS = {\n        *SparkParser.CAST_COLUMN_OPERATORS,\n        TokenType.QDCOLON,\n    }\n\n    def _parse_curdate(self) -> exp.CurrentDate:\n        # CURDATE, an alias for CURRENT_DATE, has optional parentheses\n        if self._match(TokenType.L_PAREN):\n            self._match_r_paren()\n        return self.expression(exp.CurrentDate())\n"
  },
  {
    "path": "sqlglot/parsers/doris.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.mysql import MySQLParser\nfrom sqlglot.tokens import TokenType\n\n\n# Accept both DATE_TRUNC(datetime, unit) and DATE_TRUNC(unit, datetime)\ndef _build_date_trunc(args: t.List[exp.Expr]) -> exp.Expr:\n    a0, a1 = seq_get(args, 0), seq_get(args, 1)\n\n    def _is_unit_like(e: exp.Expr | None) -> bool:\n        if not (isinstance(e, exp.Literal) and e.is_string):\n            return False\n        text = e.this\n        return not any(ch.isdigit() for ch in text)\n\n    # Determine which argument is the unit\n    unit, this = (a0, a1) if _is_unit_like(a0) else (a1, a0)\n\n    return exp.TimestampTrunc(this=this, unit=unit)\n\n\nclass DorisParser(MySQLParser):\n    FUNCTIONS = {\n        **MySQLParser.FUNCTIONS,\n        \"COLLECT_SET\": exp.ArrayUniqueAgg.from_arg_list,\n        \"DATE_TRUNC\": _build_date_trunc,\n        \"L2_DISTANCE\": exp.EuclideanDistance.from_arg_list,\n        \"MONTHS_ADD\": exp.AddMonths.from_arg_list,\n        \"REGEXP\": exp.RegexpLike.from_arg_list,\n        \"TO_DATE\": exp.TsOrDsToDate.from_arg_list,\n    }\n\n    FUNCTION_PARSERS = {\n        k: v for k, v in MySQLParser.FUNCTION_PARSERS.items() if k != \"GROUP_CONCAT\"\n    }\n\n    NO_PAREN_FUNCTIONS = {\n        k: v for k, v in MySQLParser.NO_PAREN_FUNCTIONS.items() if k != TokenType.CURRENT_DATE\n    }\n\n    PROPERTY_PARSERS = {\n        **MySQLParser.PROPERTY_PARSERS,\n        \"PROPERTIES\": lambda self: self._parse_wrapped_properties(),\n        \"UNIQUE\": lambda self: self._parse_composite_key_property(exp.UniqueKeyProperty),\n        # Plain KEY without UNIQUE/DUPLICATE/AGGREGATE prefixes should be treated as UniqueKeyProperty with unique=False\n        \"KEY\": lambda self: self._parse_composite_key_property(exp.UniqueKeyProperty),\n        \"BUILD\": lambda self: self._parse_build_property(),\n        \"REFRESH\": lambda self: self._parse_refresh_property(),\n    }\n\n    def _parse_partition_property(\n        self,\n    ) -> t.Optional[exp.Expr] | t.List[exp.Expr]:\n        expr = super()._parse_partition_property()\n\n        if not expr:\n            return self._parse_partitioned_by()\n\n        if isinstance(expr, exp.Property):\n            return expr\n\n        self._match_l_paren()\n\n        if self._match_text_seq(\"FROM\", advance=False):\n            create_expressions = self._parse_csv(self._parse_partitioning_granularity_dynamic)\n        else:\n            create_expressions = None\n\n        self._match_r_paren()\n\n        return self.expression(\n            exp.PartitionByRangeProperty(\n                partition_expressions=expr, create_expressions=create_expressions\n            )\n        )\n\n    def _parse_partitioning_granularity_dynamic(self) -> exp.PartitionByRangePropertyDynamic:\n        self._match_text_seq(\"FROM\")\n        start = self._parse_wrapped(self._parse_string)\n        self._match_text_seq(\"TO\")\n        end = self._parse_wrapped(self._parse_string)\n        self._match_text_seq(\"INTERVAL\")\n        number = self._parse_number()\n        unit = self._parse_var(any_token=True)\n        every = self.expression(exp.Interval(this=number, unit=unit))\n        return self.expression(\n            exp.PartitionByRangePropertyDynamic(start=start, end=end, every=every)\n        )\n\n    def _parse_partition_range_value(self) -> t.Optional[exp.Expr]:\n        expr = super()._parse_partition_range_value()\n\n        if isinstance(expr, exp.Partition):\n            return expr\n\n        self._match_text_seq(\"VALUES\")\n        name = expr\n\n        # Doris-specific bracket syntax: VALUES [(...), (...))\n        self._match(TokenType.L_BRACKET)\n        values = self._parse_csv(lambda: self._parse_wrapped_csv(self._parse_expression))\n\n        self._match(TokenType.R_BRACKET)\n        self._match(TokenType.R_PAREN)\n\n        part_range = self.expression(exp.PartitionRange(this=name, expressions=values))\n        return self.expression(exp.Partition(expressions=[part_range]))\n\n    def _parse_build_property(self) -> exp.BuildProperty:\n        return self.expression(exp.BuildProperty(this=self._parse_var(upper=True)))\n\n    def _parse_refresh_property(self) -> exp.RefreshTriggerProperty:\n        method = self._parse_var(upper=True)\n\n        self._match(TokenType.ON)\n\n        kind = self._match_texts((\"MANUAL\", \"COMMIT\", \"SCHEDULE\")) and self._prev.text.upper()\n        every = self._match_text_seq(\"EVERY\") and self._parse_number()\n        unit = self._parse_var(any_token=True) if every else None\n        starts = self._match_text_seq(\"STARTS\") and self._parse_string()\n\n        return self.expression(\n            exp.RefreshTriggerProperty(\n                method=method, kind=kind, every=every, unit=unit, starts=starts\n            )\n        )\n"
  },
  {
    "path": "sqlglot/parsers/dremio.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import expressions as exp\nfrom sqlglot import parser\nfrom sqlglot.dialects.dialect import (\n    build_date_delta,\n    build_formatted_time,\n    build_timetostr_or_tochar,\n)\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n\nDATE_DELTA = t.Union[exp.DateAdd, exp.DateSub]\n\n\ndef to_char_is_numeric_handler(args: t.List, dialect: DialectType) -> exp.TimeToStr | exp.ToChar:\n    expression = build_timetostr_or_tochar(args, dialect)\n    fmt = seq_get(args, 1)\n\n    if fmt and isinstance(expression, exp.ToChar) and fmt.is_string and \"#\" in fmt.name:\n        # Only mark as numeric if format is a literal containing #\n        expression.set(\"is_numeric\", True)\n\n    return expression\n\n\ndef build_date_delta_with_cast_interval(\n    expression_class: t.Type[DATE_DELTA],\n) -> t.Callable[[t.List[exp.Expr]], exp.Expr]:\n    fallback_builder = build_date_delta(expression_class)\n\n    def _builder(args):\n        if len(args) == 2:\n            date_arg, interval_arg = args\n\n            if (\n                isinstance(interval_arg, exp.Cast)\n                and isinstance(interval_arg.to, exp.DataType)\n                and isinstance(interval_arg.to.this, exp.Interval)\n            ):\n                return expression_class(\n                    this=date_arg,\n                    expression=interval_arg.this,\n                    unit=interval_arg.to.this.unit,\n                )\n\n            return expression_class(this=date_arg, expression=interval_arg)\n\n        return fallback_builder(args)\n\n    return _builder\n\n\ndef datetype_handler(args: t.List[exp.Expr], dialect: DialectType) -> exp.Expr:\n    from sqlglot.dialects.dialect import Dialect\n\n    year, month, day = args\n\n    if all(isinstance(arg, exp.Literal) and arg.is_int for arg in (year, month, day)):\n        date_str = f\"{int(year.this):04d}-{int(month.this):02d}-{int(day.this):02d}\"\n        return exp.Date(this=exp.Literal.string(date_str))\n\n    dialect = Dialect.get_or_raise(dialect)\n\n    return exp.Cast(\n        this=exp.Concat(\n            expressions=[\n                year,\n                exp.Literal.string(\"-\"),\n                month,\n                exp.Literal.string(\"-\"),\n                day,\n            ],\n            coalesce=dialect.CONCAT_COALESCE,\n        ),\n        to=exp.DataType.build(\"DATE\"),\n    )\n\n\nclass DremioParser(parser.Parser):\n    LOG_DEFAULTS_TO_LN = True\n\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {\n        TokenType.ANTI,\n        TokenType.SEMI,\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **parser.Parser.NO_PAREN_FUNCTION_PARSERS,\n        \"CURRENT_DATE_UTC\": lambda self: self._parse_current_date_utc(),\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"ARRAY_GENERATE_RANGE\": exp.GenerateSeries.from_arg_list,\n        \"BIT_AND\": exp.BitwiseAndAgg.from_arg_list,\n        \"BIT_OR\": exp.BitwiseOrAgg.from_arg_list,\n        \"DATE_ADD\": build_date_delta_with_cast_interval(exp.DateAdd),\n        \"DATE_FORMAT\": build_formatted_time(exp.TimeToStr, \"dremio\"),\n        \"DATE_SUB\": build_date_delta_with_cast_interval(exp.DateSub),\n        \"REGEXP_MATCHES\": exp.RegexpLike.from_arg_list,\n        \"REPEATSTR\": exp.Repeat.from_arg_list,\n        \"TO_CHAR\": to_char_is_numeric_handler,\n        \"TO_DATE\": build_formatted_time(exp.TsOrDsToDate, \"dremio\"),\n        \"DATE_PART\": exp.Extract.from_arg_list,\n        \"DATETYPE\": datetype_handler,\n    }\n\n    def _parse_current_date_utc(self) -> exp.Cast:\n        if self._match(TokenType.L_PAREN):\n            self._match_r_paren()\n\n        return exp.Cast(\n            this=exp.AtTimeZone(\n                this=exp.CurrentTimestamp(),\n                zone=exp.Literal.string(\"UTC\"),\n            ),\n            to=exp.DataType.build(\"DATE\"),\n        )\n"
  },
  {
    "path": "sqlglot/parsers/drill.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import build_formatted_time\nfrom sqlglot.tokens import TokenType\n\n\nclass DrillParser(parser.Parser):\n    STRICT_CAST = False\n\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {\n        TokenType.ANTI,\n        TokenType.SEMI,\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"REPEATED_COUNT\": exp.ArraySize.from_arg_list,\n        \"TO_TIMESTAMP\": exp.TimeStrToTime.from_arg_list,\n        \"TO_CHAR\": build_formatted_time(exp.TimeToStr, \"drill\"),\n        \"LEVENSHTEIN_DISTANCE\": exp.Levenshtein.from_arg_list,\n    }\n\n    LOG_DEFAULTS_TO_LN = True\n"
  },
  {
    "path": "sqlglot/parsers/druid.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot.parser import Parser\n\n\nclass DruidParser(Parser):\n    pass\n"
  },
  {
    "path": "sqlglot/parsers/duckdb.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.trie import new_trie\nfrom sqlglot.dialects.dialect import (\n    binary_from_function,\n    build_default_decimal_type,\n    build_formatted_time,\n    build_regexp_extract,\n    date_trunc_to_time,\n    pivot_column_names,\n)\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parser import binary_range_parser\nfrom sqlglot.tokens import TokenType\nfrom collections.abc import Collection\n\n\ndef _build_sort_array_desc(args: t.List) -> exp.Expr:\n    return exp.SortArray(this=seq_get(args, 0), asc=exp.false())\n\n\ndef _build_array_prepend(args: t.List) -> exp.Expr:\n    return exp.ArrayPrepend(this=seq_get(args, 1), expression=seq_get(args, 0))\n\n\ndef _build_date_diff(args: t.List) -> exp.Expr:\n    return exp.DateDiff(this=seq_get(args, 2), expression=seq_get(args, 1), unit=seq_get(args, 0))\n\n\ndef _build_generate_series(end_exclusive: bool = False) -> t.Callable[[t.List], exp.GenerateSeries]:\n    def _builder(args: t.List) -> exp.GenerateSeries:\n        # Check https://duckdb.org/docs/sql/functions/nested.html#range-functions\n        if len(args) == 1:\n            # DuckDB uses 0 as a default for the series' start when it's omitted\n            args.insert(0, exp.Literal.number(\"0\"))\n\n        gen_series = exp.GenerateSeries.from_arg_list(args)\n        gen_series.set(\"is_end_exclusive\", end_exclusive)\n\n        return gen_series\n\n    return _builder\n\n\ndef _build_make_timestamp(args: t.List) -> exp.Expr:\n    if len(args) == 1:\n        return exp.UnixToTime(this=seq_get(args, 0), scale=exp.UnixToTime.MICROS)\n\n    return exp.TimestampFromParts(\n        year=seq_get(args, 0),\n        month=seq_get(args, 1),\n        day=seq_get(args, 2),\n        hour=seq_get(args, 3),\n        min=seq_get(args, 4),\n        sec=seq_get(args, 5),\n    )\n\n\ndef _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[DuckDBParser], exp.Show]:\n    def _parse(self: DuckDBParser) -> exp.Show:\n        return self._parse_show_duckdb(*args, **kwargs)\n\n    return _parse\n\n\nclass DuckDBParser(parser.Parser):\n    MAP_KEYS_ARE_ARBITRARY_EXPRESSIONS = True\n\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.LOCALTIME: exp.Localtime,\n        TokenType.LOCALTIMESTAMP: exp.Localtimestamp,\n        TokenType.CURRENT_CATALOG: exp.CurrentCatalog,\n        TokenType.SESSION_USER: exp.SessionUser,\n    }\n\n    BITWISE = {k: v for k, v in parser.Parser.BITWISE.items() if k != TokenType.CARET}\n\n    RANGE_PARSERS = {\n        **parser.Parser.RANGE_PARSERS,\n        TokenType.DAMP: binary_range_parser(exp.ArrayOverlaps),\n        TokenType.CARET_AT: binary_range_parser(exp.StartsWith),\n        TokenType.TILDE: binary_range_parser(exp.RegexpFullMatch),\n    }\n\n    EXPONENT = {\n        **parser.Parser.EXPONENT,\n        TokenType.CARET: exp.Pow,\n        TokenType.DSTAR: exp.Pow,\n    }\n\n    FUNCTIONS_WITH_ALIASED_ARGS = {*parser.Parser.FUNCTIONS_WITH_ALIASED_ARGS, \"STRUCT_PACK\"}\n\n    SHOW_PARSERS = {\n        \"TABLES\": _show_parser(\"TABLES\"),\n        \"ALL TABLES\": _show_parser(\"ALL TABLES\"),\n    }\n\n    FUNCTIONS = {\n        **{k: v for k, v in parser.Parser.FUNCTIONS.items() if k not in (\"DATE_SUB\", \"GLOB\")},\n        \"ANY_VALUE\": lambda args: exp.IgnoreNulls(this=exp.AnyValue.from_arg_list(args)),\n        \"ARRAY_PREPEND\": _build_array_prepend,\n        \"ARRAY_REVERSE_SORT\": _build_sort_array_desc,\n        \"ARRAY_INTERSECT\": lambda args: exp.ArrayIntersect(expressions=args),\n        \"ARRAY_SORT\": exp.SortArray.from_arg_list,\n        \"BIT_AND\": exp.BitwiseAndAgg.from_arg_list,\n        \"BIT_OR\": exp.BitwiseOrAgg.from_arg_list,\n        \"BIT_XOR\": exp.BitwiseXorAgg.from_arg_list,\n        \"CURRENT_LOCALTIMESTAMP\": exp.Localtimestamp.from_arg_list,\n        \"DATEDIFF\": _build_date_diff,\n        \"DATE_DIFF\": _build_date_diff,\n        \"DATE_TRUNC\": date_trunc_to_time,\n        \"DATETRUNC\": date_trunc_to_time,\n        \"DECODE\": lambda args: exp.Decode(\n            this=seq_get(args, 0), charset=exp.Literal.string(\"utf-8\")\n        ),\n        \"EDITDIST3\": exp.Levenshtein.from_arg_list,\n        \"ENCODE\": lambda args: exp.Encode(\n            this=seq_get(args, 0), charset=exp.Literal.string(\"utf-8\")\n        ),\n        \"EPOCH\": exp.TimeToUnix.from_arg_list,\n        \"EPOCH_MS\": lambda args: exp.UnixToTime(this=seq_get(args, 0), scale=exp.UnixToTime.MILLIS),\n        \"GENERATE_SERIES\": _build_generate_series(),\n        \"GET_CURRENT_TIME\": exp.CurrentTime.from_arg_list,\n        \"GET_BIT\": lambda args: exp.Getbit(\n            this=seq_get(args, 0), expression=seq_get(args, 1), zero_is_msb=True\n        ),\n        \"JARO_WINKLER_SIMILARITY\": exp.JarowinklerSimilarity.from_arg_list,\n        \"JSON\": exp.ParseJSON.from_arg_list,\n        \"JSON_EXTRACT_PATH\": parser.build_extract_json_with_path(exp.JSONExtract),\n        \"JSON_EXTRACT_STRING\": parser.build_extract_json_with_path(exp.JSONExtractScalar),\n        \"LIST_APPEND\": exp.ArrayAppend.from_arg_list,\n        \"LIST_CONCAT\": parser.build_array_concat,\n        \"LIST_CONTAINS\": exp.ArrayContains.from_arg_list,\n        \"LIST_COSINE_DISTANCE\": exp.CosineDistance.from_arg_list,\n        \"LIST_DISTANCE\": exp.EuclideanDistance.from_arg_list,\n        \"LIST_FILTER\": exp.ArrayFilter.from_arg_list,\n        \"LIST_HAS\": exp.ArrayContains.from_arg_list,\n        \"LIST_HAS_ANY\": exp.ArrayOverlaps.from_arg_list,\n        \"LIST_MAX\": exp.ArrayMax.from_arg_list,\n        \"LIST_MIN\": exp.ArrayMin.from_arg_list,\n        \"LIST_PREPEND\": _build_array_prepend,\n        \"LIST_REVERSE_SORT\": _build_sort_array_desc,\n        \"LIST_SORT\": exp.SortArray.from_arg_list,\n        \"LIST_TRANSFORM\": exp.Transform.from_arg_list,\n        \"LIST_VALUE\": lambda args: exp.Array(expressions=args),\n        \"MAKE_DATE\": exp.DateFromParts.from_arg_list,\n        \"MAKE_TIME\": exp.TimeFromParts.from_arg_list,\n        \"MAKE_TIMESTAMP\": _build_make_timestamp,\n        \"QUANTILE_CONT\": exp.PercentileCont.from_arg_list,\n        \"QUANTILE_DISC\": exp.PercentileDisc.from_arg_list,\n        \"RANGE\": _build_generate_series(end_exclusive=True),\n        \"REGEXP_EXTRACT\": build_regexp_extract(exp.RegexpExtract),\n        \"REGEXP_EXTRACT_ALL\": build_regexp_extract(exp.RegexpExtractAll),\n        \"REGEXP_MATCHES\": exp.RegexpLike.from_arg_list,\n        \"REGEXP_REPLACE\": lambda args: exp.RegexpReplace(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            replacement=seq_get(args, 2),\n            modifiers=seq_get(args, 3),\n            single_replace=True,\n        ),\n        \"SHA256\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),\n        \"STRFTIME\": build_formatted_time(exp.TimeToStr, \"duckdb\"),\n        \"STRING_SPLIT\": exp.Split.from_arg_list,\n        \"STRING_SPLIT_REGEX\": exp.RegexpSplit.from_arg_list,\n        \"STRING_TO_ARRAY\": exp.Split.from_arg_list,\n        \"STRPTIME\": build_formatted_time(exp.StrToTime, \"duckdb\"),\n        \"STRUCT_PACK\": exp.Struct.from_arg_list,\n        \"STR_SPLIT\": exp.Split.from_arg_list,\n        \"STR_SPLIT_REGEX\": exp.RegexpSplit.from_arg_list,\n        \"TODAY\": exp.CurrentDate.from_arg_list,\n        \"TIME_BUCKET\": exp.DateBin.from_arg_list,\n        \"TO_TIMESTAMP\": exp.UnixToTime.from_arg_list,\n        \"UNNEST\": exp.Explode.from_arg_list,\n        \"VERSION\": exp.CurrentVersion.from_arg_list,\n        \"XOR\": binary_from_function(exp.BitwiseXor),\n    }\n\n    FUNCTION_PARSERS = {\n        **{k: v for k, v in parser.Parser.FUNCTION_PARSERS.items() if k != \"DECODE\"},\n        **dict.fromkeys(\n            (\"GROUP_CONCAT\", \"LISTAGG\", \"STRINGAGG\"), lambda self: self._parse_string_agg()\n        ),\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **parser.Parser.NO_PAREN_FUNCTION_PARSERS,\n        \"MAP\": lambda self: self._parse_map(),\n        \"@\": lambda self: exp.Abs(this=self._parse_bitwise()),\n    }\n\n    PLACEHOLDER_PARSERS = {\n        **parser.Parser.PLACEHOLDER_PARSERS,\n        TokenType.PARAMETER: lambda self: (\n            self.expression(exp.Placeholder(this=self._prev.text))\n            if self._match(TokenType.NUMBER) or self._match_set(self.ID_VAR_TOKENS)\n            else None\n        ),\n    }\n\n    TYPE_CONVERTERS = {\n        # https://duckdb.org/docs/sql/data_types/numeric\n        exp.DType.DECIMAL: build_default_decimal_type(precision=18, scale=3),\n        # https://duckdb.org/docs/sql/data_types/text\n        exp.DType.TEXT: lambda dtype: exp.DataType.build(\"TEXT\"),\n    }\n\n    STATEMENT_PARSERS = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.ATTACH: lambda self: self._parse_attach_detach(),\n        TokenType.DETACH: lambda self: self._parse_attach_detach(is_attach=False),\n        TokenType.FORCE: lambda self: self._parse_force(),\n        TokenType.INSTALL: lambda self: self._parse_install(),\n        TokenType.SHOW: lambda self: self._parse_show(),\n    }\n\n    SET_PARSERS = {\n        **parser.Parser.SET_PARSERS,\n        \"VARIABLE\": lambda self: self._parse_set_item_assignment(\"VARIABLE\"),\n    }\n\n    SHOW_TRIE = new_trie(key.split(\" \") for key in SHOW_PARSERS)\n    SET_TRIE = new_trie(key.split(\" \") for key in SET_PARSERS)\n\n    def _parse_lambda(self, alias: bool = False) -> t.Optional[exp.Expr]:\n        index = self._index\n        if not self._match_text_seq(\"LAMBDA\"):\n            return super()._parse_lambda(alias=alias)\n\n        expressions = self._parse_csv(self._parse_lambda_arg)\n        if not self._match(TokenType.COLON):\n            self._retreat(index)\n            return None\n\n        this = self._replace_lambda(self._parse_assignment(), expressions)\n        return self.expression(exp.Lambda(this=this, expressions=expressions, colon=True))\n\n    def _parse_expression(self) -> t.Optional[exp.Expr]:\n        # DuckDB supports prefix aliases, e.g. foo: 1\n        if self._next.token_type == TokenType.COLON:\n            alias = self._parse_id_var(tokens=self.ALIAS_TOKENS)\n            self._match(TokenType.COLON)\n            comments = self._prev_comments\n\n            this = self._parse_assignment()\n            if isinstance(this, exp.Expr):\n                # Moves the comment next to the alias in `alias: expr /* comment */`\n                comments += this.pop_comments() or []\n\n            return self.expression(exp.Alias(this=this, alias=alias), comments=comments)\n\n        return super()._parse_expression()\n\n    def _parse_table(\n        self,\n        schema: bool = False,\n        joins: bool = False,\n        alias_tokens: t.Optional[Collection[TokenType]] = None,\n        parse_bracket: bool = False,\n        is_db_reference: bool = False,\n        parse_partition: bool = False,\n        consume_pipe: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        # DuckDB supports prefix aliases, e.g. FROM foo: bar\n        if self._next.token_type == TokenType.COLON:\n            alias = self._parse_table_alias(alias_tokens=alias_tokens or self.TABLE_ALIAS_TOKENS)\n            self._match(TokenType.COLON)\n            comments = self._prev_comments\n        else:\n            alias = None\n            comments = []\n\n        table = super()._parse_table(\n            schema=schema,\n            joins=joins,\n            alias_tokens=alias_tokens,\n            parse_bracket=parse_bracket,\n            is_db_reference=is_db_reference,\n            parse_partition=parse_partition,\n        )\n        if isinstance(table, exp.Expr) and isinstance(alias, exp.TableAlias):\n            # Moves the comment next to the alias in `alias: table /* comment */`\n            comments += table.pop_comments() or []\n            alias.comments = alias.pop_comments() + comments\n            table.set(\"alias\", alias)\n\n        return table\n\n    def _parse_table_sample(self, as_modifier: bool = False) -> t.Optional[exp.TableSample]:\n        # https://duckdb.org/docs/sql/samples.html\n        sample = super()._parse_table_sample(as_modifier=as_modifier)\n        if sample and not sample.args.get(\"method\"):\n            if sample.args.get(\"size\"):\n                sample.set(\"method\", exp.var(\"RESERVOIR\"))\n            else:\n                sample.set(\"method\", exp.var(\"SYSTEM\"))\n\n        return sample\n\n    def _parse_bracket(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        bracket = super()._parse_bracket(this)\n\n        if self.dialect.version < (1, 2) and isinstance(bracket, exp.Bracket):\n            # https://duckdb.org/2025/02/05/announcing-duckdb-120.html#breaking-changes\n            bracket.set(\"returns_list_for_maps\", True)\n\n        return bracket\n\n    def _parse_map(self) -> exp.ToMap | exp.Map:\n        if self._match(TokenType.L_BRACE, advance=False):\n            return self.expression(exp.ToMap(this=self._parse_bracket()))\n\n        args = self._parse_wrapped_csv(self._parse_assignment)\n        return self.expression(exp.Map(keys=seq_get(args, 0), values=seq_get(args, 1)))\n\n    def _parse_struct_types(self, type_required: bool = False) -> t.Optional[exp.Expr]:\n        return self._parse_field_def()\n\n    def _pivot_column_names(self, aggregations: t.List[exp.Expr]) -> t.List[str]:\n        if len(aggregations) == 1:\n            return super()._pivot_column_names(aggregations)\n        return pivot_column_names(aggregations, dialect=\"duckdb\")\n\n    def _parse_attach_detach(self, is_attach: bool = True) -> exp.Attach | exp.Detach:\n        def _parse_attach_option() -> exp.AttachOption:\n            return self.expression(\n                exp.AttachOption(\n                    this=self._parse_var(any_token=True),\n                    expression=self._parse_field(any_token=True),\n                )\n            )\n\n        self._match(TokenType.DATABASE)\n        exists = self._parse_exists(not_=is_attach)\n        this = self._parse_alias(self._parse_primary_or_var(), explicit=True)\n\n        if self._match(TokenType.L_PAREN, advance=False):\n            expressions = self._parse_wrapped_csv(_parse_attach_option)\n        else:\n            expressions = None\n\n        return (\n            self.expression(exp.Attach(this=this, exists=exists, expressions=expressions))\n            if is_attach\n            else self.expression(exp.Detach(this=this, exists=exists))\n        )\n\n    def _parse_show_duckdb(self, this: str) -> exp.Show:\n        return self.expression(exp.Show(this=this))\n\n    def _parse_force(self) -> exp.Install | exp.Command:\n        # FORCE can only be followed by INSTALL or CHECKPOINT\n        # In the case of CHECKPOINT, we fallback\n        if not self._match(TokenType.INSTALL):\n            return self._parse_as_command(self._prev)\n\n        return self._parse_install(force=True)\n\n    def _parse_install(self, force: bool = False) -> exp.Install:\n        return self.expression(\n            exp.Install(\n                this=self._parse_id_var(),\n                from_=self._parse_var_or_string() if self._match(TokenType.FROM) else None,\n                force=force,\n            )\n        )\n\n    def _parse_primary(self) -> t.Optional[exp.Expr]:\n        if self._match_pair(TokenType.HASH, TokenType.NUMBER):\n            return exp.PositionalColumn(this=exp.Literal.number(self._prev.text))\n\n        return super()._parse_primary()\n"
  },
  {
    "path": "sqlglot/parsers/dune.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot.parsers.trino import TrinoParser\n\n\nclass DuneParser(TrinoParser):\n    pass\n"
  },
  {
    "path": "sqlglot/parsers/exasol.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import (\n    binary_from_function,\n    build_date_delta,\n    build_formatted_time,\n    build_timetostr_or_tochar,\n    build_trunc,\n)\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\n\nDATE_UNITS = {\"DAY\", \"WEEK\", \"MONTH\", \"YEAR\", \"HOUR\", \"MINUTE\", \"SECOND\"}\n\n\n# https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/zeroifnull.htm\ndef _build_zeroifnull(args: t.List) -> exp.If:\n    cond = exp.Is(this=seq_get(args, 0), expression=exp.Null())\n    return exp.If(this=cond, true=exp.Literal.number(0), false=seq_get(args, 0))\n\n\n# https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/nullifzero.htm\ndef _build_nullifzero(args: t.List) -> exp.If:\n    cond = exp.EQ(this=seq_get(args, 0), expression=exp.Literal.number(0))\n    return exp.If(this=cond, true=exp.Null(), false=seq_get(args, 0))\n\n\nclass ExasolParser(parser.Parser):\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {\n        TokenType.ANTI,\n        TokenType.SEMI,\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        **{f\"ADD_{unit}S\": build_date_delta(exp.DateAdd, default_unit=unit) for unit in DATE_UNITS},\n        **{\n            f\"{unit}S_BETWEEN\": build_date_delta(exp.DateDiff, default_unit=unit)\n            for unit in DATE_UNITS\n        },\n        \"APPROXIMATE_COUNT_DISTINCT\": exp.ApproxDistinct.from_arg_list,\n        \"BIT_AND\": binary_from_function(exp.BitwiseAnd),\n        \"BIT_OR\": binary_from_function(exp.BitwiseOr),\n        \"BIT_XOR\": binary_from_function(exp.BitwiseXor),\n        \"BIT_NOT\": lambda args: exp.BitwiseNot(this=seq_get(args, 0)),\n        \"BIT_LSHIFT\": binary_from_function(exp.BitwiseLeftShift),\n        \"BIT_RSHIFT\": binary_from_function(exp.BitwiseRightShift),\n        # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/convert_tz.htm\n        \"CONVERT_TZ\": lambda args: exp.ConvertTimezone(\n            source_tz=seq_get(args, 1),\n            target_tz=seq_get(args, 2),\n            timestamp=seq_get(args, 0),\n            options=seq_get(args, 3),\n        ),\n        \"CURDATE\": exp.CurrentDate.from_arg_list,\n        # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/date_trunc.htm#DATE_TRUNC\n        \"DATE_TRUNC\": lambda args: exp.TimestampTrunc(this=seq_get(args, 1), unit=seq_get(args, 0)),\n        \"DIV\": binary_from_function(exp.IntDiv),\n        \"EVERY\": lambda args: exp.All(this=seq_get(args, 0)),\n        \"EDIT_DISTANCE\": exp.Levenshtein.from_arg_list,\n        \"FROM_POSIX_TIME\": exp.UnixToTime.from_arg_list,\n        \"HASH_SHA\": exp.SHA.from_arg_list,\n        \"HASH_SHA1\": exp.SHA.from_arg_list,\n        \"HASH_MD5\": exp.MD5.from_arg_list,\n        \"HASHTYPE_MD5\": exp.MD5Digest.from_arg_list,\n        \"HASH_SHA256\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),\n        \"HASH_SHA512\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),\n        \"NOW\": exp.CurrentTimestamp.from_arg_list,\n        \"NULLIFZERO\": _build_nullifzero,\n        \"REGEXP_LIKE\": lambda args: exp.RegexpLike(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            flag=seq_get(args, 2),\n            full_match=True,\n        ),\n        \"REGEXP_SUBSTR\": exp.RegexpExtract.from_arg_list,\n        \"REGEXP_REPLACE\": lambda args: exp.RegexpReplace(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            replacement=seq_get(args, 2),\n            position=seq_get(args, 3),\n            occurrence=seq_get(args, 4),\n        ),\n        \"TRUNC\": build_trunc,\n        \"TRUNCATE\": build_trunc,\n        \"TO_CHAR\": build_timetostr_or_tochar,\n        \"TO_DATE\": build_formatted_time(exp.TsOrDsToDate, \"exasol\"),\n        \"USER\": exp.CurrentUser.from_arg_list,\n        \"VAR_POP\": exp.VariancePop.from_arg_list,\n        \"ZEROIFNULL\": _build_zeroifnull,\n    }\n    CONSTRAINT_PARSERS = {\n        **parser.Parser.CONSTRAINT_PARSERS,\n        \"COMMENT\": lambda self: self.expression(\n            exp.CommentColumnConstraint(this=self._match(TokenType.IS) and self._parse_string())\n        ),\n    }\n\n    RANGE_PARSERS = {\n        **parser.Parser.RANGE_PARSERS,\n        TokenType.RLIKE: lambda self, this: self.expression(\n            exp.RegexpLike(this=this, expression=self._parse_bitwise(), full_match=True)\n        ),\n    }\n\n    FUNC_TOKENS = {\n        *parser.Parser.FUNC_TOKENS,\n        TokenType.SYSTIMESTAMP,\n    }\n\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.SYSTIMESTAMP: exp.Systimestamp,\n        TokenType.CURRENT_SCHEMA: exp.CurrentSchema,\n    }\n\n    FUNCTION_PARSERS = {\n        **parser.Parser.FUNCTION_PARSERS,\n        # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/listagg.htm\n        # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/group_concat.htm\n        **dict.fromkeys((\"GROUP_CONCAT\", \"LISTAGG\"), lambda self: self._parse_group_concat()),\n        # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/json_value.htm\n        \"JSON_VALUE\": lambda self: self._parse_json_value(),\n        # https://docs.exasol.com/db/latest/sql_references/functions/alphabeticallistfunctions/json_extract.htm\n        \"JSON_EXTRACT\": lambda self: self._parse_json_extract(),\n    }\n\n    def _parse_column(self) -> t.Optional[exp.Expr]:\n        column = super()._parse_column()\n        if not isinstance(column, exp.Column):\n            return column\n        table_ident = column.args.get(\"table\")\n        if (\n            isinstance(table_ident, exp.Identifier)\n            and table_ident.name.upper() == \"LOCAL\"\n            and not bool(table_ident.args.get(\"quoted\"))\n        ):\n            column.set(\"table\", None)\n        return column\n\n    def _parse_json_extract(self) -> exp.JSONExtract:\n        args = self._parse_expressions()\n        self._match_r_paren()\n\n        expression = exp.JSONExtract(expressions=args)\n\n        if self._match_texts(\"EMITS\"):\n            expression.set(\"emits\", self._parse_schema())\n\n        return expression\n\n    ODBC_DATETIME_LITERALS = {\n        \"d\": exp.Date,\n        \"ts\": exp.Timestamp,\n    }\n"
  },
  {
    "path": "sqlglot/parsers/fabric.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.parsers.tsql import TSQLParser\n\n\nclass FabricParser(TSQLParser):\n    def _parse_create(self) -> exp.Create | exp.Command:\n        create = super()._parse_create()\n\n        if isinstance(create, exp.Create):\n            # Transform VARCHAR/CHAR without precision to VARCHAR(1)/CHAR(1)\n            if create.kind == \"TABLE\" and isinstance(create.this, exp.Schema):\n                for column in create.this.expressions:\n                    if isinstance(column, exp.ColumnDef):\n                        column_type = column.kind\n                        if (\n                            isinstance(column_type, exp.DataType)\n                            and column_type.this in (exp.DType.VARCHAR, exp.DType.CHAR)\n                            and not column_type.expressions\n                        ):\n                            # Add default precision of 1 to VARCHAR/CHAR without precision\n                            # When n isn't specified in a data definition or variable declaration statement, the default length is 1.\n                            # https://learn.microsoft.com/en-us/sql/t-sql/data-types/char-and-varchar-transact-sql?view=sql-server-ver17#remarks\n                            column_type.set(\"expressions\", [exp.Literal.number(\"1\")])\n\n        return create\n"
  },
  {
    "path": "sqlglot/parsers/hive.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import build_formatted_time, build_regexp_extract\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import F\n\n\ndef build_with_ignore_nulls(\n    exp_class: t.Type[exp.Expr],\n) -> t.Callable[[t.List[exp.Expr]], exp.Expr]:\n    def _parse(args: t.List[exp.Expr]) -> exp.Expr:\n        this = exp_class(this=seq_get(args, 0))\n        if seq_get(args, 1) == exp.true():\n            return exp.IgnoreNulls(this=this)\n        return this\n\n    return _parse\n\n\ndef _build_to_date(args: t.List) -> exp.TsOrDsToDate:\n    expr = build_formatted_time(exp.TsOrDsToDate, \"hive\")(args)\n    expr.set(\"safe\", True)\n    return expr\n\n\ndef _build_date_add(args: t.List) -> exp.TsOrDsAdd:\n    expression = seq_get(args, 1)\n    if expression:\n        expression = expression * -1\n\n    return exp.TsOrDsAdd(\n        this=seq_get(args, 0), expression=expression, unit=exp.Literal.string(\"DAY\")\n    )\n\n\nclass HiveParser(parser.Parser):\n    LOG_DEFAULTS_TO_LN = True\n    STRICT_CAST = False\n    VALUES_FOLLOWED_BY_PAREN = False\n    JOINS_HAVE_EQUAL_PRECEDENCE = True\n    ADD_JOIN_ON_TRUE = True\n    ALTER_TABLE_PARTITIONS = True\n\n    CHANGE_COLUMN_ALTER_SYNTAX = False\n    # Whether the dialect supports using ALTER COLUMN syntax with CHANGE COLUMN.\n\n    FUNCTION_PARSERS = {\n        **parser.Parser.FUNCTION_PARSERS,\n        \"PERCENTILE\": lambda self: self._parse_quantile_function(exp.Quantile),\n        \"PERCENTILE_APPROX\": lambda self: self._parse_quantile_function(exp.ApproxQuantile),\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"BASE64\": exp.ToBase64.from_arg_list,\n        \"COLLECT_LIST\": lambda args: exp.ArrayAgg(this=seq_get(args, 0), nulls_excluded=True),\n        \"COLLECT_SET\": exp.ArrayUniqueAgg.from_arg_list,\n        \"DATE_ADD\": lambda args: exp.TsOrDsAdd(\n            this=seq_get(args, 0), expression=seq_get(args, 1), unit=exp.Literal.string(\"DAY\")\n        ),\n        \"DATE_FORMAT\": lambda args: build_formatted_time(exp.TimeToStr, \"hive\")(\n            [\n                exp.TimeStrToTime(this=seq_get(args, 0)),\n                seq_get(args, 1),\n            ]\n        ),\n        \"DATE_SUB\": _build_date_add,\n        \"DATEDIFF\": lambda args: exp.DateDiff(\n            this=exp.TsOrDsToDate(this=seq_get(args, 0)),\n            expression=exp.TsOrDsToDate(this=seq_get(args, 1)),\n        ),\n        \"DAY\": lambda args: exp.Day(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"FIRST\": build_with_ignore_nulls(exp.First),\n        \"FIRST_VALUE\": build_with_ignore_nulls(exp.FirstValue),\n        \"FROM_UNIXTIME\": build_formatted_time(exp.UnixToStr, \"hive\", True),\n        \"GET_JSON_OBJECT\": lambda args, dialect: exp.JSONExtractScalar(\n            this=seq_get(args, 0), expression=dialect.to_json_path(seq_get(args, 1))\n        ),\n        \"LAST\": build_with_ignore_nulls(exp.Last),\n        \"LAST_VALUE\": build_with_ignore_nulls(exp.LastValue),\n        \"MAP\": parser.build_var_map,\n        \"MONTH\": lambda args: exp.Month(this=exp.TsOrDsToDate.from_arg_list(args)),\n        \"REGEXP_EXTRACT\": build_regexp_extract(exp.RegexpExtract),\n        \"REGEXP_EXTRACT_ALL\": build_regexp_extract(exp.RegexpExtractAll),\n        \"SEQUENCE\": exp.GenerateSeries.from_arg_list,\n        \"SIZE\": exp.ArraySize.from_arg_list,\n        \"SPLIT\": exp.RegexpSplit.from_arg_list,\n        \"STR_TO_MAP\": lambda args: exp.StrToMap(\n            this=seq_get(args, 0),\n            pair_delim=seq_get(args, 1) or exp.Literal.string(\",\"),\n            key_value_delim=seq_get(args, 2) or exp.Literal.string(\":\"),\n        ),\n        \"TO_DATE\": _build_to_date,\n        \"TO_JSON\": exp.JSONFormat.from_arg_list,\n        \"TRUNC\": exp.TimestampTrunc.from_arg_list,\n        \"UNBASE64\": exp.FromBase64.from_arg_list,\n        \"UNIX_TIMESTAMP\": lambda args: build_formatted_time(exp.StrToUnix, \"hive\", True)(\n            args or [exp.CurrentTimestamp()]\n        ),\n        \"YEAR\": lambda args: exp.Year(this=exp.TsOrDsToDate.from_arg_list(args)),\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **parser.Parser.NO_PAREN_FUNCTION_PARSERS,\n        \"TRANSFORM\": lambda self: self._parse_transform(),\n    }\n\n    NO_PAREN_FUNCTIONS = {\n        k: v for k, v in parser.Parser.NO_PAREN_FUNCTIONS.items() if k != TokenType.CURRENT_TIME\n    }\n\n    PROPERTY_PARSERS = {\n        **parser.Parser.PROPERTY_PARSERS,\n        \"SERDEPROPERTIES\": lambda self: exp.SerdeProperties(\n            expressions=self._parse_wrapped_csv(self._parse_property)\n        ),\n    }\n\n    ALTER_PARSERS = {\n        **parser.Parser.ALTER_PARSERS,\n        \"CHANGE\": lambda self: self._parse_alter_table_change(),\n    }\n\n    def _parse_transform(self) -> t.Optional[exp.Transform | exp.QueryTransform]:\n        if not self._match(TokenType.L_PAREN, advance=False):\n            self._retreat(self._index - 1)\n            return None\n\n        args = self._parse_wrapped_csv(self._parse_lambda)\n        row_format_before = self._parse_row_format(match_row=True)\n\n        record_writer = None\n        if self._match_text_seq(\"RECORDWRITER\"):\n            record_writer = self._parse_string()\n\n        if not self._match(TokenType.USING):\n            return exp.Transform.from_arg_list(args)\n\n        command_script = self._parse_string()\n\n        self._match(TokenType.ALIAS)\n        schema = self._parse_schema()\n\n        row_format_after = self._parse_row_format(match_row=True)\n        record_reader = None\n        if self._match_text_seq(\"RECORDREADER\"):\n            record_reader = self._parse_string()\n\n        return self.expression(\n            exp.QueryTransform(\n                expressions=args,\n                command_script=command_script,\n                schema=schema,\n                row_format_before=row_format_before,\n                record_writer=record_writer,\n                row_format_after=row_format_after,\n                record_reader=record_reader,\n            )\n        )\n\n    def _parse_quantile_function(self, func: t.Type[F]) -> F:\n        if self._match(TokenType.DISTINCT):\n            first_arg: t.Optional[exp.Expr] = self.expression(\n                exp.Distinct(expressions=[self._parse_lambda()])\n            )\n        else:\n            self._match(TokenType.ALL)\n            first_arg = self._parse_lambda()\n\n        args = [first_arg]\n        if self._match(TokenType.COMMA):\n            args.extend(self._parse_function_args())\n\n        return func.from_arg_list(args)\n\n    def _parse_types(\n        self, check_func: bool = False, schema: bool = False, allow_identifiers: bool = True\n    ) -> t.Optional[exp.Expr]:\n        \"\"\"\n        Spark (and most likely Hive) treats casts to CHAR(length) and VARCHAR(length) as casts to\n        STRING in all contexts except for schema definitions. For example, this is in Spark v3.4.0:\n\n            spark-sql (default)> select cast(1234 as varchar(2));\n            23/06/06 15:51:18 WARN CharVarcharUtils: The Spark cast operator does not support\n            char/varchar type and simply treats them as string type. Please use string type\n            directly to avoid confusion. Otherwise, you can set spark.sql.legacy.charVarcharAsString\n            to true, so that Spark treat them as string type as same as Spark 3.0 and earlier\n\n            1234\n            Time taken: 4.265 seconds, Fetched 1 row(s)\n\n        This shows that Spark doesn't truncate the value into '12', which is inconsistent with\n        what other dialects (e.g. postgres) do, so we need to drop the length to transpile correctly.\n\n        Reference: https://spark.apache.org/docs/latest/sql-ref-datatypes.html\n        \"\"\"\n        this = super()._parse_types(\n            check_func=check_func, schema=schema, allow_identifiers=allow_identifiers\n        )\n\n        if this and not schema:\n            return this.transform(\n                lambda node: (\n                    node.replace(exp.DataType.build(\"text\"))\n                    if isinstance(node, exp.DataType) and node.is_type(\"char\", \"varchar\")\n                    else node\n                ),\n                copy=False,\n            )\n\n        return this\n\n    def _parse_alter_table_change(self) -> t.Optional[exp.Expr]:\n        self._match(TokenType.COLUMN)\n        this = self._parse_field(any_token=True)\n\n        if self.CHANGE_COLUMN_ALTER_SYNTAX and self._match_text_seq(\"TYPE\"):\n            return self.expression(exp.AlterColumn(this=this, dtype=self._parse_types(schema=True)))\n\n        column_new = self._parse_field(any_token=True)\n        dtype = self._parse_types(schema=True)\n\n        comment = self._match(TokenType.COMMENT) and self._parse_string()\n\n        if not this or not column_new or not dtype:\n            self.raise_error(\n                \"Expected 'CHANGE COLUMN' to be followed by 'column_name' 'column_name' 'data_type'\"\n            )\n\n        return self.expression(\n            exp.AlterColumn(this=this, rename_to=column_new, dtype=dtype, comment=comment)\n        )\n\n    def _parse_partition_and_order(\n        self,\n    ) -> t.Tuple[t.List[exp.Expr], t.Optional[exp.Expr]]:\n        return (\n            (\n                self._parse_csv(self._parse_assignment)\n                if self._match_set({TokenType.PARTITION_BY, TokenType.DISTRIBUTE_BY})\n                else []\n            ),\n            super()._parse_order(skip_order_token=self._match(TokenType.SORT_BY)),\n        )\n\n    def _parse_parameter(self) -> exp.Parameter:\n        self._match(TokenType.L_BRACE)\n        this = self._parse_identifier() or self._parse_primary_or_var()\n        expression = self._match(TokenType.COLON) and (\n            self._parse_identifier() or self._parse_primary_or_var()\n        )\n        self._match(TokenType.R_BRACE)\n        return self.expression(exp.Parameter(this=this, expression=expression))\n\n    def _to_prop_eq(self, expression: exp.Expr, index: int) -> exp.Expr:\n        if expression.is_star:\n            return expression\n\n        if isinstance(expression, exp.Column):\n            key = expression.this\n        else:\n            key = exp.to_identifier(f\"col{index + 1}\")\n\n        return self.expression(exp.PropertyEQ(this=key, expression=expression))\n"
  },
  {
    "path": "sqlglot/parsers/materialize.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.postgres import PostgresParser\nfrom sqlglot.tokens import TokenType\n\n\nclass MaterializeParser(PostgresParser):\n    TYPED_LAMBDA_ARGS = True\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **PostgresParser.NO_PAREN_FUNCTION_PARSERS,\n        \"MAP\": lambda self: self._parse_map(),\n    }\n\n    LAMBDAS = {\n        **PostgresParser.LAMBDAS,\n        TokenType.FARROW: lambda self, expressions: self.expression(\n            exp.Kwarg(this=seq_get(expressions, 0), expression=self._parse_assignment())\n        ),\n    }\n\n    def _parse_lambda_arg(self) -> t.Optional[exp.Expr]:\n        return self._parse_field()\n\n    def _parse_map(self) -> exp.ToMap:\n        if self._match(TokenType.L_PAREN):\n            to_map = self.expression(exp.ToMap(this=self._parse_select()))\n            self._match_r_paren()\n            return to_map\n\n        if not self._match(TokenType.L_BRACKET):\n            self.raise_error(\"Expecting [\")\n\n        entries = [\n            exp.PropertyEQ(this=e.this, expression=e.expression)\n            for e in self._parse_csv(self._parse_lambda)\n        ]\n\n        if not self._match(TokenType.R_BRACKET):\n            self.raise_error(\"Expecting ]\")\n\n        return self.expression(exp.ToMap(this=self.expression(exp.Struct(expressions=entries))))\n"
  },
  {
    "path": "sqlglot/parsers/mysql.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.trie import new_trie\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    build_date_delta,\n    build_date_delta_with_interval,\n    build_formatted_time,\n    isnull_to_is_null,\n)\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\n\n\n# All specifiers for time parts (as opposed to date parts)\n# https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_date-format\nTIME_SPECIFIERS = {\"f\", \"H\", \"h\", \"I\", \"i\", \"k\", \"l\", \"p\", \"r\", \"S\", \"s\", \"T\"}\n\n\ndef _has_time_specifier(date_format: str) -> bool:\n    i = 0\n    length = len(date_format)\n\n    while i < length:\n        if date_format[i] == \"%\":\n            i += 1\n            if i < length and date_format[i] in TIME_SPECIFIERS:\n                return True\n        i += 1\n    return False\n\n\ndef _str_to_date(args: t.List) -> exp.StrToDate | exp.StrToTime:\n    mysql_date_format = seq_get(args, 1)\n    date_format = Dialect[\"mysql\"].format_time(mysql_date_format)\n    this = seq_get(args, 0)\n\n    if mysql_date_format and _has_time_specifier(mysql_date_format.name):\n        return exp.StrToTime(this=this, format=date_format)\n\n    return exp.StrToDate(this=this, format=date_format)\n\n\ndef _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[MySQLParser], exp.Show]:\n    def _parse(self: MySQLParser) -> exp.Show:\n        return self._parse_show_mysql(*args, **kwargs)\n\n    return _parse\n\n\nclass MySQLParser(parser.Parser):\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.LOCALTIME: exp.Localtime,\n        TokenType.LOCALTIMESTAMP: exp.Localtimestamp,\n    }\n\n    ID_VAR_TOKENS = parser.Parser.ID_VAR_TOKENS - {TokenType.STRAIGHT_JOIN}\n\n    FUNC_TOKENS = {\n        *parser.Parser.FUNC_TOKENS,\n        TokenType.DATABASE,\n        TokenType.MOD,\n        TokenType.SCHEMA,\n        TokenType.VALUES,\n        TokenType.CHARACTER_SET,\n    }\n\n    CONJUNCTION = {\n        **parser.Parser.CONJUNCTION,\n        TokenType.DAMP: exp.And,\n        TokenType.XOR: exp.Xor,\n    }\n\n    DISJUNCTION = {\n        **parser.Parser.DISJUNCTION,\n        TokenType.DPIPE: exp.Or,\n    }\n\n    TABLE_ALIAS_TOKENS = (\n        (parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.ANTI, TokenType.SEMI})\n        - parser.Parser.TABLE_INDEX_HINT_TOKENS\n        - {TokenType.STRAIGHT_JOIN}\n    )\n\n    RANGE_PARSERS = {\n        **parser.Parser.RANGE_PARSERS,\n        TokenType.SOUNDS_LIKE: lambda self, this: self.expression(\n            exp.EQ(\n                this=self.expression(exp.Soundex(this=this)),\n                expression=self.expression(exp.Soundex(this=self._parse_term())),\n            )\n        ),\n        TokenType.MEMBER_OF: lambda self, this: self.expression(\n            exp.JSONArrayContains(this=this, expression=self._parse_wrapped(self._parse_expression))\n        ),\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"BIT_AND\": exp.BitwiseAndAgg.from_arg_list,\n        \"BIT_OR\": exp.BitwiseOrAgg.from_arg_list,\n        \"BIT_XOR\": exp.BitwiseXorAgg.from_arg_list,\n        \"BIT_COUNT\": exp.BitwiseCount.from_arg_list,\n        \"CONVERT_TZ\": lambda args: exp.ConvertTimezone(\n            source_tz=seq_get(args, 1), target_tz=seq_get(args, 2), timestamp=seq_get(args, 0)\n        ),\n        \"CURDATE\": exp.CurrentDate.from_arg_list,\n        \"CURTIME\": exp.CurrentTime.from_arg_list,\n        \"DATE\": lambda args: exp.TsOrDsToDate(this=seq_get(args, 0)),\n        \"DATE_ADD\": build_date_delta_with_interval(exp.DateAdd),\n        \"DATE_FORMAT\": lambda args: exp.TimeToStr(\n            this=exp.TsOrDsToTimestamp(this=seq_get(args, 0)),\n            format=Dialect[\"mysql\"].format_time(seq_get(args, 1)),\n        ),\n        \"DATE_SUB\": build_date_delta_with_interval(exp.DateSub),\n        \"DAY\": lambda args: exp.Day(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"DAYOFMONTH\": lambda args: exp.DayOfMonth(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"DAYOFWEEK\": lambda args: exp.DayOfWeek(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"DAYOFYEAR\": lambda args: exp.DayOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"FORMAT\": exp.NumberToStr.from_arg_list,\n        \"FROM_UNIXTIME\": build_formatted_time(exp.UnixToTime, \"mysql\"),\n        \"ISNULL\": isnull_to_is_null,\n        \"LENGTH\": lambda args: exp.Length(this=seq_get(args, 0), binary=True),\n        \"MAKETIME\": exp.TimeFromParts.from_arg_list,\n        \"MONTH\": lambda args: exp.Month(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"MONTHNAME\": lambda args: exp.TimeToStr(\n            this=exp.TsOrDsToDate(this=seq_get(args, 0)),\n            format=exp.Literal.string(\"%B\"),\n        ),\n        \"SCHEMA\": exp.CurrentSchema.from_arg_list,\n        \"DATABASE\": exp.CurrentSchema.from_arg_list,\n        \"STR_TO_DATE\": _str_to_date,\n        \"TIMESTAMPDIFF\": build_date_delta(exp.TimestampDiff),\n        \"TO_DAYS\": lambda args: exp.paren(\n            exp.DateDiff(\n                this=exp.TsOrDsToDate(this=seq_get(args, 0)),\n                expression=exp.TsOrDsToDate(this=exp.Literal.string(\"0000-01-01\")),\n                unit=exp.var(\"DAY\"),\n            )\n            + 1\n        ),\n        \"VERSION\": exp.CurrentVersion.from_arg_list,\n        \"WEEK\": lambda args: exp.Week(\n            this=exp.TsOrDsToDate(this=seq_get(args, 0)), mode=seq_get(args, 1)\n        ),\n        \"WEEKOFYEAR\": lambda args: exp.WeekOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"YEAR\": lambda args: exp.Year(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n    }\n\n    FUNCTION_PARSERS = {\n        **parser.Parser.FUNCTION_PARSERS,\n        \"GROUP_CONCAT\": lambda self: self._parse_group_concat(),\n        # https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_values\n        \"VALUES\": lambda self: self.expression(\n            exp.Anonymous(this=\"VALUES\", expressions=[self._parse_id_var()])\n        ),\n        \"JSON_VALUE\": lambda self: self._parse_json_value(),\n        \"SUBSTR\": lambda self: self._parse_substring(),\n    }\n\n    STATEMENT_PARSERS = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.SHOW: lambda self: self._parse_show(),\n    }\n\n    SHOW_PARSERS = {\n        \"BINARY LOGS\": _show_parser(\"BINARY LOGS\"),\n        \"MASTER LOGS\": _show_parser(\"BINARY LOGS\"),\n        \"BINLOG EVENTS\": _show_parser(\"BINLOG EVENTS\"),\n        \"CHARACTER SET\": _show_parser(\"CHARACTER SET\"),\n        \"CHARSET\": _show_parser(\"CHARACTER SET\"),\n        \"COLLATION\": _show_parser(\"COLLATION\"),\n        \"FULL COLUMNS\": _show_parser(\"COLUMNS\", target=\"FROM\", full=True),\n        \"COLUMNS\": _show_parser(\"COLUMNS\", target=\"FROM\"),\n        \"CREATE DATABASE\": _show_parser(\"CREATE DATABASE\", target=True),\n        \"CREATE EVENT\": _show_parser(\"CREATE EVENT\", target=True),\n        \"CREATE FUNCTION\": _show_parser(\"CREATE FUNCTION\", target=True),\n        \"CREATE PROCEDURE\": _show_parser(\"CREATE PROCEDURE\", target=True),\n        \"CREATE TABLE\": _show_parser(\"CREATE TABLE\", target=True),\n        \"CREATE TRIGGER\": _show_parser(\"CREATE TRIGGER\", target=True),\n        \"CREATE VIEW\": _show_parser(\"CREATE VIEW\", target=True),\n        \"DATABASES\": _show_parser(\"DATABASES\"),\n        \"SCHEMAS\": _show_parser(\"DATABASES\"),\n        \"ENGINE\": _show_parser(\"ENGINE\", target=True),\n        \"STORAGE ENGINES\": _show_parser(\"ENGINES\"),\n        \"ENGINES\": _show_parser(\"ENGINES\"),\n        \"ERRORS\": _show_parser(\"ERRORS\"),\n        \"EVENTS\": _show_parser(\"EVENTS\"),\n        \"FUNCTION CODE\": _show_parser(\"FUNCTION CODE\", target=True),\n        \"FUNCTION STATUS\": _show_parser(\"FUNCTION STATUS\"),\n        \"GRANTS\": _show_parser(\"GRANTS\", target=\"FOR\"),\n        \"INDEX\": _show_parser(\"INDEX\", target=\"FROM\"),\n        \"MASTER STATUS\": _show_parser(\"MASTER STATUS\"),\n        \"OPEN TABLES\": _show_parser(\"OPEN TABLES\"),\n        \"PLUGINS\": _show_parser(\"PLUGINS\"),\n        \"PROCEDURE CODE\": _show_parser(\"PROCEDURE CODE\", target=True),\n        \"PROCEDURE STATUS\": _show_parser(\"PROCEDURE STATUS\"),\n        \"PRIVILEGES\": _show_parser(\"PRIVILEGES\"),\n        \"FULL PROCESSLIST\": _show_parser(\"PROCESSLIST\", full=True),\n        \"PROCESSLIST\": _show_parser(\"PROCESSLIST\"),\n        \"PROFILE\": _show_parser(\"PROFILE\"),\n        \"PROFILES\": _show_parser(\"PROFILES\"),\n        \"RELAYLOG EVENTS\": _show_parser(\"RELAYLOG EVENTS\"),\n        \"REPLICAS\": _show_parser(\"REPLICAS\"),\n        \"SLAVE HOSTS\": _show_parser(\"REPLICAS\"),\n        \"REPLICA STATUS\": _show_parser(\"REPLICA STATUS\"),\n        \"SLAVE STATUS\": _show_parser(\"REPLICA STATUS\"),\n        \"GLOBAL STATUS\": _show_parser(\"STATUS\", global_=True),\n        \"SESSION STATUS\": _show_parser(\"STATUS\"),\n        \"STATUS\": _show_parser(\"STATUS\"),\n        \"TABLE STATUS\": _show_parser(\"TABLE STATUS\"),\n        \"FULL TABLES\": _show_parser(\"TABLES\", full=True),\n        \"TABLES\": _show_parser(\"TABLES\"),\n        \"TRIGGERS\": _show_parser(\"TRIGGERS\"),\n        \"GLOBAL VARIABLES\": _show_parser(\"VARIABLES\", global_=True),\n        \"SESSION VARIABLES\": _show_parser(\"VARIABLES\"),\n        \"VARIABLES\": _show_parser(\"VARIABLES\"),\n        \"WARNINGS\": _show_parser(\"WARNINGS\"),\n    }\n\n    PROPERTY_PARSERS = {\n        **parser.Parser.PROPERTY_PARSERS,\n        \"LOCK\": lambda self: self._parse_property_assignment(exp.LockProperty),\n        \"PARTITION BY\": lambda self: self._parse_partition_property(),\n    }\n\n    SET_PARSERS = {\n        **parser.Parser.SET_PARSERS,\n        \"PERSIST\": lambda self: self._parse_set_item_assignment(\"PERSIST\"),\n        \"PERSIST_ONLY\": lambda self: self._parse_set_item_assignment(\"PERSIST_ONLY\"),\n        \"CHARACTER SET\": lambda self: self._parse_set_item_charset(\"CHARACTER SET\"),\n        \"CHARSET\": lambda self: self._parse_set_item_charset(\"CHARACTER SET\"),\n        \"NAMES\": lambda self: self._parse_set_item_names(),\n    }\n\n    SHOW_TRIE = new_trie(key.split(\" \") for key in SHOW_PARSERS)\n    SET_TRIE = new_trie(key.split(\" \") for key in SET_PARSERS)\n\n    CONSTRAINT_PARSERS = {\n        **parser.Parser.CONSTRAINT_PARSERS,\n        \"FULLTEXT\": lambda self: self._parse_index_constraint(kind=\"FULLTEXT\"),\n        \"INDEX\": lambda self: self._parse_index_constraint(),\n        \"KEY\": lambda self: self._parse_index_constraint(),\n        \"SPATIAL\": lambda self: self._parse_index_constraint(kind=\"SPATIAL\"),\n        \"ZEROFILL\": lambda self: self.expression(exp.ZeroFillColumnConstraint()),\n    }\n\n    ALTER_PARSERS = {\n        **parser.Parser.ALTER_PARSERS,\n        \"MODIFY\": lambda self: self._parse_alter_table_alter(),\n    }\n\n    ALTER_ALTER_PARSERS = {\n        **parser.Parser.ALTER_ALTER_PARSERS,\n        \"INDEX\": lambda self: self._parse_alter_table_alter_index(),\n    }\n\n    SCHEMA_UNNAMED_CONSTRAINTS = {\n        *parser.Parser.SCHEMA_UNNAMED_CONSTRAINTS,\n        \"FULLTEXT\",\n        \"INDEX\",\n        \"KEY\",\n        \"SPATIAL\",\n    }\n\n    PROFILE_TYPES: parser.OPTIONS_TYPE = {\n        **dict.fromkeys((\"ALL\", \"CPU\", \"IPC\", \"MEMORY\", \"SOURCE\", \"SWAPS\"), tuple()),\n        \"BLOCK\": (\"IO\",),\n        \"CONTEXT\": (\"SWITCHES\",),\n        \"PAGE\": (\"FAULTS\",),\n    }\n\n    TYPE_TOKENS = {\n        *parser.Parser.TYPE_TOKENS,\n        TokenType.SET,\n    }\n\n    ENUM_TYPE_TOKENS = {\n        *parser.Parser.ENUM_TYPE_TOKENS,\n        TokenType.SET,\n    }\n\n    # SELECT [ ALL | DISTINCT | DISTINCTROW ] [ <OPERATION_MODIFIERS> ]\n    OPERATION_MODIFIERS = {\n        \"HIGH_PRIORITY\",\n        \"STRAIGHT_JOIN\",\n        \"SQL_SMALL_RESULT\",\n        \"SQL_BIG_RESULT\",\n        \"SQL_BUFFER_RESULT\",\n        \"SQL_NO_CACHE\",\n        \"SQL_CALC_FOUND_ROWS\",\n    }\n\n    LOG_DEFAULTS_TO_LN = True\n    STRING_ALIASES = True\n    VALUES_FOLLOWED_BY_PAREN = False\n    SUPPORTS_PARTITION_SELECTION = True\n\n    def _parse_generated_as_identity(\n        self,\n    ) -> (\n        exp.GeneratedAsIdentityColumnConstraint\n        | exp.ComputedColumnConstraint\n        | exp.GeneratedAsRowColumnConstraint\n    ):\n        this = super()._parse_generated_as_identity()\n\n        if self._match_texts((\"STORED\", \"VIRTUAL\")):\n            persisted = self._prev.text.upper() == \"STORED\"\n\n            if isinstance(this, exp.ComputedColumnConstraint):\n                this.set(\"persisted\", persisted)\n            elif isinstance(this, exp.GeneratedAsIdentityColumnConstraint):\n                this = self.expression(\n                    exp.ComputedColumnConstraint(this=this.expression, persisted=persisted)\n                )\n\n        return this\n\n    def _parse_primary_key_part(self) -> t.Optional[exp.Expr]:\n        this = self._parse_id_var()\n        if not self._match(TokenType.L_PAREN):\n            return this\n\n        expression = self._parse_number()\n        self._match_r_paren()\n        return self.expression(exp.ColumnPrefix(this=this, expression=expression))\n\n    def _parse_index_constraint(self, kind: t.Optional[str] = None) -> exp.IndexColumnConstraint:\n        if kind:\n            self._match_texts((\"INDEX\", \"KEY\"))\n\n        this = self._parse_id_var(any_token=False)\n        index_type = self._match(TokenType.USING) and self._advance_any() and self._prev.text\n        expressions = self._parse_wrapped_csv(self._parse_ordered)\n\n        options = []\n        while True:\n            if self._match_text_seq(\"KEY_BLOCK_SIZE\"):\n                self._match(TokenType.EQ)\n                opt = exp.IndexConstraintOption(key_block_size=self._parse_number())\n            elif self._match(TokenType.USING):\n                opt = exp.IndexConstraintOption(using=self._advance_any() and self._prev.text)\n            elif self._match_text_seq(\"WITH\", \"PARSER\"):\n                opt = exp.IndexConstraintOption(parser=self._parse_var(any_token=True))\n            elif self._match(TokenType.COMMENT):\n                opt = exp.IndexConstraintOption(comment=self._parse_string())\n            elif self._match_text_seq(\"VISIBLE\"):\n                opt = exp.IndexConstraintOption(visible=True)\n            elif self._match_text_seq(\"INVISIBLE\"):\n                opt = exp.IndexConstraintOption(visible=False)\n            elif self._match_text_seq(\"ENGINE_ATTRIBUTE\"):\n                self._match(TokenType.EQ)\n                opt = exp.IndexConstraintOption(engine_attr=self._parse_string())\n            elif self._match_text_seq(\"SECONDARY_ENGINE_ATTRIBUTE\"):\n                self._match(TokenType.EQ)\n                opt = exp.IndexConstraintOption(secondary_engine_attr=self._parse_string())\n            else:\n                opt = None\n\n            if not opt:\n                break\n\n            options.append(opt)\n\n        return self.expression(\n            exp.IndexColumnConstraint(\n                this=this,\n                expressions=expressions,\n                kind=kind,\n                index_type=index_type,\n                options=options,\n            )\n        )\n\n    def _parse_show_mysql(\n        self,\n        this: str,\n        target: bool | str = False,\n        full: t.Optional[bool] = None,\n        global_: t.Optional[bool] = None,\n    ) -> exp.Show:\n        json = self._match_text_seq(\"JSON\")\n\n        if target:\n            if isinstance(target, str):\n                self._match_text_seq(*target.split(\" \"))\n            target_id = self._parse_id_var()\n        else:\n            target_id = None\n\n        log = self._parse_string() if self._match_text_seq(\"IN\") else None\n\n        if this in (\"BINLOG EVENTS\", \"RELAYLOG EVENTS\"):\n            position = self._parse_number() if self._match_text_seq(\"FROM\") else None\n            db = None\n        else:\n            position = None\n            db = None\n\n            if self._match(TokenType.FROM):\n                db = self._parse_id_var()\n            elif self._match(TokenType.DOT):\n                db = target_id\n                target_id = self._parse_id_var()\n\n        channel = self._parse_id_var() if self._match_text_seq(\"FOR\", \"CHANNEL\") else None\n\n        like = self._parse_string() if self._match_text_seq(\"LIKE\") else None\n        where = self._parse_where()\n\n        if this == \"PROFILE\":\n            types = self._parse_csv(lambda: self._parse_var_from_options(self.PROFILE_TYPES))\n            query = self._parse_number() if self._match_text_seq(\"FOR\", \"QUERY\") else None\n            offset = self._parse_number() if self._match_text_seq(\"OFFSET\") else None\n            limit = self._parse_number() if self._match_text_seq(\"LIMIT\") else None\n        else:\n            types, query = None, None\n            offset, limit = self._parse_oldstyle_limit()\n\n        mutex = True if self._match_text_seq(\"MUTEX\") else None\n        mutex = False if self._match_text_seq(\"STATUS\") else mutex\n\n        for_table = self._parse_id_var() if self._match_text_seq(\"FOR\", \"TABLE\") else None\n        for_group = self._parse_string() if self._match_text_seq(\"FOR\", \"GROUP\") else None\n        for_user = self._parse_string() if self._match_text_seq(\"FOR\", \"USER\") else None\n        for_role = self._parse_string() if self._match_text_seq(\"FOR\", \"ROLE\") else None\n        into_outfile = self._parse_string() if self._match_text_seq(\"INTO\", \"OUTFILE\") else None\n\n        return self.expression(\n            exp.Show(\n                this=this,\n                target=target_id,\n                full=full,\n                log=log,\n                position=position,\n                db=db,\n                channel=channel,\n                like=like,\n                where=where,\n                types=types,\n                query=query,\n                offset=offset,\n                limit=limit,\n                mutex=mutex,\n                for_table=for_table,\n                for_group=for_group,\n                for_user=for_user,\n                for_role=for_role,\n                into_outfile=into_outfile,\n                json=json,\n                global_=global_,\n            )\n        )\n\n    def _parse_oldstyle_limit(\n        self,\n    ) -> t.Tuple[t.Optional[exp.Expr], t.Optional[exp.Expr]]:\n        limit = None\n        offset = None\n        if self._match_text_seq(\"LIMIT\"):\n            parts = self._parse_csv(self._parse_number)\n            if len(parts) == 1:\n                limit = parts[0]\n            elif len(parts) == 2:\n                limit = parts[1]\n                offset = parts[0]\n\n        return offset, limit\n\n    def _parse_set_item_charset(self, kind: str) -> exp.Expr:\n        this = self._parse_string() or self._parse_unquoted_field()\n        return self.expression(exp.SetItem(this=this, kind=kind))\n\n    def _parse_set_item_names(self) -> exp.Expr:\n        charset = self._parse_string() or self._parse_unquoted_field()\n        if self._match_text_seq(\"COLLATE\"):\n            collate = self._parse_string() or self._parse_unquoted_field()\n        else:\n            collate = None\n\n        return self.expression(exp.SetItem(this=charset, collate=collate, kind=\"NAMES\"))\n\n    def _parse_type(\n        self, parse_interval: bool = True, fallback_to_identifier: bool = False\n    ) -> t.Optional[exp.Expr]:\n        # mysql binary is special and can work anywhere, even in order by operations\n        # it operates like a no paren func\n        if self._match(TokenType.BINARY, advance=False):\n            data_type = self._parse_types(check_func=True, allow_identifiers=False)\n\n            if isinstance(data_type, exp.DataType):\n                return self.expression(exp.Cast(this=self._parse_column(), to=data_type))\n\n        return super()._parse_type(\n            parse_interval=parse_interval, fallback_to_identifier=fallback_to_identifier\n        )\n\n    def _parse_alter_table_alter_index(self) -> exp.AlterIndex:\n        index = self._parse_field(any_token=True)\n\n        if self._match_text_seq(\"VISIBLE\"):\n            visible = True\n        elif self._match_text_seq(\"INVISIBLE\"):\n            visible = False\n        else:\n            visible = None\n\n        return self.expression(exp.AlterIndex(this=index, visible=visible))\n\n    def _parse_partition_property(\n        self,\n    ) -> t.Optional[exp.Expr] | t.List[exp.Expr]:\n        partition_cls: t.Optional[t.Type[exp.Expr]] = None\n        value_parser = None\n\n        if self._match_text_seq(\"RANGE\"):\n            partition_cls = exp.PartitionByRangeProperty\n            value_parser = self._parse_partition_range_value\n        elif self._match_text_seq(\"LIST\"):\n            partition_cls = exp.PartitionByListProperty\n            value_parser = self._parse_partition_list_value\n\n        if not partition_cls or not value_parser:\n            return None\n\n        partition_expressions = self._parse_wrapped_csv(self._parse_assignment)\n\n        # For Doris and Starrocks\n        if not self._match_text_seq(\"(\", \"PARTITION\", advance=False):\n            return partition_expressions\n\n        create_expressions = self._parse_wrapped_csv(value_parser)\n\n        return self.expression(\n            partition_cls(\n                partition_expressions=partition_expressions, create_expressions=create_expressions\n            )\n        )\n\n    def _parse_partition_range_value(self) -> t.Optional[exp.Expr]:\n        self._match_text_seq(\"PARTITION\")\n        name = self._parse_id_var()\n\n        if not self._match_text_seq(\"VALUES\", \"LESS\", \"THAN\"):\n            return name\n\n        values = self._parse_wrapped_csv(self._parse_expression)\n\n        if (\n            len(values) == 1\n            and isinstance(values[0], exp.Column)\n            and values[0].name.upper() == \"MAXVALUE\"\n        ):\n            values = [exp.var(\"MAXVALUE\")]\n\n        part_range = self.expression(exp.PartitionRange(this=name, expressions=values))\n        return self.expression(exp.Partition(expressions=[part_range]))\n\n    def _parse_partition_list_value(self) -> exp.Partition:\n        self._match_text_seq(\"PARTITION\")\n        name = self._parse_id_var()\n        self._match_text_seq(\"VALUES\", \"IN\")\n        values = self._parse_wrapped_csv(self._parse_expression)\n        part_list = self.expression(exp.PartitionList(this=name, expressions=values))\n        return self.expression(exp.Partition(expressions=[part_list]))\n\n    def _parse_primary_key(\n        self,\n        wrapped_optional: bool = False,\n        in_props: bool = False,\n        named_primary_key: bool = False,\n    ) -> exp.PrimaryKeyColumnConstraint | exp.PrimaryKey:\n        return super()._parse_primary_key(\n            wrapped_optional=wrapped_optional, in_props=in_props, named_primary_key=True\n        )\n"
  },
  {
    "path": "sqlglot/parsers/oracle.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import build_formatted_time, build_timetostr_or_tochar, build_trunc\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parser import OPTIONS_TYPE, build_coalesce\nfrom sqlglot.tokens import TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n\n\ndef _build_to_timestamp(args: t.List) -> exp.StrToTime | exp.Anonymous:\n    if len(args) == 1:\n        return exp.Anonymous(this=\"TO_TIMESTAMP\", expressions=args)\n\n    return build_formatted_time(exp.StrToTime, \"oracle\")(args)\n\n\nclass OracleParser(parser.Parser):\n    WINDOW_BEFORE_PAREN_TOKENS = {TokenType.OVER, TokenType.KEEP}\n    VALUES_FOLLOWED_BY_PAREN = False\n\n    FUNCTIONS = {\n        **{k: v for k, v in parser.Parser.FUNCTIONS.items() if k != \"TO_BOOLEAN\"},\n        \"CONVERT\": exp.ConvertToCharset.from_arg_list,\n        \"L2_DISTANCE\": exp.EuclideanDistance.from_arg_list,\n        \"NVL\": lambda args: build_coalesce(args, is_nvl=True),\n        \"SQUARE\": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)),\n        \"TO_CHAR\": build_timetostr_or_tochar,\n        \"TO_TIMESTAMP\": _build_to_timestamp,\n        \"TO_DATE\": build_formatted_time(exp.StrToDate, \"oracle\"),\n        \"TRUNC\": lambda args, dialect: build_trunc(\n            args, dialect, date_trunc_unabbreviate=False, default_date_trunc_unit=\"DD\"\n        ),\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **parser.Parser.NO_PAREN_FUNCTION_PARSERS,\n        \"NEXT\": lambda self: self._parse_next_value_for(),\n        \"PRIOR\": lambda self: self.expression(exp.Prior(this=self._parse_bitwise())),\n        \"SYSDATE\": lambda self: self.expression(exp.CurrentTimestamp(sysdate=True)),\n        \"DBMS_RANDOM\": lambda self: self._parse_dbms_random(),\n    }\n\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.LOCALTIMESTAMP: exp.Localtimestamp,\n        TokenType.SYSTIMESTAMP: exp.Systimestamp,\n    }\n\n    FUNCTION_PARSERS = {\n        **{k: v for k, v in parser.Parser.FUNCTION_PARSERS.items() if k != \"CONVERT\"},\n        \"JSON_ARRAY\": lambda self: self._parse_oracle_json_array(),\n        \"JSON_ARRAYAGG\": lambda self: self._parse_oracle_json_arrayagg(),\n        \"JSON_EXISTS\": lambda self: self._parse_json_exists(),\n    }\n\n    PROPERTY_PARSERS = {\n        **parser.Parser.PROPERTY_PARSERS,\n        \"GLOBAL\": lambda self: (\n            self._match_text_seq(\"TEMPORARY\")\n            and self.expression(exp.TemporaryProperty(this=\"GLOBAL\"))\n        ),\n        \"PRIVATE\": lambda self: (\n            self._match_text_seq(\"TEMPORARY\")\n            and self.expression(exp.TemporaryProperty(this=\"PRIVATE\"))\n        ),\n        \"FORCE\": lambda self: self.expression(exp.ForceProperty()),\n    }\n\n    QUERY_MODIFIER_PARSERS = {\n        **parser.Parser.QUERY_MODIFIER_PARSERS,\n        TokenType.ORDER_SIBLINGS_BY: lambda self: (\"order\", self._parse_order()),\n        TokenType.WITH: lambda self: (\"options\", [self._parse_query_restrictions()]),\n    }\n\n    TYPE_LITERAL_PARSERS = {\n        exp.DType.DATE: lambda self, this, _: self.expression(exp.DateStrToDate(this=this)),\n        # https://docs.oracle.com/en/database/oracle/oracle-database/19/refrn/NLS_TIMESTAMP_FORMAT.html\n        exp.DType.TIMESTAMP: lambda self, this, _: _build_to_timestamp(\n            [this, '\"%Y-%m-%d %H:%M:%S.%f\"']\n        ),\n    }\n\n    # SELECT UNIQUE .. is old-style Oracle syntax for SELECT DISTINCT ..\n    # Reference: https://stackoverflow.com/a/336455\n    DISTINCT_TOKENS = {TokenType.DISTINCT, TokenType.UNIQUE}\n\n    QUERY_RESTRICTIONS: t.ClassVar[OPTIONS_TYPE] = {\n        \"WITH\": (\n            (\"READ\", \"ONLY\"),\n            (\"CHECK\", \"OPTION\"),\n        ),\n    }\n\n    def _parse_dbms_random(self) -> t.Optional[exp.Expr]:\n        if self._match_text_seq(\".\", \"VALUE\"):\n            lower, upper = None, None\n            if self._match(TokenType.L_PAREN, advance=False):\n                lower_upper = self._parse_wrapped_csv(self._parse_bitwise)\n                if len(lower_upper) == 2:\n                    lower, upper = lower_upper\n\n            return exp.Rand(lower=lower, upper=upper)\n\n        self._retreat(self._index - 1)\n        return None\n\n    def _parse_oracle_json_array(self) -> exp.JSONArray:\n        return self._parse_json_array(\n            exp.JSONArray,\n            expressions=self._parse_csv(lambda: self._parse_format_json(self._parse_bitwise())),\n        )\n\n    def _parse_oracle_json_arrayagg(self) -> exp.JSONArrayAgg:\n        return self._parse_json_array(\n            exp.JSONArrayAgg,\n            this=self._parse_format_json(self._parse_bitwise()),\n            order=self._parse_order(),\n        )\n\n    def _parse_json_array(self, expr_type: t.Type[E], **kwargs) -> E:\n        return self.expression(\n            expr_type(\n                null_handling=self._parse_on_handling(\"NULL\", \"NULL\", \"ABSENT\"),\n                return_type=self._match_text_seq(\"RETURNING\") and self._parse_type(),\n                strict=self._match_text_seq(\"STRICT\"),\n                **kwargs,\n            )\n        )\n\n    def _parse_hint_function_call(self) -> t.Optional[exp.Expr]:\n        if not self._curr or not self._next or self._next.token_type != TokenType.L_PAREN:\n            return None\n\n        name = self._curr.text\n\n        self._advance(2)\n        args = self._parse_hint_args()\n        this = self.expression(exp.Anonymous(this=name, expressions=args))\n        self._match_r_paren(this)\n        return this\n\n    def _parse_hint_args(self):\n        args = []\n        result = self._parse_var()\n\n        while result:\n            args.append(result)\n            result = self._parse_var()\n\n        return args\n\n    def _parse_query_restrictions(self) -> t.Optional[exp.Expr]:\n        kind = self._parse_var_from_options(self.QUERY_RESTRICTIONS, raise_unmatched=False)\n\n        if not kind:\n            return None\n\n        return self.expression(\n            exp.QueryOption(\n                this=kind, expression=self._match(TokenType.CONSTRAINT) and self._parse_field()\n            )\n        )\n\n    def _parse_json_exists(self) -> exp.JSONExists:\n        this = self._parse_format_json(self._parse_bitwise())\n        self._match(TokenType.COMMA)\n        return self.expression(\n            exp.JSONExists(\n                this=this,\n                path=self.dialect.to_json_path(self._parse_bitwise()),\n                passing=self._match_text_seq(\"PASSING\")\n                and self._parse_csv(lambda: self._parse_alias(self._parse_bitwise())),\n                on_condition=self._parse_on_condition(),\n            )\n        )\n\n    def _parse_into(self) -> t.Optional[exp.Into]:\n        # https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/SELECT-INTO-statement.html\n        bulk_collect = self._match(TokenType.BULK_COLLECT_INTO)\n        if not bulk_collect and not self._match(TokenType.INTO):\n            return None\n\n        index = self._index\n\n        expressions = self._parse_expressions()\n        if len(expressions) == 1:\n            self._retreat(index)\n            self._match(TokenType.TABLE)\n            return self.expression(\n                exp.Into(this=self._parse_table(schema=True), bulk_collect=bulk_collect)\n            )\n\n        return self.expression(exp.Into(bulk_collect=bulk_collect, expressions=expressions))\n\n    def _parse_connect_with_prior(self):\n        return self._parse_assignment()\n\n    def _parse_column_ops(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        this = super()._parse_column_ops(this)\n\n        if not this:\n            return this\n\n        index = self._index\n\n        # https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/Interval-Exprs.html\n        interval_span = self._try_parse(lambda: self._parse_interval_span(this))\n        if interval_span and isinstance(interval_span.args.get(\"unit\"), exp.IntervalSpan):\n            return interval_span\n\n        self._retreat(index)\n        return this\n\n    def _parse_insert_table(self) -> t.Optional[exp.Expr]:\n        # Oracle does not use AS for INSERT INTO alias\n        # https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/INSERT.html\n        # Parse table parts without schema to avoid parsing the alias with its columns\n        this = self._parse_table_parts(schema=True)\n\n        if isinstance(this, exp.Table):\n            alias_name = self._parse_id_var(any_token=False)\n            if alias_name:\n                this.set(\"alias\", exp.TableAlias(this=alias_name))\n\n            this.set(\"partition\", self._parse_partition())\n\n            # Now parse the schema (column list) if present\n            return self._parse_schema(this=this)\n\n        return this\n"
  },
  {
    "path": "sqlglot/parsers/postgres.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import (\n    DialectType,\n    binary_from_function,\n    build_formatted_time,\n    build_json_extract_path,\n    build_timestamp_trunc,\n)\nfrom sqlglot.helper import is_int, seq_get\nfrom sqlglot.parser import binary_range_parser\nfrom sqlglot.tokens import TokenType\n\n\ndef _build_generate_series(args: t.List) -> exp.ExplodingGenerateSeries:\n    # The goal is to convert step values like '1 day' or INTERVAL '1 day' into INTERVAL '1' day\n    # Note: postgres allows calls with just two arguments -- the \"step\" argument defaults to 1\n    step = seq_get(args, 2)\n    if step is not None:\n        if step.is_string:\n            args[2] = exp.to_interval(step.this)\n        elif isinstance(step, exp.Interval) and not step.args.get(\"unit\"):\n            args[2] = exp.to_interval(step.this.this)\n\n    return exp.ExplodingGenerateSeries.from_arg_list(args)\n\n\ndef _build_to_timestamp(args: t.List) -> exp.UnixToTime | exp.StrToTime:\n    # TO_TIMESTAMP accepts either a single double argument or (text, text)\n    if len(args) == 1:\n        # https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TABLE\n        return exp.UnixToTime.from_arg_list(args)\n\n    # https://www.postgresql.org/docs/current/functions-formatting.html\n    return build_formatted_time(exp.StrToTime, \"postgres\")(args)\n\n\ndef _build_regexp_replace(args: t.List, dialect: DialectType = None) -> exp.RegexpReplace:\n    # The signature of REGEXP_REPLACE is:\n    # regexp_replace(source, pattern, replacement [, start [, N ]] [, flags ])\n    #\n    # Any one of `start`, `N` and `flags` can be column references, meaning that\n    # unless we can statically see that the last argument is a non-integer string\n    # (eg. not '0'), then it's not possible to construct the correct AST\n    regexp_replace = None\n    if len(args) > 3:\n        last = args[-1]\n        if not is_int(last.name):\n            if not last.type or last.is_type(exp.DType.UNKNOWN, exp.DType.NULL):\n                from sqlglot.optimizer.annotate_types import annotate_types\n\n                last = annotate_types(last, dialect=dialect)\n\n            if last.is_type(*exp.DataType.TEXT_TYPES):\n                regexp_replace = exp.RegexpReplace.from_arg_list(args[:-1])\n                regexp_replace.set(\"modifiers\", last)\n\n    regexp_replace = regexp_replace or exp.RegexpReplace.from_arg_list(args)\n    regexp_replace.set(\"single_replace\", True)\n    return regexp_replace\n\n\ndef _build_levenshtein_less_equal(args: t.List) -> exp.Levenshtein:\n    # Postgres has two signatures for levenshtein_less_equal function, but in both cases\n    # max_dist is the last argument\n    # levenshtein_less_equal(source, target, ins_cost, del_cost, sub_cost, max_d)\n    # levenshtein_less_equal(source, target, max_d)\n    max_dist = args.pop()\n\n    return exp.Levenshtein(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        ins_cost=seq_get(args, 2),\n        del_cost=seq_get(args, 3),\n        sub_cost=seq_get(args, 4),\n        max_dist=max_dist,\n    )\n\n\nclass PostgresParser(parser.Parser):\n    SUPPORTS_OMITTED_INTERVAL_SPAN_UNIT = True\n\n    PROPERTY_PARSERS = {\n        **{k: v for k, v in parser.Parser.PROPERTY_PARSERS.items() if k != \"INPUT\"},\n        \"SET\": lambda self: self.expression(exp.SetConfigProperty(this=self._parse_set())),\n    }\n\n    PLACEHOLDER_PARSERS = {\n        **parser.Parser.PLACEHOLDER_PARSERS,\n        TokenType.PLACEHOLDER: lambda self: self.expression(exp.Placeholder(jdbc=True)),\n        TokenType.MOD: lambda self: self._parse_query_parameter(),\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"ARRAY_PREPEND\": lambda args: exp.ArrayPrepend(\n            this=seq_get(args, 1), expression=seq_get(args, 0)\n        ),\n        \"BIT_AND\": exp.BitwiseAndAgg.from_arg_list,\n        \"BIT_OR\": exp.BitwiseOrAgg.from_arg_list,\n        \"BIT_XOR\": exp.BitwiseXorAgg.from_arg_list,\n        \"VERSION\": exp.CurrentVersion.from_arg_list,\n        \"DATE_TRUNC\": build_timestamp_trunc,\n        \"DIV\": lambda args: exp.cast(binary_from_function(exp.IntDiv)(args), exp.DType.DECIMAL),\n        \"GENERATE_SERIES\": _build_generate_series,\n        \"GET_BIT\": lambda args: exp.Getbit(\n            this=seq_get(args, 0), expression=seq_get(args, 1), zero_is_msb=True\n        ),\n        \"JSON_EXTRACT_PATH\": build_json_extract_path(exp.JSONExtract),\n        \"JSON_EXTRACT_PATH_TEXT\": build_json_extract_path(exp.JSONExtractScalar),\n        \"LENGTH\": lambda args: exp.Length(this=seq_get(args, 0), encoding=seq_get(args, 1)),\n        \"MAKE_TIME\": exp.TimeFromParts.from_arg_list,\n        \"MAKE_TIMESTAMP\": exp.TimestampFromParts.from_arg_list,\n        \"NOW\": exp.CurrentTimestamp.from_arg_list,\n        \"REGEXP_REPLACE\": _build_regexp_replace,\n        \"TO_CHAR\": build_formatted_time(exp.TimeToStr, \"postgres\"),\n        \"TO_DATE\": build_formatted_time(exp.StrToDate, \"postgres\"),\n        \"TO_TIMESTAMP\": _build_to_timestamp,\n        \"UNNEST\": exp.Explode.from_arg_list,\n        \"SHA256\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),\n        \"SHA384\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(384)),\n        \"SHA512\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),\n        \"LEVENSHTEIN_LESS_EQUAL\": _build_levenshtein_less_equal,\n        \"JSON_OBJECT_AGG\": lambda args: exp.JSONObjectAgg(expressions=args),\n        \"JSONB_OBJECT_AGG\": exp.JSONBObjectAgg.from_arg_list,\n        \"WIDTH_BUCKET\": lambda args: (\n            exp.WidthBucket(this=seq_get(args, 0), threshold=seq_get(args, 1))\n            if len(args) == 2\n            else exp.WidthBucket.from_arg_list(args)\n        ),\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **parser.Parser.NO_PAREN_FUNCTION_PARSERS,\n        \"VARIADIC\": lambda self: self.expression(exp.Variadic(this=self._parse_bitwise())),\n    }\n\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.LOCALTIME: exp.Localtime,\n        TokenType.LOCALTIMESTAMP: exp.Localtimestamp,\n        TokenType.CURRENT_CATALOG: exp.CurrentCatalog,\n        TokenType.SESSION_USER: exp.SessionUser,\n        TokenType.CURRENT_SCHEMA: exp.CurrentSchema,\n    }\n\n    FUNCTION_PARSERS = {\n        **parser.Parser.FUNCTION_PARSERS,\n        \"DATE_PART\": lambda self: self._parse_date_part(),\n        \"JSON_AGG\": lambda self: self.expression(\n            exp.JSONArrayAgg(this=self._parse_lambda(), order=self._parse_order())\n        ),\n        \"JSONB_EXISTS\": lambda self: self._parse_jsonb_exists(),\n    }\n\n    BITWISE = {\n        **parser.Parser.BITWISE,\n        TokenType.HASH: exp.BitwiseXor,\n    }\n\n    EXPONENT = {\n        TokenType.CARET: exp.Pow,\n    }\n\n    RANGE_PARSERS = {\n        **parser.Parser.RANGE_PARSERS,\n        TokenType.DAMP: binary_range_parser(exp.ArrayOverlaps),\n        TokenType.DAT: lambda self, this: self.expression(\n            exp.MatchAgainst(this=self._parse_bitwise(), expressions=[this])\n        ),\n    }\n\n    STATEMENT_PARSERS = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.END: lambda self: self._parse_commit_or_rollback(),\n    }\n\n    UNARY_PARSERS = {\n        **parser.Parser.UNARY_PARSERS,\n        # The `~` token is remapped from TILDE to RLIKE in Postgres due to the binary REGEXP LIKE operator\n        TokenType.RLIKE: lambda self: self.expression(exp.BitwiseNot(this=self._parse_unary())),\n    }\n\n    JSON_ARROWS_REQUIRE_JSON_TYPE = True\n\n    COLUMN_OPERATORS = {\n        **parser.Parser.COLUMN_OPERATORS,\n        TokenType.ARROW: lambda self, this, path: self.validate_expression(\n            build_json_extract_path(\n                exp.JSONExtract, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE\n            )([this, path])\n        ),\n        TokenType.DARROW: lambda self, this, path: self.validate_expression(\n            build_json_extract_path(\n                exp.JSONExtractScalar, arrow_req_json_type=self.JSON_ARROWS_REQUIRE_JSON_TYPE\n            )([this, path])\n        ),\n    }\n\n    ARG_MODE_TOKENS: t.ClassVar = {TokenType.IN, TokenType.OUT, TokenType.INOUT, TokenType.VARIADIC}\n\n    def _parse_parameter_mode(self) -> t.Optional[TokenType]:\n        \"\"\"\n        Parse PostgreSQL function parameter mode (IN, OUT, INOUT, VARIADIC).\n\n        Disambiguates between mode keywords and identifiers with the same name:\n        - MODE TYPE      -> keyword is identifier (e.g., \"out INT\")\n        - MODE NAME TYPE -> keyword is mode (e.g., \"OUT x INT\")\n\n        Returns:\n            Mode token type if current token is a mode keyword, None otherwise.\n        \"\"\"\n        if not self._match_set(self.ARG_MODE_TOKENS, advance=False) or not self._next:\n            return None\n\n        mode_token = self._curr\n\n        # Check Pattern 1: MODE TYPE\n        # Try parsing next token as a built-in type (not UDT)\n        # If successful, the keyword is an identifier, not a mode\n        is_followed_by_builtin_type = self._try_parse(\n            lambda: (\n                self._advance()  # type: ignore\n                or self._parse_types(check_func=False, allow_identifiers=False)\n            ),\n            retreat=True,\n        )\n        if is_followed_by_builtin_type:\n            return None  # Pattern: \"out INT\" -> out is parameter name\n\n        # Check Pattern 2: MODE NAME TYPE\n        # If next token is an identifier, check if there's a type after it\n        # The type can be built-in or user-defined (allow_identifiers=True)\n        if self._next.token_type not in self.ID_VAR_TOKENS:\n            return None\n\n        is_followed_by_any_type = self._try_parse(\n            lambda: (\n                self._advance(2)  # type: ignore\n                or self._parse_types(check_func=False, allow_identifiers=True)\n            ),\n            retreat=True,\n        )\n\n        if is_followed_by_any_type:\n            return mode_token.token_type  # Pattern: \"OUT x INT\" -> OUT is mode\n\n        return None\n\n    def _create_mode_constraint(self, param_mode: TokenType) -> exp.InOutColumnConstraint:\n        \"\"\"\n        Create parameter mode constraint for function parameters.\n\n        Args:\n            param_mode: The parameter mode token (IN, OUT, INOUT, or VARIADIC).\n\n        Returns:\n            InOutColumnConstraint expression representing the parameter mode.\n        \"\"\"\n        return self.expression(\n            exp.InOutColumnConstraint(\n                input_=(param_mode in {TokenType.IN, TokenType.INOUT}),\n                output=(param_mode in {TokenType.OUT, TokenType.INOUT}),\n                variadic=(param_mode == TokenType.VARIADIC),\n            )\n        )\n\n    def _parse_function_parameter(self) -> t.Optional[exp.Expr]:\n        param_mode = self._parse_parameter_mode()\n\n        if param_mode:\n            self._advance()\n\n        # Parse parameter name and type\n        param_name = self._parse_id_var()\n        column_def = self._parse_column_def(this=param_name, computed_column=False)\n\n        # Attach mode as constraint\n        if param_mode and column_def:\n            constraint = self._create_mode_constraint(param_mode)\n            if not column_def.args.get(\"constraints\"):\n                column_def.set(\"constraints\", [])\n            column_def.args[\"constraints\"].insert(0, constraint)\n\n        return column_def\n\n    def _parse_query_parameter(self) -> t.Optional[exp.Expr]:\n        this = (\n            self._parse_wrapped(self._parse_id_var)\n            if self._match(TokenType.L_PAREN, advance=False)\n            else None\n        )\n        self._match_text_seq(\"S\")\n        return self.expression(exp.Placeholder(this=this))\n\n    def _parse_date_part(self) -> exp.Expr:\n        part = self._parse_type()\n        self._match(TokenType.COMMA)\n        value = self._parse_bitwise()\n\n        if part and isinstance(part, (exp.Column, exp.Literal)):\n            part = exp.var(part.name)\n\n        return self.expression(exp.Extract(this=part, expression=value))\n\n    def _parse_unique_key(self) -> t.Optional[exp.Expr]:\n        return None\n\n    def _parse_jsonb_exists(self) -> exp.JSONBExists:\n        return self.expression(\n            exp.JSONBExists(\n                this=self._parse_bitwise(),\n                path=self._match(TokenType.COMMA)\n                and self.dialect.to_json_path(self._parse_bitwise()),\n            )\n        )\n\n    def _parse_generated_as_identity(\n        self,\n    ) -> (\n        exp.GeneratedAsIdentityColumnConstraint\n        | exp.ComputedColumnConstraint\n        | exp.GeneratedAsRowColumnConstraint\n    ):\n        this = super()._parse_generated_as_identity()\n\n        if self._match_text_seq(\"STORED\"):\n            this = self.expression(exp.ComputedColumnConstraint(this=this.expression))\n\n        return this\n\n    def _parse_user_defined_type(self, identifier: exp.Identifier) -> t.Optional[exp.Expr]:\n        udt_type: exp.Identifier | exp.Dot = identifier\n\n        while self._match(TokenType.DOT):\n            part = self._parse_id_var()\n            if part:\n                udt_type = exp.Dot(this=udt_type, expression=part)\n\n        return exp.DataType.build(udt_type, udt=True)\n"
  },
  {
    "path": "sqlglot/parsers/presto.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import (\n    binary_from_function,\n    build_formatted_time,\n    build_regexp_extract,\n    build_replace_with_optional_replacement,\n    date_trunc_to_time,\n)\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\n\n\ndef _build_approx_percentile(args: t.List) -> exp.Expr:\n    if len(args) == 4:\n        return exp.ApproxQuantile(\n            this=seq_get(args, 0),\n            weight=seq_get(args, 1),\n            quantile=seq_get(args, 2),\n            accuracy=seq_get(args, 3),\n        )\n    if len(args) == 3:\n        return exp.ApproxQuantile(\n            this=seq_get(args, 0), quantile=seq_get(args, 1), accuracy=seq_get(args, 2)\n        )\n    return exp.ApproxQuantile.from_arg_list(args)\n\n\ndef _build_from_unixtime(args: t.List) -> exp.Expr:\n    if len(args) == 3:\n        return exp.UnixToTime(\n            this=seq_get(args, 0),\n            hours=seq_get(args, 1),\n            minutes=seq_get(args, 2),\n        )\n    if len(args) == 2:\n        return exp.UnixToTime(this=seq_get(args, 0), zone=seq_get(args, 1))\n\n    return exp.UnixToTime.from_arg_list(args)\n\n\ndef _build_to_char(args: t.List) -> exp.TimeToStr:\n    fmt = seq_get(args, 1)\n    if isinstance(fmt, exp.Literal):\n        # We uppercase this to match Teradata's format mapping keys\n        fmt.set(\"this\", fmt.this.upper())\n\n    # We use \"teradata\" on purpose here, because the time formats are different in Presto.\n    # See https://prestodb.io/docs/current/functions/teradata.html?highlight=to_char#to_char\n    return build_formatted_time(exp.TimeToStr, \"teradata\")(args)\n\n\nclass PrestoParser(parser.Parser):\n    VALUES_FOLLOWED_BY_PAREN = False\n    ZONE_AWARE_TIMESTAMP_CONSTRUCTOR = True\n\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.LOCALTIME: exp.Localtime,\n        TokenType.LOCALTIMESTAMP: exp.Localtimestamp,\n    }\n\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {\n        TokenType.ANTI,\n        TokenType.SEMI,\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"ARBITRARY\": exp.AnyValue.from_arg_list,\n        \"APPROX_DISTINCT\": exp.ApproxDistinct.from_arg_list,\n        \"APPROX_PERCENTILE\": _build_approx_percentile,\n        \"BITWISE_AND\": binary_from_function(exp.BitwiseAnd),\n        \"BITWISE_NOT\": lambda args: exp.BitwiseNot(this=seq_get(args, 0)),\n        \"BITWISE_OR\": binary_from_function(exp.BitwiseOr),\n        \"BITWISE_XOR\": binary_from_function(exp.BitwiseXor),\n        \"CARDINALITY\": exp.ArraySize.from_arg_list,\n        \"CONTAINS\": exp.ArrayContains.from_arg_list,\n        \"DATE_ADD\": lambda args: exp.DateAdd(\n            this=seq_get(args, 2), expression=seq_get(args, 1), unit=seq_get(args, 0)\n        ),\n        \"DATE_DIFF\": lambda args: exp.DateDiff(\n            this=seq_get(args, 2), expression=seq_get(args, 1), unit=seq_get(args, 0)\n        ),\n        \"DATE_FORMAT\": build_formatted_time(exp.TimeToStr, \"presto\"),\n        \"DATE_PARSE\": build_formatted_time(exp.StrToTime, \"presto\"),\n        \"DATE_TRUNC\": date_trunc_to_time,\n        \"DAY_OF_WEEK\": exp.DayOfWeekIso.from_arg_list,\n        \"DOW\": exp.DayOfWeekIso.from_arg_list,\n        \"DOY\": exp.DayOfYear.from_arg_list,\n        \"ELEMENT_AT\": lambda args: exp.Bracket(\n            this=seq_get(args, 0), expressions=[seq_get(args, 1)], offset=1, safe=True\n        ),\n        \"FROM_HEX\": exp.Unhex.from_arg_list,\n        \"FROM_UNIXTIME\": _build_from_unixtime,\n        \"FROM_UTF8\": lambda args: exp.Decode(\n            this=seq_get(args, 0), replace=seq_get(args, 1), charset=exp.Literal.string(\"utf-8\")\n        ),\n        \"JSON_FORMAT\": lambda args: exp.JSONFormat(\n            this=seq_get(args, 0), options=seq_get(args, 1), is_json=True\n        ),\n        \"LEVENSHTEIN_DISTANCE\": exp.Levenshtein.from_arg_list,\n        \"NOW\": exp.CurrentTimestamp.from_arg_list,\n        \"REGEXP_EXTRACT\": build_regexp_extract(exp.RegexpExtract),\n        \"REGEXP_EXTRACT_ALL\": build_regexp_extract(exp.RegexpExtractAll),\n        \"REGEXP_REPLACE\": lambda args: exp.RegexpReplace(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            replacement=seq_get(args, 2) or exp.Literal.string(\"\"),\n        ),\n        \"REPLACE\": build_replace_with_optional_replacement,\n        \"ROW\": exp.Struct.from_arg_list,\n        \"SEQUENCE\": exp.GenerateSeries.from_arg_list,\n        \"SET_AGG\": exp.ArrayUniqueAgg.from_arg_list,\n        \"SPLIT_TO_MAP\": exp.StrToMap.from_arg_list,\n        \"STRPOS\": lambda args: exp.StrPosition(\n            this=seq_get(args, 0), substr=seq_get(args, 1), occurrence=seq_get(args, 2)\n        ),\n        \"SLICE\": exp.ArraySlice.from_arg_list,\n        \"TO_CHAR\": _build_to_char,\n        \"TO_UNIXTIME\": exp.TimeToUnix.from_arg_list,\n        \"TO_UTF8\": lambda args: exp.Encode(\n            this=seq_get(args, 0), charset=exp.Literal.string(\"utf-8\")\n        ),\n        \"MD5\": exp.MD5Digest.from_arg_list,\n        \"SHA256\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(256)),\n        \"SHA512\": lambda args: exp.SHA2(this=seq_get(args, 0), length=exp.Literal.number(512)),\n        \"WEEK\": exp.WeekOfYear.from_arg_list,\n    }\n\n    FUNCTION_PARSERS = {k: v for k, v in parser.Parser.FUNCTION_PARSERS.items() if k != \"TRIM\"}\n"
  },
  {
    "path": "sqlglot/parsers/prql.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\nfrom collections.abc import Collection\n\n\ndef _select_all(table: exp.Expr) -> t.Optional[exp.Select]:\n    return exp.select(\"*\").from_(table, copy=False) if table else None\n\n\ndef _resolve_projection(s: exp.Expr, projections: t.Dict[str, exp.Expr]) -> exp.Expr:\n    if isinstance(s, exp.Column) and s.name in projections:\n        return projections[s.name].copy()\n    return s\n\n\nclass PRQLParser(parser.Parser):\n    CONJUNCTION = {\n        **parser.Parser.CONJUNCTION,\n        TokenType.DAMP: exp.And,\n    }\n\n    DISJUNCTION = {\n        **parser.Parser.DISJUNCTION,\n        TokenType.DPIPE: exp.Or,\n    }\n\n    TRANSFORM_PARSERS = {\n        \"DERIVE\": lambda self, query: self._parse_selection(query),\n        \"SELECT\": lambda self, query: self._parse_selection(query, append=False),\n        \"TAKE\": lambda self, query: self._parse_take(query),\n        \"FILTER\": lambda self, query: query.where(self._parse_disjunction()),\n        \"APPEND\": lambda self, query: query.union(\n            _select_all(self._parse_table()), distinct=False, copy=False\n        ),\n        \"REMOVE\": lambda self, query: query.except_(\n            _select_all(self._parse_table()), distinct=False, copy=False\n        ),\n        \"INTERSECT\": lambda self, query: query.intersect(\n            _select_all(self._parse_table()), distinct=False, copy=False\n        ),\n        \"SORT\": lambda self, query: self._parse_order_by(query),\n        \"AGGREGATE\": lambda self, query: self._parse_selection(\n            query, parse_method=self._parse_aggregate, append=False\n        ),\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"AVERAGE\": exp.Avg.from_arg_list,\n        \"SUM\": lambda args: exp.func(\"COALESCE\", exp.Sum(this=seq_get(args, 0)), 0),\n    }\n\n    def _parse_equality(self) -> t.Optional[exp.Expr]:\n        eq = self._parse_comparison()\n        while self._match_set(self.EQUALITY):\n            comments = self._prev_comments\n            eq = self.expression(\n                self.EQUALITY[self._prev.token_type](this=eq, expression=self._parse_comparison()),\n                comments=comments,\n            )\n        if not isinstance(eq, (exp.EQ, exp.NEQ)):\n            return eq\n\n        # https://prql-lang.org/book/reference/spec/null.html\n        if isinstance(eq.expression, exp.Null):\n            is_exp = exp.Is(this=eq.this, expression=eq.expression)\n            return is_exp if isinstance(eq, exp.EQ) else exp.Not(this=is_exp)\n        if isinstance(eq.this, exp.Null):\n            is_exp = exp.Is(this=eq.expression, expression=eq.this)\n            return is_exp if isinstance(eq, exp.EQ) else exp.Not(this=is_exp)\n        return eq\n\n    def _parse_statement(self) -> t.Optional[exp.Expr]:\n        expression = self._parse_expression()\n        expression = expression if expression else self._parse_query()\n        return expression\n\n    def _parse_query(self) -> t.Optional[exp.Query]:\n        from_ = self._parse_from()\n\n        if not from_:\n            return None\n\n        query: exp.Query = exp.select(\"*\").from_(from_, copy=False)\n\n        while self._match_texts(self.TRANSFORM_PARSERS):\n            query = self.TRANSFORM_PARSERS[self._prev.text.upper()](self, query)\n\n        return query\n\n    def _parse_selection(\n        self,\n        query: exp.Query,\n        parse_method: t.Optional[t.Callable] = None,\n        append: bool = True,\n    ) -> exp.Query:\n        parse_method = parse_method if parse_method else self._parse_expression\n        if self._match(TokenType.L_BRACE):\n            selects = self._parse_csv(parse_method)\n\n            if not self._match(TokenType.R_BRACE, expression=query):\n                self.raise_error(\"Expecting }\")\n        else:\n            expression = parse_method()\n            selects = [expression] if expression else []\n\n        projections = {\n            select.alias_or_name: select.this if isinstance(select, exp.Alias) else select\n            for select in query.selects\n        }\n\n        resolved = [\n            select.transform(_resolve_projection, projections=projections, copy=False)\n            for select in selects\n        ]\n\n        return query.select(*resolved, append=append, copy=False)\n\n    def _parse_take(self, query: exp.Query) -> t.Optional[exp.Query]:\n        num = self._parse_number()  # TODO: TAKE for ranges a..b\n        return query.limit(num) if num else None\n\n    def _parse_ordered(\n        self, parse_method: t.Optional[t.Callable] = None\n    ) -> t.Optional[exp.Ordered]:\n        asc = self._match(TokenType.PLUS)\n        desc = self._match(TokenType.DASH) or (asc and False)\n        term = term = super()._parse_ordered(parse_method=parse_method)\n        if term and desc:\n            term.set(\"desc\", True)\n            term.set(\"nulls_first\", False)\n        return term\n\n    def _parse_order_by(self, query: exp.Select) -> t.Optional[exp.Query]:\n        l_brace = self._match(TokenType.L_BRACE)\n        expressions = self._parse_csv(self._parse_ordered)\n        if l_brace and not self._match(TokenType.R_BRACE):\n            self.raise_error(\"Expecting }\")\n        return query.order_by(self.expression(exp.Order(expressions=expressions)), copy=False)\n\n    def _parse_aggregate(self) -> t.Optional[exp.Expr]:\n        alias = None\n        if self._next.token_type == TokenType.ALIAS:\n            alias = self._parse_id_var(any_token=True)\n            self._match(TokenType.ALIAS)\n\n        name = self._curr.text.upper()\n        func_builder = self.FUNCTIONS.get(name)\n        if func_builder:\n            self._advance()\n            args = self._parse_column()\n            func = func_builder([args])\n        else:\n            self.raise_error(f\"Unsupported aggregation function {name}\")\n        if alias:\n            return self.expression(exp.Alias(this=func, alias=alias))\n        return func\n\n    def _parse_expression(self) -> t.Optional[exp.Expr]:\n        if self._next.token_type == TokenType.ALIAS:\n            alias = self._parse_id_var(True)\n            self._match(TokenType.ALIAS)\n            return self.expression(exp.Alias(this=self._parse_assignment(), alias=alias))\n        return self._parse_assignment()\n\n    def _parse_table(\n        self,\n        schema: bool = False,\n        joins: bool = False,\n        alias_tokens: t.Optional[Collection[TokenType]] = None,\n        parse_bracket: bool = False,\n        is_db_reference: bool = False,\n        parse_partition: bool = False,\n        consume_pipe: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        return self._parse_table_parts()\n\n    def _parse_from(\n        self,\n        joins: bool = False,\n        skip_from_token: bool = False,\n        consume_pipe: bool = False,\n    ) -> t.Optional[exp.From]:\n        if not skip_from_token and not self._match(TokenType.FROM):\n            return None\n\n        comments = self._prev_comments\n        return self.expression(\n            exp.From(this=self._parse_table(joins=joins)),\n            comments=comments,\n        )\n"
  },
  {
    "path": "sqlglot/parsers/redshift.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.postgres import PostgresParser\nfrom sqlglot.parser import build_convert_timezone\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.dialects.dialect import map_date_part\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from collections.abc import Collection\n\n\ndef _build_date_delta(expr_type: t.Type[E]) -> t.Callable[[t.List], E]:\n    def _builder(args: t.List) -> E:\n        expr = expr_type(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=map_date_part(seq_get(args, 0)),\n        )\n        if expr_type is exp.TsOrDsAdd:\n            expr.set(\"return_type\", exp.DataType.build(\"TIMESTAMP\"))\n\n        return expr\n\n    return _builder\n\n\nclass RedshiftParser(PostgresParser):\n    FUNCTIONS = {\n        **{k: v for k, v in PostgresParser.FUNCTIONS.items() if k != \"GET_BIT\"},\n        \"ADD_MONTHS\": lambda args: exp.TsOrDsAdd(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            unit=exp.var(\"month\"),\n            return_type=exp.DataType.build(\"TIMESTAMP\"),\n        ),\n        \"CONVERT_TIMEZONE\": lambda args: build_convert_timezone(args, \"UTC\"),\n        \"DATEADD\": _build_date_delta(exp.TsOrDsAdd),\n        \"DATE_ADD\": _build_date_delta(exp.TsOrDsAdd),\n        \"DATEDIFF\": _build_date_delta(exp.TsOrDsDiff),\n        \"DATE_DIFF\": _build_date_delta(exp.TsOrDsDiff),\n        \"GETDATE\": exp.CurrentTimestamp.from_arg_list,\n        \"LISTAGG\": exp.GroupConcat.from_arg_list,\n        \"REGEXP_SUBSTR\": lambda args: exp.RegexpExtract(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            position=seq_get(args, 2),\n            occurrence=seq_get(args, 3),\n            parameters=seq_get(args, 4),\n        ),\n        \"SPLIT_TO_ARRAY\": lambda args: exp.StringToArray(\n            this=seq_get(args, 0), expression=seq_get(args, 1) or exp.Literal.string(\",\")\n        ),\n        \"STRTOL\": exp.FromBase.from_arg_list,\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **PostgresParser.NO_PAREN_FUNCTION_PARSERS,\n        \"APPROXIMATE\": lambda self: self._parse_approximate_count(),\n        \"SYSDATE\": lambda self: self.expression(exp.CurrentTimestamp(sysdate=True)),\n    }\n\n    SUPPORTS_IMPLICIT_UNNEST = True\n\n    def _parse_table(\n        self,\n        schema: bool = False,\n        joins: bool = False,\n        alias_tokens: t.Optional[Collection[TokenType]] = None,\n        parse_bracket: bool = False,\n        is_db_reference: bool = False,\n        parse_partition: bool = False,\n        consume_pipe: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        # Redshift supports UNPIVOTing SUPER objects, e.g. `UNPIVOT foo.obj[0] AS val AT attr`\n        unpivot = self._match(TokenType.UNPIVOT)\n        table = super()._parse_table(\n            schema=schema,\n            joins=joins,\n            alias_tokens=alias_tokens,\n            parse_bracket=parse_bracket,\n            is_db_reference=is_db_reference,\n        )\n\n        return self.expression(exp.Pivot(this=table, unpivot=True)) if unpivot else table\n\n    def _parse_convert(self, strict: bool, safe: t.Optional[bool] = None) -> t.Optional[exp.Expr]:\n        to = self._parse_types()\n        self._match(TokenType.COMMA)\n        this = self._parse_bitwise()\n        return self.expression(exp.TryCast(this=this, to=to, safe=safe))\n\n    def _parse_approximate_count(self) -> t.Optional[exp.ApproxDistinct]:\n        index = self._index - 1\n        func = self._parse_function()\n\n        if isinstance(func, exp.Count) and isinstance(func.this, exp.Distinct):\n            return self.expression(exp.ApproxDistinct(this=seq_get(func.this.expressions, 0)))\n        self._retreat(index)\n        return None\n\n    def _parse_projections(self) -> t.Tuple[t.List[exp.Expr], t.Optional[t.List[exp.Expr]]]:\n        projections, _ = super()._parse_projections()\n        if self._prev.text.upper() == \"EXCLUDE\" and self._curr:\n            self._retreat(self._index - 1)\n\n        # EXCLUDE clause always comes at the end of the projection list and applies to it as a whole\n        exclude = (\n            self._parse_wrapped_csv(self._parse_expression, optional=True)\n            if self._match_text_seq(\"EXCLUDE\")\n            else []\n        )\n\n        if (\n            exclude\n            and isinstance(expr := projections[-1], exp.Alias)\n            and expr.alias.upper() == \"EXCLUDE\"\n        ):\n            projections[-1] = expr.this.pop()\n\n        return projections, exclude\n"
  },
  {
    "path": "sqlglot/parsers/risingwave.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.parsers.postgres import PostgresParser\nfrom sqlglot.tokens import TokenType\n\n\nclass RisingWaveParser(PostgresParser):\n    WRAPPED_TRANSFORM_COLUMN_CONSTRAINT = False\n\n    PROPERTY_PARSERS = {\n        **PostgresParser.PROPERTY_PARSERS,\n        \"ENCODE\": lambda self: self._parse_encode_property(),\n        \"INCLUDE\": lambda self: self._parse_include_property(),\n        \"KEY\": lambda self: self._parse_encode_property(key=True),\n    }\n\n    CONSTRAINT_PARSERS = {\n        **PostgresParser.CONSTRAINT_PARSERS,\n        \"WATERMARK\": lambda self: self.expression(\n            exp.WatermarkColumnConstraint(\n                this=self._match(TokenType.FOR) and self._parse_column(),\n                expression=self._match(TokenType.ALIAS) and self._parse_disjunction(),\n            )\n        ),\n    }\n\n    SCHEMA_UNNAMED_CONSTRAINTS = {\n        *PostgresParser.SCHEMA_UNNAMED_CONSTRAINTS,\n        \"WATERMARK\",\n    }\n\n    def _parse_table_hints(self) -> t.Optional[t.List[exp.Expr]]:\n        # There is no hint in risingwave.\n        # Do nothing here to avoid WITH keywords conflict in CREATE SINK statement.\n        return None\n\n    def _parse_include_property(self) -> t.Optional[exp.Expr]:\n        header: t.Optional[exp.Expr] = None\n        coldef: t.Optional[exp.Expr] = None\n\n        this = self._parse_var_or_string()\n\n        if not self._match(TokenType.ALIAS):\n            header = self._parse_field()\n            if header:\n                coldef = self.expression(exp.ColumnDef(this=header, kind=self._parse_types()))\n\n        self._match(TokenType.ALIAS)\n        alias = self._parse_id_var(tokens=self.ALIAS_TOKENS)\n\n        return self.expression(exp.IncludeProperty(this=this, alias=alias, column_def=coldef))\n\n    def _parse_encode_property(self, key: t.Optional[bool] = None) -> exp.EncodeProperty:\n        self._match_text_seq(\"ENCODE\")\n        this = self._parse_var_or_string()\n\n        if self._match(TokenType.L_PAREN, advance=False):\n            properties = self.expression(\n                exp.Properties(expressions=self._parse_wrapped_properties())\n            )\n        else:\n            properties = None\n\n        return self.expression(exp.EncodeProperty(this=this, properties=properties, key=key))\n"
  },
  {
    "path": "sqlglot/parsers/singlestore.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.trie import new_trie\nfrom sqlglot.dialects.dialect import (\n    build_formatted_time,\n    build_json_extract_path,\n)\nfrom sqlglot.dialects.mysql import MySQL\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.mysql import MySQLParser, _show_parser\nfrom sqlglot.tokens import TokenType\n\n\ndef cast_to_time6(\n    expression: t.Optional[exp.Expr], time_type: exp.DType = exp.DType.TIME\n) -> exp.Cast:\n    return exp.Cast(\n        this=expression,\n        to=exp.DataType.build(\n            time_type,\n            expressions=[exp.DataTypeParam(this=exp.Literal.number(6))],\n        ),\n    )\n\n\nclass SingleStoreParser(MySQLParser):\n    FUNCTIONS = {\n        **MySQLParser.FUNCTIONS,\n        \"TO_DATE\": build_formatted_time(exp.TsOrDsToDate, \"singlestore\"),\n        \"TO_TIMESTAMP\": build_formatted_time(exp.StrToTime, \"singlestore\"),\n        \"TO_CHAR\": build_formatted_time(exp.ToChar, \"singlestore\"),\n        \"STR_TO_DATE\": build_formatted_time(exp.StrToDate, \"mysql\"),\n        \"DATE_FORMAT\": build_formatted_time(exp.TimeToStr, \"mysql\"),\n        # The first argument of following functions is converted to TIME(6)\n        # This is needed because exp.TimeToStr is converted to DATE_FORMAT\n        # which interprets the first argument as DATETIME and fails to parse\n        # string literals like '12:05:47' without a date part.\n        \"TIME_FORMAT\": lambda args: exp.TimeToStr(\n            this=cast_to_time6(seq_get(args, 0)),\n            format=MySQL.format_time(seq_get(args, 1)),\n        ),\n        \"HOUR\": lambda args: exp.cast(\n            exp.TimeToStr(\n                this=cast_to_time6(seq_get(args, 0)),\n                format=MySQL.format_time(exp.Literal.string(\"%k\")),\n            ),\n            exp.DType.INT,\n        ),\n        \"MICROSECOND\": lambda args: exp.cast(\n            exp.TimeToStr(\n                this=cast_to_time6(seq_get(args, 0)),\n                format=MySQL.format_time(exp.Literal.string(\"%f\")),\n            ),\n            exp.DType.INT,\n        ),\n        \"SECOND\": lambda args: exp.cast(\n            exp.TimeToStr(\n                this=cast_to_time6(seq_get(args, 0)),\n                format=MySQL.format_time(exp.Literal.string(\"%s\")),\n            ),\n            exp.DType.INT,\n        ),\n        \"MINUTE\": lambda args: exp.cast(\n            exp.TimeToStr(\n                this=cast_to_time6(seq_get(args, 0)),\n                format=MySQL.format_time(exp.Literal.string(\"%i\")),\n            ),\n            exp.DType.INT,\n        ),\n        \"MONTHNAME\": lambda args: exp.TimeToStr(\n            this=seq_get(args, 0),\n            format=MySQL.format_time(exp.Literal.string(\"%M\")),\n        ),\n        \"WEEKDAY\": lambda args: exp.paren(exp.DayOfWeek(this=seq_get(args, 0)) + 5, copy=False) % 7,\n        \"UNIX_TIMESTAMP\": exp.StrToUnix.from_arg_list,\n        \"FROM_UNIXTIME\": build_formatted_time(exp.UnixToTime, \"mysql\"),\n        \"TIME_BUCKET\": lambda args: exp.DateBin(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            origin=seq_get(args, 2),\n        ),\n        \"BSON_EXTRACT_BSON\": build_json_extract_path(exp.JSONBExtract),\n        \"BSON_EXTRACT_STRING\": build_json_extract_path(exp.JSONBExtractScalar, json_type=\"STRING\"),\n        \"BSON_EXTRACT_DOUBLE\": build_json_extract_path(exp.JSONBExtractScalar, json_type=\"DOUBLE\"),\n        \"BSON_EXTRACT_BIGINT\": build_json_extract_path(exp.JSONBExtractScalar, json_type=\"BIGINT\"),\n        \"JSON_EXTRACT_JSON\": build_json_extract_path(exp.JSONExtract),\n        \"JSON_EXTRACT_STRING\": build_json_extract_path(exp.JSONExtractScalar, json_type=\"STRING\"),\n        \"JSON_EXTRACT_DOUBLE\": build_json_extract_path(exp.JSONExtractScalar, json_type=\"DOUBLE\"),\n        \"JSON_EXTRACT_BIGINT\": build_json_extract_path(exp.JSONExtractScalar, json_type=\"BIGINT\"),\n        \"JSON_ARRAY_CONTAINS_STRING\": lambda args: exp.JSONArrayContains(\n            this=seq_get(args, 1),\n            expression=seq_get(args, 0),\n            json_type=\"STRING\",\n        ),\n        \"JSON_ARRAY_CONTAINS_DOUBLE\": lambda args: exp.JSONArrayContains(\n            this=seq_get(args, 1),\n            expression=seq_get(args, 0),\n            json_type=\"DOUBLE\",\n        ),\n        \"JSON_ARRAY_CONTAINS_JSON\": lambda args: exp.JSONArrayContains(\n            this=seq_get(args, 1),\n            expression=seq_get(args, 0),\n            json_type=\"JSON\",\n        ),\n        \"JSON_KEYS\": lambda args: exp.JSONKeys(\n            this=seq_get(args, 0),\n            expressions=args[1:],\n        ),\n        \"JSON_PRETTY\": exp.JSONFormat.from_arg_list,\n        \"JSON_BUILD_ARRAY\": lambda args: exp.JSONArray(expressions=args),\n        \"JSON_BUILD_OBJECT\": lambda args: exp.JSONObject(expressions=args),\n        \"DATE\": exp.Date.from_arg_list,\n        \"DAYNAME\": lambda args: exp.TimeToStr(\n            this=seq_get(args, 0),\n            format=MySQL.format_time(exp.Literal.string(\"%W\")),\n        ),\n        \"TIMESTAMPDIFF\": lambda args: exp.TimestampDiff(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=seq_get(args, 0),\n        ),\n        \"APPROX_COUNT_DISTINCT\": exp.Hll.from_arg_list,\n        \"APPROX_PERCENTILE\": lambda args, dialect: exp.ApproxQuantile(\n            this=seq_get(args, 0),\n            quantile=seq_get(args, 1),\n            error_tolerance=seq_get(args, 2),\n        ),\n        \"VARIANCE\": exp.VariancePop.from_arg_list,\n        \"INSTR\": exp.Contains.from_arg_list,\n        \"REGEXP_MATCH\": lambda args: exp.RegexpExtractAll(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            parameters=seq_get(args, 2),\n        ),\n        \"REGEXP_SUBSTR\": lambda args: exp.RegexpExtract(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            position=seq_get(args, 2),\n            occurrence=seq_get(args, 3),\n            parameters=seq_get(args, 4),\n        ),\n        \"REDUCE\": lambda args: exp.Reduce(\n            initial=seq_get(args, 0),\n            this=seq_get(args, 1),\n            merge=seq_get(args, 2),\n        ),\n    }\n\n    FUNCTION_PARSERS = {\n        **MySQLParser.FUNCTION_PARSERS,\n        \"JSON_AGG\": lambda self: exp.JSONArrayAgg(\n            this=self._parse_term(),\n            order=self._parse_order(),\n        ),\n    }\n\n    NO_PAREN_FUNCTIONS = {\n        **MySQLParser.NO_PAREN_FUNCTIONS,\n        TokenType.UTC_DATE: exp.UtcDate,\n        TokenType.UTC_TIME: exp.UtcTime,\n        TokenType.UTC_TIMESTAMP: exp.UtcTimestamp,\n    }\n\n    CAST_COLUMN_OPERATORS = {TokenType.COLON_GT, TokenType.NCOLON_GT}\n\n    COLUMN_OPERATORS = {\n        **MySQLParser.COLUMN_OPERATORS,\n        TokenType.COLON_GT: lambda self, this, to: self.expression(exp.Cast(this=this, to=to)),\n        TokenType.NCOLON_GT: lambda self, this, to: self.expression(exp.TryCast(this=this, to=to)),\n        TokenType.DCOLON: lambda self, this, path: build_json_extract_path(exp.JSONExtract)(\n            [this, exp.Literal.string(path.name)]\n        ),\n        TokenType.DCOLONDOLLAR: lambda self, this, path: build_json_extract_path(\n            exp.JSONExtractScalar, json_type=\"STRING\"\n        )([this, exp.Literal.string(path.name)]),\n        TokenType.DCOLONPERCENT: lambda self, this, path: build_json_extract_path(\n            exp.JSONExtractScalar, json_type=\"DOUBLE\"\n        )([this, exp.Literal.string(path.name)]),\n        TokenType.DCOLONQMARK: lambda self, this, path: self.expression(\n            exp.JSONExists(this=this, path=path.name, from_dcolonqmark=True)\n        ),\n    }\n    COLUMN_OPERATORS = {\n        k: v\n        for k, v in COLUMN_OPERATORS.items()\n        if k\n        not in (\n            TokenType.ARROW,\n            TokenType.DARROW,\n            TokenType.HASH_ARROW,\n            TokenType.DHASH_ARROW,\n            TokenType.PLACEHOLDER,\n        )\n    }\n\n    SHOW_PARSERS = {\n        **MySQLParser.SHOW_PARSERS,\n        \"AGGREGATES\": _show_parser(\"AGGREGATES\"),\n        \"CDC EXTRACTOR POOL\": _show_parser(\"CDC EXTRACTOR POOL\"),\n        \"CREATE AGGREGATE\": _show_parser(\"CREATE AGGREGATE\", target=True),\n        \"CREATE PIPELINE\": _show_parser(\"CREATE PIPELINE\", target=True),\n        \"CREATE PROJECTION\": _show_parser(\"CREATE PROJECTION\", target=True),\n        \"DATABASE STATUS\": _show_parser(\"DATABASE STATUS\"),\n        \"DISTRIBUTED_PLANCACHE STATUS\": _show_parser(\"DISTRIBUTED_PLANCACHE STATUS\"),\n        \"FULLTEXT SERVICE METRICS LOCAL\": _show_parser(\"FULLTEXT SERVICE METRICS LOCAL\"),\n        \"FULLTEXT SERVICE METRICS FOR NODE\": _show_parser(\n            \"FULLTEXT SERVICE METRICS FOR NODE\", target=True\n        ),\n        \"FULLTEXT SERVICE STATUS\": _show_parser(\"FULLTEXT SERVICE STATUS\"),\n        \"FUNCTIONS\": _show_parser(\"FUNCTIONS\"),\n        \"GROUPS\": _show_parser(\"GROUPS\"),\n        \"GROUPS FOR ROLE\": _show_parser(\"GROUPS FOR ROLE\", target=True),\n        \"GROUPS FOR USER\": _show_parser(\"GROUPS FOR USER\", target=True),\n        \"INDEXES\": _show_parser(\"INDEX\", target=\"FROM\"),\n        \"KEYS\": _show_parser(\"INDEX\", target=\"FROM\"),\n        \"LINKS\": _show_parser(\"LINKS\", target=\"ON\"),\n        \"LOAD ERRORS\": _show_parser(\"LOAD ERRORS\"),\n        \"LOAD WARNINGS\": _show_parser(\"LOAD WARNINGS\"),\n        \"PARTITIONS\": _show_parser(\"PARTITIONS\", target=\"ON\"),\n        \"PIPELINES\": _show_parser(\"PIPELINES\"),\n        \"PLAN\": _show_parser(\"PLAN\", target=True),\n        \"PLANCACHE\": _show_parser(\"PLANCACHE\"),\n        \"PROCEDURES\": _show_parser(\"PROCEDURES\"),\n        \"PROJECTIONS\": _show_parser(\"PROJECTIONS\", target=\"ON TABLE\"),\n        \"REPLICATION STATUS\": _show_parser(\"REPLICATION STATUS\"),\n        \"REPRODUCTION\": _show_parser(\"REPRODUCTION\"),\n        \"RESOURCE POOLS\": _show_parser(\"RESOURCE POOLS\"),\n        \"ROLES\": _show_parser(\"ROLES\"),\n        \"ROLES FOR USER\": _show_parser(\"ROLES FOR USER\", target=True),\n        \"ROLES FOR GROUP\": _show_parser(\"ROLES FOR GROUP\", target=True),\n        \"STATUS EXTENDED\": _show_parser(\"STATUS EXTENDED\"),\n        \"USERS\": _show_parser(\"USERS\"),\n        \"USERS FOR ROLE\": _show_parser(\"USERS FOR ROLE\", target=True),\n        \"USERS FOR GROUP\": _show_parser(\"USERS FOR GROUP\", target=True),\n    }\n\n    SHOW_TRIE = new_trie(key.split(\" \") for key in SHOW_PARSERS)\n\n    ALTER_PARSERS = {\n        **MySQLParser.ALTER_PARSERS,\n        \"CHANGE\": lambda self: self.expression(\n            exp.RenameColumn(this=self._parse_column(), to=self._parse_column())\n        ),\n    }\n\n    def _parse_vector_expressions(self, expressions: t.List[exp.Expr]) -> t.List[exp.Expr]:\n        type_name = expressions[1].name.upper()\n        if type_name in self.dialect.VECTOR_TYPE_ALIASES:\n            type_name = self.dialect.VECTOR_TYPE_ALIASES[type_name]\n\n        return [exp.DataType.build(type_name, dialect=self.dialect), expressions[0]]\n"
  },
  {
    "path": "sqlglot/parsers/snowflake.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.trie import new_trie\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    build_default_decimal_type,\n    build_formatted_time,\n    build_like,\n    build_replace_with_optional_replacement,\n    build_timetostr_or_tochar,\n    build_trunc,\n    binary_from_function,\n    date_trunc_to_time,\n    map_date_part,\n)\nfrom sqlglot.helper import is_date_unit, is_int, seq_get\nfrom sqlglot.tokens import TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import B, E\n    from collections.abc import Collection\n\n\ndef _build_approx_top_k(args: t.List) -> exp.ApproxTopK:\n    \"\"\"\n    Normalizes APPROX_TOP_K arguments to match Snowflake semantics.\n\n    Snowflake APPROX_TOP_K signature: APPROX_TOP_K(column [, k] [, counters])\n    - k defaults to 1 if omitted (per Snowflake documentation)\n    - counters is optional precision parameter\n    \"\"\"\n    # Add default k=1 if only column is provided\n    if len(args) == 1:\n        args.append(exp.Literal.number(1))\n\n    return exp.ApproxTopK.from_arg_list(args)\n\n\ndef _build_date_from_parts(args: t.List) -> exp.DateFromParts:\n    return exp.DateFromParts(\n        year=seq_get(args, 0),\n        month=seq_get(args, 1),\n        day=seq_get(args, 2),\n        allow_overflow=True,\n    )\n\n\n# Timestamp types used in _build_datetime\nTIMESTAMP_TYPES = {\n    exp.DType.TIMESTAMP: \"TO_TIMESTAMP\",\n    exp.DType.TIMESTAMPLTZ: \"TO_TIMESTAMP_LTZ\",\n    exp.DType.TIMESTAMPNTZ: \"TO_TIMESTAMP_NTZ\",\n    exp.DType.TIMESTAMPTZ: \"TO_TIMESTAMP_TZ\",\n}\n\n\ndef _build_datetime(\n    name: str, kind: exp.DType, safe: bool = False\n) -> t.Callable[[t.List], exp.Func]:\n    def _builder(args: t.List) -> exp.Func:\n        value = seq_get(args, 0)\n        scale_or_fmt = seq_get(args, 1)\n\n        int_value = value is not None and is_int(value.name)\n        int_scale_or_fmt = scale_or_fmt is not None and scale_or_fmt.is_int\n\n        if isinstance(value, (exp.Literal, exp.Neg)) or (value and scale_or_fmt):\n            # Converts calls like `TO_TIME('01:02:03')` into casts\n            if len(args) == 1 and value.is_string and not int_value:\n                return (\n                    exp.TryCast(this=value, to=exp.DataType.build(kind), requires_string=True)\n                    if safe\n                    else exp.cast(value, kind)\n                )\n\n            # Handles `TO_TIMESTAMP(str, fmt)` and `TO_TIMESTAMP(num, scale)` as special\n            # cases so we can transpile them, since they're relatively common\n            if kind in TIMESTAMP_TYPES:\n                if not safe and (int_scale_or_fmt or (int_value and scale_or_fmt is None)):\n                    # TRY_TO_TIMESTAMP('integer') is not parsed into exp.UnixToTime as\n                    # it's not easily transpilable. Also, numeric-looking strings with\n                    # format strings (e.g., TO_TIMESTAMP('20240115', 'YYYYMMDD')) should\n                    # use StrToTime, not UnixToTime.\n                    unix_expr = exp.UnixToTime(this=value, scale=scale_or_fmt)\n                    unix_expr.set(\"target_type\", exp.DataType.build(kind, dialect=\"snowflake\"))\n                    return unix_expr\n                if scale_or_fmt and not int_scale_or_fmt:\n                    # Format string provided (e.g., 'YYYY-MM-DD'), use StrToTime\n                    strtotime_expr = build_formatted_time(exp.StrToTime, \"snowflake\")(args)\n                    strtotime_expr.set(\"safe\", safe)\n                    strtotime_expr.set(\"target_type\", exp.DataType.build(kind, dialect=\"snowflake\"))\n                    return strtotime_expr\n\n        # Handle DATE/TIME with format strings - allow int_value if a format string is provided\n        has_format_string = scale_or_fmt and not int_scale_or_fmt\n        if kind in (exp.DType.DATE, exp.DType.TIME) and (not int_value or has_format_string):\n            klass = exp.TsOrDsToDate if kind == exp.DType.DATE else exp.TsOrDsToTime\n            formatted_exp = build_formatted_time(klass, \"snowflake\")(args)\n            formatted_exp.set(\"safe\", safe)\n            return formatted_exp\n\n        return exp.Anonymous(this=name, expressions=args)\n\n    return _builder\n\n\ndef _build_bitwise(expr_type: t.Type[B], name: str) -> t.Callable[[t.List], B | exp.Anonymous]:\n    def _builder(args: t.List) -> B | exp.Anonymous:\n        if len(args) == 3:\n            # Special handling for bitwise operations with padside argument\n            if expr_type in (exp.BitwiseAnd, exp.BitwiseOr, exp.BitwiseXor):\n                return expr_type(\n                    this=seq_get(args, 0), expression=seq_get(args, 1), padside=seq_get(args, 2)\n                )\n            return exp.Anonymous(this=name, expressions=args)\n\n        result = binary_from_function(expr_type)(args)\n\n        # Snowflake specifies INT128 for bitwise shifts\n        if expr_type in (exp.BitwiseLeftShift, exp.BitwiseRightShift):\n            result.set(\"requires_int128\", True)\n\n        return result\n\n    return _builder\n\n\n# https://docs.snowflake.com/en/sql-reference/functions/div0\ndef _build_if_from_div0(args: t.List) -> exp.If:\n    lhs = exp._wrap(seq_get(args, 0), exp.Binary)\n    rhs = exp._wrap(seq_get(args, 1), exp.Binary)\n\n    cond = exp.EQ(this=rhs, expression=exp.Literal.number(0)).and_(\n        exp.Is(this=lhs, expression=exp.null()).not_()\n    )\n    true = exp.Literal.number(0)\n    false = exp.Div(this=lhs, expression=rhs)\n    return exp.If(this=cond, true=true, false=false)\n\n\n# https://docs.snowflake.com/en/sql-reference/functions/div0null\ndef _build_if_from_div0null(args: t.List) -> exp.If:\n    lhs = exp._wrap(seq_get(args, 0), exp.Binary)\n    rhs = exp._wrap(seq_get(args, 1), exp.Binary)\n\n    # Returns 0 when divisor is 0 OR NULL\n    cond = exp.EQ(this=rhs, expression=exp.Literal.number(0)).or_(\n        exp.Is(this=rhs, expression=exp.null())\n    )\n    true = exp.Literal.number(0)\n    false = exp.Div(this=lhs, expression=rhs)\n    return exp.If(this=cond, true=true, false=false)\n\n\n# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull\ndef _build_if_from_zeroifnull(args: t.List) -> exp.If:\n    cond = exp.Is(this=seq_get(args, 0), expression=exp.Null())\n    return exp.If(this=cond, true=exp.Literal.number(0), false=seq_get(args, 0))\n\n\ndef _build_search(args: t.List) -> exp.Search:\n    kwargs = {\n        \"this\": seq_get(args, 0),\n        \"expression\": seq_get(args, 1),\n        **{arg.name.lower(): arg for arg in args[2:] if isinstance(arg, exp.Kwarg)},\n    }\n    return exp.Search(**kwargs)\n\n\n# https://docs.snowflake.com/en/sql-reference/functions/zeroifnull\ndef _build_if_from_nullifzero(args: t.List) -> exp.If:\n    cond = exp.EQ(this=seq_get(args, 0), expression=exp.Literal.number(0))\n    return exp.If(this=cond, true=exp.Null(), false=seq_get(args, 0))\n\n\ndef _build_regexp_replace(args: t.List) -> exp.RegexpReplace:\n    regexp_replace = exp.RegexpReplace.from_arg_list(args)\n\n    if not regexp_replace.args.get(\"replacement\"):\n        regexp_replace.set(\"replacement\", exp.Literal.string(\"\"))\n\n    return regexp_replace\n\n\ndef _build_regexp_like(args: t.List) -> exp.RegexpLike:\n    return exp.RegexpLike(\n        this=seq_get(args, 0),\n        expression=seq_get(args, 1),\n        flag=seq_get(args, 2),\n        full_match=True,\n    )\n\n\ndef _date_trunc_to_time(args: t.List) -> exp.DateTrunc | exp.TimestampTrunc:\n    trunc = date_trunc_to_time(args)\n    unit = map_date_part(trunc.args[\"unit\"])\n    trunc.set(\"unit\", unit)\n    is_time_input = trunc.this.is_type(exp.DType.TIME, exp.DType.TIMETZ)\n    if (isinstance(trunc, exp.TimestampTrunc) and is_date_unit(unit) or is_time_input) or (\n        isinstance(trunc, exp.DateTrunc) and not is_date_unit(unit)\n    ):\n        trunc.set(\"input_type_preserved\", True)\n    return trunc\n\n\ndef _build_regexp_extract(expr_type: t.Type[E]) -> t.Callable[[t.List, Dialect], E]:\n    def _builder(args: t.List, dialect: Dialect) -> E:\n        return expr_type(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            position=seq_get(args, 2),\n            occurrence=seq_get(args, 3),\n            parameters=seq_get(args, 4),\n            group=seq_get(args, 5) or exp.Literal.number(0),\n            **(\n                {\"null_if_pos_overflow\": dialect.REGEXP_EXTRACT_POSITION_OVERFLOW_RETURNS_NULL}\n                if expr_type is exp.RegexpExtract\n                else {}\n            ),\n        )\n\n    return _builder\n\n\ndef _build_timestamp_from_parts(args: t.List) -> exp.Func:\n    \"\"\"Build TimestampFromParts with support for both syntaxes:\n    1. TIMESTAMP_FROM_PARTS(year, month, day, hour, minute, second [, nanosecond] [, time_zone])\n    2. TIMESTAMP_FROM_PARTS(date_expr, time_expr) - Snowflake specific\n    \"\"\"\n    if len(args) == 2:\n        return exp.TimestampFromParts(this=seq_get(args, 0), expression=seq_get(args, 1))\n\n    return exp.TimestampFromParts.from_arg_list(args)\n\n\ndef _build_round(args: t.List) -> exp.Round:\n    \"\"\"\n    Build Round expression, unwrapping Snowflake's named parameters.\n\n    Maps EXPR => this, SCALE => decimals, ROUNDING_MODE => truncate.\n\n    Note: Snowflake does not support mixing named and positional arguments.\n    Arguments are either all named or all positional.\n    \"\"\"\n    kwarg_map = {\"EXPR\": \"this\", \"SCALE\": \"decimals\", \"ROUNDING_MODE\": \"truncate\"}\n    round_args = {}\n    positional_keys = [\"this\", \"decimals\", \"truncate\"]\n    positional_idx = 0\n\n    for arg in args:\n        if isinstance(arg, exp.Kwarg):\n            key = arg.this.name.upper()\n            round_key = kwarg_map.get(key)\n            if round_key:\n                round_args[round_key] = arg.expression\n        else:\n            if positional_idx < len(positional_keys):\n                round_args[positional_keys[positional_idx]] = arg\n                positional_idx += 1\n\n    expression = exp.Round(**round_args)\n    expression.set(\"casts_non_integer_decimals\", True)\n    return expression\n\n\ndef _build_array_sort(args: t.List) -> exp.SortArray:\n    asc = seq_get(args, 1)\n    nulls_first = seq_get(args, 2)\n    if nulls_first is None and isinstance(asc, exp.Boolean):\n        nulls_first = exp.Boolean(this=not asc.this)\n    return exp.SortArray(this=seq_get(args, 0), asc=asc, nulls_first=nulls_first)\n\n\ndef _build_generator(args: t.List) -> exp.Generator:\n    \"\"\"\n    Build Generator expression, unwrapping Snowflake's named parameters.\n\n    Maps ROWCOUNT => rowcount, TIMELIMIT => timelimit.\n    \"\"\"\n    kwarg_map = {\"ROWCOUNT\": \"rowcount\", \"TIMELIMIT\": \"timelimit\"}\n    gen_args = {}\n\n    positional_keys = (\"rowcount\", \"timelimit\")\n\n    for i, arg in enumerate(args):\n        if isinstance(arg, exp.Kwarg):\n            key = arg.this.name.upper()\n            gen_key = kwarg_map.get(key)\n            if gen_key:\n                gen_args[gen_key] = arg.expression\n        elif i < len(positional_keys):\n            gen_args[positional_keys[i]] = arg\n\n    return exp.Generator(**gen_args)\n\n\ndef _build_try_to_number(args: t.List[exp.Expr]) -> exp.Expr:\n    return exp.ToNumber(\n        this=seq_get(args, 0),\n        format=seq_get(args, 1),\n        precision=seq_get(args, 2),\n        scale=seq_get(args, 3),\n        safe=True,\n    )\n\n\ndef _show_parser(*args: t.Any, **kwargs: t.Any) -> t.Callable[[SnowflakeParser], exp.Show]:\n    def _parse(self: SnowflakeParser) -> exp.Show:\n        return self._parse_show_snowflake(*args, **kwargs)\n\n    return _parse\n\n\nclass SnowflakeParser(parser.Parser):\n    IDENTIFY_PIVOT_STRINGS = True\n    TYPED_LAMBDA_ARGS = True\n    DEFAULT_SAMPLING_METHOD = \"BERNOULLI\"\n    COLON_IS_VARIANT_EXTRACT = True\n    JSON_EXTRACT_REQUIRES_JSON_EXPRESSION = True\n\n    TYPE_TOKENS = {*parser.Parser.TYPE_TOKENS, TokenType.FILE}\n    STRUCT_TYPE_TOKENS = {*parser.Parser.STRUCT_TYPE_TOKENS, TokenType.FILE}\n    NESTED_TYPE_TOKENS = {*parser.Parser.NESTED_TYPE_TOKENS, TokenType.FILE}\n\n    ID_VAR_TOKENS = {\n        *parser.Parser.ID_VAR_TOKENS,\n        TokenType.EXCEPT,\n        TokenType.INTEGRATION,\n        TokenType.MATCH_CONDITION,\n        TokenType.PACKAGE,\n        TokenType.POLICY,\n        TokenType.POOL,\n        TokenType.ROLE,\n        TokenType.RULE,\n        TokenType.VOLUME,\n    }\n\n    ALIAS_TOKENS = parser.Parser.ALIAS_TOKENS | {\n        TokenType.INTEGRATION,\n        TokenType.PACKAGE,\n        TokenType.POLICY,\n        TokenType.POOL,\n        TokenType.ROLE,\n        TokenType.RULE,\n        TokenType.VOLUME,\n    }\n\n    TABLE_ALIAS_TOKENS = (\n        parser.Parser.TABLE_ALIAS_TOKENS\n        | {\n            TokenType.ANTI,\n            TokenType.INTEGRATION,\n            TokenType.PACKAGE,\n            TokenType.POLICY,\n            TokenType.POOL,\n            TokenType.ROLE,\n            TokenType.RULE,\n            TokenType.SEMI,\n            TokenType.VOLUME,\n            TokenType.WINDOW,\n        }\n    ) - {TokenType.MATCH_CONDITION}\n\n    COLON_PLACEHOLDER_TOKENS = ID_VAR_TOKENS | {TokenType.NUMBER}\n\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.LOCALTIME: exp.Localtime,\n        TokenType.LOCALTIMESTAMP: exp.Localtimestamp,\n        TokenType.CURRENT_TIME: exp.Localtime,\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"CHARINDEX\": lambda args: exp.StrPosition(\n            this=seq_get(args, 1),\n            substr=seq_get(args, 0),\n            position=seq_get(args, 2),\n            clamp_position=True,\n        ),\n        \"ADD_MONTHS\": lambda args: exp.AddMonths(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            preserve_end_of_month=True,\n        ),\n        \"APPROX_PERCENTILE\": exp.ApproxQuantile.from_arg_list,\n        \"CURRENT_TIME\": lambda args: exp.Localtime(this=seq_get(args, 0)),\n        \"APPROX_TOP_K\": _build_approx_top_k,\n        \"ARRAY_CONSTRUCT\": lambda args: exp.Array(expressions=args),\n        \"ARRAY_CONTAINS\": lambda args: exp.ArrayContains(\n            this=seq_get(args, 1),\n            expression=seq_get(args, 0),\n            ensure_variant=False,\n            check_null=True,\n        ),\n        \"ARRAY_DISTINCT\": lambda args: exp.ArrayDistinct(\n            this=seq_get(args, 0),\n            check_null=True,\n        ),\n        \"ARRAY_GENERATE_RANGE\": lambda args: exp.GenerateSeries(\n            # Snowflake has exclusive end semantics\n            start=seq_get(args, 0),\n            end=seq_get(args, 1),\n            step=seq_get(args, 2),\n            is_end_exclusive=True,\n        ),\n        \"ARRAY_EXCEPT\": lambda args: exp.ArrayExcept(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            is_multiset=True,\n        ),\n        \"ARRAY_INTERSECTION\": lambda args: exp.ArrayIntersect(\n            expressions=args,\n            is_multiset=True,\n        ),\n        \"ARRAY_POSITION\": lambda args: exp.ArrayPosition(\n            this=seq_get(args, 1),\n            expression=seq_get(args, 0),\n            zero_based=True,\n        ),\n        \"ARRAY_SLICE\": lambda args: exp.ArraySlice(\n            this=seq_get(args, 0),\n            start=seq_get(args, 1),\n            end=seq_get(args, 2),\n            zero_based=True,\n        ),\n        \"ARRAY_SORT\": _build_array_sort,\n        \"ARRAY_FLATTEN\": exp.Flatten.from_arg_list,\n        \"ARRAY_TO_STRING\": lambda args: exp.ArrayToString(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            null_is_empty=True,\n            null_delim_is_null=True,\n        ),\n        \"ARRAYS_OVERLAP\": lambda args: exp.ArrayOverlaps(\n            this=seq_get(args, 0), expression=seq_get(args, 1), null_safe=True\n        ),\n        \"BITAND\": _build_bitwise(exp.BitwiseAnd, \"BITAND\"),\n        \"BIT_AND\": _build_bitwise(exp.BitwiseAnd, \"BITAND\"),\n        \"BITNOT\": lambda args: exp.BitwiseNot(this=seq_get(args, 0)),\n        \"BIT_NOT\": lambda args: exp.BitwiseNot(this=seq_get(args, 0)),\n        \"BITXOR\": _build_bitwise(exp.BitwiseXor, \"BITXOR\"),\n        \"BIT_XOR\": _build_bitwise(exp.BitwiseXor, \"BITXOR\"),\n        \"BITOR\": _build_bitwise(exp.BitwiseOr, \"BITOR\"),\n        \"BIT_OR\": _build_bitwise(exp.BitwiseOr, \"BITOR\"),\n        \"BITSHIFTLEFT\": _build_bitwise(exp.BitwiseLeftShift, \"BITSHIFTLEFT\"),\n        \"BIT_SHIFTLEFT\": _build_bitwise(exp.BitwiseLeftShift, \"BIT_SHIFTLEFT\"),\n        \"BITSHIFTRIGHT\": _build_bitwise(exp.BitwiseRightShift, \"BITSHIFTRIGHT\"),\n        \"BIT_SHIFTRIGHT\": _build_bitwise(exp.BitwiseRightShift, \"BIT_SHIFTRIGHT\"),\n        \"BITANDAGG\": exp.BitwiseAndAgg.from_arg_list,\n        \"BITAND_AGG\": exp.BitwiseAndAgg.from_arg_list,\n        \"BIT_AND_AGG\": exp.BitwiseAndAgg.from_arg_list,\n        \"BIT_ANDAGG\": exp.BitwiseAndAgg.from_arg_list,\n        \"BITORAGG\": exp.BitwiseOrAgg.from_arg_list,\n        \"BITOR_AGG\": exp.BitwiseOrAgg.from_arg_list,\n        \"BIT_OR_AGG\": exp.BitwiseOrAgg.from_arg_list,\n        \"BIT_ORAGG\": exp.BitwiseOrAgg.from_arg_list,\n        \"BITXORAGG\": exp.BitwiseXorAgg.from_arg_list,\n        \"BITXOR_AGG\": exp.BitwiseXorAgg.from_arg_list,\n        \"BIT_XOR_AGG\": exp.BitwiseXorAgg.from_arg_list,\n        \"BIT_XORAGG\": exp.BitwiseXorAgg.from_arg_list,\n        \"BITMAP_OR_AGG\": exp.BitmapOrAgg.from_arg_list,\n        \"BOOLAND\": lambda args: exp.Booland(\n            this=seq_get(args, 0), expression=seq_get(args, 1), round_input=True\n        ),\n        \"BOOLOR\": lambda args: exp.Boolor(\n            this=seq_get(args, 0), expression=seq_get(args, 1), round_input=True\n        ),\n        \"BOOLNOT\": lambda args: exp.Boolnot(this=seq_get(args, 0), round_input=True),\n        \"BOOLXOR\": lambda args: exp.Xor(\n            this=seq_get(args, 0), expression=seq_get(args, 1), round_input=True\n        ),\n        \"CORR\": lambda args: exp.Corr(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            null_on_zero_variance=True,\n        ),\n        \"DATE\": _build_datetime(\"DATE\", exp.DType.DATE),\n        \"DATEFROMPARTS\": _build_date_from_parts,\n        \"DATE_FROM_PARTS\": _build_date_from_parts,\n        \"DATE_TRUNC\": _date_trunc_to_time,\n        \"DATEADD\": lambda args: exp.DateAdd(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=map_date_part(seq_get(args, 0)),\n        ),\n        \"DATEDIFF\": lambda args: exp.DateDiff(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=map_date_part(seq_get(args, 0)),\n            date_part_boundary=True,\n        ),\n        \"DAYNAME\": lambda args: exp.Dayname(this=seq_get(args, 0), abbreviated=True),\n        \"DAYOFWEEKISO\": exp.DayOfWeekIso.from_arg_list,\n        \"DIV0\": _build_if_from_div0,\n        \"DIV0NULL\": _build_if_from_div0null,\n        \"EDITDISTANCE\": lambda args: exp.Levenshtein(\n            this=seq_get(args, 0), expression=seq_get(args, 1), max_dist=seq_get(args, 2)\n        ),\n        \"FLATTEN\": exp.Explode.from_arg_list,\n        \"GENERATOR\": _build_generator,\n        \"GET\": exp.GetExtract.from_arg_list,\n        \"GETDATE\": exp.CurrentTimestamp.from_arg_list,\n        \"GET_PATH\": lambda args, dialect: exp.JSONExtract(\n            this=seq_get(args, 0),\n            expression=dialect.to_json_path(seq_get(args, 1)),\n            requires_json=True,\n        ),\n        \"GREATEST_IGNORE_NULLS\": lambda args: exp.Greatest(\n            this=seq_get(args, 0), expressions=args[1:], ignore_nulls=True\n        ),\n        \"LEAST_IGNORE_NULLS\": lambda args: exp.Least(\n            this=seq_get(args, 0), expressions=args[1:], ignore_nulls=True\n        ),\n        \"HEX_DECODE_BINARY\": exp.Unhex.from_arg_list,\n        \"IFF\": exp.If.from_arg_list,\n        \"JAROWINKLER_SIMILARITY\": lambda args: exp.JarowinklerSimilarity(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            case_insensitive=True,\n        ),\n        \"MD5_HEX\": exp.MD5.from_arg_list,\n        \"MD5_BINARY\": exp.MD5Digest.from_arg_list,\n        \"MD5_NUMBER_LOWER64\": exp.MD5NumberLower64.from_arg_list,\n        \"MD5_NUMBER_UPPER64\": exp.MD5NumberUpper64.from_arg_list,\n        \"MONTHNAME\": lambda args: exp.Monthname(this=seq_get(args, 0), abbreviated=True),\n        \"LAST_DAY\": lambda args: exp.LastDay(\n            this=seq_get(args, 0), unit=map_date_part(seq_get(args, 1))\n        ),\n        \"LEN\": lambda args: exp.Length(this=seq_get(args, 0), binary=True),\n        \"LENGTH\": lambda args: exp.Length(this=seq_get(args, 0), binary=True),\n        \"LOCALTIMESTAMP\": exp.CurrentTimestamp.from_arg_list,\n        \"NULLIFZERO\": _build_if_from_nullifzero,\n        \"OBJECT_CONSTRUCT\": lambda args: build_object_construct(args),\n        \"OBJECT_KEYS\": exp.JSONKeys.from_arg_list,\n        \"OCTET_LENGTH\": exp.ByteLength.from_arg_list,\n        \"PARSE_URL\": lambda args: exp.ParseUrl(this=seq_get(args, 0), permissive=seq_get(args, 1)),\n        \"REGEXP_EXTRACT_ALL\": _build_regexp_extract(exp.RegexpExtractAll),\n        \"REGEXP_LIKE\": _build_regexp_like,\n        \"REGEXP_REPLACE\": _build_regexp_replace,\n        \"REGEXP_SUBSTR\": _build_regexp_extract(exp.RegexpExtract),\n        \"REGEXP_SUBSTR_ALL\": _build_regexp_extract(exp.RegexpExtractAll),\n        \"RANDOM\": lambda args: exp.Rand(\n            this=seq_get(args, 0),\n            lower=exp.Literal.number(-9223372036854775808.0),  # -2^63 as float to avoid overflow\n            upper=exp.Literal.number(9223372036854775807.0),  # 2^63-1 as float\n        ),\n        \"REPLACE\": build_replace_with_optional_replacement,\n        \"RLIKE\": _build_regexp_like,\n        \"ROUND\": _build_round,\n        \"SHA1_BINARY\": exp.SHA1Digest.from_arg_list,\n        \"SHA1_HEX\": exp.SHA.from_arg_list,\n        \"SHA2_BINARY\": exp.SHA2Digest.from_arg_list,\n        \"SHA2_HEX\": exp.SHA2.from_arg_list,\n        \"SPLIT\": lambda args: exp.Split(\n            this=seq_get(args, 0),\n            expression=seq_get(args, 1),\n            null_returns_null=True,\n            empty_delimiter_returns_whole=True,\n        ),\n        \"SQUARE\": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)),\n        \"STDDEV_SAMP\": exp.Stddev.from_arg_list,\n        \"SYSDATE\": lambda args: exp.CurrentTimestamp(this=seq_get(args, 0), sysdate=True),\n        \"TABLE\": lambda args: exp.TableFromRows(this=seq_get(args, 0)),\n        \"TIMEADD\": lambda args: exp.TimeAdd(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=map_date_part(seq_get(args, 0)),\n        ),\n        \"TIMEDIFF\": lambda args: exp.DateDiff(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=map_date_part(seq_get(args, 0)),\n            date_part_boundary=True,\n        ),\n        \"TIME_FROM_PARTS\": lambda args: exp.TimeFromParts(\n            hour=seq_get(args, 0),\n            min=seq_get(args, 1),\n            sec=seq_get(args, 2),\n            nano=seq_get(args, 3),\n            overflow=True,\n        ),\n        \"TIMESTAMPADD\": lambda args: exp.DateAdd(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=map_date_part(seq_get(args, 0)),\n        ),\n        \"TIMESTAMPDIFF\": lambda args: exp.DateDiff(\n            this=seq_get(args, 2),\n            expression=seq_get(args, 1),\n            unit=map_date_part(seq_get(args, 0)),\n            date_part_boundary=True,\n        ),\n        \"TIMESTAMPFROMPARTS\": _build_timestamp_from_parts,\n        \"TIMESTAMP_FROM_PARTS\": _build_timestamp_from_parts,\n        \"TIMESTAMPNTZFROMPARTS\": _build_timestamp_from_parts,\n        \"TIMESTAMP_NTZ_FROM_PARTS\": _build_timestamp_from_parts,\n        \"TRUNC\": lambda args, dialect: build_trunc(args, dialect, date_trunc_requires_part=False),\n        \"TRUNCATE\": lambda args, dialect: build_trunc(\n            args, dialect, date_trunc_requires_part=False\n        ),\n        \"TRY_DECRYPT\": lambda args: exp.Decrypt(\n            this=seq_get(args, 0),\n            passphrase=seq_get(args, 1),\n            aad=seq_get(args, 2),\n            encryption_method=seq_get(args, 3),\n            safe=True,\n        ),\n        \"TRY_DECRYPT_RAW\": lambda args: exp.DecryptRaw(\n            this=seq_get(args, 0),\n            key=seq_get(args, 1),\n            iv=seq_get(args, 2),\n            aad=seq_get(args, 3),\n            encryption_method=seq_get(args, 4),\n            aead=seq_get(args, 5),\n            safe=True,\n        ),\n        \"TRY_PARSE_JSON\": lambda args: exp.ParseJSON(this=seq_get(args, 0), safe=True),\n        \"TRY_TO_BINARY\": lambda args: exp.ToBinary(\n            this=seq_get(args, 0), format=seq_get(args, 1), safe=True\n        ),\n        \"TRY_TO_BOOLEAN\": lambda args: exp.ToBoolean(this=seq_get(args, 0), safe=True),\n        \"TRY_TO_DATE\": _build_datetime(\"TRY_TO_DATE\", exp.DType.DATE, safe=True),\n        **dict.fromkeys(\n            (\"TRY_TO_DECIMAL\", \"TRY_TO_NUMBER\", \"TRY_TO_NUMERIC\"), _build_try_to_number\n        ),\n        \"TRY_TO_DOUBLE\": lambda args: exp.ToDouble(\n            this=seq_get(args, 0), format=seq_get(args, 1), safe=True\n        ),\n        \"TRY_TO_FILE\": lambda args: exp.ToFile(\n            this=seq_get(args, 0), path=seq_get(args, 1), safe=True\n        ),\n        \"TRY_TO_TIME\": _build_datetime(\"TRY_TO_TIME\", exp.DType.TIME, safe=True),\n        \"TRY_TO_TIMESTAMP\": _build_datetime(\"TRY_TO_TIMESTAMP\", exp.DType.TIMESTAMP, safe=True),\n        \"TRY_TO_TIMESTAMP_LTZ\": _build_datetime(\n            \"TRY_TO_TIMESTAMP_LTZ\", exp.DType.TIMESTAMPLTZ, safe=True\n        ),\n        \"TRY_TO_TIMESTAMP_NTZ\": _build_datetime(\n            \"TRY_TO_TIMESTAMP_NTZ\", exp.DType.TIMESTAMPNTZ, safe=True\n        ),\n        \"TRY_TO_TIMESTAMP_TZ\": _build_datetime(\n            \"TRY_TO_TIMESTAMP_TZ\", exp.DType.TIMESTAMPTZ, safe=True\n        ),\n        \"TO_CHAR\": build_timetostr_or_tochar,\n        \"TO_DATE\": _build_datetime(\"TO_DATE\", exp.DType.DATE),\n        **dict.fromkeys(\n            (\"TO_DECIMAL\", \"TO_NUMBER\", \"TO_NUMERIC\"),\n            lambda args: exp.ToNumber(\n                this=seq_get(args, 0),\n                format=seq_get(args, 1),\n                precision=seq_get(args, 2),\n                scale=seq_get(args, 3),\n            ),\n        ),\n        \"TO_TIME\": _build_datetime(\"TO_TIME\", exp.DType.TIME),\n        \"TO_TIMESTAMP\": _build_datetime(\"TO_TIMESTAMP\", exp.DType.TIMESTAMP),\n        \"TO_TIMESTAMP_LTZ\": _build_datetime(\"TO_TIMESTAMP_LTZ\", exp.DType.TIMESTAMPLTZ),\n        \"TO_TIMESTAMP_NTZ\": _build_datetime(\"TO_TIMESTAMP_NTZ\", exp.DType.TIMESTAMPNTZ),\n        \"TO_TIMESTAMP_TZ\": _build_datetime(\"TO_TIMESTAMP_TZ\", exp.DType.TIMESTAMPTZ),\n        \"TO_GEOGRAPHY\": lambda args: (\n            exp.cast(args[0], exp.DType.GEOGRAPHY)\n            if len(args) == 1\n            else exp.Anonymous(this=\"TO_GEOGRAPHY\", expressions=args)\n        ),\n        \"TO_GEOMETRY\": lambda args: (\n            exp.cast(args[0], exp.DType.GEOMETRY)\n            if len(args) == 1\n            else exp.Anonymous(this=\"TO_GEOMETRY\", expressions=args)\n        ),\n        \"TO_VARCHAR\": build_timetostr_or_tochar,\n        \"TO_JSON\": exp.JSONFormat.from_arg_list,\n        \"VECTOR_COSINE_SIMILARITY\": exp.CosineDistance.from_arg_list,\n        \"VECTOR_INNER_PRODUCT\": exp.DotProduct.from_arg_list,\n        \"VECTOR_L1_DISTANCE\": exp.ManhattanDistance.from_arg_list,\n        \"VECTOR_L2_DISTANCE\": exp.EuclideanDistance.from_arg_list,\n        \"ZEROIFNULL\": _build_if_from_zeroifnull,\n        \"LIKE\": build_like(exp.Like),\n        \"ILIKE\": build_like(exp.ILike),\n        \"SEARCH\": _build_search,\n        \"SKEW\": exp.Skewness.from_arg_list,\n        \"SPLIT_PART\": lambda args: exp.SplitPart(\n            this=seq_get(args, 0),\n            delimiter=seq_get(args, 1),\n            part_index=seq_get(args, 2),\n            part_index_zero_as_one=True,\n            empty_delimiter_returns_whole=True,\n        ),\n        \"STRTOK\": lambda args: exp.Strtok(\n            this=seq_get(args, 0),\n            delimiter=seq_get(args, 1) or exp.Literal.string(\" \"),\n            part_index=seq_get(args, 2) or exp.Literal.number(\"1\"),\n        ),\n        \"SYSTIMESTAMP\": exp.CurrentTimestamp.from_arg_list,\n        \"WEEKISO\": exp.WeekOfYear.from_arg_list,\n        \"WEEKOFYEAR\": exp.Week.from_arg_list,\n    }\n    FUNCTIONS = {k: v for k, v in FUNCTIONS.items() if k != \"PREDICT\"}\n\n    FUNCTION_PARSERS = {\n        **parser.Parser.FUNCTION_PARSERS,\n        \"DATE_PART\": lambda self: self._parse_date_part(),\n        \"DIRECTORY\": lambda self: self._parse_directory(),\n        \"OBJECT_CONSTRUCT_KEEP_NULL\": lambda self: self._parse_json_object(),\n        \"LISTAGG\": lambda self: self._parse_string_agg(),\n        \"SEMANTIC_VIEW\": lambda self: self._parse_semantic_view(),\n    }\n    FUNCTION_PARSERS = {k: v for k, v in FUNCTION_PARSERS.items() if k != \"TRIM\"}\n\n    TIMESTAMPS = parser.Parser.TIMESTAMPS - {TokenType.TIME}\n\n    ALTER_PARSERS = {\n        **parser.Parser.ALTER_PARSERS,\n        \"MODIFY\": lambda self: self._parse_alter_table_alter(),\n        \"SESSION\": lambda self: self._parse_alter_session(),\n        \"UNSET\": lambda self: self.expression(\n            exp.Set(\n                tag=self._match_text_seq(\"TAG\"),\n                expressions=self._parse_csv(self._parse_id_var),\n                unset=True,\n            )\n        ),\n    }\n\n    STATEMENT_PARSERS = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.GET: lambda self: self._parse_get(),\n        TokenType.PUT: lambda self: self._parse_put(),\n        TokenType.SHOW: lambda self: self._parse_show(),\n    }\n\n    PROPERTY_PARSERS = {\n        **parser.Parser.PROPERTY_PARSERS,\n        \"CREDENTIALS\": lambda self: self._parse_credentials_property(),\n        \"FILE_FORMAT\": lambda self: self._parse_file_format_property(),\n        \"LOCATION\": lambda self: self._parse_location_property(),\n        \"TAG\": lambda self: self._parse_tag(),\n        \"USING\": lambda self: (\n            self._match_text_seq(\"TEMPLATE\")\n            and self.expression(exp.UsingTemplateProperty(this=self._parse_statement()))\n        ),\n    }\n\n    DESCRIBE_QUALIFIER_PARSERS: t.ClassVar[t.Dict[str, t.Callable]] = {\n        \"API\": lambda self: self.expression(exp.ApiProperty()),\n        \"APPLICATION\": lambda self: self.expression(exp.ApplicationProperty()),\n        \"CATALOG\": lambda self: self.expression(exp.CatalogProperty()),\n        \"COMPUTE\": lambda self: self.expression(exp.ComputeProperty()),\n        \"DATABASE\": lambda self: (\n            self.expression(exp.DatabaseProperty())\n            if self._curr and self._curr.text.upper() == \"ROLE\"\n            else None\n        ),\n        \"DYNAMIC\": lambda self: self.expression(exp.DynamicProperty()),\n        \"EXTERNAL\": lambda self: self.expression(exp.ExternalProperty()),\n        \"HYBRID\": lambda self: self.expression(exp.HybridProperty()),\n        \"ICEBERG\": lambda self: self.expression(exp.IcebergProperty()),\n        \"MASKING\": lambda self: self.expression(exp.MaskingProperty()),\n        \"MATERIALIZED\": lambda self: self.expression(exp.MaterializedProperty()),\n        \"NETWORK\": lambda self: self.expression(exp.NetworkProperty()),\n        \"ROW\": lambda self: (\n            self.expression(exp.RowAccessProperty()) if self._match_text_seq(\"ACCESS\") else None\n        ),\n        \"SECURITY\": lambda self: (\n            self.expression(exp.SecurityIntegrationProperty())\n            if self._curr and self._curr.text.upper() == \"INTEGRATION\"\n            else None\n        ),\n    }\n\n    TYPE_CONVERTERS = {\n        # https://docs.snowflake.com/en/sql-reference/data-types-numeric#number\n        exp.DType.DECIMAL: build_default_decimal_type(precision=38, scale=0),\n    }\n\n    SHOW_PARSERS = {\n        \"DATABASES\": _show_parser(\"DATABASES\"),\n        \"TERSE DATABASES\": _show_parser(\"DATABASES\"),\n        \"SCHEMAS\": _show_parser(\"SCHEMAS\"),\n        \"TERSE SCHEMAS\": _show_parser(\"SCHEMAS\"),\n        \"OBJECTS\": _show_parser(\"OBJECTS\"),\n        \"TERSE OBJECTS\": _show_parser(\"OBJECTS\"),\n        \"TABLES\": _show_parser(\"TABLES\"),\n        \"TERSE TABLES\": _show_parser(\"TABLES\"),\n        \"VIEWS\": _show_parser(\"VIEWS\"),\n        \"TERSE VIEWS\": _show_parser(\"VIEWS\"),\n        \"PRIMARY KEYS\": _show_parser(\"PRIMARY KEYS\"),\n        \"TERSE PRIMARY KEYS\": _show_parser(\"PRIMARY KEYS\"),\n        \"IMPORTED KEYS\": _show_parser(\"IMPORTED KEYS\"),\n        \"TERSE IMPORTED KEYS\": _show_parser(\"IMPORTED KEYS\"),\n        \"UNIQUE KEYS\": _show_parser(\"UNIQUE KEYS\"),\n        \"TERSE UNIQUE KEYS\": _show_parser(\"UNIQUE KEYS\"),\n        \"SEQUENCES\": _show_parser(\"SEQUENCES\"),\n        \"TERSE SEQUENCES\": _show_parser(\"SEQUENCES\"),\n        \"STAGES\": _show_parser(\"STAGES\"),\n        \"COLUMNS\": _show_parser(\"COLUMNS\"),\n        \"USERS\": _show_parser(\"USERS\"),\n        \"TERSE USERS\": _show_parser(\"USERS\"),\n        \"FILE FORMATS\": _show_parser(\"FILE FORMATS\"),\n        \"FUNCTIONS\": _show_parser(\"FUNCTIONS\"),\n        \"PROCEDURES\": _show_parser(\"PROCEDURES\"),\n        \"WAREHOUSES\": _show_parser(\"WAREHOUSES\"),\n    }\n\n    SHOW_TRIE = new_trie(key.split(\" \") for key in SHOW_PARSERS)\n\n    CONSTRAINT_PARSERS = {\n        **parser.Parser.CONSTRAINT_PARSERS,\n        \"WITH\": lambda self: self._parse_with_constraint(),\n        \"MASKING\": lambda self: self._parse_with_constraint(),\n        \"PROJECTION\": lambda self: self._parse_with_constraint(),\n        \"TAG\": lambda self: self._parse_with_constraint(),\n    }\n\n    STAGED_FILE_SINGLE_TOKENS = {\n        TokenType.DOT,\n        TokenType.MOD,\n        TokenType.SLASH,\n    }\n\n    FLATTEN_COLUMNS = [\"SEQ\", \"KEY\", \"PATH\", \"INDEX\", \"VALUE\", \"THIS\"]\n\n    SCHEMA_KINDS = {\"OBJECTS\", \"TABLES\", \"VIEWS\", \"SEQUENCES\", \"UNIQUE KEYS\", \"IMPORTED KEYS\"}\n\n    NON_TABLE_CREATABLES = {\"STORAGE INTEGRATION\", \"TAG\", \"WAREHOUSE\", \"STREAMLIT\"}\n\n    CREATABLES = {\n        *parser.Parser.CREATABLES,\n        TokenType.INTEGRATION,\n        TokenType.PACKAGE,\n        TokenType.POLICY,\n        TokenType.POOL,\n        TokenType.ROLE,\n        TokenType.RULE,\n        TokenType.VOLUME,\n    }\n\n    LAMBDAS = {\n        **parser.Parser.LAMBDAS,\n        TokenType.ARROW: lambda self, expressions: self.expression(\n            exp.Lambda(\n                this=self._replace_lambda(\n                    self._parse_assignment(),\n                    expressions,\n                ),\n                expressions=[e.this if isinstance(e, exp.Cast) else e for e in expressions],\n            )\n        ),\n    }\n\n    COLUMN_OPERATORS = {\n        **parser.Parser.COLUMN_OPERATORS,\n        TokenType.EXCLAMATION: lambda self, this, attr: self.expression(\n            exp.ModelAttribute(this=this, expression=attr)\n        ),\n    }\n\n    def _parse_directory(self) -> exp.DirectoryStage:\n        table = self._parse_table_parts()\n        this = table.this if isinstance(table, exp.Table) else table\n        return self.expression(exp.DirectoryStage(this=this))\n\n    def _parse_describe(self) -> exp.Describe:\n        index = self._index\n\n        if self._match_texts(self.DESCRIBE_QUALIFIER_PARSERS):\n            qualifier = self.DESCRIBE_QUALIFIER_PARSERS[self._prev.text.upper()](self)\n\n            if qualifier:\n                kind = self._match_set(self.CREATABLES) and self._prev.text.upper()\n\n                if kind:\n                    this = self._parse_table(schema=True)\n                    properties = self.expression(exp.Properties(expressions=[qualifier]))\n                    post_props = self._parse_properties()\n                    expressions = post_props.expressions if post_props else None\n                    return self.expression(\n                        exp.Describe(\n                            this=this,\n                            kind=kind,\n                            properties=properties,\n                            expressions=expressions,\n                        )\n                    )\n\n        self._retreat(index)\n        return super()._parse_describe()\n\n    def _parse_use(self) -> exp.Use:\n        if self._match_text_seq(\"SECONDARY\", \"ROLES\"):\n            this = self._match_texts((\"ALL\", \"NONE\")) and exp.var(self._prev.text.upper())\n            roles = None if this else self._parse_csv(lambda: self._parse_table(schema=False))\n            return self.expression(exp.Use(kind=\"SECONDARY ROLES\", this=this, expressions=roles))\n\n        return super()._parse_use()\n\n    def _negate_range(self, this: t.Optional[exp.Expr] = None) -> t.Optional[exp.Expr]:\n        if not this:\n            return this\n\n        query = this.args.get(\"query\")\n        if isinstance(this, exp.In) and isinstance(query, exp.Query):\n            # Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`, so\n            # we do this conversion here to avoid parsing it into `NOT value IN (subquery)`\n            # which can produce different results (most likely a SnowFlake bug).\n            #\n            # https://docs.snowflake.com/en/sql-reference/functions/in\n            # Context: https://github.com/tobymao/sqlglot/issues/3890\n            return self.expression(exp.NEQ(this=this.this, expression=exp.All(this=query.unnest())))\n\n        return self.expression(exp.Not(this=this))\n\n    def _parse_tag(self) -> exp.Tags:\n        return self.expression(exp.Tags(expressions=self._parse_wrapped_csv(self._parse_property)))\n\n    def _parse_with_constraint(self) -> t.Optional[exp.Expr]:\n        if self._prev.token_type != TokenType.WITH:\n            self._retreat(self._index - 1)\n\n        if self._match_text_seq(\"MASKING\", \"POLICY\"):\n            policy = self._parse_column()\n            return self.expression(\n                exp.MaskingPolicyColumnConstraint(\n                    this=policy.to_dot() if isinstance(policy, exp.Column) else policy,\n                    expressions=self._match(TokenType.USING)\n                    and self._parse_wrapped_csv(self._parse_id_var),\n                )\n            )\n        if self._match_text_seq(\"PROJECTION\", \"POLICY\"):\n            policy = self._parse_column()\n            return self.expression(\n                exp.ProjectionPolicyColumnConstraint(\n                    this=policy.to_dot() if isinstance(policy, exp.Column) else policy\n                )\n            )\n        if self._match(TokenType.TAG):\n            return self._parse_tag()\n\n        return None\n\n    def _parse_with_property(self) -> t.Optional[exp.Expr] | t.List[exp.Expr]:\n        if self._match(TokenType.TAG):\n            return self._parse_tag()\n\n        return super()._parse_with_property()\n\n    def _parse_create(self) -> exp.Create | exp.Command:\n        expression = super()._parse_create()\n        if isinstance(expression, exp.Create) and expression.kind in self.NON_TABLE_CREATABLES:\n            # Replace the Table node with the enclosed Identifier\n            expression.this.replace(expression.this.this)\n\n        return expression\n\n    # https://docs.snowflake.com/en/sql-reference/functions/date_part.html\n    # https://docs.snowflake.com/en/sql-reference/functions-date-time.html#label-supported-date-time-parts\n    def _parse_date_part(self) -> t.Optional[exp.Expr]:\n        this = self._parse_var() or self._parse_type()\n\n        if not this:\n            return None\n\n        # Handle both syntaxes: DATE_PART(part, expr) and DATE_PART(part FROM expr)\n        expression = self._match_set((TokenType.FROM, TokenType.COMMA)) and self._parse_bitwise()\n        return self.expression(\n            exp.Extract(this=map_date_part(this, self.dialect), expression=expression)\n        )\n\n    def _parse_bracket_key_value(self, is_map: bool = False) -> t.Optional[exp.Expr]:\n        if is_map:\n            # Keys are strings in Snowflake's objects, see also:\n            # - https://docs.snowflake.com/en/sql-reference/data-types-semistructured\n            # - https://docs.snowflake.com/en/sql-reference/functions/object_construct\n            return self._parse_slice(self._parse_string()) or self._parse_assignment()\n\n        return self._parse_slice(self._parse_alias(self._parse_assignment(), explicit=True))\n\n    def _parse_lateral(self) -> t.Optional[exp.Lateral]:\n        lateral = super()._parse_lateral()\n        if not lateral:\n            return lateral\n\n        if isinstance(lateral.this, exp.Explode):\n            table_alias = lateral.args.get(\"alias\")\n            columns = [exp.to_identifier(col) for col in self.FLATTEN_COLUMNS]\n            if table_alias and not table_alias.args.get(\"columns\"):\n                table_alias.set(\"columns\", columns)\n            elif not table_alias:\n                exp.alias_(lateral, \"_flattened\", table=columns, copy=False)\n\n        return lateral\n\n    def _parse_table_parts(\n        self,\n        schema: bool = False,\n        is_db_reference: bool = False,\n        wildcard: bool = False,\n        fast: bool = False,\n    ) -> t.Optional[exp.Table | exp.Dot]:\n        # https://docs.snowflake.com/en/user-guide/querying-stage\n        if self._match(TokenType.STRING, advance=False):\n            table = self._parse_string()\n        elif self._match_text_seq(\"@\", advance=False):\n            table = self._parse_location_path()\n        else:\n            table = None\n\n        if table:\n            file_format = None\n            pattern = None\n\n            wrapped = self._match(TokenType.L_PAREN)\n            while self._curr and wrapped and not self._match(TokenType.R_PAREN):\n                if self._match_text_seq(\"FILE_FORMAT\", \"=>\"):\n                    file_format = self._parse_string() or super()._parse_table_parts(\n                        is_db_reference=is_db_reference\n                    )\n                elif self._match_text_seq(\"PATTERN\", \"=>\"):\n                    pattern = self._parse_string()\n                else:\n                    break\n\n                self._match(TokenType.COMMA)\n\n            table = self.expression(exp.Table(this=table, format=file_format, pattern=pattern))\n        else:\n            table = super()._parse_table_parts(\n                schema=schema,\n                is_db_reference=is_db_reference,\n                fast=fast,\n            )\n\n        return table\n\n    def _parse_table(\n        self,\n        schema: bool = False,\n        joins: bool = False,\n        alias_tokens: t.Optional[Collection[TokenType]] = None,\n        parse_bracket: bool = False,\n        is_db_reference: bool = False,\n        parse_partition: bool = False,\n        consume_pipe: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        table = super()._parse_table(\n            schema=schema,\n            joins=joins,\n            alias_tokens=alias_tokens,\n            parse_bracket=parse_bracket,\n            is_db_reference=is_db_reference,\n            parse_partition=parse_partition,\n        )\n        if isinstance(table, exp.Table) and isinstance(table.this, exp.TableFromRows):\n            table_from_rows = table.this\n            for arg in exp.TableFromRows.arg_types:\n                if arg != \"this\":\n                    table_from_rows.set(arg, table.args.get(arg))\n\n            table = table_from_rows\n\n        return table\n\n    def _parse_id_var(\n        self,\n        any_token: bool = True,\n        tokens: t.Optional[Collection[TokenType]] = None,\n    ) -> t.Optional[exp.Expr]:\n        if self._match_text_seq(\"IDENTIFIER\", \"(\"):\n            identifier = (\n                super()._parse_id_var(any_token=any_token, tokens=tokens) or self._parse_string()\n            )\n            self._match_r_paren()\n            return self.expression(exp.Anonymous(this=\"IDENTIFIER\", expressions=[identifier]))\n\n        return super()._parse_id_var(any_token=any_token, tokens=tokens)\n\n    def _parse_show_snowflake(self, this: str) -> exp.Show:\n        scope = None\n        scope_kind = None\n\n        # will identity SHOW TERSE SCHEMAS but not SHOW TERSE PRIMARY KEYS\n        # which is syntactically valid but has no effect on the output\n        terse = self._tokens[self._index - 2].text.upper() == \"TERSE\"\n\n        history = self._match_text_seq(\"HISTORY\")\n\n        like = self._parse_string() if self._match(TokenType.LIKE) else None\n\n        if self._match(TokenType.IN):\n            if self._match_text_seq(\"ACCOUNT\"):\n                scope_kind = \"ACCOUNT\"\n            elif self._match_text_seq(\"CLASS\"):\n                scope_kind = \"CLASS\"\n                scope = self._parse_table_parts()\n            elif self._match_text_seq(\"APPLICATION\"):\n                scope_kind = \"APPLICATION\"\n                if self._match_text_seq(\"PACKAGE\"):\n                    scope_kind += \" PACKAGE\"\n                scope = self._parse_table_parts()\n            elif self._match_set(self.DB_CREATABLES):\n                scope_kind = self._prev.text.upper()\n                if self._curr:\n                    scope = self._parse_table_parts()\n            elif self._curr:\n                scope_kind = \"SCHEMA\" if this in self.SCHEMA_KINDS else \"TABLE\"\n                scope = self._parse_table_parts()\n\n        return self.expression(\n            exp.Show(\n                terse=terse,\n                this=this,\n                history=history,\n                like=like,\n                scope=scope,\n                scope_kind=scope_kind,\n                starts_with=self._match_text_seq(\"STARTS\", \"WITH\") and self._parse_string(),\n                limit=self._parse_limit(),\n                from_=self._parse_string() if self._match(TokenType.FROM) else None,\n                privileges=self._match_text_seq(\"WITH\", \"PRIVILEGES\")\n                and self._parse_csv(lambda: self._parse_var(any_token=True, upper=True)),\n            )\n        )\n\n    def _parse_put(self) -> exp.Put | exp.Command:\n        if self._curr.token_type != TokenType.STRING:\n            return self._parse_as_command(self._prev)\n\n        return self.expression(\n            exp.Put(\n                this=self._parse_string(),\n                target=self._parse_location_path(),\n                properties=self._parse_properties(),\n            )\n        )\n\n    def _parse_get(self) -> t.Optional[exp.Expr]:\n        start = self._prev\n\n        # If we detect GET( then we need to parse a function, not a statement\n        if self._match(TokenType.L_PAREN):\n            self._retreat(self._index - 2)\n            return self._parse_expression()\n\n        target = self._parse_location_path()\n\n        # Parse as command if unquoted file path\n        if self._curr.token_type == TokenType.URI_START:\n            return self._parse_as_command(start)\n\n        return self.expression(\n            exp.Get(this=self._parse_string(), target=target, properties=self._parse_properties())\n        )\n\n    def _parse_location_property(self) -> exp.LocationProperty:\n        self._match(TokenType.EQ)\n        return self.expression(exp.LocationProperty(this=self._parse_location_path()))\n\n    def _parse_file_location(self) -> t.Optional[exp.Expr]:\n        # Parse either a subquery or a staged file\n        return (\n            self._parse_select(table=True, parse_subquery_alias=False)\n            if self._match(TokenType.L_PAREN, advance=False)\n            else self._parse_table_parts()\n        )\n\n    def _parse_location_path(self) -> exp.Var:\n        start = self._curr\n        self._advance_any(ignore_reserved=True)\n\n        # We avoid consuming a comma token because external tables like @foo and @bar\n        # can be joined in a query with a comma separator, as well as closing paren\n        # in case of subqueries\n        while self._is_connected() and not self._match_set(\n            (TokenType.COMMA, TokenType.L_PAREN, TokenType.R_PAREN), advance=False\n        ):\n            self._advance_any(ignore_reserved=True)\n\n        return exp.var(self._find_sql(start, self._prev))\n\n    def _parse_lambda_arg(self) -> t.Optional[exp.Expr]:\n        this = super()._parse_lambda_arg()\n\n        if not this:\n            return this\n\n        typ = self._parse_types()\n\n        if typ:\n            return self.expression(exp.Cast(this=this, to=typ))\n\n        return this\n\n    def _parse_foreign_key(self) -> exp.ForeignKey:\n        # inlineFK, the REFERENCES columns are implied\n        if self._match(TokenType.REFERENCES, advance=False):\n            return self.expression(exp.ForeignKey())\n\n        # outoflineFK, explicitly names the columns\n        return super()._parse_foreign_key()\n\n    def _parse_file_format_property(self) -> exp.FileFormatProperty:\n        self._match(TokenType.EQ)\n        if self._match(TokenType.L_PAREN, advance=False):\n            expressions = self._parse_wrapped_options()\n        else:\n            expressions = [self._parse_format_name()]\n\n        return self.expression(exp.FileFormatProperty(expressions=expressions))\n\n    def _parse_credentials_property(self) -> exp.CredentialsProperty:\n        return self.expression(exp.CredentialsProperty(expressions=self._parse_wrapped_options()))\n\n    def _parse_semantic_view(self) -> exp.SemanticView:\n        kwargs: t.Dict[str, t.Any] = {\"this\": self._parse_table_parts()}\n\n        while self._curr and not self._match(TokenType.R_PAREN, advance=False):\n            if self._match_texts((\"DIMENSIONS\", \"METRICS\", \"FACTS\")):\n                keyword = self._prev.text.lower()\n                kwargs[keyword] = self._parse_csv(\n                    lambda: self._parse_alias(self._parse_disjunction(), explicit=True)\n                )\n            elif self._match_text_seq(\"WHERE\"):\n                kwargs[\"where\"] = self._parse_expression()\n            else:\n                self.raise_error(\"Expecting ) or encountered unexpected keyword\")\n                break\n\n        return self.expression(exp.SemanticView(**kwargs))\n\n    def _parse_set(self, unset: bool = False, tag: bool = False) -> exp.Set | exp.Command:\n        set = super()._parse_set(unset=unset, tag=tag)\n\n        if isinstance(set, exp.Set):\n            for expr in set.expressions:\n                if isinstance(expr, exp.SetItem):\n                    expr.set(\"kind\", \"VARIABLE\")\n        return set\n\n    def _parse_window(\n        self, this: t.Optional[exp.Expr], alias: bool = False\n    ) -> t.Optional[exp.Expr]:\n        if isinstance(this, exp.NthValue):\n            if self._match_text_seq(\"FROM\"):\n                if self._match_texts((\"FIRST\", \"LAST\")):\n                    from_first = self._prev.text.upper() == \"FIRST\"\n                    this.set(\"from_first\", from_first)\n\n        result = super()._parse_window(this, alias)\n\n        # Set default window frame for ranking functions if not present\n        if (\n            isinstance(result, exp.Window)\n            and isinstance(this, RANKING_WINDOW_FUNCTIONS_WITH_FRAME)\n            and not result.args.get(\"spec\")\n        ):\n            frame = exp.WindowSpec(\n                kind=\"ROWS\",\n                start=\"UNBOUNDED\",\n                start_side=\"PRECEDING\",\n                end=\"UNBOUNDED\",\n                end_side=\"FOLLOWING\",\n            )\n            result.set(\"spec\", frame)\n        return result\n\n\n# This is imported and used by both the parser (above) and the generator in the dialect file\nRANKING_WINDOW_FUNCTIONS_WITH_FRAME = (\n    exp.FirstValue,\n    exp.LastValue,\n    exp.NthValue,\n)\n\n\ndef build_object_construct(args: t.List) -> t.Union[exp.StarMap, exp.Struct]:\n    expression = parser.build_var_map(args)\n\n    if isinstance(expression, exp.StarMap):\n        return expression\n\n    return exp.Struct(\n        expressions=[\n            exp.PropertyEQ(this=k, expression=v) for k, v in zip(expression.keys, expression.values)\n        ]\n    )\n"
  },
  {
    "path": "sqlglot/parsers/solr.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp, parser\nfrom sqlglot.tokens import TokenType\n\n\nclass SolrParser(parser.Parser):\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {\n        TokenType.ANTI,\n        TokenType.SEMI,\n    }\n\n    DISJUNCTION = {\n        **parser.Parser.DISJUNCTION,\n        TokenType.DPIPE: exp.Or,\n    }\n"
  },
  {
    "path": "sqlglot/parsers/spark.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.trie import new_trie\nfrom sqlglot.dialects.dialect import build_date_delta, build_like\nfrom sqlglot.helper import ensure_list, seq_get\nfrom sqlglot.parsers.hive import build_with_ignore_nulls\nfrom sqlglot.parsers.spark2 import Spark2Parser, build_as_cast\nfrom sqlglot.tokens import TokenType\n\n\ndef _build_datediff(args: t.List) -> exp.Expr:\n    \"\"\"\n    Although Spark docs don't mention the \"unit\" argument, Spark3 added support for\n    it at some point. Databricks also supports this variant (see below).\n\n    For example, in spark-sql (v3.3.1):\n    - SELECT DATEDIFF('2020-01-01', '2020-01-05') results in -4\n    - SELECT DATEDIFF(day, '2020-01-01', '2020-01-05') results in 4\n\n    See also:\n    - https://docs.databricks.com/sql/language-manual/functions/datediff3.html\n    - https://docs.databricks.com/sql/language-manual/functions/datediff.html\n    \"\"\"\n    unit = None\n    this = seq_get(args, 0)\n    expression = seq_get(args, 1)\n\n    if len(args) == 3:\n        unit = exp.var(t.cast(exp.Expr, this).name)\n        this = args[2]\n\n    return exp.DateDiff(\n        this=exp.TsOrDsToDate(this=this), expression=exp.TsOrDsToDate(this=expression), unit=unit\n    )\n\n\ndef _build_dateadd(args: t.List) -> exp.Expr:\n    expression = seq_get(args, 1)\n\n    if len(args) == 2:\n        # DATE_ADD(startDate, numDays INTEGER)\n        # https://docs.databricks.com/en/sql/language-manual/functions/date_add.html\n        return exp.TsOrDsAdd(\n            this=seq_get(args, 0), expression=expression, unit=exp.Literal.string(\"DAY\")\n        )\n\n    # DATE_ADD / DATEADD / TIMESTAMPADD(unit, value integer, expr)\n    # https://docs.databricks.com/en/sql/language-manual/functions/date_add3.html\n    return exp.TimestampAdd(this=seq_get(args, 2), expression=expression, unit=seq_get(args, 0))\n\n\nclass SparkParser(Spark2Parser):\n    NO_PAREN_FUNCTIONS = {\n        **Spark2Parser.NO_PAREN_FUNCTIONS,\n        TokenType.SESSION_USER: exp.SessionUser,\n    }\n\n    SET_PARSERS = {\n        **Spark2Parser.SET_PARSERS,\n        \"VAR\": lambda self: self._parse_set_item_assignment(\"VARIABLE\"),\n        \"VARIABLE\": lambda self: self._parse_set_item_assignment(\"VARIABLE\"),\n    }\n\n    SET_TRIE = new_trie(key.split(\" \") for key in SET_PARSERS)\n\n    FUNCTIONS = {\n        **Spark2Parser.FUNCTIONS,\n        \"ANY_VALUE\": build_with_ignore_nulls(exp.AnyValue),\n        \"ARRAY_INSERT\": lambda args: exp.ArrayInsert(\n            this=seq_get(args, 0),\n            position=seq_get(args, 1),\n            expression=seq_get(args, 2),\n            offset=1,\n        ),\n        \"BIT_AND\": exp.BitwiseAndAgg.from_arg_list,\n        \"BIT_GET\": exp.Getbit.from_arg_list,\n        \"BIT_OR\": exp.BitwiseOrAgg.from_arg_list,\n        \"BIT_XOR\": exp.BitwiseXorAgg.from_arg_list,\n        \"BIT_COUNT\": exp.BitwiseCount.from_arg_list,\n        \"CURDATE\": exp.CurrentDate.from_arg_list,\n        \"DATE_ADD\": _build_dateadd,\n        \"DATEADD\": _build_dateadd,\n        \"MAKE_TIMESTAMP\": exp.TimestampFromParts.from_arg_list,\n        \"TIMESTAMPADD\": _build_dateadd,\n        \"TIMESTAMPDIFF\": build_date_delta(exp.TimestampDiff),\n        \"TRY_ADD\": exp.SafeAdd.from_arg_list,\n        \"TRY_MULTIPLY\": exp.SafeMultiply.from_arg_list,\n        \"TRY_SUBTRACT\": exp.SafeSubtract.from_arg_list,\n        \"DATEDIFF\": _build_datediff,\n        \"DATE_DIFF\": _build_datediff,\n        \"JSON_OBJECT_KEYS\": exp.JSONKeys.from_arg_list,\n        \"LISTAGG\": exp.GroupConcat.from_arg_list,\n        \"TIMESTAMP_LTZ\": build_as_cast(\"TIMESTAMP_LTZ\"),\n        \"TIMESTAMP_NTZ\": build_as_cast(\"TIMESTAMP_NTZ\"),\n        \"TRY_ELEMENT_AT\": lambda args: exp.Bracket(\n            this=seq_get(args, 0),\n            expressions=ensure_list(seq_get(args, 1)),\n            offset=1,\n            safe=True,\n        ),\n        \"LIKE\": build_like(exp.Like),\n        \"ILIKE\": build_like(exp.ILike),\n    }\n\n    PLACEHOLDER_PARSERS = {\n        **Spark2Parser.PLACEHOLDER_PARSERS,\n        TokenType.L_BRACE: lambda self: self._parse_query_parameter(),\n    }\n\n    def _parse_query_parameter(self) -> t.Optional[exp.Expr]:\n        this = self._parse_id_var()\n        self._match(TokenType.R_BRACE)\n        return self.expression(exp.Placeholder(this=this, widget=True))\n\n    FUNCTION_PARSERS = {\n        **Spark2Parser.FUNCTION_PARSERS,\n        \"SUBSTR\": lambda self: self._parse_substring(),\n    }\n\n    STATEMENT_PARSERS = {\n        **Spark2Parser.STATEMENT_PARSERS,\n        TokenType.DECLARE: lambda self: self._parse_declare(),\n    }\n\n    def _parse_generated_as_identity(\n        self,\n    ) -> (\n        exp.GeneratedAsIdentityColumnConstraint\n        | exp.ComputedColumnConstraint\n        | exp.GeneratedAsRowColumnConstraint\n    ):\n        this = super()._parse_generated_as_identity()\n        if this.expression:\n            return self.expression(exp.ComputedColumnConstraint(this=this.expression))\n        return this\n\n    def _parse_pivot_aggregation(self) -> t.Optional[exp.Expr]:\n        # Spark 3+ and Databricks support non aggregate functions in PIVOT too, e.g\n        # PIVOT (..., 'foo' AS bar FOR col_to_pivot IN (...))\n        aggregate_expr = self._parse_function() or self._parse_disjunction()\n        return self._parse_alias(aggregate_expr)\n"
  },
  {
    "path": "sqlglot/parsers/spark2.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import (\n    binary_from_function,\n    build_formatted_time,\n    pivot_column_names,\n)\nfrom sqlglot.helper import ensure_list, seq_get\nfrom sqlglot.parsers.hive import HiveParser\nfrom sqlglot.parser import build_trim\n\n\ndef build_as_cast(to_type: str) -> t.Callable[[t.List], exp.Expr]:\n    return lambda args: exp.Cast(this=seq_get(args, 0), to=exp.DataType.build(to_type))\n\n\nclass Spark2Parser(HiveParser):\n    TRIM_PATTERN_FIRST = True\n    CHANGE_COLUMN_ALTER_SYNTAX = True\n\n    FUNCTIONS = {\n        **HiveParser.FUNCTIONS,\n        \"AGGREGATE\": exp.Reduce.from_arg_list,\n        \"BOOLEAN\": build_as_cast(\"boolean\"),\n        \"DATE\": build_as_cast(\"date\"),\n        \"DATE_TRUNC\": lambda args: exp.TimestampTrunc(\n            this=seq_get(args, 1), unit=exp.var(seq_get(args, 0))\n        ),\n        \"DAYOFMONTH\": lambda args: exp.DayOfMonth(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"DAYOFWEEK\": lambda args: exp.DayOfWeek(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"DAYOFYEAR\": lambda args: exp.DayOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n        \"DOUBLE\": build_as_cast(\"double\"),\n        \"ELEMENT_AT\": lambda args: exp.Bracket(\n            this=seq_get(args, 0),\n            expressions=ensure_list(seq_get(args, 1)),\n            offset=1,\n            safe=False,\n        ),\n        \"FLOAT\": build_as_cast(\"float\"),\n        \"FORMAT_STRING\": exp.Format.from_arg_list,\n        \"FROM_UTC_TIMESTAMP\": lambda args, dialect: exp.AtTimeZone(\n            this=exp.cast(\n                seq_get(args, 0) or exp.Var(this=\"\"),\n                exp.DType.TIMESTAMP,\n                dialect=dialect,\n            ),\n            zone=seq_get(args, 1),\n        ),\n        \"LTRIM\": lambda args: build_trim(args, reverse_args=True),\n        \"INT\": build_as_cast(\"int\"),\n        \"MAP_FROM_ARRAYS\": exp.Map.from_arg_list,\n        \"RLIKE\": exp.RegexpLike.from_arg_list,\n        \"RTRIM\": lambda args: build_trim(args, is_left=False, reverse_args=True),\n        \"SHIFTLEFT\": binary_from_function(exp.BitwiseLeftShift),\n        \"SHIFTRIGHT\": binary_from_function(exp.BitwiseRightShift),\n        \"STRING\": build_as_cast(\"string\"),\n        \"SLICE\": exp.ArraySlice.from_arg_list,\n        \"TIMESTAMP\": build_as_cast(\"timestamp\"),\n        \"TO_TIMESTAMP\": lambda args: (\n            build_as_cast(\"timestamp\")(args)\n            if len(args) == 1\n            else build_formatted_time(exp.StrToTime, \"spark\")(args)\n        ),\n        \"TO_UNIX_TIMESTAMP\": exp.StrToUnix.from_arg_list,\n        \"TO_UTC_TIMESTAMP\": lambda args, dialect: exp.FromTimeZone(\n            this=exp.cast(\n                seq_get(args, 0) or exp.Var(this=\"\"),\n                exp.DType.TIMESTAMP,\n                dialect=dialect,\n            ),\n            zone=seq_get(args, 1),\n        ),\n        \"TRUNC\": lambda args: exp.DateTrunc(unit=seq_get(args, 1), this=seq_get(args, 0)),\n        \"WEEKOFYEAR\": lambda args: exp.WeekOfYear(this=exp.TsOrDsToDate(this=seq_get(args, 0))),\n    }\n\n    FUNCTION_PARSERS = {\n        **HiveParser.FUNCTION_PARSERS,\n        \"APPROX_PERCENTILE\": lambda self: self._parse_quantile_function(exp.ApproxQuantile),\n        \"BROADCAST\": lambda self: self._parse_join_hint(\"BROADCAST\"),\n        \"BROADCASTJOIN\": lambda self: self._parse_join_hint(\"BROADCASTJOIN\"),\n        \"MAPJOIN\": lambda self: self._parse_join_hint(\"MAPJOIN\"),\n        \"MERGE\": lambda self: self._parse_join_hint(\"MERGE\"),\n        \"SHUFFLEMERGE\": lambda self: self._parse_join_hint(\"SHUFFLEMERGE\"),\n        \"MERGEJOIN\": lambda self: self._parse_join_hint(\"MERGEJOIN\"),\n        \"SHUFFLE_HASH\": lambda self: self._parse_join_hint(\"SHUFFLE_HASH\"),\n        \"SHUFFLE_REPLICATE_NL\": lambda self: self._parse_join_hint(\"SHUFFLE_REPLICATE_NL\"),\n    }\n\n    def _parse_drop_column(self) -> t.Optional[exp.Drop | exp.Command]:\n        return (\n            self.expression(exp.Drop(this=self._parse_schema(), kind=\"COLUMNS\"))\n            if self._match_text_seq(\"DROP\", \"COLUMNS\")\n            else None\n        )\n\n    def _pivot_column_names(self, aggregations: t.List[exp.Expr]) -> t.List[str]:\n        if len(aggregations) == 1:\n            return []\n        return pivot_column_names(aggregations, dialect=\"spark\")\n"
  },
  {
    "path": "sqlglot/parsers/sqlite.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.parser import binary_range_parser\nfrom sqlglot.tokens import TokenType\n\n\ndef _build_strftime(args: t.List) -> exp.Anonymous | exp.TimeToStr:\n    if len(args) == 1:\n        args.append(exp.CurrentTimestamp())\n    if len(args) == 2:\n        return exp.TimeToStr(this=exp.TsOrDsToTimestamp(this=args[1]), format=args[0])\n    return exp.Anonymous(this=\"STRFTIME\", expressions=args)\n\n\nclass SQLiteParser(parser.Parser):\n    STRING_ALIASES = True\n    ALTER_RENAME_REQUIRES_COLUMN = False\n    JOINS_HAVE_EQUAL_PRECEDENCE = True\n    ADD_JOIN_ON_TRUE = True\n\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {\n        TokenType.ANTI,\n        TokenType.SEMI,\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"DATETIME\": lambda args: exp.Anonymous(this=\"DATETIME\", expressions=args),\n        \"EDITDIST3\": exp.Levenshtein.from_arg_list,\n        \"JSON_GROUP_ARRAY\": exp.JSONArrayAgg.from_arg_list,\n        \"JSON_GROUP_OBJECT\": lambda args: exp.JSONObjectAgg(expressions=args),\n        \"STRFTIME\": _build_strftime,\n        \"SQLITE_VERSION\": exp.CurrentVersion.from_arg_list,\n        \"TIME\": lambda args: exp.Anonymous(this=\"TIME\", expressions=args),\n    }\n\n    STATEMENT_PARSERS = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.ATTACH: lambda self: self._parse_attach_detach(),\n        TokenType.DETACH: lambda self: self._parse_attach_detach(is_attach=False),\n    }\n\n    RANGE_PARSERS = {\n        **parser.Parser.RANGE_PARSERS,\n        # https://www.sqlite.org/lang_expr.html\n        TokenType.MATCH: binary_range_parser(exp.Match),\n    }\n\n    def _parse_unique(self) -> exp.UniqueColumnConstraint:\n        # Do not consume more tokens if UNIQUE is used as a standalone constraint, e.g:\n        # CREATE TABLE foo (bar TEXT UNIQUE REFERENCES baz ...)\n        if self._curr.text.upper() in self.CONSTRAINT_PARSERS:\n            return self.expression(exp.UniqueColumnConstraint())\n\n        return super()._parse_unique()\n\n    def _parse_attach_detach(self, is_attach=True) -> exp.Attach | exp.Detach:\n        self._match(TokenType.DATABASE)\n        this = self._parse_expression()\n\n        return (\n            self.expression(exp.Attach(this=this))\n            if is_attach\n            else self.expression(exp.Detach(this=this))\n        )\n"
  },
  {
    "path": "sqlglot/parsers/starrocks.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.dialects.dialect import build_timestamp_trunc\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parsers.mysql import MySQLParser\n\n\nclass StarRocksParser(MySQLParser):\n    FUNCTIONS = {\n        **MySQLParser.FUNCTIONS,\n        \"DATE_TRUNC\": build_timestamp_trunc,\n        \"DATEDIFF\": lambda args: exp.DateDiff(\n            this=seq_get(args, 0), expression=seq_get(args, 1), unit=exp.Literal.string(\"DAY\")\n        ),\n        \"DATE_DIFF\": lambda args: exp.DateDiff(\n            this=seq_get(args, 1), expression=seq_get(args, 2), unit=seq_get(args, 0)\n        ),\n        \"ARRAY_FLATTEN\": exp.Flatten.from_arg_list,\n        \"REGEXP\": exp.RegexpLike.from_arg_list,\n    }\n\n    PROPERTY_PARSERS = {\n        **MySQLParser.PROPERTY_PARSERS,\n        \"PROPERTIES\": lambda self: self._parse_wrapped_properties(),\n        \"UNIQUE\": lambda self: self._parse_composite_key_property(exp.UniqueKeyProperty),\n        \"ROLLUP\": lambda self: self._parse_rollup_property(),\n        \"REFRESH\": lambda self: self._parse_refresh_property(),\n    }\n\n    def _parse_rollup_property(self) -> exp.RollupProperty:\n        # ROLLUP (rollup_name (col1, col2) [FROM from_index] [PROPERTIES (...)], ...)\n        def parse_rollup_index() -> exp.RollupIndex:\n            return self.expression(\n                exp.RollupIndex(\n                    this=self._parse_id_var(),\n                    expressions=self._parse_wrapped_id_vars(),\n                    from_index=self._parse_id_var() if self._match_text_seq(\"FROM\") else None,\n                    properties=self.expression(\n                        exp.Properties(expressions=self._parse_wrapped_properties())\n                    )\n                    if self._match_text_seq(\"PROPERTIES\")\n                    else None,\n                )\n            )\n\n        return self.expression(\n            exp.RollupProperty(expressions=self._parse_wrapped_csv(parse_rollup_index))\n        )\n\n    def _parse_create(self) -> exp.Create | exp.Command:\n        create = super()._parse_create()\n\n        # Starrocks' primary key is defined outside of the schema, so we need to move it there\n        # https://docs.starrocks.io/docs/table_design/table_types/primary_key_table/#usage\n        if isinstance(create, exp.Create) and isinstance(create.this, exp.Schema):\n            props = create.args.get(\"properties\")\n            if props:\n                primary_key = props.find(exp.PrimaryKey)\n                if primary_key:\n                    create.this.append(\"expressions\", primary_key.pop())\n\n        return create\n\n    def _parse_unnest(self, with_alias: bool = True) -> t.Optional[exp.Unnest]:\n        unnest = super()._parse_unnest(with_alias=with_alias)\n\n        if unnest:\n            alias = unnest.args.get(\"alias\")\n\n            if not alias:\n                # Starrocks defaults to naming the table alias as \"unnest\"\n                alias = exp.TableAlias(\n                    this=exp.to_identifier(\"unnest\"), columns=[exp.to_identifier(\"unnest\")]\n                )\n                unnest.set(\"alias\", alias)\n            elif not alias.args.get(\"columns\"):\n                # Starrocks defaults to naming the UNNEST column as \"unnest\"\n                # if it's not otherwise specified\n                alias.set(\"columns\", [exp.to_identifier(\"unnest\")])\n\n        return unnest\n\n    def _parse_partitioned_by(self) -> exp.PartitionedByProperty:\n        return self.expression(\n            exp.PartitionedByProperty(\n                this=exp.Schema(\n                    expressions=self._parse_wrapped_csv(self._parse_assignment, optional=True)\n                )\n            )\n        )\n\n    def _parse_partition_property(\n        self,\n    ) -> t.Optional[exp.Expr] | t.List[exp.Expr]:\n        expr = super()._parse_partition_property()\n\n        if not expr:\n            return self._parse_partitioned_by()\n\n        if isinstance(expr, exp.Property):\n            return expr\n\n        self._match_l_paren()\n\n        if self._match_text_seq(\"START\", advance=False):\n            create_expressions = self._parse_csv(self._parse_partitioning_granularity_dynamic)\n        else:\n            create_expressions = None\n\n        self._match_r_paren()\n\n        return self.expression(\n            exp.PartitionByRangeProperty(\n                partition_expressions=expr, create_expressions=create_expressions\n            )\n        )\n\n    def _parse_partitioning_granularity_dynamic(self) -> exp.PartitionByRangePropertyDynamic:\n        self._match_text_seq(\"START\")\n        start = self._parse_wrapped(self._parse_string)\n        self._match_text_seq(\"END\")\n        end = self._parse_wrapped(self._parse_string)\n        self._match_text_seq(\"EVERY\")\n        every = self._parse_wrapped(lambda: self._parse_interval() or self._parse_number())\n        return self.expression(\n            exp.PartitionByRangePropertyDynamic(start=start, end=end, every=every)\n        )\n\n    def _parse_refresh_property(self) -> exp.RefreshTriggerProperty:\n        \"\"\"\n        REFRESH [DEFERRED | IMMEDIATE]\n                [ASYNC | ASYNC [START (<start_time>)] EVERY (INTERVAL <refresh_interval>) | MANUAL]\n        \"\"\"\n        method = self._match_texts((\"DEFERRED\", \"IMMEDIATE\")) and self._prev.text.upper()\n        kind = self._match_texts((\"ASYNC\", \"MANUAL\")) and self._prev.text.upper()\n        start = self._match_text_seq(\"START\") and self._parse_wrapped(self._parse_string)\n\n        if self._match_text_seq(\"EVERY\"):\n            self._match_l_paren()\n            self._match_text_seq(\"INTERVAL\")\n            every = self._parse_number()\n            unit = self._parse_var(any_token=True)\n            self._match_r_paren()\n        else:\n            every = None\n            unit = None\n\n        return self.expression(\n            exp.RefreshTriggerProperty(\n                method=method, kind=kind, starts=start, every=every, unit=unit\n            )\n        )\n"
  },
  {
    "path": "sqlglot/parsers/tableau.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp, parser\nfrom sqlglot.helper import seq_get\n\n\nclass TableauParser(parser.Parser):\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"COUNTD\": lambda args: exp.Count(this=exp.Distinct(expressions=args)),\n        \"FIND\": exp.StrPosition.from_arg_list,\n        \"FINDNTH\": lambda args: exp.StrPosition(\n            this=seq_get(args, 0), substr=seq_get(args, 1), occurrence=seq_get(args, 2)\n        ),\n    }\n    NO_PAREN_IF_COMMANDS = False\n"
  },
  {
    "path": "sqlglot/parsers/teradata.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.helper import seq_get\nfrom sqlglot.tokens import TokenType\nfrom sqlglot.trie import new_trie\n\n\nclass TeradataParser(parser.Parser):\n    TABLESAMPLE_CSV = True\n    VALUES_FOLLOWED_BY_PAREN = False\n\n    TABLE_ALIAS_TOKENS = parser.Parser.TABLE_ALIAS_TOKENS | {\n        TokenType.ANTI,\n        TokenType.SEMI,\n    }\n\n    CHARSET_TRANSLATORS = {\n        \"GRAPHIC_TO_KANJISJIS\",\n        \"GRAPHIC_TO_LATIN\",\n        \"GRAPHIC_TO_UNICODE\",\n        \"GRAPHIC_TO_UNICODE_PadSpace\",\n        \"KANJI1_KanjiEBCDIC_TO_UNICODE\",\n        \"KANJI1_KanjiEUC_TO_UNICODE\",\n        \"KANJI1_KANJISJIS_TO_UNICODE\",\n        \"KANJI1_SBC_TO_UNICODE\",\n        \"KANJISJIS_TO_GRAPHIC\",\n        \"KANJISJIS_TO_LATIN\",\n        \"KANJISJIS_TO_UNICODE\",\n        \"LATIN_TO_GRAPHIC\",\n        \"LATIN_TO_KANJISJIS\",\n        \"LATIN_TO_UNICODE\",\n        \"LOCALE_TO_UNICODE\",\n        \"UNICODE_TO_GRAPHIC\",\n        \"UNICODE_TO_GRAPHIC_PadGraphic\",\n        \"UNICODE_TO_GRAPHIC_VarGraphic\",\n        \"UNICODE_TO_KANJI1_KanjiEBCDIC\",\n        \"UNICODE_TO_KANJI1_KanjiEUC\",\n        \"UNICODE_TO_KANJI1_KANJISJIS\",\n        \"UNICODE_TO_KANJI1_SBC\",\n        \"UNICODE_TO_KANJISJIS\",\n        \"UNICODE_TO_LATIN\",\n        \"UNICODE_TO_LOCALE\",\n        \"UNICODE_TO_UNICODE_FoldSpace\",\n        \"UNICODE_TO_UNICODE_Fullwidth\",\n        \"UNICODE_TO_UNICODE_Halfwidth\",\n        \"UNICODE_TO_UNICODE_NFC\",\n        \"UNICODE_TO_UNICODE_NFD\",\n        \"UNICODE_TO_UNICODE_NFKC\",\n        \"UNICODE_TO_UNICODE_NFKD\",\n    }\n\n    FUNC_TOKENS = parser.Parser.FUNC_TOKENS - {TokenType.REPLACE}\n\n    STATEMENT_PARSERS = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.DATABASE: lambda self: self.expression(\n            exp.Use(this=self._parse_table(schema=False))\n        ),\n        TokenType.REPLACE: lambda self: self._parse_create(),\n        TokenType.LOCK: lambda self: self._parse_locking_statement(),\n    }\n\n    def _parse_locking_statement(self) -> exp.LockingStatement:\n        # Reuse exp.LockingProperty parsing for the lock kind, type etc\n        locking_property = self._parse_locking()\n        wrapped_query = self._parse_select()\n\n        if not wrapped_query:\n            self.raise_error(\"Expected SELECT statement after LOCKING clause\")\n\n        return self.expression(\n            exp.LockingStatement(this=locking_property, expression=wrapped_query)\n        )\n\n    SET_PARSERS = {\n        **parser.Parser.SET_PARSERS,\n        \"QUERY_BAND\": lambda self: self._parse_query_band(),\n    }\n\n    SET_TRIE = new_trie(key.split(\" \") for key in SET_PARSERS)\n\n    FUNCTION_PARSERS = {\n        **parser.Parser.FUNCTION_PARSERS,\n        # https://docs.teradata.com/r/SQL-Functions-Operators-Exprs-and-Predicates/June-2017/Data-Type-Conversions/TRYCAST\n        \"TRYCAST\": parser.Parser.FUNCTION_PARSERS[\"TRY_CAST\"],\n        \"RANGE_N\": lambda self: self._parse_rangen(),\n        \"TRANSLATE\": lambda self: self._parse_translate(),\n    }\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"CARDINALITY\": exp.ArraySize.from_arg_list,\n        \"RANDOM\": lambda args: exp.Rand(lower=seq_get(args, 0), upper=seq_get(args, 1)),\n    }\n\n    EXPONENT = {\n        TokenType.DSTAR: exp.Pow,\n    }\n\n    def _parse_translate(self) -> exp.TranslateCharacters:\n        this = self._parse_assignment()\n        self._match(TokenType.USING)\n        self._match_texts(self.CHARSET_TRANSLATORS)\n\n        return self.expression(\n            exp.TranslateCharacters(\n                this=this,\n                expression=self._prev.text.upper(),\n                with_error=self._match_text_seq(\"WITH\", \"ERROR\"),\n            )\n        )\n\n    # FROM before SET in Teradata UPDATE syntax\n    # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/Teradata-VantageTM-SQL-Data-Manipulation-Language-17.20/Statement-Syntax/UPDATE/UPDATE-Syntax-Basic-Form-FROM-Clause\n    def _parse_update(self) -> exp.Update:\n        return self.expression(\n            exp.Update(\n                this=self._parse_table(alias_tokens=self.UPDATE_ALIAS_TOKENS),\n                from_=self._parse_from(joins=True),\n                expressions=self._match(TokenType.SET) and self._parse_csv(self._parse_equality),\n                where=self._parse_where(),\n            )\n        )\n\n    def _parse_rangen(self):\n        this = self._parse_id_var()\n        self._match(TokenType.BETWEEN)\n\n        expressions = self._parse_csv(self._parse_assignment)\n        each = self._match_text_seq(\"EACH\") and self._parse_assignment()\n\n        return self.expression(exp.RangeN(this=this, expressions=expressions, each=each))\n\n    def _parse_query_band(self) -> exp.QueryBand:\n        # Parse: SET QUERY_BAND = 'key=value;key2=value2;' FOR SESSION|TRANSACTION\n        # Also supports: SET QUERY_BAND = 'key=value;' UPDATE FOR SESSION|TRANSACTION\n        # Also supports: SET QUERY_BAND = NONE FOR SESSION|TRANSACTION\n        self._match(TokenType.EQ)\n\n        # Handle both string literals and NONE keyword\n        if self._match_text_seq(\"NONE\"):\n            query_band_string: t.Optional[exp.Expr] = exp.Var(this=\"NONE\")\n        else:\n            query_band_string = self._parse_string()\n\n        update = self._match_text_seq(\"UPDATE\")\n        self._match_text_seq(\"FOR\")\n\n        # Handle scope - can be SESSION, TRANSACTION, VOLATILE, or SESSION VOLATILE\n        if self._match_text_seq(\"SESSION\", \"VOLATILE\"):\n            scope = \"SESSION VOLATILE\"\n        elif self._match_texts((\"SESSION\", \"TRANSACTION\")):\n            scope = self._prev.text.upper()\n        else:\n            scope = None\n\n        return self.expression(exp.QueryBand(this=query_band_string, scope=scope, update=update))\n\n    def _parse_index_params(self) -> exp.IndexParameters:\n        this = super()._parse_index_params()\n\n        if this.args.get(\"on\"):\n            this.set(\"on\", None)\n            self._retreat(self._index - 2)\n        return this\n\n    def _parse_function(\n        self,\n        functions: t.Optional[t.Dict[str, t.Callable]] = None,\n        anonymous: bool = False,\n        optional_parens: bool = True,\n        any_token: bool = False,\n    ) -> t.Optional[exp.Expr]:\n        # Teradata uses a `(FORMAT <format_string>)` clause after column references to\n        # override the output format. When we see this pattern we do not\n        # parse it as a function call.  The syntax is documented at\n        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT\n        if (\n            self._next\n            and self._next.token_type == TokenType.L_PAREN\n            and self._index + 2 < len(self._tokens)\n            and self._tokens[self._index + 2].token_type == TokenType.FORMAT\n        ):\n            return None\n\n        return super()._parse_function(\n            functions=functions,\n            anonymous=anonymous,\n            optional_parens=optional_parens,\n            any_token=any_token,\n        )\n\n    def _parse_column_ops(self, this: t.Optional[exp.Expr]) -> t.Optional[exp.Expr]:\n        this = super()._parse_column_ops(this)\n\n        if self._match_pair(TokenType.L_PAREN, TokenType.FORMAT):\n            # `(FORMAT <format_string>)` after a column specifies a Teradata format override.\n            # See https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT\n            fmt_string = self._parse_string()\n            self._match_r_paren()\n\n            this = self.expression(exp.FormatPhrase(this=this, format=fmt_string))\n\n        return this\n"
  },
  {
    "path": "sqlglot/parsers/trino.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.parsers.presto import PrestoParser\nfrom sqlglot.tokens import TokenType\n\n\nclass TrinoParser(PrestoParser):\n    NO_PAREN_FUNCTIONS = {\n        **PrestoParser.NO_PAREN_FUNCTIONS,\n        TokenType.CURRENT_CATALOG: exp.CurrentCatalog,\n    }\n\n    FUNCTIONS = {\n        **PrestoParser.FUNCTIONS,\n        \"VERSION\": exp.CurrentVersion.from_arg_list,\n    }\n\n    FUNCTION_PARSERS = {\n        **PrestoParser.FUNCTION_PARSERS,\n        \"TRIM\": lambda self: self._parse_trim(),\n        \"JSON_QUERY\": lambda self: self._parse_json_query(),\n        \"JSON_VALUE\": lambda self: self._parse_json_value(),\n        \"LISTAGG\": lambda self: self._parse_string_agg(),\n    }\n\n    JSON_QUERY_OPTIONS: parser.OPTIONS_TYPE = {\n        **dict.fromkeys(\n            (\"WITH\", \"WITHOUT\"),\n            (\n                (\"WRAPPER\"),\n                (\"ARRAY\", \"WRAPPER\"),\n                (\"CONDITIONAL\", \"WRAPPER\"),\n                (\"CONDITIONAL\", \"ARRAY\", \"WRAPPED\"),\n                (\"UNCONDITIONAL\", \"WRAPPER\"),\n                (\"UNCONDITIONAL\", \"ARRAY\", \"WRAPPER\"),\n            ),\n        ),\n    }\n\n    def _parse_json_query_quote(self) -> t.Optional[exp.JSONExtractQuote]:\n        if not (self._match_text_seq(\"KEEP\", \"QUOTES\") or self._match_text_seq(\"OMIT\", \"QUOTES\")):\n            return None\n\n        return self.expression(\n            exp.JSONExtractQuote(\n                option=self._tokens[self._index - 2].text.upper(),\n                scalar=self._match_text_seq(\"ON\", \"SCALAR\", \"STRING\"),\n            )\n        )\n\n    def _parse_json_query(self) -> exp.JSONExtract:\n        return self.expression(\n            exp.JSONExtract(\n                this=self._parse_bitwise(),\n                expression=self._match(TokenType.COMMA) and self._parse_bitwise(),\n                option=self._parse_var_from_options(self.JSON_QUERY_OPTIONS, raise_unmatched=False),\n                json_query=True,\n                quote=self._parse_json_query_quote(),\n                on_condition=self._parse_on_condition(),\n            )\n        )\n"
  },
  {
    "path": "sqlglot/parsers/tsql.py",
    "content": "from __future__ import annotations\n\nimport datetime\nimport re\nimport typing as t\n\nfrom sqlglot import exp, parser\nfrom sqlglot.dialects.dialect import (\n    Dialect,\n    build_date_delta,\n    map_date_part,\n)\nfrom sqlglot.helper import seq_get\nfrom sqlglot.parser import build_coalesce\nfrom sqlglot.time import format_time\nfrom sqlglot.tokens import TokenType\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from collections.abc import Collection\n\nFULL_FORMAT_TIME_MAPPING = {\n    \"weekday\": \"%A\",\n    \"dw\": \"%A\",\n    \"w\": \"%A\",\n    \"month\": \"%B\",\n    \"mm\": \"%B\",\n    \"m\": \"%B\",\n}\n\nDATE_DELTA_INTERVAL = {\n    \"year\": \"year\",\n    \"yyyy\": \"year\",\n    \"yy\": \"year\",\n    \"quarter\": \"quarter\",\n    \"qq\": \"quarter\",\n    \"q\": \"quarter\",\n    \"month\": \"month\",\n    \"mm\": \"month\",\n    \"m\": \"month\",\n    \"week\": \"week\",\n    \"ww\": \"week\",\n    \"wk\": \"week\",\n    \"day\": \"day\",\n    \"dd\": \"day\",\n    \"d\": \"day\",\n}\n\nDATE_FMT_RE = re.compile(\"([dD]{1,2})|([mM]{1,2})|([yY]{1,4})|([hH]{1,2})|([sS]{1,2})\")\n\n# N = Numeric, C=Currency\nTRANSPILE_SAFE_NUMBER_FMT = {\"N\", \"C\"}\n\nDEFAULT_START_DATE = datetime.date(1900, 1, 1)\n\n# Unsupported options:\n# - OPTIMIZE FOR ( @variable_name { UNKNOWN | = <literal_constant> } [ , ...n ] )\n# - TABLE HINT\nOPTIONS: parser.OPTIONS_TYPE = {\n    **dict.fromkeys(\n        (\n            \"DISABLE_OPTIMIZED_PLAN_FORCING\",\n            \"FAST\",\n            \"IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX\",\n            \"LABEL\",\n            \"MAXDOP\",\n            \"MAXRECURSION\",\n            \"MAX_GRANT_PERCENT\",\n            \"MIN_GRANT_PERCENT\",\n            \"NO_PERFORMANCE_SPOOL\",\n            \"QUERYTRACEON\",\n            \"RECOMPILE\",\n        ),\n        tuple(),\n    ),\n    \"CONCAT\": (\"UNION\",),\n    \"DISABLE\": (\"EXTERNALPUSHDOWN\", \"SCALEOUTEXECUTION\"),\n    \"EXPAND\": (\"VIEWS\",),\n    \"FORCE\": (\"EXTERNALPUSHDOWN\", \"ORDER\", \"SCALEOUTEXECUTION\"),\n    \"HASH\": (\"GROUP\", \"JOIN\", \"UNION\"),\n    \"KEEP\": (\"PLAN\",),\n    \"KEEPFIXED\": (\"PLAN\",),\n    \"LOOP\": (\"JOIN\",),\n    \"MERGE\": (\"JOIN\", \"UNION\"),\n    \"OPTIMIZE\": ((\"FOR\", \"UNKNOWN\"),),\n    \"ORDER\": (\"GROUP\",),\n    \"PARAMETERIZATION\": (\"FORCED\", \"SIMPLE\"),\n    \"ROBUST\": (\"PLAN\",),\n    \"USE\": (\"PLAN\",),\n}\n\n\nXML_OPTIONS: parser.OPTIONS_TYPE = {\n    **dict.fromkeys(\n        (\n            \"AUTO\",\n            \"EXPLICIT\",\n            \"TYPE\",\n        ),\n        tuple(),\n    ),\n    \"ELEMENTS\": (\n        \"XSINIL\",\n        \"ABSENT\",\n    ),\n    \"BINARY\": (\"BASE64\",),\n}\n\n\nOPTIONS_THAT_REQUIRE_EQUAL = (\"MAX_GRANT_PERCENT\", \"MIN_GRANT_PERCENT\", \"LABEL\")\n\n\ndef _build_formatted_time(\n    exp_class: t.Type[E], full_format_mapping: t.Optional[bool] = None\n) -> t.Callable[[t.List], E]:\n    def _builder(args: t.List) -> E:\n        fmt = seq_get(args, 0)\n        if isinstance(fmt, exp.Expr):\n            from sqlglot.dialects.tsql import TSQL\n\n            fmt = exp.Literal.string(\n                format_time(\n                    fmt.name.lower(),\n                    (\n                        {**TSQL.TIME_MAPPING, **FULL_FORMAT_TIME_MAPPING}\n                        if full_format_mapping\n                        else TSQL.TIME_MAPPING\n                    ),\n                )\n            )\n\n        this = seq_get(args, 1)\n        if isinstance(this, exp.Expr):\n            this = exp.cast(this, exp.DType.DATETIME2)\n\n        return exp_class(this=this, format=fmt)\n\n    return _builder\n\n\ndef _build_format(args: t.List) -> exp.NumberToStr | exp.TimeToStr:\n    this = seq_get(args, 0)\n    fmt = seq_get(args, 1)\n    culture = seq_get(args, 2)\n\n    number_fmt = fmt and (fmt.name in TRANSPILE_SAFE_NUMBER_FMT or not DATE_FMT_RE.search(fmt.name))\n\n    if number_fmt:\n        return exp.NumberToStr(this=this, format=fmt, culture=culture)\n\n    if fmt:\n        from sqlglot.dialects.tsql import TSQL\n\n        fmt = exp.Literal.string(\n            format_time(fmt.name, TSQL.FORMAT_TIME_MAPPING)\n            if len(fmt.name) == 1\n            else format_time(fmt.name, TSQL.TIME_MAPPING)\n        )\n\n    return exp.TimeToStr(this=this, format=fmt, culture=culture)\n\n\ndef _build_eomonth(args: t.List) -> exp.LastDay:\n    date = exp.TsOrDsToDate(this=seq_get(args, 0))\n    month_lag = seq_get(args, 1)\n\n    if month_lag is None:\n        this: exp.Expr = date\n    else:\n        unit = DATE_DELTA_INTERVAL.get(\"month\")\n        this = exp.DateAdd(this=date, expression=month_lag, unit=unit and exp.var(unit))\n\n    return exp.LastDay(this=this)\n\n\ndef _build_hashbytes(args: t.List) -> exp.Expr:\n    kind, data = args\n    kind = kind.name.upper() if kind.is_string else \"\"\n\n    if kind == \"MD5\":\n        args.pop(0)\n        return exp.MD5(this=data)\n    if kind in (\"SHA\", \"SHA1\"):\n        args.pop(0)\n        return exp.SHA(this=data)\n    if kind == \"SHA2_256\":\n        return exp.SHA2(this=data, length=exp.Literal.number(256))\n    if kind == \"SHA2_512\":\n        return exp.SHA2(this=data, length=exp.Literal.number(512))\n\n    return exp.func(\"HASHBYTES\", *args)\n\n\ndef _build_date_delta(\n    exp_class: t.Type[E], unit_mapping: t.Optional[t.Dict[str, str]] = None, big_int: bool = False\n) -> t.Callable[[t.List], E]:\n    def _builder(args: t.List) -> E:\n        unit = seq_get(args, 0)\n        if unit and unit_mapping:\n            unit = exp.var(unit_mapping.get(unit.name.lower(), unit.name))\n\n        start_date = seq_get(args, 1)\n        if start_date and start_date.is_number:\n            # Numeric types are valid DATETIME values\n            if start_date.is_int:\n                adds = DEFAULT_START_DATE + datetime.timedelta(days=start_date.to_py())\n                start_date = exp.Literal.string(adds.strftime(\"%F\"))\n            else:\n                # We currently don't handle float values, i.e. they're not converted to equivalent DATETIMEs.\n                # This is not a problem when generating T-SQL code, it is when transpiling to other dialects.\n                return exp_class(\n                    this=seq_get(args, 2), expression=start_date, unit=unit, big_int=big_int\n                )\n\n        return exp_class(\n            this=exp.TimeStrToTime(this=seq_get(args, 2)),\n            expression=exp.TimeStrToTime(this=start_date),\n            unit=unit,\n            big_int=big_int,\n        )\n\n    return _builder\n\n\n# https://learn.microsoft.com/en-us/sql/t-sql/functions/datetimefromparts-transact-sql?view=sql-server-ver16#syntax\ndef _build_datetimefromparts(args: t.List) -> exp.TimestampFromParts:\n    return exp.TimestampFromParts(\n        year=seq_get(args, 0),\n        month=seq_get(args, 1),\n        day=seq_get(args, 2),\n        hour=seq_get(args, 3),\n        min=seq_get(args, 4),\n        sec=seq_get(args, 5),\n        milli=seq_get(args, 6),\n    )\n\n\n# https://learn.microsoft.com/en-us/sql/t-sql/functions/timefromparts-transact-sql?view=sql-server-ver16#syntax\ndef _build_timefromparts(args: t.List) -> exp.TimeFromParts:\n    return exp.TimeFromParts(\n        hour=seq_get(args, 0),\n        min=seq_get(args, 1),\n        sec=seq_get(args, 2),\n        fractions=seq_get(args, 3),\n        precision=seq_get(args, 4),\n    )\n\n\ndef _build_with_arg_as_text(\n    klass: t.Type[exp.Expr],\n) -> t.Callable[[t.List[exp.Expr]], exp.Expr]:\n    def _parse(args: t.List[exp.Expr]) -> exp.Expr:\n        this = seq_get(args, 0)\n\n        if this and not this.is_string:\n            this = exp.cast(this, exp.DType.TEXT)\n\n        expression = seq_get(args, 1)\n        kwargs = {\"this\": this}\n\n        if expression:\n            kwargs[\"expression\"] = expression\n\n        return klass(**kwargs)\n\n    return _parse\n\n\n# https://learn.microsoft.com/en-us/sql/t-sql/functions/parsename-transact-sql?view=sql-server-ver16\ndef _build_parsename(args: t.List) -> exp.SplitPart | exp.Anonymous:\n    # PARSENAME(...) will be stored into exp.SplitPart if:\n    # - All args are literals\n    # - The part index (2nd arg) is <= 4 (max valid value, otherwise TSQL returns NULL)\n    if len(args) == 2 and all(isinstance(arg, exp.Literal) for arg in args):\n        this = args[0]\n        part_index = args[1]\n        split_count = len(this.name.split(\".\"))\n        if split_count <= 4:\n            return exp.SplitPart(\n                this=this,\n                delimiter=exp.Literal.string(\".\"),\n                part_index=exp.Literal.number(split_count + 1 - part_index.to_py()),\n            )\n\n    return exp.Anonymous(this=\"PARSENAME\", expressions=args)\n\n\ndef _build_json_query(args: t.List, dialect: Dialect) -> exp.JSONExtract:\n    if len(args) == 1:\n        # The default value for path is '$'. As a result, if you don't provide a\n        # value for path, JSON_QUERY returns the input expression.\n        args.append(exp.Literal.string(\"$\"))\n\n    return parser.build_extract_json_with_path(exp.JSONExtract)(args, dialect)\n\n\ndef _build_datetrunc(args: t.List) -> exp.TimestampTrunc:\n    unit = seq_get(args, 0)\n    this = seq_get(args, 1)\n\n    if this and this.is_string:\n        this = exp.cast(this, exp.DType.DATETIME2)\n\n    return exp.TimestampTrunc(this=this, unit=unit)\n\n\nclass TSQLParser(parser.Parser):\n    SET_REQUIRES_ASSIGNMENT_DELIMITER = False\n    LOG_DEFAULTS_TO_LN = True\n    STRING_ALIASES = True\n    NO_PAREN_IF_COMMANDS = False\n\n    NO_PAREN_FUNCTIONS = {\n        **parser.Parser.NO_PAREN_FUNCTIONS,\n        TokenType.SESSION_USER: exp.SessionUser,\n    }\n\n    QUERY_MODIFIER_PARSERS = {\n        **parser.Parser.QUERY_MODIFIER_PARSERS,\n        TokenType.OPTION: lambda self: (\"options\", self._parse_options()),\n        TokenType.FOR: lambda self: (\"for_\", self._parse_for()),\n    }\n\n    # T-SQL does not allow BEGIN to be used as an identifier\n    ID_VAR_TOKENS = parser.Parser.ID_VAR_TOKENS - {TokenType.BEGIN}\n    ALIAS_TOKENS = parser.Parser.ALIAS_TOKENS - {TokenType.BEGIN}\n    TABLE_ALIAS_TOKENS = (parser.Parser.TABLE_ALIAS_TOKENS | {TokenType.ANTI, TokenType.SEMI}) - {\n        TokenType.BEGIN\n    }\n    COMMENT_TABLE_ALIAS_TOKENS = parser.Parser.COMMENT_TABLE_ALIAS_TOKENS - {TokenType.BEGIN}\n    UPDATE_ALIAS_TOKENS = parser.Parser.UPDATE_ALIAS_TOKENS - {TokenType.BEGIN}\n\n    FUNCTIONS = {\n        **parser.Parser.FUNCTIONS,\n        \"ATN2\": exp.Atan2.from_arg_list,\n        \"CHARINDEX\": lambda args: exp.StrPosition(\n            this=seq_get(args, 1),\n            substr=seq_get(args, 0),\n            position=seq_get(args, 2),\n        ),\n        \"COUNT\": lambda args: exp.Count(this=seq_get(args, 0), expressions=args[1:], big_int=False),\n        \"COUNT_BIG\": lambda args: exp.Count(\n            this=seq_get(args, 0), expressions=args[1:], big_int=True\n        ),\n        \"DATEADD\": build_date_delta(exp.DateAdd, unit_mapping=DATE_DELTA_INTERVAL),\n        \"DATEDIFF\": _build_date_delta(exp.DateDiff, unit_mapping=DATE_DELTA_INTERVAL),\n        \"DATEDIFF_BIG\": _build_date_delta(\n            exp.DateDiff, unit_mapping=DATE_DELTA_INTERVAL, big_int=True\n        ),\n        \"DATENAME\": _build_formatted_time(exp.TimeToStr, full_format_mapping=True),\n        \"DATETIMEFROMPARTS\": _build_datetimefromparts,\n        \"EOMONTH\": _build_eomonth,\n        \"FORMAT\": _build_format,\n        \"GETDATE\": exp.CurrentTimestamp.from_arg_list,\n        \"HASHBYTES\": _build_hashbytes,\n        \"ISNULL\": lambda args: build_coalesce(args=args, is_null=True),\n        \"JSON_QUERY\": _build_json_query,\n        \"JSON_VALUE\": parser.build_extract_json_with_path(exp.JSONExtractScalar),\n        \"LEN\": _build_with_arg_as_text(exp.Length),\n        \"LEFT\": _build_with_arg_as_text(exp.Left),\n        \"NEWID\": exp.Uuid.from_arg_list,\n        \"RIGHT\": _build_with_arg_as_text(exp.Right),\n        \"PARSENAME\": _build_parsename,\n        \"REPLICATE\": exp.Repeat.from_arg_list,\n        \"SCHEMA_NAME\": exp.CurrentSchema.from_arg_list,\n        \"SQUARE\": lambda args: exp.Pow(this=seq_get(args, 0), expression=exp.Literal.number(2)),\n        \"SYSDATETIME\": exp.CurrentTimestamp.from_arg_list,\n        \"SUSER_NAME\": exp.CurrentUser.from_arg_list,\n        \"SUSER_SNAME\": exp.CurrentUser.from_arg_list,\n        \"SYSDATETIMEOFFSET\": exp.CurrentTimestampLTZ.from_arg_list,\n        \"SYSTEM_USER\": exp.CurrentUser.from_arg_list,\n        \"TIMEFROMPARTS\": _build_timefromparts,\n        \"DATETRUNC\": _build_datetrunc,\n    }\n\n    JOIN_HINTS = {\"LOOP\", \"HASH\", \"MERGE\", \"REMOTE\"}\n\n    PROCEDURE_OPTIONS = dict.fromkeys(\n        (\"ENCRYPTION\", \"RECOMPILE\", \"SCHEMABINDING\", \"NATIVE_COMPILATION\", \"EXECUTE\"), tuple()\n    )\n\n    COLUMN_DEFINITION_MODES = {\"OUT\", \"OUTPUT\", \"READONLY\"}\n\n    RETURNS_TABLE_TOKENS = parser.Parser.ID_VAR_TOKENS - {\n        TokenType.TABLE,\n        *parser.Parser.TYPE_TOKENS,\n    }\n\n    STATEMENT_PARSERS = {\n        **parser.Parser.STATEMENT_PARSERS,\n        TokenType.DECLARE: lambda self: self._parse_declare(),\n        TokenType.EXECUTE: lambda self: self._parse_execute(),\n    }\n\n    RANGE_PARSERS = {\n        **parser.Parser.RANGE_PARSERS,\n        TokenType.DCOLON: lambda self, this: self.expression(\n            exp.ScopeResolution(\n                this=this, expression=self._parse_function() or self._parse_var(any_token=True)\n            )\n        ),\n    }\n\n    NO_PAREN_FUNCTION_PARSERS = {\n        **parser.Parser.NO_PAREN_FUNCTION_PARSERS,\n        \"NEXT\": lambda self: self._parse_next_value_for(),\n    }\n\n    FUNCTION_PARSERS = {\n        **parser.Parser.FUNCTION_PARSERS,\n        \"JSON_ARRAYAGG\": lambda self: self.expression(\n            exp.JSONArrayAgg(\n                this=self._parse_bitwise(),\n                order=self._parse_order(),\n                null_handling=self._parse_on_handling(\"NULL\", \"NULL\", \"ABSENT\"),\n            )\n        ),\n        \"DATEPART\": lambda self: self._parse_datepart(),\n    }\n\n    # The DCOLON (::) operator serves as a scope resolution (exp.ScopeResolution) operator in T-SQL\n    COLUMN_OPERATORS = {\n        **parser.Parser.COLUMN_OPERATORS,\n        TokenType.DCOLON: lambda self, this, to: (\n            self.expression(exp.Cast(this=this, to=to))\n            if isinstance(to, exp.DataType) and to.this != exp.DType.USERDEFINED\n            else self.expression(exp.ScopeResolution(this=this, expression=to))\n        ),\n    }\n\n    SET_OP_MODIFIERS = {\"offset\"}\n\n    ODBC_DATETIME_LITERALS = {\n        \"d\": exp.Date,\n        \"t\": exp.Time,\n        \"ts\": exp.Timestamp,\n    }\n\n    def _parse_execute(self) -> exp.Execute:\n        execute = self.expression(\n            exp.Execute(\n                this=self._parse_table(schema=True),\n                expressions=self._parse_csv(self._parse_expression),\n            )\n        )\n\n        if execute.name.lower() == \"sp_executesql\":\n            execute = self.expression(exp.ExecuteSql(**execute.args))\n\n        return execute\n\n    def _parse_datepart(self) -> exp.Extract:\n        this = self._parse_var(tokens=[TokenType.IDENTIFIER])\n        expression = self._match(TokenType.COMMA) and self._parse_bitwise()\n        name = map_date_part(this, self.dialect)\n\n        return self.expression(exp.Extract(this=name, expression=expression))\n\n    def _parse_alter_table_set(self) -> exp.AlterSet:\n        return self._parse_wrapped(super()._parse_alter_table_set)\n\n    def _parse_wrapped_select(self, table: bool = False) -> t.Optional[exp.Expr]:\n        if self._match(TokenType.MERGE):\n            comments = self._prev_comments\n            merge = self._parse_merge()\n            merge.add_comments(comments, prepend=True)\n            return merge\n\n        return super()._parse_wrapped_select(table=table)\n\n    def _parse_dcolon(self) -> t.Optional[exp.Expr]:\n        # We want to use _parse_types() if the first token after :: is a known type,\n        # otherwise we could parse something like x::varchar(max) into a function\n        if self._match_set(self.TYPE_TOKENS, advance=False):\n            return self._parse_types()\n\n        return self._parse_function() or self._parse_types()\n\n    def _parse_options(self) -> t.Optional[t.List[exp.Expr]]:\n        if not self._match(TokenType.OPTION):\n            return None\n\n        def _parse_option() -> t.Optional[exp.Expr]:\n            option = self._parse_var_from_options(OPTIONS)\n            if not option:\n                return None\n\n            self._match(TokenType.EQ)\n            return self.expression(\n                exp.QueryOption(this=option, expression=self._parse_primary_or_var())\n            )\n\n        return self._parse_wrapped_csv(_parse_option)\n\n    def _parse_xml_key_value_option(self) -> exp.XMLKeyValueOption:\n        this = self._parse_primary_or_var()\n        if self._match(TokenType.L_PAREN, advance=False):\n            expression = self._parse_wrapped(self._parse_string)\n        else:\n            expression = None\n\n        return exp.XMLKeyValueOption(this=this, expression=expression)\n\n    def _parse_for(self) -> t.Optional[t.List[exp.Expr]]:\n        if not self._match_pair(TokenType.FOR, TokenType.XML):\n            return None\n\n        def _parse_for_xml() -> t.Optional[exp.Expr]:\n            return self.expression(\n                exp.QueryOption(\n                    this=self._parse_var_from_options(XML_OPTIONS, raise_unmatched=False)\n                    or self._parse_xml_key_value_option()\n                )\n            )\n\n        return self._parse_csv(_parse_for_xml)\n\n    def _parse_projections(\n        self,\n    ) -> t.Tuple[t.List[exp.Expr], t.Optional[t.List[exp.Expr]]]:\n        \"\"\"\n        T-SQL supports the syntax alias = expression in the SELECT's projection list,\n        so we transform all parsed Selects to convert their EQ projections into Aliases.\n\n        See: https://learn.microsoft.com/en-us/sql/t-sql/queries/select-clause-transact-sql?view=sql-server-ver16#syntax\n        \"\"\"\n        projections, _ = super()._parse_projections()\n        return [\n            (\n                exp.alias_(projection.expression, projection.this.this, copy=False)\n                if isinstance(projection, exp.EQ) and isinstance(projection.this, exp.Column)\n                else projection\n            )\n            for projection in projections\n        ], None\n\n    def _parse_commit_or_rollback(self) -> exp.Commit | exp.Rollback:\n        \"\"\"Applies to SQL Server and Azure SQL Database\n        COMMIT [ { TRAN | TRANSACTION }\n            [ transaction_name | @tran_name_variable ] ]\n            [ WITH ( DELAYED_DURABILITY = { OFF | ON } ) ]\n\n        ROLLBACK { TRAN | TRANSACTION }\n            [ transaction_name | @tran_name_variable\n            | savepoint_name | @savepoint_variable ]\n        \"\"\"\n        rollback = self._prev.token_type == TokenType.ROLLBACK\n\n        self._match_texts((\"TRAN\", \"TRANSACTION\"))\n        this = self._parse_id_var()\n\n        if rollback:\n            return self.expression(exp.Rollback(this=this))\n\n        durability = None\n        if self._match_pair(TokenType.WITH, TokenType.L_PAREN):\n            self._match_text_seq(\"DELAYED_DURABILITY\")\n            self._match(TokenType.EQ)\n\n            if self._match_text_seq(\"OFF\"):\n                durability = False\n            else:\n                self._match(TokenType.ON)\n                durability = True\n\n            self._match_r_paren()\n\n        return self.expression(exp.Commit(this=this, durability=durability))\n\n    def _parse_transaction(self) -> exp.Transaction | exp.Command:\n        \"\"\"Applies to SQL Server and Azure SQL Database\n        BEGIN { TRAN | TRANSACTION }\n        [ { transaction_name | @tran_name_variable }\n        [ WITH MARK [ 'description' ] ]\n        ]\n        \"\"\"\n        if self._match_texts((\"TRAN\", \"TRANSACTION\")):\n            transaction = self.expression(exp.Transaction(this=self._parse_id_var()))\n            if self._match_text_seq(\"WITH\", \"MARK\"):\n                transaction.set(\"mark\", self._parse_string())\n\n            return transaction\n\n        return self._parse_as_command(self._prev)\n\n    def _parse_returns(self) -> exp.ReturnsProperty:\n        table = self._parse_id_var(any_token=False, tokens=self.RETURNS_TABLE_TOKENS)\n        returns = super()._parse_returns()\n        returns.set(\"table\", table)\n        return returns\n\n    def _parse_convert(self, strict: bool, safe: t.Optional[bool] = None) -> t.Optional[exp.Expr]:\n        this = self._parse_types()\n        self._match(TokenType.COMMA)\n        args = [this, *self._parse_csv(self._parse_assignment)]\n        convert = exp.Convert.from_arg_list(args)\n        convert.set(\"safe\", safe)\n        return convert\n\n    def _parse_column_def(\n        self, this: t.Optional[exp.Expr], computed_column: bool = True\n    ) -> t.Optional[exp.Expr]:\n        this = super()._parse_column_def(this=this, computed_column=computed_column)\n        if not this:\n            return None\n        if self._match(TokenType.EQ):\n            this.set(\"default\", self._parse_disjunction())\n        if self._match_texts(self.COLUMN_DEFINITION_MODES):\n            this.set(\"output\", self._prev.text)\n        return this\n\n    def _parse_user_defined_function(\n        self, kind: t.Optional[TokenType] = None\n    ) -> t.Optional[exp.Expr]:\n        this = super()._parse_user_defined_function(kind=kind)\n\n        if kind == TokenType.FUNCTION or isinstance(this, exp.UserDefinedFunction):\n            return this\n\n        if kind == TokenType.PROCEDURE and this:\n            expressions = this.expressions\n            if not (\n                expressions or self._match_set((TokenType.ALIAS, TokenType.WITH), advance=False)\n            ):\n                expressions = self._parse_csv(self._parse_function_parameter)\n\n            return self.expression(\n                exp.StoredProcedure(\n                    this=this if isinstance(this, exp.Table) else this.this,\n                    expressions=expressions,\n                    wrapped=this.args.get(\"wrapped\"),\n                )\n            )\n\n        return self.expression(exp.UserDefinedFunction(this=this))\n\n    def _parse_into(self) -> t.Optional[exp.Into]:\n        into = super()._parse_into()\n\n        table = isinstance(into, exp.Into) and into.find(exp.Table)\n        if isinstance(table, exp.Table):\n            table_identifier = table.this\n            if table_identifier.args.get(\"temporary\"):\n                # Promote the temporary property from the Identifier to the Into expression\n                t.cast(exp.Into, into).set(\"temporary\", True)\n\n        return into\n\n    def _parse_id_var(\n        self,\n        any_token: bool = True,\n        tokens: t.Optional[Collection[TokenType]] = None,\n    ) -> t.Optional[exp.Expr]:\n        is_temporary = self._match(TokenType.HASH)\n        is_global = is_temporary and self._match(TokenType.HASH)\n\n        this = super()._parse_id_var(any_token=any_token, tokens=tokens)\n        if this:\n            if is_global:\n                this.set(\"global_\", True)\n            elif is_temporary:\n                this.set(\"temporary\", True)\n\n        return this\n\n    def _parse_create(self) -> exp.Create | exp.Command:\n        create = super()._parse_create()\n\n        if isinstance(create, exp.Create):\n            table = create.this.this if isinstance(create.this, exp.Schema) else create.this\n            if isinstance(table, exp.Table) and table.this and table.this.args.get(\"temporary\"):\n                if not create.args.get(\"properties\"):\n                    create.set(\"properties\", exp.Properties(expressions=[]))\n\n                create.args[\"properties\"].append(\"expressions\", exp.TemporaryProperty())\n\n        return create\n\n    def _parse_if(self) -> t.Optional[exp.Expr]:\n        this = self._parse_condition()\n        true = self._parse_block()\n\n        false = self._match(TokenType.ELSE) and self._parse_block()\n\n        return self.expression(exp.IfBlock(this=this, true=true, false=false))\n\n    def _parse_unique(self) -> exp.UniqueColumnConstraint:\n        if self._match_texts((\"CLUSTERED\", \"NONCLUSTERED\")):\n            this = self.CONSTRAINT_PARSERS[self._prev.text.upper()](self)\n        else:\n            this = self._parse_schema(self._parse_id_var(any_token=False))\n\n        return self.expression(exp.UniqueColumnConstraint(this=this))\n\n    def _parse_update(self) -> exp.Update:\n        expression = super()._parse_update()\n        expression.set(\"options\", self._parse_options())\n        return expression\n\n    def _parse_partition(self) -> t.Optional[exp.Partition]:\n        if not self._match_text_seq(\"WITH\", \"(\", \"PARTITIONS\"):\n            return None\n\n        def parse_range():\n            low = self._parse_bitwise()\n            high = self._parse_bitwise() if self._match_text_seq(\"TO\") else None\n\n            return self.expression(exp.PartitionRange(this=low, expression=high)) if high else low\n\n        partition = self.expression(exp.Partition(expressions=self._parse_wrapped_csv(parse_range)))\n\n        self._match_r_paren()\n\n        return partition\n\n    def _parse_alter_table_alter(self) -> t.Optional[exp.Expr]:\n        expression = super()._parse_alter_table_alter()\n\n        if expression is not None:\n            collation = expression.args.get(\"collate\")\n            if isinstance(collation, exp.Column) and isinstance(collation.this, exp.Identifier):\n                identifier = collation.this\n                collation.set(\"this\", exp.Var(this=identifier.name))\n\n        return expression\n\n    def _parse_primary_key_part(self) -> t.Optional[exp.Expr]:\n        return self._parse_ordered()\n"
  },
  {
    "path": "sqlglot/planner.py",
    "content": "from __future__ import annotations\n\nimport math\nimport typing as t\n\nfrom sqlglot import alias, exp\nfrom sqlglot.helper import name_sequence\nfrom sqlglot.optimizer.eliminate_joins import join_condition\nfrom collections.abc import Iterator, Sequence, Iterable\n\n\nclass Plan:\n    def __init__(self, expression: exp.Expr) -> None:\n        self.expression = expression.copy()\n        self.root = Step.from_expression(self.expression)\n        self._dag: dict[Step, set[Step]] = {}\n\n    @property\n    def dag(self) -> dict[Step, set[Step]]:\n        if not self._dag:\n            dag: dict[Step, set[Step]] = {}\n            nodes = {self.root}\n\n            while nodes:\n                node = nodes.pop()\n                dag[node] = set()\n\n                for dep in node.dependencies:\n                    dag[node].add(dep)\n                    nodes.add(dep)\n\n            self._dag = dag\n\n        return self._dag\n\n    @property\n    def leaves(self) -> Iterator[Step]:\n        return (node for node, deps in self.dag.items() if not deps)\n\n    def __repr__(self) -> str:\n        return f\"Plan\\n----\\n{repr(self.root)}\"\n\n\nclass Step:\n    @classmethod\n    def from_expression(\n        cls, expression: exp.Expr, ctes: t.Optional[t.Dict[str, Step]] = None\n    ) -> Step:\n        \"\"\"\n        Builds a DAG of Steps from a SQL expression so that it's easier to execute in an engine.\n        Note: the expression's tables and subqueries must be aliased for this method to work. For\n        example, given the following expression:\n\n        SELECT\n          x.a,\n          SUM(x.b)\n        FROM x AS x\n        JOIN y AS y\n          ON x.a = y.a\n        GROUP BY x.a\n\n        the following DAG is produced (the expression IDs might differ per execution):\n\n        - Aggregate: x (4347984624)\n            Context:\n              Aggregations:\n                - SUM(x.b)\n              Group:\n                - x.a\n            Projections:\n              - x.a\n              - \"x\".\"\"\n            Dependencies:\n            - Join: x (4347985296)\n              Context:\n                y:\n                On: x.a = y.a\n              Projections:\n              Dependencies:\n              - Scan: x (4347983136)\n                Context:\n                  Source: x AS x\n                Projections:\n              - Scan: y (4343416624)\n                Context:\n                  Source: y AS y\n                Projections:\n\n        Args:\n            expression: the expression to build the DAG from.\n            ctes: a dictionary that maps CTEs to their corresponding Step DAG by name.\n\n        Returns:\n            A Step DAG corresponding to `expression`.\n        \"\"\"\n        ctes = ctes or {}\n        expression = expression.unnest()\n        with_ = expression.args.get(\"with_\")\n\n        # CTEs break the mold of scope and introduce themselves to all in the context.\n        if with_:\n            ctes = ctes.copy()\n            for cte in with_.expressions:\n                step = Step.from_expression(cte.this, ctes)\n                step.name = cte.alias\n                ctes[step.name] = step  # type: ignore\n\n        from_ = expression.args.get(\"from_\")\n\n        if isinstance(expression, exp.Select) and from_:\n            step = Scan.from_expression(from_.this, ctes)\n        elif isinstance(expression, exp.SetOperation):\n            step = SetOperation.from_expression(expression, ctes)\n        else:\n            step = Scan()\n\n        joins = expression.args.get(\"joins\")\n\n        if joins:\n            join = Join.from_joins(joins, ctes)\n            join.name = step.name\n            join.source_name = step.name\n            join.add_dependency(step)\n            step = join\n\n        projections: t.List[\n            exp.Expr\n        ] = []  # final selects in this chain of steps representing a select\n        operands = {}  # intermediate computations of agg funcs eg x + 1 in SUM(x + 1)\n        aggregations = {}\n        next_operand_name = name_sequence(\"_a_\")\n\n        def extract_agg_operands(expression):\n            agg_funcs = tuple(expression.find_all(exp.AggFunc))\n            if agg_funcs:\n                aggregations[expression] = None\n\n            for agg in agg_funcs:\n                for operand in agg.unnest_operands():\n                    if isinstance(operand, exp.Column):\n                        continue\n                    if operand not in operands:\n                        operands[operand] = next_operand_name()\n\n                    operand.replace(exp.column(operands[operand], quoted=True))\n\n            return bool(agg_funcs)\n\n        def set_ops_and_aggs(step):\n            step.operands = tuple(alias(operand, alias_) for operand, alias_ in operands.items())\n            step.aggregations = list(aggregations)\n\n        for e in expression.expressions:\n            if e.find(exp.AggFunc):\n                projections.append(exp.column(e.alias_or_name, step.name, quoted=True))\n                extract_agg_operands(e)\n            else:\n                projections.append(e)\n\n        where = expression.args.get(\"where\")\n\n        if where:\n            step.condition = where.this\n\n        group = expression.args.get(\"group\")\n\n        if group or aggregations:\n            aggregate = Aggregate()\n            aggregate.source = step.name\n            aggregate.name = step.name\n\n            having = expression.args.get(\"having\")\n\n            if having:\n                if extract_agg_operands(exp.alias_(having.this, \"_h\", quoted=True)):\n                    aggregate.condition = exp.column(\"_h\", step.name, quoted=True)\n                else:\n                    aggregate.condition = having.this\n\n            set_ops_and_aggs(aggregate)\n\n            # give aggregates names and replace projections with references to them\n            aggregate.group = {\n                f\"_g{i}\": e for i, e in enumerate(group.expressions if group else [])\n            }\n\n            intermediate: t.Dict[str | exp.Expr, str] = {}\n            for k, v in aggregate.group.items():\n                intermediate[v] = k\n                if isinstance(v, exp.Column):\n                    intermediate[v.name] = k\n\n            for projection in projections:\n                for node in projection.walk():\n                    name = intermediate.get(node)\n                    if name:\n                        node.replace(exp.column(name, step.name))\n\n            if aggregate.condition:\n                for node in aggregate.condition.walk():\n                    name = intermediate.get(node) or intermediate.get(node.name)\n                    if name:\n                        node.replace(exp.column(name, step.name))\n\n            aggregate.add_dependency(step)\n            step = aggregate\n        else:\n            aggregate = None\n\n        order = expression.args.get(\"order\")\n\n        if order:\n            if aggregate and isinstance(step, Aggregate):\n                for i, ordered in enumerate(order.expressions):\n                    if extract_agg_operands(exp.alias_(ordered.this, f\"_o_{i}\", quoted=True)):\n                        ordered.this.replace(exp.column(f\"_o_{i}\", step.name, quoted=True))\n\n                set_ops_and_aggs(aggregate)\n\n            sort = Sort()\n            sort.name = step.name\n            sort.key = order.expressions\n            sort.add_dependency(step)\n            step = sort\n\n        step.projections = projections\n\n        if isinstance(expression, exp.Select) and expression.args.get(\"distinct\"):\n            distinct = Aggregate()\n            distinct.source = step.name\n            distinct.name = step.name\n            distinct.group = {\n                e.alias_or_name: exp.column(col=e.alias_or_name, table=step.name)\n                for e in projections or expression.expressions\n            }\n            distinct.add_dependency(step)\n            step = distinct\n\n        limit = expression.args.get(\"limit\")\n\n        if limit:\n            step.limit = int(limit.text(\"expression\"))\n\n        return step\n\n    def __init__(self) -> None:\n        self.name: t.Optional[str] = None\n        self.dependencies: set[Step] = set()\n        self.dependents: set[Step] = set()\n        self.projections: Sequence[exp.Expr] = []\n        self.limit: float = math.inf\n        self.condition: t.Optional[exp.Expr] = None\n\n    def add_dependency(self, dependency: Step) -> None:\n        self.dependencies.add(dependency)\n        dependency.dependents.add(self)\n\n    def __repr__(self) -> str:\n        return self.to_s()\n\n    def to_s(self, level: int = 0) -> str:\n        indent = \"  \" * level\n        nested = f\"{indent}    \"\n\n        context = self._to_s(f\"{nested}  \")\n\n        if context:\n            context = [f\"{nested}Context:\"] + context\n\n        lines = [\n            f\"{indent}- {self.id}\",\n            *context,\n            f\"{nested}Projections:\",\n        ]\n\n        for expression in self.projections:\n            lines.append(f\"{nested}  - {expression.sql()}\")\n\n        if self.condition:\n            lines.append(f\"{nested}Condition: {self.condition.sql()}\")\n\n        if self.limit is not math.inf:\n            lines.append(f\"{nested}Limit: {self.limit}\")\n\n        if self.dependencies:\n            lines.append(f\"{nested}Dependencies:\")\n            for dependency in self.dependencies:\n                lines.append(\"  \" + dependency.to_s(level + 1))\n\n        return \"\\n\".join(lines)\n\n    @property\n    def type_name(self) -> str:\n        return self.__class__.__name__\n\n    @property\n    def id(self) -> str:\n        name = self.name\n        name = f\" {name}\" if name else \"\"\n        return f\"{self.type_name}:{name} ({id(self)})\"\n\n    def _to_s(self, _indent: str) -> t.List[str]:\n        return []\n\n\nclass Scan(Step):\n    @classmethod\n    def from_expression(\n        cls, expression: exp.Expr, ctes: t.Optional[t.Dict[str, Step]] = None\n    ) -> Step:\n        table = expression\n        alias_ = expression.alias_or_name\n\n        if isinstance(expression, exp.Subquery):\n            table = expression.this\n            step = Step.from_expression(table, ctes)\n            step.name = alias_\n            return step\n\n        step = Scan()\n        step.name = alias_\n        step.source = expression\n        if ctes and table.name in ctes:\n            step.add_dependency(ctes[table.name])\n\n        return step\n\n    def __init__(self) -> None:\n        super().__init__()\n        self.source: t.Optional[exp.Expr] = None\n\n    def _to_s(self, indent: str) -> t.List[str]:\n        return [f\"{indent}Source: {self.source.sql() if self.source else '-static-'}\"]  # type: ignore\n\n\nclass Join(Step):\n    @classmethod\n    def from_joins(\n        cls, joins: Iterable[exp.Join], ctes: t.Optional[dict[str, Step]] = None\n    ) -> Join:\n        step = Join()\n\n        for join in joins:\n            source_key, join_key, condition = join_condition(join)\n            step.joins[join.alias_or_name] = {\n                \"side\": join.side,  # type: ignore\n                \"join_key\": join_key,\n                \"source_key\": source_key,\n                \"condition\": condition,\n            }\n\n            step.add_dependency(Scan.from_expression(join.this, ctes))\n\n        return step\n\n    def __init__(self) -> None:\n        super().__init__()\n        self.source_name: t.Optional[str] = None\n        self.joins: t.Dict[str, t.Dict[str, t.List[str] | exp.Expr]] = {}\n\n    def _to_s(self, indent: str) -> t.List[str]:\n        lines = [f\"{indent}Source: {self.source_name or self.name}\"]\n        for name, join in self.joins.items():\n            lines.append(f\"{indent}{name}: {join['side'] or 'INNER'}\")\n            join_key = \", \".join(str(key) for key in t.cast(list, join.get(\"join_key\") or []))\n            if join_key:\n                lines.append(f\"{indent}Key: {join_key}\")\n            if join.get(\"condition\"):\n                lines.append(f\"{indent}On: {join['condition'].sql()}\")  # type: ignore\n        return lines\n\n\nclass Aggregate(Step):\n    def __init__(self) -> None:\n        super().__init__()\n        self.aggregations: t.List[exp.Expr] = []\n        self.operands: t.Tuple[exp.Expr, ...] = ()\n        self.group: t.Dict[str, exp.Expr] = {}\n        self.source: t.Optional[str] = None\n\n    def _to_s(self, indent: str) -> t.List[str]:\n        lines = [f\"{indent}Aggregations:\"]\n\n        for expression in self.aggregations:\n            lines.append(f\"{indent}  - {expression.sql()}\")\n\n        if self.group:\n            lines.append(f\"{indent}Group:\")\n            for expression in self.group.values():\n                lines.append(f\"{indent}  - {expression.sql()}\")\n        if self.condition:\n            lines.append(f\"{indent}Having:\")\n            lines.append(f\"{indent}  - {self.condition.sql()}\")\n        if self.operands:\n            lines.append(f\"{indent}Operands:\")\n            for expression in self.operands:\n                lines.append(f\"{indent}  - {expression.sql()}\")\n\n        return lines\n\n\nclass Sort(Step):\n    def __init__(self) -> None:\n        super().__init__()\n        self.key = None\n\n    def _to_s(self, indent: str) -> t.List[str]:\n        lines = [f\"{indent}Key:\"]\n\n        for expression in self.key:  # type: ignore\n            lines.append(f\"{indent}  - {expression.sql()}\")\n\n        return lines\n\n\nclass SetOperation(Step):\n    def __init__(\n        self,\n        op: t.Type[exp.Expr],\n        left: str | None,\n        right: str | None,\n        distinct: bool = False,\n    ) -> None:\n        super().__init__()\n        self.op = op\n        self.left = left\n        self.right = right\n        self.distinct = distinct\n\n    @classmethod\n    def from_expression(\n        cls, expression: exp.Expr, ctes: t.Optional[t.Dict[str, Step]] = None\n    ) -> SetOperation:\n        assert isinstance(expression, exp.SetOperation)\n\n        left = Step.from_expression(expression.left, ctes)\n        # SELECT 1 UNION SELECT 2  <-- these subqueries don't have names\n        left.name = left.name or \"left\"\n        right = Step.from_expression(expression.right, ctes)\n        right.name = right.name or \"right\"\n        step = cls(\n            op=expression.__class__,\n            left=left.name,\n            right=right.name,\n            distinct=bool(expression.args.get(\"distinct\")),\n        )\n\n        step.add_dependency(left)\n        step.add_dependency(right)\n\n        limit = expression.args.get(\"limit\")\n\n        if limit:\n            step.limit = int(limit.text(\"expression\"))\n\n        return step\n\n    def _to_s(self, indent: str) -> t.List[str]:\n        lines = []\n        if self.distinct:\n            lines.append(f\"{indent}Distinct: {self.distinct}\")\n        return lines\n\n    @property\n    def type_name(self) -> str:\n        return self.op.__name__\n"
  },
  {
    "path": "sqlglot/py.typed",
    "content": ""
  },
  {
    "path": "sqlglot/schema.py",
    "content": "from __future__ import annotations\n\nimport abc\nimport typing as t\n\nfrom sqlglot import expressions as exp\nfrom sqlglot.dialects.dialect import Dialect\nfrom sqlglot.errors import SchemaError\nfrom sqlglot.helper import dict_depth, first\nfrom sqlglot.trie import TrieResult, in_trie, new_trie\n\nfrom sqlglot.helper import trait\n\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n    from collections.abc import Sequence\n\n    ColumnMapping = t.Union[dict, str, list]\n\n\n@trait\nclass Schema(abc.ABC):\n    \"\"\"Abstract base class for database schemas\"\"\"\n\n    @property\n    def dialect(self) -> t.Optional[Dialect]:\n        \"\"\"\n        Returns None by default. Subclasses that require dialect-specific\n        behavior should override this property.\n        \"\"\"\n        return None\n\n    @abc.abstractmethod\n    def add_table(\n        self,\n        table: exp.Table | str,\n        column_mapping: t.Optional[ColumnMapping] = None,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n        match_depth: bool = True,\n    ) -> None:\n        \"\"\"\n        Register or update a table. Some implementing classes may require column information to also be provided.\n        The added table must have the necessary number of qualifiers in its path to match the schema's nesting level.\n\n        Args:\n            table: the `Table` expression instance or string representing the table.\n            column_mapping: a column mapping that describes the structure of the table.\n            dialect: the SQL dialect that will be used to parse `table` if it's a string.\n            normalize: whether to normalize identifiers according to the dialect of interest.\n            match_depth: whether to enforce that the table must match the schema's depth or not.\n        \"\"\"\n\n    @abc.abstractmethod\n    def column_names(\n        self,\n        table: exp.Table | str,\n        only_visible: bool = False,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> Sequence[str]:\n        \"\"\"\n        Get the column names for a table.\n\n        Args:\n            table: the `Table` expression instance.\n            only_visible: whether to include invisible columns.\n            dialect: the SQL dialect that will be used to parse `table` if it's a string.\n            normalize: whether to normalize identifiers according to the dialect of interest.\n\n        Returns:\n            The sequence of column names.\n        \"\"\"\n\n    @abc.abstractmethod\n    def get_column_type(\n        self,\n        table: exp.Table | str,\n        column: exp.Column | str,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> exp.DataType:\n        \"\"\"\n        Get the `sqlglot.exp.DataType` type of a column in the schema.\n\n        Args:\n            table: the source table.\n            column: the target column.\n            dialect: the SQL dialect that will be used to parse `table` if it's a string.\n            normalize: whether to normalize identifiers according to the dialect of interest.\n\n        Returns:\n            The resulting column type.\n        \"\"\"\n\n    def has_column(\n        self,\n        table: exp.Table | str,\n        column: exp.Column | str,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> bool:\n        \"\"\"\n        Returns whether `column` appears in `table`'s schema.\n\n        Args:\n            table: the source table.\n            column: the target column.\n            dialect: the SQL dialect that will be used to parse `table` if it's a string.\n            normalize: whether to normalize identifiers according to the dialect of interest.\n\n        Returns:\n            True if the column appears in the schema, False otherwise.\n        \"\"\"\n        name = column if isinstance(column, str) else column.name\n        return name in self.column_names(table, dialect=dialect, normalize=normalize)\n\n    def get_udf_type(\n        self,\n        udf: exp.Anonymous | str,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> exp.DataType:\n        \"\"\"\n        Get the return type of a UDF.\n\n        Args:\n            udf: the UDF expression or string.\n            dialect: the SQL dialect for parsing string arguments.\n            normalize: whether to normalize identifiers.\n\n        Returns:\n            The return type as a DataType, or UNKNOWN if not found.\n        \"\"\"\n        return exp.DataType.build(\"unknown\")\n\n    @property\n    @abc.abstractmethod\n    def supported_table_args(self) -> t.Tuple[str, ...]:\n        \"\"\"\n        Table arguments this schema support, e.g. `(\"this\", \"db\", \"catalog\")`\n        \"\"\"\n\n    @property\n    def empty(self) -> bool:\n        \"\"\"Returns whether the schema is empty.\"\"\"\n        return True\n\n\nclass AbstractMappingSchema:\n    def __init__(\n        self,\n        mapping: t.Optional[t.Dict] = None,\n        udf_mapping: t.Optional[t.Dict] = None,\n    ) -> None:\n        self.mapping = mapping or {}\n        self.mapping_trie = new_trie(\n            tuple(reversed(t)) for t in flatten_schema(self.mapping, depth=self.depth())\n        )\n\n        self.udf_mapping = udf_mapping or {}\n        self.udf_trie = new_trie(\n            tuple(reversed(t)) for t in flatten_schema(self.udf_mapping, depth=self.udf_depth())\n        )\n\n        self._supported_table_args: t.Tuple[str, ...] = tuple()\n\n    @property\n    def empty(self) -> bool:\n        return not self.mapping\n\n    def depth(self) -> int:\n        return dict_depth(self.mapping)\n\n    def udf_depth(self) -> int:\n        return dict_depth(self.udf_mapping)\n\n    @property\n    def supported_table_args(self) -> t.Tuple[str, ...]:\n        if not self._supported_table_args and self.mapping:\n            depth = self.depth()\n\n            if not depth:  # None\n                self._supported_table_args = tuple()\n            elif 1 <= depth <= 3:\n                self._supported_table_args = exp.TABLE_PARTS[:depth]\n            else:\n                raise SchemaError(f\"Invalid mapping shape. Depth: {depth}\")\n\n        return self._supported_table_args\n\n    def table_parts(self, table: exp.Table) -> t.List[str]:\n        return [p.name for p in reversed(table.parts)]\n\n    def udf_parts(self, udf: exp.Anonymous) -> t.List[str]:\n        # a.b.c(...) is represented as Dot(Dot(a, b), Anonymous(c, ...))\n        parent = udf.parent\n        parts = [p.name for p in parent.flatten()] if isinstance(parent, exp.Dot) else [udf.name]\n        return list(reversed(parts))[0 : self.udf_depth()]\n\n    def _find_in_trie(\n        self,\n        parts: t.List[str],\n        trie: t.Dict,\n        raise_on_missing: bool,\n    ) -> t.Optional[t.List[str]]:\n        value, trie = in_trie(trie, parts)\n\n        if value == TrieResult.FAILED:\n            return None\n\n        if value == TrieResult.PREFIX:\n            possibilities = flatten_schema(trie)\n\n            if len(possibilities) == 1:\n                parts.extend(possibilities[0])\n            else:\n                if raise_on_missing:\n                    joined_parts = \".\".join(parts)\n                    message = \", \".join(\".\".join(p) for p in possibilities)\n                    raise SchemaError(f\"Ambiguous mapping for {joined_parts}: {message}.\")\n\n                return None\n\n        return parts\n\n    def find(\n        self, table: exp.Table, raise_on_missing: bool = True, ensure_data_types: bool = False\n    ) -> t.Optional[t.Any]:\n        \"\"\"\n        Returns the schema of a given table.\n\n        Args:\n            table: the target table.\n            raise_on_missing: whether to raise in case the schema is not found.\n            ensure_data_types: whether to convert `str` types to their `DataType` equivalents.\n\n        Returns:\n            The schema of the target table.\n        \"\"\"\n        parts = self.table_parts(table)[0 : len(self.supported_table_args)]\n        resolved_parts = self._find_in_trie(parts, self.mapping_trie, raise_on_missing)\n\n        if resolved_parts is None:\n            return None\n\n        return self.nested_get(resolved_parts, raise_on_missing=raise_on_missing)\n\n    def find_udf(self, udf: exp.Anonymous, raise_on_missing: bool = False) -> t.Optional[t.Any]:\n        \"\"\"\n        Returns the return type of a given UDF.\n\n        Args:\n            udf: the target UDF expression.\n            raise_on_missing: whether to raise if the UDF is not found.\n\n        Returns:\n            The return type of the UDF, or None if not found.\n        \"\"\"\n        parts = self.udf_parts(udf)\n        resolved_parts = self._find_in_trie(parts, self.udf_trie, raise_on_missing)\n\n        if resolved_parts is None:\n            return None\n\n        return nested_get(\n            self.udf_mapping,\n            *zip(resolved_parts, reversed(resolved_parts)),\n            raise_on_missing=raise_on_missing,\n        )\n\n    def nested_get(\n        self, parts: Sequence[str], d: t.Optional[dict] = None, raise_on_missing=True\n    ) -> t.Optional[t.Any]:\n        return nested_get(\n            d or self.mapping,\n            *zip(self.supported_table_args, reversed(parts)),\n            raise_on_missing=raise_on_missing,\n        )\n\n\nclass MappingSchema(AbstractMappingSchema, Schema):\n    \"\"\"\n    Schema based on a nested mapping.\n\n    Args:\n        schema: Mapping in one of the following forms:\n            1. {table: {col: type}}\n            2. {db: {table: {col: type}}}\n            3. {catalog: {db: {table: {col: type}}}}\n            4. None - Tables will be added later\n        visible: Optional mapping of which columns in the schema are visible. If not provided, all columns\n            are assumed to be visible. The nesting should mirror that of the schema:\n            1. {table: set(*cols)}}\n            2. {db: {table: set(*cols)}}}\n            3. {catalog: {db: {table: set(*cols)}}}}\n        dialect: The dialect to be used for custom type mappings & parsing string arguments.\n        normalize: Whether to normalize identifier names according to the given dialect or not.\n    \"\"\"\n\n    def __init__(\n        self,\n        schema: t.Optional[t.Dict] = None,\n        visible: t.Optional[t.Dict] = None,\n        dialect: DialectType = None,\n        normalize: bool = True,\n        udf_mapping: t.Optional[t.Dict] = None,\n    ) -> None:\n        self.visible = {} if visible is None else visible\n        self.normalize = normalize\n        self._dialect = Dialect.get_or_raise(dialect)\n        self._type_mapping_cache: t.Dict[str, exp.DataType] = {}\n        self._normalized_table_cache: t.Dict[t.Tuple[exp.Table, DialectType, bool], exp.Table] = {}\n        self._normalized_name_cache: t.Dict[t.Tuple[str, DialectType, bool, bool], str] = {}\n        self._depth = 0\n        schema = {} if schema is None else schema\n        udf_mapping = {} if udf_mapping is None else udf_mapping\n\n        super().__init__(\n            self._normalize(schema) if self.normalize else schema,\n            self._normalize_udfs(udf_mapping) if self.normalize else udf_mapping,\n        )\n\n    @property\n    def dialect(self) -> Dialect:\n        \"\"\"Returns the dialect for this mapping schema.\"\"\"\n        return self._dialect\n\n    @classmethod\n    def from_mapping_schema(cls, mapping_schema: MappingSchema) -> MappingSchema:\n        return MappingSchema(\n            schema=mapping_schema.mapping,\n            visible=mapping_schema.visible,\n            dialect=mapping_schema.dialect,\n            normalize=mapping_schema.normalize,\n            udf_mapping=mapping_schema.udf_mapping,\n        )\n\n    def find(\n        self, table: exp.Table, raise_on_missing: bool = True, ensure_data_types: bool = False\n    ) -> t.Optional[t.Any]:\n        schema = super().find(\n            table, raise_on_missing=raise_on_missing, ensure_data_types=ensure_data_types\n        )\n        if ensure_data_types and isinstance(schema, dict):\n            schema = {\n                col: self._to_data_type(dtype) if isinstance(dtype, str) else dtype\n                for col, dtype in schema.items()\n            }\n\n        return schema\n\n    def copy(self, **kwargs) -> MappingSchema:\n        return MappingSchema(\n            **{  # type: ignore\n                \"schema\": self.mapping.copy(),\n                \"visible\": self.visible.copy(),\n                \"dialect\": self.dialect,\n                \"normalize\": self.normalize,\n                \"udf_mapping\": self.udf_mapping.copy(),\n                **kwargs,\n            }\n        )\n\n    def add_table(\n        self,\n        table: exp.Table | str,\n        column_mapping: t.Optional[ColumnMapping] = None,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n        match_depth: bool = True,\n    ) -> None:\n        \"\"\"\n        Register or update a table. Updates are only performed if a new column mapping is provided.\n        The added table must have the necessary number of qualifiers in its path to match the schema's nesting level.\n\n        Args:\n            table: the `Table` expression instance or string representing the table.\n            column_mapping: a column mapping that describes the structure of the table.\n            dialect: the SQL dialect that will be used to parse `table` if it's a string.\n            normalize: whether to normalize identifiers according to the dialect of interest.\n            match_depth: whether to enforce that the table must match the schema's depth or not.\n        \"\"\"\n        normalized_table = self._normalize_table(table, dialect=dialect, normalize=normalize)\n\n        if match_depth and not self.empty and len(normalized_table.parts) != self.depth():\n            raise SchemaError(\n                f\"Table {normalized_table.sql(dialect=self.dialect)} must match the \"\n                f\"schema's nesting level: {self.depth()}.\"\n            )\n\n        normalized_column_mapping = {\n            self._normalize_name(key, dialect=dialect, normalize=normalize): value\n            for key, value in ensure_column_mapping(column_mapping).items()\n        }\n\n        schema = self.find(normalized_table, raise_on_missing=False)\n        if schema and not normalized_column_mapping:\n            return\n\n        parts = self.table_parts(normalized_table)\n\n        nested_set(self.mapping, tuple(reversed(parts)), normalized_column_mapping)\n        new_trie([parts], self.mapping_trie)\n\n    def column_names(\n        self,\n        table: exp.Table | str,\n        only_visible: bool = False,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> t.List[str]:\n        normalized_table = self._normalize_table(table, dialect=dialect, normalize=normalize)\n\n        schema = self.find(normalized_table)\n        if schema is None:\n            return []\n\n        if not only_visible or not self.visible:\n            return list(schema)\n\n        visible = self.nested_get(self.table_parts(normalized_table), self.visible) or []\n        return [col for col in schema if col in visible]\n\n    def get_column_type(\n        self,\n        table: exp.Table | str,\n        column: exp.Column | str,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> exp.DataType:\n        normalized_table = self._normalize_table(table, dialect=dialect, normalize=normalize)\n\n        normalized_column_name = self._normalize_name(\n            column if isinstance(column, str) else column.this, dialect=dialect, normalize=normalize\n        )\n\n        table_schema = self.find(normalized_table, raise_on_missing=False)\n        if table_schema:\n            column_type = table_schema.get(normalized_column_name)\n\n            if isinstance(column_type, exp.DataType):\n                return column_type\n            elif isinstance(column_type, str):\n                return self._to_data_type(column_type, dialect=dialect)\n\n        return exp.DataType.build(\"unknown\")\n\n    def get_udf_type(\n        self,\n        udf: exp.Anonymous | str,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> exp.DataType:\n        \"\"\"\n        Get the return type of a UDF.\n\n        Args:\n            udf: the UDF expression or string (e.g., \"db.my_func()\").\n            dialect: the SQL dialect for parsing string arguments.\n            normalize: whether to normalize identifiers.\n\n        Returns:\n            The return type as a DataType, or UNKNOWN if not found.\n        \"\"\"\n        parts = self._normalize_udf(udf, dialect=dialect, normalize=normalize)\n        resolved_parts = self._find_in_trie(parts, self.udf_trie, raise_on_missing=False)\n\n        if resolved_parts is None:\n            return exp.DataType.build(\"unknown\")\n\n        udf_type = nested_get(\n            self.udf_mapping,\n            *zip(resolved_parts, reversed(resolved_parts)),\n            raise_on_missing=False,\n        )\n\n        if isinstance(udf_type, exp.DataType):\n            return udf_type\n        elif isinstance(udf_type, str):\n            return self._to_data_type(udf_type, dialect=dialect)\n\n        return exp.DataType.build(\"unknown\")\n\n    def has_column(\n        self,\n        table: exp.Table | str,\n        column: exp.Column | str,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> bool:\n        normalized_table = self._normalize_table(table, dialect=dialect, normalize=normalize)\n\n        normalized_column_name = self._normalize_name(\n            column if isinstance(column, str) else column.this, dialect=dialect, normalize=normalize\n        )\n\n        table_schema = self.find(normalized_table, raise_on_missing=False)\n        return normalized_column_name in table_schema if table_schema else False\n\n    def _normalize(self, schema: t.Dict) -> t.Dict:\n        \"\"\"\n        Normalizes all identifiers in the schema.\n\n        Args:\n            schema: the schema to normalize.\n\n        Returns:\n            The normalized schema mapping.\n        \"\"\"\n        normalized_mapping: t.Dict = {}\n        flattened_schema = flatten_schema(schema)\n        error_msg = \"Table {} must match the schema's nesting level: {}.\"\n\n        for keys in flattened_schema:\n            columns = nested_get(schema, *zip(keys, keys))\n\n            if not isinstance(columns, dict):\n                raise SchemaError(error_msg.format(\".\".join(keys[:-1]), len(flattened_schema[0])))\n            if not columns:\n                raise SchemaError(f\"Table {'.'.join(keys[:-1])} must have at least one column\")\n            if isinstance(first(columns.values()), dict):\n                raise SchemaError(\n                    error_msg.format(\n                        \".\".join(keys + flatten_schema(columns)[0]), len(flattened_schema[0])\n                    ),\n                )\n\n            normalized_keys = [self._normalize_name(key, is_table=True) for key in keys]\n            for column_name, column_type in columns.items():\n                nested_set(\n                    normalized_mapping,\n                    normalized_keys + [self._normalize_name(column_name)],\n                    column_type,\n                )\n\n        return normalized_mapping\n\n    def _normalize_udfs(self, udfs: t.Dict) -> t.Dict:\n        \"\"\"\n        Normalizes all identifiers in the UDF mapping.\n\n        Args:\n            udfs: the UDF mapping to normalize.\n\n        Returns:\n            The normalized UDF mapping.\n        \"\"\"\n        normalized_mapping: t.Dict = {}\n\n        for keys in flatten_schema(udfs, depth=dict_depth(udfs)):\n            udf_type = nested_get(udfs, *zip(keys, keys))\n            normalized_keys = [self._normalize_name(key, is_table=True) for key in keys]\n            nested_set(normalized_mapping, normalized_keys, udf_type)\n\n        return normalized_mapping\n\n    def _normalize_udf(\n        self,\n        udf: exp.Anonymous | str,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> t.List[str]:\n        \"\"\"\n        Extract and normalize UDF parts for lookup.\n\n        Args:\n            udf: the UDF expression or qualified string (e.g., \"db.my_func()\").\n            dialect: the SQL dialect for parsing.\n            normalize: whether to normalize identifiers.\n\n        Returns:\n            A list of normalized UDF parts (reversed for trie lookup).\n        \"\"\"\n        dialect = dialect or self.dialect\n        normalize = self.normalize if normalize is None else normalize\n\n        if isinstance(udf, str):\n            parsed: exp.Expr = exp.maybe_parse(udf, dialect=dialect)\n\n            if isinstance(parsed, exp.Anonymous):\n                udf = parsed\n            elif isinstance(parsed, exp.Dot) and isinstance(parsed.expression, exp.Anonymous):\n                udf = parsed.expression\n            else:\n                raise SchemaError(f\"Unable to parse UDF from: {udf!r}\")\n        parts = self.udf_parts(udf)\n\n        if normalize:\n            parts = [self._normalize_name(part, dialect=dialect, is_table=True) for part in parts]\n\n        return parts\n\n    def _normalize_table(\n        self,\n        table: exp.Table | str,\n        dialect: DialectType = None,\n        normalize: t.Optional[bool] = None,\n    ) -> exp.Table:\n        dialect = dialect or self.dialect\n        normalize = self.normalize if normalize is None else normalize\n\n        # Cache normalized tables by object id for exp.Table inputs\n        # This is effective when the same Table object is looked up multiple times\n        if isinstance(table, exp.Table) and (\n            cached := self._normalized_table_cache.get((table, dialect, normalize))\n        ):\n            return cached\n\n        normalized_table = exp.maybe_parse(table, into=exp.Table, dialect=dialect, copy=normalize)\n\n        if normalize:\n            for part in normalized_table.parts:\n                if isinstance(part, exp.Identifier):\n                    part.replace(\n                        normalize_name(part, dialect=dialect, is_table=True, normalize=normalize)\n                    )\n\n        self._normalized_table_cache[(normalized_table, dialect, normalize)] = normalized_table\n        return normalized_table\n\n    def _normalize_name(\n        self,\n        name: str | exp.Identifier,\n        dialect: DialectType = None,\n        is_table: bool = False,\n        normalize: t.Optional[bool] = None,\n    ) -> str:\n        normalize = self.normalize if normalize is None else normalize\n\n        dialect = dialect or self.dialect\n        name_str = name if isinstance(name, str) else name.name\n        cache_key = (name_str, dialect, is_table, normalize)\n\n        if cached := self._normalized_name_cache.get(cache_key):\n            return cached\n\n        result = normalize_name(\n            name,\n            dialect=dialect,\n            is_table=is_table,\n            normalize=normalize,\n        ).name\n\n        self._normalized_name_cache[cache_key] = result\n        return result\n\n    def depth(self) -> int:\n        if not self.empty and not self._depth:\n            # The columns themselves are a mapping, but we don't want to include those\n            self._depth = super().depth() - 1\n        return self._depth\n\n    def _to_data_type(self, schema_type: str, dialect: DialectType = None) -> exp.DataType:\n        \"\"\"\n        Convert a type represented as a string to the corresponding `sqlglot.exp.DataType` object.\n\n        Args:\n            schema_type: the type we want to convert.\n            dialect: the SQL dialect that will be used to parse `schema_type`, if needed.\n\n        Returns:\n            The resulting expression type.\n        \"\"\"\n        if schema_type not in self._type_mapping_cache:\n            dialect = Dialect.get_or_raise(dialect) if dialect else self.dialect\n            udt = dialect.SUPPORTS_USER_DEFINED_TYPES\n\n            try:\n                expression = exp.DataType.build(schema_type, dialect=dialect, udt=udt)\n                expression.transform(dialect.normalize_identifier, copy=False)\n                self._type_mapping_cache[schema_type] = expression\n            except AttributeError:\n                in_dialect = f\" in dialect {dialect}\" if dialect else \"\"\n                raise SchemaError(f\"Failed to build type '{schema_type}'{in_dialect}.\")\n\n        return self._type_mapping_cache[schema_type]\n\n\ndef normalize_name(\n    identifier: str | exp.Identifier,\n    dialect: DialectType = None,\n    is_table: bool = False,\n    normalize: t.Optional[bool] = True,\n) -> exp.Identifier:\n    if isinstance(identifier, str):\n        identifier = exp.parse_identifier(identifier, dialect=dialect)\n\n    if not normalize:\n        return identifier\n\n    # this is used for normalize_identifier, bigquery has special rules pertaining tables\n    identifier.meta[\"is_table\"] = is_table\n    return Dialect.get_or_raise(dialect).normalize_identifier(identifier)\n\n\ndef ensure_schema(schema: Schema | t.Optional[t.Dict], **kwargs: t.Any) -> Schema:\n    if isinstance(schema, Schema):\n        return schema\n\n    return MappingSchema(schema, **kwargs)\n\n\ndef ensure_column_mapping(mapping: t.Optional[ColumnMapping]) -> t.Dict:\n    if mapping is None:\n        return {}\n    elif isinstance(mapping, dict):\n        return mapping\n    elif isinstance(mapping, str):\n        col_name_type_strs = [x.strip() for x in mapping.split(\",\")]\n        return {\n            name_type_str.split(\":\")[0].strip(): name_type_str.split(\":\")[1].strip()\n            for name_type_str in col_name_type_strs\n        }\n    elif isinstance(mapping, list):\n        return {x.strip(): None for x in mapping}\n\n    raise ValueError(f\"Invalid mapping provided: {type(mapping)}\")\n\n\ndef flatten_schema(\n    schema: t.Dict, depth: t.Optional[int] = None, keys: t.Optional[t.List[str]] = None\n) -> t.List[t.List[str]]:\n    tables = []\n    keys = keys or []\n    depth = dict_depth(schema) - 1 if depth is None else depth\n\n    for k, v in schema.items():\n        if depth == 1 or not isinstance(v, dict):\n            tables.append(keys + [k])\n        elif depth >= 2:\n            tables.extend(flatten_schema(v, depth - 1, keys + [k]))\n\n    return tables\n\n\ndef nested_get(\n    d: t.Dict, *path: t.Tuple[str, str], raise_on_missing: bool = True\n) -> t.Optional[t.Any]:\n    \"\"\"\n    Get a value for a nested dictionary.\n\n    Args:\n        d: the dictionary to search.\n        *path: tuples of (name, key), where:\n            `key` is the key in the dictionary to get.\n            `name` is a string to use in the error if `key` isn't found.\n\n    Returns:\n        The value or None if it doesn't exist.\n    \"\"\"\n    result: t.Any = d\n    for name, key in path:\n        result = result.get(key)\n        if result is None:\n            if raise_on_missing:\n                name = \"table\" if name == \"this\" else name\n                raise ValueError(f\"Unknown {name}: {key}\")\n            return None\n\n    return result\n\n\ndef nested_set(d: dict, keys: Sequence[str], value: t.Any) -> dict:\n    \"\"\"\n    In-place set a value for a nested dictionary\n\n    Example:\n        >>> nested_set({}, [\"top_key\", \"second_key\"], \"value\")\n        {'top_key': {'second_key': 'value'}}\n\n        >>> nested_set({\"top_key\": {\"third_key\": \"third_value\"}}, [\"top_key\", \"second_key\"], \"value\")\n        {'top_key': {'third_key': 'third_value', 'second_key': 'value'}}\n\n    Args:\n        d: dictionary to update.\n        keys: the keys that makeup the path to `value`.\n        value: the value to set in the dictionary for the given key path.\n\n    Returns:\n        The (possibly) updated dictionary.\n    \"\"\"\n    if not keys:\n        return d\n\n    if len(keys) == 1:\n        d[keys[0]] = value\n        return d\n\n    subd = d\n    for key in keys[:-1]:\n        if key not in subd:\n            subd = subd.setdefault(key, {})\n        else:\n            subd = subd[key]\n\n    subd[keys[-1]] = value\n    return d\n"
  },
  {
    "path": "sqlglot/serde.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import expressions as exp\n\n\nINDEX = \"i\"\nARG_KEY = \"k\"\nIS_ARRAY = \"a\"\nCLASS = \"c\"\nTYPE = \"t\"\nCOMMENTS = \"o\"\nMETA = \"m\"\nVALUE = \"v\"\nDATA_TYPE = \"DataType.Type\"\n\n\ndef dump(expression: exp.Expr) -> t.List[t.Dict[str, t.Any]]:\n    \"\"\"\n    Dump an Expr into a JSON serializable List.\n    \"\"\"\n    i = 0\n    payloads = []\n    stack: t.List[t.Tuple[t.Any, t.Optional[int], t.Optional[str], bool]] = [\n        (expression, None, None, False)\n    ]\n\n    while stack:\n        node, index, arg_key, is_array = stack.pop()\n\n        payload: t.Dict[str, t.Any] = {}\n\n        if index is not None:\n            payload[INDEX] = index\n        if arg_key is not None:\n            payload[ARG_KEY] = arg_key\n        if is_array:\n            payload[IS_ARRAY] = is_array\n\n        payloads.append(payload)\n\n        if hasattr(node, \"parent\"):\n            klass = node.__class__.__qualname__\n\n            if node.__class__.__module__ != exp.__name__:\n                klass = f\"{node.__module__}.{klass}\"\n\n            payload[CLASS] = klass\n\n            if node.type:\n                payload[TYPE] = dump(node.type)\n            if node.comments:\n                payload[COMMENTS] = node.comments\n            if node._meta is not None:\n                payload[META] = node._meta\n            if node.args:\n                for k, vs in reversed(node.args.items()):\n                    if type(vs) is list:\n                        for v in reversed(vs):\n                            stack.append((v, i, k, True))\n                    elif vs is not None:\n                        stack.append((vs, i, k, False))\n        elif type(node) is exp.DType:\n            payload[CLASS] = DATA_TYPE\n            payload[VALUE] = node.value\n        else:\n            payload[VALUE] = node\n\n        i += 1\n\n    return payloads\n\n\ndef load(\n    payloads: t.Optional[t.List[t.Dict[str, t.Any]]],\n) -> t.Optional[exp.Expr | exp.DType]:\n    \"\"\"\n    Load a list of dicts generated by dump into an Expr.\n    \"\"\"\n\n    if not payloads:\n        return None\n\n    payload, *tail = payloads\n    root = _load(payload)\n    nodes: t.List[object] = [root]\n    for payload in tail:\n        if CLASS in payload:\n            node: object = _load(payload)\n        else:\n            node = payload[VALUE]\n\n        nodes.append(node)\n        parent = nodes[payload[INDEX]]\n        arg_key = payload[ARG_KEY]\n\n        if payload.get(IS_ARRAY):\n            parent.append(arg_key, node)\n        else:\n            parent.set(arg_key, node)\n\n    return root\n\n\ndef _load(payload: t.Dict[str, t.Any]) -> exp.Expr | exp.DType:\n    class_name = payload[CLASS]\n\n    if class_name == DATA_TYPE:\n        return exp.DType(payload[VALUE])\n\n    if \".\" in class_name:\n        module_path, class_name = class_name.rsplit(\".\", maxsplit=1)\n        module = __import__(module_path, fromlist=[class_name])\n    else:\n        module = exp\n\n    expression = getattr(module, class_name)()\n    expression._type = load(payload.get(TYPE))\n    expression.comments = payload.get(COMMENTS)\n    expression._meta = payload.get(META)\n    return expression\n"
  },
  {
    "path": "sqlglot/time.py",
    "content": "import typing as t\nimport datetime\n\n# The generic time format is based on python time.strftime.\n# https://docs.python.org/3/library/time.html#time.strftime\nfrom sqlglot.trie import TrieResult, in_trie, new_trie\n\n\ndef format_time(\n    string: str, mapping: t.Dict[str, str], trie: t.Optional[t.Dict] = None\n) -> t.Optional[str]:\n    \"\"\"\n    Converts a time string given a mapping.\n\n    Examples:\n        >>> format_time(\"%Y\", {\"%Y\": \"YYYY\"})\n        'YYYY'\n\n        Args:\n            mapping: dictionary of time format to target time format.\n            trie: optional trie, can be passed in for performance.\n\n        Returns:\n            The converted time string.\n    \"\"\"\n    if not string:\n        return None\n\n    start = 0\n    end = 1\n    size = len(string)\n    trie = trie or new_trie(mapping)\n    current = trie\n    chunks = []\n    sym = None\n\n    while end <= size:\n        chars = string[start:end]\n        result, current = in_trie(current, chars[-1])\n\n        if result == TrieResult.FAILED:\n            if sym:\n                end -= 1\n                chars = sym\n                sym = None\n            else:\n                chars = chars[0]\n                end = start + 1\n\n            start += len(chars)\n            chunks.append(chars)\n            current = trie\n        elif result == TrieResult.EXISTS:\n            sym = chars\n\n        end += 1\n\n        if result != TrieResult.FAILED and end > size:\n            chunks.append(chars)\n\n    return \"\".join(mapping.get(chars, chars) for chars in chunks)\n\n\nTIMEZONES = {\n    tz.lower()\n    for tz in (\n        \"Africa/Abidjan\",\n        \"Africa/Accra\",\n        \"Africa/Addis_Ababa\",\n        \"Africa/Algiers\",\n        \"Africa/Asmara\",\n        \"Africa/Asmera\",\n        \"Africa/Bamako\",\n        \"Africa/Bangui\",\n        \"Africa/Banjul\",\n        \"Africa/Bissau\",\n        \"Africa/Blantyre\",\n        \"Africa/Brazzaville\",\n        \"Africa/Bujumbura\",\n        \"Africa/Cairo\",\n        \"Africa/Casablanca\",\n        \"Africa/Ceuta\",\n        \"Africa/Conakry\",\n        \"Africa/Dakar\",\n        \"Africa/Dar_es_Salaam\",\n        \"Africa/Djibouti\",\n        \"Africa/Douala\",\n        \"Africa/El_Aaiun\",\n        \"Africa/Freetown\",\n        \"Africa/Gaborone\",\n        \"Africa/Harare\",\n        \"Africa/Johannesburg\",\n        \"Africa/Juba\",\n        \"Africa/Kampala\",\n        \"Africa/Khartoum\",\n        \"Africa/Kigali\",\n        \"Africa/Kinshasa\",\n        \"Africa/Lagos\",\n        \"Africa/Libreville\",\n        \"Africa/Lome\",\n        \"Africa/Luanda\",\n        \"Africa/Lubumbashi\",\n        \"Africa/Lusaka\",\n        \"Africa/Malabo\",\n        \"Africa/Maputo\",\n        \"Africa/Maseru\",\n        \"Africa/Mbabane\",\n        \"Africa/Mogadishu\",\n        \"Africa/Monrovia\",\n        \"Africa/Nairobi\",\n        \"Africa/Ndjamena\",\n        \"Africa/Niamey\",\n        \"Africa/Nouakchott\",\n        \"Africa/Ouagadougou\",\n        \"Africa/Porto-Novo\",\n        \"Africa/Sao_Tome\",\n        \"Africa/Timbuktu\",\n        \"Africa/Tripoli\",\n        \"Africa/Tunis\",\n        \"Africa/Windhoek\",\n        \"America/Adak\",\n        \"America/Anchorage\",\n        \"America/Anguilla\",\n        \"America/Antigua\",\n        \"America/Araguaina\",\n        \"America/Argentina/Buenos_Aires\",\n        \"America/Argentina/Catamarca\",\n        \"America/Argentina/ComodRivadavia\",\n        \"America/Argentina/Cordoba\",\n        \"America/Argentina/Jujuy\",\n        \"America/Argentina/La_Rioja\",\n        \"America/Argentina/Mendoza\",\n        \"America/Argentina/Rio_Gallegos\",\n        \"America/Argentina/Salta\",\n        \"America/Argentina/San_Juan\",\n        \"America/Argentina/San_Luis\",\n        \"America/Argentina/Tucuman\",\n        \"America/Argentina/Ushuaia\",\n        \"America/Aruba\",\n        \"America/Asuncion\",\n        \"America/Atikokan\",\n        \"America/Atka\",\n        \"America/Bahia\",\n        \"America/Bahia_Banderas\",\n        \"America/Barbados\",\n        \"America/Belem\",\n        \"America/Belize\",\n        \"America/Blanc-Sablon\",\n        \"America/Boa_Vista\",\n        \"America/Bogota\",\n        \"America/Boise\",\n        \"America/Buenos_Aires\",\n        \"America/Cambridge_Bay\",\n        \"America/Campo_Grande\",\n        \"America/Cancun\",\n        \"America/Caracas\",\n        \"America/Catamarca\",\n        \"America/Cayenne\",\n        \"America/Cayman\",\n        \"America/Chicago\",\n        \"America/Chihuahua\",\n        \"America/Ciudad_Juarez\",\n        \"America/Coral_Harbour\",\n        \"America/Cordoba\",\n        \"America/Costa_Rica\",\n        \"America/Creston\",\n        \"America/Cuiaba\",\n        \"America/Curacao\",\n        \"America/Danmarkshavn\",\n        \"America/Dawson\",\n        \"America/Dawson_Creek\",\n        \"America/Denver\",\n        \"America/Detroit\",\n        \"America/Dominica\",\n        \"America/Edmonton\",\n        \"America/Eirunepe\",\n        \"America/El_Salvador\",\n        \"America/Ensenada\",\n        \"America/Fort_Nelson\",\n        \"America/Fort_Wayne\",\n        \"America/Fortaleza\",\n        \"America/Glace_Bay\",\n        \"America/Godthab\",\n        \"America/Goose_Bay\",\n        \"America/Grand_Turk\",\n        \"America/Grenada\",\n        \"America/Guadeloupe\",\n        \"America/Guatemala\",\n        \"America/Guayaquil\",\n        \"America/Guyana\",\n        \"America/Halifax\",\n        \"America/Havana\",\n        \"America/Hermosillo\",\n        \"America/Indiana/Indianapolis\",\n        \"America/Indiana/Knox\",\n        \"America/Indiana/Marengo\",\n        \"America/Indiana/Petersburg\",\n        \"America/Indiana/Tell_City\",\n        \"America/Indiana/Vevay\",\n        \"America/Indiana/Vincennes\",\n        \"America/Indiana/Winamac\",\n        \"America/Indianapolis\",\n        \"America/Inuvik\",\n        \"America/Iqaluit\",\n        \"America/Jamaica\",\n        \"America/Jujuy\",\n        \"America/Juneau\",\n        \"America/Kentucky/Louisville\",\n        \"America/Kentucky/Monticello\",\n        \"America/Knox_IN\",\n        \"America/Kralendijk\",\n        \"America/La_Paz\",\n        \"America/Lima\",\n        \"America/Los_Angeles\",\n        \"America/Louisville\",\n        \"America/Lower_Princes\",\n        \"America/Maceio\",\n        \"America/Managua\",\n        \"America/Manaus\",\n        \"America/Marigot\",\n        \"America/Martinique\",\n        \"America/Matamoros\",\n        \"America/Mazatlan\",\n        \"America/Mendoza\",\n        \"America/Menominee\",\n        \"America/Merida\",\n        \"America/Metlakatla\",\n        \"America/Mexico_City\",\n        \"America/Miquelon\",\n        \"America/Moncton\",\n        \"America/Monterrey\",\n        \"America/Montevideo\",\n        \"America/Montreal\",\n        \"America/Montserrat\",\n        \"America/Nassau\",\n        \"America/New_York\",\n        \"America/Nipigon\",\n        \"America/Nome\",\n        \"America/Noronha\",\n        \"America/North_Dakota/Beulah\",\n        \"America/North_Dakota/Center\",\n        \"America/North_Dakota/New_Salem\",\n        \"America/Nuuk\",\n        \"America/Ojinaga\",\n        \"America/Panama\",\n        \"America/Pangnirtung\",\n        \"America/Paramaribo\",\n        \"America/Phoenix\",\n        \"America/Port-au-Prince\",\n        \"America/Port_of_Spain\",\n        \"America/Porto_Acre\",\n        \"America/Porto_Velho\",\n        \"America/Puerto_Rico\",\n        \"America/Punta_Arenas\",\n        \"America/Rainy_River\",\n        \"America/Rankin_Inlet\",\n        \"America/Recife\",\n        \"America/Regina\",\n        \"America/Resolute\",\n        \"America/Rio_Branco\",\n        \"America/Rosario\",\n        \"America/Santa_Isabel\",\n        \"America/Santarem\",\n        \"America/Santiago\",\n        \"America/Santo_Domingo\",\n        \"America/Sao_Paulo\",\n        \"America/Scoresbysund\",\n        \"America/Shiprock\",\n        \"America/Sitka\",\n        \"America/St_Barthelemy\",\n        \"America/St_Johns\",\n        \"America/St_Kitts\",\n        \"America/St_Lucia\",\n        \"America/St_Thomas\",\n        \"America/St_Vincent\",\n        \"America/Swift_Current\",\n        \"America/Tegucigalpa\",\n        \"America/Thule\",\n        \"America/Thunder_Bay\",\n        \"America/Tijuana\",\n        \"America/Toronto\",\n        \"America/Tortola\",\n        \"America/Vancouver\",\n        \"America/Virgin\",\n        \"America/Whitehorse\",\n        \"America/Winnipeg\",\n        \"America/Yakutat\",\n        \"America/Yellowknife\",\n        \"Antarctica/Casey\",\n        \"Antarctica/Davis\",\n        \"Antarctica/DumontDUrville\",\n        \"Antarctica/Macquarie\",\n        \"Antarctica/Mawson\",\n        \"Antarctica/McMurdo\",\n        \"Antarctica/Palmer\",\n        \"Antarctica/Rothera\",\n        \"Antarctica/South_Pole\",\n        \"Antarctica/Syowa\",\n        \"Antarctica/Troll\",\n        \"Antarctica/Vostok\",\n        \"Arctic/Longyearbyen\",\n        \"Asia/Aden\",\n        \"Asia/Almaty\",\n        \"Asia/Amman\",\n        \"Asia/Anadyr\",\n        \"Asia/Aqtau\",\n        \"Asia/Aqtobe\",\n        \"Asia/Ashgabat\",\n        \"Asia/Ashkhabad\",\n        \"Asia/Atyrau\",\n        \"Asia/Baghdad\",\n        \"Asia/Bahrain\",\n        \"Asia/Baku\",\n        \"Asia/Bangkok\",\n        \"Asia/Barnaul\",\n        \"Asia/Beirut\",\n        \"Asia/Bishkek\",\n        \"Asia/Brunei\",\n        \"Asia/Calcutta\",\n        \"Asia/Chita\",\n        \"Asia/Choibalsan\",\n        \"Asia/Chongqing\",\n        \"Asia/Chungking\",\n        \"Asia/Colombo\",\n        \"Asia/Dacca\",\n        \"Asia/Damascus\",\n        \"Asia/Dhaka\",\n        \"Asia/Dili\",\n        \"Asia/Dubai\",\n        \"Asia/Dushanbe\",\n        \"Asia/Famagusta\",\n        \"Asia/Gaza\",\n        \"Asia/Harbin\",\n        \"Asia/Hebron\",\n        \"Asia/Ho_Chi_Minh\",\n        \"Asia/Hong_Kong\",\n        \"Asia/Hovd\",\n        \"Asia/Irkutsk\",\n        \"Asia/Istanbul\",\n        \"Asia/Jakarta\",\n        \"Asia/Jayapura\",\n        \"Asia/Jerusalem\",\n        \"Asia/Kabul\",\n        \"Asia/Kamchatka\",\n        \"Asia/Karachi\",\n        \"Asia/Kashgar\",\n        \"Asia/Kathmandu\",\n        \"Asia/Katmandu\",\n        \"Asia/Khandyga\",\n        \"Asia/Kolkata\",\n        \"Asia/Krasnoyarsk\",\n        \"Asia/Kuala_Lumpur\",\n        \"Asia/Kuching\",\n        \"Asia/Kuwait\",\n        \"Asia/Macao\",\n        \"Asia/Macau\",\n        \"Asia/Magadan\",\n        \"Asia/Makassar\",\n        \"Asia/Manila\",\n        \"Asia/Muscat\",\n        \"Asia/Nicosia\",\n        \"Asia/Novokuznetsk\",\n        \"Asia/Novosibirsk\",\n        \"Asia/Omsk\",\n        \"Asia/Oral\",\n        \"Asia/Phnom_Penh\",\n        \"Asia/Pontianak\",\n        \"Asia/Pyongyang\",\n        \"Asia/Qatar\",\n        \"Asia/Qostanay\",\n        \"Asia/Qyzylorda\",\n        \"Asia/Rangoon\",\n        \"Asia/Riyadh\",\n        \"Asia/Saigon\",\n        \"Asia/Sakhalin\",\n        \"Asia/Samarkand\",\n        \"Asia/Seoul\",\n        \"Asia/Shanghai\",\n        \"Asia/Singapore\",\n        \"Asia/Srednekolymsk\",\n        \"Asia/Taipei\",\n        \"Asia/Tashkent\",\n        \"Asia/Tbilisi\",\n        \"Asia/Tehran\",\n        \"Asia/Tel_Aviv\",\n        \"Asia/Thimbu\",\n        \"Asia/Thimphu\",\n        \"Asia/Tokyo\",\n        \"Asia/Tomsk\",\n        \"Asia/Ujung_Pandang\",\n        \"Asia/Ulaanbaatar\",\n        \"Asia/Ulan_Bator\",\n        \"Asia/Urumqi\",\n        \"Asia/Ust-Nera\",\n        \"Asia/Vientiane\",\n        \"Asia/Vladivostok\",\n        \"Asia/Yakutsk\",\n        \"Asia/Yangon\",\n        \"Asia/Yekaterinburg\",\n        \"Asia/Yerevan\",\n        \"Atlantic/Azores\",\n        \"Atlantic/Bermuda\",\n        \"Atlantic/Canary\",\n        \"Atlantic/Cape_Verde\",\n        \"Atlantic/Faeroe\",\n        \"Atlantic/Faroe\",\n        \"Atlantic/Jan_Mayen\",\n        \"Atlantic/Madeira\",\n        \"Atlantic/Reykjavik\",\n        \"Atlantic/South_Georgia\",\n        \"Atlantic/St_Helena\",\n        \"Atlantic/Stanley\",\n        \"Australia/ACT\",\n        \"Australia/Adelaide\",\n        \"Australia/Brisbane\",\n        \"Australia/Broken_Hill\",\n        \"Australia/Canberra\",\n        \"Australia/Currie\",\n        \"Australia/Darwin\",\n        \"Australia/Eucla\",\n        \"Australia/Hobart\",\n        \"Australia/LHI\",\n        \"Australia/Lindeman\",\n        \"Australia/Lord_Howe\",\n        \"Australia/Melbourne\",\n        \"Australia/NSW\",\n        \"Australia/North\",\n        \"Australia/Perth\",\n        \"Australia/Queensland\",\n        \"Australia/South\",\n        \"Australia/Sydney\",\n        \"Australia/Tasmania\",\n        \"Australia/Victoria\",\n        \"Australia/West\",\n        \"Australia/Yancowinna\",\n        \"Brazil/Acre\",\n        \"Brazil/DeNoronha\",\n        \"Brazil/East\",\n        \"Brazil/West\",\n        \"CET\",\n        \"CST6CDT\",\n        \"Canada/Atlantic\",\n        \"Canada/Central\",\n        \"Canada/Eastern\",\n        \"Canada/Mountain\",\n        \"Canada/Newfoundland\",\n        \"Canada/Pacific\",\n        \"Canada/Saskatchewan\",\n        \"Canada/Yukon\",\n        \"Chile/Continental\",\n        \"Chile/EasterIsland\",\n        \"Cuba\",\n        \"EET\",\n        \"EST\",\n        \"EST5EDT\",\n        \"Egypt\",\n        \"Eire\",\n        \"Etc/GMT\",\n        \"Etc/GMT+0\",\n        \"Etc/GMT+1\",\n        \"Etc/GMT+10\",\n        \"Etc/GMT+11\",\n        \"Etc/GMT+12\",\n        \"Etc/GMT+2\",\n        \"Etc/GMT+3\",\n        \"Etc/GMT+4\",\n        \"Etc/GMT+5\",\n        \"Etc/GMT+6\",\n        \"Etc/GMT+7\",\n        \"Etc/GMT+8\",\n        \"Etc/GMT+9\",\n        \"Etc/GMT-0\",\n        \"Etc/GMT-1\",\n        \"Etc/GMT-10\",\n        \"Etc/GMT-11\",\n        \"Etc/GMT-12\",\n        \"Etc/GMT-13\",\n        \"Etc/GMT-14\",\n        \"Etc/GMT-2\",\n        \"Etc/GMT-3\",\n        \"Etc/GMT-4\",\n        \"Etc/GMT-5\",\n        \"Etc/GMT-6\",\n        \"Etc/GMT-7\",\n        \"Etc/GMT-8\",\n        \"Etc/GMT-9\",\n        \"Etc/GMT0\",\n        \"Etc/Greenwich\",\n        \"Etc/UCT\",\n        \"Etc/UTC\",\n        \"Etc/Universal\",\n        \"Etc/Zulu\",\n        \"Europe/Amsterdam\",\n        \"Europe/Andorra\",\n        \"Europe/Astrakhan\",\n        \"Europe/Athens\",\n        \"Europe/Belfast\",\n        \"Europe/Belgrade\",\n        \"Europe/Berlin\",\n        \"Europe/Bratislava\",\n        \"Europe/Brussels\",\n        \"Europe/Bucharest\",\n        \"Europe/Budapest\",\n        \"Europe/Busingen\",\n        \"Europe/Chisinau\",\n        \"Europe/Copenhagen\",\n        \"Europe/Dublin\",\n        \"Europe/Gibraltar\",\n        \"Europe/Guernsey\",\n        \"Europe/Helsinki\",\n        \"Europe/Isle_of_Man\",\n        \"Europe/Istanbul\",\n        \"Europe/Jersey\",\n        \"Europe/Kaliningrad\",\n        \"Europe/Kiev\",\n        \"Europe/Kirov\",\n        \"Europe/Kyiv\",\n        \"Europe/Lisbon\",\n        \"Europe/Ljubljana\",\n        \"Europe/London\",\n        \"Europe/Luxembourg\",\n        \"Europe/Madrid\",\n        \"Europe/Malta\",\n        \"Europe/Mariehamn\",\n        \"Europe/Minsk\",\n        \"Europe/Monaco\",\n        \"Europe/Moscow\",\n        \"Europe/Nicosia\",\n        \"Europe/Oslo\",\n        \"Europe/Paris\",\n        \"Europe/Podgorica\",\n        \"Europe/Prague\",\n        \"Europe/Riga\",\n        \"Europe/Rome\",\n        \"Europe/Samara\",\n        \"Europe/San_Marino\",\n        \"Europe/Sarajevo\",\n        \"Europe/Saratov\",\n        \"Europe/Simferopol\",\n        \"Europe/Skopje\",\n        \"Europe/Sofia\",\n        \"Europe/Stockholm\",\n        \"Europe/Tallinn\",\n        \"Europe/Tirane\",\n        \"Europe/Tiraspol\",\n        \"Europe/Ulyanovsk\",\n        \"Europe/Uzhgorod\",\n        \"Europe/Vaduz\",\n        \"Europe/Vatican\",\n        \"Europe/Vienna\",\n        \"Europe/Vilnius\",\n        \"Europe/Volgograd\",\n        \"Europe/Warsaw\",\n        \"Europe/Zagreb\",\n        \"Europe/Zaporozhye\",\n        \"Europe/Zurich\",\n        \"GB\",\n        \"GB-Eire\",\n        \"GMT\",\n        \"GMT+0\",\n        \"GMT-0\",\n        \"GMT0\",\n        \"Greenwich\",\n        \"HST\",\n        \"Hongkong\",\n        \"Iceland\",\n        \"Indian/Antananarivo\",\n        \"Indian/Chagos\",\n        \"Indian/Christmas\",\n        \"Indian/Cocos\",\n        \"Indian/Comoro\",\n        \"Indian/Kerguelen\",\n        \"Indian/Mahe\",\n        \"Indian/Maldives\",\n        \"Indian/Mauritius\",\n        \"Indian/Mayotte\",\n        \"Indian/Reunion\",\n        \"Iran\",\n        \"Israel\",\n        \"Jamaica\",\n        \"Japan\",\n        \"Kwajalein\",\n        \"Libya\",\n        \"MET\",\n        \"MST\",\n        \"MST7MDT\",\n        \"Mexico/BajaNorte\",\n        \"Mexico/BajaSur\",\n        \"Mexico/General\",\n        \"NZ\",\n        \"NZ-CHAT\",\n        \"Navajo\",\n        \"PRC\",\n        \"PST8PDT\",\n        \"Pacific/Apia\",\n        \"Pacific/Auckland\",\n        \"Pacific/Bougainville\",\n        \"Pacific/Chatham\",\n        \"Pacific/Chuuk\",\n        \"Pacific/Easter\",\n        \"Pacific/Efate\",\n        \"Pacific/Enderbury\",\n        \"Pacific/Fakaofo\",\n        \"Pacific/Fiji\",\n        \"Pacific/Funafuti\",\n        \"Pacific/Galapagos\",\n        \"Pacific/Gambier\",\n        \"Pacific/Guadalcanal\",\n        \"Pacific/Guam\",\n        \"Pacific/Honolulu\",\n        \"Pacific/Johnston\",\n        \"Pacific/Kanton\",\n        \"Pacific/Kiritimati\",\n        \"Pacific/Kosrae\",\n        \"Pacific/Kwajalein\",\n        \"Pacific/Majuro\",\n        \"Pacific/Marquesas\",\n        \"Pacific/Midway\",\n        \"Pacific/Nauru\",\n        \"Pacific/Niue\",\n        \"Pacific/Norfolk\",\n        \"Pacific/Noumea\",\n        \"Pacific/Pago_Pago\",\n        \"Pacific/Palau\",\n        \"Pacific/Pitcairn\",\n        \"Pacific/Pohnpei\",\n        \"Pacific/Ponape\",\n        \"Pacific/Port_Moresby\",\n        \"Pacific/Rarotonga\",\n        \"Pacific/Saipan\",\n        \"Pacific/Samoa\",\n        \"Pacific/Tahiti\",\n        \"Pacific/Tarawa\",\n        \"Pacific/Tongatapu\",\n        \"Pacific/Truk\",\n        \"Pacific/Wake\",\n        \"Pacific/Wallis\",\n        \"Pacific/Yap\",\n        \"Poland\",\n        \"Portugal\",\n        \"ROC\",\n        \"ROK\",\n        \"Singapore\",\n        \"Turkey\",\n        \"UCT\",\n        \"US/Alaska\",\n        \"US/Aleutian\",\n        \"US/Arizona\",\n        \"US/Central\",\n        \"US/East-Indiana\",\n        \"US/Eastern\",\n        \"US/Hawaii\",\n        \"US/Indiana-Starke\",\n        \"US/Michigan\",\n        \"US/Mountain\",\n        \"US/Pacific\",\n        \"US/Samoa\",\n        \"UTC\",\n        \"Universal\",\n        \"W-SU\",\n        \"WET\",\n        \"Zulu\",\n    )\n}\n\n\ndef subsecond_precision(timestamp_literal: str) -> int:\n    \"\"\"\n    Given an ISO-8601 timestamp literal, eg '2023-01-01 12:13:14.123456+00:00'\n    figure out its subsecond precision so we can construct types like DATETIME(6)\n\n    Note that in practice, this is either 3 or 6 digits (3 = millisecond precision, 6 = microsecond precision)\n    - 6 is the maximum because strftime's '%f' formats to microseconds and almost every database supports microsecond precision in timestamps\n    - Except Presto/Trino which in most cases only supports millisecond precision but will still honour '%f' and format to microseconds (replacing the remaining 3 digits with 0's)\n    - Python prior to 3.11 only supports 0, 3 or 6 digits in a timestamp literal. Any other amounts will throw a 'ValueError: Invalid isoformat string:' error\n    \"\"\"\n    try:\n        parsed = datetime.datetime.fromisoformat(timestamp_literal)\n        subsecond_digit_count = len(str(parsed.microsecond).rstrip(\"0\"))\n        precision = 0\n        if subsecond_digit_count > 3:\n            precision = 6\n        elif subsecond_digit_count > 0:\n            precision = 3\n        return precision\n    except ValueError:\n        return 0\n"
  },
  {
    "path": "sqlglot/tokenizer_core.py",
    "content": "from __future__ import annotations\n\nimport typing as t\nfrom enum import IntEnum, auto\n\nfrom sqlglot.errors import TokenError\n\n# dict lookup is faster than .upper() and .isdigit()\n_CHAR_UPPER: t.Dict[str, str] = {chr(i): chr(i).upper() for i in range(97, 123)}\n_DIGIT_CHARS: t.FrozenSet[str] = frozenset(\"0123456789\")\n\n\nclass TokenType(IntEnum):\n    L_PAREN = auto()\n    R_PAREN = auto()\n    L_BRACKET = auto()\n    R_BRACKET = auto()\n    L_BRACE = auto()\n    R_BRACE = auto()\n    COMMA = auto()\n    DOT = auto()\n    DASH = auto()\n    PLUS = auto()\n    COLON = auto()\n    DOTCOLON = auto()\n    DOTCARET = auto()\n    DCOLON = auto()\n    DCOLONDOLLAR = auto()\n    DCOLONPERCENT = auto()\n    DCOLONQMARK = auto()\n    DQMARK = auto()\n    SEMICOLON = auto()\n    STAR = auto()\n    BACKSLASH = auto()\n    SLASH = auto()\n    LT = auto()\n    LTE = auto()\n    GT = auto()\n    GTE = auto()\n    NOT = auto()\n    EQ = auto()\n    NEQ = auto()\n    NULLSAFE_EQ = auto()\n    COLON_EQ = auto()\n    COLON_GT = auto()\n    NCOLON_GT = auto()\n    AND = auto()\n    OR = auto()\n    AMP = auto()\n    DPIPE = auto()\n    PIPE_GT = auto()\n    PIPE = auto()\n    PIPE_SLASH = auto()\n    DPIPE_SLASH = auto()\n    CARET = auto()\n    CARET_AT = auto()\n    TILDE = auto()\n    ARROW = auto()\n    DARROW = auto()\n    FARROW = auto()\n    HASH = auto()\n    HASH_ARROW = auto()\n    DHASH_ARROW = auto()\n    LR_ARROW = auto()\n    DAT = auto()\n    LT_AT = auto()\n    AT_GT = auto()\n    DOLLAR = auto()\n    PARAMETER = auto()\n    SESSION = auto()\n    SESSION_PARAMETER = auto()\n    SESSION_USER = auto()\n    DAMP = auto()\n    AMP_LT = auto()\n    AMP_GT = auto()\n    ADJACENT = auto()\n    XOR = auto()\n    DSTAR = auto()\n    QMARK_AMP = auto()\n    QMARK_PIPE = auto()\n    HASH_DASH = auto()\n    EXCLAMATION = auto()\n\n    URI_START = auto()\n\n    BLOCK_START = auto()\n    BLOCK_END = auto()\n\n    SPACE = auto()\n    BREAK = auto()\n\n    STRING = auto()\n    NUMBER = auto()\n    IDENTIFIER = auto()\n    DATABASE = auto()\n    COLUMN = auto()\n    COLUMN_DEF = auto()\n    SCHEMA = auto()\n    TABLE = auto()\n    WAREHOUSE = auto()\n    STAGE = auto()\n    STREAM = auto()\n    STREAMLIT = auto()\n    VAR = auto()\n    BIT_STRING = auto()\n    HEX_STRING = auto()\n    BYTE_STRING = auto()\n    NATIONAL_STRING = auto()\n    RAW_STRING = auto()\n    HEREDOC_STRING = auto()\n    UNICODE_STRING = auto()\n\n    # types\n    BIT = auto()\n    BOOLEAN = auto()\n    TINYINT = auto()\n    UTINYINT = auto()\n    SMALLINT = auto()\n    USMALLINT = auto()\n    MEDIUMINT = auto()\n    UMEDIUMINT = auto()\n    INT = auto()\n    UINT = auto()\n    BIGINT = auto()\n    UBIGINT = auto()\n    BIGNUM = auto()\n    INT128 = auto()\n    UINT128 = auto()\n    INT256 = auto()\n    UINT256 = auto()\n    FLOAT = auto()\n    DOUBLE = auto()\n    UDOUBLE = auto()\n    DECIMAL = auto()\n    DECIMAL32 = auto()\n    DECIMAL64 = auto()\n    DECIMAL128 = auto()\n    DECIMAL256 = auto()\n    DECFLOAT = auto()\n    UDECIMAL = auto()\n    BIGDECIMAL = auto()\n    CHAR = auto()\n    NCHAR = auto()\n    VARCHAR = auto()\n    NVARCHAR = auto()\n    BPCHAR = auto()\n    TEXT = auto()\n    MEDIUMTEXT = auto()\n    LONGTEXT = auto()\n    BLOB = auto()\n    MEDIUMBLOB = auto()\n    LONGBLOB = auto()\n    TINYBLOB = auto()\n    TINYTEXT = auto()\n    NAME = auto()\n    BINARY = auto()\n    VARBINARY = auto()\n    JSON = auto()\n    JSONB = auto()\n    TIME = auto()\n    TIMETZ = auto()\n    TIME_NS = auto()\n    TIMESTAMP = auto()\n    TIMESTAMPTZ = auto()\n    TIMESTAMPLTZ = auto()\n    TIMESTAMPNTZ = auto()\n    TIMESTAMP_S = auto()\n    TIMESTAMP_MS = auto()\n    TIMESTAMP_NS = auto()\n    DATETIME = auto()\n    DATETIME2 = auto()\n    DATETIME64 = auto()\n    SMALLDATETIME = auto()\n    DATE = auto()\n    DATE32 = auto()\n    INT4RANGE = auto()\n    INT4MULTIRANGE = auto()\n    INT8RANGE = auto()\n    INT8MULTIRANGE = auto()\n    NUMRANGE = auto()\n    NUMMULTIRANGE = auto()\n    TSRANGE = auto()\n    TSMULTIRANGE = auto()\n    TSTZRANGE = auto()\n    TSTZMULTIRANGE = auto()\n    DATERANGE = auto()\n    DATEMULTIRANGE = auto()\n    UUID = auto()\n    GEOGRAPHY = auto()\n    GEOGRAPHYPOINT = auto()\n    NULLABLE = auto()\n    GEOMETRY = auto()\n    POINT = auto()\n    RING = auto()\n    LINESTRING = auto()\n    LOCALTIME = auto()\n    LOCALTIMESTAMP = auto()\n    SYSTIMESTAMP = auto()\n    MULTILINESTRING = auto()\n    POLYGON = auto()\n    MULTIPOLYGON = auto()\n    HLLSKETCH = auto()\n    HSTORE = auto()\n    SUPER = auto()\n    SERIAL = auto()\n    SMALLSERIAL = auto()\n    BIGSERIAL = auto()\n    XML = auto()\n    YEAR = auto()\n    USERDEFINED = auto()\n    MONEY = auto()\n    SMALLMONEY = auto()\n    ROWVERSION = auto()\n    IMAGE = auto()\n    VARIANT = auto()\n    OBJECT = auto()\n    INET = auto()\n    IPADDRESS = auto()\n    IPPREFIX = auto()\n    IPV4 = auto()\n    IPV6 = auto()\n    ENUM = auto()\n    ENUM8 = auto()\n    ENUM16 = auto()\n    FIXEDSTRING = auto()\n    LOWCARDINALITY = auto()\n    NESTED = auto()\n    AGGREGATEFUNCTION = auto()\n    SIMPLEAGGREGATEFUNCTION = auto()\n    TDIGEST = auto()\n    UNKNOWN = auto()\n    VECTOR = auto()\n    DYNAMIC = auto()\n    VOID = auto()\n\n    # keywords\n    ALIAS = auto()\n    ALTER = auto()\n    ALL = auto()\n    ANTI = auto()\n    ANY = auto()\n    APPLY = auto()\n    ARRAY = auto()\n    ASC = auto()\n    ASOF = auto()\n    ATTACH = auto()\n    AUTO_INCREMENT = auto()\n    BEGIN = auto()\n    BETWEEN = auto()\n    BULK_COLLECT_INTO = auto()\n    CACHE = auto()\n    CASE = auto()\n    CHARACTER_SET = auto()\n    CLUSTER_BY = auto()\n    COLLATE = auto()\n    COMMAND = auto()\n    COMMENT = auto()\n    COMMIT = auto()\n    CONNECT_BY = auto()\n    CONSTRAINT = auto()\n    COPY = auto()\n    CREATE = auto()\n    CROSS = auto()\n    CUBE = auto()\n    CURRENT_DATE = auto()\n    CURRENT_DATETIME = auto()\n    CURRENT_SCHEMA = auto()\n    CURRENT_TIME = auto()\n    CURRENT_TIMESTAMP = auto()\n    CURRENT_USER = auto()\n    CURRENT_ROLE = auto()\n    CURRENT_CATALOG = auto()\n    DECLARE = auto()\n    DEFAULT = auto()\n    DELETE = auto()\n    DESC = auto()\n    DESCRIBE = auto()\n    DETACH = auto()\n    DICTIONARY = auto()\n    DISTINCT = auto()\n    DISTRIBUTE_BY = auto()\n    DIV = auto()\n    DROP = auto()\n    ELSE = auto()\n    END = auto()\n    ESCAPE = auto()\n    EXCEPT = auto()\n    EXECUTE = auto()\n    EXISTS = auto()\n    FALSE = auto()\n    FETCH = auto()\n    FILE = auto()\n    FILE_FORMAT = auto()\n    FILTER = auto()\n    FINAL = auto()\n    FIRST = auto()\n    FOR = auto()\n    FORCE = auto()\n    FOREIGN_KEY = auto()\n    FORMAT = auto()\n    FROM = auto()\n    FULL = auto()\n    FUNCTION = auto()\n    GET = auto()\n    GLOB = auto()\n    GLOBAL = auto()\n    GRANT = auto()\n    GROUP_BY = auto()\n    GROUPING_SETS = auto()\n    HAVING = auto()\n    HINT = auto()\n    IGNORE = auto()\n    ILIKE = auto()\n    IN = auto()\n    INDEX = auto()\n    INDEXED_BY = auto()\n    INNER = auto()\n    INSERT = auto()\n    INSTALL = auto()\n    INTEGRATION = auto()\n    INTERSECT = auto()\n    INTERVAL = auto()\n    INTO = auto()\n    INTRODUCER = auto()\n    IRLIKE = auto()\n    IS = auto()\n    ISNULL = auto()\n    JOIN = auto()\n    JOIN_MARKER = auto()\n    KEEP = auto()\n    KEY = auto()\n    KILL = auto()\n    LANGUAGE = auto()\n    LATERAL = auto()\n    LEFT = auto()\n    LIKE = auto()\n    LIMIT = auto()\n    LIST = auto()\n    LOAD = auto()\n    LOCK = auto()\n    MAP = auto()\n    MATCH = auto()\n    MATCH_CONDITION = auto()\n    MATCH_RECOGNIZE = auto()\n    MEMBER_OF = auto()\n    MERGE = auto()\n    MOD = auto()\n    MODEL = auto()\n    NATURAL = auto()\n    NEXT = auto()\n    NOTHING = auto()\n    NOTNULL = auto()\n    NULL = auto()\n    OBJECT_IDENTIFIER = auto()\n    OFFSET = auto()\n    ON = auto()\n    ONLY = auto()\n    OPERATOR = auto()\n    ORDER_BY = auto()\n    ORDER_SIBLINGS_BY = auto()\n    ORDERED = auto()\n    ORDINALITY = auto()\n    OUT = auto()\n    INOUT = auto()\n    OUTER = auto()\n    OVER = auto()\n    OVERLAPS = auto()\n    OVERWRITE = auto()\n    PACKAGE = auto()\n    PARTITION = auto()\n    PARTITION_BY = auto()\n    PERCENT = auto()\n    PIVOT = auto()\n    PLACEHOLDER = auto()\n    POLICY = auto()\n    POOL = auto()\n    POSITIONAL = auto()\n    PRAGMA = auto()\n    PREWHERE = auto()\n    PRIMARY_KEY = auto()\n    PROCEDURE = auto()\n    PROPERTIES = auto()\n    PSEUDO_TYPE = auto()\n    PUT = auto()\n    QUALIFY = auto()\n    QUOTE = auto()\n    QDCOLON = auto()\n    RANGE = auto()\n    RECURSIVE = auto()\n    REFRESH = auto()\n    RENAME = auto()\n    REPLACE = auto()\n    RETURNING = auto()\n    REVOKE = auto()\n    REFERENCES = auto()\n    RIGHT = auto()\n    RLIKE = auto()\n    ROLE = auto()\n    ROLLBACK = auto()\n    ROLLUP = auto()\n    ROW = auto()\n    ROWS = auto()\n    RULE = auto()\n    SELECT = auto()\n    SEMI = auto()\n    SEPARATOR = auto()\n    SEQUENCE = auto()\n    SERDE_PROPERTIES = auto()\n    SET = auto()\n    SETTINGS = auto()\n    SHOW = auto()\n    SIMILAR_TO = auto()\n    SOME = auto()\n    SORT_BY = auto()\n    SOUNDS_LIKE = auto()\n    SQL_SECURITY = auto()\n    START_WITH = auto()\n    STORAGE_INTEGRATION = auto()\n    STRAIGHT_JOIN = auto()\n    STRUCT = auto()\n    SUMMARIZE = auto()\n    TABLE_SAMPLE = auto()\n    TAG = auto()\n    TEMPORARY = auto()\n    TOP = auto()\n    THEN = auto()\n    TRUE = auto()\n    TRUNCATE = auto()\n    TRIGGER = auto()\n    UNCACHE = auto()\n    UNION = auto()\n    UNNEST = auto()\n    UNPIVOT = auto()\n    UPDATE = auto()\n    USE = auto()\n    USING = auto()\n    VALUES = auto()\n    VARIADIC = auto()\n    VIEW = auto()\n    SEMANTIC_VIEW = auto()\n    VOLATILE = auto()\n    VOLUME = auto()\n    WHEN = auto()\n    WHERE = auto()\n    WINDOW = auto()\n    WITH = auto()\n    UNIQUE = auto()\n    UTC_DATE = auto()\n    UTC_TIME = auto()\n    UTC_TIMESTAMP = auto()\n    VERSION_SNAPSHOT = auto()\n    TIMESTAMP_SNAPSHOT = auto()\n    OPTION = auto()\n    SINK = auto()\n    SOURCE = auto()\n    ANALYZE = auto()\n    NAMESPACE = auto()\n    EXPORT = auto()\n\n    # sentinels\n    HIVE_TOKEN_STREAM = auto()\n    SENTINEL = auto()\n\n    def __str__(self) -> str:\n        return f\"TokenType.{self.name}\"\n\n\nclass Token:\n    # mypyc doesn't expose slots\n    _attrs: t.ClassVar[t.Tuple[str, ...]] = (\n        \"token_type\",\n        \"text\",\n        \"line\",\n        \"col\",\n        \"start\",\n        \"end\",\n        \"comments\",\n    )\n    __slots__ = _attrs\n\n    @classmethod\n    def number(cls, number: int) -> Token:\n        \"\"\"Returns a NUMBER token with `number` as its text.\"\"\"\n        return cls(TokenType.NUMBER, str(number))\n\n    @classmethod\n    def string(cls, string: str) -> Token:\n        \"\"\"Returns a STRING token with `string` as its text.\"\"\"\n        return cls(TokenType.STRING, string)\n\n    @classmethod\n    def identifier(cls, identifier: str) -> Token:\n        \"\"\"Returns an IDENTIFIER token with `identifier` as its text.\"\"\"\n        return cls(TokenType.IDENTIFIER, identifier)\n\n    @classmethod\n    def var(cls, var: str) -> Token:\n        \"\"\"Returns an VAR token with `var` as its text.\"\"\"\n        return cls(TokenType.VAR, var)\n\n    def __init__(\n        self,\n        token_type: TokenType,\n        text: str,\n        line: int = 1,\n        col: int = 1,\n        start: int = 0,\n        end: int = 0,\n        comments: t.Optional[t.List[str]] = None,\n    ) -> None:\n        self.token_type = token_type\n        self.text = text\n        self.line = line\n        self.col = col\n        self.start = start\n        self.end = end\n        self.comments = [] if comments is None else comments\n\n    def __bool__(self) -> bool:\n        return self.token_type != TokenType.SENTINEL\n\n    def __repr__(self) -> str:\n        attributes = \", \".join(\n            f\"{k}: TokenType.{self.token_type.name}\"\n            if k == \"token_type\"\n            else f\"{k}: {getattr(self, k)}\"\n            for k in self._attrs\n        )\n        return f\"<Token {attributes}>\"\n\n\nclass TokenizerCore:\n    __slots__ = (\n        \"sql\",\n        \"size\",\n        \"tokens\",\n        \"_start\",\n        \"_current\",\n        \"_line\",\n        \"_col\",\n        \"_comments\",\n        \"_char\",\n        \"_end\",\n        \"_peek\",\n        \"_prev_token_line\",\n        \"single_tokens\",\n        \"keywords\",\n        \"quotes\",\n        \"format_strings\",\n        \"identifiers\",\n        \"comments\",\n        \"string_escapes\",\n        \"byte_string_escapes\",\n        \"identifier_escapes\",\n        \"escape_follow_chars\",\n        \"commands\",\n        \"command_prefix_tokens\",\n        \"nested_comments\",\n        \"hint_start\",\n        \"tokens_preceding_hint\",\n        \"bit_strings\",\n        \"hex_strings\",\n        \"numeric_literals\",\n        \"var_single_tokens\",\n        \"string_escapes_allowed_in_raw_strings\",\n        \"heredoc_tag_is_identifier\",\n        \"heredoc_string_alternative\",\n        \"keyword_trie\",\n        \"numbers_can_be_underscore_separated\",\n        \"identifiers_can_start_with_digit\",\n        \"unescaped_sequences\",\n    )\n\n    def __init__(\n        self,\n        single_tokens: t.Dict[str, TokenType],\n        keywords: t.Dict[str, TokenType],\n        quotes: t.Dict[str, str],\n        format_strings: t.Dict[str, t.Tuple[str, TokenType]],\n        identifiers: t.Dict[str, str],\n        comments: t.Dict[str, t.Optional[str]],\n        string_escapes: t.Set[str],\n        byte_string_escapes: t.Set[str],\n        identifier_escapes: t.Set[str],\n        escape_follow_chars: t.Set[str],\n        commands: t.Set[TokenType],\n        command_prefix_tokens: t.Set[TokenType],\n        nested_comments: bool,\n        hint_start: str,\n        tokens_preceding_hint: t.Set[TokenType],\n        bit_strings: t.List[t.Union[str, t.Tuple[str, str]]],\n        hex_strings: t.List[t.Union[str, t.Tuple[str, str]]],\n        numeric_literals: t.Dict[str, str],\n        var_single_tokens: t.Set[str],\n        string_escapes_allowed_in_raw_strings: bool,\n        heredoc_tag_is_identifier: bool,\n        heredoc_string_alternative: TokenType,\n        keyword_trie: t.Dict,\n        numbers_can_be_underscore_separated: bool,\n        identifiers_can_start_with_digit: bool,\n        unescaped_sequences: t.Dict[str, str],\n    ) -> None:\n        self.single_tokens = single_tokens\n        self.keywords = keywords\n        self.quotes = quotes\n        self.format_strings = format_strings\n        self.identifiers = identifiers\n        self.comments = comments\n        self.string_escapes = string_escapes\n        self.byte_string_escapes = byte_string_escapes\n        self.identifier_escapes = identifier_escapes\n        self.escape_follow_chars = escape_follow_chars\n        self.commands = commands\n        self.command_prefix_tokens = command_prefix_tokens\n        self.nested_comments = nested_comments\n        self.hint_start = hint_start\n        self.tokens_preceding_hint = tokens_preceding_hint\n        self.bit_strings = bit_strings\n        self.hex_strings = hex_strings\n        self.numeric_literals = numeric_literals\n        self.var_single_tokens = var_single_tokens\n        self.string_escapes_allowed_in_raw_strings = string_escapes_allowed_in_raw_strings\n        self.heredoc_tag_is_identifier = heredoc_tag_is_identifier\n        self.heredoc_string_alternative = heredoc_string_alternative\n        self.keyword_trie = keyword_trie\n        self.numbers_can_be_underscore_separated = numbers_can_be_underscore_separated\n        self.identifiers_can_start_with_digit = identifiers_can_start_with_digit\n        self.unescaped_sequences = unescaped_sequences\n        self.sql = \"\"\n        self.size = 0\n        self.tokens: t.List[Token] = []\n        self._start = 0\n        self._current = 0\n        self._line = 1\n        self._col = 0\n        self._comments: t.List[str] = []\n        self._char = \"\"\n        self._end = False\n        self._peek = \"\"\n        self._prev_token_line = -1\n\n    def reset(self) -> None:\n        self.sql = \"\"\n        self.size = 0\n        self.tokens = []\n        self._start = 0\n        self._current = 0\n        self._line = 1\n        self._col = 0\n        self._comments = []\n        self._char = \"\"\n        self._end = False\n        self._peek = \"\"\n        self._prev_token_line = -1\n\n    def tokenize(self, sql: str) -> t.List[Token]:\n        \"\"\"Returns a list of tokens corresponding to the SQL string `sql`.\"\"\"\n        self.reset()\n        self.sql = sql\n        self.size = len(sql)\n\n        try:\n            self._scan()\n        except Exception as e:\n            start = max(self._current - 50, 0)\n            end = min(self._current + 50, self.size - 1)\n            context = self.sql[start:end]\n            raise TokenError(f\"Error tokenizing '{context}'\") from e\n\n        return self.tokens\n\n    def _scan(self, check_semicolon: bool = False) -> None:\n        identifiers = self.identifiers\n        digit_chars = _DIGIT_CHARS\n\n        while self.size and not self._end:\n            current = self._current\n\n            # Skip spaces here rather than iteratively calling advance() for performance reasons\n            while current < self.size:\n                char = self.sql[current]\n\n                if char == \" \" or char == \"\\t\":\n                    current += 1\n                else:\n                    break\n\n            offset = current - self._current if current > self._current else 1\n\n            self._start = current\n            self._advance(offset)\n\n            if not self._char.isspace():\n                if self._char in digit_chars:\n                    self._scan_number()\n                elif self._char in identifiers:\n                    self._scan_identifier(identifiers[self._char])\n                else:\n                    self._scan_keywords()\n\n            if check_semicolon and self._peek == \";\":\n                break\n\n        if self.tokens and self._comments:\n            self.tokens[-1].comments.extend(self._comments)\n\n    def _chars(self, size: int) -> str:\n        if size == 1:\n            return self._char\n\n        start = self._current - 1\n        end = start + size\n\n        return self.sql[start:end] if end <= self.size else \"\"\n\n    def _advance(self, i: int = 1, alnum: bool = False) -> None:\n        char = self._char\n\n        if char == \"\\n\" or char == \"\\r\":\n            # Ensures we don't count an extra line if we get a \\r\\n line break sequence\n            if not (char == \"\\r\" and self._peek == \"\\n\"):\n                self._col = i\n                self._line += 1\n        else:\n            self._col += i\n\n        self._current += i\n        sql = self.sql\n        size = self.size\n        self._end = self._current >= size\n        self._char = sql[self._current - 1]\n        self._peek = \"\" if self._end else sql[self._current]\n\n        if alnum and self._char.isalnum():\n            # Cache to local variables instead of attributes for better performance\n            _col = self._col\n            _current = self._current\n            _end = self._end\n            _peek = self._peek\n\n            while _peek.isalnum():\n                _col += 1\n                _current += 1\n                _end = _current >= size\n                _peek = \"\" if _end else sql[_current]\n\n            self._col = _col\n            self._current = _current\n            self._end = _end\n            self._peek = _peek\n            self._char = sql[_current - 1]\n\n    @property\n    def _text(self) -> str:\n        return self.sql[self._start : self._current]\n\n    def _add(self, token_type: TokenType, text: t.Optional[str] = None) -> None:\n        self._prev_token_line = self._line\n\n        if self._comments and token_type == TokenType.SEMICOLON and self.tokens:\n            self.tokens[-1].comments.extend(self._comments)\n            self._comments = []\n\n        if text is None:\n            text = self.sql[self._start : self._current]\n\n        self.tokens.append(\n            Token(\n                token_type,\n                text=text,\n                line=self._line,\n                col=self._col,\n                start=self._start,\n                end=self._current - 1,\n                comments=self._comments,\n            )\n        )\n        self._comments = []\n\n        # If we have either a semicolon or a begin token before the command's token, we'll parse\n        # whatever follows the command's token as a string\n        if (\n            token_type in self.commands\n            and self._peek != \";\"\n            and (len(self.tokens) == 1 or self.tokens[-2].token_type in self.command_prefix_tokens)\n        ):\n            start = self._current\n            tokens = len(self.tokens)\n            self._scan(check_semicolon=True)\n            self.tokens = self.tokens[:tokens]\n            text = self.sql[start : self._current].strip()\n            if text:\n                self._add(TokenType.STRING, text)\n\n    def _scan_keywords(self) -> None:\n        sql = self.sql\n        sql_size = self.size\n        single_tokens = self.single_tokens\n        char_upper = _CHAR_UPPER\n        size = 0\n        word = None\n        chars = self._char\n        char = chars\n        prev_space = False\n        skip = False\n        trie = self.keyword_trie\n        single_token = char in single_tokens\n\n        while chars:\n            if not skip:\n                sub = trie.get(char_upper.get(char, char))\n                if sub is None:\n                    break\n                trie = sub\n                if 0 in trie:\n                    word = chars\n\n            end = self._current + size\n            size += 1\n\n            if end < sql_size:\n                char = sql[end]\n                single_token = single_token or char in single_tokens\n                is_space = char.isspace()\n\n                if not is_space or not prev_space:\n                    if is_space:\n                        char = \" \"\n                    chars += char\n                    prev_space = is_space\n                    skip = False\n                else:\n                    skip = True\n            else:\n                char = \"\"\n                break\n\n        if word:\n            if self._scan_string(word):\n                return\n            if self._scan_comment(word):\n                return\n            if prev_space or single_token or not char:\n                self._advance(size - 1)\n                word = word.upper()\n                self._add(self.keywords[word], text=word)\n                return\n\n        if self._char in single_tokens:\n            self._add(single_tokens[self._char], text=self._char)\n            return\n\n        self._scan_var()\n\n    def _scan_comment(self, comment_start: str) -> bool:\n        if comment_start not in self.comments:\n            return False\n\n        comment_start_line = self._line\n        comment_start_size = len(comment_start)\n        comment_end = self.comments[comment_start]\n\n        if comment_end:\n            # Skip the comment's start delimiter\n            self._advance(comment_start_size)\n\n            comment_count = 1\n            comment_end_size = len(comment_end)\n            nested_comments = self.nested_comments\n\n            while not self._end:\n                if self._chars(comment_end_size) == comment_end:\n                    comment_count -= 1\n                    if not comment_count:\n                        break\n\n                self._advance(alnum=True)\n\n                # Nested comments are allowed by some dialects, e.g. databricks, duckdb, postgres\n                if (\n                    nested_comments\n                    and not self._end\n                    and self._chars(comment_end_size) == comment_start\n                ):\n                    self._advance(comment_start_size)\n                    comment_count += 1\n\n            self._comments.append(self._text[comment_start_size : -comment_end_size + 1])\n            self._advance(comment_end_size - 1)\n        else:\n            _peek = self._peek\n            while not self._end and _peek != \"\\n\" and _peek != \"\\r\":\n                self._advance(alnum=True)\n                _peek = self._peek\n            self._comments.append(self._text[comment_start_size:])\n\n        if (\n            comment_start == self.hint_start\n            and self.tokens\n            and self.tokens[-1].token_type in self.tokens_preceding_hint\n        ):\n            self._add(TokenType.HINT)\n\n        # Leading comment is attached to the succeeding token, whilst trailing comment to the preceding.\n        # Multiple consecutive comments are preserved by appending them to the current comments list.\n        if comment_start_line == self._prev_token_line:\n            self.tokens[-1].comments.extend(self._comments)\n            self._comments = []\n            self._prev_token_line = self._line\n\n        return True\n\n    def _scan_number(self) -> None:\n        if self._char == \"0\":\n            peek = _CHAR_UPPER.get(self._peek, self._peek)\n            if peek == \"B\":\n                return self._scan_bits() if self.bit_strings else self._add(TokenType.NUMBER)\n            elif peek == \"X\":\n                return self._scan_hex() if self.hex_strings else self._add(TokenType.NUMBER)\n\n        decimal = False\n        scientific = 0\n        numbers_can_be_underscore_separated = self.numbers_can_be_underscore_separated\n        single_tokens = self.single_tokens\n        keywords = self.keywords\n        numeric_literals = self.numeric_literals\n        identifiers_can_start_with_digit = self.identifiers_can_start_with_digit\n\n        is_underscore_separated: bool = False\n        number_text: str = \"\"\n        numeric_literal: str = \"\"\n        numeric_type: t.Optional[TokenType] = None\n\n        while True:\n            if self._peek in _DIGIT_CHARS:\n                # Batch consecutive digits: scan ahead to find how many\n                sql = self.sql\n                end = self._current + 1\n                size = self.size\n                while end < size and sql[end] in _DIGIT_CHARS:\n                    end += 1\n                self._advance(end - self._current)\n            elif self._peek == \".\" and not decimal:\n                if self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER:\n                    break\n                decimal = True\n                self._advance()\n            elif self._peek in (\"-\", \"+\") and scientific == 1:\n                # Only consume +/- if followed by a digit\n                if self._current + 1 < self.size and self.sql[self._current + 1] in _DIGIT_CHARS:\n                    scientific += 1\n                    self._advance()\n                else:\n                    break\n            elif _CHAR_UPPER.get(self._peek, self._peek) == \"E\" and not scientific:\n                scientific += 1\n                self._advance()\n            elif self._peek == \"_\" and numbers_can_be_underscore_separated:\n                is_underscore_separated = True\n                self._advance()\n            elif self._peek.isidentifier():\n                number_text = self._text\n\n                while self._peek and not self._peek.isspace() and self._peek not in single_tokens:\n                    numeric_literal += self._peek\n                    self._advance()\n\n                numeric_type = keywords.get(numeric_literals.get(numeric_literal.upper(), \"\"))\n\n                if numeric_type:\n                    break\n                elif identifiers_can_start_with_digit:\n                    return self._add(TokenType.VAR)\n\n                self._advance(-len(numeric_literal))\n                break\n            else:\n                break\n\n        number_text = number_text or self.sql[self._start : self._current]\n\n        # Normalize inputs such as 100_000 to 100000\n        if is_underscore_separated:\n            number_text = number_text.replace(\"_\", \"\")\n\n        self._add(TokenType.NUMBER, number_text)\n\n        # Normalize inputs such as 123L to 123::BIGINT so that they're parsed as casts\n        if numeric_type:\n            self._add(TokenType.DCOLON, \"::\")\n            self._add(numeric_type, numeric_literal)\n\n    def _scan_bits(self) -> None:\n        self._advance()\n        value = self._extract_value()\n        try:\n            # If `value` can't be converted to a binary, fallback to tokenizing it as an identifier\n            int(value, 2)\n            self._add(TokenType.BIT_STRING, value[2:])  # Drop the 0b\n        except ValueError:\n            self._add(TokenType.IDENTIFIER)\n\n    def _scan_hex(self) -> None:\n        self._advance()\n        value = self._extract_value()\n        try:\n            # If `value` can't be converted to a hex, fallback to tokenizing it as an identifier\n            int(value, 16)\n            self._add(TokenType.HEX_STRING, value[2:])  # Drop the 0x\n        except ValueError:\n            self._add(TokenType.IDENTIFIER)\n\n    def _extract_value(self) -> str:\n        single_tokens = self.single_tokens\n\n        while True:\n            char = self._peek.strip()\n            if char and char not in single_tokens:\n                self._advance(alnum=True)\n            else:\n                break\n\n        return self._text\n\n    def _scan_string(self, start: str) -> bool:\n        base = None\n        token_type = TokenType.STRING\n\n        if start in self.quotes:\n            end = self.quotes[start]\n        elif start in self.format_strings:\n            end, token_type = self.format_strings[start]\n\n            if token_type == TokenType.HEX_STRING:\n                base = 16\n            elif token_type == TokenType.BIT_STRING:\n                base = 2\n            elif token_type == TokenType.HEREDOC_STRING:\n                self._advance()\n\n                if self._char == end:\n                    tag = \"\"\n                else:\n                    tag = self._extract_string(\n                        end,\n                        raw_string=True,\n                        raise_unmatched=not self.heredoc_tag_is_identifier,\n                    )\n\n                if (\n                    tag\n                    and self.heredoc_tag_is_identifier\n                    and (self._end or tag.isdigit() or any(c.isspace() for c in tag))\n                ):\n                    if not self._end:\n                        self._advance(-1)\n\n                    self._advance(-len(tag))\n                    self._add(self.heredoc_string_alternative)\n                    return True\n\n                end = f\"{start}{tag}{end}\"\n        else:\n            return False\n\n        self._advance(len(start))\n        text = self._extract_string(\n            end,\n            escapes=(\n                self.byte_string_escapes\n                if token_type == TokenType.BYTE_STRING\n                else self.string_escapes\n            ),\n            raw_string=token_type == TokenType.RAW_STRING,\n        )\n\n        if base and text:\n            try:\n                int(text, base)\n            except Exception:\n                raise TokenError(\n                    f\"Numeric string contains invalid characters from {self._line}:{self._start}\"\n                )\n\n        self._add(token_type, text)\n        return True\n\n    def _scan_identifier(self, identifier_end: str) -> None:\n        self._advance()\n        text = self._extract_string(\n            identifier_end, escapes=self.identifier_escapes | {identifier_end}\n        )\n        self._add(TokenType.IDENTIFIER, text)\n\n    def _scan_var(self) -> None:\n        var_single_tokens = self.var_single_tokens\n        single_tokens = self.single_tokens\n\n        while True:\n            peek = self._peek\n            if not peek or peek.isspace():\n                break\n            if peek not in var_single_tokens and peek in single_tokens:\n                break\n            self._advance(alnum=True)\n\n        self._add(\n            TokenType.VAR\n            if self.tokens and self.tokens[-1].token_type == TokenType.PARAMETER\n            else self.keywords.get(self.sql[self._start : self._current].upper(), TokenType.VAR)\n        )\n\n    def _extract_string(\n        self,\n        delimiter: str,\n        escapes: t.Optional[t.Set[str]] = None,\n        raw_string: bool = False,\n        raise_unmatched: bool = True,\n    ) -> str:\n        text = \"\"\n        delim_size = len(delimiter)\n        escapes = self.string_escapes if escapes is None else escapes\n        unescaped_sequences = self.unescaped_sequences\n        escape_follow_chars = self.escape_follow_chars\n        string_escapes_allowed_in_raw_strings = self.string_escapes_allowed_in_raw_strings\n        quotes = self.quotes\n        sql = self.sql\n\n        # use str.find() when the string is simple... no \\ or other escapes\n        if delim_size == 1:\n            pos = self._current - 1\n            end = sql.find(delimiter, pos)\n\n            if (\n                # the closing delimiter was found\n                end != -1\n                # there's no doubled delimiter (e.g. '' escape), or the delimiter isn't an escape char\n                and (end + 1 >= self.size or sql[end + 1] != delimiter or delimiter not in escapes)\n                # no backslash in the string that would need escape processing\n                and (not (unescaped_sequences or \"\\\\\" in escapes) or sql.find(\"\\\\\", pos, end) == -1)\n            ):\n                newlines = sql.count(\"\\n\", pos, end)\n                if newlines:\n                    self._line += newlines\n                    self._col = end - sql.rfind(\"\\n\", pos, end)\n                else:\n                    self._col += end - pos\n\n                self._current = end + 1\n                self._end = self._current >= self.size\n                self._char = sql[end]\n                self._peek = \"\" if self._end else sql[self._current]\n                return sql[pos:end]\n\n        while True:\n            if not raw_string and unescaped_sequences and self._peek and self._char in escapes:\n                unescaped_sequence = unescaped_sequences.get(self._char + self._peek)\n                if unescaped_sequence:\n                    self._advance(2)\n                    text += unescaped_sequence\n                    continue\n\n            is_valid_custom_escape = (\n                escape_follow_chars and self._char == \"\\\\\" and self._peek not in escape_follow_chars\n            )\n\n            if (\n                (string_escapes_allowed_in_raw_strings or not raw_string)\n                and self._char in escapes\n                and (self._peek == delimiter or self._peek in escapes or is_valid_custom_escape)\n                and (self._char not in quotes or self._char == self._peek)\n            ):\n                if self._peek == delimiter:\n                    text += self._peek\n                elif is_valid_custom_escape and self._char != self._peek:\n                    text += self._peek\n                else:\n                    text += self._char + self._peek\n\n                if self._current + 1 < self.size:\n                    self._advance(2)\n                else:\n                    raise TokenError(f\"Missing {delimiter} from {self._line}:{self._current}\")\n            else:\n                if self._chars(delim_size) == delimiter:\n                    if delim_size > 1:\n                        self._advance(delim_size - 1)\n                    break\n\n                if self._end:\n                    if not raise_unmatched:\n                        return text + self._char\n\n                    raise TokenError(f\"Missing {delimiter} from {self._line}:{self._start}\")\n\n                current = self._current - 1\n                self._advance(alnum=True)\n                text += sql[current : self._current - 1]\n\n        return text\n"
  },
  {
    "path": "sqlglot/tokens.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot.trie import new_trie\n\n# Import Token and TokenType from tokenizer_core (compiled with mypyc)\nfrom sqlglot.tokenizer_core import Token, TokenType\n\ntry:\n    import sqlglotc  # noqa: F401\nexcept ImportError:\n    pass\n\ntry:\n    import sqlglotrs  # type: ignore # noqa: F401\n    import warnings\n\n    if \"sqlglotc\" not in globals():\n        warnings.warn(\n            \"sqlglot[rs] is deprecated and no longer compatible with sqlglot. \"\n            \"Please use sqlglotc instead for faster parsing: pip install sqlglot[c]\",\n        )\nexcept ImportError:\n    pass\n\nif t.TYPE_CHECKING:\n    from sqlglot.dialects.dialect import DialectType\n\n\ndef _convert_quotes(arr: t.List[str | t.Tuple[str, str]]) -> t.Dict[str, str]:\n    return dict((item, item) if isinstance(item, str) else (item[0], item[1]) for item in arr)\n\n\ndef _quotes_to_format(\n    token_type: TokenType, arr: t.List[str | t.Tuple[str, str]]\n) -> t.Dict[str, t.Tuple[str, TokenType]]:\n    return {k: (v, token_type) for k, v in _convert_quotes(arr).items()}\n\n\nclass _TokenizerBase:\n    QUOTES: t.ClassVar[t.List[t.Tuple[str, str] | str]]\n    IDENTIFIERS: t.ClassVar[t.List[str | t.Tuple[str, str]]]\n    BIT_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]]\n    BYTE_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]]\n    HEX_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]]\n    RAW_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]]\n    HEREDOC_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]]\n    UNICODE_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]]\n    STRING_ESCAPES: t.ClassVar[t.List[str]]\n    BYTE_STRING_ESCAPES: t.ClassVar[t.List[str]]\n    ESCAPE_FOLLOW_CHARS: t.ClassVar[t.List[str]]\n    IDENTIFIER_ESCAPES: t.ClassVar[t.List[str]]\n    HINT_START: t.ClassVar[str]\n    KEYWORDS: t.ClassVar[t.Dict[str, TokenType]]\n    SINGLE_TOKENS: t.ClassVar[t.Dict[str, TokenType]]\n    NUMERIC_LITERALS: t.ClassVar[t.Dict[str, str]]\n    VAR_SINGLE_TOKENS: t.ClassVar[t.Set[str]]\n    COMMANDS: t.ClassVar[t.Set[TokenType]]\n    COMMAND_PREFIX_TOKENS: t.ClassVar[t.Set[TokenType]]\n    HEREDOC_TAG_IS_IDENTIFIER: t.ClassVar[bool]\n    STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS: t.ClassVar[bool]\n    NESTED_COMMENTS: t.ClassVar[bool]\n    TOKENS_PRECEDING_HINT: t.ClassVar[t.Set[TokenType]]\n    HEREDOC_STRING_ALTERNATIVE: t.ClassVar[TokenType]\n    COMMENTS: t.ClassVar[t.List[str | t.Tuple[str, str]]]\n    _QUOTES: t.ClassVar[t.Dict[str, str]]\n    _IDENTIFIERS: t.ClassVar[t.Dict[str, str]]\n    _FORMAT_STRINGS: t.ClassVar[t.Dict[str, t.Tuple[str, TokenType]]]\n    _STRING_ESCAPES: t.ClassVar[t.Set[str]]\n    _BYTE_STRING_ESCAPES: t.ClassVar[t.Set[str]]\n    _ESCAPE_FOLLOW_CHARS: t.ClassVar[t.Set[str]]\n    _IDENTIFIER_ESCAPES: t.ClassVar[t.Set[str]]\n    _COMMENTS: t.ClassVar[t.Dict[str, t.Optional[str]]]\n    _KEYWORD_TRIE: t.ClassVar[t.Dict]\n\n    @classmethod\n    def __init_subclass__(cls, **kwargs: t.Any) -> None:\n        super().__init_subclass__(**kwargs)\n        cls._QUOTES = _convert_quotes(cls.QUOTES)\n        cls._IDENTIFIERS = _convert_quotes(cls.IDENTIFIERS)\n        cls._FORMAT_STRINGS = {\n            **{\n                p + s: (e, TokenType.NATIONAL_STRING)\n                for s, e in cls._QUOTES.items()\n                for p in (\"n\", \"N\")\n            },\n            **_quotes_to_format(TokenType.BIT_STRING, cls.BIT_STRINGS),\n            **_quotes_to_format(TokenType.BYTE_STRING, cls.BYTE_STRINGS),\n            **_quotes_to_format(TokenType.HEX_STRING, cls.HEX_STRINGS),\n            **_quotes_to_format(TokenType.RAW_STRING, cls.RAW_STRINGS),\n            **_quotes_to_format(TokenType.HEREDOC_STRING, cls.HEREDOC_STRINGS),\n            **_quotes_to_format(TokenType.UNICODE_STRING, cls.UNICODE_STRINGS),\n        }\n        if \"BYTE_STRING_ESCAPES\" not in cls.__dict__:\n            cls.BYTE_STRING_ESCAPES = cls.STRING_ESCAPES.copy()\n        cls._STRING_ESCAPES = set(cls.STRING_ESCAPES)\n        cls._BYTE_STRING_ESCAPES = set(cls.BYTE_STRING_ESCAPES)\n        cls._ESCAPE_FOLLOW_CHARS = set(cls.ESCAPE_FOLLOW_CHARS)\n        cls._IDENTIFIER_ESCAPES = set(cls.IDENTIFIER_ESCAPES)\n        cls._COMMENTS = {\n            **{c: None for c in cls.COMMENTS if isinstance(c, str)},\n            **{c[0]: c[1] for c in cls.COMMENTS if not isinstance(c, str)},\n            \"{#\": \"#}\",  # Ensure Jinja comments are tokenized correctly in all dialects\n        }\n        if cls.HINT_START in cls.KEYWORDS:\n            cls._COMMENTS[cls.HINT_START] = \"*/\"\n        cls._KEYWORD_TRIE = new_trie(\n            key.upper()\n            for key in (\n                *cls.KEYWORDS,\n                *cls._COMMENTS,\n                *cls._QUOTES,\n                *cls._FORMAT_STRINGS,\n            )\n            if \" \" in key or any(single in key for single in cls.SINGLE_TOKENS)\n        )\n\n\nclass Tokenizer(_TokenizerBase):\n    SINGLE_TOKENS = {\n        \"(\": TokenType.L_PAREN,\n        \")\": TokenType.R_PAREN,\n        \"[\": TokenType.L_BRACKET,\n        \"]\": TokenType.R_BRACKET,\n        \"{\": TokenType.L_BRACE,\n        \"}\": TokenType.R_BRACE,\n        \"&\": TokenType.AMP,\n        \"^\": TokenType.CARET,\n        \":\": TokenType.COLON,\n        \",\": TokenType.COMMA,\n        \".\": TokenType.DOT,\n        \"-\": TokenType.DASH,\n        \"=\": TokenType.EQ,\n        \">\": TokenType.GT,\n        \"<\": TokenType.LT,\n        \"%\": TokenType.MOD,\n        \"!\": TokenType.NOT,\n        \"|\": TokenType.PIPE,\n        \"+\": TokenType.PLUS,\n        \";\": TokenType.SEMICOLON,\n        \"/\": TokenType.SLASH,\n        \"\\\\\": TokenType.BACKSLASH,\n        \"*\": TokenType.STAR,\n        \"~\": TokenType.TILDE,\n        \"?\": TokenType.PLACEHOLDER,\n        \"@\": TokenType.PARAMETER,\n        \"#\": TokenType.HASH,\n        # Used for breaking a var like x'y' but nothing else the token type doesn't matter\n        \"'\": TokenType.UNKNOWN,\n        \"`\": TokenType.UNKNOWN,\n        '\"': TokenType.UNKNOWN,\n    }\n\n    BIT_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]] = []\n    BYTE_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]] = []\n    HEX_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]] = []\n    RAW_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]] = []\n    HEREDOC_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]] = []\n    UNICODE_STRINGS: t.ClassVar[t.List[str | t.Tuple[str, str]]] = []\n    IDENTIFIERS: t.ClassVar[t.List[str | t.Tuple[str, str]]] = ['\"']\n    QUOTES: t.ClassVar[t.List[t.Tuple[str, str] | str]] = [\"'\"]\n    STRING_ESCAPES = [\"'\"]\n    BYTE_STRING_ESCAPES: t.ClassVar[t.List[str]] = []\n    VAR_SINGLE_TOKENS: t.ClassVar[t.Set[str]] = set()\n    ESCAPE_FOLLOW_CHARS: t.ClassVar[t.List[str]] = []\n\n    # The strings in this list can always be used as escapes, regardless of the surrounding\n    # identifier delimiters. By default, the closing delimiter is assumed to also act as an\n    # identifier escape, e.g. if we use double-quotes, then they also act as escapes: \"x\"\"\"\n    IDENTIFIER_ESCAPES: t.ClassVar[t.List[str]] = []\n\n    # Whether the heredoc tags follow the same lexical rules as unquoted identifiers\n    HEREDOC_TAG_IS_IDENTIFIER = False\n\n    # Token that we'll generate as a fallback if the heredoc prefix doesn't correspond to a heredoc\n    HEREDOC_STRING_ALTERNATIVE = TokenType.VAR\n\n    # Whether string escape characters function as such when placed within raw strings\n    STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS = True\n\n    NESTED_COMMENTS = True\n\n    HINT_START = \"/*+\"\n\n    TOKENS_PRECEDING_HINT = {TokenType.SELECT, TokenType.INSERT, TokenType.UPDATE, TokenType.DELETE}\n\n    # Autofilled\n    _COMMENTS: t.ClassVar[t.Dict[str, t.Optional[str]]] = {}\n    _FORMAT_STRINGS: t.ClassVar[t.Dict[str, t.Tuple[str, TokenType]]] = {}\n    _IDENTIFIERS: t.ClassVar[t.Dict[str, str]] = {}\n    _IDENTIFIER_ESCAPES: t.ClassVar[t.Set[str]] = set()\n    _QUOTES: t.ClassVar[t.Dict[str, str]] = {}\n    _STRING_ESCAPES: t.ClassVar[t.Set[str]] = set()\n    _BYTE_STRING_ESCAPES: t.ClassVar[t.Set[str]] = set()\n    _KEYWORD_TRIE: t.ClassVar[t.Dict] = {}\n    _ESCAPE_FOLLOW_CHARS: t.ClassVar[t.Set[str]] = set()\n\n    KEYWORDS: t.ClassVar[t.Dict[str, TokenType]] = {\n        **{f\"{{%{postfix}\": TokenType.BLOCK_START for postfix in (\"\", \"+\", \"-\")},\n        **{f\"{prefix}%}}\": TokenType.BLOCK_END for prefix in (\"\", \"+\", \"-\")},\n        **{f\"{{{{{postfix}\": TokenType.BLOCK_START for postfix in (\"+\", \"-\")},\n        **{f\"{prefix}}}}}\": TokenType.BLOCK_END for prefix in (\"+\", \"-\")},\n        HINT_START: TokenType.HINT,\n        \"&<\": TokenType.AMP_LT,\n        \"&>\": TokenType.AMP_GT,\n        \"==\": TokenType.EQ,\n        \"::\": TokenType.DCOLON,\n        \"?::\": TokenType.QDCOLON,\n        \"||\": TokenType.DPIPE,\n        \"|>\": TokenType.PIPE_GT,\n        \">=\": TokenType.GTE,\n        \"<=\": TokenType.LTE,\n        \"<>\": TokenType.NEQ,\n        \"!=\": TokenType.NEQ,\n        \":=\": TokenType.COLON_EQ,\n        \"<=>\": TokenType.NULLSAFE_EQ,\n        \"->\": TokenType.ARROW,\n        \"->>\": TokenType.DARROW,\n        \"=>\": TokenType.FARROW,\n        \"#>\": TokenType.HASH_ARROW,\n        \"#>>\": TokenType.DHASH_ARROW,\n        \"<->\": TokenType.LR_ARROW,\n        \"&&\": TokenType.DAMP,\n        \"??\": TokenType.DQMARK,\n        \"~~~\": TokenType.GLOB,\n        \"~~\": TokenType.LIKE,\n        \"~~*\": TokenType.ILIKE,\n        \"~*\": TokenType.IRLIKE,\n        \"-|-\": TokenType.ADJACENT,\n        \"ALL\": TokenType.ALL,\n        \"AND\": TokenType.AND,\n        \"ANTI\": TokenType.ANTI,\n        \"ANY\": TokenType.ANY,\n        \"ASC\": TokenType.ASC,\n        \"AS\": TokenType.ALIAS,\n        \"ASOF\": TokenType.ASOF,\n        \"AUTOINCREMENT\": TokenType.AUTO_INCREMENT,\n        \"AUTO_INCREMENT\": TokenType.AUTO_INCREMENT,\n        \"BEGIN\": TokenType.BEGIN,\n        \"BETWEEN\": TokenType.BETWEEN,\n        \"CACHE\": TokenType.CACHE,\n        \"UNCACHE\": TokenType.UNCACHE,\n        \"CASE\": TokenType.CASE,\n        \"CHARACTER SET\": TokenType.CHARACTER_SET,\n        \"CLUSTER BY\": TokenType.CLUSTER_BY,\n        \"COLLATE\": TokenType.COLLATE,\n        \"COLUMN\": TokenType.COLUMN,\n        \"COMMIT\": TokenType.COMMIT,\n        \"CONNECT BY\": TokenType.CONNECT_BY,\n        \"CONSTRAINT\": TokenType.CONSTRAINT,\n        \"COPY\": TokenType.COPY,\n        \"CREATE\": TokenType.CREATE,\n        \"CROSS\": TokenType.CROSS,\n        \"CUBE\": TokenType.CUBE,\n        \"CURRENT_DATE\": TokenType.CURRENT_DATE,\n        \"CURRENT_SCHEMA\": TokenType.CURRENT_SCHEMA,\n        \"CURRENT_TIME\": TokenType.CURRENT_TIME,\n        \"CURRENT_TIMESTAMP\": TokenType.CURRENT_TIMESTAMP,\n        \"CURRENT_USER\": TokenType.CURRENT_USER,\n        \"CURRENT_CATALOG\": TokenType.CURRENT_CATALOG,\n        \"DATABASE\": TokenType.DATABASE,\n        \"DEFAULT\": TokenType.DEFAULT,\n        \"DELETE\": TokenType.DELETE,\n        \"DESC\": TokenType.DESC,\n        \"DESCRIBE\": TokenType.DESCRIBE,\n        \"DISTINCT\": TokenType.DISTINCT,\n        \"DISTRIBUTE BY\": TokenType.DISTRIBUTE_BY,\n        \"DIV\": TokenType.DIV,\n        \"DROP\": TokenType.DROP,\n        \"ELSE\": TokenType.ELSE,\n        \"END\": TokenType.END,\n        \"ENUM\": TokenType.ENUM,\n        \"ESCAPE\": TokenType.ESCAPE,\n        \"EXCEPT\": TokenType.EXCEPT,\n        \"EXECUTE\": TokenType.EXECUTE,\n        \"EXISTS\": TokenType.EXISTS,\n        \"FALSE\": TokenType.FALSE,\n        \"FETCH\": TokenType.FETCH,\n        \"FILTER\": TokenType.FILTER,\n        \"FILE\": TokenType.FILE,\n        \"FIRST\": TokenType.FIRST,\n        \"FULL\": TokenType.FULL,\n        \"FUNCTION\": TokenType.FUNCTION,\n        \"FOR\": TokenType.FOR,\n        \"FOREIGN KEY\": TokenType.FOREIGN_KEY,\n        \"FORMAT\": TokenType.FORMAT,\n        \"FROM\": TokenType.FROM,\n        \"GEOGRAPHY\": TokenType.GEOGRAPHY,\n        \"GEOMETRY\": TokenType.GEOMETRY,\n        \"GLOB\": TokenType.GLOB,\n        \"GROUP BY\": TokenType.GROUP_BY,\n        \"GROUPING SETS\": TokenType.GROUPING_SETS,\n        \"HAVING\": TokenType.HAVING,\n        \"ILIKE\": TokenType.ILIKE,\n        \"IN\": TokenType.IN,\n        \"INDEX\": TokenType.INDEX,\n        \"INET\": TokenType.INET,\n        \"INNER\": TokenType.INNER,\n        \"INSERT\": TokenType.INSERT,\n        \"INTERVAL\": TokenType.INTERVAL,\n        \"INTERSECT\": TokenType.INTERSECT,\n        \"INTO\": TokenType.INTO,\n        \"IS\": TokenType.IS,\n        \"ISNULL\": TokenType.ISNULL,\n        \"JOIN\": TokenType.JOIN,\n        \"KEEP\": TokenType.KEEP,\n        \"KILL\": TokenType.KILL,\n        \"LATERAL\": TokenType.LATERAL,\n        \"LEFT\": TokenType.LEFT,\n        \"LIKE\": TokenType.LIKE,\n        \"LIMIT\": TokenType.LIMIT,\n        \"LOAD\": TokenType.LOAD,\n        \"LOCALTIME\": TokenType.LOCALTIME,\n        \"LOCALTIMESTAMP\": TokenType.LOCALTIMESTAMP,\n        \"LOCK\": TokenType.LOCK,\n        \"MERGE\": TokenType.MERGE,\n        \"NAMESPACE\": TokenType.NAMESPACE,\n        \"NATURAL\": TokenType.NATURAL,\n        \"NEXT\": TokenType.NEXT,\n        \"NOT\": TokenType.NOT,\n        \"NOTNULL\": TokenType.NOTNULL,\n        \"NULL\": TokenType.NULL,\n        \"OBJECT\": TokenType.OBJECT,\n        \"OFFSET\": TokenType.OFFSET,\n        \"ON\": TokenType.ON,\n        \"OR\": TokenType.OR,\n        \"XOR\": TokenType.XOR,\n        \"ORDER BY\": TokenType.ORDER_BY,\n        \"ORDINALITY\": TokenType.ORDINALITY,\n        \"OUT\": TokenType.OUT,\n        \"OUTER\": TokenType.OUTER,\n        \"OVER\": TokenType.OVER,\n        \"OVERLAPS\": TokenType.OVERLAPS,\n        \"OVERWRITE\": TokenType.OVERWRITE,\n        \"PARTITION\": TokenType.PARTITION,\n        \"PARTITION BY\": TokenType.PARTITION_BY,\n        \"PARTITIONED BY\": TokenType.PARTITION_BY,\n        \"PARTITIONED_BY\": TokenType.PARTITION_BY,\n        \"PERCENT\": TokenType.PERCENT,\n        \"PIVOT\": TokenType.PIVOT,\n        \"PRAGMA\": TokenType.PRAGMA,\n        \"PRIMARY KEY\": TokenType.PRIMARY_KEY,\n        \"PROCEDURE\": TokenType.PROCEDURE,\n        \"OPERATOR\": TokenType.OPERATOR,\n        \"QUALIFY\": TokenType.QUALIFY,\n        \"RANGE\": TokenType.RANGE,\n        \"RECURSIVE\": TokenType.RECURSIVE,\n        \"REGEXP\": TokenType.RLIKE,\n        \"RENAME\": TokenType.RENAME,\n        \"REPLACE\": TokenType.REPLACE,\n        \"RETURNING\": TokenType.RETURNING,\n        \"REFERENCES\": TokenType.REFERENCES,\n        \"RIGHT\": TokenType.RIGHT,\n        \"RLIKE\": TokenType.RLIKE,\n        \"ROLLBACK\": TokenType.ROLLBACK,\n        \"ROLLUP\": TokenType.ROLLUP,\n        \"ROW\": TokenType.ROW,\n        \"ROWS\": TokenType.ROWS,\n        \"SCHEMA\": TokenType.SCHEMA,\n        \"SELECT\": TokenType.SELECT,\n        \"SEMI\": TokenType.SEMI,\n        \"SESSION\": TokenType.SESSION,\n        \"SESSION_USER\": TokenType.SESSION_USER,\n        \"SET\": TokenType.SET,\n        \"SETTINGS\": TokenType.SETTINGS,\n        \"SHOW\": TokenType.SHOW,\n        \"SIMILAR TO\": TokenType.SIMILAR_TO,\n        \"SOME\": TokenType.SOME,\n        \"SORT BY\": TokenType.SORT_BY,\n        \"SQL SECURITY\": TokenType.SQL_SECURITY,\n        \"START WITH\": TokenType.START_WITH,\n        \"STRAIGHT_JOIN\": TokenType.STRAIGHT_JOIN,\n        \"TABLE\": TokenType.TABLE,\n        \"TABLESAMPLE\": TokenType.TABLE_SAMPLE,\n        \"TEMP\": TokenType.TEMPORARY,\n        \"TEMPORARY\": TokenType.TEMPORARY,\n        \"THEN\": TokenType.THEN,\n        \"TRUE\": TokenType.TRUE,\n        \"TRUNCATE\": TokenType.TRUNCATE,\n        \"TRIGGER\": TokenType.TRIGGER,\n        \"UNION\": TokenType.UNION,\n        \"UNKNOWN\": TokenType.UNKNOWN,\n        \"UNNEST\": TokenType.UNNEST,\n        \"UNPIVOT\": TokenType.UNPIVOT,\n        \"UPDATE\": TokenType.UPDATE,\n        \"USE\": TokenType.USE,\n        \"USING\": TokenType.USING,\n        \"UUID\": TokenType.UUID,\n        \"VALUES\": TokenType.VALUES,\n        \"VIEW\": TokenType.VIEW,\n        \"VOLATILE\": TokenType.VOLATILE,\n        \"WHEN\": TokenType.WHEN,\n        \"WHERE\": TokenType.WHERE,\n        \"WINDOW\": TokenType.WINDOW,\n        \"WITH\": TokenType.WITH,\n        \"APPLY\": TokenType.APPLY,\n        \"ARRAY\": TokenType.ARRAY,\n        \"BIT\": TokenType.BIT,\n        \"BOOL\": TokenType.BOOLEAN,\n        \"BOOLEAN\": TokenType.BOOLEAN,\n        \"BYTE\": TokenType.TINYINT,\n        \"MEDIUMINT\": TokenType.MEDIUMINT,\n        \"INT1\": TokenType.TINYINT,\n        \"TINYINT\": TokenType.TINYINT,\n        \"INT16\": TokenType.SMALLINT,\n        \"SHORT\": TokenType.SMALLINT,\n        \"SMALLINT\": TokenType.SMALLINT,\n        \"HUGEINT\": TokenType.INT128,\n        \"UHUGEINT\": TokenType.UINT128,\n        \"INT2\": TokenType.SMALLINT,\n        \"INTEGER\": TokenType.INT,\n        \"INT\": TokenType.INT,\n        \"INT4\": TokenType.INT,\n        \"INT32\": TokenType.INT,\n        \"INT64\": TokenType.BIGINT,\n        \"INT128\": TokenType.INT128,\n        \"INT256\": TokenType.INT256,\n        \"LONG\": TokenType.BIGINT,\n        \"BIGINT\": TokenType.BIGINT,\n        \"INT8\": TokenType.TINYINT,\n        \"UINT\": TokenType.UINT,\n        \"UINT128\": TokenType.UINT128,\n        \"UINT256\": TokenType.UINT256,\n        \"DEC\": TokenType.DECIMAL,\n        \"DECIMAL\": TokenType.DECIMAL,\n        \"DECIMAL32\": TokenType.DECIMAL32,\n        \"DECIMAL64\": TokenType.DECIMAL64,\n        \"DECIMAL128\": TokenType.DECIMAL128,\n        \"DECIMAL256\": TokenType.DECIMAL256,\n        \"DECFLOAT\": TokenType.DECFLOAT,\n        \"BIGDECIMAL\": TokenType.BIGDECIMAL,\n        \"BIGNUMERIC\": TokenType.BIGDECIMAL,\n        \"BIGNUM\": TokenType.BIGNUM,\n        \"LIST\": TokenType.LIST,\n        \"MAP\": TokenType.MAP,\n        \"NULLABLE\": TokenType.NULLABLE,\n        \"NUMBER\": TokenType.DECIMAL,\n        \"NUMERIC\": TokenType.DECIMAL,\n        \"FIXED\": TokenType.DECIMAL,\n        \"REAL\": TokenType.FLOAT,\n        \"FLOAT\": TokenType.FLOAT,\n        \"FLOAT4\": TokenType.FLOAT,\n        \"FLOAT8\": TokenType.DOUBLE,\n        \"DOUBLE\": TokenType.DOUBLE,\n        \"DOUBLE PRECISION\": TokenType.DOUBLE,\n        \"JSON\": TokenType.JSON,\n        \"JSONB\": TokenType.JSONB,\n        \"CHAR\": TokenType.CHAR,\n        \"CHARACTER\": TokenType.CHAR,\n        \"CHAR VARYING\": TokenType.VARCHAR,\n        \"CHARACTER VARYING\": TokenType.VARCHAR,\n        \"NCHAR\": TokenType.NCHAR,\n        \"VARCHAR\": TokenType.VARCHAR,\n        \"VARCHAR2\": TokenType.VARCHAR,\n        \"NVARCHAR\": TokenType.NVARCHAR,\n        \"NVARCHAR2\": TokenType.NVARCHAR,\n        \"BPCHAR\": TokenType.BPCHAR,\n        \"STR\": TokenType.TEXT,\n        \"STRING\": TokenType.TEXT,\n        \"TEXT\": TokenType.TEXT,\n        \"LONGTEXT\": TokenType.LONGTEXT,\n        \"MEDIUMTEXT\": TokenType.MEDIUMTEXT,\n        \"TINYTEXT\": TokenType.TINYTEXT,\n        \"CLOB\": TokenType.TEXT,\n        \"LONGVARCHAR\": TokenType.TEXT,\n        \"BINARY\": TokenType.BINARY,\n        \"BLOB\": TokenType.VARBINARY,\n        \"LONGBLOB\": TokenType.LONGBLOB,\n        \"MEDIUMBLOB\": TokenType.MEDIUMBLOB,\n        \"TINYBLOB\": TokenType.TINYBLOB,\n        \"BYTEA\": TokenType.VARBINARY,\n        \"VARBINARY\": TokenType.VARBINARY,\n        \"TIME\": TokenType.TIME,\n        \"TIMETZ\": TokenType.TIMETZ,\n        \"TIME_NS\": TokenType.TIME_NS,\n        \"TIMESTAMP\": TokenType.TIMESTAMP,\n        \"TIMESTAMPTZ\": TokenType.TIMESTAMPTZ,\n        \"TIMESTAMPLTZ\": TokenType.TIMESTAMPLTZ,\n        \"TIMESTAMP_LTZ\": TokenType.TIMESTAMPLTZ,\n        \"TIMESTAMPNTZ\": TokenType.TIMESTAMPNTZ,\n        \"TIMESTAMP_NTZ\": TokenType.TIMESTAMPNTZ,\n        \"DATE\": TokenType.DATE,\n        \"DATETIME\": TokenType.DATETIME,\n        \"INT4RANGE\": TokenType.INT4RANGE,\n        \"INT4MULTIRANGE\": TokenType.INT4MULTIRANGE,\n        \"INT8RANGE\": TokenType.INT8RANGE,\n        \"INT8MULTIRANGE\": TokenType.INT8MULTIRANGE,\n        \"NUMRANGE\": TokenType.NUMRANGE,\n        \"NUMMULTIRANGE\": TokenType.NUMMULTIRANGE,\n        \"TSRANGE\": TokenType.TSRANGE,\n        \"TSMULTIRANGE\": TokenType.TSMULTIRANGE,\n        \"TSTZRANGE\": TokenType.TSTZRANGE,\n        \"TSTZMULTIRANGE\": TokenType.TSTZMULTIRANGE,\n        \"DATERANGE\": TokenType.DATERANGE,\n        \"DATEMULTIRANGE\": TokenType.DATEMULTIRANGE,\n        \"UNIQUE\": TokenType.UNIQUE,\n        \"VECTOR\": TokenType.VECTOR,\n        \"STRUCT\": TokenType.STRUCT,\n        \"SEQUENCE\": TokenType.SEQUENCE,\n        \"VARIANT\": TokenType.VARIANT,\n        \"ALTER\": TokenType.ALTER,\n        \"ANALYZE\": TokenType.ANALYZE,\n        \"CALL\": TokenType.COMMAND,\n        \"COMMENT\": TokenType.COMMENT,\n        \"EXPLAIN\": TokenType.COMMAND,\n        \"GRANT\": TokenType.GRANT,\n        \"REVOKE\": TokenType.REVOKE,\n        \"OPTIMIZE\": TokenType.COMMAND,\n        \"PREPARE\": TokenType.COMMAND,\n        \"VACUUM\": TokenType.COMMAND,\n        \"USER-DEFINED\": TokenType.USERDEFINED,\n        \"FOR VERSION\": TokenType.VERSION_SNAPSHOT,\n        \"FOR TIMESTAMP\": TokenType.TIMESTAMP_SNAPSHOT,\n    }\n\n    COMMANDS = {\n        TokenType.COMMAND,\n        TokenType.EXECUTE,\n        TokenType.FETCH,\n        TokenType.SHOW,\n        TokenType.RENAME,\n    }\n\n    COMMAND_PREFIX_TOKENS = {TokenType.SEMICOLON, TokenType.BEGIN}\n\n    # Handle numeric literals like in hive (3L = BIGINT)\n    NUMERIC_LITERALS: t.ClassVar[t.Dict[str, str]] = {}\n\n    COMMENTS = [\"--\", (\"/*\", \"*/\")]\n\n    __slots__ = (\n        \"dialect\",\n        \"_core\",\n    )\n\n    def __init__(\n        self,\n        dialect: DialectType = None,\n        **opts: t.Any,\n    ) -> None:\n        from sqlglot.dialects import Dialect\n        from sqlglot.tokenizer_core import TokenizerCore as _TokenizerCore\n\n        self.dialect = Dialect.get_or_raise(dialect)\n\n        self._core = _TokenizerCore(\n            single_tokens=self.SINGLE_TOKENS,\n            keywords=self.KEYWORDS,\n            quotes=self._QUOTES,\n            format_strings=self._FORMAT_STRINGS,\n            identifiers=self._IDENTIFIERS,\n            comments=self._COMMENTS,\n            string_escapes=self._STRING_ESCAPES,\n            byte_string_escapes=self._BYTE_STRING_ESCAPES,\n            identifier_escapes=self._IDENTIFIER_ESCAPES,\n            escape_follow_chars=self._ESCAPE_FOLLOW_CHARS,\n            commands=self.COMMANDS,\n            command_prefix_tokens=self.COMMAND_PREFIX_TOKENS,\n            nested_comments=self.NESTED_COMMENTS,\n            hint_start=self.HINT_START,\n            tokens_preceding_hint=self.TOKENS_PRECEDING_HINT,\n            bit_strings=list(self.BIT_STRINGS),\n            hex_strings=list(self.HEX_STRINGS),\n            numeric_literals=self.NUMERIC_LITERALS,\n            var_single_tokens=self.VAR_SINGLE_TOKENS,\n            string_escapes_allowed_in_raw_strings=self.STRING_ESCAPES_ALLOWED_IN_RAW_STRINGS,\n            heredoc_tag_is_identifier=self.HEREDOC_TAG_IS_IDENTIFIER,\n            heredoc_string_alternative=self.HEREDOC_STRING_ALTERNATIVE,\n            keyword_trie=self._KEYWORD_TRIE,\n            numbers_can_be_underscore_separated=self.dialect.NUMBERS_CAN_BE_UNDERSCORE_SEPARATED,\n            identifiers_can_start_with_digit=self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT,\n            unescaped_sequences=self.dialect.UNESCAPED_SEQUENCES,\n        )\n\n    def tokenize(self, sql: str) -> t.List[Token]:\n        \"\"\"Returns a list of tokens corresponding to the SQL string `sql`.\"\"\"\n        return self._core.tokenize(sql)  # type: ignore\n\n    @property\n    def sql(self) -> str:\n        \"\"\"The SQL string being tokenized.\"\"\"\n        return self._core.sql\n\n    @property\n    def size(self) -> int:\n        \"\"\"Length of the SQL string.\"\"\"\n        return self._core.size\n\n    @property\n    def tokens(self) -> t.List[Token]:\n        \"\"\"The list of tokens produced by tokenization.\"\"\"\n        return self._core.tokens\n"
  },
  {
    "path": "sqlglot/transforms.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import expressions as exp\nfrom sqlglot.errors import UnsupportedError\nfrom sqlglot.helper import find_new_name, name_sequence, seq_get\n\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from sqlglot.generator import Generator\n\n\ndef preprocess(\n    transforms: t.List[t.Callable[[exp.Expr], exp.Expr]],\n    generator: t.Optional[t.Callable[[Generator, exp.Expr], str]] = None,\n) -> t.Callable[[Generator, exp.Expr], str]:\n    \"\"\"\n    Creates a new transform by chaining a sequence of transformations and converts the resulting\n    expression to SQL, using either the \"_sql\" method corresponding to the resulting expression,\n    or the appropriate `Generator.TRANSFORMS` function (when applicable -- see below).\n\n    Args:\n        transforms: sequence of transform functions. These will be called in order.\n\n    Returns:\n        Function that can be used as a generator transform.\n    \"\"\"\n\n    def _to_sql(self, expression: exp.Expr) -> str:\n        expression_type = type(expression)\n\n        try:\n            expression = transforms[0](expression)\n            for transform in transforms[1:]:\n                expression = transform(expression)\n        except UnsupportedError as unsupported_error:\n            self.unsupported(str(unsupported_error))\n\n        if generator:\n            return generator(self, expression)\n\n        _sql_handler = getattr(self, expression.key + \"_sql\", None)\n        if _sql_handler:\n            return _sql_handler(expression)\n\n        transforms_handler = self.TRANSFORMS.get(type(expression))\n        if transforms_handler:\n            if expression_type is type(expression):\n                if isinstance(expression, exp.Func):\n                    return self.function_fallback_sql(expression)\n\n                # Ensures we don't enter an infinite loop. This can happen when the original expression\n                # has the same type as the final expression and there's no _sql method available for it,\n                # because then it'd re-enter _to_sql.\n                raise ValueError(\n                    f\"Expr type {expression.__class__.__name__} requires a _sql method in order to be transformed.\"\n                )\n\n            return transforms_handler(self, expression)\n\n        raise ValueError(f\"Unsupported expression type {expression.__class__.__name__}.\")\n\n    return _to_sql\n\n\ndef unnest_generate_date_array_using_recursive_cte(expression: exp.Expr) -> exp.Expr:\n    if isinstance(expression, exp.Select):\n        count = 0\n        recursive_ctes = []\n\n        for unnest in expression.find_all(exp.Unnest):\n            if (\n                not isinstance(unnest.parent, (exp.From, exp.Join))\n                or len(unnest.expressions) != 1\n                or not isinstance(unnest.expressions[0], exp.GenerateDateArray)\n            ):\n                continue\n\n            generate_date_array = unnest.expressions[0]\n            start = generate_date_array.args.get(\"start\")\n            end = generate_date_array.args.get(\"end\")\n            step = generate_date_array.args.get(\"step\")\n\n            if not start or not end or not isinstance(step, exp.Interval):\n                continue\n\n            alias = unnest.args.get(\"alias\")\n            column_name = alias.columns[0] if isinstance(alias, exp.TableAlias) else \"date_value\"\n\n            start = exp.cast(start, \"date\")\n            date_add = exp.func(\n                \"date_add\", column_name, exp.Literal.number(step.name), step.args.get(\"unit\")\n            )\n            cast_date_add = exp.cast(date_add, \"date\")\n\n            cte_name = \"_generated_dates\" + (f\"_{count}\" if count else \"\")\n\n            base_query = exp.select(start.as_(column_name))\n            recursive_query = (\n                exp.select(cast_date_add)\n                .from_(cte_name)\n                .where(cast_date_add <= exp.cast(end, \"date\"))\n            )\n            cte_query = base_query.union(recursive_query, distinct=False)\n\n            generate_dates_query = exp.select(column_name).from_(cte_name)\n            unnest.replace(generate_dates_query.subquery(cte_name))\n\n            recursive_ctes.append(\n                exp.alias_(exp.CTE(this=cte_query), cte_name, table=[column_name])\n            )\n            count += 1\n\n        if recursive_ctes:\n            with_expression = expression.args.get(\"with_\") or exp.With()\n            with_expression.set(\"recursive\", True)\n            with_expression.set(\"expressions\", [*recursive_ctes, *with_expression.expressions])\n            expression.set(\"with_\", with_expression)\n\n    return expression\n\n\ndef unnest_generate_series(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Unnests GENERATE_SERIES or SEQUENCE table references.\"\"\"\n    this = expression.this\n    if isinstance(expression, exp.Table) and isinstance(this, exp.GenerateSeries):\n        unnest = exp.Unnest(expressions=[this])\n        if expression.alias:\n            return exp.alias_(unnest, alias=\"_u\", table=[expression.alias], copy=False)\n\n        return unnest\n\n    return expression\n\n\ndef eliminate_distinct_on(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Convert SELECT DISTINCT ON statements to a subquery with a window function.\n\n    This is useful for dialects that don't support SELECT DISTINCT ON but support window functions.\n\n    Args:\n        expression: the expression that will be transformed.\n\n    Returns:\n        The transformed expression.\n    \"\"\"\n    if (\n        isinstance(expression, exp.Select)\n        and expression.args.get(\"distinct\")\n        and isinstance(expression.args[\"distinct\"].args.get(\"on\"), exp.Tuple)\n    ):\n        row_number_window_alias = find_new_name(expression.named_selects, \"_row_number\")\n\n        distinct_cols = expression.args[\"distinct\"].pop().args[\"on\"].expressions\n        window = exp.Window(this=exp.RowNumber(), partition_by=distinct_cols)\n\n        order = expression.args.get(\"order\")\n        if order:\n            window.set(\"order\", order.pop())\n        else:\n            window.set(\"order\", exp.Order(expressions=[c.copy() for c in distinct_cols]))\n\n        expression.select(exp.alias_(window, row_number_window_alias), copy=False)\n\n        # We add aliases to the projections so that we can safely reference them in the outer query\n        new_selects = []\n        taken_names = {row_number_window_alias}\n        for select in expression.selects[:-1]:\n            if select.is_star:\n                new_selects = [exp.Star()]\n                break\n\n            if not isinstance(select, exp.Alias):\n                alias = find_new_name(taken_names, select.output_name or \"_col\")\n                quoted = select.this.args.get(\"quoted\") if isinstance(select, exp.Column) else None\n                select = select.replace(exp.alias_(select, alias, quoted=quoted))\n\n            taken_names.add(select.output_name)\n            new_selects.append(select.args[\"alias\"])\n\n        return (\n            exp.select(*new_selects, copy=False)\n            .from_(expression.subquery(\"_t\", copy=False), copy=False)\n            .where(exp.column(row_number_window_alias).eq(1), copy=False)\n        )\n\n    return expression\n\n\ndef eliminate_qualify(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Convert SELECT statements that contain the QUALIFY clause into subqueries, filtered equivalently.\n\n    The idea behind this transformation can be seen in Snowflake's documentation for QUALIFY:\n    https://docs.snowflake.com/en/sql-reference/constructs/qualify\n\n    Some dialects don't support window functions in the WHERE clause, so we need to include them as\n    projections in the subquery, in order to refer to them in the outer filter using aliases. Also,\n    if a column is referenced in the QUALIFY clause but is not selected, we need to include it too,\n    otherwise we won't be able to refer to it in the outer query's WHERE clause. Finally, if a\n    newly aliased projection is referenced in the QUALIFY clause, it will be replaced by the\n    corresponding expression to avoid creating invalid column references.\n    \"\"\"\n    if isinstance(expression, exp.Select) and expression.args.get(\"qualify\"):\n        taken = set(expression.named_selects)\n        for select in expression.selects:\n            if not select.alias_or_name:\n                alias = find_new_name(taken, \"_c\")\n                select.replace(exp.alias_(select, alias))\n                taken.add(alias)\n\n        def _select_alias_or_name(select: exp.Expr) -> str | exp.Column:\n            alias_or_name = select.alias_or_name\n            identifier = select.args.get(\"alias\") or select.this\n            if isinstance(identifier, exp.Identifier):\n                return exp.column(alias_or_name, quoted=identifier.args.get(\"quoted\"))\n            return alias_or_name\n\n        outer_selects = exp.select(*list(map(_select_alias_or_name, expression.selects)))\n        qualify_filters = expression.args[\"qualify\"].pop().this\n        expression_by_alias = {\n            select.alias: select.this\n            for select in expression.selects\n            if isinstance(select, exp.Alias)\n        }\n\n        select_candidates = exp.Window if expression.is_star else (exp.Window, exp.Column)\n        for select_candidate in list(qualify_filters.find_all(select_candidates)):\n            if isinstance(select_candidate, exp.Window):\n                if expression_by_alias:\n                    for column in select_candidate.find_all(exp.Column):\n                        expr = expression_by_alias.get(column.name)\n                        if expr:\n                            column.replace(expr)\n\n                alias = find_new_name(expression.named_selects, \"_w\")\n                expression.select(exp.alias_(select_candidate, alias), copy=False)\n                column = exp.column(alias)\n\n                if isinstance(select_candidate.parent, exp.Qualify):\n                    qualify_filters = column\n                else:\n                    select_candidate.replace(column)\n            elif select_candidate.name not in expression.named_selects:\n                expression.select(select_candidate.copy(), copy=False)\n\n        return outer_selects.from_(expression.subquery(alias=\"_t\", copy=False), copy=False).where(\n            qualify_filters, copy=False\n        )\n\n    return expression\n\n\ndef remove_precision_parameterized_types(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Some dialects only allow the precision for parameterized types to be defined in the DDL and not in\n    other expressions. This transforms removes the precision from parameterized types in expressions.\n    \"\"\"\n    for node in expression.find_all(exp.DataType):\n        node.set(\n            \"expressions\", [e for e in node.expressions if not isinstance(e, exp.DataTypeParam)]\n        )\n\n    return expression\n\n\ndef unqualify_unnest(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Remove references to unnest table aliases, added by the optimizer's qualify_columns step.\"\"\"\n    from sqlglot.optimizer.scope import find_all_in_scope\n\n    if isinstance(expression, exp.Select):\n        unnest_aliases = {\n            unnest.alias\n            for unnest in find_all_in_scope(expression, exp.Unnest)\n            if isinstance(unnest.parent, (exp.From, exp.Join))\n        }\n        if unnest_aliases:\n            for column in expression.find_all(exp.Column):\n                leftmost_part = column.parts[0]\n                if leftmost_part.arg_key != \"this\" and leftmost_part.this in unnest_aliases:\n                    leftmost_part.pop()\n\n    return expression\n\n\ndef unnest_to_explode(\n    expression: exp.Expr,\n    unnest_using_arrays_zip: bool = True,\n) -> exp.Expr:\n    \"\"\"Convert cross join unnest into lateral view explode.\"\"\"\n\n    def _unnest_zip_exprs(\n        u: exp.Unnest, unnest_exprs: t.List[exp.Expr], has_multi_expr: bool\n    ) -> t.List[exp.Expr]:\n        if has_multi_expr:\n            if not unnest_using_arrays_zip:\n                raise UnsupportedError(\"Cannot transpile UNNEST with multiple input arrays\")\n\n            # Use INLINE(ARRAYS_ZIP(...)) for multiple expressions\n            zip_exprs: t.List[exp.Expr] = [\n                exp.Anonymous(this=\"ARRAYS_ZIP\", expressions=unnest_exprs)\n            ]\n            u.set(\"expressions\", zip_exprs)\n            return zip_exprs\n        return unnest_exprs\n\n    def _udtf_type(u: exp.Unnest, has_multi_expr: bool) -> t.Type[exp.Func]:\n        if u.args.get(\"offset\"):\n            return exp.Posexplode\n        return exp.Inline if has_multi_expr else exp.Explode\n\n    if isinstance(expression, exp.Select):\n        from_ = expression.args.get(\"from_\")\n\n        if from_ and isinstance(from_.this, exp.Unnest):\n            unnest = from_.this\n            alias = unnest.args.get(\"alias\")\n            exprs = unnest.expressions\n            has_multi_expr = len(exprs) > 1\n            this, *_ = _unnest_zip_exprs(unnest, exprs, has_multi_expr)\n\n            columns = alias.columns if alias else []\n            offset = unnest.args.get(\"offset\")\n            if offset:\n                columns.insert(\n                    0, offset if isinstance(offset, exp.Identifier) else exp.to_identifier(\"pos\")\n                )\n\n            unnest.replace(\n                exp.Table(\n                    this=_udtf_type(unnest, has_multi_expr)(this=this),\n                    alias=exp.TableAlias(this=alias.this, columns=columns) if alias else None,\n                )\n            )\n\n        joins = expression.args.get(\"joins\") or []\n        for join in list(joins):\n            join_expr = join.this\n\n            is_lateral = isinstance(join_expr, exp.Lateral)\n\n            unnest = join_expr.this if is_lateral else join_expr\n\n            if isinstance(unnest, exp.Unnest):\n                if is_lateral:\n                    alias = join_expr.args.get(\"alias\")\n                else:\n                    alias = unnest.args.get(\"alias\")\n                exprs = unnest.expressions\n                # The number of unnest.expressions will be changed by _unnest_zip_exprs, we need to record it here\n                has_multi_expr = len(exprs) > 1\n                exprs = _unnest_zip_exprs(unnest, exprs, has_multi_expr)\n\n                joins.remove(join)\n\n                alias_cols = alias.columns if alias else []\n\n                # # Handle UNNEST to LATERAL VIEW EXPLODE: Exception is raised when there are 0 or > 2 aliases\n                # Spark LATERAL VIEW EXPLODE requires single alias for array/struct and two for Map type column unlike unnest in trino/presto which can take an arbitrary amount.\n                # Refs: https://spark.apache.org/docs/latest/sql-ref-syntax-qry-select-lateral-view.html\n\n                if not has_multi_expr and len(alias_cols) not in (1, 2):\n                    raise UnsupportedError(\n                        \"CROSS JOIN UNNEST to LATERAL VIEW EXPLODE transformation requires explicit column aliases\"\n                    )\n\n                offset = unnest.args.get(\"offset\")\n                if offset:\n                    alias_cols.insert(\n                        0,\n                        offset if isinstance(offset, exp.Identifier) else exp.to_identifier(\"pos\"),\n                    )\n\n                for e, column in zip(exprs, alias_cols):\n                    expression.append(\n                        \"laterals\",\n                        exp.Lateral(\n                            this=_udtf_type(unnest, has_multi_expr)(this=e),\n                            view=True,\n                            alias=exp.TableAlias(\n                                this=alias.this,  # type: ignore\n                                columns=alias_cols,\n                            ),\n                        ),\n                    )\n\n    return expression\n\n\ndef explode_projection_to_unnest(\n    index_offset: int = 0,\n) -> t.Callable[[exp.Expr], exp.Expr]:\n    \"\"\"Convert explode/posexplode projections into unnests.\"\"\"\n\n    def _explode_projection_to_unnest(expression: exp.Expr) -> exp.Expr:\n        if isinstance(expression, exp.Select):\n            from sqlglot.optimizer.scope import Scope\n\n            taken_select_names = set(expression.named_selects)\n            taken_source_names = {name for name, _ in Scope(expression).references}\n\n            def new_name(names: t.Set[str], name: str) -> str:\n                name = find_new_name(names, name)\n                names.add(name)\n                return name\n\n            arrays: t.List[exp.Condition] = []\n            series_alias = new_name(taken_select_names, \"pos\")\n            series = exp.alias_(\n                exp.Unnest(\n                    expressions=[exp.GenerateSeries(start=exp.Literal.number(index_offset))]\n                ),\n                new_name(taken_source_names, \"_u\"),\n                table=[series_alias],\n            )\n\n            # we use list here because expression.selects is mutated inside the loop\n            for select in list(expression.selects):\n                explode = select.find(exp.Explode)\n\n                if explode:\n                    pos_alias: t.Any = \"\"\n                    explode_alias: t.Any = \"\"\n\n                    if isinstance(select, exp.Alias):\n                        explode_alias = select.args[\"alias\"]\n                        alias = select\n                    elif isinstance(select, exp.Aliases):\n                        pos_alias = select.aliases[0]\n                        explode_alias = select.aliases[1]\n                        alias = select.replace(exp.alias_(select.this, \"\", copy=False))\n                    else:\n                        alias = select.replace(exp.alias_(select, \"\"))\n                        explode = alias.find(exp.Explode)\n                        assert explode\n\n                    is_posexplode = isinstance(explode, exp.Posexplode)\n                    explode_arg = explode.this\n\n                    if isinstance(explode, exp.ExplodeOuter):\n                        bracket = explode_arg[0]\n                        bracket.set(\"safe\", True)\n                        bracket.set(\"offset\", True)\n                        explode_arg = exp.func(\n                            \"IF\",\n                            exp.func(\n                                \"ARRAY_SIZE\", exp.func(\"COALESCE\", explode_arg, exp.Array())\n                            ).eq(0),\n                            exp.array(bracket, copy=False),\n                            explode_arg,\n                        )\n\n                    # This ensures that we won't use [POS]EXPLODE's argument as a new selection\n                    if isinstance(explode_arg, exp.Column):\n                        taken_select_names.add(explode_arg.output_name)\n\n                    unnest_source_alias = new_name(taken_source_names, \"_u\")\n\n                    if not explode_alias:\n                        explode_alias = new_name(taken_select_names, \"col\")\n\n                        if is_posexplode:\n                            pos_alias = new_name(taken_select_names, \"pos\")\n\n                    if not pos_alias:\n                        pos_alias = new_name(taken_select_names, \"pos\")\n\n                    alias.set(\"alias\", exp.to_identifier(explode_alias))\n\n                    series_table_alias = series.args[\"alias\"].this\n                    column = exp.If(\n                        this=exp.column(series_alias, table=series_table_alias).eq(\n                            exp.column(pos_alias, table=unnest_source_alias)\n                        ),\n                        true=exp.column(explode_alias, table=unnest_source_alias),\n                    )\n\n                    explode.replace(column)\n\n                    if is_posexplode:\n                        expressions = expression.expressions\n                        expressions.insert(\n                            expressions.index(alias) + 1,\n                            exp.If(\n                                this=exp.column(series_alias, table=series_table_alias).eq(\n                                    exp.column(pos_alias, table=unnest_source_alias)\n                                ),\n                                true=exp.column(pos_alias, table=unnest_source_alias),\n                            ).as_(pos_alias),\n                        )\n                        expression.set(\"expressions\", expressions)\n\n                    if not arrays:\n                        if expression.args.get(\"from_\"):\n                            expression.join(series, copy=False, join_type=\"CROSS\")\n                        else:\n                            expression.from_(series, copy=False)\n\n                    size: exp.Condition = exp.ArraySize(this=explode_arg.copy())\n                    arrays.append(size)\n\n                    # trino doesn't support left join unnest with on conditions\n                    # if it did, this would be much simpler\n                    expression.join(\n                        exp.alias_(\n                            exp.Unnest(\n                                expressions=[explode_arg.copy()],\n                                offset=exp.to_identifier(pos_alias),\n                            ),\n                            unnest_source_alias,\n                            table=[explode_alias],\n                        ),\n                        join_type=\"CROSS\",\n                        copy=False,\n                    )\n\n                    if index_offset != 1:\n                        size = size - 1\n\n                    expression.where(\n                        exp.column(series_alias, table=series_table_alias)\n                        .eq(exp.column(pos_alias, table=unnest_source_alias))\n                        .or_(\n                            (exp.column(series_alias, table=series_table_alias) > size).and_(\n                                exp.column(pos_alias, table=unnest_source_alias).eq(size)\n                            )\n                        ),\n                        copy=False,\n                    )\n\n            if arrays:\n                end: exp.Condition = exp.Greatest(this=arrays[0], expressions=arrays[1:])\n\n                if index_offset != 1:\n                    end = end - (1 - index_offset)\n                series.expressions[0].set(\"end\", end)\n\n        return expression\n\n    return _explode_projection_to_unnest\n\n\ndef add_within_group_for_percentiles(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Transforms percentiles by adding a WITHIN GROUP clause to them.\"\"\"\n    if (\n        isinstance(expression, exp.PERCENTILES)\n        and not isinstance(expression.parent, exp.WithinGroup)\n        and expression.expression\n    ):\n        column = expression.this.pop()\n        expression.set(\"this\", expression.expression.pop())\n        order = exp.Order(expressions=[exp.Ordered(this=column)])\n        expression = exp.WithinGroup(this=expression, expression=order)\n\n    return expression\n\n\ndef remove_within_group_for_percentiles(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Transforms percentiles by getting rid of their corresponding WITHIN GROUP clause.\"\"\"\n    if (\n        isinstance(expression, exp.WithinGroup)\n        and isinstance(expression.this, exp.PERCENTILES)\n        and isinstance(expression.expression, exp.Order)\n    ):\n        quantile = expression.this.this\n        input_value = t.cast(exp.Ordered, expression.find(exp.Ordered)).this\n        return expression.replace(exp.ApproxQuantile(this=input_value, quantile=quantile))\n\n    return expression\n\n\ndef add_recursive_cte_column_names(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Uses projection output names in recursive CTE definitions to define the CTEs' columns.\"\"\"\n    if isinstance(expression, exp.With) and expression.recursive:\n        next_name = name_sequence(\"_c_\")\n\n        for cte in expression.expressions:\n            if not cte.args[\"alias\"].columns:\n                query = cte.this\n                if isinstance(query, exp.SetOperation):\n                    query = query.this\n\n                cte.args[\"alias\"].set(\n                    \"columns\",\n                    [exp.to_identifier(s.alias_or_name or next_name()) for s in query.selects],\n                )\n\n    return expression\n\n\ndef epoch_cast_to_ts(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Replace 'epoch' in casts by the equivalent date literal.\"\"\"\n    if (\n        isinstance(expression, (exp.Cast, exp.TryCast))\n        and expression.name.lower() == \"epoch\"\n        and expression.to.this in exp.DataType.TEMPORAL_TYPES\n    ):\n        expression.this.replace(exp.Literal.string(\"1970-01-01 00:00:00\"))\n\n    return expression\n\n\ndef eliminate_semi_and_anti_joins(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Convert SEMI and ANTI joins into equivalent forms that use EXIST instead.\"\"\"\n    if isinstance(expression, exp.Select):\n        for join in expression.args.get(\"joins\") or []:\n            on = join.args.get(\"on\")\n            if on and join.kind in (\"SEMI\", \"ANTI\"):\n                subquery = exp.select(\"1\").from_(join.this).where(on)\n                exists: exp.Exists | exp.Not = exp.Exists(this=subquery)\n                if join.kind == \"ANTI\":\n                    exists = exists.not_(copy=False)\n\n                join.pop()\n                expression.where(exists, copy=False)\n\n    return expression\n\n\ndef eliminate_full_outer_join(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Converts a query with a FULL OUTER join to a union of identical queries that\n    use LEFT/RIGHT OUTER joins instead. This transformation currently only works\n    for queries that have a single FULL OUTER join.\n    \"\"\"\n    if isinstance(expression, exp.Select):\n        full_outer_joins = [\n            (index, join)\n            for index, join in enumerate(expression.args.get(\"joins\") or [])\n            if join.side == \"FULL\"\n        ]\n\n        if len(full_outer_joins) == 1:\n            expression_copy = expression.copy()\n            expression.set(\"limit\", None)\n            index, full_outer_join = full_outer_joins[0]\n\n            tables = (expression.args[\"from_\"].alias_or_name, full_outer_join.alias_or_name)\n            join_conditions = full_outer_join.args.get(\"on\") or exp.and_(\n                *[\n                    exp.column(col, tables[0]).eq(exp.column(col, tables[1]))\n                    for col in full_outer_join.args.get(\"using\")\n                ]\n            )\n\n            full_outer_join.set(\"side\", \"left\")\n            anti_join_clause = (\n                exp.select(\"1\").from_(expression.args[\"from_\"]).where(join_conditions)\n            )\n            expression_copy.args[\"joins\"][index].set(\"side\", \"right\")\n            expression_copy = expression_copy.where(exp.Exists(this=anti_join_clause).not_())\n            expression_copy.set(\"with_\", None)  # remove CTEs from RIGHT side\n            expression.set(\"order\", None)  # remove order by from LEFT side\n\n            return exp.union(expression, expression_copy, copy=False, distinct=False)\n\n    return expression\n\n\ndef move_ctes_to_top_level(expression: E) -> E:\n    \"\"\"\n    Some dialects (e.g. Hive, T-SQL, Spark prior to version 3) only allow CTEs to be\n    defined at the top-level, so for example queries like:\n\n        SELECT * FROM (WITH t(c) AS (SELECT 1) SELECT * FROM t) AS subq\n\n    are invalid in those dialects. This transformation can be used to ensure all CTEs are\n    moved to the top level so that the final SQL code is valid from a syntax standpoint.\n\n    TODO: handle name clashes whilst moving CTEs (it can get quite tricky & costly).\n    \"\"\"\n    top_level_with = expression.args.get(\"with_\")\n    for inner_with in expression.find_all(exp.With):\n        if inner_with.parent is expression:\n            continue\n\n        if not top_level_with:\n            top_level_with = inner_with.pop()\n            expression.set(\"with_\", top_level_with)\n        else:\n            if inner_with.recursive:\n                top_level_with.set(\"recursive\", True)\n\n            parent_cte = inner_with.find_ancestor(exp.CTE)\n            inner_with.pop()\n\n            if parent_cte:\n                i = top_level_with.expressions.index(parent_cte)\n                top_level_with.expressions[i:i] = inner_with.expressions\n                top_level_with.set(\"expressions\", top_level_with.expressions)\n            else:\n                top_level_with.set(\n                    \"expressions\", top_level_with.expressions + inner_with.expressions\n                )\n\n    return expression\n\n\ndef ensure_bools(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Converts numeric values used in conditions into explicit boolean expressions.\"\"\"\n    from sqlglot.optimizer.canonicalize import ensure_bools\n\n    def _ensure_bool(node: exp.Expr) -> None:\n        if (\n            node.is_number\n            or (\n                not isinstance(node, exp.SubqueryPredicate)\n                and node.is_type(exp.DType.UNKNOWN, *exp.DataType.NUMERIC_TYPES)\n            )\n            or (isinstance(node, exp.Column) and not node.type)\n        ):\n            node.replace(node.neq(0))\n\n    for node in expression.walk():\n        ensure_bools(node, _ensure_bool)\n\n    return expression\n\n\ndef unqualify_columns(expression: exp.Expr) -> exp.Expr:\n    for column in expression.find_all(exp.Column):\n        # We only wanna pop off the table, db, catalog args\n        for part in column.parts[:-1]:\n            part.pop()\n\n    return expression\n\n\ndef remove_unique_constraints(expression: exp.Expr) -> exp.Expr:\n    assert isinstance(expression, exp.Create)\n    for constraint in expression.find_all(exp.UniqueColumnConstraint):\n        if constraint.parent:\n            constraint.parent.pop()\n\n    return expression\n\n\ndef ctas_with_tmp_tables_to_create_tmp_view(\n    expression: exp.Expr,\n    tmp_storage_provider: t.Callable[[exp.Expr], exp.Expr] = lambda e: e,\n) -> exp.Expr:\n    assert isinstance(expression, exp.Create)\n    properties = expression.args.get(\"properties\")\n    temporary = any(\n        isinstance(prop, exp.TemporaryProperty)\n        for prop in (properties.expressions if properties else [])\n    )\n\n    # CTAS with temp tables map to CREATE TEMPORARY VIEW\n    if expression.kind == \"TABLE\" and temporary:\n        if expression.expression:\n            return exp.Create(\n                kind=\"TEMPORARY VIEW\",\n                this=expression.this,\n                expression=expression.expression,\n            )\n        return tmp_storage_provider(expression)\n\n    return expression\n\n\ndef move_schema_columns_to_partitioned_by(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    In Hive, the PARTITIONED BY property acts as an extension of a table's schema. When the\n    PARTITIONED BY value is an array of column names, they are transformed into a schema.\n    The corresponding columns are removed from the create statement.\n    \"\"\"\n    assert isinstance(expression, exp.Create)\n    has_schema = isinstance(expression.this, exp.Schema)\n    is_partitionable = expression.kind in {\"TABLE\", \"VIEW\"}\n\n    if has_schema and is_partitionable:\n        prop = expression.find(exp.PartitionedByProperty)\n        if prop and prop.this and not isinstance(prop.this, exp.Schema):\n            schema = expression.this\n            columns = {v.name.upper() for v in prop.this.expressions}\n            partitions = [col for col in schema.expressions if col.name.upper() in columns]\n            schema.set(\"expressions\", [e for e in schema.expressions if e not in partitions])\n            prop.replace(exp.PartitionedByProperty(this=exp.Schema(expressions=partitions)))\n            expression.set(\"this\", schema)\n\n    return expression\n\n\ndef move_partitioned_by_to_schema_columns(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Spark 3 supports both \"HIVEFORMAT\" and \"DATASOURCE\" formats for CREATE TABLE.\n\n    Currently, SQLGlot uses the DATASOURCE format for Spark 3.\n    \"\"\"\n    assert isinstance(expression, exp.Create)\n    prop = expression.find(exp.PartitionedByProperty)\n    if (\n        prop\n        and prop.this\n        and isinstance(prop.this, exp.Schema)\n        and all(isinstance(e, exp.ColumnDef) and e.kind for e in prop.this.expressions)\n    ):\n        prop_this = exp.Tuple(\n            expressions=[exp.to_identifier(e.this) for e in prop.this.expressions]\n        )\n        schema = expression.this\n        for e in prop.this.expressions:\n            schema.append(\"expressions\", e)\n        prop.set(\"this\", prop_this)\n\n    return expression\n\n\ndef struct_kv_to_alias(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Converts struct arguments to aliases, e.g. STRUCT(1 AS y).\"\"\"\n    if isinstance(expression, exp.Struct):\n        expression.set(\n            \"expressions\",\n            [\n                exp.alias_(e.expression, e.this) if isinstance(e, exp.PropertyEQ) else e\n                for e in expression.expressions\n            ],\n        )\n\n    return expression\n\n\ndef eliminate_join_marks(expression: exp.Expr) -> exp.Expr:\n    \"\"\"https://docs.oracle.com/cd/B19306_01/server.102/b14200/queries006.htm#sthref3178\n\n    1. You cannot specify the (+) operator in a query block that also contains FROM clause join syntax.\n\n    2. The (+) operator can appear only in the WHERE clause or, in the context of left-correlation (that is, when specifying the TABLE clause) in the FROM clause, and can be applied only to a column of a table or view.\n\n    The (+) operator does not produce an outer join if you specify one table in the outer query and the other table in an inner query.\n\n    You cannot use the (+) operator to outer-join a table to itself, although self joins are valid.\n\n    The (+) operator can be applied only to a column, not to an arbitrary expression. However, an arbitrary expression can contain one or more columns marked with the (+) operator.\n\n    A WHERE condition containing the (+) operator cannot be combined with another condition using the OR logical operator.\n\n    A WHERE condition cannot use the IN comparison condition to compare a column marked with the (+) operator with an expression.\n\n    A WHERE condition cannot compare any column marked with the (+) operator with a subquery.\n\n    -- example with WHERE\n    SELECT d.department_name, sum(e.salary) as total_salary\n    FROM departments d, employees e\n    WHERE e.department_id(+) = d.department_id\n    group by department_name\n\n    -- example of left correlation in select\n    SELECT d.department_name, (\n        SELECT SUM(e.salary)\n            FROM employees e\n            WHERE e.department_id(+) = d.department_id) AS total_salary\n    FROM departments d;\n\n    -- example of left correlation in from\n    SELECT d.department_name, t.total_salary\n    FROM departments d, (\n            SELECT SUM(e.salary) AS total_salary\n            FROM employees e\n            WHERE e.department_id(+) = d.department_id\n        ) t\n    \"\"\"\n\n    from sqlglot.optimizer.scope import traverse_scope\n    from sqlglot.optimizer.normalize import normalize, normalized\n    from collections import defaultdict\n\n    # we go in reverse to check the main query for left correlation\n    for scope in reversed(traverse_scope(expression)):\n        query = scope.expression\n\n        where = query.args.get(\"where\")\n        joins = query.args.get(\"joins\", [])\n\n        if not where or not any(c.args.get(\"join_mark\") for c in where.find_all(exp.Column)):\n            continue\n\n        # knockout: we do not support left correlation (see point 2)\n        assert not scope.is_correlated_subquery, \"Correlated queries are not supported\"\n\n        # make sure we have AND of ORs to have clear join terms\n        where = normalize(where.this)\n        assert normalized(where), \"Cannot normalize JOIN predicates\"\n\n        joins_ons = defaultdict(list)  # dict of {name: list of join AND conditions}\n        for cond in [where] if not isinstance(where, exp.And) else where.flatten():\n            join_cols = [col for col in cond.find_all(exp.Column) if col.args.get(\"join_mark\")]\n\n            left_join_table = set(col.table for col in join_cols)\n            if not left_join_table:\n                continue\n\n            assert not (len(left_join_table) > 1), (\n                \"Cannot combine JOIN predicates from different tables\"\n            )\n\n            for col in join_cols:\n                col.set(\"join_mark\", False)\n\n            joins_ons[left_join_table.pop()].append(cond)\n\n        old_joins = {join.alias_or_name: join for join in joins}\n        new_joins = {}\n        query_from = query.args[\"from_\"]\n\n        for table, predicates in joins_ons.items():\n            join_what = old_joins.get(table, query_from).this.copy()\n            new_joins[join_what.alias_or_name] = exp.Join(\n                this=join_what, on=exp.and_(*predicates), kind=\"LEFT\"\n            )\n\n            for p in predicates:\n                while isinstance(p.parent, exp.Paren):\n                    p.parent.replace(p)\n\n                parent = p.parent\n                p.pop()\n                if isinstance(parent, exp.Binary):\n                    left = parent.args.get(\"this\")\n                    parent.replace(parent.right if left is None else left)\n                elif isinstance(parent, exp.Where):\n                    parent.pop()\n\n        if query_from.alias_or_name in new_joins:\n            only_old_joins = old_joins.keys() - new_joins.keys()\n            assert len(only_old_joins) >= 1, (\n                \"Cannot determine which table to use in the new FROM clause\"\n            )\n\n            new_from_name = list(only_old_joins)[0]\n            query.set(\"from_\", exp.From(this=old_joins[new_from_name].this))\n\n        if new_joins:\n            for n, j in old_joins.items():  # preserve any other joins\n                if n not in new_joins and n != query.args[\"from_\"].name:\n                    if not j.kind:\n                        j.set(\"kind\", \"CROSS\")\n                    new_joins[n] = j\n            query.set(\"joins\", list(new_joins.values()))\n\n    return expression\n\n\ndef any_to_exists(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Transform ANY operator to Spark's EXISTS\n\n    For example,\n        - Postgres: SELECT * FROM tbl WHERE 5 > ANY(tbl.col)\n        - Spark: SELECT * FROM tbl WHERE EXISTS(tbl.col, x -> x < 5)\n\n    Both ANY and EXISTS accept queries but currently only array expressions are supported for this\n    transformation\n    \"\"\"\n    if isinstance(expression, exp.Select):\n        for any_expr in expression.find_all(exp.Any):\n            this = any_expr.this\n            if isinstance(this, exp.Query) or isinstance(any_expr.parent, (exp.Like, exp.ILike)):\n                continue\n\n            binop = any_expr.parent\n            if isinstance(binop, exp.Binary):\n                lambda_arg = exp.to_identifier(\"x\")\n                any_expr.replace(lambda_arg)\n                lambda_expr = exp.Lambda(this=binop.copy(), expressions=[lambda_arg])\n                binop.replace(exp.Exists(this=this.unnest(), expression=lambda_expr))\n\n    return expression\n\n\ndef eliminate_window_clause(expression: exp.Expr) -> exp.Expr:\n    \"\"\"Eliminates the `WINDOW` query clause by inling each named window.\"\"\"\n    if isinstance(expression, exp.Select) and expression.args.get(\"windows\"):\n        from sqlglot.optimizer.scope import find_all_in_scope\n\n        windows = expression.args[\"windows\"]\n        expression.set(\"windows\", None)\n\n        window_expression: t.Dict[str, exp.Expr] = {}\n\n        def _inline_inherited_window(window: exp.Expr) -> None:\n            inherited_window = window_expression.get(window.alias.lower())\n            if not inherited_window:\n                return\n\n            window.set(\"alias\", None)\n            for key in (\"partition_by\", \"order\", \"spec\"):\n                arg = inherited_window.args.get(key)\n                if arg:\n                    window.set(key, arg.copy())\n\n        for window in windows:\n            _inline_inherited_window(window)\n            window_expression[window.name.lower()] = window\n\n        for window in find_all_in_scope(expression, exp.Window):\n            _inline_inherited_window(window)\n\n    return expression\n\n\ndef inherit_struct_field_names(expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Inherit field names from the first struct in an array.\n\n    BigQuery supports implicitly inheriting names from the first STRUCT in an array:\n\n    Example:\n        ARRAY[\n          STRUCT('Alice' AS name, 85 AS score),  -- defines names\n          STRUCT('Bob', 92),                     -- inherits names\n          STRUCT('Diana', 95)                    -- inherits names\n        ]\n\n    This transformation makes the field names explicit on all structs by adding\n    PropertyEQ nodes, in order to facilitate transpilation to other dialects.\n\n    Args:\n        expression: The expression tree to transform\n\n    Returns:\n        The modified expression with field names inherited in all structs\n    \"\"\"\n    if (\n        isinstance(expression, exp.Array)\n        and expression.args.get(\"struct_name_inheritance\")\n        and isinstance(first_item := seq_get(expression.expressions, 0), exp.Struct)\n        and all(isinstance(fld, exp.PropertyEQ) for fld in first_item.expressions)\n    ):\n        field_names = [fld.this for fld in first_item.expressions]\n\n        # Apply field names to subsequent structs that don't have them\n        for struct in expression.expressions[1:]:\n            if not isinstance(struct, exp.Struct) or len(struct.expressions) != len(field_names):\n                continue\n\n            # Convert unnamed expressions to PropertyEQ with inherited names\n            new_expressions = []\n            for i, expr in enumerate(struct.expressions):\n                if not isinstance(expr, exp.PropertyEQ):\n                    # Create PropertyEQ: field_name := value, preserving the type from the inner expression\n                    property_eq = exp.PropertyEQ(\n                        this=field_names[i].copy(),\n                        expression=expr,\n                    )\n                    property_eq.type = expr.type\n                    new_expressions.append(property_eq)\n                else:\n                    new_expressions.append(expr)\n\n            struct.set(\"expressions\", new_expressions)\n\n    return expression\n"
  },
  {
    "path": "sqlglot/trie.py",
    "content": "import typing as t\nfrom enum import Enum, auto\nfrom collections.abc import Sequence, Iterable\n\nkey = Sequence[t.Hashable]\n\n\nclass TrieResult(Enum):\n    FAILED = auto()\n    PREFIX = auto()\n    EXISTS = auto()\n\n\ndef new_trie(keywords: Iterable[key], trie: t.Optional[dict] = None) -> dict:\n    \"\"\"\n    Creates a new trie out of a collection of keywords.\n\n    The trie is represented as a sequence of nested dictionaries keyed by either single\n    character strings, or by 0, which is used to designate that a keyword is in the trie.\n\n    Example:\n        >>> new_trie([\"bla\", \"foo\", \"blab\"])\n        {'b': {'l': {'a': {0: True, 'b': {0: True}}}}, 'f': {'o': {'o': {0: True}}}}\n\n    Args:\n        keywords: the keywords to create the trie from.\n        trie: a trie to mutate instead of creating a new one\n\n    Returns:\n        The trie corresponding to `keywords`.\n    \"\"\"\n    trie = {} if trie is None else trie\n\n    for key in keywords:\n        current = trie\n        for char in key:\n            current = current.setdefault(char, {})\n\n        current[0] = True\n\n    return trie\n\n\ndef in_trie(trie: t.Dict, key: key) -> t.Tuple[TrieResult, t.Dict]:\n    \"\"\"\n    Checks whether a key is in a trie.\n\n    Examples:\n        >>> in_trie(new_trie([\"cat\"]), \"bob\")\n        (<TrieResult.FAILED: 1>, {'c': {'a': {'t': {0: True}}}})\n\n        >>> in_trie(new_trie([\"cat\"]), \"ca\")\n        (<TrieResult.PREFIX: 2>, {'t': {0: True}})\n\n        >>> in_trie(new_trie([\"cat\"]), \"cat\")\n        (<TrieResult.EXISTS: 3>, {0: True})\n\n    Args:\n        trie: The trie to be searched.\n        key: The target key.\n\n    Returns:\n        A pair `(value, subtrie)`, where `subtrie` is the sub-trie we get at the point\n        where the search stops, and `value` is a TrieResult value that can be one of:\n\n        - TrieResult.FAILED: the search was unsuccessful\n        - TrieResult.PREFIX: `value` is a prefix of a keyword in `trie`\n        - TrieResult.EXISTS: `key` exists in `trie`\n    \"\"\"\n    if not key:\n        return (TrieResult.FAILED, trie)\n\n    current = trie\n    for char in key:\n        if char not in current:\n            return (TrieResult.FAILED, current)\n        current = current[char]\n\n    if 0 in current:\n        return (TrieResult.EXISTS, current)\n\n    return (TrieResult.PREFIX, current)\n"
  },
  {
    "path": "sqlglot/typing/__init__.py",
    "content": "import typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.helper import subclasses\nfrom builtins import type as Type\n\nExprMetadataType = t.Dict[Type[exp.Expr], t.Dict[str, t.Any]]\n\nTIMESTAMP_EXPRESSIONS = {\n    exp.CurrentTimestamp,\n    exp.StrToTime,\n    exp.TimeStrToTime,\n    exp.TimestampAdd,\n    exp.TimestampSub,\n    exp.UnixToTime,\n}\n\nEXPRESSION_METADATA: ExprMetadataType = {\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_binary(e)}\n        for expr_type in subclasses(exp.__name__, exp.Binary)\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_unary(e)}\n        for expr_type in subclasses(exp.__name__, (exp.Unary, exp.Alias))\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BIGINT}\n        for expr_type in {\n            exp.ApproxDistinct,\n            exp.ArraySize,\n            exp.CountIf,\n            exp.Int64,\n            exp.UnixSeconds,\n            exp.UnixMicros,\n            exp.UnixMillis,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BINARY}\n        for expr_type in {\n            exp.FromBase32,\n            exp.FromBase64,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BOOLEAN}\n        for expr_type in {\n            exp.All,\n            exp.Any,\n            exp.ArrayContains,\n            exp.Between,\n            exp.Boolean,\n            exp.Contains,\n            exp.EndsWith,\n            exp.Exists,\n            exp.In,\n            exp.IsInf,\n            exp.IsNan,\n            exp.LogicalAnd,\n            exp.LogicalOr,\n            exp.RegexpLike,\n            exp.StartsWith,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DATE}\n        for expr_type in {\n            exp.CurrentDate,\n            exp.Date,\n            exp.DateFromParts,\n            exp.DateStrToDate,\n            exp.DiToDate,\n            exp.LastDay,\n            exp.StrToDate,\n            exp.TimeStrToDate,\n            exp.TsOrDsToDate,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DATETIME}\n        for expr_type in {\n            exp.CurrentDatetime,\n            exp.Datetime,\n            exp.DatetimeAdd,\n            exp.DatetimeSub,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DOUBLE}\n        for expr_type in {\n            exp.Asin,\n            exp.Asinh,\n            exp.Acos,\n            exp.Acosh,\n            exp.ApproxQuantile,\n            exp.Atan,\n            exp.Atanh,\n            exp.Avg,\n            exp.Cbrt,\n            exp.Cos,\n            exp.Cosh,\n            exp.Cot,\n            exp.Degrees,\n            exp.Exp,\n            exp.Kurtosis,\n            exp.Ln,\n            exp.Log,\n            exp.Pi,\n            exp.Pow,\n            exp.Quantile,\n            exp.Radians,\n            exp.Round,\n            exp.SafeDivide,\n            exp.Sin,\n            exp.Sinh,\n            exp.Sqrt,\n            exp.Stddev,\n            exp.StddevPop,\n            exp.StddevSamp,\n            exp.Rand,\n            exp.Tan,\n            exp.Tanh,\n            exp.ToDouble,\n            exp.Variance,\n            exp.VariancePop,\n            exp.Skewness,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.INT}\n        for expr_type in {\n            exp.Ascii,\n            exp.BitLength,\n            exp.Ceil,\n            exp.DatetimeDiff,\n            exp.DayOfMonth,\n            exp.DayOfWeek,\n            exp.DayOfYear,\n            exp.Getbit,\n            exp.Hour,\n            exp.TimestampDiff,\n            exp.TimeDiff,\n            exp.Unicode,\n            exp.DateToDi,\n            exp.Levenshtein,\n            exp.Length,\n            exp.Sign,\n            exp.StrPosition,\n            exp.TsOrDiToDi,\n            exp.Quarter,\n            exp.UnixDate,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.INTERVAL}\n        for expr_type in {\n            exp.Interval,\n            exp.JustifyDays,\n            exp.JustifyHours,\n            exp.JustifyInterval,\n            exp.MakeInterval,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.JSON}\n        for expr_type in {\n            exp.ParseJSON,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.TIME}\n        for expr_type in {\n            exp.CurrentTime,\n            exp.Localtime,\n            exp.Time,\n            exp.TimeAdd,\n            exp.TimeSub,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.TIMESTAMPLTZ}\n        for expr_type in {\n            exp.TimestampLtzFromParts,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.TIMESTAMPTZ}\n        for expr_type in {\n            exp.CurrentTimestampLTZ,\n            exp.TimestampTzFromParts,\n        }\n    },\n    **{expr_type: {\"returns\": exp.DType.TIMESTAMP} for expr_type in TIMESTAMP_EXPRESSIONS},\n    **{\n        expr_type: {\"returns\": exp.DType.TINYINT}\n        for expr_type in {\n            exp.Day,\n            exp.DayOfWeekIso,\n            exp.Month,\n            exp.Week,\n            exp.WeekOfYear,\n            exp.Year,\n            exp.YearOfWeek,\n            exp.YearOfWeekIso,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.VARCHAR}\n        for expr_type in {\n            exp.ArrayToString,\n            exp.Concat,\n            exp.ConcatWs,\n            exp.Chr,\n            exp.CurrentCatalog,\n            exp.CurrentSchema,\n            exp.CurrentVersion,\n            exp.CurrentUser,\n            exp.Dayname,\n            exp.DateToDateStr,\n            exp.DPipe,\n            exp.GroupConcat,\n            exp.Initcap,\n            exp.Lower,\n            exp.MD5,\n            exp.Monthname,\n            exp.SHA,\n            exp.SHA2,\n            exp.Substring,\n            exp.String,\n            exp.TimeToStr,\n            exp.TimeToTimeStr,\n            exp.Trim,\n            exp.ToBase32,\n            exp.ToBase64,\n            exp.Translate,\n            exp.TsOrDsToDateStr,\n            exp.UnixToStr,\n            exp.UnixToTimeStr,\n            exp.Upper,\n            exp.RawString,\n            exp.SessionUser,\n            exp.Space,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\")}\n        for expr_type in {\n            exp.Abs,\n            exp.AnyValue,\n            exp.ArrayConcatAgg,\n            exp.ArrayReverse,\n            exp.ArraySlice,\n            exp.Filter,\n            exp.HavingMax,\n            exp.LastValue,\n            exp.Limit,\n            exp.Order,\n            exp.SortArray,\n            exp.Window,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\", \"expressions\")}\n        for expr_type in {\n            exp.ArrayConcat,\n            exp.Coalesce,\n            exp.Greatest,\n            exp.Least,\n            exp.Max,\n            exp.Min,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_array_element(e)}\n        for expr_type in {\n            exp.ArrayFirst,\n            exp.ArrayLast,\n        }\n    },\n    exp.Anonymous: {\"annotator\": lambda self, e: self._set_type(e, self.schema.get_udf_type(e))},\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_timeunit(e)}\n        for expr_type in {\n            exp.DateAdd,\n            exp.DateSub,\n            exp.DateTrunc,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._set_type(e, e.args[\"to\"])}\n        for expr_type in {\n            exp.Cast,\n            exp.TryCast,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_map(e)}\n        for expr_type in {\n            exp.Map,\n            exp.VarMap,\n        }\n    },\n    exp.Array: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"expressions\", array=True)},\n    exp.ArrayAgg: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\", array=True)},\n    exp.Bracket: {\"annotator\": lambda self, e: self._annotate_bracket(e)},\n    exp.Case: {\n        \"annotator\": lambda self, e: self._annotate_by_args(\n            e, *[if_expr.args[\"true\"] for if_expr in e.args[\"ifs\"]], \"default\"\n        )\n    },\n    exp.Count: {\n        \"annotator\": lambda self, e: self._set_type(\n            e, exp.DType.BIGINT if e.args.get(\"big_int\") else exp.DType.INT\n        )\n    },\n    exp.DateDiff: {\n        \"annotator\": lambda self, e: self._set_type(\n            e, exp.DType.BIGINT if e.args.get(\"big_int\") else exp.DType.INT\n        )\n    },\n    exp.DataType: {\"annotator\": lambda self, e: self._set_type(e, e.copy())},\n    exp.Div: {\"annotator\": lambda self, e: self._annotate_div(e)},\n    exp.Distinct: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"expressions\")},\n    exp.Dot: {\"annotator\": lambda self, e: self._annotate_dot(e)},\n    exp.Explode: {\"annotator\": lambda self, e: self._annotate_explode(e)},\n    exp.Extract: {\"annotator\": lambda self, e: self._annotate_extract(e)},\n    exp.HexString: {\n        \"annotator\": lambda self, e: self._set_type(\n            e,\n            exp.DType.BIGINT if e.args.get(\"is_integer\") else exp.DType.BINARY,\n        )\n    },\n    exp.GenerateSeries: {\n        \"annotator\": lambda self, e: self._annotate_by_args(e, \"start\", \"end\", \"step\", array=True)\n    },\n    exp.GenerateDateArray: {\n        \"annotator\": lambda self, e: self._set_type(e, exp.DataType.build(\"ARRAY<DATE>\"))\n    },\n    exp.GenerateTimestampArray: {\n        \"annotator\": lambda self, e: self._set_type(e, exp.DataType.build(\"ARRAY<TIMESTAMP>\"))\n    },\n    exp.If: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"true\", \"false\")},\n    exp.Literal: {\"annotator\": lambda self, e: self._annotate_literal(e)},\n    exp.Null: {\"returns\": exp.DType.NULL},\n    exp.Nullif: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\", \"expression\")},\n    exp.PropertyEQ: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"expression\")},\n    exp.Struct: {\"annotator\": lambda self, e: self._annotate_struct(e)},\n    exp.Sum: {\n        \"annotator\": lambda self, e: self._annotate_by_args(e, \"this\", \"expressions\", promote=True)\n    },\n    exp.Timestamp: {\n        \"annotator\": lambda self, e: self._set_type(\n            e,\n            exp.DType.TIMESTAMPTZ if e.args.get(\"with_tz\") else exp.DType.TIMESTAMP,\n        )\n    },\n    exp.ToMap: {\"annotator\": lambda self, e: self._annotate_to_map(e)},\n    exp.Unnest: {\"annotator\": lambda self, e: self._annotate_unnest(e)},\n    exp.Subquery: {\"annotator\": lambda self, e: self._annotate_subquery(e)},\n}\n"
  },
  {
    "path": "sqlglot/typing/bigquery.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.typing import EXPRESSION_METADATA, TIMESTAMP_EXPRESSIONS\n\nif t.TYPE_CHECKING:\n    from sqlglot.optimizer.annotate_types import TypeAnnotator\n\n\ndef _annotate_math_functions(self: TypeAnnotator, expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    Many BigQuery math functions such as CEIL, FLOOR etc follow this return type convention:\n    +---------+---------+---------+------------+---------+\n    |  INPUT  | INT64   | NUMERIC | BIGNUMERIC | FLOAT64 |\n    +---------+---------+---------+------------+---------+\n    |  OUTPUT | FLOAT64 | NUMERIC | BIGNUMERIC | FLOAT64 |\n    +---------+---------+---------+------------+---------+\n    \"\"\"\n    this: exp.Expr = expression.this\n\n    self._set_type(\n        expression,\n        exp.DType.DOUBLE if this.is_type(*exp.DataType.INTEGER_TYPES) else this.type,\n    )\n    return expression\n\n\ndef _annotate_safe_divide(self: TypeAnnotator, expression: exp.SafeDivide) -> exp.Expr:\n    \"\"\"\n    +------------+------------+------------+-------------+---------+\n    | INPUT      | INT64      | NUMERIC    | BIGNUMERIC  | FLOAT64 |\n    +------------+------------+------------+-------------+---------+\n    | INT64      | FLOAT64    | NUMERIC    | BIGNUMERIC  | FLOAT64 |\n    | NUMERIC    | NUMERIC    | NUMERIC    | BIGNUMERIC  | FLOAT64 |\n    | BIGNUMERIC | BIGNUMERIC | BIGNUMERIC | BIGNUMERIC  | FLOAT64 |\n    | FLOAT64    | FLOAT64    | FLOAT64    | FLOAT64     | FLOAT64 |\n    +------------+------------+------------+-------------+---------+\n    \"\"\"\n    if expression.this.is_type(*exp.DataType.INTEGER_TYPES) and expression.expression.is_type(\n        *exp.DataType.INTEGER_TYPES\n    ):\n        return self._set_type(expression, exp.DType.DOUBLE)\n\n    return _annotate_by_args_with_coerce(self, expression)\n\n\ndef _annotate_by_args_with_coerce(self: TypeAnnotator, expression: exp.Expr) -> exp.Expr:\n    \"\"\"\n    +------------+------------+------------+-------------+---------+\n    | INPUT      | INT64      | NUMERIC    | BIGNUMERIC  | FLOAT64 |\n    +------------+------------+------------+-------------+---------+\n    | INT64      | INT64      | NUMERIC    | BIGNUMERIC  | FLOAT64 |\n    | NUMERIC    | NUMERIC    | NUMERIC    | BIGNUMERIC  | FLOAT64 |\n    | BIGNUMERIC | BIGNUMERIC | BIGNUMERIC | BIGNUMERIC  | FLOAT64 |\n    | FLOAT64    | FLOAT64    | FLOAT64    | FLOAT64     | FLOAT64 |\n    +------------+------------+------------+-------------+---------+\n    \"\"\"\n    self._set_type(expression, self._maybe_coerce(expression.this.type, expression.expression.type))\n    return expression\n\n\ndef _annotate_by_args_approx_top(self: TypeAnnotator, expression: exp.ApproxTopK) -> exp.ApproxTopK:\n    struct_type = exp.DataType(\n        this=exp.DType.STRUCT,\n        expressions=[expression.this.type, exp.DataType(this=exp.DType.BIGINT)],\n        nested=True,\n    )\n    self._set_type(\n        expression,\n        exp.DataType(this=exp.DType.ARRAY, expressions=[struct_type], nested=True),\n    )\n\n    return expression\n\n\ndef _annotate_concat(self: TypeAnnotator, expression: exp.Concat) -> exp.Concat:\n    annotated = self._annotate_by_args(expression, \"expressions\")\n\n    # Args must be BYTES or types that can be cast to STRING, return type is either BYTES or STRING\n    # https://cloud.google.com/bigquery/docs/reference/standard-sql/string_functions#concat\n    if not annotated.is_type(exp.DType.BINARY, exp.DType.UNKNOWN):\n        self._set_type(annotated, exp.DType.VARCHAR)\n\n    return annotated\n\n\ndef _annotate_array(self: TypeAnnotator, expression: exp.Array) -> exp.Array:\n    array_args = expression.expressions\n\n    # BigQuery behaves as follows:\n    #\n    # SELECT t, TYPEOF(t) FROM (SELECT 'foo') AS t            -- foo, STRUCT<STRING>\n    # SELECT ARRAY(SELECT 'foo'), TYPEOF(ARRAY(SELECT 'foo')) -- foo, ARRAY<STRING>\n    # ARRAY(SELECT ... UNION ALL SELECT ...) -- ARRAY<type from coerced projections>\n    if len(array_args) == 1:\n        unnested = array_args[0].unnest()\n        projection_type: t.Optional[exp.DataType | exp.DType] = None\n\n        # Handle ARRAY(SELECT ...) - single SELECT query\n        if isinstance(unnested, exp.Select):\n            if (\n                (query_type := unnested.meta.get(\"query_type\")) is not None\n                and query_type.is_type(exp.DType.STRUCT)\n                and len(query_type.expressions) == 1\n                and isinstance(col_def := query_type.expressions[0], exp.ColumnDef)\n                and (col_type := col_def.kind) is not None\n                and not col_type.is_type(exp.DType.UNKNOWN)\n            ):\n                projection_type = col_type\n\n        # Handle ARRAY(SELECT ... UNION ALL SELECT ...) - set operations\n        elif isinstance(unnested, exp.SetOperation):\n            # Get all column types for the SetOperation\n            col_types = self._get_setop_column_types(unnested)\n            # For ARRAY constructor, there should only be one projection\n            # https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/array_functions#array\n            if col_types and unnested.left.selects:\n                first_col_name = unnested.left.selects[0].alias_or_name\n                projection_type = col_types.get(first_col_name)\n\n        # If we successfully determine a projection type and it's not UNKNOWN, wrap it in ARRAY\n        if projection_type and not (\n            (\n                isinstance(projection_type, exp.DataType)\n                and projection_type.is_type(exp.DType.UNKNOWN)\n            )\n            or projection_type == exp.DType.UNKNOWN\n        ):\n            element_type = (\n                projection_type.copy()\n                if isinstance(projection_type, exp.DataType)\n                else exp.DataType(this=projection_type)\n            )\n            array_type = exp.DataType(\n                this=exp.DType.ARRAY,\n                expressions=[element_type],\n                nested=True,\n            )\n            return self._set_type(expression, array_type)\n\n    return self._annotate_by_args(expression, \"expressions\", array=True)\n\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        expr_type: {\"annotator\": lambda self, e: _annotate_math_functions(self, e)}\n        for expr_type in {\n            exp.Avg,\n            exp.Ceil,\n            exp.Exp,\n            exp.Floor,\n            exp.Ln,\n            exp.Log,\n            exp.Round,\n            exp.Sqrt,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\")}\n        for expr_type in {\n            exp.ArgMax,\n            exp.ArgMin,\n            exp.DateAdd,\n            exp.DateTrunc,\n            exp.DatetimeTrunc,\n            exp.FirstValue,\n            exp.GroupConcat,\n            exp.IgnoreNulls,\n            exp.JSONExtract,\n            exp.Lead,\n            exp.Left,\n            exp.Lower,\n            exp.NetFunc,\n            exp.NthValue,\n            exp.Pad,\n            exp.PercentileDisc,\n            exp.RegexpExtract,\n            exp.RegexpReplace,\n            exp.Repeat,\n            exp.Replace,\n            exp.RespectNulls,\n            exp.Reverse,\n            exp.Right,\n            exp.SafeFunc,\n            exp.SafeNegate,\n            exp.Sign,\n            exp.Substring,\n            exp.TimestampTrunc,\n            exp.Translate,\n            exp.Trim,\n            exp.Upper,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BIGINT}\n        for expr_type in {\n            exp.BitwiseAndAgg,\n            exp.BitwiseCount,\n            exp.BitwiseOrAgg,\n            exp.BitwiseXorAgg,\n            exp.ByteLength,\n            exp.DenseRank,\n            exp.FarmFingerprint,\n            exp.Grouping,\n            exp.LaxInt64,\n            exp.Length,\n            exp.Ntile,\n            exp.Rank,\n            exp.RangeBucket,\n            exp.RegexpInstr,\n            exp.RowNumber,\n            exp.UnixDate,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BINARY}\n        for expr_type in {\n            exp.ByteString,\n            exp.CodePointsToBytes,\n            exp.MD5Digest,\n            exp.SHA,\n            exp.SHA2,\n            exp.SHA1Digest,\n            exp.SHA2Digest,\n            exp.Unhex,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BOOLEAN}\n        for expr_type in {\n            exp.JSONBool,\n            exp.LaxBool,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DATETIME}\n        for expr_type in {\n            exp.ParseDatetime,\n            exp.TimestampFromParts,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DOUBLE}\n        for expr_type in {\n            exp.Atan2,\n            exp.Corr,\n            exp.CosineDistance,\n            exp.Coth,\n            exp.CovarPop,\n            exp.CovarSamp,\n            exp.Csc,\n            exp.Csch,\n            exp.CumeDist,\n            exp.EuclideanDistance,\n            exp.Float64,\n            exp.LaxFloat64,\n            exp.PercentRank,\n            exp.Sec,\n            exp.Sech,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.JSON}\n        for expr_type in {\n            exp.JSONArray,\n            exp.JSONArrayAppend,\n            exp.JSONArrayInsert,\n            exp.JSONObject,\n            exp.JSONRemove,\n            exp.JSONSet,\n            exp.JSONStripNulls,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.TIME}\n        for expr_type in {\n            exp.ParseTime,\n            exp.TimeFromParts,\n            exp.TimeTrunc,\n            exp.TsOrDsToTime,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.VARCHAR}\n        for expr_type in {\n            exp.CodePointsToString,\n            exp.Format,\n            exp.Host,\n            exp.JSONExtractScalar,\n            exp.JSONType,\n            exp.LaxString,\n            exp.LowerHex,\n            exp.Normalize,\n            exp.RegDomain,\n            exp.SafeConvertBytesToString,\n            exp.Soundex,\n            exp.Uuid,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: _annotate_by_args_with_coerce(self, e)}\n        for expr_type in {\n            exp.PercentileCont,\n            exp.SafeAdd,\n            exp.SafeDivide,\n            exp.SafeMultiply,\n            exp.SafeSubtract,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\", array=True)}\n        for expr_type in {\n            exp.ApproxQuantiles,\n            exp.JSONExtractArray,\n            exp.RegexpExtractAll,\n            exp.Split,\n        }\n    },\n    **{expr_type: {\"returns\": exp.DType.TIMESTAMPTZ} for expr_type in TIMESTAMP_EXPRESSIONS},\n    exp.ApproxTopK: {\"annotator\": lambda self, e: _annotate_by_args_approx_top(self, e)},\n    exp.ApproxTopSum: {\"annotator\": lambda self, e: _annotate_by_args_approx_top(self, e)},\n    exp.Array: {\"annotator\": _annotate_array},\n    exp.Concat: {\"annotator\": _annotate_concat},\n    exp.DateFromUnixDate: {\"returns\": exp.DType.DATE},\n    exp.GenerateTimestampArray: {\n        \"annotator\": lambda self, e: self._set_type(\n            e, exp.DataType.build(\"ARRAY<TIMESTAMP>\", dialect=\"bigquery\")\n        )\n    },\n    exp.JSONFormat: {\n        \"annotator\": lambda self, e: self._set_type(\n            e, exp.DType.JSON if e.args.get(\"to_json\") else exp.DType.VARCHAR\n        )\n    },\n    exp.JSONKeysAtDepth: {\n        \"annotator\": lambda self, e: self._set_type(\n            e, exp.DataType.build(\"ARRAY<VARCHAR>\", dialect=\"bigquery\")\n        )\n    },\n    exp.JSONValueArray: {\n        \"annotator\": lambda self, e: self._set_type(\n            e, exp.DataType.build(\"ARRAY<VARCHAR>\", dialect=\"bigquery\")\n        )\n    },\n    exp.Lag: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\", \"default\")},\n    exp.ParseBignumeric: {\"returns\": exp.DType.BIGDECIMAL},\n    exp.ParseNumeric: {\"returns\": exp.DType.DECIMAL},\n    exp.SafeDivide: {\"annotator\": lambda self, e: _annotate_safe_divide(self, e)},\n    exp.ToCodePoints: {\n        \"annotator\": lambda self, e: self._set_type(\n            e, exp.DataType.build(\"ARRAY<BIGINT>\", dialect=\"bigquery\")\n        )\n    },\n}\n"
  },
  {
    "path": "sqlglot/typing/clickhouse.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        expr_type: {\"returns\": exp.DType.UBIGINT}\n        for expr_type in {\n            exp.CountIf,\n        }\n    },\n}\n"
  },
  {
    "path": "sqlglot/typing/duckdb.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        expr_type: {\"returns\": exp.DType.BIGINT}\n        for expr_type in {\n            exp.BitLength,\n            exp.DateDiff,\n            exp.Day,\n            exp.DayOfMonth,\n            exp.DayOfWeek,\n            exp.DayOfWeekIso,\n            exp.DayOfYear,\n            exp.Extract,\n            exp.Hour,\n            exp.Length,\n            exp.Minute,\n            exp.Month,\n            exp.Quarter,\n            exp.Second,\n            exp.Week,\n            exp.Year,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.INT128}\n        for expr_type in {\n            exp.CountIf,\n            exp.Factorial,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DOUBLE}\n        for expr_type in {\n            exp.Atan2,\n            exp.JarowinklerSimilarity,\n            exp.TimeToUnix,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.VARCHAR}\n        for expr_type in {\n            exp.Format,\n            exp.Reverse,\n        }\n    },\n    exp.DateBin: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"expression\")},\n    exp.Localtimestamp: {\"returns\": exp.DType.TIMESTAMP},\n    exp.ToDays: {\"returns\": exp.DType.INTERVAL},\n    exp.TimeFromParts: {\"returns\": exp.DType.TIME},\n}\n"
  },
  {
    "path": "sqlglot/typing/hive.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        expr_type: {\"returns\": exp.DType.BINARY}\n        for expr_type in {\n            exp.Encode,\n            exp.Unhex,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DOUBLE}\n        for expr_type in {\n            exp.Corr,\n            exp.MonthsBetween,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.VARCHAR}\n        for expr_type in {\n            exp.AddMonths,\n            exp.CurrentDatabase,\n            exp.CurrentUser,\n            exp.CurrentSchema,\n            exp.Hex,\n            exp.NextDay,\n            exp.Repeat,\n            exp.Replace,\n            exp.Soundex,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BIGINT}\n        for expr_type in {\n            exp.StrToUnix,\n            exp.Factorial,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.INT}\n        for expr_type in {\n            exp.Month,\n            exp.Second,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\")}\n        for expr_type in {\n            exp.ArrayDistinct,\n            exp.ArrayExcept,\n            exp.Reverse,\n        }\n    },\n    exp.ApproxQuantile: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"quantile\")},\n    exp.ArrayIntersect: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"expressions\")},\n    exp.Coalesce: {\n        \"annotator\": lambda self, e: self._annotate_by_args(e, \"this\", \"expressions\", promote=True)\n    },\n    exp.If: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"true\", \"false\", promote=True)},\n    exp.Quantile: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"quantile\")},\n    exp.RegexpSplit: {\"returns\": exp.DataType.build(\"ARRAY<STRING>\")},\n}\n"
  },
  {
    "path": "sqlglot/typing/mysql.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        expr_type: {\"returns\": exp.DType.DOUBLE}\n        for expr_type in {\n            exp.Atan2,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DATETIME}\n        for expr_type in {\n            exp.CurrentTimestamp,\n            exp.Localtime,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.VARCHAR}\n        for expr_type in {\n            exp.Elt,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.INT}\n        for expr_type in {\n            exp.Month,\n            exp.Second,\n            exp.Week,\n        }\n    },\n}\n"
  },
  {
    "path": "sqlglot/typing/presto.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        expr_type: {\"returns\": exp.DType.BIGINT}\n        for expr_type in {\n            exp.BitwiseAnd,\n            exp.BitwiseNot,\n            exp.BitwiseOr,\n            exp.BitwiseXor,\n            exp.Length,\n            exp.Levenshtein,\n            exp.StrPosition,\n            exp.WidthBucket,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\")}\n        for expr_type in {\n            exp.Ceil,\n            exp.Floor,\n            exp.Round,\n            exp.Sign,\n        }\n    },\n    exp.Mod: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\", \"expression\")},\n    exp.Rand: {\n        \"annotator\": lambda self, e: (\n            self._annotate_by_args(e, \"this\") if e.this else self._set_type(e, exp.DType.DOUBLE)\n        )\n    },\n    exp.MD5Digest: {\"returns\": exp.DType.VARBINARY},\n}\n"
  },
  {
    "path": "sqlglot/typing/redshift.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    # Redshift's TO_TIMESTAMP returns TIMESTAMPTZ, not TIMESTAMP\n    # https://docs.aws.amazon.com/redshift/latest/dg/r_TO_TIMESTAMP.html\n    exp.StrToTime: {\"returns\": exp.DataType.Type.TIMESTAMPTZ},\n}\n"
  },
  {
    "path": "sqlglot/typing/snowflake.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.helper import seq_get\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nif t.TYPE_CHECKING:\n    from sqlglot.optimizer.annotate_types import TypeAnnotator\n\nDATE_PARTS = {\"DAY\", \"WEEK\", \"MONTH\", \"QUARTER\", \"YEAR\"}\n\nMAX_PRECISION = 38\n\nMAX_SCALE = 37\n\n\ndef _annotate_reverse(self: TypeAnnotator, expression: exp.Reverse) -> exp.Reverse:\n    expression = self._annotate_by_args(expression, \"this\")\n    if expression.is_type(exp.DType.NULL):\n        # Snowflake treats REVERSE(NULL) as a VARCHAR\n        self._set_type(expression, exp.DType.VARCHAR)\n\n    return expression\n\n\ndef _annotate_timestamp_from_parts(\n    self: TypeAnnotator, expression: exp.TimestampFromParts\n) -> exp.TimestampFromParts:\n    \"\"\"Annotate TimestampFromParts with correct type based on arguments.\n    TIMESTAMP_FROM_PARTS with time_zone -> TIMESTAMPTZ\n    TIMESTAMP_FROM_PARTS without time_zone -> TIMESTAMP (defaults to TIMESTAMP_NTZ)\n    \"\"\"\n    if expression.args.get(\"zone\"):\n        self._set_type(expression, exp.DType.TIMESTAMPTZ)\n    else:\n        self._set_type(expression, exp.DType.TIMESTAMP)\n\n    return expression\n\n\ndef _annotate_date_or_time_add(self: TypeAnnotator, expression: exp.Expr) -> exp.Expr:\n    if (\n        expression.this.is_type(exp.DType.DATE)\n        and expression.text(\"unit\").upper() not in DATE_PARTS\n    ):\n        self._set_type(expression, exp.DType.TIMESTAMPNTZ)\n    else:\n        self._annotate_by_args(expression, \"this\")\n    return expression\n\n\ndef _annotate_decode_case(self: TypeAnnotator, expression: exp.DecodeCase) -> exp.DecodeCase:\n    \"\"\"Annotate DecodeCase with the type inferred from return values only.\n\n    DECODE uses the format: DECODE(expr, val1, ret1, val2, ret2, ..., default)\n    We only look at the return values (ret1, ret2, ..., default) to determine the type,\n    not the comparison values (val1, val2, ...) or the expression being compared.\n    \"\"\"\n    expressions = expression.expressions\n\n    # Return values are at indices 2, 4, 6, ... and the last element (if even length)\n    # DECODE(expr, val1, ret1, val2, ret2, ..., default)\n    return_types = [expressions[i].type for i in range(2, len(expressions), 2)]\n\n    # If the total number of expressions is even, the last one is the default\n    # Example:\n    #   DECODE(x, 1, 'a', 2, 'b')             -> len=5 (odd), no default\n    #   DECODE(x, 1, 'a', 2, 'b', 'default')  -> len=6 (even), has default\n    if len(expressions) % 2 == 0:\n        return_types.append(expressions[-1].type)\n\n    # Determine the common type from all return values\n    last_type = None\n    for ret_type in return_types:\n        last_type = self._maybe_coerce(last_type or ret_type, ret_type)\n\n    self._set_type(expression, last_type)\n    return expression\n\n\ndef _annotate_arg_max_min(self, expression):\n    self._set_type(\n        expression,\n        exp.DType.ARRAY if expression.args.get(\"count\") else expression.this.type,\n    )\n    return expression\n\n\ndef _annotate_within_group(self: TypeAnnotator, expression: exp.WithinGroup) -> exp.WithinGroup:\n    \"\"\"Annotate WithinGroup with correct type based on the inner function.\n\n    1) Annotate args first\n    2) Check if this is PercentileDisc/PercentileCont and if so, re-annotate its type to match the ordered expression's type\n    \"\"\"\n\n    if (\n        isinstance(expression.this, (exp.PercentileDisc, exp.PercentileCont))\n        and isinstance(order_expr := expression.expression, exp.Order)\n        and len(order_expr.expressions) == 1\n        and isinstance(ordered_expr := order_expr.expressions[0], exp.Ordered)\n    ):\n        self._set_type(expression, ordered_expr.this.type)\n\n    return expression\n\n\ndef _annotate_median(self: TypeAnnotator, expression: exp.Median) -> exp.Median:\n    \"\"\"Annotate MEDIAN function with correct return type.\n\n    Based on Snowflake documentation:\n    - If the expr is FLOAT/DOUBLE -> annotate as DOUBLE (FLOAT is a synonym for DOUBLE)\n    - If the expr is NUMBER(p, s) -> annotate as NUMBER(min(p+3, 38), min(s+3, 37))\n    \"\"\"\n    # First annotate the argument to get its type\n    expression = self._annotate_by_args(expression, \"this\")\n\n    # Get the input type\n    input_type = expression.this.type\n\n    if input_type.is_type(exp.DType.DOUBLE):\n        # If input is FLOAT/DOUBLE, return DOUBLE (FLOAT is normalized to DOUBLE in Snowflake)\n        self._set_type(expression, exp.DType.DOUBLE)\n    else:\n        # If input is NUMBER(p, s), return NUMBER(min(p+3, 38), min(s+3, 37))\n        exprs = input_type.expressions\n\n        precision_expr = seq_get(exprs, 0)\n        precision = precision_expr.this.to_py() if precision_expr else MAX_PRECISION\n\n        scale_expr = seq_get(exprs, 1)\n        scale = scale_expr.this.to_py() if scale_expr else 0\n\n        new_precision = min(precision + 3, MAX_PRECISION)\n        new_scale = min(scale + 3, MAX_SCALE)\n\n        # Build the new NUMBER type\n        new_type = exp.DataType.build(f\"NUMBER({new_precision}, {new_scale})\", dialect=\"snowflake\")\n        self._set_type(expression, new_type)\n\n    return expression\n\n\ndef _annotate_variance(self: TypeAnnotator, expression: exp.Expr) -> exp.Expr:\n    \"\"\"Annotate variance functions (VAR_POP, VAR_SAMP, VARIANCE, VARIANCE_POP) with correct return type.\n\n    Based on Snowflake behavior:\n    - DECFLOAT -> DECFLOAT(38)\n    - FLOAT/DOUBLE -> FLOAT\n    - INT, NUMBER(p, 0) -> NUMBER(38, 6)\n    - NUMBER(p, s) -> NUMBER(38, max(12, s))\n    \"\"\"\n    # First annotate the argument to get its type\n    expression = self._annotate_by_args(expression, \"this\")\n\n    # Get the input type\n    input_type = expression.this.type\n\n    # Special case: DECFLOAT -> DECFLOAT(38)\n    if input_type.is_type(exp.DType.DECFLOAT):\n        self._set_type(expression, exp.DataType.build(\"DECFLOAT\", dialect=\"snowflake\"))\n    # Special case: FLOAT/DOUBLE -> DOUBLE\n    elif input_type.is_type(exp.DType.FLOAT, exp.DType.DOUBLE):\n        self._set_type(expression, exp.DType.DOUBLE)\n    # For NUMBER types: determine the scale\n    else:\n        exprs = input_type.expressions\n        scale_expr = seq_get(exprs, 1)\n        scale = scale_expr.this.to_py() if scale_expr else 0\n\n        # If scale is 0 (INT, BIGINT, NUMBER(p,0)): return NUMBER(38, 6)\n        # Otherwise, Snowflake appears to assign scale through the formula MAX(12, s)\n        new_scale = 6 if scale == 0 else max(12, scale)\n\n        # Build the new NUMBER type\n        new_type = exp.DataType.build(f\"NUMBER({MAX_PRECISION}, {new_scale})\", dialect=\"snowflake\")\n        self._set_type(expression, new_type)\n\n    return expression\n\n\ndef _annotate_kurtosis(self: TypeAnnotator, expression: exp.Kurtosis) -> exp.Kurtosis:\n    \"\"\"Annotate KURTOSIS with correct return type.\n\n    Based on Snowflake behavior:\n    - DECFLOAT input -> DECFLOAT\n    - DOUBLE or FLOAT input -> DOUBLE\n    - Other numeric types (INT, NUMBER) -> NUMBER(38, 12)\n    \"\"\"\n    expression = self._annotate_by_args(expression, \"this\")\n    input_type = expression.this.type\n\n    if input_type.is_type(exp.DType.DECFLOAT):\n        self._set_type(expression, exp.DataType.build(\"DECFLOAT\", dialect=\"snowflake\"))\n    elif input_type.is_type(exp.DType.FLOAT, exp.DType.DOUBLE):\n        self._set_type(expression, exp.DType.DOUBLE)\n    else:\n        self._set_type(\n            expression, exp.DataType.build(f\"NUMBER({MAX_PRECISION}, 12)\", dialect=\"snowflake\")\n        )\n\n    return expression\n\n\ndef _annotate_math_with_float_decfloat(self: TypeAnnotator, expression: exp.Expr) -> exp.Expr:\n    \"\"\"Annotate math functions that preserve  DECFLOAT but return DOUBLE for others.\n\n    In Snowflake, trigonometric and exponential math functions:\n    - If input is DECFLOAT -> return DECFLOAT\n    - For integer types (INT, BIGINT, etc.) -> return DOUBLE\n    - For other numeric types (NUMBER, DECIMAL, DOUBLE) -> return DOUBLE\n    \"\"\"\n    expression = self._annotate_by_args(expression, \"this\")\n\n    # If input is DECFLOAT, preserve\n    if expression.this.is_type(exp.DType.DECFLOAT):\n        self._set_type(expression, expression.this.type)\n    else:\n        # For all other types (integers, decimals, etc.), return DOUBLE\n        self._set_type(expression, exp.DType.DOUBLE)\n\n    return expression\n\n\ndef _annotate_str_to_time(self: TypeAnnotator, expression: exp.StrToTime) -> exp.StrToTime:\n    # target_type is stored as a DataType instance\n    target_type_arg = expression.args.get(\"target_type\")\n    target_type = (\n        target_type_arg.this if isinstance(target_type_arg, exp.DataType) else exp.DType.TIMESTAMP\n    )\n    self._set_type(expression, target_type)\n    return expression\n\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\")}\n        for expr_type in {\n            exp.AddMonths,\n            exp.Ceil,\n            exp.DateTrunc,\n            exp.Floor,\n            exp.Left,\n            exp.Mode,\n            exp.Pad,\n            exp.Right,\n            exp.Round,\n            exp.Stuff,\n            exp.Substring,\n            exp.TimeSlice,\n            exp.TimestampTrunc,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.ARRAY}\n        for expr_type in (\n            exp.ApproxTopK,\n            exp.ApproxTopKEstimate,\n            exp.Array,\n            exp.ArrayAgg,\n            exp.ArrayAppend,\n            exp.ArrayCompact,\n            exp.ArrayConcat,\n            exp.ArrayConstructCompact,\n            exp.ArrayPrepend,\n            exp.ArrayRemove,\n            exp.ArraysZip,\n            exp.ArrayUniqueAgg,\n            exp.ArrayUnionAgg,\n            exp.MapKeys,\n            exp.RegexpExtractAll,\n            exp.Split,\n            exp.StringToArray,\n        )\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BIGINT}\n        for expr_type in {\n            exp.BitmapBitPosition,\n            exp.BitmapBucketNumber,\n            exp.BitmapCount,\n            exp.Factorial,\n            exp.GroupingId,\n            exp.MD5NumberLower64,\n            exp.MD5NumberUpper64,\n            exp.Rand,\n            exp.Seq8,\n            exp.Zipf,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BINARY}\n        for expr_type in {\n            exp.Base64DecodeBinary,\n            exp.BitmapConstructAgg,\n            exp.BitmapOrAgg,\n            exp.Compress,\n            exp.DecompressBinary,\n            exp.Decrypt,\n            exp.DecryptRaw,\n            exp.Encrypt,\n            exp.EncryptRaw,\n            exp.HexString,\n            exp.MD5Digest,\n            exp.SHA1Digest,\n            exp.SHA2Digest,\n            exp.ToBinary,\n            exp.TryBase64DecodeBinary,\n            exp.TryHexDecodeBinary,\n            exp.Unhex,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.BOOLEAN}\n        for expr_type in {\n            exp.Booland,\n            exp.Boolnot,\n            exp.Boolor,\n            exp.BoolxorAgg,\n            exp.EqualNull,\n            exp.IsNullValue,\n            exp.MapContainsKey,\n            exp.Search,\n            exp.SearchIp,\n            exp.ToBoolean,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DATE}\n        for expr_type in {\n            exp.NextDay,\n            exp.PreviousDay,\n        }\n    },\n    **{\n        expr_type: {\n            \"annotator\": lambda self, e: self._set_type(\n                e, exp.DataType.build(\"NUMBER\", dialect=\"snowflake\")\n            )\n        }\n        for expr_type in (\n            exp.BitwiseAndAgg,\n            exp.BitwiseOrAgg,\n            exp.BitwiseXorAgg,\n            exp.RegexpCount,\n            exp.RegexpInstr,\n            exp.ToNumber,\n        )\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.DOUBLE}\n        for expr_type in {\n            exp.ApproxPercentileEstimate,\n            exp.ApproximateSimilarity,\n            exp.CosineDistance,\n            exp.CovarPop,\n            exp.CovarSamp,\n            exp.DotProduct,\n            exp.EuclideanDistance,\n            exp.ManhattanDistance,\n            exp.MonthsBetween,\n            exp.Normal,\n        }\n    },\n    exp.Kurtosis: {\"annotator\": _annotate_kurtosis},\n    **{\n        expr_type: {\"returns\": exp.DType.DECFLOAT}\n        for expr_type in {\n            exp.ToDecfloat,\n            exp.TryToDecfloat,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": _annotate_math_with_float_decfloat}\n        for expr_type in {\n            exp.Acos,\n            exp.Asin,\n            exp.Atan,\n            exp.Atan2,\n            exp.Cbrt,\n            exp.Cos,\n            exp.Cot,\n            exp.Degrees,\n            exp.Exp,\n            exp.Ln,\n            exp.Log,\n            exp.Pow,\n            exp.Radians,\n            exp.RegrAvgx,\n            exp.RegrAvgy,\n            exp.RegrCount,\n            exp.RegrIntercept,\n            exp.RegrR2,\n            exp.RegrSlope,\n            exp.RegrSxx,\n            exp.RegrSxy,\n            exp.RegrSyy,\n            exp.RegrValx,\n            exp.RegrValy,\n            exp.Sin,\n            exp.Sqrt,\n            exp.Tan,\n            exp.Tanh,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.INT}\n        for expr_type in {\n            exp.ByteLength,\n            exp.Grouping,\n            exp.JarowinklerSimilarity,\n            exp.MapSize,\n            exp.Minute,\n            exp.RtrimmedLength,\n            exp.Second,\n            exp.Seq1,\n            exp.Seq2,\n            exp.Seq4,\n            exp.WidthBucket,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.OBJECT}\n        for expr_type in {\n            exp.ApproxPercentileAccumulate,\n            exp.ApproxPercentileCombine,\n            exp.ApproxTopKAccumulate,\n            exp.ApproxTopKCombine,\n            exp.ObjectAgg,\n            exp.ParseIp,\n            exp.ParseUrl,\n            exp.XMLGet,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.MAP}\n        for expr_type in {\n            exp.MapCat,\n            exp.MapDelete,\n            exp.MapInsert,\n            exp.MapPick,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.FILE}\n        for expr_type in {\n            exp.ToFile,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.TIME}\n        for expr_type in {\n            exp.TimeFromParts,\n            exp.TsOrDsToTime,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.TIMESTAMPLTZ}\n        for expr_type in {\n            exp.CurrentTimestamp,\n            exp.Localtimestamp,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.TINYINT}\n        for expr_type in {\n            exp.DayOfMonth,\n            exp.DayOfWeek,\n            exp.DayOfYear,\n            exp.Quarter,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.VARCHAR}\n        for expr_type in {\n            exp.AIAgg,\n            exp.AIClassify,\n            exp.AISummarizeAgg,\n            exp.Base64DecodeString,\n            exp.Base64Encode,\n            exp.CheckJson,\n            exp.CheckXml,\n            exp.Collate,\n            exp.Collation,\n            exp.CurrentAccount,\n            exp.CurrentAccountName,\n            exp.CurrentAvailableRoles,\n            exp.CurrentClient,\n            exp.CurrentDatabase,\n            exp.CurrentIpAddress,\n            exp.CurrentSchemas,\n            exp.CurrentSecondaryRoles,\n            exp.CurrentSession,\n            exp.CurrentStatement,\n            exp.CurrentTransaction,\n            exp.CurrentWarehouse,\n            exp.CurrentOrganizationUser,\n            exp.CurrentRegion,\n            exp.CurrentRole,\n            exp.CurrentRoleType,\n            exp.CurrentOrganizationName,\n            exp.DecompressString,\n            exp.HexDecodeString,\n            exp.HexEncode,\n            exp.Randstr,\n            exp.RegexpExtract,\n            exp.RegexpReplace,\n            exp.Repeat,\n            exp.Replace,\n            exp.Soundex,\n            exp.SoundexP123,\n            exp.SplitPart,\n            exp.Strtok,\n            exp.TryBase64DecodeString,\n            exp.TryHexDecodeString,\n            exp.Uuid,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.VARIANT}\n        for expr_type in {\n            exp.Minhash,\n            exp.MinhashCombine,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": _annotate_variance}\n        for expr_type in (\n            exp.Variance,\n            exp.VariancePop,\n        )\n    },\n    exp.ArgMax: {\"annotator\": _annotate_arg_max_min},\n    exp.ArgMin: {\"annotator\": _annotate_arg_max_min},\n    exp.ConcatWs: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"expressions\")},\n    exp.ConvertTimezone: {\n        \"annotator\": lambda self, e: self._set_type(\n            e,\n            exp.DType.TIMESTAMPNTZ if e.args.get(\"source_tz\") else exp.DType.TIMESTAMPTZ,\n        )\n    },\n    exp.DateAdd: {\"annotator\": _annotate_date_or_time_add},\n    exp.DecodeCase: {\"annotator\": _annotate_decode_case},\n    exp.HashAgg: {\n        \"annotator\": lambda self, e: self._set_type(\n            e, exp.DataType.build(\"NUMBER(19, 0)\", dialect=\"snowflake\")\n        )\n    },\n    exp.Median: {\"annotator\": _annotate_median},\n    exp.Reverse: {\"annotator\": _annotate_reverse},\n    exp.StrToTime: {\"annotator\": _annotate_str_to_time},\n    exp.TimeAdd: {\"annotator\": _annotate_date_or_time_add},\n    exp.TimestampFromParts: {\"annotator\": _annotate_timestamp_from_parts},\n    exp.WithinGroup: {\"annotator\": _annotate_within_group},\n}\n"
  },
  {
    "path": "sqlglot/typing/spark.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.typing.spark2 import EXPRESSION_METADATA\n\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        exp_type: {\"returns\": exp.DType.DOUBLE}\n        for exp_type in {\n            exp.Sec,\n        }\n    },\n    **{\n        exp_type: {\"returns\": exp.DType.INT}\n        for exp_type in {\n            exp.ArraySize,\n        }\n    },\n    **{\n        exp_type: {\"returns\": exp.DType.VARCHAR}\n        for exp_type in {\n            exp.Collation,\n            exp.CurrentTimezone,\n            exp.Randstr,\n        }\n    },\n    **{\n        exp_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\")}\n        for exp_type in {\n            exp.ArrayCompact,\n            exp.ArrayInsert,\n            exp.BitwiseAndAgg,\n            exp.BitwiseOrAgg,\n            exp.BitwiseXorAgg,\n            exp.Overlay,\n        }\n    },\n    exp.BitmapCount: {\"returns\": exp.DType.BIGINT},\n    exp.Localtimestamp: {\"returns\": exp.DType.TIMESTAMPNTZ},\n    exp.ToBinary: {\"returns\": exp.DType.BINARY},\n    exp.DateFromUnixDate: {\"returns\": exp.DType.DATE},\n}\n"
  },
  {
    "path": "sqlglot/typing/spark2.py",
    "content": "from __future__ import annotations\n\nimport typing as t\n\nfrom sqlglot import exp\nfrom sqlglot.helper import ensure_list\nfrom sqlglot.typing.hive import EXPRESSION_METADATA as HIVE_EXPRESSION_METADATA\n\nif t.TYPE_CHECKING:\n    from sqlglot._typing import E\n    from sqlglot.optimizer.annotate_types import TypeAnnotator\n    from sqlglot.typing import ExprMetadataType\n\n\ndef _annotate_by_similar_args(\n    self: TypeAnnotator, expression: E, *args: str, target_type: exp.DataType | exp.DType\n) -> E:\n    \"\"\"\n    Infers the type of the expression according to the following rules:\n    - If all args are of the same type OR any arg is of target_type, the expr is inferred as such\n    - If any arg is of UNKNOWN type and none of target_type, the expr is inferred as UNKNOWN\n    \"\"\"\n    expressions: t.List[exp.Expr] = []\n    for arg in args:\n        arg_expr = expression.args.get(arg)\n        expressions.extend(expr for expr in ensure_list(arg_expr) if expr)\n\n    last_datatype = None\n\n    has_unknown = False\n    for expr in expressions:\n        if expr.is_type(exp.DType.UNKNOWN):\n            has_unknown = True\n        elif expr.is_type(target_type):\n            has_unknown = False\n            last_datatype = target_type\n            break\n        else:\n            last_datatype = expr.type\n\n    self._set_type(expression, exp.DType.UNKNOWN if has_unknown else last_datatype)\n    return expression\n\n\nEXPRESSION_METADATA: ExprMetadataType = {\n    **HIVE_EXPRESSION_METADATA,\n    **{\n        expr_type: {\"returns\": exp.DType.DOUBLE}\n        for expr_type in {\n            exp.Atan2,\n            exp.Randn,\n        }\n    },\n    **{\n        exp_type: {\"returns\": exp.DType.VARCHAR}\n        for exp_type in {\n            exp.Format,\n            exp.Right,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\")}\n        for expr_type in {\n            exp.ArrayFilter,\n            exp.Substring,\n        }\n    },\n    exp.AddMonths: {\"returns\": exp.DType.DATE},\n    exp.ApproxQuantile: {\n        \"annotator\": lambda self, e: self._annotate_by_args(\n            e, \"this\", array=e.args[\"quantile\"].is_type(exp.DType.ARRAY)\n        )\n    },\n    exp.AtTimeZone: {\"returns\": exp.DType.TIMESTAMP},\n    exp.Concat: {\n        \"annotator\": lambda self, e: _annotate_by_similar_args(\n            self, e, \"expressions\", target_type=exp.DType.TEXT\n        )\n    },\n    exp.NextDay: {\"returns\": exp.DType.DATE},\n    exp.Pad: {\n        \"annotator\": lambda self, e: _annotate_by_similar_args(\n            self, e, \"this\", \"fill_pattern\", target_type=exp.DType.TEXT\n        )\n    },\n}\n"
  },
  {
    "path": "sqlglot/typing/tsql.py",
    "content": "from __future__ import annotations\n\nfrom sqlglot import exp\nfrom sqlglot.typing import EXPRESSION_METADATA\n\nEXPRESSION_METADATA = {\n    **EXPRESSION_METADATA,\n    **{\n        expr_type: {\"returns\": exp.DType.FLOAT}\n        for expr_type in {\n            exp.Acos,\n            exp.Asin,\n            exp.Atan,\n            exp.Atan2,\n            exp.Cos,\n            exp.Cot,\n            exp.Sin,\n            exp.Tan,\n        }\n    },\n    **{\n        expr_type: {\"returns\": exp.DType.VARCHAR}\n        for expr_type in {\n            exp.Soundex,\n            exp.Stuff,\n        }\n    },\n    **{\n        expr_type: {\"annotator\": lambda self, e: self._annotate_by_args(e, \"this\")}\n        for expr_type in {\n            exp.Degrees,\n            exp.Radians,\n        }\n    },\n    exp.CurrentTimezone: {\"returns\": exp.DType.NVARCHAR},\n    exp.CurrentTimestamp: {\"returns\": exp.DType.DATETIME},\n}\n"
  },
  {
    "path": "sqlglotc/MANIFEST.in",
    "content": "include pyproject.toml\ninclude setup.py\nrecursive-include sqlglot *.py\n"
  },
  {
    "path": "sqlglotc/pyproject.toml",
    "content": "[project]\nname = \"sqlglotc\"\ndynamic = [\"version\"]\ndescription = \"mypyc-compiled extensions for sqlglot\"\nauthors = [{ name = \"Toby Mao\", email = \"toby.mao@gmail.com\" }]\nlicense = \"MIT\"\nrequires-python = \">= 3.9\"\n\n[project.optional-dependencies]\ndev = [\"setuptools >= 61.0\", \"setuptools_scm\", \"sqlglot-mypy>=1.19.1.post1\"]\n\n[project.urls]\nHomepage = \"https://sqlglot.com/\"\nRepository = \"https://github.com/tobymao/sqlglot\"\n\n[build-system]\nrequires = [\"setuptools >= 61.0\", \"setuptools_scm\", \"sqlglot-mypy>=1.19.1.post1\", \"types-python-dateutil\", \"sqlglot\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.setuptools]\ninclude-package-data = false\n\n[tool.setuptools_scm]\nroot = \"..\"\nfallback_version = \"0.0.0\"\nlocal_scheme = \"no-local-version\"\n\n[[tool.mypy.overrides]]\nmodule = [\"sqlglot._version\", \"sqlglotc\"]\nignore_missing_imports = true\n"
  },
  {
    "path": "sqlglotc/setup.py",
    "content": "import os\nimport shutil\n\nfrom setuptools import setup\nfrom setuptools.command.build_ext import build_ext as _build_ext\nfrom setuptools.command.sdist import sdist as _sdist\nfrom mypyc.build import mypycify\n\nSQLGLOT_SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"..\", \"sqlglot\")\n\n\ndef _find_sqlglot_dir():\n    \"\"\"Find the sqlglot source directory: repo source, or installed package.\n\n    When the installed package is in site-packages, copy it to a clean temp\n    directory so mypy doesn't discover unrelated modules (mypy_extensions,\n    typing_extensions) that cause shadowing errors.\n    \"\"\"\n    if os.path.isdir(SQLGLOT_SRC):\n        return SQLGLOT_SRC\n\n    # Fall back to the installed sqlglot package (build dependency).\n    import sqlglot\n    import tempfile\n\n    installed = os.path.dirname(sqlglot.__file__)\n    tmp = tempfile.mkdtemp(prefix=\"sqlglotc_build_\")\n    dst = os.path.join(tmp, \"sqlglot\")\n    shutil.copytree(installed, dst)\n    return dst\n\n\ndef _subpkg_files(src_dir, subpkg, files=None):\n    \"\"\"List source files from a sqlglot subpackage. Compiles all .py files if `files` is None.\"\"\"\n    if files is None:\n        files = sorted(\n            f\n            for f in os.listdir(os.path.join(src_dir, subpkg))\n            if f.endswith(\".py\") and f != \"__init__.py\"\n        )\n    return [os.path.join(subpkg, f) for f in files]\n\n\ndef _source_files(src_dir):\n    return [\n        \"errors.py\",\n        \"helper.py\",\n        \"parser.py\",\n        \"schema.py\",\n        \"serde.py\",\n        \"time.py\",\n        \"tokenizer_core.py\",\n        \"trie.py\",\n        *_subpkg_files(src_dir, \"expressions\"),\n        *_subpkg_files(\n            src_dir,\n            \"optimizer\",\n            [\n                \"scope.py\",\n                \"resolver.py\",\n                \"isolate_table_selects.py\",\n                \"normalize_identifiers.py\",\n                \"qualify.py\",\n                \"qualify_tables.py\",\n                \"qualify_columns.py\",\n            ],\n        ),\n        *_subpkg_files(src_dir, \"parsers\"),\n        *_subpkg_files(src_dir, \"executor\", [\"table.py\"]),\n    ]\n\n\nSRC_DIR = _find_sqlglot_dir()\nSOURCE_FILES = _source_files(SRC_DIR)\n\n# Set MYPYPATH to the parent of the sqlglot source so mypy resolves\n# `import sqlglot` from there — not from site-packages where\n# mypy_extensions.py / typing_extensions.py can cause shadowing errors.\nos.environ[\"MYPYPATH\"] = os.path.dirname(SRC_DIR)\n\n\ndef _source_paths():\n    return [os.path.join(SRC_DIR, f) for f in SOURCE_FILES]\n\n\nclass build_ext(_build_ext):\n    def copy_extensions_to_source(self):\n        \"\"\"For editable installs, put sqlglot.* .so files in the sqlglot source dir.\"\"\"\n        for ext in self.extensions:\n            fullname = self.get_ext_fullname(ext.name)\n            filename = self.get_ext_filename(fullname)\n            src = os.path.join(self.build_lib, filename)\n            parts = fullname.split(\".\")\n            if parts[0] == \"sqlglot\" and os.path.isdir(SQLGLOT_SRC):\n                # Place compiled sqlglot.* / sqlglot.sub.* modules in the sqlglot source tree.\n                sub_module = \".\".join(parts[1:])\n                dst = os.path.join(SQLGLOT_SRC, self.get_ext_filename(sub_module))\n            else:\n                # Place the mypyc runtime helper (e.g., HASH__mypyc) inside sqlglot/.\n                # sqlglot/__init__.py bootstraps it into sys.modules for editable installs.\n                dst = os.path.join(SQLGLOT_SRC, os.path.basename(filename))\n            self.copy_file(src, dst, level=self.verbose)\n\n\nclass sdist(_sdist):\n    \"\"\"Bundle sqlglot source files into the sdist as a fallback.\"\"\"\n\n    def run(self):\n        local_sqlglot = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"sqlglot\")\n        os.makedirs(local_sqlglot, exist_ok=True)\n        subpkgs = {os.path.dirname(f) for f in SOURCE_FILES if os.path.dirname(f)}\n        for subpkg in subpkgs:\n            pkg_dir = os.path.join(local_sqlglot, subpkg)\n            os.makedirs(pkg_dir, exist_ok=True)\n        for fname in SOURCE_FILES:\n            dst_path = os.path.join(local_sqlglot, fname)\n            os.makedirs(os.path.dirname(dst_path), exist_ok=True)\n            shutil.copy2(os.path.join(SQLGLOT_SRC, fname), dst_path)\n        try:\n            super().run()\n        finally:\n            shutil.rmtree(local_sqlglot, ignore_errors=True)\n\n\nsetup(\n    name=\"sqlglotc\",\n    packages=[],\n    ext_modules=mypycify(_source_paths(), opt_level=os.environ.get(\"MYPYC_OPT\", \"2\")),\n    cmdclass={\"build_ext\": build_ext, \"sdist\": sdist},\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/dialects/__init__.py",
    "content": ""
  },
  {
    "path": "tests/dialects/test_athena.py",
    "content": "from sqlglot import exp\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestAthena(Validator):\n    dialect = \"athena\"\n    maxDiff = None\n\n    def test_athena(self):\n        self.validate_identity(r\"SELECT '\\d+'\")\n        self.validate_identity(\"SELECT 'foo''bar'\")\n        self.validate_identity(\n            \"CREATE TABLE IF NOT EXISTS t (name STRING) LOCATION 's3://bucket/tmp/mytable/' TBLPROPERTIES ('table_type'='iceberg', 'FORMAT'='parquet')\"\n        )\n        self.validate_identity(\n            \"UNLOAD (SELECT name1, address1, comment1, key1 FROM table1) \"\n            \"TO 's3://amzn-s3-demo-bucket/ partitioned/' \"\n            \"WITH (format = 'TEXTFILE', partitioned_by = ARRAY['key1'])\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"\"\"USING EXTERNAL FUNCTION some_function(input VARBINARY)\n            RETURNS VARCHAR\n                LAMBDA 'some-name'\n            SELECT\n            some_function(1)\"\"\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"/* leading comment */CREATE SCHEMA foo\",\n            \"/* leading comment */ CREATE SCHEMA `foo`\",\n            identify=True,\n        )\n        self.validate_identity(\n            \"/* leading comment */SELECT * FROM foo\",\n            '/* leading comment */ SELECT * FROM \"foo\"',\n            identify=True,\n        )\n\n    def test_ddl(self):\n        # Hive-like, https://docs.aws.amazon.com/athena/latest/ug/create-table.html\n        self.validate_identity(\"CREATE EXTERNAL TABLE foo (id INT) COMMENT 'test comment'\")\n        self.validate_identity(\n            r\"CREATE EXTERNAL TABLE george.t (id INT COMMENT 'foo \\\\ bar') LOCATION 's3://my-bucket/'\"\n        )\n        self.validate_identity(\n            r\"CREATE EXTERNAL TABLE my_table (id BIGINT COMMENT 'this is the row\\'s id') LOCATION 's3://my-s3-bucket'\"\n        )\n        self.validate_identity(\n            \"CREATE EXTERNAL TABLE foo (id INT, val STRING) CLUSTERED BY (id, val) INTO 10 BUCKETS\"\n        )\n        self.validate_identity(\n            \"CREATE EXTERNAL TABLE foo (id INT, val STRING) STORED AS PARQUET LOCATION 's3://foo' TBLPROPERTIES ('has_encryped_data'='true', 'classification'='test')\"\n        )\n        self.validate_identity(\n            \"CREATE EXTERNAL TABLE IF NOT EXISTS foo (a INT, b STRING) ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe' WITH SERDEPROPERTIES ('case.insensitive'='FALSE') LOCATION 's3://table/path'\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE EXTERNAL TABLE x (y INT) ROW FORMAT SERDE 'serde' ROW FORMAT DELIMITED FIELDS TERMINATED BY '1' WITH SERDEPROPERTIES ('input.regex'='')\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"CREATE EXTERNAL TABLE `my_table` (`a7` ARRAY<DATE>) ROW FORMAT SERDE 'a' STORED AS INPUTFORMAT 'b' OUTPUTFORMAT 'c' LOCATION 'd' TBLPROPERTIES ('e'='f')\"\"\"\n        )\n\n        # Iceberg, https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html\n        self.validate_identity(\n            \"CREATE TABLE iceberg_table (`id` BIGINT, `data` STRING, category STRING) PARTITIONED BY (category, BUCKET(16, id)) LOCATION 's3://amzn-s3-demo-bucket/your-folder/' TBLPROPERTIES ('table_type'='ICEBERG', 'write_compression'='snappy')\"\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE TABLE iceberg_table (`id` BIGINT, `data` STRING, category STRING) PARTITIONED BY (category, BUCKET(16, id)) LOCATION 's3://amzn-s3-demo-bucket/your-folder/' TBLPROPERTIES ('table_type'='ICEBERG', 'write_compression'='snappy')\"\n        )\n\n        # CTAS goes to the Trino engine, where the table properties cant be encased in single quotes like they can for Hive\n        # ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties\n        # They're also case sensitive and need to be lowercase, otherwise you get eg \"Table properties [FORMAT] are not supported.\"\n        self.validate_identity(\n            \"CREATE TABLE foo WITH (table_type='ICEBERG', location='s3://foo/', format='orc', partitioning=ARRAY['bucket(id, 5)']) AS SELECT * FROM a\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE foo WITH (table_type='HIVE', external_location='s3://foo/', format='parquet', partitioned_by=ARRAY['ds']) AS SELECT * FROM a\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE foo AS WITH foo AS (SELECT a, b FROM bar) SELECT * FROM foo\"\n        )\n\n        # ALTER TABLE ADD COLUMN not supported, it needs to be generated as ALTER TABLE ADD COLUMNS\n        self.validate_identity(\n            \"ALTER TABLE `foo`.`bar` ADD COLUMN `end_ts` BIGINT\",\n            \"ALTER TABLE `foo`.`bar` ADD COLUMNS (`end_ts` BIGINT)\",\n        )\n        self.validate_identity(\"ALTER TABLE `foo` DROP COLUMN `id`\")\n\n    def test_dml(self):\n        self.validate_all(\n            \"SELECT CAST(ds AS VARCHAR) AS ds FROM (VALUES ('2022-01-01')) AS t(ds)\",\n            read={\"\": \"SELECT CAST(ds AS STRING) AS ds FROM (VALUES ('2022-01-01')) AS t(ds)\"},\n            write={\n                \"hive\": \"SELECT CAST(ds AS STRING) AS ds FROM (VALUES ('2022-01-01')) AS t(ds)\",\n                \"trino\": \"SELECT CAST(ds AS VARCHAR) AS ds FROM (VALUES ('2022-01-01')) AS t(ds)\",\n                \"athena\": \"SELECT CAST(ds AS VARCHAR) AS ds FROM (VALUES ('2022-01-01')) AS t(ds)\",\n            },\n        )\n\n    def test_ddl_quoting(self):\n        self.validate_identity(\"CREATE SCHEMA `foo`\")\n        self.validate_identity(\"CREATE SCHEMA foo\")\n\n        self.validate_identity(\"CREATE EXTERNAL TABLE `foo` (`id` INT) LOCATION 's3://foo/'\")\n        self.validate_identity(\"CREATE EXTERNAL TABLE foo (id INT) LOCATION 's3://foo/'\")\n        self.validate_identity(\n            \"CREATE EXTERNAL TABLE foo (id INT) LOCATION 's3://foo/'\",\n            \"CREATE EXTERNAL TABLE `foo` (`id` INT) LOCATION 's3://foo/'\",\n            identify=True,\n        )\n\n        self.validate_identity(\"CREATE TABLE foo AS SELECT * FROM a\")\n        self.validate_identity('CREATE TABLE \"foo\" AS SELECT * FROM \"a\"')\n\n        self.validate_identity('DROP VIEW IF EXISTS \"foo\".\"bar\"')\n        self.validate_identity('CREATE VIEW \"foo\" AS SELECT \"id\" FROM \"tbl\"')\n        self.validate_identity(\n            \"CREATE VIEW foo AS SELECT id FROM tbl\",\n            'CREATE VIEW \"foo\" AS SELECT \"id\" FROM \"tbl\"',\n            identify=True,\n        )\n\n        self.validate_identity(\"DROP TABLE `foo`\")\n        self.validate_identity(\"DROP TABLE foo\")\n        self.validate_identity(\n            \"DROP TABLE foo\",\n            \"DROP TABLE `foo`\",\n            identify=True,\n        )\n\n        self.validate_identity('CREATE VIEW \"foo\" AS SELECT \"id\" FROM \"tbl\"')\n        self.validate_identity(\"CREATE VIEW foo AS SELECT id FROM tbl\")\n        self.validate_identity(\n            \"CREATE VIEW foo AS SELECT id FROM tbl\",\n            'CREATE VIEW \"foo\" AS SELECT \"id\" FROM \"tbl\"',\n            identify=True,\n        )\n\n        # As a side effect of being able to parse both quote types, we can also fix the quoting on incorrectly quoted source queries\n        self.validate_identity('CREATE SCHEMA \"foo\"', \"CREATE SCHEMA `foo`\")\n        self.validate_identity('DROP TABLE \"foo\"', \"DROP TABLE `foo`\")\n        self.validate_identity(\n            \"DESCRIBE foo.bar\",\n            \"DESCRIBE `foo`.`bar`\",\n            identify=True,\n        )\n        self.validate_identity(\n            'CREATE TABLE \"foo\" AS WITH \"foo\" AS (SELECT \"a\", \"b\" FROM \"bar\") SELECT * FROM \"foo\"'\n        )\n\n    def test_dml_quoting(self):\n        self.validate_identity(\"SELECT a AS foo FROM tbl\")\n        self.validate_identity('SELECT \"a\" AS \"foo\" FROM \"tbl\"')\n\n        self.validate_identity(\"INSERT INTO foo (id) VALUES (1)\")\n        self.validate_identity('INSERT INTO \"foo\" (\"id\") VALUES (1)')\n\n        self.validate_identity(\"UPDATE foo SET id = 3 WHERE id = 7\")\n        self.validate_identity('UPDATE \"foo\" SET \"id\" = 3 WHERE \"id\" = 7')\n\n        self.validate_identity(\"DELETE FROM foo WHERE id > 10\")\n        self.validate_identity('DELETE FROM \"foo\" WHERE \"id\" > 10')\n\n        self.validate_identity(\"WITH foo AS (SELECT a, b FROM bar) SELECT * FROM foo\")\n        self.validate_identity(\n            \"WITH foo AS (SELECT a, b FROM bar) SELECT * FROM foo\",\n            'WITH \"foo\" AS (SELECT \"a\", \"b\" FROM \"bar\") SELECT * FROM \"foo\"',\n            identify=True,\n        )\n\n    def test_create_table(self):\n        # There are two CREATE TABLE syntaxes\n        # Both hit Athena's Hive engine but creating an Iceberg table is different from creating a normal Hive table\n\n        table_schema = exp.Schema(\n            this=exp.to_table(\"foo.bar\"),\n            expressions=[\n                exp.ColumnDef(this=exp.to_identifier(\"a\"), kind=exp.DataType.build(\"int\")),\n                exp.ColumnDef(this=exp.to_identifier(\"b\"), kind=exp.DataType.build(\"varchar\")),\n            ],\n        )\n\n        # Hive tables - CREATE EXTERNAL TABLE\n        ct_hive = exp.Create(\n            this=table_schema,\n            kind=\"TABLE\",\n            properties=exp.Properties(\n                expressions=[\n                    exp.ExternalProperty(),\n                    exp.FileFormatProperty(this=exp.Literal.string(\"parquet\")),\n                    exp.LocationProperty(this=exp.Literal.string(\"s3://foo\")),\n                    exp.PartitionedByProperty(\n                        this=exp.Schema(expressions=[exp.to_column(\"partition_col\")])\n                    ),\n                ]\n            ),\n        )\n        self.assertEqual(\n            ct_hive.sql(dialect=self.dialect, identify=True),\n            \"CREATE EXTERNAL TABLE `foo`.`bar` (`a` INT, `b` STRING) STORED AS PARQUET LOCATION 's3://foo' PARTITIONED BY (`partition_col`)\",\n        )\n\n        # Iceberg tables - CREATE TABLE... TBLPROPERTIES ('table_type'='iceberg')\n        # no EXTERNAL keyword and the 'table_type=iceberg' property must be set\n        # ref: https://docs.aws.amazon.com/athena/latest/ug/querying-iceberg-creating-tables.html#querying-iceberg-partitioning\n        ct_iceberg = exp.Create(\n            this=table_schema,\n            kind=\"TABLE\",\n            properties=exp.Properties(\n                expressions=[\n                    exp.FileFormatProperty(this=exp.Literal.string(\"parquet\")),\n                    exp.LocationProperty(this=exp.Literal.string(\"s3://foo\")),\n                    exp.PartitionedByProperty(\n                        this=exp.Schema(\n                            expressions=[\n                                exp.to_column(\"partition_col\"),\n                                exp.PartitionedByBucket(\n                                    this=exp.to_column(\"a\"), expression=exp.Literal.number(4)\n                                ),\n                            ]\n                        )\n                    ),\n                    exp.Property(this=exp.var(\"table_type\"), value=exp.Literal.string(\"iceberg\")),\n                ]\n            ),\n        )\n        self.assertEqual(\n            ct_iceberg.sql(dialect=self.dialect, identify=True),\n            \"CREATE TABLE `foo`.`bar` (`a` INT, `b` STRING) STORED AS PARQUET LOCATION 's3://foo' PARTITIONED BY (`partition_col`, BUCKET(4, `a`)) TBLPROPERTIES ('table_type'='iceberg')\",\n        )\n\n    def test_ctas(self):\n        # Hive tables use 'external_location' to specify the table location, Iceberg tables use 'location' to specify the table location\n        # In addition, Hive tables used 'partitioned_by' to specify the partition fields and Iceberg tables use 'partitioning' to specify the partition fields\n        # The 'table_type' property is used to determine if it's a Hive or an Iceberg table. If it's omitted, it defaults to Hive\n        # ref: https://docs.aws.amazon.com/athena/latest/ug/create-table-as.html#ctas-table-properties\n        ctas_hive = exp.Create(\n            this=exp.to_table(\"foo.bar\"),\n            kind=\"TABLE\",\n            properties=exp.Properties(\n                expressions=[\n                    exp.FileFormatProperty(this=exp.Literal.string(\"parquet\")),\n                    exp.LocationProperty(this=exp.Literal.string(\"s3://foo\")),\n                    exp.PartitionedByProperty(\n                        this=exp.Schema(expressions=[exp.to_column(\"partition_col\", quoted=True)])\n                    ),\n                ]\n            ),\n            expression=exp.select(\"1\"),\n        )\n\n        # Even if identify=True, the column names should not be quoted within the string literals in the partitioned_by ARRAY[]\n        self.assertEqual(\n            ctas_hive.sql(dialect=self.dialect, identify=True),\n            \"CREATE TABLE \\\"foo\\\".\\\"bar\\\" WITH (format='parquet', external_location='s3://foo', partitioned_by=ARRAY['partition_col']) AS SELECT 1\",\n        )\n        self.assertEqual(\n            ctas_hive.sql(dialect=self.dialect, identify=False),\n            \"CREATE TABLE foo.bar WITH (format='parquet', external_location='s3://foo', partitioned_by=ARRAY['partition_col']) AS SELECT 1\",\n        )\n\n        ctas_iceberg = exp.Create(\n            this=exp.to_table(\"foo.bar\"),\n            kind=\"TABLE\",\n            properties=exp.Properties(\n                expressions=[\n                    exp.Property(this=exp.var(\"table_type\"), value=exp.Literal.string(\"iceberg\")),\n                    exp.LocationProperty(this=exp.Literal.string(\"s3://foo\")),\n                    exp.PartitionedByProperty(\n                        this=exp.Schema(\n                            expressions=[\n                                exp.to_column(\"partition_col\"),\n                                exp.PartitionedByBucket(\n                                    this=exp.to_column(\"a\", quoted=True),\n                                    expression=exp.Literal.number(4),\n                                ),\n                            ]\n                        )\n                    ),\n                ]\n            ),\n            expression=exp.select(\"1\"),\n        )\n        # Even if identify=True, the column names should not be quoted within the string literals in the partitioning ARRAY[]\n        # Technically Trino's Iceberg connector does support quoted column names in the string literals but its undocumented\n        # so we dont do it to keep consistency with the Hive connector\n        self.assertEqual(\n            ctas_iceberg.sql(dialect=self.dialect, identify=True),\n            \"CREATE TABLE \\\"foo\\\".\\\"bar\\\" WITH (table_type='iceberg', location='s3://foo', partitioning=ARRAY['partition_col', 'BUCKET(a, 4)']) AS SELECT 1\",\n        )\n        self.assertEqual(\n            ctas_iceberg.sql(dialect=self.dialect, identify=False),\n            \"CREATE TABLE foo.bar WITH (table_type='iceberg', location='s3://foo', partitioning=ARRAY['partition_col', 'BUCKET(a, 4)']) AS SELECT 1\",\n        )\n\n    def test_parse_partitioned_by_returns_iceberg_transforms(self):\n        # check that parse_into works for PartitionedByProperty and also that correct AST nodes are emitted for Iceberg transforms\n        parsed = self.parse_one(\n            \"(a, bucket(4, b), truncate(3, c), month(d))\", into=exp.PartitionedByProperty\n        )\n\n        assert isinstance(parsed, exp.PartitionedByProperty)\n        assert isinstance(parsed.this, exp.Schema)\n        assert next(n for n in parsed.this.expressions if isinstance(n, exp.PartitionedByBucket))\n        assert next(n for n in parsed.this.expressions if isinstance(n, exp.PartitionByTruncate))\n"
  },
  {
    "path": "tests/dialects/test_bigquery.py",
    "content": "from unittest import mock\nimport datetime\nimport pytz\n\nfrom sqlglot import (\n    ErrorLevel,\n    ParseError,\n    TokenError,\n    UnsupportedError,\n    exp,\n    parse,\n    transpile,\n    parse_one,\n)\nfrom sqlglot.helper import logger as helper_logger\nfrom sqlglot.parser import logger as parser_logger\nfrom tests.dialects.test_dialect import Validator\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom sqlglot.optimizer.qualify import qualify\n\n\nclass TestBigQuery(Validator):\n    dialect = \"bigquery\"\n    maxDiff = None\n\n    def test_bigquery(self):\n        for prefix in (\"c.db.\", \"db.\", \"\"):\n            with self.subTest(f\"Parsing {prefix}INFORMATION_SCHEMA.X into a Table\"):\n                table = self.parse_one(f\"`{prefix}INFORMATION_SCHEMA.X`\", into=exp.Table)\n                this = table.this\n\n                self.assertIsInstance(this, exp.Identifier)\n                self.assertTrue(this.quoted)\n                self.assertEqual(this.name, \"INFORMATION_SCHEMA.X\")\n\n        table = self.parse_one(\"x-0._y.z\", into=exp.Table)\n        self.assertEqual(table.catalog, \"x-0\")\n        self.assertEqual(table.db, \"_y\")\n        self.assertEqual(table.name, \"z\")\n\n        table = self.parse_one(\"x-0._y\", into=exp.Table)\n        self.assertEqual(table.db, \"x-0\")\n        self.assertEqual(table.name, \"_y\")\n\n        self.validate_identity(\"SAFE.SOME_RANDOM_FUNC(a, b, c)\").assert_is(exp.SafeFunc)\n        self.validate_identity(\n            \"SAFE.SUBSTR('foo', 0, -2)\",\n        ).assert_is(exp.SafeFunc).this.assert_is(exp.Substring)\n        self.validate_identity(\"SAFE.TIMESTAMP(foo, zone)\").assert_is(exp.SafeFunc).this.assert_is(\n            exp.Timestamp\n        )\n        self.validate_identity(\n            \"SAFE.PARSE_DATE('%Y-%m-%d', '2024-01-15')\",\n            \"SAFE.PARSE_DATE('%F', '2024-01-15')\",\n        ).assert_is(exp.SafeFunc).this.assert_is(exp.StrToDate)\n        self.validate_identity(\n            \"SAFE.PARSE_DATETIME('%Y-%m-%d %H:%M:%S', '2024-01-15 10:30:00')\",\n            \"SAFE.PARSE_DATETIME('%F %T', '2024-01-15 10:30:00')\",\n        ).assert_is(exp.SafeFunc).this.assert_is(exp.ParseDatetime)\n        self.validate_identity(\n            \"SAFE.PARSE_TIMESTAMP('%Y-%m-%d %H:%M:%S', '2024-01-15 10:30:00')\",\n            \"SAFE.PARSE_TIMESTAMP('%F %T', '2024-01-15 10:30:00')\",\n        ).assert_is(exp.SafeFunc).this.assert_is(exp.StrToTime)\n\n        self.validate_identity(\"TIMESTAMP(foo, zone)\").assert_is(exp.Timestamp)\n        self.validate_identity(\"SELECT * FROM x-0.y\")\n        self.assertEqual(exp.to_table(\"`a.b`.`c.d`\", dialect=\"bigquery\").sql(), '\"a\".\"b\".\"c\".\"d\"')\n        self.assertEqual(exp.to_table(\"`x`.`y.z`\", dialect=\"bigquery\").sql(), '\"x\".\"y\".\"z\"')\n        self.assertEqual(exp.to_table(\"`x.y.z`\", dialect=\"bigquery\").sql(), '\"x\".\"y\".\"z\"')\n        self.assertEqual(exp.to_table(\"`x.y.z`\", dialect=\"bigquery\").sql(\"bigquery\"), \"`x.y.z`\")\n        self.assertEqual(exp.to_table(\"`x`.`y`\", dialect=\"bigquery\").sql(\"bigquery\"), \"`x`.`y`\")\n\n        column = self.validate_identity(\"SELECT `db.t`.`c` FROM `db.t`\").selects[0]\n        self.assertEqual(len(column.parts), 3)\n\n        select_with_quoted_udf = self.validate_identity(\"SELECT `p.d.UdF`(data) FROM `p.d.t`\")\n        self.assertEqual(select_with_quoted_udf.selects[0].name, \"p.d.UdF\")\n\n        self.validate_identity(\"SELECT EXP(1)\")\n        self.validate_identity(\"NET.HOST('http://example.com')\").assert_is(\n            exp.NetFunc\n        ).this.assert_is(exp.Host)\n        self.validate_identity(\"NET.REG_DOMAIN('http://example.com')\").assert_is(\n            exp.NetFunc\n        ).this.assert_is(exp.RegDomain)\n        self.validate_identity(\"DATE_TRUNC(x, @foo)\").unit.assert_is(exp.Parameter)\n        self.validate_identity(\"ARRAY_CONCAT_AGG(x ORDER BY ARRAY_LENGTH(x) LIMIT 2)\")\n        self.validate_identity(\"ARRAY_CONCAT_AGG(x LIMIT 2)\")\n        self.validate_identity(\"ARRAY_CONCAT_AGG(x ORDER BY ARRAY_LENGTH(x))\")\n        self.validate_identity(\"ARRAY_CONCAT_AGG(x)\")\n        self.validate_identity(\"PARSE_TIMESTAMP('%FT%H:%M:%E*S%z', x)\")\n        self.validate_identity(\"SELECT ARRAY_CONCAT([1])\")\n        self.validate_identity(\"SELECT * FROM READ_CSV('bla.csv')\")\n        self.validate_identity(\"CAST(x AS STRUCT<list ARRAY<INT64>>)\")\n        self.validate_identity(\"assert.true(1 = 1)\")\n        self.validate_identity(\"SELECT jsondoc['some_key']\")\n        self.validate_identity(\"SELECT `p.d.UdF`(data).* FROM `p.d.t`\")\n        self.validate_identity(\"SELECT * FROM `my-project.my-dataset.my-table`\")\n        self.validate_identity(\"CREATE OR REPLACE TABLE `a.b.c` CLONE `a.b.d`\")\n        self.validate_identity(\"SELECT x, 1 AS y GROUP BY 1 ORDER BY 1\")\n        self.validate_identity(\"SELECT * FROM x.*\")\n        self.validate_identity(\"SELECT * FROM x.y*\")\n        self.validate_identity(\"CASE A WHEN 90 THEN 'red' WHEN 50 THEN 'blue' ELSE 'green' END\")\n        self.validate_identity(\"CREATE SCHEMA x DEFAULT COLLATE 'en'\")\n        self.validate_identity(\"CREATE TABLE x (y INT64) DEFAULT COLLATE 'en'\")\n        self.validate_identity(\"PARSE_JSON('{}', wide_number_mode => 'exact')\")\n        self.validate_identity(\"FOO(values)\")\n        self.validate_identity(\"STRUCT(values AS value)\")\n\n        self.validate_identity(\"SELECT SEARCH(data_to_search, 'search_query')\")\n        self.validate_identity(\n            \"SELECT SEARCH(data_to_search, 'search_query', json_scope => 'JSON_KEYS_AND_VALUES')\"\n        )\n        self.validate_identity(\n            \"SELECT SEARCH(data_to_search, 'search_query', analyzer => 'PATTERN_ANALYZER')\"\n        )\n        self.validate_identity(\n            \"SELECT SEARCH(data_to_search, 'search_query', analyzer_options => 'analyzer_options_values')\"\n        )\n        self.validate_identity(\n            \"SELECT SEARCH(data_to_search, 'search_query', json_scope => 'JSON_VALUES', analyzer => 'LOG_ANALYZER')\"\n        )\n        self.validate_identity(\n            \"SELECT SEARCH(data_to_search, 'search_query', analyzer => 'PATTERN_ANALYZER', analyzer_options => 'options')\"\n        )\n\n        self.validate_identity(\"ARRAY_AGG(x IGNORE NULLS LIMIT 1)\")\n        self.validate_identity(\"ARRAY_AGG(x IGNORE NULLS ORDER BY x LIMIT 1)\")\n        self.validate_identity(\"ARRAY_AGG(DISTINCT x IGNORE NULLS ORDER BY x LIMIT 1)\")\n        self.validate_identity(\"ARRAY_AGG(x IGNORE NULLS)\")\n        self.validate_identity(\"ARRAY_AGG(DISTINCT x IGNORE NULLS HAVING MAX x ORDER BY x LIMIT 1)\")\n        self.validate_identity(\"SELECT * FROM dataset.my_table TABLESAMPLE SYSTEM (10 PERCENT)\")\n        self.validate_identity(\"TIME('2008-12-25 15:30:00+08')\")\n        self.validate_identity(\"TIME('2008-12-25 15:30:00+08', 'America/Los_Angeles')\")\n        self.validate_identity(r\"SELECT '\\n\\r\\a\\v\\f\\t'\")\n        self.validate_identity(\"SELECT * FROM tbl FOR SYSTEM_TIME AS OF z\")\n        self.validate_identity(\"SELECT PARSE_TIMESTAMP('%c', 'Thu Dec 25 07:30:00 2008', 'UTC')\")\n        self.validate_identity(\"SELECT ANY_VALUE(fruit HAVING MAX sold) FROM fruits\")\n        self.validate_identity(\"SELECT ANY_VALUE(fruit HAVING MIN sold) FROM fruits\")\n        self.validate_all(\n            \"SELECT ANY_VALUE(fruit HAVING MAX sold) FROM Store\",\n            write={\n                \"bigquery\": \"SELECT ANY_VALUE(fruit HAVING MAX sold) FROM Store\",\n                \"duckdb\": \"SELECT ARG_MAX_NULL(fruit, sold) FROM Store\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ANY_VALUE(fruit HAVING MIN sold) FROM Store\",\n            write={\n                \"bigquery\": \"SELECT ANY_VALUE(fruit HAVING MIN sold) FROM Store\",\n                \"duckdb\": \"SELECT ARG_MIN_NULL(fruit, sold) FROM Store\",\n            },\n        )\n        self.validate_all(\n            \"SELECT category, ANY_VALUE(product HAVING MAX price), ANY_VALUE(product HAVING MIN cost), ANY_VALUE(supplier) FROM products GROUP BY category\",\n            write={\n                \"bigquery\": \"SELECT category, ANY_VALUE(product HAVING MAX price), ANY_VALUE(product HAVING MIN cost), ANY_VALUE(supplier) FROM products GROUP BY category\",\n                \"duckdb\": \"SELECT category, ARG_MAX_NULL(product, price), ARG_MIN_NULL(product, cost), ANY_VALUE(supplier) FROM products GROUP BY category\",\n            },\n        )\n        self.validate_all(\n            'WITH data AS (SELECT \"A\" AS fruit, 20 AS sold UNION ALL SELECT NULL AS fruit, 25 AS sold) SELECT ANY_VALUE(fruit HAVING MAX sold) FROM data',\n            write={\n                \"duckdb\": \"WITH data AS (SELECT 'A' AS fruit, 20 AS sold UNION ALL SELECT NULL AS fruit, 25 AS sold) SELECT ARG_MAX_NULL(fruit, sold) FROM data\",\n            },\n        )\n        self.validate_identity(\"SELECT `project-id`.udfs.func(call.dir)\")\n        self.validate_identity(\"SELECT CAST(CURRENT_DATE AS STRING FORMAT 'DAY') AS current_day\")\n        self.validate_identity(\"SAFE_CAST(encrypted_value AS STRING FORMAT 'BASE64')\")\n        self.validate_identity(\"CAST(encrypted_value AS STRING FORMAT 'BASE64')\")\n        self.validate_identity(\"DATE(2016, 12, 25)\")\n        self.validate_identity(\"DATE(CAST('2016-12-25 23:59:59' AS DATETIME))\")\n        self.validate_identity(\"SELECT foo IN UNNEST(bar) AS bla\")\n        self.validate_identity(\"SELECT * FROM x-0.a\")\n        self.validate_identity(\"SELECT * FROM pivot CROSS JOIN foo\")\n        self.validate_identity(\"SAFE_CAST(x AS STRING)\")\n        self.validate_identity(\"SELECT * FROM a-b-c.mydataset.mytable\")\n        self.validate_identity(\"SELECT * FROM abc-def-ghi\")\n        self.validate_identity(\"SELECT * FROM a-b-c\")\n        self.validate_identity(\"SELECT * FROM my-table\")\n        self.validate_identity(\"SELECT * FROM my-project.mydataset.mytable\")\n        self.validate_identity(\"SELECT * FROM pro-ject_id.c.d CROSS JOIN foo-bar\")\n        self.validate_identity(\"SELECT * FROM foo.bar.25\", \"SELECT * FROM foo.bar.`25`\")\n        self.validate_identity(\"SELECT * FROM foo.bar.25_\", \"SELECT * FROM foo.bar.`25_`\")\n        self.validate_identity(\"SELECT * FROM foo.bar.25x a\", \"SELECT * FROM foo.bar.`25x` AS a\")\n        self.validate_identity(\"SELECT * FROM foo.bar.25ab c\", \"SELECT * FROM foo.bar.`25ab` AS c\")\n        self.validate_identity(\"x <> ''\")\n        self.validate_identity(\"DATE_TRUNC(col, WEEK(MONDAY))\")\n        self.validate_identity(\"DATE_TRUNC(col, MONTH, 'UTC+8')\")\n        self.validate_identity(\"SELECT b'abc'\")\n        self.validate_identity(\"SELECT AS STRUCT 1 AS a, 2 AS b\")\n        self.validate_identity(\"SELECT DISTINCT AS STRUCT 1 AS a, 2 AS b\")\n        self.validate_identity(\"SELECT AS VALUE STRUCT(1 AS a, 2 AS b)\")\n        self.validate_identity(\"SELECT * FROM q UNPIVOT(values FOR quarter IN (b, c))\")\n        self.validate_identity(\"\"\"CREATE TABLE x (a STRUCT<values ARRAY<INT64>>)\"\"\")\n        self.validate_identity(\"\"\"CREATE TABLE x (a STRUCT<b STRING OPTIONS (description='b')>)\"\"\")\n        self.validate_identity(\"CAST(x AS TIMESTAMP)\")\n        self.validate_identity(\"BEGIN DECLARE y INT64\", check_command_warning=True)\n        self.validate_identity(\"LOOP SET x = x + 1\", check_command_warning=True)\n        self.validate_identity(\"REPEAT SET x = x + 1\", check_command_warning=True)\n        self.validate_identity(\"SELECT MAKE_INTERVAL(100, 11, 1, 12, 30, 10)\")\n        self.validate_identity(\n            \"WHILE i < ARRAY_LENGTH(batches) DO SET x = batches[OFFSET(i)]\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\"BEGIN TRANSACTION\")\n        self.validate_identity(\"COMMIT TRANSACTION\")\n        self.validate_identity(\"ROLLBACK TRANSACTION\")\n        self.validate_identity(\"CAST(x AS BIGNUMERIC)\")\n        self.validate_identity(\"SELECT y + 1 FROM x GROUP BY y + 1 ORDER BY 1\")\n        self.validate_identity(\"SELECT TIMESTAMP_SECONDS(2) AS t\")\n        self.validate_identity(\"SELECT TIMESTAMP_MILLIS(2) AS t\")\n        self.validate_identity(\"UPDATE x SET y = NULL\")\n        self.validate_identity(\"LOG(n, b)\")\n        self.validate_identity(\"SELECT COUNT(x RESPECT NULLS)\")\n        self.validate_identity(\"SELECT LAST_VALUE(x IGNORE NULLS) OVER y AS x\")\n        self.validate_identity(\"SELECT ARRAY((SELECT AS STRUCT 1 AS a, 2 AS b))\")\n        self.validate_identity(\"SELECT ARRAY((SELECT AS STRUCT 1 AS a, 2 AS b) LIMIT 10)\")\n        self.validate_identity(\"CAST(x AS CHAR)\", \"CAST(x AS STRING)\")\n        self.validate_identity(\"CAST(x AS NCHAR)\", \"CAST(x AS STRING)\")\n        self.validate_identity(\"CAST(x AS NVARCHAR)\", \"CAST(x AS STRING)\")\n        self.validate_identity(\"CAST(x AS TIMESTAMPTZ)\", \"CAST(x AS TIMESTAMP)\")\n        self.validate_identity(\"CAST(x AS RECORD)\", \"CAST(x AS STRUCT)\")\n        self.validate_identity(\"SELECT * FROM x WHERE x.y >= (SELECT MAX(a) FROM b-c) - 20\")\n        self.validate_identity(\n            \"\"\"WITH t AS (SELECT '{\"x-y\": \"z\"}' AS c) SELECT JSON_EXTRACT(c, '$.x-y') FROM t\"\"\"\n        ).selects[0].expression.assert_is(exp.JSONPath)\n        self.validate_identity(\n            \"SELECT FORMAT_TIMESTAMP('%F %T', CURRENT_TIMESTAMP(), 'Europe/Berlin') AS ts\"\n        )\n        self.validate_identity(\n            \"SELECT cars, apples FROM some_table PIVOT(SUM(total_counts) FOR products IN ('general.cars' AS cars, 'food.apples' AS apples))\"\n        )\n        self.validate_identity(\n            \"MERGE INTO dataset.NewArrivals USING (SELECT * FROM UNNEST([('microwave', 10, 'warehouse #1'), ('dryer', 30, 'warehouse #1'), ('oven', 20, 'warehouse #2')])) ON FALSE WHEN NOT MATCHED THEN INSERT ROW WHEN NOT MATCHED BY SOURCE THEN DELETE\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM test QUALIFY a IS DISTINCT FROM b WINDOW c AS (PARTITION BY d)\"\n        )\n        self.validate_identity(\n            \"FOR record IN (SELECT word, word_count FROM bigquery-public-data.samples.shakespeare LIMIT 5) DO SELECT record.word, record.word_count\"\n        )\n        self.validate_identity(\n            \"DATE(CAST('2016-12-25 05:30:00+07' AS DATETIME), 'America/Los_Angeles')\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE TABLE x (a STRING OPTIONS (description='x')) OPTIONS (table_expiration_days=1)\"\"\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM (SELECT * FROM `t`) AS a UNPIVOT((c) FOR c_name IN (v1, v2))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE IF NOT EXISTS foo AS SELECT * FROM bla EXCEPT DISTINCT (SELECT * FROM bar) LIMIT 0\"\n        )\n        self.validate_identity(\n            \"SELECT ROW() OVER (y ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM x WINDOW y AS (PARTITION BY CATEGORY)\"\n        )\n        self.validate_identity(\n            \"SELECT item, purchases, LAST_VALUE(item) OVER (item_window ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS most_popular FROM Produce WINDOW item_window AS (ORDER BY purchases)\"\n        )\n        self.validate_identity(\n            \"SELECT LAST_VALUE(a IGNORE NULLS) OVER y FROM x WINDOW y AS (PARTITION BY CATEGORY)\",\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE VIEW test (tenant_id OPTIONS (description='Test description on table creation')) AS SELECT 1 AS tenant_id, 1 AS customer_id\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM foo AS t0 FOR SYSTEM_TIME AS OF '2026-02-12T23:22:21.743416+00:00'\",\n        )\n        self.validate_identity(\n            '''SELECT b\"\\\\x0a$'x'00\"''',\n            \"\"\"SELECT b'\\\\x0a$\\\\'x\\\\'00'\"\"\",\n        )\n        self.validate_identity(\n            \"--c\\nARRAY_AGG(v IGNORE NULLS)\",\n            \"ARRAY_AGG(v IGNORE NULLS) /* c */\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1, t2\",\n            \"SELECT * FROM t1 CROSS JOIN t2\",\n        )\n        self.validate_identity(\n            'SELECT r\"\\\\t\"',\n            \"SELECT '\\\\\\\\t'\",\n        )\n        self.validate_identity(\n            \"ARRAY(SELECT AS STRUCT e.x AS y, e.z AS bla FROM UNNEST(bob))::ARRAY<STRUCT<y STRING, bro NUMERIC>>\",\n            \"CAST(ARRAY(SELECT AS STRUCT e.x AS y, e.z AS bla FROM UNNEST(bob)) AS ARRAY<STRUCT<y STRING, bro NUMERIC>>)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM `proj.dataset.INFORMATION_SCHEMA.SOME_VIEW`\",\n            \"SELECT * FROM `proj.dataset.INFORMATION_SCHEMA.SOME_VIEW` AS `proj.dataset.INFORMATION_SCHEMA.SOME_VIEW`\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM region_or_dataset.INFORMATION_SCHEMA.TABLES\",\n            \"SELECT * FROM region_or_dataset.`INFORMATION_SCHEMA.TABLES` AS TABLES\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM region_or_dataset.INFORMATION_SCHEMA.TABLES AS some_name\",\n            \"SELECT * FROM region_or_dataset.`INFORMATION_SCHEMA.TABLES` AS some_name\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM proj.region_or_dataset.INFORMATION_SCHEMA.TABLES\",\n            \"SELECT * FROM proj.region_or_dataset.`INFORMATION_SCHEMA.TABLES` AS TABLES\",\n        )\n        self.validate_identity(\n            \"CREATE VIEW `d.v` OPTIONS (expiration_timestamp=TIMESTAMP '2020-01-02T04:05:06.007Z') AS SELECT 1 AS c\",\n            \"CREATE VIEW `d.v` OPTIONS (expiration_timestamp=CAST('2020-01-02T04:05:06.007Z' AS TIMESTAMP)) AS SELECT 1 AS c\",\n        )\n        self.validate_identity(\n            \"SELECT ARRAY(SELECT AS STRUCT 1 a, 2 b)\",\n            \"SELECT ARRAY(SELECT AS STRUCT 1 AS a, 2 AS b)\",\n        )\n        self.validate_identity(\n            \"select array_contains([1, 2, 3], 1)\",\n            \"SELECT EXISTS(SELECT 1 FROM UNNEST([1, 2, 3]) AS _col WHERE _col = 1)\",\n        )\n        self.validate_identity(\n            \"SELECT SPLIT(foo)\",\n            \"SELECT SPLIT(foo, ',')\",\n        )\n        self.validate_identity(\n            \"SELECT 1 AS hash\",\n            \"SELECT 1 AS `hash`\",\n        )\n        self.validate_identity(\n            \"SELECT 1 AS at\",\n            \"SELECT 1 AS `at`\",\n        )\n        self.validate_identity(\n            'x <> \"\"',\n            \"x <> ''\",\n        )\n        self.validate_identity(\n            'x <> \"\"\"\"\"\"',\n            \"x <> ''\",\n        )\n        self.validate_identity(\n            \"x <> ''''''\",\n            \"x <> ''\",\n        )\n        self.validate_identity(\n            \"SELECT a overlaps\",\n            \"SELECT a AS overlaps\",\n        )\n        self.validate_identity(\n            \"SELECT y + 1 z FROM x GROUP BY y + 1 ORDER BY z\",\n            \"SELECT y + 1 AS z FROM x GROUP BY z ORDER BY z\",\n        )\n        self.validate_identity(\n            \"SELECT y + 1 z FROM x GROUP BY y + 1\",\n            \"SELECT y + 1 AS z FROM x GROUP BY y + 1\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT JSON '\"foo\"' AS json_data\"\"\",\n            \"\"\"SELECT PARSE_JSON('\"foo\"') AS json_data\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM (SELECT a, b, c FROM test) PIVOT(SUM(b) d, COUNT(*) e FOR c IN ('x', 'y'))\",\n            \"SELECT * FROM (SELECT a, b, c FROM test) PIVOT(SUM(b) AS d, COUNT(*) AS e FOR c IN ('x', 'y'))\",\n        )\n        self.validate_identity(\n            \"SELECT CAST(1 AS BYTEINT)\",\n            \"SELECT CAST(1 AS INT64)\",\n        )\n        self.validate_identity(\n            \"\"\"CREATE TEMPORARY FUNCTION FOO()\nRETURNS STRING\nLANGUAGE js AS\n'return \"Hello world!\"'\"\"\",\n            pretty=True,\n        )\n        self.validate_identity(\n            \"[a, a(1, 2,3,4444444444444444, tttttaoeunthaoentuhaoentuheoantu, toheuntaoheutnahoeunteoahuntaoeh), b(3, 4,5), c, d, tttttttttttttttteeeeeeeeeeeeeett, 12312312312]\",\n            \"\"\"[\n  a,\n  a(\n    1,\n    2,\n    3,\n    4444444444444444,\n    tttttaoeunthaoentuhaoentuheoantu,\n    toheuntaoheutnahoeunteoahuntaoeh\n  ),\n  b(3, 4, 5),\n  c,\n  d,\n  tttttttttttttttteeeeeeeeeeeeeett,\n  12312312312\n]\"\"\",\n            pretty=True,\n        )\n\n        self.validate_all(\n            \"SELECT TRUE IS TRUE\",\n            write={\n                \"bigquery\": \"SELECT TRUE IS TRUE\",\n                \"snowflake\": \"SELECT TRUE\",\n            },\n        )\n        self.validate_all(\n            \"SELECT REPEAT(' ', 2)\",\n            read={\n                \"hive\": \"SELECT SPACE(2)\",\n                \"spark\": \"SELECT SPACE(2)\",\n                \"databricks\": \"SELECT SPACE(2)\",\n                \"trino\": \"SELECT REPEAT(' ', 2)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n            write={\n                \"bigquery\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"clickhouse\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases NULLS FIRST ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"databricks\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"duckdb\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases NULLS FIRST ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"mysql\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"oracle\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases NULLS FIRST ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"postgres\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases NULLS FIRST ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"presto\": \"SELECT purchases, LAST_VALUE(item) OVER (PARTITION BY purchases ORDER BY purchases NULLS FIRST ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS most_popular FROM Produce\",\n                \"redshift\": \"SELECT purchases, LAST_VALUE(item) OVER (PARTITION BY purchases ORDER BY purchases NULLS FIRST ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS most_popular FROM Produce\",\n                \"snowflake\": \"SELECT purchases, LAST_VALUE(item) OVER (PARTITION BY purchases ORDER BY purchases NULLS FIRST ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS most_popular FROM Produce\",\n                \"spark\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"trino\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases NULLS FIRST ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n                \"tsql\": \"SELECT purchases, LAST_VALUE(item) OVER item_window AS most_popular FROM Produce WINDOW item_window AS (PARTITION BY purchases ORDER BY purchases ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE(2024, 1, 15)\",\n            write={\n                \"bigquery\": \"SELECT DATE(2024, 1, 15)\",\n                \"duckdb\": \"SELECT MAKE_DATE(2024, 1, 15)\",\n            },\n        )\n        self.validate_all(\n            \"EXTRACT(HOUR FROM DATETIME(2008, 12, 25, 15, 30, 00))\",\n            write={\n                \"bigquery\": \"EXTRACT(HOUR FROM DATETIME(2008, 12, 25, 15, 30, 00))\",\n                \"duckdb\": \"EXTRACT(HOUR FROM MAKE_TIMESTAMP(2008, 12, 25, 15, 30, 00))\",\n                \"snowflake\": \"DATE_PART(HOUR, TIMESTAMP_FROM_PARTS(2008, 12, 25, 15, 30, 00))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT STRUCT(1, 2, 3), STRUCT(), STRUCT('abc'), STRUCT(1, t.str_col), STRUCT(1 as a, 'abc' AS b), STRUCT(str_col AS abc)\",\n            write={\n                \"bigquery\": \"SELECT STRUCT(1, 2, 3), STRUCT(), STRUCT('abc'), STRUCT(1, t.str_col), STRUCT(1 AS a, 'abc' AS b), STRUCT(str_col AS abc)\",\n                \"duckdb\": \"SELECT {'_0': 1, '_1': 2, '_2': 3}, {}, {'_0': 'abc'}, {'_0': 1, 'str_col': t.str_col}, {'a': 1, 'b': 'abc'}, {'abc': str_col}\",\n                \"hive\": \"SELECT STRUCT(1, 2, 3), STRUCT(), STRUCT('abc'), STRUCT(1, t.str_col), STRUCT(1, 'abc'), STRUCT(str_col)\",\n                \"spark2\": \"SELECT STRUCT(1, 2, 3), STRUCT(), STRUCT('abc'), STRUCT(1, t.str_col), STRUCT(1 AS a, 'abc' AS b), STRUCT(str_col AS abc)\",\n                \"spark\": \"SELECT STRUCT(1, 2, 3), STRUCT(), STRUCT('abc'), STRUCT(1, t.str_col), STRUCT(1 AS a, 'abc' AS b), STRUCT(str_col AS abc)\",\n                \"snowflake\": \"SELECT OBJECT_CONSTRUCT('_0', 1, '_1', 2, '_2', 3), OBJECT_CONSTRUCT(), OBJECT_CONSTRUCT('_0', 'abc'), OBJECT_CONSTRUCT('_0', 1, '_1', t.str_col), OBJECT_CONSTRUCT('a', 1, 'b', 'abc'), OBJECT_CONSTRUCT('abc', str_col)\",\n                # fallback to unnamed without type inference\n                \"trino\": \"SELECT ROW(1, 2, 3), ROW(), ROW('abc'), ROW(1, t.str_col), CAST(ROW(1, 'abc') AS ROW(a INTEGER, b VARCHAR)), ROW(str_col)\",\n            },\n        )\n        self.validate_all(\n            \"PARSE_TIMESTAMP('%Y-%m-%dT%H:%M:%E6S%z', x)\",\n            write={\n                \"bigquery\": \"PARSE_TIMESTAMP('%FT%H:%M:%E6S%z', x)\",\n                \"duckdb\": \"STRPTIME(x, '%Y-%m-%dT%H:%M:%S.%f%z')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_SUB(CURRENT_DATE(), INTERVAL 2 DAY)\",\n            write={\n                \"bigquery\": \"SELECT DATE_SUB(CURRENT_DATE, INTERVAL '2' DAY)\",\n                \"databricks\": \"SELECT DATE_ADD(CURRENT_DATE, -2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_SUB(DATE '2008-12-25', INTERVAL 5 DAY)\",\n            write={\n                \"bigquery\": \"SELECT DATE_SUB(CAST('2008-12-25' AS DATE), INTERVAL '5' DAY)\",\n                \"duckdb\": \"SELECT CAST('2008-12-25' AS DATE) - INTERVAL '5' DAY\",\n                \"snowflake\": \"SELECT DATEADD(DAY, '5' * -1, CAST('2008-12-25' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"EDIT_DISTANCE(col1, col2, max_distance => 3)\",\n            write={\n                \"bigquery\": \"EDIT_DISTANCE(col1, col2, max_distance => 3)\",\n                \"clickhouse\": UnsupportedError,\n                \"databricks\": UnsupportedError,\n                \"drill\": UnsupportedError,\n                \"duckdb\": \"CASE WHEN LEVENSHTEIN(col1, col2) IS NULL OR 3 IS NULL THEN NULL ELSE LEAST(LEVENSHTEIN(col1, col2), 3) END\",\n                \"hive\": UnsupportedError,\n                \"postgres\": \"LEVENSHTEIN_LESS_EQUAL(col1, col2, 3)\",\n                \"presto\": UnsupportedError,\n                \"snowflake\": \"EDITDISTANCE(col1, col2, 3)\",\n                \"spark\": UnsupportedError,\n                \"spark2\": UnsupportedError,\n                \"sqlite\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"EDIT_DISTANCE(a, b)\",\n            write={\n                \"bigquery\": \"EDIT_DISTANCE(a, b)\",\n                \"duckdb\": \"LEVENSHTEIN(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"SAFE_CAST(some_date AS DATE FORMAT 'DD MONTH YYYY')\",\n            write={\n                \"bigquery\": \"SAFE_CAST(some_date AS DATE FORMAT 'DD MONTH YYYY')\",\n                \"duckdb\": \"CAST(TRY_STRPTIME(some_date, '%d %B %Y') AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SAFE_CAST(some_date AS DATE FORMAT 'YYYY-MM-DD') AS some_date\",\n            write={\n                \"bigquery\": \"SAFE_CAST(some_date AS DATE FORMAT 'YYYY-MM-DD') AS some_date\",\n                \"duckdb\": \"CAST(TRY_STRPTIME(some_date, '%Y-%m-%d') AS DATE) AS some_date\",\n            },\n        )\n        self.validate_all(\n            \"SAFE_CAST(x AS TIMESTAMP)\",\n            write={\n                \"bigquery\": \"SAFE_CAST(x AS TIMESTAMP)\",\n                \"snowflake\": \"CAST(x AS TIMESTAMPTZ)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT t.c1, h.c2, s.c3 FROM t1 AS t, UNNEST(t.t2) AS h, UNNEST(h.t3) AS s\",\n            write={\n                \"bigquery\": \"SELECT t.c1, h.c2, s.c3 FROM t1 AS t CROSS JOIN UNNEST(t.t2) AS h CROSS JOIN UNNEST(h.t3) AS s\",\n                \"duckdb\": \"SELECT t.c1, h.c2, s.c3 FROM t1 AS t CROSS JOIN UNNEST(t.t2) AS _t0(h) CROSS JOIN UNNEST(h.t3) AS _t1(s)\",\n            },\n        )\n        self.validate_all(\n            \"PARSE_TIMESTAMP('%Y-%m-%dT%H:%M:%E6S%z', x)\",\n            write={\n                \"bigquery\": \"PARSE_TIMESTAMP('%FT%H:%M:%E6S%z', x)\",\n                \"duckdb\": \"STRPTIME(x, '%Y-%m-%dT%H:%M:%S.%f%z')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT results FROM Coordinates, Coordinates.position AS results\",\n            write={\n                \"bigquery\": \"SELECT results FROM Coordinates CROSS JOIN UNNEST(Coordinates.position) AS results\",\n                \"presto\": \"SELECT results FROM Coordinates CROSS JOIN UNNEST(Coordinates.position) AS _t0(results)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT results FROM Coordinates, `Coordinates.position` AS results\",\n            write={\n                \"bigquery\": \"SELECT results FROM Coordinates CROSS JOIN `Coordinates.position` AS results\",\n                \"presto\": 'SELECT results FROM Coordinates CROSS JOIN \"Coordinates\".\"position\" AS results',\n            },\n        )\n        self.validate_all(\n            \"SELECT results FROM Coordinates AS c, UNNEST(c.position) AS results\",\n            read={\n                \"presto\": \"SELECT results FROM Coordinates AS c, UNNEST(c.position) AS _t(results)\",\n                \"redshift\": \"SELECT results FROM Coordinates AS c, c.position AS results\",\n            },\n            write={\n                \"bigquery\": \"SELECT results FROM Coordinates AS c CROSS JOIN UNNEST(c.position) AS results\",\n                \"presto\": \"SELECT results FROM Coordinates AS c CROSS JOIN UNNEST(c.position) AS _t0(results)\",\n                \"redshift\": \"SELECT results FROM Coordinates AS c CROSS JOIN c.position AS results\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP(x)\",\n            write={\n                \"bigquery\": \"TIMESTAMP(x)\",\n                \"duckdb\": \"CAST(x AS TIMESTAMPTZ)\",\n                \"snowflake\": \"CAST(x AS TIMESTAMPTZ)\",\n                \"presto\": \"CAST(x AS TIMESTAMP WITH TIME ZONE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMP('2008-12-25 15:30:00', 'America/Los_Angeles')\",\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP('2008-12-25 15:30:00', 'America/Los_Angeles')\",\n                \"duckdb\": \"SELECT CAST('2008-12-25 15:30:00' AS TIMESTAMP) AT TIME ZONE 'America/Los_Angeles'\",\n                \"snowflake\": \"SELECT CONVERT_TIMEZONE('America/Los_Angeles', CAST('2008-12-25 15:30:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SUM(x IGNORE NULLS) AS x\",\n            read={\n                \"bigquery\": \"SELECT SUM(x IGNORE NULLS) AS x\",\n                \"duckdb\": \"SELECT SUM(x IGNORE NULLS) AS x\",\n                \"spark\": \"SELECT SUM(x) IGNORE NULLS AS x\",\n                \"snowflake\": \"SELECT SUM(x) IGNORE NULLS AS x\",\n            },\n            write={\n                \"bigquery\": \"SELECT SUM(x IGNORE NULLS) AS x\",\n                \"duckdb\": \"SELECT SUM(x) AS x\",\n                \"postgres\": UnsupportedError,\n                \"spark\": \"SELECT SUM(x) IGNORE NULLS AS x\",\n                \"snowflake\": \"SELECT SUM(x) IGNORE NULLS AS x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SUM(x RESPECT NULLS) AS x\",\n            read={\n                \"bigquery\": \"SELECT SUM(x RESPECT NULLS) AS x\",\n                \"spark\": \"SELECT SUM(x) RESPECT NULLS AS x\",\n                \"snowflake\": \"SELECT SUM(x) RESPECT NULLS AS x\",\n            },\n            write={\n                \"bigquery\": \"SELECT SUM(x RESPECT NULLS) AS x\",\n                \"duckdb\": \"SELECT SUM(x) AS x\",\n                \"postgres\": UnsupportedError,\n                \"spark\": \"SELECT SUM(x) RESPECT NULLS AS x\",\n                \"snowflake\": \"SELECT SUM(x) RESPECT NULLS AS x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT PERCENTILE_CONT(x, 0.5 RESPECT NULLS) OVER ()\",\n            write={\n                \"bigquery\": \"SELECT PERCENTILE_CONT(x, 0.5 RESPECT NULLS) OVER ()\",\n                \"duckdb\": \"SELECT QUANTILE_CONT(x, 0.5) OVER ()\",\n                \"spark\": \"SELECT PERCENTILE_CONT(x, 0.5) RESPECT NULLS OVER ()\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_AGG(DISTINCT x IGNORE NULLS ORDER BY a, b DESC LIMIT 10) AS x\",\n            write={\n                \"bigquery\": \"SELECT ARRAY_AGG(DISTINCT x IGNORE NULLS ORDER BY a, b DESC LIMIT 10) AS x\",\n                \"duckdb\": \"SELECT ARRAY_AGG(DISTINCT x ORDER BY a NULLS FIRST, b DESC LIMIT 10) AS x\",\n                \"spark\": \"SELECT COLLECT_LIST(DISTINCT x ORDER BY a, b DESC LIMIT 10) IGNORE NULLS AS x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_AGG(DISTINCT x IGNORE NULLS ORDER BY a, b DESC LIMIT 1, 10) AS x\",\n            write={\n                \"bigquery\": \"SELECT ARRAY_AGG(DISTINCT x IGNORE NULLS ORDER BY a, b DESC LIMIT 1, 10) AS x\",\n                \"duckdb\": \"SELECT ARRAY_AGG(DISTINCT x ORDER BY a NULLS FIRST, b DESC LIMIT 1, 10) AS x\",\n                \"spark\": \"SELECT COLLECT_LIST(DISTINCT x ORDER BY a, b DESC LIMIT 1, 10) IGNORE NULLS AS x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM Produce UNPIVOT((first_half_sales, second_half_sales) FOR semesters IN ((Q1, Q2) AS 'semester_1', (Q3, Q4) AS 'semester_2'))\",\n            read={\n                \"spark\": \"SELECT * FROM Produce UNPIVOT((first_half_sales, second_half_sales) FOR semesters IN ((Q1, Q2) AS semester_1, (Q3, Q4) AS semester_2))\",\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM Produce UNPIVOT((first_half_sales, second_half_sales) FOR semesters IN ((Q1, Q2) AS 'semester_1', (Q3, Q4) AS 'semester_2'))\",\n                \"spark\": \"SELECT * FROM Produce UNPIVOT((first_half_sales, second_half_sales) FOR semesters IN ((Q1, Q2) AS semester_1, (Q3, Q4) AS semester_2))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM Produce UNPIVOT((first_half_sales, second_half_sales) FOR semesters IN ((Q1, Q2) AS 1, (Q3, Q4) AS 2))\",\n            write={\n                \"bigquery\": \"SELECT * FROM Produce UNPIVOT((first_half_sales, second_half_sales) FOR semesters IN ((Q1, Q2) AS 1, (Q3, Q4) AS 2))\",\n                \"spark\": \"SELECT * FROM Produce UNPIVOT((first_half_sales, second_half_sales) FOR semesters IN ((Q1, Q2) AS `1`, (Q3, Q4) AS `2`))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNIX_DATE(DATE '2008-12-25')\",\n            write={\n                \"bigquery\": \"SELECT UNIX_DATE(CAST('2008-12-25' AS DATE))\",\n                \"duckdb\": \"SELECT DATE_DIFF('DAY', CAST('1970-01-01' AS DATE), CAST('2008-12-25' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), MONTH)\",\n            read={\n                \"snowflake\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), MONS)\",\n            },\n            write={\n                \"bigquery\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), MONTH)\",\n                \"duckdb\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE))\",\n                \"clickhouse\": \"SELECT LAST_DAY(CAST('2008-11-25' AS Nullable(DATE)))\",\n                \"mysql\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE))\",\n                \"oracle\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE))\",\n                \"postgres\": \"SELECT CAST(DATE_TRUNC('MONTH', CAST('2008-11-25' AS DATE)) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)\",\n                \"presto\": \"SELECT LAST_DAY_OF_MONTH(CAST('2008-11-25' AS DATE))\",\n                \"redshift\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE))\",\n                \"snowflake\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), MONTH)\",\n                \"spark\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE))\",\n                \"tsql\": \"SELECT EOMONTH(CAST('2008-11-25' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), QUARTER)\",\n            read={\n                \"snowflake\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), QUARTER)\",\n            },\n            write={\n                \"bigquery\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), QUARTER)\",\n                \"snowflake\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), QUARTER)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS DATETIME)\",\n            read={\n                \"\": \"x::timestamp\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME(15, 30, 00)\",\n            read={\n                \"duckdb\": \"SELECT MAKE_TIME(15, 30, 00)\",\n                \"mysql\": \"SELECT MAKETIME(15, 30, 00)\",\n                \"postgres\": \"SELECT MAKE_TIME(15, 30, 00)\",\n                \"snowflake\": \"SELECT TIME_FROM_PARTS(15, 30, 00)\",\n            },\n            write={\n                \"bigquery\": \"SELECT TIME(15, 30, 00)\",\n                \"duckdb\": \"SELECT MAKE_TIME(15, 30, 00)\",\n                \"mysql\": \"SELECT MAKETIME(15, 30, 00)\",\n                \"postgres\": \"SELECT MAKE_TIME(15, 30, 00)\",\n                \"snowflake\": \"SELECT TIME_FROM_PARTS(15, 30, 00)\",\n                \"tsql\": \"SELECT TIMEFROMPARTS(15, 30, 00, 0, 0)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME('2008-12-25 15:30:00')\",\n            write={\n                \"bigquery\": \"SELECT TIME('2008-12-25 15:30:00')\",\n                \"duckdb\": \"SELECT CAST('2008-12-25 15:30:00' AS TIME)\",\n                \"mysql\": \"SELECT CAST('2008-12-25 15:30:00' AS TIME)\",\n                \"postgres\": \"SELECT CAST('2008-12-25 15:30:00' AS TIME)\",\n                \"redshift\": \"SELECT CAST('2008-12-25 15:30:00' AS TIME)\",\n                \"spark\": \"SELECT CAST('2008-12-25 15:30:00' AS TIMESTAMP)\",\n                \"tsql\": \"SELECT CAST('2008-12-25 15:30:00' AS TIME)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT COUNTIF(x)\",\n            read={\n                \"clickhouse\": \"SELECT countIf(x)\",\n                \"duckdb\": \"SELECT COUNT_IF(x)\",\n            },\n            write={\n                \"bigquery\": \"SELECT COUNTIF(x)\",\n                \"clickhouse\": \"SELECT countIf(x)\",\n                \"duckdb\": \"SELECT COUNT_IF(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMP_DIFF(TIMESTAMP_SECONDS(60), TIMESTAMP_SECONDS(0), minute)\",\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_DIFF(TIMESTAMP_SECONDS(60), TIMESTAMP_SECONDS(0), MINUTE)\",\n                \"databricks\": \"SELECT TIMESTAMPDIFF(MINUTE, CAST(FROM_UNIXTIME(0) AS TIMESTAMP), CAST(FROM_UNIXTIME(60) AS TIMESTAMP))\",\n                \"duckdb\": \"SELECT DATE_DIFF('MINUTE', TO_TIMESTAMP(0), TO_TIMESTAMP(60))\",\n                \"snowflake\": \"SELECT TIMESTAMPDIFF(MINUTE, TO_TIMESTAMP(0), TO_TIMESTAMP(60))\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP_DIFF(a, b, MONTH)\",\n            read={\n                \"bigquery\": \"TIMESTAMP_DIFF(a, b, month)\",\n                \"databricks\": \"TIMESTAMPDIFF(month, b, a)\",\n                \"mysql\": \"TIMESTAMPDIFF(month, b, a)\",\n            },\n            write={\n                \"databricks\": \"TIMESTAMPDIFF(MONTH, b, a)\",\n                \"mysql\": \"TIMESTAMPDIFF(MONTH, b, a)\",\n                \"snowflake\": \"TIMESTAMPDIFF(MONTH, b, a)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT TIMESTAMP_MICROS(x)\",\n            read={\n                \"duckdb\": \"SELECT MAKE_TIMESTAMP(x)\",\n                \"spark\": \"SELECT TIMESTAMP_MICROS(x)\",\n            },\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_MICROS(x)\",\n                \"duckdb\": \"SELECT MAKE_TIMESTAMP(x)\",\n                \"snowflake\": \"SELECT TO_TIMESTAMP(x, 6)\",\n                \"spark\": \"SELECT TIMESTAMP_MICROS(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t WHERE EXISTS(SELECT * FROM unnest(nums) AS x WHERE x > 1)\",\n            write={\n                \"bigquery\": \"SELECT * FROM t WHERE EXISTS(SELECT * FROM UNNEST(nums) AS x WHERE x > 1)\",\n                \"duckdb\": \"SELECT * FROM t WHERE EXISTS(SELECT * FROM UNNEST(nums) AS _t0(x) WHERE x > 1)\",\n            },\n        )\n        self.validate_all(\n            \"NULL\",\n            read={\n                \"duckdb\": \"NULL = a\",\n                \"postgres\": \"a = NULL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '\\\\n'\",\n            read={\n                \"bigquery\": \"SELECT '''\\n'''\",\n            },\n            write={\n                \"bigquery\": \"SELECT '\\\\n'\",\n                \"postgres\": \"SELECT '\\n'\",\n            },\n        )\n        self.validate_all(\n            \"TRIM(item, '*')\",\n            read={\n                \"snowflake\": \"TRIM(item, '*')\",\n                \"spark\": \"TRIM('*', item)\",\n            },\n            write={\n                \"bigquery\": \"TRIM(item, '*')\",\n                \"snowflake\": \"TRIM(item, '*')\",\n                \"spark\": \"TRIM('*' FROM item)\",\n            },\n        )\n        expr = self.parse_one(\n            \"SELECT TRIM(CAST('***apple***' AS BYTES), CAST('*' AS BYTES)) AS result\"\n        )\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"SELECT CAST(TRIM(CAST(CAST('***apple***' AS BLOB) AS TEXT), CAST(CAST('*' AS BLOB) AS TEXT)) AS BLOB) AS result\",\n        )\n        expr = self.parse_one(\"SELECT TRIM('***apple***', '*') AS result\")\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"SELECT TRIM('***apple***', '*') AS result\")\n        self.validate_all(\n            \"CREATE OR REPLACE TABLE `a.b.c` COPY `a.b.d`\",\n            write={\n                \"bigquery\": \"CREATE OR REPLACE TABLE `a.b.c` COPY `a.b.d`\",\n                \"snowflake\": 'CREATE OR REPLACE TABLE \"a\".\"b\".\"c\" CLONE \"a\".\"b\".\"d\"',\n            },\n        )\n        (\n            self.validate_all(\n                \"SELECT DATETIME_DIFF('2023-01-01T00:00:00', '2023-01-01T05:00:00', MILLISECOND)\",\n                write={\n                    \"bigquery\": \"SELECT DATETIME_DIFF('2023-01-01T00:00:00', '2023-01-01T05:00:00', MILLISECOND)\",\n                    \"databricks\": \"SELECT TIMESTAMPDIFF(MILLISECOND, '2023-01-01T05:00:00', '2023-01-01T00:00:00')\",\n                    \"snowflake\": \"SELECT TIMESTAMPDIFF(MILLISECOND, '2023-01-01T05:00:00', '2023-01-01T00:00:00')\",\n                    \"duckdb\": \"SELECT DATE_DIFF('MILLISECOND', CAST('2023-01-01T05:00:00' AS TIMESTAMP), CAST('2023-01-01T00:00:00' AS TIMESTAMP))\",\n                },\n            ),\n        )\n        (\n            self.validate_all(\n                \"SELECT DATETIME_ADD('2023-01-01T00:00:00', INTERVAL 1 MILLISECOND)\",\n                write={\n                    \"bigquery\": \"SELECT DATETIME_ADD('2023-01-01T00:00:00', INTERVAL '1' MILLISECOND)\",\n                    \"databricks\": \"SELECT TIMESTAMPADD(MILLISECOND, '1', '2023-01-01T00:00:00')\",\n                    \"duckdb\": \"SELECT CAST('2023-01-01T00:00:00' AS TIMESTAMP) + INTERVAL '1' MILLISECOND\",\n                    \"snowflake\": \"SELECT TIMESTAMPADD(MILLISECOND, '1', '2023-01-01T00:00:00')\",\n                    \"spark\": \"SELECT '2023-01-01T00:00:00' + INTERVAL '1' MILLISECOND\",\n                },\n            ),\n        )\n        (\n            self.validate_all(\n                \"SELECT DATETIME_SUB('2023-01-01T00:00:00', INTERVAL 1 MILLISECOND)\",\n                write={\n                    \"bigquery\": \"SELECT DATETIME_SUB('2023-01-01T00:00:00', INTERVAL '1' MILLISECOND)\",\n                    \"databricks\": \"SELECT TIMESTAMPADD(MILLISECOND, '1' * -1, '2023-01-01T00:00:00')\",\n                    \"duckdb\": \"SELECT CAST('2023-01-01T00:00:00' AS TIMESTAMP) - INTERVAL '1' MILLISECOND\",\n                    \"spark\": \"SELECT '2023-01-01T00:00:00' - INTERVAL '1' MILLISECOND\",\n                },\n            ),\n        )\n        (\n            self.validate_all(\n                \"SELECT DATETIME_TRUNC('2023-01-01T01:01:01', HOUR)\",\n                write={\n                    \"bigquery\": \"SELECT DATETIME_TRUNC('2023-01-01T01:01:01', HOUR)\",\n                    \"databricks\": \"SELECT DATE_TRUNC('HOUR', '2023-01-01T01:01:01')\",\n                    \"duckdb\": \"SELECT DATE_TRUNC('HOUR', CAST('2023-01-01T01:01:01' AS TIMESTAMP))\",\n                },\n            ),\n        )\n        self.validate_all(\"LEAST(x, y)\", read={\"sqlite\": \"MIN(x, y)\"})\n        self.validate_all(\n            'SELECT TIMESTAMP_ADD(TIMESTAMP \"2008-12-25 15:30:00+00\", INTERVAL 10 MINUTE)',\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_ADD(CAST('2008-12-25 15:30:00+00' AS TIMESTAMP), INTERVAL '10' MINUTE)\",\n                \"databricks\": \"SELECT DATE_ADD(MINUTE, '10', CAST('2008-12-25 15:30:00+00' AS TIMESTAMP))\",\n                \"duckdb\": \"SELECT CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ) + INTERVAL '10' MINUTE\",\n                \"mysql\": \"SELECT DATE_ADD(TIMESTAMP('2008-12-25 15:30:00+00'), INTERVAL '10' MINUTE)\",\n                \"spark\": \"SELECT DATE_ADD(MINUTE, '10', CAST('2008-12-25 15:30:00+00' AS TIMESTAMP))\",\n                \"snowflake\": \"SELECT TIMESTAMPADD(MINUTE, '10', CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ))\",\n            },\n        )\n        self.validate_all(\n            'SELECT TIMESTAMP_SUB(TIMESTAMP \"2008-12-25 15:30:00+00\", INTERVAL 10 MINUTE)',\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_SUB(CAST('2008-12-25 15:30:00+00' AS TIMESTAMP), INTERVAL '10' MINUTE)\",\n                \"duckdb\": \"SELECT CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ) - INTERVAL '10' MINUTE\",\n                \"mysql\": \"SELECT DATE_SUB(TIMESTAMP('2008-12-25 15:30:00+00'), INTERVAL '10' MINUTE)\",\n                \"snowflake\": \"SELECT TIMESTAMPADD(MINUTE, '10' * -1, CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ))\",\n                \"spark\": \"SELECT CAST('2008-12-25 15:30:00+00' AS TIMESTAMP) - INTERVAL '10' MINUTE\",\n            },\n        )\n        self.validate_all(\n            'SELECT TIMESTAMP_SUB(TIMESTAMP \"2008-12-25 15:30:00+00\", INTERVAL col MINUTE)',\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_SUB(CAST('2008-12-25 15:30:00+00' AS TIMESTAMP), INTERVAL col MINUTE)\",\n                \"snowflake\": \"SELECT TIMESTAMPADD(MINUTE, col * -1, CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_ADD(CAST('09:05:03' AS TIME), INTERVAL 2 HOUR)\",\n            write={\n                \"bigquery\": \"SELECT TIME_ADD(CAST('09:05:03' AS TIME), INTERVAL '2' HOUR)\",\n                \"duckdb\": \"SELECT CAST('09:05:03' AS TIME) + INTERVAL '2' HOUR\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_SUB(CAST('09:05:03' AS TIME), INTERVAL 2 HOUR)\",\n            write={\n                \"bigquery\": \"SELECT TIME_SUB(CAST('09:05:03' AS TIME), INTERVAL '2' HOUR)\",\n                \"duckdb\": \"SELECT CAST('09:05:03' AS TIME) - INTERVAL '2' HOUR\",\n            },\n        )\n\n        expr = self.parse_one(\"LOWER(CAST('HELLO' AS BYTES))\")\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"), \"CAST(LOWER(CAST(CAST('HELLO' AS BLOB) AS TEXT)) AS BLOB)\"\n        )\n\n        sql = \"LOWER('HELLO')\"\n        expr = self.parse_one(sql)\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"LOWER('HELLO')\")\n\n        self.validate_all(\n            \"LOWER(TO_HEX(x))\",\n            write={\n                \"\": \"LOWER(HEX(x))\",\n                \"bigquery\": \"TO_HEX(x)\",\n                \"clickhouse\": \"LOWER(HEX(x))\",\n                \"duckdb\": \"LOWER(HEX(x))\",\n                \"hive\": \"LOWER(HEX(x))\",\n                \"mysql\": \"LOWER(HEX(x))\",\n                \"spark\": \"LOWER(HEX(x))\",\n                \"sqlite\": \"LOWER(HEX(x))\",\n                \"presto\": \"LOWER(TO_HEX(x))\",\n                \"trino\": \"LOWER(TO_HEX(x))\",\n            },\n        )\n        self.validate_all(\n            \"TO_HEX(x)\",\n            read={\n                \"\": \"LOWER(HEX(x))\",\n                \"clickhouse\": \"LOWER(HEX(x))\",\n                \"duckdb\": \"LOWER(HEX(x))\",\n                \"hive\": \"LOWER(HEX(x))\",\n                \"mysql\": \"LOWER(HEX(x))\",\n                \"spark\": \"LOWER(HEX(x))\",\n                \"sqlite\": \"LOWER(HEX(x))\",\n                \"presto\": \"LOWER(TO_HEX(x))\",\n                \"trino\": \"LOWER(TO_HEX(x))\",\n            },\n            write={\n                \"\": \"LOWER(HEX(x))\",\n                \"bigquery\": \"TO_HEX(x)\",\n                \"clickhouse\": \"LOWER(HEX(x))\",\n                \"duckdb\": \"LOWER(HEX(x))\",\n                \"hive\": \"LOWER(HEX(x))\",\n                \"mysql\": \"LOWER(HEX(x))\",\n                \"presto\": \"LOWER(TO_HEX(x))\",\n                \"spark\": \"LOWER(HEX(x))\",\n                \"sqlite\": \"LOWER(HEX(x))\",\n                \"trino\": \"LOWER(TO_HEX(x))\",\n            },\n        )\n\n        sql = \"UPPER(CAST('hello' AS BYTES))\"\n        expr = self.parse_one(\"UPPER(CAST('hello' AS BYTES))\")\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"), \"CAST(UPPER(CAST(CAST('hello' AS BLOB) AS TEXT)) AS BLOB)\"\n        )\n\n        sql = \"UPPER('hello')\"\n        expr = self.parse_one(sql)\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"UPPER('hello')\")\n\n        self.validate_all(\n            \"UPPER(TO_HEX(x))\",\n            read={\n                \"\": \"HEX(x)\",\n                \"clickhouse\": \"HEX(x)\",\n                \"duckdb\": \"HEX(x)\",\n                \"hive\": \"HEX(x)\",\n                \"mysql\": \"HEX(x)\",\n                \"presto\": \"TO_HEX(x)\",\n                \"spark\": \"HEX(x)\",\n                \"sqlite\": \"HEX(x)\",\n                \"trino\": \"TO_HEX(x)\",\n            },\n            write={\n                \"\": \"HEX(x)\",\n                \"bigquery\": \"UPPER(TO_HEX(x))\",\n                \"clickhouse\": \"HEX(x)\",\n                \"duckdb\": \"HEX(x)\",\n                \"hive\": \"HEX(x)\",\n                \"mysql\": \"HEX(x)\",\n                \"presto\": \"TO_HEX(x)\",\n                \"spark\": \"HEX(x)\",\n                \"sqlite\": \"HEX(x)\",\n                \"trino\": \"TO_HEX(x)\",\n            },\n        )\n        self.validate_all(\n            \"MD5(x)\",\n            read={\n                \"clickhouse\": \"MD5(x)\",\n                \"presto\": \"MD5(x)\",\n                \"trino\": \"MD5(x)\",\n            },\n            write={\n                \"\": \"MD5_DIGEST(x)\",\n                \"bigquery\": \"MD5(x)\",\n                \"clickhouse\": \"MD5(x)\",\n                \"hive\": \"UNHEX(MD5(x))\",\n                \"presto\": \"MD5(x)\",\n                \"spark\": \"UNHEX(MD5(x))\",\n                \"trino\": \"MD5(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_HEX(MD5(some_string))\",\n            read={\n                \"duckdb\": \"SELECT MD5(some_string)\",\n                \"spark\": \"SELECT MD5(some_string)\",\n                \"clickhouse\": \"SELECT LOWER(HEX(MD5(some_string)))\",\n                \"presto\": \"SELECT LOWER(TO_HEX(MD5(some_string)))\",\n                \"trino\": \"SELECT LOWER(TO_HEX(MD5(some_string)))\",\n            },\n            write={\n                \"\": \"SELECT MD5(some_string)\",\n                \"bigquery\": \"SELECT TO_HEX(MD5(some_string))\",\n                \"duckdb\": \"SELECT MD5(some_string)\",\n                \"clickhouse\": \"SELECT LOWER(HEX(MD5(some_string)))\",\n                \"presto\": \"SELECT LOWER(TO_HEX(MD5(some_string)))\",\n                \"trino\": \"SELECT LOWER(TO_HEX(MD5(some_string)))\",\n            },\n        )\n        self.validate_all(\n            \"SHA1(x)\",\n            read={\n                \"bigquery\": \"SHA1(x)\",\n                \"clickhouse\": \"SHA1(x)\",\n                \"presto\": \"SHA1(x)\",\n                \"trino\": \"SHA1(x)\",\n            },\n            write={\n                \"clickhouse\": \"SHA1(x)\",\n                \"bigquery\": \"SHA1(x)\",\n                \"presto\": \"SHA1(x)\",\n                \"trino\": \"SHA1(x)\",\n                \"duckdb\": \"UNHEX(SHA1(x))\",\n            },\n        )\n        self.validate_all(\n            \"SHA256(x)\",\n            read={\n                \"clickhouse\": \"SHA256(x)\",\n                \"presto\": \"SHA256(x)\",\n                \"trino\": \"SHA256(x)\",\n                \"postgres\": \"SHA256(x)\",\n                \"duckdb\": \"SHA256(x)\",\n            },\n            write={\n                \"bigquery\": \"SHA256(x)\",\n                \"spark2\": \"SHA2(x, 256)\",\n                \"clickhouse\": \"SHA256(x)\",\n                \"postgres\": \"SHA256(x)\",\n                \"presto\": \"SHA256(x)\",\n                \"redshift\": \"SHA2(x, 256)\",\n                \"trino\": \"SHA256(x)\",\n                \"duckdb\": \"UNHEX(SHA256(x))\",\n                \"snowflake\": \"SHA2_BINARY(x, 256)\",\n            },\n        )\n        self.validate_all(\n            \"SHA512(x)\",\n            read={\n                \"clickhouse\": \"SHA512(x)\",\n                \"presto\": \"SHA512(x)\",\n                \"trino\": \"SHA512(x)\",\n            },\n            write={\n                \"clickhouse\": \"SHA512(x)\",\n                \"bigquery\": \"SHA512(x)\",\n                \"spark2\": \"SHA2(x, 512)\",\n                \"presto\": \"SHA512(x)\",\n                \"trino\": \"SHA512(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('20201225' AS TIMESTAMP FORMAT 'YYYYMMDD' AT TIME ZONE 'America/New_York')\",\n            write={\"bigquery\": \"SELECT PARSE_TIMESTAMP('%Y%m%d', '20201225', 'America/New_York')\"},\n        )\n        self.validate_all(\n            \"SELECT CAST('20201225' AS TIMESTAMP FORMAT 'YYYYMMDD')\",\n            write={\"bigquery\": \"SELECT PARSE_TIMESTAMP('%Y%m%d', '20201225')\"},\n        )\n        self.validate_all(\n            \"SELECT CAST(TIMESTAMP '2008-12-25 00:00:00+00:00' AS STRING FORMAT 'YYYY-MM-DD HH24:MI:SS TZH:TZM') AS date_time_to_string\",\n            write={\n                \"bigquery\": \"SELECT CAST(CAST('2008-12-25 00:00:00+00:00' AS TIMESTAMP) AS STRING FORMAT 'YYYY-MM-DD HH24:MI:SS TZH:TZM') AS date_time_to_string\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(TIMESTAMP '2008-12-25 00:00:00+00:00' AS STRING FORMAT 'YYYY-MM-DD HH24:MI:SS TZH:TZM' AT TIME ZONE 'Asia/Kolkata') AS date_time_to_string\",\n            write={\n                \"bigquery\": \"SELECT CAST(CAST('2008-12-25 00:00:00+00:00' AS TIMESTAMP) AS STRING FORMAT 'YYYY-MM-DD HH24:MI:SS TZH:TZM' AT TIME ZONE 'Asia/Kolkata') AS date_time_to_string\",\n            },\n        )\n        self.validate_all(\n            \"WITH cte AS (SELECT [1, 2, 3] AS arr) SELECT IF(pos = pos_2, col, NULL) AS col FROM cte CROSS JOIN UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(arr)) - 1)) AS pos CROSS JOIN UNNEST(arr) AS col WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH(arr) - 1) AND pos_2 = (ARRAY_LENGTH(arr) - 1))\",\n            read={\n                \"spark\": \"WITH cte AS (SELECT ARRAY(1, 2, 3) AS arr) SELECT EXPLODE(arr) FROM cte\"\n            },\n        )\n        self.validate_all(\n            \"SELECT IF(pos = pos_2, col, NULL) AS col FROM UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(IF(ARRAY_LENGTH(COALESCE([], [])) = 0, [[][SAFE_ORDINAL(0)]], []))) - 1)) AS pos CROSS JOIN UNNEST(IF(ARRAY_LENGTH(COALESCE([], [])) = 0, [[][SAFE_ORDINAL(0)]], [])) AS col WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH(IF(ARRAY_LENGTH(COALESCE([], [])) = 0, [[][SAFE_ORDINAL(0)]], [])) - 1) AND pos_2 = (ARRAY_LENGTH(IF(ARRAY_LENGTH(COALESCE([], [])) = 0, [[][SAFE_ORDINAL(0)]], [])) - 1))\",\n            read={\"spark\": \"select explode_outer([])\"},\n        )\n        self.validate_all(\n            \"SELECT IF(pos = pos_2, col, NULL) AS col, IF(pos = pos_2, pos_2, NULL) AS pos_2 FROM UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(IF(ARRAY_LENGTH(COALESCE([], [])) = 0, [[][SAFE_ORDINAL(0)]], []))) - 1)) AS pos CROSS JOIN UNNEST(IF(ARRAY_LENGTH(COALESCE([], [])) = 0, [[][SAFE_ORDINAL(0)]], [])) AS col WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH(IF(ARRAY_LENGTH(COALESCE([], [])) = 0, [[][SAFE_ORDINAL(0)]], [])) - 1) AND pos_2 = (ARRAY_LENGTH(IF(ARRAY_LENGTH(COALESCE([], [])) = 0, [[][SAFE_ORDINAL(0)]], [])) - 1))\",\n            read={\"spark\": \"select posexplode_outer([])\"},\n        )\n        self.validate_all(\n            \"SELECT AS STRUCT ARRAY(SELECT AS STRUCT 1 AS b FROM x) AS y FROM z\",\n            write={\n                \"\": \"SELECT AS STRUCT ARRAY(SELECT AS STRUCT 1 AS b FROM x) AS y FROM z\",\n                \"bigquery\": \"SELECT AS STRUCT ARRAY(SELECT AS STRUCT 1 AS b FROM x) AS y FROM z\",\n                \"duckdb\": \"SELECT {'y': ARRAY(SELECT {'b': 1} FROM x)} FROM z\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(STRUCT(1) AS STRUCT<INT64>)\",\n            write={\n                \"bigquery\": \"SELECT CAST(STRUCT(1) AS STRUCT<INT64>)\",\n                \"snowflake\": \"SELECT CAST(OBJECT_CONSTRUCT('_0', 1) AS OBJECT)\",\n            },\n        )\n        self.validate_all(\n            \"cast(x as date format 'MM/DD/YYYY')\",\n            write={\n                \"bigquery\": \"PARSE_DATE('%m/%d/%Y', x)\",\n            },\n        )\n        self.validate_all(\n            \"cast(x as time format 'YYYY.MM.DD HH:MI:SSTZH')\",\n            write={\n                \"bigquery\": \"PARSE_TIMESTAMP('%Y.%m.%d %I:%M:%S%z', x)\",\n            },\n        )\n        self.validate_identity(\n            \"CREATE TEMP TABLE foo AS SELECT 1\",\n            \"CREATE TEMPORARY TABLE foo AS SELECT 1\",\n        )\n        self.validate_all(\n            \"REGEXP_CONTAINS('foo', '.*')\",\n            read={\n                \"bigquery\": \"REGEXP_CONTAINS('foo', '.*')\",\n                \"mysql\": \"REGEXP_LIKE('foo', '.*')\",\n                \"starrocks\": \"REGEXP('foo', '.*')\",\n            },\n            write={\n                \"mysql\": \"REGEXP_LIKE('foo', '.*')\",\n                \"starrocks\": \"REGEXP('foo', '.*')\",\n            },\n        )\n        self.validate_all(\n            '\"\"\"x\"\"\"',\n            write={\n                \"bigquery\": \"'x'\",\n                \"duckdb\": \"'x'\",\n                \"presto\": \"'x'\",\n                \"hive\": \"'x'\",\n                \"spark\": \"'x'\",\n            },\n        )\n        self.validate_all(\n            '\"\"\"x\\'\"\"\"',\n            write={\n                \"bigquery\": \"'x\\\\''\",\n                \"duckdb\": \"'x'''\",\n                \"presto\": \"'x'''\",\n                \"hive\": \"'x\\\\''\",\n                \"spark\": \"'x\\\\''\",\n            },\n        )\n        self.validate_all(\n            \"r'x\\\\''\",\n            write={\n                \"bigquery\": \"'x\\\\''\",\n                \"hive\": \"'x\\\\''\",\n            },\n        )\n\n        self.validate_all(\n            \"r'x\\\\y'\",\n            write={\n                \"bigquery\": \"'x\\\\\\\\y'\",\n                \"hive\": \"'x\\\\\\\\y'\",\n            },\n        )\n        self.validate_all(\n            \"'\\\\\\\\'\",\n            write={\n                \"bigquery\": \"'\\\\\\\\'\",\n                \"duckdb\": \"'\\\\'\",\n                \"presto\": \"'\\\\'\",\n                \"hive\": \"'\\\\\\\\'\",\n            },\n        )\n        self.validate_all(\n            r'r\"\"\"/\\*.*\\*/\"\"\"',\n            write={\n                \"bigquery\": r\"'/\\\\*.*\\\\*/'\",\n                \"duckdb\": r\"'/\\*.*\\*/'\",\n                \"presto\": r\"'/\\*.*\\*/'\",\n                \"hive\": r\"'/\\\\*.*\\\\*/'\",\n                \"spark\": r\"'/\\\\*.*\\\\*/'\",\n            },\n        )\n        self.validate_all(\n            r'R\"\"\"/\\*.*\\*/\"\"\"',\n            write={\n                \"bigquery\": r\"'/\\\\*.*\\\\*/'\",\n                \"duckdb\": r\"'/\\*.*\\*/'\",\n                \"presto\": r\"'/\\*.*\\*/'\",\n                \"hive\": r\"'/\\\\*.*\\\\*/'\",\n                \"spark\": r\"'/\\\\*.*\\\\*/'\",\n            },\n        )\n        self.validate_all(\n            'r\"\"\"a\\n\"\"\"',\n            write={\n                \"bigquery\": \"'a\\\\n'\",\n                \"duckdb\": \"'a\\n'\",\n            },\n        )\n        self.validate_all(\n            '\"\"\"a\\n\"\"\"',\n            write={\n                \"bigquery\": \"'a\\\\n'\",\n                \"duckdb\": \"'a\\n'\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS INT64)\",\n            write={\n                \"bigquery\": \"CAST(a AS INT64)\",\n                \"duckdb\": \"CAST(a AS BIGINT)\",\n                \"presto\": \"CAST(a AS BIGINT)\",\n                \"hive\": \"CAST(a AS BIGINT)\",\n                \"spark\": \"CAST(a AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS BYTES)\",\n            write={\n                \"bigquery\": \"CAST(a AS BYTES)\",\n                \"duckdb\": \"CAST(a AS BLOB)\",\n                \"presto\": \"CAST(a AS VARBINARY)\",\n                \"hive\": \"CAST(a AS BINARY)\",\n                \"spark\": \"CAST(a AS BINARY)\",\n            },\n        )\n        # Test STARTS_WITH with BYTES/BLOB handling from BigQuery to DuckDB\n        # Requires type annotation for proper BLOB -> VARCHAR casting\n        expr = self.parse_one(\"STARTS_WITH(CAST('foo' AS BYTES), CAST('f' AS BYTES))\")\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"STARTS_WITH(CAST(CAST('foo' AS BLOB) AS TEXT), CAST(CAST('f' AS BLOB) AS TEXT))\",\n        )\n\n        expr = self.parse_one(\"STARTS_WITH(CAST('foo' AS BYTES), b'f')\")\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"STARTS_WITH(CAST(CAST('foo' AS BLOB) AS TEXT), CAST(CAST(e'f' AS BLOB) AS TEXT))\",\n        )\n        self.validate_all(\n            \"CAST(a AS NUMERIC)\",\n            write={\n                \"bigquery\": \"CAST(a AS NUMERIC)\",\n                \"duckdb\": \"CAST(a AS DECIMAL)\",\n                \"presto\": \"CAST(a AS DECIMAL)\",\n                \"hive\": \"CAST(a AS DECIMAL)\",\n                \"spark\": \"CAST(a AS DECIMAL)\",\n            },\n        )\n        self.validate_all(\n            \"[1, 2, 3]\",\n            read={\n                \"duckdb\": \"[1, 2, 3]\",\n                \"presto\": \"ARRAY[1, 2, 3]\",\n                \"hive\": \"ARRAY(1, 2, 3)\",\n                \"spark\": \"ARRAY(1, 2, 3)\",\n            },\n            write={\n                \"bigquery\": \"[1, 2, 3]\",\n                \"duckdb\": \"[1, 2, 3]\",\n                \"presto\": \"ARRAY[1, 2, 3]\",\n                \"hive\": \"ARRAY(1, 2, 3)\",\n                \"spark\": \"ARRAY(1, 2, 3)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST(['7', '14']) AS x\",\n            read={\n                \"spark\": \"SELECT * FROM UNNEST(ARRAY('7', '14')) AS (x)\",\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM UNNEST(['7', '14']) AS x\",\n                \"presto\": \"SELECT * FROM UNNEST(ARRAY['7', '14']) AS _t0(x)\",\n                \"spark\": \"SELECT * FROM EXPLODE(ARRAY('7', '14')) AS _t0(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY(SELECT x FROM UNNEST([0, 1]) AS x)\",\n            write={\"bigquery\": \"SELECT ARRAY(SELECT x FROM UNNEST([0, 1]) AS x)\"},\n        )\n        self.validate_all(\n            \"SELECT ARRAY(SELECT DISTINCT x FROM UNNEST(some_numbers) AS x) AS unique_numbers\",\n            write={\n                \"bigquery\": \"SELECT ARRAY(SELECT DISTINCT x FROM UNNEST(some_numbers) AS x) AS unique_numbers\"\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY(SELECT * FROM foo JOIN bla ON x = y)\",\n            write={\"bigquery\": \"SELECT ARRAY(SELECT * FROM foo JOIN bla ON x = y)\"},\n        )\n        self.validate_all(\n            \"CURRENT_TIMESTAMP()\",\n            read={\n                \"tsql\": \"GETDATE()\",\n            },\n            write={\n                \"tsql\": \"GETDATE()\",\n            },\n        )\n        self.validate_all(\n            \"current_datetime\",\n            write={\n                \"bigquery\": \"CURRENT_DATETIME()\",\n                \"presto\": \"CURRENT_DATETIME()\",\n                \"hive\": \"CURRENT_DATETIME()\",\n                \"spark\": \"CURRENT_DATETIME()\",\n            },\n        )\n        self.validate_all(\n            \"current_time\",\n            write={\n                \"bigquery\": \"CURRENT_TIME()\",\n                \"duckdb\": \"CURRENT_TIME\",\n                \"presto\": \"CURRENT_TIME\",\n                \"trino\": \"CURRENT_TIME\",\n                \"hive\": \"CURRENT_TIME()\",\n                \"spark\": \"CURRENT_TIME()\",\n            },\n        )\n        self.validate_all(\n            \"CURRENT_TIMESTAMP\",\n            write={\n                \"bigquery\": \"CURRENT_TIMESTAMP()\",\n                \"duckdb\": \"CURRENT_TIMESTAMP\",\n                \"postgres\": \"CURRENT_TIMESTAMP\",\n                \"presto\": \"CURRENT_TIMESTAMP\",\n                \"hive\": \"CURRENT_TIMESTAMP()\",\n                \"spark\": \"CURRENT_TIMESTAMP()\",\n            },\n        )\n        self.validate_all(\n            \"CURRENT_TIMESTAMP()\",\n            write={\n                \"bigquery\": \"CURRENT_TIMESTAMP()\",\n                \"duckdb\": \"CURRENT_TIMESTAMP\",\n                \"postgres\": \"CURRENT_TIMESTAMP\",\n                \"presto\": \"CURRENT_TIMESTAMP\",\n                \"hive\": \"CURRENT_TIMESTAMP()\",\n                \"spark\": \"CURRENT_TIMESTAMP()\",\n            },\n        )\n        self.validate_all(\n            \"DIV(x, y)\",\n            write={\n                \"bigquery\": \"DIV(x, y)\",\n                \"duckdb\": \"x // y\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a struct<struct_col_a:int, struct_col_b:string>)\",\n            write={\n                \"bigquery\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a INT64, struct_col_b STRING>)\",\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a STRUCT(struct_col_a INT, struct_col_b TEXT))\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b VARCHAR))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRING>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRING>)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a INT64, struct_col_b STRUCT<nested_col_a STRING, nested_col_b STRING>>)\",\n            write={\n                \"bigquery\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a INT64, struct_col_b STRUCT<nested_col_a STRING, nested_col_b STRING>>)\",\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a STRUCT(struct_col_a BIGINT, struct_col_b STRUCT(nested_col_a TEXT, nested_col_b TEXT)))\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ROW(struct_col_a BIGINT, struct_col_b ROW(nested_col_a VARCHAR, nested_col_b VARCHAR)))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: BIGINT, struct_col_b: STRUCT<nested_col_a: STRING, nested_col_b: STRING>>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: BIGINT, struct_col_b: STRUCT<nested_col_a: STRING, nested_col_b: STRING>>)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE db.example_table (x int) PARTITION BY x cluster by x\",\n            write={\n                \"bigquery\": \"CREATE TABLE db.example_table (x INT64) PARTITION BY x CLUSTER BY x\",\n            },\n        )\n        self.validate_all(\n            \"DELETE db.example_table WHERE x = 1\",\n            write={\n                \"bigquery\": \"DELETE db.example_table WHERE x = 1\",\n                \"presto\": \"DELETE FROM db.example_table WHERE x = 1\",\n            },\n        )\n        self.validate_all(\n            \"DELETE db.example_table tb WHERE tb.x = 1\",\n            write={\n                \"bigquery\": \"DELETE db.example_table AS tb WHERE tb.x = 1\",\n                \"presto\": \"DELETE FROM db.example_table WHERE x = 1\",\n            },\n        )\n        self.validate_all(\n            \"DELETE db.example_table AS tb WHERE tb.x = 1\",\n            write={\n                \"bigquery\": \"DELETE db.example_table AS tb WHERE tb.x = 1\",\n                \"presto\": \"DELETE FROM db.example_table WHERE x = 1\",\n            },\n        )\n        self.validate_all(\n            \"DELETE FROM db.example_table WHERE x = 1\",\n            write={\n                \"bigquery\": \"DELETE FROM db.example_table WHERE x = 1\",\n                \"presto\": \"DELETE FROM db.example_table WHERE x = 1\",\n            },\n        )\n        self.validate_all(\n            \"DELETE FROM db.example_table tb WHERE tb.x = 1\",\n            write={\n                \"bigquery\": \"DELETE FROM db.example_table AS tb WHERE tb.x = 1\",\n                \"presto\": \"DELETE FROM db.example_table WHERE x = 1\",\n            },\n        )\n        self.validate_all(\n            \"DELETE FROM db.example_table AS tb WHERE tb.x = 1\",\n            write={\n                \"bigquery\": \"DELETE FROM db.example_table AS tb WHERE tb.x = 1\",\n                \"presto\": \"DELETE FROM db.example_table WHERE x = 1\",\n            },\n        )\n        self.validate_all(\n            \"DELETE FROM db.example_table AS tb WHERE example_table.x = 1\",\n            write={\n                \"bigquery\": \"DELETE FROM db.example_table AS tb WHERE example_table.x = 1\",\n                \"presto\": \"DELETE FROM db.example_table WHERE x = 1\",\n            },\n        )\n        self.validate_all(\n            \"DELETE FROM db.example_table WHERE example_table.x = 1\",\n            write={\n                \"bigquery\": \"DELETE FROM db.example_table WHERE example_table.x = 1\",\n                \"presto\": \"DELETE FROM db.example_table WHERE example_table.x = 1\",\n            },\n        )\n        self.validate_all(\n            \"DELETE FROM db.t1 AS t1 WHERE NOT t1.c IN (SELECT db.t2.c FROM db.t2)\",\n            write={\n                \"bigquery\": \"DELETE FROM db.t1 AS t1 WHERE NOT t1.c IN (SELECT db.t2.c FROM db.t2)\",\n                \"presto\": \"DELETE FROM db.t1 WHERE NOT c IN (SELECT c FROM db.t2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a WHERE b IN UNNEST([1, 2, 3])\",\n            write={\n                \"bigquery\": \"SELECT * FROM a WHERE b IN UNNEST([1, 2, 3])\",\n                \"presto\": \"SELECT * FROM a WHERE b IN (SELECT UNNEST(ARRAY[1, 2, 3]))\",\n                \"hive\": \"SELECT * FROM a WHERE b IN (SELECT EXPLODE(ARRAY(1, 2, 3)))\",\n                \"spark\": \"SELECT * FROM a WHERE b IN (SELECT EXPLODE(ARRAY(1, 2, 3)))\",\n            },\n        )\n        self.validate_all(\n            \"DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)\",\n            write={\n                \"postgres\": \"CURRENT_DATE - INTERVAL '1 DAY'\",\n                \"bigquery\": \"DATE_SUB(CURRENT_DATE, INTERVAL '1' DAY)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD(CURRENT_DATE(), INTERVAL -1 DAY)\",\n            write={\n                \"bigquery\": \"DATE_ADD(CURRENT_DATE, INTERVAL '-1' DAY)\",\n                \"duckdb\": \"CURRENT_DATE + INTERVAL '-1' DAY\",\n                \"mysql\": \"DATE_ADD(CURRENT_DATE, INTERVAL '-1' DAY)\",\n                \"postgres\": \"CURRENT_DATE + INTERVAL '-1 DAY'\",\n                \"presto\": \"DATE_ADD('DAY', CAST('-1' AS BIGINT), CURRENT_DATE)\",\n                \"hive\": \"DATE_ADD(CURRENT_DATE, -1)\",\n                \"spark\": \"DATE_ADD(CURRENT_DATE, -1)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_DIFF(DATE '2010-07-07', DATE '2008-12-25', DAY)\",\n            write={\n                \"bigquery\": \"DATE_DIFF(CAST('2010-07-07' AS DATE), CAST('2008-12-25' AS DATE), DAY)\",\n                \"mysql\": \"DATEDIFF(CAST('2010-07-07' AS DATE), CAST('2008-12-25' AS DATE))\",\n                \"starrocks\": \"DATE_DIFF('DAY', CAST('2010-07-07' AS DATE), CAST('2008-12-25' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"DATE_DIFF(CAST('2010-07-07' AS DATE), CAST('2008-12-25' AS DATE), DAY)\",\n            read={\n                \"mysql\": \"DATEDIFF(CAST('2010-07-07' AS DATE), CAST('2008-12-25' AS DATE))\",\n                \"starrocks\": \"DATEDIFF(CAST('2010-07-07' AS DATE), CAST('2008-12-25' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"DATE_DIFF(DATE '2010-07-07', DATE '2008-12-25', MINUTE)\",\n            write={\n                \"bigquery\": \"DATE_DIFF(CAST('2010-07-07' AS DATE), CAST('2008-12-25' AS DATE), MINUTE)\",\n                \"starrocks\": \"DATE_DIFF('MINUTE', CAST('2010-07-07' AS DATE), CAST('2008-12-25' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"DATE_DIFF('2021-01-01', '2020-01-01', DAY)\",\n            write={\n                \"bigquery\": \"DATE_DIFF('2021-01-01', '2020-01-01', DAY)\",\n                \"duckdb\": \"DATE_DIFF('DAY', CAST('2020-01-01' AS DATE), CAST('2021-01-01' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"CURRENT_DATE('UTC')\",\n            write={\n                \"bigquery\": \"CURRENT_DATE('UTC')\",\n                \"duckdb\": \"CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE)\",\n                \"mysql\": \"CURRENT_DATE AT TIME ZONE 'UTC'\",\n                \"postgres\": \"CURRENT_DATE AT TIME ZONE 'UTC'\",\n                \"snowflake\": \"CAST(CONVERT_TIMEZONE('UTC', CURRENT_TIMESTAMP()) AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM test WHERE a = 1 GROUP BY a HAVING a = 2 QUALIFY z ORDER BY a LIMIT 10\",\n            write={\n                \"bigquery\": \"SELECT a FROM test WHERE a = 1 GROUP BY a HAVING a = 2 QUALIFY z ORDER BY a LIMIT 10\",\n                \"snowflake\": \"SELECT a FROM test WHERE a = 1 GROUP BY a HAVING a = 2 QUALIFY z ORDER BY a NULLS FIRST LIMIT 10\",\n            },\n        )\n        self.validate_all(\n            \"SELECT cola, colb FROM UNNEST([STRUCT(1 AS cola, 'test' AS colb)]) AS tab\",\n            read={\n                \"bigquery\": \"SELECT cola, colb FROM UNNEST([STRUCT(1 AS cola, 'test' AS colb)]) as tab\",\n                \"snowflake\": \"SELECT cola, colb FROM (VALUES (1, 'test')) AS tab(cola, colb)\",\n                \"spark\": \"SELECT cola, colb FROM VALUES (1, 'test') AS tab(cola, colb)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST([STRUCT(1 AS _c0)]) AS t1\",\n            read={\n                \"bigquery\": \"SELECT * FROM UNNEST([STRUCT(1 AS _c0)]) AS t1\",\n                \"postgres\": \"SELECT * FROM (VALUES (1)) AS t1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST([STRUCT(1 AS id)]) AS t1 CROSS JOIN UNNEST([STRUCT(1 AS id)]) AS t2\",\n            read={\n                \"bigquery\": \"SELECT * FROM UNNEST([STRUCT(1 AS id)]) AS t1 CROSS JOIN UNNEST([STRUCT(1 AS id)]) AS t2\",\n                \"postgres\": \"SELECT * FROM (VALUES (1)) AS t1(id) CROSS JOIN (VALUES (1)) AS t2(id)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST([1]) WITH OFFSET\",\n            write={\"bigquery\": \"SELECT * FROM UNNEST([1]) WITH OFFSET AS offset\"},\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST([1]) WITH OFFSET y\",\n            write={\"bigquery\": \"SELECT * FROM UNNEST([1]) WITH OFFSET AS y\"},\n        )\n        self.validate_all(\n            \"GENERATE_ARRAY(1, 4)\",\n            read={\"bigquery\": \"GENERATE_ARRAY(1, 4)\"},\n            write={\"duckdb\": \"GENERATE_SERIES(1, 4)\"},\n        )\n        self.validate_all(\n            \"TO_JSON_STRING(x)\",\n            read={\n                \"bigquery\": \"TO_JSON_STRING(x)\",\n            },\n            write={\n                \"bigquery\": \"TO_JSON_STRING(x)\",\n                \"duckdb\": \"CAST(TO_JSON(x) AS TEXT)\",\n                \"presto\": \"JSON_FORMAT(CAST(x AS JSON))\",\n                \"spark\": \"TO_JSON(x)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT\n  `u`.`user_email` AS `user_email`,\n  `d`.`user_id` AS `user_id`,\n  `account_id` AS `account_id`\nFROM `analytics_staging`.`stg_mongodb__users` AS `u`, UNNEST(`u`.`cluster_details`) AS `d`, UNNEST(`d`.`account_ids`) AS `account_id`\nWHERE\n  NOT `account_id` IS NULL\"\"\",\n            read={\n                \"\": \"\"\"\n                SELECT\n                  \"u\".\"user_email\" AS \"user_email\",\n                  \"_q_0\".\"d\".\"user_id\" AS \"user_id\",\n                  \"_q_1\".\"account_id\" AS \"account_id\"\n                FROM\n                  \"analytics_staging\".\"stg_mongodb__users\" AS \"u\",\n                  UNNEST(\"u\".\"cluster_details\") AS \"_q_0\"(\"d\"),\n                  UNNEST(\"_q_0\".\"d\".\"account_ids\") AS \"_q_1\"(\"account_id\")\n                WHERE\n                  NOT \"_q_1\".\"account_id\" IS NULL\n                \"\"\"\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"SELECT MOD(x, 10)\",\n            read={\"postgres\": \"SELECT x % 10\"},\n            write={\n                \"bigquery\": \"SELECT MOD(x, 10)\",\n                \"postgres\": \"SELECT x % 10\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(x AS DATETIME)\",\n            write={\n                \"\": \"SELECT CAST(x AS TIMESTAMP)\",\n                \"bigquery\": \"SELECT CAST(x AS DATETIME)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME(foo, 'America/Los_Angeles')\",\n            write={\n                \"duckdb\": \"SELECT CAST(CAST(foo AS TIMESTAMPTZ) AT TIME ZONE 'America/Los_Angeles' AS TIME)\",\n                \"bigquery\": \"SELECT TIME(foo, 'America/Los_Angeles')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATETIME('2020-01-01')\",\n            write={\n                \"duckdb\": \"SELECT CAST('2020-01-01' AS TIMESTAMP)\",\n                \"bigquery\": \"SELECT DATETIME('2020-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATETIME('2020-01-01', TIME '23:59:59')\",\n            write={\n                \"duckdb\": \"SELECT CAST(CAST('2020-01-01' AS DATE) + CAST('23:59:59' AS TIME) AS TIMESTAMP)\",\n                \"bigquery\": \"SELECT DATETIME('2020-01-01', CAST('23:59:59' AS TIME))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATETIME('2020-01-01', 'America/Los_Angeles')\",\n            write={\n                \"duckdb\": \"SELECT CAST(CAST('2020-01-01' AS TIMESTAMPTZ) AT TIME ZONE 'America/Los_Angeles' AS TIMESTAMP)\",\n                \"bigquery\": \"SELECT DATETIME('2020-01-01', 'America/Los_Angeles')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LENGTH(foo)\",\n            read={\n                \"bigquery\": \"SELECT LENGTH(foo)\",\n                \"snowflake\": \"SELECT LENGTH(foo)\",\n            },\n            write={\n                \"duckdb\": \"SELECT CASE TYPEOF(foo) WHEN 'BLOB' THEN OCTET_LENGTH(CAST(foo AS BLOB)) ELSE LENGTH(CAST(foo AS TEXT)) END\",\n                \"snowflake\": \"SELECT LENGTH(foo)\",\n                \"\": \"SELECT LENGTH(foo)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_DIFF('12:00:00', '12:30:00', MINUTE)\",\n            write={\n                \"duckdb\": \"SELECT DATE_DIFF('MINUTE', CAST('12:30:00' AS TIME), CAST('12:00:00' AS TIME))\",\n                \"bigquery\": \"SELECT TIME_DIFF('12:00:00', '12:30:00', MINUTE)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_CONCAT([1, 2], [3, 4], [5, 6])\",\n            write={\n                \"bigquery\": \"ARRAY_CONCAT([1, 2], [3, 4], [5, 6])\",\n                \"duckdb\": \"LIST_CONCAT([1, 2], [3, 4], [5, 6])\",\n                \"postgres\": \"ARRAY_CAT(ARRAY[1, 2], ARRAY_CAT(ARRAY[3, 4], ARRAY[5, 6]))\",\n                \"redshift\": \"ARRAY_CONCAT(ARRAY(1, 2), ARRAY_CONCAT(ARRAY(3, 4), ARRAY(5, 6)))\",\n                \"snowflake\": \"ARRAY_CAT([1, 2], ARRAY_CAT([3, 4], [5, 6]))\",\n                \"hive\": \"CONCAT(ARRAY(1, 2), ARRAY(3, 4), ARRAY(5, 6))\",\n                \"spark2\": \"CONCAT(ARRAY(1, 2), ARRAY(3, 4), ARRAY(5, 6))\",\n                \"spark\": \"CONCAT(ARRAY(1, 2), ARRAY(3, 4), ARRAY(5, 6))\",\n                \"databricks\": \"CONCAT(ARRAY(1, 2), ARRAY(3, 4), ARRAY(5, 6))\",\n                \"presto\": \"CONCAT(ARRAY[1, 2], ARRAY[3, 4], ARRAY[5, 6])\",\n                \"trino\": \"CONCAT(ARRAY[1, 2], ARRAY[3, 4], ARRAY[5, 6])\",\n            },\n        )\n        self.validate_all(\n            \"SELECT GENERATE_TIMESTAMP_ARRAY('2016-10-05 00:00:00', '2016-10-07 00:00:00', INTERVAL '1' DAY)\",\n            write={\n                \"duckdb\": \"SELECT GENERATE_SERIES(CAST('2016-10-05 00:00:00' AS TIMESTAMP), CAST('2016-10-07 00:00:00' AS TIMESTAMP), INTERVAL '1' DAY)\",\n                \"bigquery\": \"SELECT GENERATE_TIMESTAMP_ARRAY('2016-10-05 00:00:00', '2016-10-07 00:00:00', INTERVAL '1' DAY)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT PARSE_DATE('%A %b %e %Y', 'Thursday Dec 25 2008')\",\n            write={\n                \"bigquery\": \"SELECT PARSE_DATE('%A %b %e %Y', 'Thursday Dec 25 2008')\",\n                \"duckdb\": \"SELECT CAST(STRPTIME('Thursday Dec 25 2008', '%A %b %-d %Y') AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT PARSE_DATE('%Y%m%d', '20081225')\",\n            write={\n                \"bigquery\": \"SELECT PARSE_DATE('%Y%m%d', '20081225')\",\n                \"duckdb\": \"SELECT CAST(STRPTIME('20081225', '%Y%m%d') AS DATE)\",\n                \"snowflake\": \"SELECT DATE('20081225', 'yyyymmDD')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_TO_STRING(['cake', 'pie', NULL], '--') AS text\",\n            write={\n                \"bigquery\": \"SELECT ARRAY_TO_STRING(['cake', 'pie', NULL], '--') AS text\",\n                \"duckdb\": \"SELECT ARRAY_TO_STRING(['cake', 'pie', NULL], '--') AS text\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_TO_STRING(['cake', 'pie', NULL], '--', 'MISSING') AS text\",\n            write={\n                \"bigquery\": \"SELECT ARRAY_TO_STRING(['cake', 'pie', NULL], '--', 'MISSING') AS text\",\n                \"duckdb\": \"SELECT ARRAY_TO_STRING(LIST_TRANSFORM(['cake', 'pie', NULL], x -> COALESCE(x, 'MISSING')), '--') AS text\",\n            },\n        )\n        self.validate_all(\n            \"STRING(a)\",\n            write={\n                \"bigquery\": \"STRING(a)\",\n                \"snowflake\": \"CAST(a AS VARCHAR)\",\n                \"duckdb\": \"CAST(a AS TEXT)\",\n            },\n        )\n        self.validate_all(\n            \"STRING('2008-12-25 15:30:00', 'America/New_York')\",\n            write={\n                \"bigquery\": \"STRING('2008-12-25 15:30:00', 'America/New_York')\",\n                \"snowflake\": \"CAST(CONVERT_TIMEZONE('UTC', 'America/New_York', '2008-12-25 15:30:00') AS VARCHAR)\",\n                \"duckdb\": \"CAST(CAST('2008-12-25 15:30:00' AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE 'America/New_York' AS TEXT)\",\n            },\n        )\n\n        self.validate_identity(\"SELECT * FROM a-b c\", \"SELECT * FROM a-b AS c\")\n\n        self.validate_all(\n            \"SAFE_DIVIDE(x, y)\",\n            write={\n                \"bigquery\": \"SAFE_DIVIDE(x, y)\",\n                \"duckdb\": \"CASE WHEN y <> 0 THEN x / y ELSE NULL END\",\n                \"presto\": \"IF(y <> 0, CAST(x AS DOUBLE) / y, NULL)\",\n                \"trino\": \"IF(y <> 0, CAST(x AS DOUBLE) / y, NULL)\",\n                \"hive\": \"IF(y <> 0, x / y, NULL)\",\n                \"spark2\": \"IF(y <> 0, x / y, NULL)\",\n                \"spark\": \"IF(y <> 0, x / y, NULL)\",\n                \"databricks\": \"IF(y <> 0, x / y, NULL)\",\n                \"snowflake\": \"IFF(y <> 0, x / y, NULL)\",\n                \"postgres\": \"CASE WHEN y <> 0 THEN CAST(x AS DOUBLE PRECISION) / y ELSE NULL END\",\n            },\n        )\n        self.validate_all(\n            \"SAFE_DIVIDE(x + 1, 2 * y)\",\n            write={\n                \"bigquery\": \"SAFE_DIVIDE(x + 1, 2 * y)\",\n                \"duckdb\": \"CASE WHEN (2 * y) <> 0 THEN (x + 1) / (2 * y) ELSE NULL END\",\n                \"presto\": \"IF((2 * y) <> 0, CAST((x + 1) AS DOUBLE) / (2 * y), NULL)\",\n                \"trino\": \"IF((2 * y) <> 0, CAST((x + 1) AS DOUBLE) / (2 * y), NULL)\",\n                \"hive\": \"IF((2 * y) <> 0, (x + 1) / (2 * y), NULL)\",\n                \"spark2\": \"IF((2 * y) <> 0, (x + 1) / (2 * y), NULL)\",\n                \"spark\": \"IF((2 * y) <> 0, (x + 1) / (2 * y), NULL)\",\n                \"databricks\": \"IF((2 * y) <> 0, (x + 1) / (2 * y), NULL)\",\n                \"snowflake\": \"IFF((2 * y) <> 0, (x + 1) / (2 * y), NULL)\",\n                \"postgres\": \"CASE WHEN (2 * y) <> 0 THEN CAST((x + 1) AS DOUBLE PRECISION) / (2 * y) ELSE NULL END\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT JSON_VALUE_ARRAY('{\"arr\": [1, \"a\"]}', '$.arr')\"\"\",\n            write={\n                \"bigquery\": \"\"\"SELECT JSON_VALUE_ARRAY('{\"arr\": [1, \"a\"]}', '$.arr')\"\"\",\n                \"duckdb\": \"\"\"SELECT CAST('{\"arr\": [1, \"a\"]}' -> '$.arr' AS TEXT[])\"\"\",\n                \"snowflake\": \"\"\"SELECT TRANSFORM(GET_PATH(PARSE_JSON('{\"arr\": [1, \"a\"]}'), 'arr'), x -> CAST(x AS VARCHAR))\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT INSTR('foo@example.com', '@')\",\n            write={\n                \"bigquery\": \"SELECT INSTR('foo@example.com', '@')\",\n                \"duckdb\": \"SELECT STRPOS('foo@example.com', '@')\",\n                \"snowflake\": \"SELECT CHARINDEX('@', 'foo@example.com')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ts + MAKE_INTERVAL(1, 2, minute => 5, day => 3)\",\n            write={\n                \"bigquery\": \"SELECT ts + MAKE_INTERVAL(1, 2, day => 3, minute => 5)\",\n                \"duckdb\": \"SELECT ts + INTERVAL '1 year 2 month 5 minute 3 day'\",\n                \"snowflake\": \"SELECT ts + INTERVAL '1 year, 2 month, 5 minute, 3 day'\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT INT64(JSON_QUERY(JSON '{\"key\": 2000}', '$.key'))\"\"\",\n            write={\n                \"bigquery\": \"\"\"SELECT INT64(JSON_QUERY(PARSE_JSON('{\"key\": 2000}'), '$.key'))\"\"\",\n                \"duckdb\": \"\"\"SELECT CAST(JSON('{\"key\": 2000}') -> '$.key' AS BIGINT)\"\"\",\n                \"snowflake\": \"\"\"SELECT CAST(GET_PATH(PARSE_JSON('{\"key\": 2000}'), 'key') AS BIGINT)\"\"\",\n            },\n        )\n\n        self.validate_identity(\"CONTAINS_SUBSTR(a, b, json_scope => 'JSON_KEYS_AND_VALUES')\")\n\n        self.validate_all(\n            \"\"\"CONTAINS_SUBSTR(a, b)\"\"\",\n            read={\n                \"\": \"CONTAINS(a, b)\",\n                \"spark\": \"CONTAINS(a, b)\",\n                \"databricks\": \"CONTAINS(a, b)\",\n                \"snowflake\": \"CONTAINS(a, b)\",\n                \"duckdb\": \"CONTAINS(a, b)\",\n                \"oracle\": \"CONTAINS(a, b)\",\n            },\n            write={\n                \"\": \"CONTAINS(LOWER(a), LOWER(b))\",\n                \"spark\": \"CONTAINS(LOWER(a), LOWER(b))\",\n                \"databricks\": \"CONTAINS(LOWER(a), LOWER(b))\",\n                \"snowflake\": \"CONTAINS(LOWER(a), LOWER(b))\",\n                \"duckdb\": \"CONTAINS(LOWER(a), LOWER(b))\",\n                \"oracle\": \"CONTAINS(LOWER(a), LOWER(b))\",\n                \"bigquery\": \"CONTAINS_SUBSTR(a, b)\",\n            },\n        )\n\n        self.validate_identity(\n            \"EXPORT DATA OPTIONS (URI='gs://path*.csv.gz', FORMAT='CSV') AS SELECT * FROM all_rows\"\n        )\n        self.validate_identity(\n            \"EXPORT DATA WITH CONNECTION myproject.us.myconnection OPTIONS (URI='gs://path*.csv.gz', FORMAT='CSV') AS SELECT * FROM all_rows\"\n        )\n\n        self.validate_all(\n            \"SELECT * FROM t1, UNNEST(`t1`) AS `col`\",\n            read={\n                \"duckdb\": 'SELECT * FROM t1, UNNEST(\"t1\") \"t1\" (\"col\")',\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM t1 CROSS JOIN UNNEST(`t1`) AS `col`\",\n                \"redshift\": 'SELECT * FROM t1 CROSS JOIN \"t1\" AS \"col\"',\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM t, UNNEST(`t2`.`t3`) AS `col`\",\n            read={\n                \"duckdb\": 'SELECT * FROM t, UNNEST(\"t1\".\"t2\".\"t3\") \"t1\" (\"col\")',\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM t CROSS JOIN UNNEST(`t2`.`t3`) AS `col`\",\n                \"redshift\": 'SELECT * FROM t CROSS JOIN \"t2\".\"t3\" AS \"col\"',\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM t1, UNNEST(`t1`.`t2`.`t3`.`t4`) AS `col`\",\n            read={\n                \"duckdb\": 'SELECT * FROM t1, UNNEST(\"t1\".\"t2\".\"t3\".\"t4\") \"t3\" (\"col\")',\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM t1 CROSS JOIN UNNEST(`t1`.`t2`.`t3`.`t4`) AS `col`\",\n                \"redshift\": 'SELECT * FROM t1 CROSS JOIN \"t1\".\"t2\".\"t3\".\"t4\" AS \"col\"',\n            },\n        )\n\n        self.validate_identity(\"ARRAY_FIRST(['a', 'b'])\")\n        self.validate_identity(\"ARRAY_LAST(['a', 'b'])\")\n        self.validate_identity(\"JSON_TYPE(PARSE_JSON('1'))\")\n\n        self.validate_all(\n            \"SELECT CAST(col AS STRUCT<fld1 STRUCT<fld2 INT>>).fld1.fld2\",\n            write={\n                \"bigquery\": \"SELECT CAST(col AS STRUCT<fld1 STRUCT<fld2 INT64>>).fld1.fld2\",\n                \"snowflake\": \"SELECT CAST(col AS OBJECT(fld1 OBJECT(fld2 INT))):fld1.fld2\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT PARSE_DATETIME('%a %b %e %I:%M:%S %Y', 'Thu Dec 25 07:30:00 2008')\"\n        )\n        self.validate_identity(\"FORMAT_TIME('%R', CAST('15:30:00' AS TIME))\")\n        self.validate_identity(\"PARSE_TIME('%I:%M:%S', '07:30:00')\")\n        self.validate_identity(\"BYTE_LENGTH('foo')\")\n        self.validate_identity(\"BYTE_LENGTH(b'foo')\")\n        self.validate_identity(\"CODE_POINTS_TO_STRING([65, 255])\")\n        self.validate_identity(\"APPROX_TOP_COUNT(col, 2)\")\n        self.validate_identity(\"ARPOX_TOP_SUM(col, 1.5, 2)\")\n        self.validate_identity(\"SAFE_CONVERT_BYTES_TO_STRING(b'\\xc2')\")\n        self.validate_identity(\"FROM_HEX('foo')\")\n        self.validate_identity(\"TO_CODE_POINTS('foo')\")\n        self.validate_identity(\"CODE_POINTS_TO_BYTES([65, 98])\")\n        self.validate_identity(\"PARSE_BIGNUMERIC('1.2')\")\n        self.validate_identity(\"PARSE_NUMERIC('1.2')\")\n        self.validate_identity(\"BOOL(PARSE_JSON('true'))\")\n        self.validate_identity(\"FLOAT64(PARSE_JSON('9.8'))\")\n        self.validate_identity(\"FLOAT64(PARSE_JSON('9.8'), wide_number_mode => 'round')\")\n        self.validate_identity(\"FLOAT64(PARSE_JSON('9.8'), wide_number_mode => 'exact')\")\n        self.validate_identity(\"NORMALIZE_AND_CASEFOLD('foo')\")\n        self.validate_identity(\"NORMALIZE_AND_CASEFOLD('foo', NFKC)\")\n        self.validate_identity(\n            \"OCTET_LENGTH('foo')\",\n            \"BYTE_LENGTH('foo')\",\n        )\n        self.validate_identity(\n            \"OCTET_LENGTH(b'foo')\",\n            \"BYTE_LENGTH(b'foo')\",\n        )\n        self.validate_identity(\n            \"\"\"JSON_ARRAY_APPEND(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$', [1, 2], append_each_element => FALSE)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"JSON_ARRAY_INSERT(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$[1]', [1, 2], insert_each_element => FALSE)\"\"\"\n        )\n        self.validate_identity(\"\"\"JSON_KEYS(PARSE_JSON('{\"a\": {\"b\":1}}'))\"\"\")\n        self.validate_identity(\"\"\"JSON_KEYS(PARSE_JSON('{\"a\": {\"b\":1}}', 1))\"\"\")\n        self.validate_identity(\"\"\"JSON_KEYS(PARSE_JSON('{\"a\": {\"b\":1}}'), 1, mode => 'lax')\"\"\")\n        self.validate_identity(\n            \"\"\"JSON_SET(PARSE_JSON('{\"a\": 1}'), '$.b', 999, create_if_missing => FALSE)\"\"\"\n        )\n        self.validate_identity(\"\"\"JSON_STRIP_NULLS(PARSE_JSON('[1, null, 2, null, [null]]'))\"\"\")\n        self.validate_identity(\n            \"\"\"JSON_STRIP_NULLS(PARSE_JSON('[1, null, 2, null]'), include_arrays => FALSE)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"JSON_STRIP_NULLS(PARSE_JSON('{\"a\": {\"b\": {\"c\": null}}, \"d\": [null], \"e\": [], \"f\": 1}'), include_arrays => FALSE, remove_empty => TRUE)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"JSON_EXTRACT_STRING_ARRAY(PARSE_JSON('{\"fruits\": [\"apples\", \"oranges\", \"grapes\"]}'), '$.fruits')\"\"\",\n            \"\"\"JSON_VALUE_ARRAY(PARSE_JSON('{\"fruits\": [\"apples\", \"oranges\", \"grapes\"]}'), '$.fruits')\"\"\",\n        )\n        self.validate_identity(\"TO_JSON(STRUCT(1 AS id, [10, 20] AS cords))\")\n        self.validate_identity(\"TO_JSON(9999999999, stringify_wide_numbers => FALSE)\")\n        self.validate_identity(\"RANGE_BUCKET(20, [0, 10, 20, 30, 40])\")\n        self.validate_identity(\"SELECT TRANSLATE(MODEL, 'in', 't') FROM (SELECT 'input' AS MODEL)\")\n        self.validate_identity(\"SELECT GRANT FROM (SELECT 'input' AS GRANT)\")\n\n        self.validate_all(\n            \"SELECT 0xA\",\n            write={\n                \"bigquery\": \"SELECT 0xA\",\n                \"duckdb\": \"SELECT 10\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ARRAY_CONCAT_AGG(1)\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_FLATTEN(ARRAY_AGG(1))\",\n                \"bigquery\": \"SELECT ARRAY_CONCAT_AGG(1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT b'\\x61'\",\n            write={\n                \"bigquery\": \"SELECT b'\\x61'\",\n                \"duckdb\": \"SELECT CAST(e'\\x61' AS BLOB)\",\n                \"postgres\": \"SELECT CAST(e'\\x61' AS BYTEA)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT b'a'\",\n            write={\n                \"bigquery\": \"SELECT b'a'\",\n                \"duckdb\": \"SELECT CAST(e'a' AS BLOB)\",\n                \"postgres\": \"SELECT CAST(e'a' AS BYTEA)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT GENERATE_UUID()\",\n            write={\n                \"bigquery\": \"SELECT GENERATE_UUID()\",\n                \"duckdb\": \"SELECT CAST(UUID() AS TEXT)\",\n                \"spark2\": \"SELECT CAST(UUID() AS STRING)\",\n                \"spark\": \"SELECT CAST(UUID() AS STRING)\",\n                \"presto\": \"SELECT CAST(UUID() AS VARCHAR)\",\n                \"trino\": \"SELECT CAST(UUID() AS VARCHAR)\",\n                \"snowflake\": \"SELECT UUID_STRING()\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT REPLACE('apple pie', 'pie', 'cobbler') AS result\",\n            write={\n                \"bigquery\": \"SELECT REPLACE('apple pie', 'pie', 'cobbler') AS result\",\n                \"duckdb\": \"SELECT REPLACE('apple pie', 'pie', 'cobbler') AS result\",\n            },\n        )\n        expr = self.parse_one(\n            \"SELECT REPLACE(CAST('apple pie' AS BYTES), CAST('pie' AS BYTES), CAST('cobbler' AS BYTES)) AS result\"\n        )\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"SELECT CAST(REPLACE(CAST(CAST('apple pie' AS BLOB) AS TEXT), CAST(CAST('pie' AS BLOB) AS TEXT), CAST(CAST('cobbler' AS BLOB) AS TEXT)) AS BLOB) AS result\",\n        )\n        expr = self.parse_one(\"REPLACE('apple pie', 'pie', 'cobbler')\")\n        annotated = annotate_types(expr, dialect=\"bigquery\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"REPLACE('apple pie', 'pie', 'cobbler')\")\n\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(TIMESTAMP '2024-03-15 14:35:47.123456', DAY, 'America/New_York')\",\n            write={\n                \"bigquery\": \"TIMESTAMP_TRUNC(CAST('2024-03-15 14:35:47.123456' AS TIMESTAMP), DAY, 'America/New_York')\",\n                \"duckdb\": \"DATE_TRUNC('DAY', CAST('2024-03-15 14:35:47.123456' AS TIMESTAMPTZ) AT TIME ZONE 'America/New_York') AT TIME ZONE 'America/New_York'\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(TIMESTAMP '2024-03-15 14:35:00', MINUTE, 'America/New_York')\",\n            write={\n                \"bigquery\": \"TIMESTAMP_TRUNC(CAST('2024-03-15 14:35:00' AS TIMESTAMP), MINUTE, 'America/New_York')\",\n                \"duckdb\": \"DATE_TRUNC('MINUTE', CAST('2024-03-15 14:35:00' AS TIMESTAMPTZ))\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(TIMESTAMP '2024-03-15 14:35:47.123456', DAY)\",\n            write={\n                \"bigquery\": \"TIMESTAMP_TRUNC(CAST('2024-03-15 14:35:47.123456' AS TIMESTAMP), DAY)\",\n                \"duckdb\": \"DATE_TRUNC('DAY', CAST('2024-03-15 14:35:47.123456' AS TIMESTAMPTZ))\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(TIMESTAMP '2025-01-01 14:35:47.123456', MINUTE)\",\n            write={\n                \"bigquery\": \"TIMESTAMP_TRUNC(CAST('2025-01-01 14:35:47.123456' AS TIMESTAMP), MINUTE)\",\n                \"duckdb\": \"DATE_TRUNC('MINUTE', CAST('2025-01-01 14:35:47.123456' AS TIMESTAMPTZ))\",\n            },\n        )\n        self.validate_all(\n            \"WITH sample AS (SELECT * FROM UNNEST([TIMESTAMP '2024-03-15 14:35:46', TIMESTAMP '2024-03-16 01:12:03']) AS ts) SELECT ts, TIMESTAMP_TRUNC(ts, DAY, 'America/New_York') AS truncated_ts FROM sample\",\n            write={\n                \"bigquery\": \"WITH sample AS (SELECT * FROM UNNEST([CAST('2024-03-15 14:35:46' AS TIMESTAMP), CAST('2024-03-16 01:12:03' AS TIMESTAMP)]) AS ts) SELECT ts, TIMESTAMP_TRUNC(ts, DAY, 'America/New_York') AS truncated_ts FROM sample\",\n                \"duckdb\": \"WITH sample AS (SELECT * FROM UNNEST([CAST('2024-03-15 14:35:46' AS TIMESTAMPTZ), CAST('2024-03-16 01:12:03' AS TIMESTAMPTZ)]) AS _t0(ts)) SELECT ts, DATE_TRUNC('DAY', ts AT TIME ZONE 'America/New_York') AT TIME ZONE 'America/New_York' AS truncated_ts FROM sample\",\n            },\n        )\n        self.validate_all(\n            \"WITH sample AS (SELECT ts FROM UNNEST([TIMESTAMP '2024-03-15 14:35:46', TIMESTAMP '2024-03-16 01:12:03']) AS ts) SELECT ts, TIMESTAMP_TRUNC(ts, DAY) AS truncated_ts FROM sample\",\n            write={\n                \"bigquery\": \"WITH sample AS (SELECT ts FROM UNNEST([CAST('2024-03-15 14:35:46' AS TIMESTAMP), CAST('2024-03-16 01:12:03' AS TIMESTAMP)]) AS ts) SELECT ts, TIMESTAMP_TRUNC(ts, DAY) AS truncated_ts FROM sample\",\n                \"duckdb\": \"WITH sample AS (SELECT ts FROM UNNEST([CAST('2024-03-15 14:35:46' AS TIMESTAMPTZ), CAST('2024-03-16 01:12:03' AS TIMESTAMPTZ)]) AS _t0(ts)) SELECT ts, DATE_TRUNC('DAY', ts) AS truncated_ts FROM sample\",\n            },\n        )\n        self.validate_all(\n            \"WITH sample AS (SELECT * FROM UNNEST([TIMESTAMP '2024-03-15 14:35:46', TIMESTAMP '2024-03-16 01:12:03']) AS ts) SELECT ts, TIMESTAMP_TRUNC(ts, MINUTE, 'America/New_York') AS truncated_ts FROM sample\",\n            write={\n                \"bigquery\": \"WITH sample AS (SELECT * FROM UNNEST([CAST('2024-03-15 14:35:46' AS TIMESTAMP), CAST('2024-03-16 01:12:03' AS TIMESTAMP)]) AS ts) SELECT ts, TIMESTAMP_TRUNC(ts, MINUTE, 'America/New_York') AS truncated_ts FROM sample\",\n                \"duckdb\": \"WITH sample AS (SELECT * FROM UNNEST([CAST('2024-03-15 14:35:46' AS TIMESTAMPTZ), CAST('2024-03-16 01:12:03' AS TIMESTAMPTZ)]) AS _t0(ts)) SELECT ts, DATE_TRUNC('MINUTE', ts) AS truncated_ts FROM sample\",\n            },\n        )\n        self.validate_all(\n            \"WITH sample AS (SELECT * FROM UNNEST([TIMESTAMP '2024-03-15 14:35:46', TIMESTAMP '2024-03-16 01:12:03']) AS ts) SELECT ts, TIMESTAMP_TRUNC(ts, MINUTE) AS truncated_ts FROM sample\",\n            write={\n                \"bigquery\": \"WITH sample AS (SELECT * FROM UNNEST([CAST('2024-03-15 14:35:46' AS TIMESTAMP), CAST('2024-03-16 01:12:03' AS TIMESTAMP)]) AS ts) SELECT ts, TIMESTAMP_TRUNC(ts, MINUTE) AS truncated_ts FROM sample\",\n                \"duckdb\": \"WITH sample AS (SELECT * FROM UNNEST([CAST('2024-03-15 14:35:46' AS TIMESTAMPTZ), CAST('2024-03-16 01:12:03' AS TIMESTAMPTZ)]) AS _t0(ts)) SELECT ts, DATE_TRUNC('MINUTE', ts) AS truncated_ts FROM sample\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT GREATEST(1, NULL, 3)\",\n            write={\n                \"duckdb\": \"SELECT CASE WHEN 1 IS NULL OR NULL IS NULL OR 3 IS NULL THEN NULL ELSE GREATEST(1, NULL, 3) END\",\n                \"bigquery\": \"SELECT GREATEST(1, NULL, 3)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT LEAST(1, NULL, 3)\",\n            write={\n                \"duckdb\": \"SELECT CASE WHEN 1 IS NULL OR NULL IS NULL OR 3 IS NULL THEN NULL ELSE LEAST(1, NULL, 3) END\",\n                \"bigquery\": \"SELECT LEAST(1, NULL, 3)\",\n            },\n        )\n\n    def test_errors(self):\n        with self.assertRaises(ParseError):\n            self.parse_one(\"SELECT * FROM a - b.c.d2\")\n\n        with self.assertRaises(TokenError):\n            transpile(\"'\\\\'\", read=\"bigquery\")\n\n        # Reference: https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#set_operators\n        with self.assertRaises(UnsupportedError):\n            transpile(\n                \"SELECT * FROM a INTERSECT ALL SELECT * FROM b\",\n                write=\"bigquery\",\n                unsupported_level=ErrorLevel.RAISE,\n            )\n\n        with self.assertRaises(UnsupportedError):\n            transpile(\n                \"SELECT * FROM a EXCEPT ALL SELECT * FROM b\",\n                write=\"bigquery\",\n                unsupported_level=ErrorLevel.RAISE,\n            )\n\n        with self.assertRaises(ParseError):\n            transpile(\"SELECT * FROM UNNEST(x) AS x(y)\", read=\"bigquery\")\n\n        with self.assertRaises(ParseError):\n            transpile(\"DATE_ADD(x, day)\", read=\"bigquery\")\n\n    def test_warnings(self):\n        with self.assertLogs(helper_logger) as cm:\n            self.validate_identity(\n                \"WITH cte(c) AS (SELECT * FROM t) SELECT * FROM cte\",\n                \"WITH cte AS (SELECT * FROM t) SELECT * FROM cte\",\n            )\n\n            self.assertIn(\"Can't push down CTE column names for star queries.\", cm.output[0])\n            self.assertIn(\"Named columns are not supported in table alias.\", cm.output[1])\n\n        with self.assertLogs(helper_logger) as cm:\n            self.validate_identity(\n                \"SELECT * FROM t AS t(c1, c2)\",\n                \"SELECT * FROM t AS t\",\n            )\n\n            self.assertIn(\"Named columns are not supported in table alias.\", cm.output[0])\n\n        with self.assertLogs(helper_logger) as cm:\n            statements = parse(\n                \"\"\"\n            BEGIN\n              DECLARE 1;\n              IF from_date IS NULL THEN SET x = 1;\n              END IF;\n            END\n            \"\"\",\n                read=\"bigquery\",\n            )\n\n            for actual, expected in zip(\n                statements,\n                (\"BEGIN DECLARE 1\", \"IF from_date IS NULL THEN SET x = 1\", \"END IF\", \"END\"),\n            ):\n                self.assertEqual(actual.sql(dialect=\"bigquery\"), expected)\n\n            self.assertIn(\"unsupported syntax\", cm.output[0])\n\n        with self.assertLogs(helper_logger) as cm:\n            statements = parse(\n                \"\"\"\n                BEGIN CALL `project_id.dataset_id.stored_procedure_id`();\n                EXCEPTION WHEN ERROR THEN INSERT INTO `project_id.dataset_id.table_id` SELECT @@error.message, CURRENT_TIMESTAMP();\n                END\n                \"\"\",\n                read=\"bigquery\",\n            )\n\n            expected_statements = (\n                \"BEGIN CALL `project_id.dataset_id.stored_procedure_id`()\",\n                \"EXCEPTION WHEN ERROR THEN INSERT INTO `project_id.dataset_id.table_id` SELECT @@error.message, CURRENT_TIMESTAMP()\",\n                \"END\",\n            )\n\n            for actual, expected in zip(statements, expected_statements):\n                self.assertEqual(actual.sql(dialect=\"bigquery\"), expected)\n\n            self.assertIn(\"unsupported syntax\", cm.output[0])\n\n        with self.assertLogs(helper_logger):\n            statements = parse(\n                \"\"\"\n                BEGIN\n                    DECLARE MY_VAR INT64 DEFAULT 1;\n                    SET MY_VAR = (SELECT 0);\n\n                    IF MY_VAR = 1 THEN SELECT 'TRUE';\n                    ELSEIF MY_VAR = 0 THEN SELECT 'FALSE';\n                    ELSE SELECT 'NULL';\n                    END IF;\n                END\n                \"\"\",\n                read=\"bigquery\",\n            )\n\n            expected_statements = (\n                \"BEGIN DECLARE MY_VAR INT64 DEFAULT 1\",\n                \"SET MY_VAR = (SELECT 0)\",\n                \"IF MY_VAR = 1 THEN SELECT 'TRUE'\",\n                \"ELSEIF MY_VAR = 0 THEN SELECT 'FALSE'\",\n                \"ELSE SELECT 'NULL'\",\n                \"END IF\",\n                \"END\",\n            )\n\n            for actual, expected in zip(statements, expected_statements):\n                self.assertEqual(actual.sql(dialect=\"bigquery\"), expected)\n\n        with self.assertLogs(helper_logger) as cm:\n            self.validate_identity(\n                \"SELECT * FROM t AS t(c1, c2)\",\n                \"SELECT * FROM t AS t\",\n            )\n\n            self.assertIn(\"Named columns are not supported in table alias.\", cm.output[0])\n\n        with self.assertLogs(helper_logger):\n            self.validate_all(\n                \"SELECT a[1], b[OFFSET(1)], c[ORDINAL(1)], d[SAFE_OFFSET(1)], e[SAFE_ORDINAL(1)]\",\n                write={\n                    \"duckdb\": \"SELECT a[2], b[2], c[1], d[2], e[1]\",\n                    \"bigquery\": \"SELECT a[1], b[OFFSET(1)], c[ORDINAL(1)], d[SAFE_OFFSET(1)], e[SAFE_ORDINAL(1)]\",\n                    \"presto\": \"SELECT a[2], b[2], c[1], ELEMENT_AT(d, 2), ELEMENT_AT(e, 1)\",\n                },\n            )\n            self.validate_all(\n                \"a[0]\",\n                read={\n                    \"bigquery\": \"a[0]\",\n                    \"duckdb\": \"a[1]\",\n                    \"presto\": \"a[1]\",\n                },\n            )\n\n        with self.assertLogs(parser_logger) as cm:\n            for_in_stmts = parse(\n                \"FOR record IN (SELECT word FROM shakespeare) DO SELECT record.word; END FOR;\",\n                read=\"bigquery\",\n            )\n            self.assertEqual(\n                [s.sql(dialect=\"bigquery\") for s in for_in_stmts],\n                [\"FOR record IN (SELECT word FROM shakespeare) DO SELECT record.word\", \"END FOR\"],\n            )\n            self.assertIn(\"'END FOR'\", cm.output[0])\n\n        with self.assertLogs(parser_logger) as cm:\n            for_in_stmts = parse(\n                'FOR record IN (SELECT word FROM shakespeare) DO BEGIN SET x = \"SELECT 1\"; EXECUTE IMMEDIATE x; END; END FOR;',\n                read=\"bigquery\",\n            )\n            self.assertEqual(\n                [s.sql(dialect=\"bigquery\") for s in for_in_stmts],\n                [\n                    'FOR record IN (SELECT word FROM shakespeare) DO BEGIN SET x = \"SELECT 1\"',\n                    \"EXECUTE IMMEDIATE x\",\n                    \"END\",\n                    \"END FOR\",\n                ],\n            )\n            self.assertIn(\"FOR record\", cm.output[0])\n            self.assertIn(\"EXECUTE IMMEDIATE\", cm.output[1])\n            self.assertIn(\"END FOR\", cm.output[2])\n\n    def test_user_defined_functions(self):\n        self.validate_identity(\n            \"CREATE TEMPORARY FUNCTION a(x FLOAT64, y FLOAT64) RETURNS FLOAT64 NOT DETERMINISTIC LANGUAGE js AS 'return x*y;'\"\n        )\n        self.validate_identity(\"CREATE TEMPORARY FUNCTION udf(x ANY TYPE) AS (x)\")\n        self.validate_identity(\"CREATE TEMPORARY FUNCTION a(x FLOAT64, y FLOAT64) AS ((x + 4) / y)\")\n        self.validate_identity(\n            \"CREATE TABLE FUNCTION a(x INT64) RETURNS TABLE <q STRING, r INT64> AS SELECT s, t\"\n        )\n        self.validate_identity(\n            '''CREATE TEMPORARY FUNCTION string_length_0(strings ARRAY<STRING>) RETURNS FLOAT64 LANGUAGE js AS \"\"\"'use strict'; function string_length(strings) { return _.sum(_.map(strings, ((x) => x.length))); } return string_length(strings);\"\"\" OPTIONS (library=['gs://ibis-testing-libraries/lodash.min.js'])''',\n            \"CREATE TEMPORARY FUNCTION string_length_0(strings ARRAY<STRING>) RETURNS FLOAT64 LANGUAGE js OPTIONS (library=['gs://ibis-testing-libraries/lodash.min.js']) AS '\\\\'use strict\\\\'; function string_length(strings) { return _.sum(_.map(strings, ((x) => x.length))); } return string_length(strings);'\",\n        )\n\n    def test_remove_precision_parameterized_types(self):\n        self.validate_identity(\"CREATE TABLE test (a NUMERIC(10, 2))\")\n        self.validate_identity(\n            \"INSERT INTO test (cola, colb) VALUES (CAST(7 AS STRING(10)), CAST(14 AS STRING(10)))\",\n            \"INSERT INTO test (cola, colb) VALUES (CAST(7 AS STRING), CAST(14 AS STRING))\",\n        )\n        self.validate_identity(\n            \"SELECT CAST(1 AS NUMERIC(10, 2))\",\n            \"SELECT CAST(1 AS NUMERIC)\",\n        )\n        self.validate_identity(\n            \"SELECT CAST('1' AS STRING(10)) UNION ALL SELECT CAST('2' AS STRING(10))\",\n            \"SELECT CAST('1' AS STRING) UNION ALL SELECT CAST('2' AS STRING)\",\n        )\n        self.validate_identity(\n            \"SELECT cola FROM (SELECT CAST('1' AS STRING(10)) AS cola UNION ALL SELECT CAST('2' AS STRING(10)) AS cola)\",\n            \"SELECT cola FROM (SELECT CAST('1' AS STRING) AS cola UNION ALL SELECT CAST('2' AS STRING) AS cola)\",\n        )\n\n    def test_gap_fill(self):\n        self.validate_identity(\n            \"SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE, value_columns => [('signal', 'locf')]) ORDER BY time\"\n        )\n        self.validate_identity(\n            \"SELECT a, b, c, d, e FROM GAP_FILL(TABLE foo, ts_column => 'b', partitioning_columns => ['a'], value_columns => [('c', 'bar'), ('d', 'baz'), ('e', 'bla')], bucket_width => INTERVAL '1' DAY)\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE, value_columns => [('signal', 'linear')], ignore_null_values => FALSE) ORDER BY time\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE) ORDER BY time\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE, value_columns => [('signal', 'null')], origin => CAST('2023-11-01 09:30:01' AS DATETIME)) ORDER BY time\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE, value_columns => [('signal', 'locf')]) ORDER BY time\"\n        )\n\n    def test_models(self):\n        self.validate_identity(\n            \"CREATE OR REPLACE MODEL foo OPTIONS (model_type='linear_reg') AS SELECT bla FROM foo WHERE cond\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE OR REPLACE MODEL m\nTRANSFORM(\n  ML.FEATURE_CROSS(STRUCT(f1, f2)) AS cross_f,\n  ML.QUANTILE_BUCKETIZE(f3) OVER () AS buckets,\n  label_col\n)\nOPTIONS (\n  model_type='linear_reg',\n  input_label_cols=['label_col']\n) AS\nSELECT\n  *\nFROM t\"\"\",\n            pretty=True,\n        )\n        self.validate_identity(\n            \"\"\"CREATE MODEL project_id.mydataset.mymodel\nINPUT(\n  f1 INT64,\n  f2 FLOAT64,\n  f3 STRING,\n  f4 ARRAY<INT64>\n)\nOUTPUT(\n  out1 INT64,\n  out2 INT64\n)\nREMOTE WITH CONNECTION myproject.us.test_connection\nOPTIONS (\n  ENDPOINT='https://us-central1-aiplatform.googleapis.com/v1/projects/myproject/locations/us-central1/endpoints/1234'\n)\"\"\",\n            pretty=True,\n        )\n\n    def test_ml_functions(self):\n        ast = self.validate_identity(\n            \"SELECT * FROM ML.PREDICT(MODEL mydataset.mymodel, (SELECT label, column1, column2 FROM mydataset.mytable))\"\n        )\n        assert ast.find(exp.Predict)\n\n        self.validate_identity(\n            \"SELECT label, predicted_label1, predicted_label AS predicted_label2 FROM ML.PREDICT(MODEL mydataset.mymodel2, (SELECT * EXCEPT (predicted_label), predicted_label AS predicted_label1 FROM ML.PREDICT(MODEL mydataset.mymodel1, TABLE mydataset.mytable)))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM ML.PREDICT(MODEL mydataset.mymodel, (SELECT custom_label, column1, column2 FROM mydataset.mytable), STRUCT(0.55 AS threshold))\"\n        )\n        self.validate_identity(\"SELECT COSH(1.5)\")\n        self.validate_identity(\n            \"SELECT * FROM ML.PREDICT(MODEL `my_project`.my_dataset.my_model, (SELECT * FROM input_data))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM ML.PREDICT(MODEL my_dataset.vision_model, (SELECT uri, ML.RESIZE_IMAGE(ML.DECODE_IMAGE(data), 480, 480, FALSE) AS input FROM my_dataset.object_table))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM ML.PREDICT(MODEL my_dataset.vision_model, (SELECT uri, ML.CONVERT_COLOR_SPACE(ML.RESIZE_IMAGE(ML.DECODE_IMAGE(data), 224, 280, TRUE), 'YIQ') AS input FROM my_dataset.object_table WHERE content_type = 'image/jpeg'))\"\n        )\n        ast = self.validate_identity(\"SELECT * FROM ML.FEATURES_AT_TIME((SELECT 1), num_rows => 1)\")\n        assert ast.find(exp.FeaturesAtTime)\n        self.validate_identity(\n            \"SELECT * FROM ML.FEATURES_AT_TIME(TABLE mydataset.feature_table, time => '2022-06-11 10:00:00+00', num_rows => 1, ignore_feature_nulls => TRUE)\"\n        )\n\n        ast = self.validate_identity(\n            \"SELECT * FROM VECTOR_SEARCH(TABLE mydataset.base_table, 'column_to_search', TABLE mydataset.query_table, 'query_column_to_search', top_k => 2, distance_type => 'cosine', options => '{\\\"fraction_lists_to_search\\\":0.15}')\"\n        )\n        assert ast.find(exp.VectorSearch)\n        self.validate_identity(\n            \"SELECT * FROM VECTOR_SEARCH(TABLE mydataset.base_table, 'column_to_search', TABLE mydataset.query_table, query_column_to_search => 'query_column_to_search', top_k => 2, distance_type => 'cosine', options => '{\\\"fraction_lists_to_search\\\":0.15}')\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM VECTOR_SEARCH((SELECT * FROM mydataset.base_table), 'column_to_search', (SELECT * FROM mydataset.query_table), 'query_column_to_search')\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM VECTOR_SEARCH(TABLE mydataset.base_table, 'column_to_search', TABLE mydataset.query_table)\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM ML.TRANSLATE(MODEL `mydataset.mytranslatemodel`, TABLE `mydataset.mybqtable`, STRUCT('translate_text' AS translate_mode, 'zh-CN' AS target_language_code))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM ML.TRANSLATE(MODEL `mydataset.mymodel`, (SELECT comment AS text_content FROM mydataset.mytable), STRUCT('translate_text' AS translate_mode, 'en' AS target_language_code))\"\n        ).find(exp.MLTranslate).assert_is(exp.MLTranslate)\n        self.validate_identity(\"TRANSLATE(x, y, z)\").assert_is(exp.Translate)\n\n        ast = self.validate_identity(\n            \"SELECT * FROM ML.FORECAST(MODEL `mydataset.mymodel`, STRUCT(2 AS horizon))\"\n        )\n        assert ast.find(exp.MLForecast)\n        self.validate_identity(\n            \"SELECT * FROM ML.FORECAST(MODEL `mydataset.mymodel`, TABLE `mydataset.mybqtable`, STRUCT(2 AS horizon, 4 AS confidence_level))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM ML.FORECAST(MODEL `mydataset.mymodel`, (SELECT * FROM mydataset.query_table), STRUCT())\"\n        )\n\n        for name in (\"GENERATE_EMBEDDING\", \"GENERATE_TEXT_EMBEDDING\"):\n            with self.subTest(f\"Testing BigQuery's ML function {name}\"):\n                ast = self.validate_identity(\n                    f\"SELECT * FROM ML.{name}(MODEL mydataset.mymodel, (SELECT label, column1, column2 FROM mydataset.mytable))\"\n                )\n                self.validate_identity(\n                    f\"SELECT * FROM ML.{name}(MODEL mydataset.mymodel, TABLE mydataset.mytable, STRUCT(TRUE AS flatten_json_output))\"\n                )\n\n                assert ast.find(exp.GenerateEmbedding)\n\n    def test_merge(self):\n        self.validate_all(\n            \"\"\"\n            MERGE dataset.Inventory T\n            USING dataset.NewArrivals S ON FALSE\n            WHEN NOT MATCHED BY TARGET AND product LIKE '%a%'\n            THEN DELETE\n            WHEN NOT MATCHED BY SOURCE AND product LIKE '%b%'\n            THEN DELETE\"\"\",\n            write={\n                \"bigquery\": \"MERGE INTO dataset.Inventory AS T USING dataset.NewArrivals AS S ON FALSE WHEN NOT MATCHED AND product LIKE '%a%' THEN DELETE WHEN NOT MATCHED BY SOURCE AND product LIKE '%b%' THEN DELETE\",\n                \"snowflake\": \"MERGE INTO dataset.Inventory AS T USING dataset.NewArrivals AS S ON FALSE WHEN NOT MATCHED AND product LIKE '%a%' THEN DELETE WHEN NOT MATCHED AND product LIKE '%b%' THEN DELETE\",\n            },\n        )\n\n    def test_rename_table(self):\n        self.validate_all(\n            \"ALTER TABLE db.t1 RENAME TO db.t2\",\n            write={\n                \"snowflake\": \"ALTER TABLE db.t1 RENAME TO db.t2\",\n                \"bigquery\": \"ALTER TABLE db.t1 RENAME TO t2\",\n            },\n        )\n\n    @mock.patch(\"sqlglot.dialects.bigquery.logger\")\n    def test_pushdown_cte_column_names(self, logger):\n        with self.assertRaises(UnsupportedError):\n            transpile(\n                \"WITH cte(foo) AS (SELECT * FROM tbl) SELECT foo FROM cte\",\n                read=\"spark\",\n                write=\"bigquery\",\n                unsupported_level=ErrorLevel.RAISE,\n            )\n\n        self.validate_all(\n            \"WITH cte AS (SELECT 1 AS foo) SELECT foo FROM cte\",\n            read={\"spark\": \"WITH cte(foo) AS (SELECT 1) SELECT foo FROM cte\"},\n        )\n        self.validate_all(\n            \"WITH cte AS (SELECT 1 AS foo) SELECT foo FROM cte\",\n            read={\"spark\": \"WITH cte(foo) AS (SELECT 1 AS bar) SELECT foo FROM cte\"},\n        )\n        self.validate_all(\n            \"WITH cte AS (SELECT 1 AS bar) SELECT bar FROM cte\",\n            read={\"spark\": \"WITH cte AS (SELECT 1 AS bar) SELECT bar FROM cte\"},\n        )\n        self.validate_all(\n            \"WITH cte AS (SELECT 1 AS foo, 2) SELECT foo FROM cte\",\n            read={\"postgres\": \"WITH cte(foo) AS (SELECT 1, 2) SELECT foo FROM cte\"},\n        )\n        self.validate_all(\n            \"WITH cte AS (SELECT 1 AS foo UNION ALL SELECT 2) SELECT foo FROM cte\",\n            read={\"postgres\": \"WITH cte(foo) AS (SELECT 1 UNION ALL SELECT 2) SELECT foo FROM cte\"},\n        )\n\n    def test_json_object(self):\n        self.validate_identity(\"SELECT JSON_OBJECT() AS json_data\")\n        self.validate_identity(\"SELECT JSON_OBJECT('foo', 10, 'bar', TRUE) AS json_data\")\n        self.validate_identity(\"SELECT JSON_OBJECT('foo', 10, 'bar', ['a', 'b']) AS json_data\")\n        self.validate_identity(\"SELECT JSON_OBJECT('a', 10, 'a', 'foo') AS json_data\")\n        self.validate_identity(\n            \"SELECT JSON_OBJECT(['a', 'b'], [10, NULL]) AS json_data\",\n            \"SELECT JSON_OBJECT('a', 10, 'b', NULL) AS json_data\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT JSON_OBJECT(['a', 'b'], [JSON '10', JSON '\"foo\"']) AS json_data\"\"\",\n            \"\"\"SELECT JSON_OBJECT('a', PARSE_JSON('10'), 'b', PARSE_JSON('\"foo\"')) AS json_data\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT JSON_OBJECT(['a', 'b'], [STRUCT(10 AS id, 'Red' AS color), STRUCT(20 AS id, 'Blue' AS color)]) AS json_data\",\n            \"SELECT JSON_OBJECT('a', STRUCT(10 AS id, 'Red' AS color), 'b', STRUCT(20 AS id, 'Blue' AS color)) AS json_data\",\n        )\n        self.validate_identity(\n            \"SELECT JSON_OBJECT(['a', 'b'], [TO_JSON(10), TO_JSON(['foo', 'bar'])]) AS json_data\",\n            \"SELECT JSON_OBJECT('a', TO_JSON(10), 'b', TO_JSON(['foo', 'bar'])) AS json_data\",\n        )\n\n        with self.assertRaises(ParseError):\n            transpile(\"SELECT JSON_OBJECT('a', 1, 'b') AS json_data\", read=\"bigquery\")\n\n    def test_mod(self):\n        for sql in (\"MOD(a, b)\", \"MOD('a', b)\", \"MOD(5, 2)\", \"MOD((a + 1) * 8, 5 - 1)\"):\n            with self.subTest(f\"Testing BigQuery roundtrip of modulo operation: {sql}\"):\n                self.validate_identity(sql)\n\n        self.validate_identity(\"SELECT MOD((SELECT 1), 2)\")\n        self.validate_identity(\n            \"MOD((a + 1), b)\",\n            \"MOD(a + 1, b)\",\n        )\n\n    def test_inline_constructor(self):\n        self.validate_identity(\n            \"\"\"SELECT STRUCT<ARRAY<STRING>>([\"2023-01-17\"])\"\"\",\n            \"\"\"SELECT CAST(STRUCT(['2023-01-17']) AS STRUCT<ARRAY<STRING>>)\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT STRUCT<STRING>((SELECT 'foo')).*\"\"\",\n            \"\"\"SELECT CAST(STRUCT((SELECT 'foo')) AS STRUCT<STRING>).*\"\"\",\n        )\n\n        self.validate_all(\n            \"SELECT ARRAY<FLOAT64>[1, 2, 3]\",\n            write={\n                \"bigquery\": \"SELECT ARRAY<FLOAT64>[1, 2, 3]\",\n                \"duckdb\": \"SELECT CAST([1, 2, 3] AS DOUBLE[])\",\n            },\n        )\n        self.validate_all(\n            \"CAST(STRUCT<a INT64>(1) AS STRUCT<a INT64>)\",\n            write={\n                \"bigquery\": \"CAST(CAST(STRUCT(1) AS STRUCT<a INT64>) AS STRUCT<a INT64>)\",\n                \"duckdb\": \"CAST(CAST(ROW(1) AS STRUCT(a BIGINT)) AS STRUCT(a BIGINT))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST(ARRAY<STRUCT<x INT64>>[])\",\n            write={\n                \"bigquery\": \"SELECT * FROM UNNEST(ARRAY<STRUCT<x INT64>>[])\",\n                \"duckdb\": \"SELECT * FROM (SELECT UNNEST(CAST([] AS STRUCT(x BIGINT)[]), max_depth => 2))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST(ARRAY<STRUCT<device_id INT64, time DATETIME, signal INT64, state STRING>>[STRUCT(1, DATETIME '2023-11-01 09:34:01', 74, 'INACTIVE'),STRUCT(4, DATETIME '2023-11-01 09:38:01', 80, 'ACTIVE')])\",\n            write={\n                \"bigquery\": \"SELECT * FROM UNNEST(ARRAY<STRUCT<device_id INT64, time DATETIME, signal INT64, state STRING>>[STRUCT(1, CAST('2023-11-01 09:34:01' AS DATETIME), 74, 'INACTIVE'), STRUCT(4, CAST('2023-11-01 09:38:01' AS DATETIME), 80, 'ACTIVE')])\",\n                \"duckdb\": \"SELECT * FROM (SELECT UNNEST(CAST([ROW(1, CAST('2023-11-01 09:34:01' AS TIMESTAMP), 74, 'INACTIVE'), ROW(4, CAST('2023-11-01 09:38:01' AS TIMESTAMP), 80, 'ACTIVE')] AS STRUCT(device_id BIGINT, time TIMESTAMP, signal BIGINT, state TEXT)[]), max_depth => 2))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT STRUCT<a INT64, b STRUCT<c STRING>>(1, STRUCT('c_str'))\",\n            write={\n                \"bigquery\": \"SELECT CAST(STRUCT(1, STRUCT('c_str')) AS STRUCT<a INT64, b STRUCT<c STRING>>)\",\n                \"duckdb\": \"SELECT CAST(ROW(1, ROW('c_str')) AS STRUCT(a BIGINT, b STRUCT(c TEXT)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MAX_BY(name, score) FROM table1\",\n            write={\n                \"bigquery\": \"SELECT MAX_BY(name, score) FROM table1\",\n                \"duckdb\": \"SELECT ARG_MAX(name, score) FROM table1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MIN_BY(product, price) FROM table1\",\n            write={\n                \"bigquery\": \"SELECT MIN_BY(product, price) FROM table1\",\n                \"duckdb\": \"SELECT ARG_MIN(product, price) FROM table1\",\n            },\n        )\n\n    def test_convert(self):\n        for value, expected in [\n            (datetime.datetime(2023, 1, 1), \"CAST('2023-01-01 00:00:00' AS DATETIME)\"),\n            (datetime.datetime(2023, 1, 1, 12, 13, 14), \"CAST('2023-01-01 12:13:14' AS DATETIME)\"),\n            (\n                datetime.datetime(2023, 1, 1, 12, 13, 14, tzinfo=datetime.timezone.utc),\n                \"CAST('2023-01-01 12:13:14+00:00' AS TIMESTAMP)\",\n            ),\n            (\n                pytz.timezone(\"America/Los_Angeles\").localize(\n                    datetime.datetime(2023, 1, 1, 12, 13, 14)\n                ),\n                \"CAST('2023-01-01 12:13:14-08:00' AS TIMESTAMP)\",\n            ),\n        ]:\n            with self.subTest(value):\n                self.assertEqual(exp.convert(value).sql(dialect=self.dialect), expected)\n\n    def test_unnest(self):\n        self.validate_all(\n            \"SELECT name, laps FROM UNNEST([STRUCT('Rudisha' AS name, [23.4, 26.3, 26.4, 26.1] AS laps), STRUCT('Makhloufi' AS name, [24.5, 25.4, 26.6, 26.1] AS laps)])\",\n            write={\n                \"bigquery\": \"SELECT name, laps FROM UNNEST([STRUCT('Rudisha' AS name, [23.4, 26.3, 26.4, 26.1] AS laps), STRUCT('Makhloufi' AS name, [24.5, 25.4, 26.6, 26.1] AS laps)])\",\n                \"duckdb\": \"SELECT name, laps FROM (SELECT UNNEST([{'name': 'Rudisha', 'laps': [23.4, 26.3, 26.4, 26.1]}, {'name': 'Makhloufi', 'laps': [24.5, 25.4, 26.6, 26.1]}], max_depth => 2))\",\n            },\n        )\n        self.validate_all(\n            \"WITH Races AS (SELECT '800M' AS race) SELECT race, name, laps FROM Races AS r CROSS JOIN UNNEST([STRUCT('Rudisha' AS name, [23.4, 26.3, 26.4, 26.1] AS laps)])\",\n            write={\n                \"bigquery\": \"WITH Races AS (SELECT '800M' AS race) SELECT race, name, laps FROM Races AS r CROSS JOIN UNNEST([STRUCT('Rudisha' AS name, [23.4, 26.3, 26.4, 26.1] AS laps)])\",\n                \"duckdb\": \"WITH Races AS (SELECT '800M' AS race) SELECT race, name, laps FROM Races AS r CROSS JOIN (SELECT UNNEST([{'name': 'Rudisha', 'laps': [23.4, 26.3, 26.4, 26.1]}], max_depth => 2))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT participant FROM UNNEST([STRUCT('Rudisha' AS name, [23.4, 26.3, 26.4, 26.1] AS laps)]) AS participant\",\n            write={\n                \"bigquery\": \"SELECT participant FROM UNNEST([STRUCT('Rudisha' AS name, [23.4, 26.3, 26.4, 26.1] AS laps)]) AS participant\",\n                \"duckdb\": \"SELECT participant FROM (SELECT UNNEST([{'name': 'Rudisha', 'laps': [23.4, 26.3, 26.4, 26.1]}], max_depth => 2)) AS participant\",\n            },\n        )\n        self.validate_all(\n            \"WITH Races AS (SELECT '800M' AS race) SELECT race, participant FROM Races AS r CROSS JOIN UNNEST([STRUCT('Rudisha' AS name, [23.4, 26.3, 26.4, 26.1] AS laps)]) AS participant\",\n            write={\n                \"bigquery\": \"WITH Races AS (SELECT '800M' AS race) SELECT race, participant FROM Races AS r CROSS JOIN UNNEST([STRUCT('Rudisha' AS name, [23.4, 26.3, 26.4, 26.1] AS laps)]) AS participant\",\n                \"duckdb\": \"WITH Races AS (SELECT '800M' AS race) SELECT race, participant FROM Races AS r CROSS JOIN (SELECT UNNEST([{'name': 'Rudisha', 'laps': [23.4, 26.3, 26.4, 26.1]}], max_depth => 2)) AS participant\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM UNNEST([STRUCT('Alice' AS name, STRUCT(85 AS math, 90 AS english) AS scores), STRUCT('Bob' AS name, STRUCT(92 AS math, 88 AS english) AS scores)])\",\n            write={\n                \"bigquery\": \"SELECT * FROM UNNEST([STRUCT('Alice' AS name, STRUCT(85 AS math, 90 AS english) AS scores), STRUCT('Bob' AS name, STRUCT(92 AS math, 88 AS english) AS scores)])\",\n                \"duckdb\": \"SELECT * FROM (SELECT UNNEST([{'name': 'Alice', 'scores': {'math': 85, 'english': 90}}, {'name': 'Bob', 'scores': {'math': 92, 'english': 88}}], max_depth => 2))\",\n                \"snowflake\": \"SELECT * FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('name', 'Alice', 'scores', OBJECT_CONSTRUCT('math', 85, 'english', 90)), OBJECT_CONSTRUCT('name', 'Bob', 'scores', OBJECT_CONSTRUCT('math', 92, 'english', 88))])) AS _t0(seq, key, path, index, value, this)\",\n                \"presto\": \"SELECT * FROM UNNEST(ARRAY[CAST(ROW('Alice', CAST(ROW(85, 90) AS ROW(math INTEGER, english INTEGER))) AS ROW(name VARCHAR, scores ROW(math INTEGER, english INTEGER))), CAST(ROW('Bob', CAST(ROW(92, 88) AS ROW(math INTEGER, english INTEGER))) AS ROW(name VARCHAR, scores ROW(math INTEGER, english INTEGER)))])\",\n                \"trino\": \"SELECT * FROM UNNEST(ARRAY[CAST(ROW('Alice', CAST(ROW(85, 90) AS ROW(math INTEGER, english INTEGER))) AS ROW(name VARCHAR, scores ROW(math INTEGER, english INTEGER))), CAST(ROW('Bob', CAST(ROW(92, 88) AS ROW(math INTEGER, english INTEGER))) AS ROW(name VARCHAR, scores ROW(math INTEGER, english INTEGER)))])\",\n                \"spark2\": \"SELECT * FROM EXPLODE(ARRAY(STRUCT('Alice' AS name, STRUCT(85 AS math, 90 AS english) AS scores), STRUCT('Bob' AS name, STRUCT(92 AS math, 88 AS english) AS scores)))\",\n                \"databricks\": \"SELECT * FROM EXPLODE(ARRAY(STRUCT('Alice' AS name, STRUCT(85 AS math, 90 AS english) AS scores), STRUCT('Bob' AS name, STRUCT(92 AS math, 88 AS english) AS scores)))\",\n                \"hive\": \"SELECT * FROM EXPLODE(ARRAY(STRUCT('Alice', STRUCT(85, 90)), STRUCT('Bob', STRUCT(92, 88))))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM UNNEST([STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob', 92), STRUCT('Diana', 95)])\",\n            write={\n                \"bigquery\": \"SELECT * FROM UNNEST([STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob', 92), STRUCT('Diana', 95)])\",\n                \"duckdb\": \"SELECT * FROM (SELECT UNNEST([{'name': 'Alice', 'score': 85}, {'name': 'Bob', 'score': 92}, {'name': 'Diana', 'score': 95}], max_depth => 2))\",\n                \"snowflake\": \"SELECT * FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('name', 'Alice', 'score', 85), OBJECT_CONSTRUCT('name', 'Bob', 'score', 92), OBJECT_CONSTRUCT('name', 'Diana', 'score', 95)])) AS _t0(seq, key, path, index, value, this)\",\n                \"presto\": \"SELECT * FROM UNNEST(ARRAY[CAST(ROW('Alice', 85) AS ROW(name VARCHAR, score INTEGER)), CAST(ROW('Bob', 92) AS ROW(name VARCHAR, score INTEGER)), CAST(ROW('Diana', 95) AS ROW(name VARCHAR, score INTEGER))])\",\n                \"trino\": \"SELECT * FROM UNNEST(ARRAY[CAST(ROW('Alice', 85) AS ROW(name VARCHAR, score INTEGER)), CAST(ROW('Bob', 92) AS ROW(name VARCHAR, score INTEGER)), CAST(ROW('Diana', 95) AS ROW(name VARCHAR, score INTEGER))])\",\n                \"spark2\": \"SELECT * FROM EXPLODE(ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS name, 92 AS score), STRUCT('Diana' AS name, 95 AS score)))\",\n                \"databricks\": \"SELECT * FROM EXPLODE(ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS name, 92 AS score), STRUCT('Diana' AS name, 95 AS score)))\",\n                \"hive\": \"SELECT * FROM EXPLODE(ARRAY(STRUCT('Alice', 85), STRUCT('Bob', 92), STRUCT('Diana', 95)))\",\n            },\n        )\n\n    def test_range_type(self):\n        for type, value in (\n            (\"RANGE<DATE>\", \"'[2020-01-01, 2020-12-31)'\"),\n            (\"RANGE<DATE>\", \"'[UNBOUNDED, 2020-12-31)'\"),\n            (\"RANGE<DATETIME>\", \"'[2020-01-01 12:00:00, 2020-12-31 12:00:00)'\"),\n            (\"RANGE<TIMESTAMP>\", \"'[2020-10-01 12:00:00+08, 2020-12-31 12:00:00+08)'\"),\n        ):\n            with self.subTest(f\"Testing BigQuery's RANGE<T> type: {type} {value}\"):\n                self.validate_identity(f\"SELECT {type} {value}\", f\"SELECT CAST({value} AS {type})\")\n\n                self.assertEqual(self.parse_one(type), exp.DataType.build(type, dialect=\"bigquery\"))\n\n        self.validate_identity(\n            \"SELECT RANGE(CAST('2022-12-01' AS DATE), CAST('2022-12-31' AS DATE))\"\n        )\n        self.validate_identity(\"SELECT RANGE(NULL, CAST('2022-12-31' AS DATE))\")\n        self.validate_identity(\n            \"SELECT RANGE(CAST('2022-10-01 14:53:27' AS DATETIME), CAST('2022-10-01 16:00:00' AS DATETIME))\"\n        )\n        self.validate_identity(\n            \"SELECT RANGE(CAST('2022-10-01 14:53:27 America/Los_Angeles' AS TIMESTAMP), CAST('2022-10-01 16:00:00 America/Los_Angeles' AS TIMESTAMP))\"\n        )\n\n    def test_null_ordering(self):\n        # Aggregate functions allow \"NULLS FIRST\" only with ascending order and\n        # \"NULLS LAST\" only with descending\n        for sort_order, null_order in ((\"ASC\", \"NULLS LAST\"), (\"DESC\", \"NULLS FIRST\")):\n            self.validate_all(\n                f\"SELECT color, ARRAY_AGG(id ORDER BY id {sort_order}) AS ids FROM colors GROUP BY 1\",\n                read={\n                    \"\": f\"SELECT color, ARRAY_AGG(id ORDER BY id {sort_order} {null_order}) AS ids FROM colors GROUP BY 1\"\n                },\n                write={\n                    \"bigquery\": f\"SELECT color, ARRAY_AGG(id ORDER BY id {sort_order}) AS ids FROM colors GROUP BY 1\",\n                },\n            )\n\n            self.validate_all(\n                f\"SELECT SUM(f1) OVER (ORDER BY f2 {sort_order}) FROM t\",\n                read={\n                    \"\": f\"SELECT SUM(f1) OVER (ORDER BY f2 {sort_order} {null_order}) FROM t\",\n                },\n                write={\n                    \"bigquery\": f\"SELECT SUM(f1) OVER (ORDER BY f2 {sort_order}) FROM t\",\n                },\n            )\n\n    def test_null_ordering_in_analytic_functions(self):\n        for func_call in (\n            \"FIRST_VALUE(col1)\",\n            \"LAST_VALUE(col1)\",\n            \"NTH_VALUE(col1, 2)\",\n        ):\n            for sort_order, null_order in ((\"ASC\", \"NULLS LAST\"), (\"DESC\", \"NULLS FIRST\")):\n                with self.subTest(f\"{func_call} with {sort_order} {null_order} ROWS\"):\n                    self.validate_identity(\n                        f\"WITH t AS (SELECT 1 AS id, 2 AS col1) SELECT {func_call} OVER (PARTITION BY id ORDER BY col1 {sort_order} {null_order} ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t\"\n                    )\n\n        for func_call in (\n            \"LAG(col1)\",\n            \"LEAD(col1)\",\n            \"CUME_DIST()\",\n            \"DENSE_RANK()\",\n            \"NTILE(4)\",\n            \"PERCENT_RANK()\",\n            \"RANK()\",\n            \"ROW_NUMBER()\",\n        ):\n            for sort_order, null_order in ((\"ASC\", \"NULLS LAST\"), (\"DESC\", \"NULLS FIRST\")):\n                with self.subTest(f\"{func_call} with {sort_order} {null_order}\"):\n                    self.validate_identity(\n                        f\"WITH t AS (SELECT 1 AS id, 2 AS col1) SELECT {func_call} OVER (PARTITION BY id ORDER BY col1 {sort_order} {null_order}) FROM t\"\n                    )\n\n    def test_json_extract(self):\n        self.validate_all(\n            \"\"\"SELECT JSON_QUERY('{\"class\": {\"students\": []}}', '$.class')\"\"\",\n            write={\n                \"bigquery\": \"\"\"SELECT JSON_QUERY('{\"class\": {\"students\": []}}', '$.class')\"\"\",\n                \"duckdb\": \"\"\"SELECT '{\"class\": {\"students\": []}}' -> '$.class'\"\"\",\n                \"snowflake\": \"\"\"SELECT GET_PATH(PARSE_JSON('{\"class\": {\"students\": []}}'), 'class')\"\"\",\n            },\n        )\n\n        self.validate_all(\n            \"\"\"SELECT JSON_QUERY(foo, '$.class')\"\"\",\n            write={\n                \"bigquery\": \"\"\"SELECT JSON_QUERY(foo, '$.class')\"\"\",\n                \"snowflake\": \"\"\"SELECT GET_PATH(PARSE_JSON(foo), 'class')\"\"\",\n            },\n        )\n\n        for func in (\"JSON_EXTRACT_SCALAR\", \"JSON_VALUE\"):\n            with self.subTest(f\"Testing BigQuery's {func}\"):\n                self.validate_all(\n                    f\"SELECT {func}('5')\",\n                    write={\n                        \"bigquery\": f\"SELECT {func}('5', '$')\",\n                        \"duckdb\": \"\"\"SELECT JSON_VALUE('5', '$') ->> '$'\"\"\",\n                    },\n                )\n\n                sql = f\"\"\"SELECT {func}('{{\"name\": \"Jakob\", \"age\": \"6\"}}', '$.age')\"\"\"\n                self.validate_all(\n                    sql,\n                    write={\n                        \"bigquery\": sql,\n                        \"duckdb\": \"\"\"SELECT JSON_VALUE('{\"name\": \"Jakob\", \"age\": \"6\"}', '$.age') ->> '$'\"\"\",\n                        \"snowflake\": \"\"\"SELECT JSON_EXTRACT_PATH_TEXT('{\"name\": \"Jakob\", \"age\": \"6\"}', 'age')\"\"\",\n                    },\n                )\n\n        self.assertEqual(self.parse_one(sql).sql(\"bigquery\", normalize_functions=\"upper\"), sql)\n\n        # Test double quote escaping\n        for func in (\"JSON_VALUE\", \"JSON_QUERY\", \"JSON_QUERY_ARRAY\"):\n            self.validate_identity(\n                f\"{func}(doc, '$. a b c .d')\", f\"\"\"{func}(doc, '$.\" a b c \".d')\"\"\"\n            )\n\n        # Test single quote & bracket escaping\n        for func in (\"JSON_EXTRACT\", \"JSON_EXTRACT_SCALAR\", \"JSON_EXTRACT_ARRAY\"):\n            self.validate_identity(\n                f\"{func}(doc, '$. a b c .d')\", f\"\"\"{func}(doc, '$[\\\\' a b c \\\\'].d')\"\"\"\n            )\n\n    def test_json_extract_array(self):\n        for func in (\"JSON_QUERY_ARRAY\", \"JSON_EXTRACT_ARRAY\"):\n            with self.subTest(f\"Testing BigQuery's {func}\"):\n                sql = f\"\"\"SELECT {func}('{{\"fruits\": [1, \"oranges\"]}}', '$.fruits')\"\"\"\n                self.validate_all(\n                    sql,\n                    write={\n                        \"bigquery\": sql,\n                        \"duckdb\": \"\"\"SELECT CAST('{\"fruits\": [1, \"oranges\"]}' -> '$.fruits' AS JSON[])\"\"\",\n                        \"snowflake\": \"\"\"SELECT TRANSFORM(GET_PATH(PARSE_JSON('{\"fruits\": [1, \"oranges\"]}'), 'fruits'), x -> PARSE_JSON(TO_JSON(x)))\"\"\",\n                    },\n                )\n\n                self.assertEqual(\n                    self.parse_one(sql).sql(\"bigquery\", normalize_functions=\"upper\"), sql\n                )\n\n    def test_unix_seconds(self):\n        self.validate_all(\n            \"SELECT UNIX_SECONDS('2008-12-25 15:30:00+00')\",\n            read={\n                \"bigquery\": \"SELECT UNIX_SECONDS('2008-12-25 15:30:00+00')\",\n                \"spark\": \"SELECT UNIX_SECONDS('2008-12-25 15:30:00+00')\",\n                \"databricks\": \"SELECT UNIX_SECONDS('2008-12-25 15:30:00+00')\",\n            },\n            write={\n                \"spark\": \"SELECT UNIX_SECONDS('2008-12-25 15:30:00+00')\",\n                \"databricks\": \"SELECT UNIX_SECONDS('2008-12-25 15:30:00+00')\",\n                \"duckdb\": \"SELECT CAST(EPOCH(CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ)) AS BIGINT)\",\n                \"snowflake\": \"SELECT TIMESTAMPDIFF(SECONDS, CAST('1970-01-01 00:00:00+00' AS TIMESTAMPTZ), '2008-12-25 15:30:00+00')\",\n            },\n        )\n\n        for dialect in (\"bigquery\", \"spark\", \"databricks\"):\n            parse_one(\"UNIX_SECONDS(col)\", dialect=dialect).assert_is(exp.UnixSeconds)\n\n    def test_unix_micros(self):\n        self.validate_all(\n            \"SELECT UNIX_MICROS('2008-12-25 15:30:00+00')\",\n            write={\n                \"bigquery\": \"SELECT UNIX_MICROS('2008-12-25 15:30:00+00')\",\n                \"duckdb\": \"SELECT EPOCH_US(CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNIX_MICROS(TIMESTAMP '2008-12-25 15:30:00+00')\",\n            write={\n                \"bigquery\": \"SELECT UNIX_MICROS(CAST('2008-12-25 15:30:00+00' AS TIMESTAMP))\",\n                \"duckdb\": \"SELECT EPOCH_US(CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ))\",\n            },\n        )\n\n    def test_unix_millis(self):\n        self.validate_all(\n            \"SELECT UNIX_MILLIS('2008-12-25 15:30:00+00')\",\n            write={\n                \"bigquery\": \"SELECT UNIX_MILLIS('2008-12-25 15:30:00+00')\",\n                \"duckdb\": \"SELECT EPOCH_MS(CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNIX_MILLIS(TIMESTAMP '2008-12-25 15:30:00+00')\",\n            write={\n                \"bigquery\": \"SELECT UNIX_MILLIS(CAST('2008-12-25 15:30:00+00' AS TIMESTAMP))\",\n                \"duckdb\": \"SELECT EPOCH_MS(CAST('2008-12-25 15:30:00+00' AS TIMESTAMPTZ))\",\n            },\n        )\n\n    def test_regexp_extract(self):\n        self.validate_identity(\"REGEXP_EXTRACT(x, '(?<)')\")\n        self.validate_identity(\"REGEXP_EXTRACT(`foo`, 'bar: (.+?)', 1, 1)\")\n        self.validate_identity(\n            r\"REGEXP_EXTRACT(svc_plugin_output, r'\\\\\\((.*)')\",\n            r\"REGEXP_EXTRACT(svc_plugin_output, '\\\\\\\\\\\\((.*)')\",\n        )\n        self.validate_identity(\n            r\"REGEXP_SUBSTR(value, pattern, position, occurrence)\",\n            r\"REGEXP_EXTRACT(value, pattern, position, occurrence)\",\n        )\n\n        self.validate_all(\n            \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)') FROM table\",\n            write={\n                \"bigquery\": \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)') FROM table\",\n                \"duckdb\": '''SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 1) FROM \"table\"''',\n            },\n        )\n\n        # Position = 1\n        self.validate_all(\n            \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 1) FROM table\",\n            write={\n                \"bigquery\": \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 1) FROM table\",\n                \"duckdb\": '''SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 1) FROM \"table\"''',\n            },\n        )\n\n        # Position = 2\n        self.validate_all(\n            \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 2) FROM table\",\n            write={\n                \"bigquery\": \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 2) FROM table\",\n                \"duckdb\": '''SELECT REGEXP_EXTRACT(NULLIF(SUBSTRING(abc, 2), ''), 'pattern(group)', 1) FROM \"table\"''',\n            },\n        )\n\n        # Position = 1, occurrence = 1\n        self.validate_all(\n            \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 1, 1) FROM table\",\n            write={\n                \"bigquery\": \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 1, 1) FROM table\",\n                \"duckdb\": '''SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 1) FROM \"table\"''',\n            },\n        )\n\n        # Position = 2, occurrence = 3\n        self.validate_all(\n            \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 2, 3) FROM table\",\n            write={\n                \"bigquery\": \"SELECT REGEXP_EXTRACT(abc, 'pattern(group)', 2, 3) FROM table\",\n                \"duckdb\": '''SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(NULLIF(SUBSTRING(abc, 2), ''), 'pattern(group)', 1), 3) FROM \"table\"''',\n            },\n        )\n\n        # The pattern does not capture a group (entire regular expression is extracted)\n        self.validate_all(\n            \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n            read={\n                \"bigquery\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"trino\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"presto\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"snowflake\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"spark\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]', 0)\",\n                \"databricks\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]', 0)\",\n            },\n            write={\n                \"bigquery\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"trino\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"presto\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"snowflake\": \"REGEXP_SUBSTR_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"duckdb\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]')\",\n                \"spark\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]', 0)\",\n                \"databricks\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', 'a[0-9]', 0)\",\n            },\n        )\n\n        # The pattern does capture >=1 group (the default is to extract the first instance)\n        self.validate_all(\n            \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', '(a)[0-9]')\",\n            write={\n                \"bigquery\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', '(a)[0-9]')\",\n                \"trino\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', '(a)[0-9]', 1)\",\n                \"presto\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', '(a)[0-9]', 1)\",\n                \"snowflake\": \"REGEXP_SUBSTR_ALL('a1_a2a3_a4A5a6', '(a)[0-9]', 1, 1, 'c', 1)\",\n                \"duckdb\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', '(a)[0-9]', 1)\",\n                \"spark\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', '(a)[0-9]')\",\n                \"databricks\": \"REGEXP_EXTRACT_ALL('a1_a2a3_a4A5a6', '(a)[0-9]')\",\n            },\n        )\n\n    def test_format_temporal(self):\n        self.validate_all(\n            \"SELECT FORMAT_DATE('%Y%m%d', '2023-12-25')\",\n            write={\n                \"bigquery\": \"SELECT FORMAT_DATE('%Y%m%d', '2023-12-25')\",\n                \"duckdb\": \"SELECT STRFTIME(CAST('2023-12-25' AS DATE), '%Y%m%d')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT_DATETIME('%Y%m%d %H:%M:%S', DATETIME '2023-12-25 15:30:00')\",\n            write={\n                \"bigquery\": \"SELECT FORMAT_DATETIME('%Y%m%d %T', CAST('2023-12-25 15:30:00' AS DATETIME))\",\n                \"duckdb\": \"SELECT STRFTIME(CAST('2023-12-25 15:30:00' AS TIMESTAMP), '%Y%m%d %H:%M:%S')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT_DATETIME('%x', '2023-12-25 15:30:00')\",\n            write={\n                \"bigquery\": \"SELECT FORMAT_DATETIME('%D', '2023-12-25 15:30:00')\",\n                \"duckdb\": \"SELECT STRFTIME(CAST('2023-12-25 15:30:00' AS TIMESTAMP), '%m/%d/%y')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT_DATETIME('%F %T', DATETIME '2023-10-15 14:30:45')\",\n            write={\n                \"bigquery\": \"SELECT FORMAT_DATETIME('%F %T', CAST('2023-10-15 14:30:45' AS DATETIME))\",\n                \"duckdb\": \"SELECT STRFTIME(CAST('2023-10-15 14:30:45' AS TIMESTAMP), '%Y-%m-%d %H:%M:%S')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT_DATETIME('%c', DATETIME '2008-12-25 15:30:00')\",\n            write={\n                \"bigquery\": \"SELECT FORMAT_DATETIME('%c', CAST('2008-12-25 15:30:00' AS DATETIME))\",\n                \"duckdb\": \"SELECT STRFTIME(CAST('2008-12-25 15:30:00' AS TIMESTAMP), '%a %b %-d %H:%M:%S %Y')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT_DATETIME('%Y-%m-%e', DATETIME '2020-09-09 10:15:30')\",\n            write={\n                \"bigquery\": \"SELECT FORMAT_DATETIME('%Y-%m-%e', CAST('2020-09-09 10:15:30' AS DATETIME))\",\n                \"duckdb\": \"SELECT STRFTIME(CAST('2020-09-09 10:15:30' AS TIMESTAMP), '%Y-%m-%-d')\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT FORMAT_TIMESTAMP(\"%b-%d-%Y\", TIMESTAMP \"2050-12-25 15:30:55+00\")\"\"\",\n            write={\n                \"bigquery\": \"SELECT FORMAT_TIMESTAMP('%b-%d-%Y', CAST('2050-12-25 15:30:55+00' AS TIMESTAMP))\",\n                \"duckdb\": \"SELECT STRFTIME(CAST(CAST('2050-12-25 15:30:55+00' AS TIMESTAMPTZ) AS TIMESTAMP), '%b-%d-%Y')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST(CAST('2050-12-25 15:30:55+00' AS TIMESTAMPTZ) AS TIMESTAMP), 'mon-DD-yyyy')\",\n            },\n        )\n\n    def test_string_agg(self):\n        self.validate_identity(\"STRING_AGG(a, ' & ')\")\n        self.validate_identity(\"STRING_AGG(DISTINCT a, ' & ')\")\n        self.validate_identity(\"STRING_AGG(a, ' & ' ORDER BY LENGTH(a))\")\n        self.validate_identity(\"STRING_AGG(foo, b'|' ORDER BY bar)\")\n        self.validate_identity(\"STRING_AGG(a)\")\n        self.validate_identity(\"STRING_AGG(DISTINCT v, sep LIMIT 3)\")\n        self.validate_identity(\"STRING_AGG(DISTINCT a ORDER BY b DESC, c DESC LIMIT 10)\")\n        self.validate_identity(\n            \"SELECT a, GROUP_CONCAT(b) FROM table GROUP BY a\",\n            \"SELECT a, STRING_AGG(b) FROM table GROUP BY a\",\n        )\n\n    def test_annotate_timestamps(self):\n        sql = \"\"\"\n        SELECT\n          CURRENT_TIMESTAMP() AS curr_ts,\n          TIMESTAMP_SECONDS(2) AS ts_seconds,\n          PARSE_TIMESTAMP('%c', 'Thu Dec 25 07:30:00 2008', 'UTC') AS parsed_ts,\n          TIMESTAMP_ADD(TIMESTAMP \"2008-12-25 15:30:00+00\", INTERVAL 10 MINUTE) AS ts_add,\n          TIMESTAMP_SUB(TIMESTAMP \"2008-12-25 15:30:00+00\", INTERVAL 10 MINUTE) AS ts_sub,\n        \"\"\"\n\n        annotated = annotate_types(self.parse_one(sql), dialect=\"bigquery\")\n\n        for select in annotated.selects:\n            self.assertEqual(select.type.sql(\"bigquery\"), \"TIMESTAMP\")\n\n    def test_set_operations(self):\n        self.validate_identity(\"SELECT 1 AS foo INNER UNION ALL SELECT 3 AS foo, 4 AS bar\")\n\n        for side in (\"\", \" LEFT\", \" FULL\"):\n            for kind in (\"\", \" OUTER\"):\n                for name in (\n                    \"\",\n                    \" BY NAME\",\n                    \" BY NAME ON (foo, bar)\",\n                ):\n                    with self.subTest(f\"Testing {side} {kind} {name} in test_set_operations\"):\n                        self.validate_identity(\n                            f\"SELECT 1 AS foo{side}{kind} UNION ALL{name} SELECT 3 AS foo, 4 AS bar\",\n                        )\n\n        self.validate_identity(\n            \"SELECT 1 AS x UNION ALL CORRESPONDING SELECT 2 AS x\",\n            \"SELECT 1 AS x INNER UNION ALL BY NAME SELECT 2 AS x\",\n        )\n\n        self.validate_identity(\n            \"SELECT 1 AS x UNION ALL CORRESPONDING BY (foo, bar) SELECT 2 AS x\",\n            \"SELECT 1 AS x INNER UNION ALL BY NAME ON (foo, bar) SELECT 2 AS x\",\n        )\n\n        self.validate_identity(\n            \"SELECT 1 AS x LEFT UNION ALL CORRESPONDING SELECT 2 AS x\",\n            \"SELECT 1 AS x LEFT UNION ALL BY NAME SELECT 2 AS x\",\n        )\n\n        self.validate_identity(\n            \"SELECT 1 AS x UNION ALL STRICT CORRESPONDING SELECT 2 AS x\",\n            \"SELECT 1 AS x UNION ALL BY NAME SELECT 2 AS x\",\n        )\n\n        self.validate_identity(\n            \"SELECT 1 AS x UNION ALL STRICT CORRESPONDING BY (foo, bar) SELECT 2 AS x\",\n            \"SELECT 1 AS x UNION ALL BY NAME ON (foo, bar) SELECT 2 AS x\",\n        )\n\n    def test_with_offset(self):\n        self.validate_identity(\n            \"SELECT * FROM UNNEST(x) WITH OFFSET EXCEPT DISTINCT SELECT * FROM UNNEST(y) WITH OFFSET\",\n            \"SELECT * FROM UNNEST(x) WITH OFFSET AS offset EXCEPT DISTINCT SELECT * FROM UNNEST(y) WITH OFFSET AS offset\",\n        )\n\n        for join_ops in (\"LEFT\", \"RIGHT\", \"FULL\", \"NATURAL\", \"SEMI\", \"ANTI\"):\n            with self.subTest(f\"Testing {join_ops} in test_with_offset\"):\n                self.validate_identity(\n                    f\"SELECT * FROM t1, UNNEST([1, 2]) AS hit WITH OFFSET {join_ops} JOIN foo\",\n                    f\"SELECT * FROM t1 CROSS JOIN UNNEST([1, 2]) AS hit WITH OFFSET AS offset {join_ops} JOIN foo\",\n                )\n\n    def test_identifier_meta(self):\n        ast = parse_one(\n            \"SELECT a, b FROM test_schema.test_table_a UNION ALL SELECT c, d FROM test_catalog.test_schema.test_table_b\",\n            dialect=\"bigquery\",\n        )\n        for identifier in ast.find_all(exp.Identifier):\n            self.assertEqual(set(identifier.meta), {\"line\", \"col\", \"start\", \"end\"})\n\n        self.assertEqual(\n            ast.this.args[\"from_\"].this.args[\"this\"].meta,\n            {\"line\": 1, \"col\": 41, \"start\": 29, \"end\": 40},\n        )\n        self.assertEqual(\n            ast.this.args[\"from_\"].this.args[\"db\"].meta,\n            {\"line\": 1, \"col\": 28, \"start\": 17, \"end\": 27},\n        )\n        self.assertEqual(\n            ast.expression.args[\"from_\"].this.args[\"this\"].meta,\n            {\"line\": 1, \"col\": 106, \"start\": 94, \"end\": 105},\n        )\n        self.assertEqual(\n            ast.expression.args[\"from_\"].this.args[\"db\"].meta,\n            {\"line\": 1, \"col\": 93, \"start\": 82, \"end\": 92},\n        )\n        self.assertEqual(\n            ast.expression.args[\"from_\"].this.args[\"catalog\"].meta,\n            {\"line\": 1, \"col\": 81, \"start\": 69, \"end\": 80},\n        )\n\n        information_schema_sql = \"SELECT a, b FROM region.INFORMATION_SCHEMA.COLUMNS\"\n        ast = parse_one(information_schema_sql, dialect=\"bigquery\")\n        meta = ast.args[\"from_\"].this.this.meta\n        self.assertEqual(meta, {\"line\": 1, \"col\": 50, \"start\": 24, \"end\": 49})\n        assert (\n            information_schema_sql[meta[\"start\"] : meta[\"end\"] + 1] == \"INFORMATION_SCHEMA.COLUMNS\"\n        )\n\n    def test_quoted_identifier_meta(self):\n        sql = \"SELECT `a` FROM `test_schema`.`test_table_a`\"\n        ast = parse_one(sql, dialect=\"bigquery\")\n        db_meta = ast.args[\"from_\"].this.args[\"db\"].meta\n        self.assertEqual(sql[db_meta[\"start\"] : db_meta[\"end\"] + 1], \"`test_schema`\")\n        table_meta = ast.args[\"from_\"].this.this.meta\n        self.assertEqual(sql[table_meta[\"start\"] : table_meta[\"end\"] + 1], \"`test_table_a`\")\n\n        information_schema_sql = \"SELECT a, b FROM `region.INFORMATION_SCHEMA.COLUMNS`\"\n        ast = parse_one(information_schema_sql, dialect=\"bigquery\")\n        table_meta = ast.args[\"from_\"].this.this.meta\n        assert (\n            information_schema_sql[table_meta[\"start\"] : table_meta[\"end\"] + 1]\n            == \"`region.INFORMATION_SCHEMA.COLUMNS`\"\n        )\n\n    def test_override_normalization_strategy(self):\n        sql = \"SELECT * FROM p.d.t\"\n        ast = self.parse_one(sql)\n        qualified = qualify(ast.copy(), dialect=\"bigquery,normalization_strategy=uppercase\")\n        self.assertEqual(qualified.sql(\"bigquery\"), \"SELECT * FROM `P`.`D`.`T` AS `T`\")\n\n        from sqlglot.dialects import BigQuery\n        from sqlglot.dialects.dialect import NormalizationStrategy\n\n        try:\n            BigQuery.NORMALIZATION_STRATEGY = NormalizationStrategy.UPPERCASE\n\n            qualified = qualify(ast.copy(), dialect=\"bigquery,normalization_strategy=uppercase\")\n            self.assertEqual(qualified.sql(\"bigquery\"), \"SELECT * FROM `P`.`D`.`T` AS `T`\")\n        finally:\n            BigQuery.NORMALIZATION_STRATEGY = NormalizationStrategy.CASE_INSENSITIVE\n\n    def test_array_agg(self):\n        for distinct in (\"\", \"DISTINCT \"):\n            self.validate_all(\n                f\"SELECT ARRAY_AGG({distinct}x ORDER BY x)\",\n                write={\n                    \"bigquery\": f\"SELECT ARRAY_AGG({distinct}x ORDER BY x)\",\n                    \"snowflake\": f\"SELECT ARRAY_AGG({distinct}x) WITHIN GROUP (ORDER BY x NULLS FIRST)\",\n                },\n            )\n\n        for nulls in (\"\", \" IGNORE NULLS\", \" RESPECT NULLS\"):\n            self.validate_all(\n                f\"SELECT ARRAY_AGG(x{nulls} ORDER BY col1 ASC, col2 DESC)\",\n                write={\n                    \"bigquery\": f\"SELECT ARRAY_AGG(x{nulls} ORDER BY col1 ASC, col2 DESC)\",\n                    \"snowflake\": \"SELECT ARRAY_AGG(x) WITHIN GROUP (ORDER BY col1 ASC NULLS FIRST, col2 DESC NULLS LAST)\",\n                },\n            )\n\n    def test_array_concat(self):\n        self.validate_all(\n            \"WITH x AS ( SELECT 1 AS id), test_cte AS ( SELECT ARRAY_CONCAT(( SELECT id FROM x WHERE FALSE)) AS result ) SELECT * FROM test_cte;\",\n            write={\n                \"snowflake\": \"WITH x AS (SELECT 1 AS id), test_cte AS (SELECT ARRAY_CAT((SELECT id FROM x WHERE FALSE), []) AS result) SELECT * FROM test_cte\",\n            },\n        )\n\n    def test_select_as_struct(self):\n        self.validate_all(\n            \"SELECT ARRAY(SELECT AS STRUCT x1 AS x1, x2 AS x2 FROM t) AS array_col\",\n            write={\n                \"bigquery\": \"SELECT ARRAY(SELECT AS STRUCT x1 AS x1, x2 AS x2 FROM t) AS array_col\",\n                \"snowflake\": \"SELECT (SELECT ARRAY_AGG(OBJECT_CONSTRUCT('x1', x1, 'x2', x2)) FROM t) AS array_col\",\n            },\n        )\n\n        self.validate_all(\n            \"WITH t1 AS (SELECT ARRAY(SELECT AS STRUCT x1 AS alias_x1, x2 /* test */ FROM t2) AS array_col) SELECT array_col[0].alias_x1, array_col[0].x2 FROM t1\",\n            write={\n                \"bigquery\": \"WITH t1 AS (SELECT ARRAY(SELECT AS STRUCT x1 AS alias_x1, x2 /* test */ FROM t2) AS array_col) SELECT array_col[0].alias_x1, array_col[0].x2 FROM t1\",\n                \"snowflake\": \"WITH t1 AS (SELECT (SELECT ARRAY_AGG(OBJECT_CONSTRUCT('alias_x1', x1, 'x2', x2 /* test */)) FROM t2) AS array_col) SELECT array_col[0].alias_x1, array_col[0].x2 FROM t1\",\n            },\n        )\n\n        self.validate_all(\n            \"WITH t1 AS (SELECT ARRAY(SELECT AS STRUCT 1 AS a, 2 AS b) AS array_col) SELECT array_col[0].a, array_col[0].b FROM t1\",\n            write={\n                \"bigquery\": \"WITH t1 AS (SELECT ARRAY(SELECT AS STRUCT 1 AS a, 2 AS b) AS array_col) SELECT array_col[0].a, array_col[0].b FROM t1\",\n                \"snowflake\": \"WITH t1 AS (SELECT (SELECT ARRAY_AGG(OBJECT_CONSTRUCT('a', 1, 'b', 2))) AS array_col) SELECT array_col[0].a, array_col[0].b FROM t1\",\n            },\n        )\n\n        self.validate_all(\n            \"WITH t1 AS (SELECT ARRAY(SELECT AS STRUCT x1 AS alias_x1, x2 /* test */ FROM t2 WHERE x2 = 4) AS array_col) SELECT array_col[0].alias_x1, array_col[0].x2 FROM t1\",\n            write={\n                \"bigquery\": \"WITH t1 AS (SELECT ARRAY(SELECT AS STRUCT x1 AS alias_x1, x2 /* test */ FROM t2 WHERE x2 = 4) AS array_col) SELECT array_col[0].alias_x1, array_col[0].x2 FROM t1\",\n                \"snowflake\": \"WITH t1 AS (SELECT (SELECT ARRAY_AGG(OBJECT_CONSTRUCT('alias_x1', x1, 'x2', x2 /* test */)) FROM t2 WHERE x2 = 4) AS array_col) SELECT array_col[0].alias_x1, array_col[0].x2 FROM t1\",\n            },\n        )\n\n    def test_avoid_generating_nested_comment(self):\n        sql = \"\"\"\n        select\n            id,\n            foo,\n            -- bar, /* the thing */\n        from facts\n        \"\"\"\n        expected = \"SELECT\\n  id,\\n  foo\\n/* bar, / * the thing * / */\\nFROM facts\"\n        self.assertEqual(self.parse_one(sql).sql(\"bigquery\", pretty=True), expected)\n\n    def test_unnest_with_offset(self):\n        for offset, alias in ((\"\", \"offset\"), (\"AS pos\", \"pos\")):\n            self.validate_all(\n                f\"SELECT * FROM tbl CROSS JOIN UNNEST(col) AS ref WITH OFFSET {offset}\",\n                write={\n                    \"bigquery\": f\"SELECT * FROM tbl CROSS JOIN UNNEST(col) AS ref WITH OFFSET AS {alias}\",\n                    \"hive\": f\"SELECT * FROM tbl LATERAL VIEW POSEXPLODE(col) AS {alias}, ref\",\n                    \"spark2\": f\"SELECT * FROM tbl LATERAL VIEW POSEXPLODE(col) AS {alias}, ref\",\n                    \"spark\": f\"SELECT * FROM tbl LATERAL VIEW POSEXPLODE(col) AS {alias}, ref\",\n                    \"databricks\": f\"SELECT * FROM tbl LATERAL VIEW POSEXPLODE(col) AS {alias}, ref\",\n                },\n            )\n\n    def test_generate_date_array(self):\n        self.validate_all(\n            \"SELECT GENERATE_DATE_ARRAY('2016-10-05', '2016-10-08')\",\n            write={\n                \"bigquery\": \"SELECT GENERATE_DATE_ARRAY('2016-10-05', '2016-10-08', INTERVAL '1' DAY)\",\n                \"duckdb\": \"SELECT CAST(GENERATE_SERIES(CAST('2016-10-05' AS DATE), CAST('2016-10-08' AS DATE), INTERVAL '1' DAY) AS DATE[])\",\n            },\n        )\n        self.validate_all(\n            \"SELECT GENERATE_DATE_ARRAY('2016-10-05', '2016-10-08', INTERVAL '1' MONTH)\",\n            write={\n                \"bigquery\": \"SELECT GENERATE_DATE_ARRAY('2016-10-05', '2016-10-08', INTERVAL '1' MONTH)\",\n                \"duckdb\": \"SELECT CAST(GENERATE_SERIES(CAST('2016-10-05' AS DATE), CAST('2016-10-08' AS DATE), INTERVAL '1' MONTH) AS DATE[])\",\n            },\n        )\n        self.validate_all(\n            \"SELECT id, mnth FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start_month, DATE_TRUNC(CURRENT_DATE, MONTH), INTERVAL '1' MONTH)) AS mnth\",\n            write={\n                \"bigquery\": \"SELECT id, mnth FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start_month, DATE_TRUNC(CURRENT_DATE, MONTH), INTERVAL '1' MONTH)) AS mnth\",\n                \"duckdb\": \"SELECT id, mnth FROM t CROSS JOIN UNNEST(CAST(GENERATE_SERIES(start_month, DATE_TRUNC('MONTH', CURRENT_DATE), INTERVAL '1' MONTH) AS DATE[])) AS _t0(mnth)\",\n                \"snowflake\": \"SELECT id, DATEADD(MONTH, CAST(mnth AS INT), CAST(start_month AS DATE)) AS mnth FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(MONTH, start_month, DATE_TRUNC('MONTH', CURRENT_DATE)) + 1)) AS _t0(seq, key, path, index, mnth, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT id, mnth AS a_mnth FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start_month, DATE_TRUNC(CURRENT_DATE, MONTH), INTERVAL '1' MONTH)) AS mnth\",\n            write={\n                \"bigquery\": \"SELECT id, mnth AS a_mnth FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start_month, DATE_TRUNC(CURRENT_DATE, MONTH), INTERVAL '1' MONTH)) AS mnth\",\n                \"duckdb\": \"SELECT id, mnth AS a_mnth FROM t CROSS JOIN UNNEST(CAST(GENERATE_SERIES(start_month, DATE_TRUNC('MONTH', CURRENT_DATE), INTERVAL '1' MONTH) AS DATE[])) AS _t0(mnth)\",\n                \"snowflake\": \"SELECT id, DATEADD(MONTH, CAST(mnth AS INT), CAST(start_month AS DATE)) AS a_mnth FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(MONTH, start_month, DATE_TRUNC('MONTH', CURRENT_DATE)) + 1)) AS _t0(seq, key, path, index, mnth, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT id, mnth + 1 AS a_mnth FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start_month, DATE_TRUNC(CURRENT_DATE, MONTH), INTERVAL '1' MONTH)) AS mnth\",\n            write={\n                \"bigquery\": \"SELECT id, mnth + 1 AS a_mnth FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start_month, DATE_TRUNC(CURRENT_DATE, MONTH), INTERVAL '1' MONTH)) AS mnth\",\n                \"duckdb\": \"SELECT id, mnth + 1 AS a_mnth FROM t CROSS JOIN UNNEST(CAST(GENERATE_SERIES(start_month, DATE_TRUNC('MONTH', CURRENT_DATE), INTERVAL '1' MONTH) AS DATE[])) AS _t0(mnth)\",\n                \"snowflake\": \"SELECT id, DATEADD(MONTH, CAST(mnth AS INT), CAST(start_month AS DATE)) + 1 AS a_mnth FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(MONTH, start_month, DATE_TRUNC('MONTH', CURRENT_DATE)) + 1)) AS _t0(seq, key, path, index, mnth, this)\",\n            },\n        )\n\n    def test_json_array(self):\n        self.validate_identity(\"JSON_ARRAY()\")\n        self.validate_identity(\"JSON_ARRAY(10)\")\n        self.validate_identity(\"JSON_ARRAY([])\")\n        self.validate_identity(\"JSON_ARRAY(STRUCT(10 AS a, 'foo' AS b))\")\n        self.validate_identity(\"JSON_ARRAY(10, ['foo', 'bar'], [20, 30])\")\n\n    def test_declare(self):\n        # supported cases\n        self.validate_identity(\"DECLARE X INT64\")\n        self.validate_identity(\"DECLARE X INT64 DEFAULT 1\")\n        self.validate_identity(\"DECLARE X FLOAT64 DEFAULT 0.9\")\n        self.validate_identity(\"DECLARE X INT64 DEFAULT (SELECT MAX(col) FROM foo)\")\n        self.validate_identity(\"DECLARE X, Y, Z INT64\")\n        self.validate_identity(\"DECLARE X, Y, Z INT64 DEFAULT 42\")\n        self.validate_identity(\"DECLARE X, Y, Z INT64 DEFAULT (SELECT 42)\")\n        self.validate_identity(\"DECLARE START_DATE DATE DEFAULT CURRENT_DATE - 1\")\n        self.validate_identity(\n            \"DECLARE TS TIMESTAMP DEFAULT CURRENT_TIMESTAMP() - INTERVAL '1' HOUR\"\n        )\n\n    def test_week(self):\n        self.validate_identity(\"DATE_TRUNC(date, WEEK(MONDAY))\")\n        self.validate_identity(\n            \"LAST_DAY(DATETIME '2008-11-10 15:30:00', WEEK(SUNDAY))\",\n            \"LAST_DAY(CAST('2008-11-10 15:30:00' AS DATETIME), WEEK)\",\n        )\n        self.validate_identity(\"DATE_DIFF('2017-12-18', '2017-12-17', WEEK(SATURDAY))\")\n        self.validate_identity(\"DATETIME_DIFF('2017-12-18', '2017-12-17', WEEK(MONDAY))\")\n        self.validate_identity(\n            \"EXTRACT(WEEK(THURSDAY) FROM DATE '2013-12-25')\",\n            \"EXTRACT(WEEK(THURSDAY) FROM CAST('2013-12-25' AS DATE))\",\n        )\n\n        week_trunc = {\n            \"MONDAY\": (\"WEEK(MONDAY)\", \"DATE_TRUNC('WEEK', date)\"),\n            \"TUESDAY\": (\n                \"WEEK(TUESDAY)\",\n                \"CAST(DATE_TRUNC('WEEK', date + INTERVAL '-1' DAY) + INTERVAL '1' DAY AS DATE)\",\n            ),\n            \"WEDNESDAY\": (\n                \"WEEK(WEDNESDAY)\",\n                \"CAST(DATE_TRUNC('WEEK', date + INTERVAL '-2' DAY) + INTERVAL '2' DAY AS DATE)\",\n            ),\n            \"THURSDAY\": (\n                \"WEEK(THURSDAY)\",\n                \"CAST(DATE_TRUNC('WEEK', date + INTERVAL '-3' DAY) + INTERVAL '3' DAY AS DATE)\",\n            ),\n            \"FRIDAY\": (\n                \"WEEK(FRIDAY)\",\n                \"CAST(DATE_TRUNC('WEEK', date + INTERVAL '-4' DAY) + INTERVAL '4' DAY AS DATE)\",\n            ),\n            \"SATURDAY\": (\n                \"WEEK(SATURDAY)\",\n                \"CAST(DATE_TRUNC('WEEK', date + INTERVAL '-5' DAY) + INTERVAL '5' DAY AS DATE)\",\n            ),\n            \"SUNDAY\": (\n                \"WEEK\",\n                \"CAST(DATE_TRUNC('WEEK', date + INTERVAL '1' DAY) + INTERVAL '-1' DAY AS DATE)\",\n            ),\n        }\n        for day, (bq_unit, duckdb_sql) in week_trunc.items():\n            with self.subTest(\n                f\"Testing transpilation of DATE_TRUNC from Bigquery to Duckdb for unit: {day}\"\n            ):\n                self.validate_all(\n                    f\"SELECT DATE_TRUNC(date, WEEK({day}))\",\n                    write={\n                        \"bigquery\": f\"SELECT DATE_TRUNC(date, {bq_unit})\",\n                        \"duckdb\": f\"SELECT {duckdb_sql}\",\n                    },\n                )\n\n        # BigQuery → DuckDB transpilation tests for DATE_DIFF with week units\n        self.validate_all(\n            \"SELECT DATE_DIFF('2024-06-15', '2024-01-08', WEEK(MONDAY))\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF('2024-06-15', '2024-01-08', WEEK(MONDAY))\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-01-08' AS DATE)), DATE_TRUNC('WEEK', CAST('2024-06-15' AS DATE)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF('2026-01-15', '2024-01-08', WEEK(SUNDAY))\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF('2026-01-15', '2024-01-08', WEEK)\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-01-08' AS DATE) + INTERVAL '1' DAY), DATE_TRUNC('WEEK', CAST('2026-01-15' AS DATE) + INTERVAL '1' DAY))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF('2024-01-15', '2022-04-28', WEEK(SATURDAY))\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF('2024-01-15', '2022-04-28', WEEK(SATURDAY))\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2022-04-28' AS DATE) + INTERVAL '-5' DAY), DATE_TRUNC('WEEK', CAST('2024-01-15' AS DATE) + INTERVAL '-5' DAY))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF('2024-01-15', '2024-01-08', WEEK)\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF('2024-01-15', '2024-01-08', WEEK)\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-01-08' AS DATE) + INTERVAL '1' DAY), DATE_TRUNC('WEEK', CAST('2024-01-15' AS DATE) + INTERVAL '1' DAY))\",\n            },\n        )\n        # Test WEEK - Saturday to Sunday boundary (critical test for Sunday-start weeks)\n        # In BigQuery: Saturday -> Sunday crosses week boundary = 1 week\n        # Without fix: DuckDB treats as Monday-start weeks = 0 weeks (both in same week)\n        self.validate_all(\n            \"SELECT DATE_DIFF('2024-01-07', '2024-01-06', WEEK)\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF('2024-01-07', '2024-01-06', WEEK)\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-01-06' AS DATE) + INTERVAL '1' DAY), DATE_TRUNC('WEEK', CAST('2024-01-07' AS DATE) + INTERVAL '1' DAY))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF('2024-01-15', '2024-01-08', ISOWEEK)\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF('2024-01-15', '2024-01-08', ISOWEEK)\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-01-08' AS DATE)), DATE_TRUNC('WEEK', CAST('2024-01-15' AS DATE)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF(DATE '2024-09-15', DATE '2024-01-08', WEEK(MONDAY))\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF(CAST('2024-09-15' AS DATE), CAST('2024-01-08' AS DATE), WEEK(MONDAY))\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-01-08' AS DATE)), DATE_TRUNC('WEEK', CAST('2024-09-15' AS DATE)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF(DATE '2024-01-01', DATE '2024-01-15', WEEK(SUNDAY))\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF(CAST('2024-01-01' AS DATE), CAST('2024-01-15' AS DATE), WEEK)\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-01-15' AS DATE) + INTERVAL '1' DAY), DATE_TRUNC('WEEK', CAST('2024-01-01' AS DATE) + INTERVAL '1' DAY))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF(DATE '2023-05-01', DATE '2024-01-15', ISOWEEK)\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF(CAST('2023-05-01' AS DATE), CAST('2024-01-15' AS DATE), ISOWEEK)\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-01-15' AS DATE)), DATE_TRUNC('WEEK', CAST('2023-05-01' AS DATE)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF(DATE '2024-01-01', DATE '2024-01-15', DAY)\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF(CAST('2024-01-01' AS DATE), CAST('2024-01-15' AS DATE), DAY)\",\n                \"duckdb\": \"SELECT DATE_DIFF('DAY', CAST('2024-01-15' AS DATE), CAST('2024-01-01' AS DATE))\",\n            },\n        )\n\n    def test_approx_qunatiles(self):\n        self.validate_identity(\"APPROX_QUANTILES(foo, 2)\")\n        self.validate_identity(\"APPROX_QUANTILES(DISTINCT foo, 2 RESPECT NULLS)\")\n        self.validate_identity(\"APPROX_QUANTILES(DISTINCT foo, 2 IGNORE NULLS)\")\n\n    def test_json_lax(self):\n        self.validate_identity(\"LAX_BOOL(PARSE_JSON('true'))\")\n        self.validate_identity(\"LAX_FLOAT64(PARSE_JSON('9.8'))\")\n        self.validate_identity(\"LAX_INT64(PARSE_JSON('10'))\")\n        self.validate_identity(\"\"\"LAX_STRING(PARSE_JSON('\"str\"'))\"\"\")\n\n    def test_safe_math_funcs(self):\n        self.validate_identity(\"SAFE_NEGATE(x)\")\n        self.validate_all(\n            \"SAFE_ADD(x, y)\",\n            read={\n                \"bigquery\": \"SAFE_ADD(x, y)\",\n                \"spark\": \"TRY_ADD(x, y)\",\n                \"databricks\": \"TRY_ADD(x, y)\",\n            },\n            write={\n                \"spark\": \"TRY_ADD(x, y)\",\n                \"databricks\": \"TRY_ADD(x, y)\",\n            },\n        )\n        self.validate_all(\n            \"SAFE_MULTIPLY(x, y)\",\n            read={\n                \"bigquery\": \"SAFE_MULTIPLY(x, y)\",\n                \"spark\": \"TRY_MULTIPLY(x, y)\",\n                \"databricks\": \"TRY_MULTIPLY(x, y)\",\n            },\n            write={\n                \"spark\": \"TRY_MULTIPLY(x, y)\",\n                \"databricks\": \"TRY_MULTIPLY(x, y)\",\n            },\n        )\n        self.validate_all(\n            \"SAFE_SUBTRACT(x, y)\",\n            read={\n                \"bigquery\": \"SAFE_SUBTRACT(x, y)\",\n                \"spark\": \"TRY_SUBTRACT(x, y)\",\n                \"databricks\": \"TRY_SUBTRACT(x, y)\",\n            },\n            write={\n                \"spark\": \"TRY_SUBTRACT(x, y)\",\n                \"databricks\": \"TRY_SUBTRACT(x, y)\",\n            },\n        )\n\n    def test_bitwise_and(self):\n        self.validate_all(\n            \"SELECT 1 & 1\",\n            write={\n                \"bigquery\": \"SELECT 1 & 1\",\n                \"snowflake\": \"SELECT BITAND(1, 1)\",\n            },\n        )\n\n    def test_bitwise_not(self):\n        self.validate_all(\n            \"SELECT ~1\",\n            write={\n                \"bigquery\": \"SELECT ~1\",\n                \"snowflake\": \"SELECT BITNOT(1)\",\n            },\n        )\n\n    def test_bit_aggs(self):\n        self.validate_all(\n            \"BIT_AND(x)\",\n            read={\n                \"bigquery\": \"BIT_AND(x)\",\n                \"databricks\": \"BIT_AND(x)\",\n                \"dremio\": \"BIT_AND(x)\",\n                \"duckdb\": \"BIT_AND(x)\",\n                \"mysql\": \"BIT_AND(x)\",\n                \"postgres\": \"BIT_AND(x)\",\n                \"spark\": \"BIT_AND(x)\",\n            },\n            write={\n                \"databricks\": \"BIT_AND(x)\",\n                \"dremio\": \"BIT_AND(x)\",\n                \"duckdb\": \"BIT_AND(x)\",\n                \"mysql\": \"BIT_AND(x)\",\n                \"postgres\": \"BIT_AND(x)\",\n                \"spark\": \"BIT_AND(x)\",\n            },\n        )\n        self.validate_all(\n            \"BIT_OR(x)\",\n            read={\n                \"bigquery\": \"BIT_OR(x)\",\n                \"databricks\": \"BIT_OR(x)\",\n                \"dremio\": \"BIT_OR(x)\",\n                \"duckdb\": \"BIT_OR(x)\",\n                \"mysql\": \"BIT_OR(x)\",\n                \"postgres\": \"BIT_OR(x)\",\n                \"spark\": \"BIT_OR(x)\",\n            },\n            write={\n                \"databricks\": \"BIT_OR(x)\",\n                \"dremio\": \"BIT_OR(x)\",\n                \"duckdb\": \"BIT_OR(x)\",\n                \"mysql\": \"BIT_OR(x)\",\n                \"postgres\": \"BIT_OR(x)\",\n                \"spark\": \"BIT_OR(x)\",\n            },\n        )\n        self.validate_all(\n            \"BIT_XOR(x)\",\n            read={\n                \"bigquery\": \"BIT_XOR(x)\",\n                \"databricks\": \"BIT_XOR(x)\",\n                \"duckdb\": \"BIT_XOR(x)\",\n                \"mysql\": \"BIT_XOR(x)\",\n                \"postgres\": \"BIT_XOR(x)\",\n                \"spark\": \"BIT_XOR(x)\",\n            },\n            write={\n                \"databricks\": \"BIT_XOR(x)\",\n                \"duckdb\": \"BIT_XOR(x)\",\n                \"mysql\": \"BIT_XOR(x)\",\n                \"postgres\": \"BIT_XOR(x)\",\n                \"spark\": \"BIT_XOR(x)\",\n            },\n        )\n        self.validate_all(\n            \"BIT_COUNT(x)\",\n            read={\n                \"bigquery\": \"BIT_COUNT(x)\",\n                \"spark\": \"BIT_COUNT(x)\",\n                \"databricks\": \"BIT_COUNT(x)\",\n                \"mysql\": \"BIT_COUNT(x)\",\n            },\n            write={\n                \"spark\": \"BIT_COUNT(x)\",\n                \"databricks\": \"BIT_COUNT(x)\",\n                \"mysql\": \"BIT_COUNT(x)\",\n            },\n        )\n\n    def test_to_hex(self):\n        self.validate_all(\n            \"SELECT TO_HEX(SHA1('abc'))\",\n            write={\n                \"bigquery\": \"SELECT TO_HEX(SHA1('abc'))\",\n                \"snowflake\": \"SELECT TO_CHAR(SHA1_BINARY('abc'))\",\n            },\n        )\n\n    def test_md5(self):\n        self.validate_all(\n            \"SELECT MD5('abc')\",\n            write={\n                \"bigquery\": \"SELECT MD5('abc')\",\n                \"snowflake\": \"SELECT MD5_BINARY('abc')\",\n            },\n        )\n\n    def test_to_json_string(self):\n        self.validate_all(\n            \"\"\"SELECT TO_JSON_STRING(STRUCT('Alice' AS name)) AS json_data\"\"\",\n            write={\n                \"bigquery\": \"\"\"SELECT TO_JSON_STRING(STRUCT('Alice' AS name)) AS json_data\"\"\",\n                \"snowflake\": \"\"\"SELECT TO_JSON(OBJECT_CONSTRUCT('name', 'Alice')) AS json_data\"\"\",\n            },\n        )\n\n    def test_concat(self):\n        self.validate_all(\n            \"SELECT CONCAT('T.P.', ' ', 'Bar') AS author\",\n            write={\n                \"bigquery\": \"SELECT CONCAT('T.P.', ' ', 'Bar') AS author\",\n                \"duckdb\": \"SELECT 'T.P.' || ' ' || 'Bar' AS author\",\n            },\n        )\n\n    def test_pseudocolumns(self):\n        schema = {\n            \"t\": {\n                \"col\": \"INT\",\n                \"a\": \"TIMESTAMP\",\n                \"b\": \"TIMESTAMP\",\n            }\n        }\n\n        ast = self.validate_identity(\"SELECT col FROM t WHERE _PARTITIONTIME BETWEEN a AND b\")\n        self.assertIsNone(ast.find(exp.Pseudocolumn))\n\n        qualified = qualify(ast, schema=schema, dialect=\"bigquery\")\n        self.assertIsNotNone(qualified.find(exp.Pseudocolumn))\n        self.assertEqual(\n            qualified.sql(dialect=\"bigquery\"),\n            \"SELECT `t`.`col` AS `col` FROM `t` AS `t` WHERE `_partitiontime` BETWEEN `t`.`a` AND `t`.`b`\",\n        )\n\n        ast = self.validate_identity(\"SELECT _DBT_MAX_PARTITION FROM t\")\n        self.assertIsNone(ast.find(exp.Pseudocolumn))\n\n        qualified = qualify(ast, schema=schema, dialect=\"bigquery\")\n        self.assertIsNotNone(qualified.find(exp.Pseudocolumn))\n\n    def test_round(self):\n        self.validate_all(\n            \"SELECT ROUND(2.25) AS value\",\n            write={\n                \"bigquery\": \"SELECT ROUND(2.25) AS value\",\n                \"duckdb\": \"SELECT ROUND(2.25) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(2.25, 1) AS value\",\n            write={\n                \"bigquery\": \"SELECT ROUND(2.25, 1) AS value\",\n                \"duckdb\": \"SELECT ROUND(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(NUMERIC '2.25', 1, 'ROUND_HALF_AWAY_FROM_ZERO') AS value\",\n            write={\n                \"bigquery\": \"\"\"SELECT ROUND(CAST('2.25' AS NUMERIC), 1, 'ROUND_HALF_AWAY_FROM_ZERO') AS value\"\"\",\n                \"duckdb\": \"SELECT ROUND(CAST('2.25' AS DECIMAL), 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(NUMERIC '2.25', 1, 'ROUND_HALF_EVEN') AS value\",\n            write={\n                \"bigquery\": \"\"\"SELECT ROUND(CAST('2.25' AS NUMERIC), 1, 'ROUND_HALF_EVEN') AS value\"\"\",\n                \"duckdb\": \"SELECT ROUND_EVEN(CAST('2.25' AS DECIMAL), 1) AS value\",\n            },\n        )\n\n    def test_approx_quantiles(self):\n        self.validate_identity(\"APPROX_QUANTILES(x, 2)\")\n        self.validate_identity(\"APPROX_QUANTILES(FALSE OR TRUE, 2)\")\n        self.validate_identity(\"APPROX_QUANTILES((SELECT 1 AS val), CAST(2.1 AS INT64))\")\n        self.validate_identity(\"APPROX_QUANTILES(DISTINCT x, 2)\")\n        self.validate_identity(\"APPROX_QUANTILES(x, 2 RESPECT NULLS)\")\n        self.validate_identity(\"APPROX_QUANTILES(x, 2 IGNORE NULLS)\")\n        self.validate_identity(\"APPROX_QUANTILES(DISTINCT x, 2 RESPECT NULLS)\")\n\n    def test_approx_quantiles_to_duckdb(self):\n        self.validate_all(\n            \"APPROX_QUANTILES(x, 1)\",\n            write={\"duckdb\": \"APPROX_QUANTILE(x, [0, 1])\"},\n        )\n        self.validate_all(\n            \"APPROX_QUANTILES(x, 2)\",\n            write={\"duckdb\": \"APPROX_QUANTILE(x, [0, 0.5, 1])\"},\n        )\n        self.validate_all(\n            \"APPROX_QUANTILES(x, 4)\",\n            write={\"duckdb\": \"APPROX_QUANTILE(x, [0, 0.25, 0.5, 0.75, 1])\"},\n        )\n        self.validate_all(\n            \"APPROX_QUANTILES(DISTINCT x, 2)\",\n            write={\"duckdb\": \"APPROX_QUANTILE(DISTINCT x, [0, 0.5, 1])\"},\n        )\n\n        with self.subTest(\"APPROX_QUANTILES 100 buckets\"):\n            result = self.parse_one(\"APPROX_QUANTILES(x, 100)\").sql(\"duckdb\")\n            self.assertEqual(result.count(\"APPROX_QUANTILE(\"), 1)\n            self.assertIn(\"0.01\", result)\n            self.assertIn(\"0.99\", result)\n            self.assertRegex(result, r\"APPROX_QUANTILE\\(x, \\[.*\\]\\)\")\n\n        for expr in (\"x + y\", \"CASE WHEN x > 0 THEN x ELSE 0 END\", \"ABS(x)\"):\n            with self.subTest(expr=expr):\n                self.validate_all(\n                    f\"APPROX_QUANTILES({expr}, 2)\",\n                    write={\"duckdb\": f\"APPROX_QUANTILE({expr}, [0, 0.5, 1])\"},\n                )\n\n        with self.subTest(\"non-literal bucket count\"):\n            with self.assertRaises(UnsupportedError):\n                self.parse_one(\"APPROX_QUANTILES(x, bucket_count)\").sql(\n                    \"duckdb\", unsupported_level=ErrorLevel.RAISE\n                )\n\n        with self.subTest(\"non-integer bucket count\"):\n            for value in (\"0\", \"-1\", \"2.5\"):\n                with self.subTest(value=value):\n                    with self.assertRaises(UnsupportedError):\n                        self.parse_one(f\"APPROX_QUANTILES(x, {value})\").sql(\n                            \"duckdb\", unsupported_level=ErrorLevel.RAISE\n                        )\n\n        with self.subTest(\"NULL bucket count\"):\n            with self.assertRaises(UnsupportedError):\n                self.parse_one(\"APPROX_QUANTILES(x, NULL)\").sql(\n                    \"duckdb\", unsupported_level=ErrorLevel.RAISE\n                )\n\n        with self.subTest(\"missing bucket count\"):\n            with self.assertRaises(UnsupportedError):\n                self.parse_one(\"APPROX_QUANTILES(x)\").sql(\n                    \"duckdb\", unsupported_level=ErrorLevel.RAISE\n                )\n\n        with self.subTest(\"missing bucket count with DISTINCT\"):\n            with self.assertRaises(UnsupportedError):\n                self.parse_one(\"APPROX_QUANTILES(DISTINCT x)\").sql(\n                    \"duckdb\", unsupported_level=ErrorLevel.RAISE\n                )\n\n        with self.subTest(\"APPROX_QUANTILES IGNORE NULLS\"):\n            # No warning: IGNORE NULLS is the default behavior in DuckDB\n            from sqlglot.generator import logger as generator_logger\n\n            with mock.patch.object(generator_logger, \"warning\") as mock_warning:\n                self.validate_all(\n                    \"APPROX_QUANTILES(x, 2 IGNORE NULLS)\",\n                    write={\"duckdb\": \"APPROX_QUANTILE(x, [0, 0.5, 1])\"},\n                )\n                mock_warning.assert_not_called()\n\n        with self.subTest(\"APPROX_QUANTILES RESPECT NULLS\"):\n            with self.assertRaises(UnsupportedError):\n                self.parse_one(\"APPROX_QUANTILES(x, 2 RESPECT NULLS)\").sql(\n                    \"duckdb\", unsupported_level=ErrorLevel.RAISE\n                )\n\n    def test_bignumeric(self):\n        # BIGDECIMAL is an alias of BIGNUMERIC\n        for type_ in (\"BIGNUMERIC\", \"BIGDECIMAL\"):\n            with self.subTest(f\"Testing BigQuery's {type_}\"):\n                self.validate_all(\n                    f\"SELECT {type_} '1'\",\n                    write={\n                        \"bigquery\": \"SELECT CAST('1' AS BIGNUMERIC)\",\n                        \"duckdb\": \"SELECT CAST('1' AS DECIMAL(38, 5))\",\n                    },\n                )\n\n                self.validate_all(\n                    f\"SELECT CAST(1 AS {type_})\",\n                    write={\n                        \"bigquery\": \"SELECT CAST(1 AS BIGNUMERIC)\",\n                        \"duckdb\": \"SELECT CAST(1 AS DECIMAL(38, 5))\",\n                    },\n                )\n"
  },
  {
    "path": "tests/dialects/test_clickhouse.py",
    "content": "from datetime import date, datetime, timezone\nfrom sqlglot import exp, parse_one\nfrom sqlglot.dialects import ClickHouse\nfrom sqlglot.expressions import convert\nfrom sqlglot.helper import logger as helper_logger\nfrom sqlglot.optimizer import traverse_scope\nfrom sqlglot.optimizer.qualify_columns import quote_identifiers\nfrom tests.dialects.test_dialect import Validator\nfrom sqlglot.errors import ErrorLevel\n\n\nclass TestClickhouse(Validator):\n    dialect = \"clickhouse\"\n\n    def test_clickhouse(self):\n        self.validate_identity(\n            \"SELECT col.^nested, t.col2.^nested, t.col3.^nested.twice FROM t\"\n        ).selects[0].assert_is(exp.NestedJSONSelect)\n\n        self.validate_identity(\n            \"cast(notEmpty(report_task_id)?report_task_id:'-1' AS text)\",\n            \"CAST(CASE WHEN notEmpty(report_task_id) THEN report_task_id ELSE '-1' END AS String)\",\n        )\n\n        expr = quote_identifiers(self.parse_one(\"{start_date:String}\"), dialect=\"clickhouse\")\n        self.assertEqual(expr.sql(\"clickhouse\"), \"{start_date: String}\")\n\n        for string_type_enum in ClickHouse.Generator.STRING_TYPE_MAPPING:\n            self.validate_identity(f\"CAST(x AS {string_type_enum.value})\", \"CAST(x AS String)\")\n\n        # Arrays, maps and tuples can't be Nullable in ClickHouse\n        for non_nullable_type in (\"ARRAY<INT>\", \"MAP<INT, INT>\", \"STRUCT(a: INT)\"):\n            try_cast = parse_one(f\"TRY_CAST(x AS {non_nullable_type})\")\n            target_type = try_cast.to.sql(\"clickhouse\")\n            self.assertEqual(try_cast.sql(\"clickhouse\"), f\"CAST(x AS {target_type})\")\n\n        for nullable_type in (\"INT\", \"UINT\", \"BIGINT\", \"FLOAT\", \"DOUBLE\", \"TEXT\", \"DATE\", \"UUID\"):\n            try_cast = parse_one(f\"TRY_CAST(x AS {nullable_type})\")\n            target_type = exp.DataType.build(nullable_type, dialect=\"clickhouse\").sql(\"clickhouse\")\n            self.assertEqual(try_cast.sql(\"clickhouse\"), f\"CAST(x AS Nullable({target_type}))\")\n\n        expr = parse_one(\"count(x)\")\n        self.assertEqual(expr.sql(dialect=\"clickhouse\"), \"COUNT(x)\")\n\n        self.validate_identity('SELECT DISTINCT ON (\"id\") * FROM t')\n        self.validate_identity(\"SELECT 1 OR (1 = 2)\")\n        self.validate_identity(\"SELECT 1 AND (1 = 2)\")\n        self.validate_identity(\"SELECT json.a.:Int64\")\n        self.validate_identity(\"SELECT json.a.:JSON.b.:Int64\")\n        self.validate_identity('SELECT json.a.b.:\"Array(JSON)\".c')\n        self.validate_identity('SELECT json.a.b.:\"Array(Array(JSON))\".c')\n        self.validate_identity(\"SELECT json.a.b[].c\", 'SELECT json.a.b.:\"Array(JSON)\".c')\n        self.validate_identity(\"SELECT json.a.b[][]\", 'SELECT json.a.b.:\"Array(Array(JSON))\"')\n        self.validate_identity(\"WITH arrayJoin([(1, [2, 3])]) AS arr SELECT arr\")\n        self.validate_identity(\"CAST(1 AS Bool)\")\n        self.validate_identity(\"SELECT toString(CHAR(104.1, 101, 108.9, 108.9, 111, 32))\")\n        self.validate_identity(\"@macro\").assert_is(exp.Parameter).this.assert_is(exp.Var)\n        self.validate_identity(\"SELECT toFloat(like)\")\n        self.validate_identity(\"SELECT like\")\n        self.validate_identity(\"SELECT STR_TO_DATE(str, fmt, tz)\")\n        self.validate_identity(\"SELECT STR_TO_DATE('05 12 2000', '%d %m %Y')\")\n        self.validate_identity(\"SELECT EXTRACT(YEAR FROM toDateTime('2023-02-01'))\")\n        self.validate_identity(\"extract(haystack, pattern)\")\n        self.validate_identity(\"SELECT * FROM x LIMIT 1 UNION ALL SELECT * FROM y\")\n        self.validate_identity(\"SELECT CAST(x AS Tuple(String, Array(Nullable(Float64))))\")\n        self.validate_identity(\"countIf(x, y)\")\n        self.validate_identity(\"x = y\")\n        self.validate_identity(\"x <> y\")\n        self.validate_identity(\"SELECT * FROM (SELECT a FROM b SAMPLE 0.01)\")\n        self.validate_identity(\"SELECT * FROM (SELECT a FROM b SAMPLE 1 / 10 OFFSET 1 / 2)\")\n        self.validate_identity(\"SELECT sum(foo * bar) FROM bla SAMPLE 10000000\")\n        self.validate_identity(\"CAST(x AS Nested(ID UInt32, Serial UInt32, EventTime DateTime))\")\n        self.validate_identity(\"CAST(x AS Enum('hello' = 1, 'world' = 2))\")\n        self.validate_identity(\"CAST(x AS Enum('hello', 'world'))\")\n        self.validate_identity(\"CAST(x AS Enum('hello' = 1, 'world'))\")\n        self.validate_identity(\"CAST(x AS Enum8('hello' = -123, 'world'))\")\n        self.validate_identity(\"CAST(x AS FixedString(1))\")\n        self.validate_identity(\"CAST(x AS LowCardinality(FixedString))\")\n        self.validate_identity(\"SELECT isNaN(1.0)\")\n        self.validate_identity(\"SELECT startsWith('Spider-Man', 'Spi')\")\n        self.validate_identity(\"SELECT xor(TRUE, FALSE)\")\n        self.validate_identity(\"CAST(['hello'], 'Array(Enum8(''hello'' = 1))')\")\n        self.validate_identity(\"SELECT x, COUNT() FROM y GROUP BY x WITH TOTALS\")\n        self.validate_identity(\"SELECT INTERVAL t.days DAY\")\n        self.validate_identity(\"SELECT match('abc', '([a-z]+)')\")\n        self.validate_identity(\"dictGet(x, 'y')\")\n        self.validate_identity(\"WITH final AS (SELECT 1) SELECT * FROM final\")\n        self.validate_identity(\"SELECT * FROM x FINAL\")\n        self.validate_identity(\"SELECT * FROM x AS y FINAL\")\n        self.validate_identity(\"'a' IN mapKeys(map('a', 1, 'b', 2))\")\n        self.validate_identity(\"CAST((1, 2) AS Tuple(a Int8, b Int16))\")\n        self.validate_identity(\"SELECT * FROM foo LEFT ANY JOIN bla\")\n        self.validate_identity(\"SELECT * FROM foo LEFT ASOF JOIN bla\")\n        self.validate_identity(\"SELECT * FROM foo ASOF JOIN bla\")\n        self.validate_identity(\"SELECT * FROM foo ANY JOIN bla\")\n        self.validate_identity(\"SELECT * FROM foo GLOBAL ANY JOIN bla\")\n        self.validate_identity(\"SELECT * FROM foo GLOBAL LEFT ANY JOIN bla\")\n        self.validate_identity(\"SELECT quantile(0.5)(a)\")\n        self.validate_identity(\"SELECT quantiles(0.5)(a) AS x FROM t\")\n        self.validate_identity(\"SELECT quantilesIf(0.5)(a, a > 1) AS x FROM t\")\n        self.validate_identity(\"SELECT quantileState(0.5)(a) AS x FROM t\")\n        self.validate_identity(\"SELECT deltaSumMerge(a) AS x FROM t\")\n        self.validate_identity(\"SELECT quantiles(0.1, 0.2, 0.3)(a)\")\n        self.validate_identity(\"SELECT quantileTiming(0.5)(RANGE(100))\")\n        self.validate_identity(\"SELECT histogram(5)(a)\")\n        self.validate_identity(\"SELECT groupUniqArray(2)(a)\")\n        self.validate_identity(\"SELECT exponentialTimeDecayedAvg(60)(a, b)\")\n        self.validate_identity(\"levenshteinDistance(col1, col2)\", \"editDistance(col1, col2)\")\n        self.validate_identity(\"jaroWinklerSimilarity('hello', 'world')\")\n        self.validate_identity(\"SELECT * FROM foo WHERE x GLOBAL IN (SELECT * FROM bar)\")\n        self.validate_identity(\"SELECT * FROM foo WHERE x GLOBAL NOT IN (SELECT * FROM bar)\")\n        self.validate_identity(\"POSITION(haystack, needle)\")\n        self.validate_identity(\"POSITION(haystack, needle, position)\")\n        self.validate_identity(\"CAST(x AS DATETIME)\", \"CAST(x AS DateTime)\")\n        self.validate_identity(\"CAST(x AS TIMESTAMPTZ)\", \"CAST(x AS DateTime)\")\n        self.validate_identity(\"CAST(x as MEDIUMINT)\", \"CAST(x AS Int32)\")\n        self.validate_identity(\"CAST(x AS DECIMAL(38, 2))\", \"CAST(x AS Decimal(38, 2))\")\n        self.validate_identity(\"SELECT arrayJoin([1, 2, 3] AS src) AS dst, 'Hello', src\")\n        self.validate_identity(\"\"\"SELECT JSONExtractString('{\"x\": {\"y\": 1}}', 'x', 'y')\"\"\")\n        self.validate_identity(\"SELECT * FROM table LIMIT 1 BY a, b\")\n        self.validate_identity(\"SELECT * FROM table LIMIT 2 OFFSET 1 BY a, b\")\n        self.validate_identity(\"TRUNCATE TABLE t1 ON CLUSTER test_cluster\")\n        self.validate_identity(\"TRUNCATE TABLE t1 ON CLUSTER '{cluster}'\")\n        self.validate_identity(\"TRUNCATE DATABASE db\")\n        self.validate_identity(\"TRUNCATE DATABASE db ON CLUSTER test_cluster\")\n        self.validate_identity(\"TRUNCATE DATABASE db ON CLUSTER '{cluster}'\")\n\n        # Numeric trunc\n        self.validate_identity(\"trunc(3.14159, 2)\").assert_is(exp.Trunc)\n        self.validate_identity(\"trunc(3.14159)\").assert_is(exp.Trunc)\n        self.validate_all(\n            \"trunc(3.14159, 2)\",\n            read={\"postgres\": \"TRUNC(3.14159, 2)\"},\n        )\n\n        self.validate_identity(\"EXCHANGE TABLES x.a AND y.b\", check_command_warning=True)\n        self.validate_identity(\"CREATE TABLE test (id UInt8) ENGINE=Null()\")\n        self.validate_identity(\n            \"SELECT * FROM foo ORDER BY bar OFFSET 0 ROWS FETCH NEXT 10 ROWS WITH TIES\"\n        )\n        self.validate_identity(\n            \"SELECT DATE_BIN(toDateTime('2023-01-01 14:45:00'), INTERVAL '1' MINUTE, toDateTime('2023-01-01 14:35:30'), 'UTC')\",\n        )\n        self.validate_identity(\n            \"SELECT CAST(1730098800 AS DateTime64) AS DATETIME, 'test' AS interp ORDER BY DATETIME WITH FILL FROM toDateTime64(1730098800, 3) - INTERVAL '7' HOUR TO toDateTime64(1730185140, 3) - INTERVAL '7' HOUR STEP toIntervalSecond(900) INTERPOLATE (interp)\"\n        )\n        self.validate_identity(\n            \"SELECT number, COUNT() OVER (PARTITION BY number % 3) AS partition_count FROM numbers(10) WINDOW window_name AS (PARTITION BY number) QUALIFY partition_count = 4 ORDER BY number\"\n        )\n        self.validate_identity(\n            \"SELECT id, quantileGK(100, 0.95)(reading) OVER (PARTITION BY id ORDER BY id RANGE BETWEEN 30000 PRECEDING AND CURRENT ROW) AS window FROM table\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM table LIMIT 1 BY CONCAT(datalayerVariantNo, datalayerProductId, warehouse)\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT JSONExtractString('{\"a\": \"hello\", \"b\": [-100, 200.0, 300]}', 'a')\"\"\"\n        )\n        self.validate_identity(\n            \"ATTACH DATABASE DEFAULT ENGINE = ORDINARY\", check_command_warning=True\n        )\n        self.validate_identity(\n            \"SELECT n, source FROM (SELECT toFloat32(number % 10) AS n, 'original' AS source FROM numbers(10) WHERE number % 3 = 1) ORDER BY n WITH FILL\"\n        )\n        self.validate_identity(\n            \"SELECT n, source FROM (SELECT toFloat32(number % 10) AS n, 'original' AS source FROM numbers(10) WHERE number % 3 = 1) ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5\"\n        )\n        self.validate_identity(\n            \"SELECT toDate((number * 10) * 86400) AS d1, toDate(number * 86400) AS d2, 'original' AS source FROM numbers(10) WHERE (number % 3) = 1 ORDER BY d2 WITH FILL, d1 WITH FILL STEP 5\"\n        )\n        self.validate_identity(\n            \"SELECT n, source, inter FROM (SELECT toFloat32(number % 10) AS n, 'original' AS source, number AS inter FROM numbers(10) WHERE number % 3 = 1) ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5 INTERPOLATE (inter AS inter + 1)\"\n        )\n        self.validate_identity(\n            \"SELECT SUM(1) AS impressions, arrayJoin(cities) AS city, arrayJoin(browsers) AS browser FROM (SELECT ['Istanbul', 'Berlin', 'Bobruisk'] AS cities, ['Firefox', 'Chrome', 'Chrome'] AS browsers) GROUP BY 2, 3\"\n        )\n        self.validate_identity(\n            \"SELECT sum(1) AS impressions, (arrayJoin(arrayZip(cities, browsers)) AS t).1 AS city, t.2 AS browser FROM (SELECT ['Istanbul', 'Berlin', 'Bobruisk'] AS cities, ['Firefox', 'Chrome', 'Chrome'] AS browsers) GROUP BY 2, 3\"\n        )\n        self.validate_identity(\n            'SELECT CAST(tuple(1 AS \"a\", 2 AS \"b\", 3.0 AS \"c\").2 AS Nullable(String))'\n        )\n        self.validate_identity(\n            \"CREATE TABLE test (id UInt8) ENGINE=AggregatingMergeTree() ORDER BY tuple()\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test ON CLUSTER default (id UInt8) ENGINE=AggregatingMergeTree() ORDER BY tuple()\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test ON CLUSTER '{cluster}' (id UInt8) ENGINE=AggregatingMergeTree() ORDER BY tuple()\"\n        )\n        self.validate_identity(\n            \"CREATE MATERIALIZED VIEW test_view ON CLUSTER cl1 (id UInt8) ENGINE=AggregatingMergeTree() ORDER BY tuple() AS SELECT * FROM test_data\"\n        )\n        self.validate_identity(\n            \"CREATE MATERIALIZED VIEW test_view ON CLUSTER '{cluster}' (id UInt8) ENGINE=AggregatingMergeTree() ORDER BY tuple() AS SELECT * FROM test_data\"\n        )\n        self.validate_identity(\n            \"CREATE MATERIALIZED VIEW test_view ON CLUSTER cl1 TO table1 AS SELECT * FROM test_data\"\n        )\n        self.validate_identity(\n            \"CREATE MATERIALIZED VIEW test_view ON CLUSTER '{cluster}' TO table1 AS SELECT * FROM test_data\"\n        )\n        self.validate_identity(\n            \"CREATE MATERIALIZED VIEW test_view TO db.table1 (id UInt8) AS SELECT * FROM test_data\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (foo String CODEC(LZ4HC(9), ZSTD, DELTA), size String ALIAS formatReadableSize(size_bytes), INDEX idx1 a TYPE bloom_filter(0.001) GRANULARITY 1, INDEX idx2 a TYPE set(100) GRANULARITY 2, INDEX idx3 a TYPE minmax GRANULARITY 3)\"\n        )\n        self.validate_identity(\n            \"SELECT generate_series FROM generate_series(0, 10) AS g(x)\",\n        )\n        self.validate_identity(\n            \"SELECT t.c FROM (SELECT arrayJoin([1,2,3,4,5]) AS c) AS t WHERE (t.c + 0) NOT IN (1,2)\",\n            \"SELECT t.c FROM (SELECT arrayJoin([1, 2, 3, 4, 5]) AS c) AS t WHERE NOT ((t.c + 0) IN (1, 2))\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1, t2\",\n            \"SELECT * FROM t1 CROSS JOIN t2\",\n        )\n        self.validate_identity(\n            \"SELECT and(1, 2)\",\n            \"SELECT 1 AND 2\",\n        )\n        self.validate_identity(\n            \"SELECT or(1, 2)\",\n            \"SELECT 1 OR 2\",\n        )\n        self.validate_identity(\n            \"SELECT generate_series FROM generate_series(0, 10) AS g\",\n            \"SELECT generate_series FROM generate_series(0, 10) AS g(generate_series)\",\n        )\n        self.validate_identity(\n            \"INSERT INTO tab VALUES ({'key1': 1, 'key2': 10}), ({'key1': 2, 'key2': 20}), ({'key1': 3, 'key2': 30})\",\n            \"INSERT INTO tab VALUES ((map('key1', 1, 'key2', 10))), ((map('key1', 2, 'key2', 20))), ((map('key1', 3, 'key2', 30)))\",\n        )\n        self.validate_identity(\n            \"SELECT (toUInt8('1') + toUInt8('2')) IS NOT NULL\",\n            \"SELECT NOT ((toUInt8('1') + toUInt8('2')) IS NULL)\",\n        )\n        self.validate_identity(\n            \"SELECT $1$foo$1$\",\n            \"SELECT 'foo'\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM table LIMIT 1, 2 BY a, b\",\n            \"SELECT * FROM table LIMIT 2 OFFSET 1 BY a, b\",\n        )\n        self.validate_identity(\n            \"SELECT SUM(1) AS impressions FROM (SELECT ['Istanbul', 'Berlin', 'Bobruisk'] AS cities) WHERE arrayJoin(cities) IN ['Istanbul', 'Berlin']\",\n            \"SELECT SUM(1) AS impressions FROM (SELECT ['Istanbul', 'Berlin', 'Bobruisk'] AS cities) WHERE arrayJoin(cities) IN ('Istanbul', 'Berlin')\",\n        )\n\n        self.validate_identity(\"SELECT SUBSTRING_INDEX(str, delim, count)\")\n        self.validate_identity(\"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\")\n        self.validate_identity(\"SELECT SUBSTRING_INDEX('a.b.c.d', '.', -2)\")\n\n        self.validate_all(\n            \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n            write={\n                \"databricks\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n                \"spark\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n                \"mysql\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT substringIndex('a.b.c.d', '.', 2)\",\n            write={\n                \"databricks\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n                \"spark\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n                \"mysql\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n                \"clickhouse\": \"SELECT substringIndex('a.b.c.d', '.', 2)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT CAST(STR_TO_DATE(SUBSTRING(a.eta, 1, 10), '%Y-%m-%d') AS Nullable(DATE))\",\n            read={\n                \"clickhouse\": \"SELECT CAST(STR_TO_DATE(SUBSTRING(a.eta, 1, 10), '%Y-%m-%d') AS Nullable(DATE))\",\n                \"oracle\": \"SELECT to_date(substr(a.eta, 1,10), 'YYYY-MM-DD')\",\n            },\n        )\n\n        self.validate_all(\n            \"CHAR(67) || CHAR(65) || CHAR(84)\",\n            read={\n                \"clickhouse\": \"CHAR(67) || CHAR(65) || CHAR(84)\",\n                \"oracle\": \"CHR(67) || CHR(65) || CHR(84)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT lagInFrame(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_sal FROM employees\",\n            read={\n                \"clickhouse\": \"SELECT lagInFrame(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_sal FROM employees\",\n                \"oracle\": \"SELECT LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_sal FROM employees\",\n            },\n        )\n        self.validate_all(\n            \"SELECT leadInFrame(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_sal FROM employees\",\n            read={\n                \"clickhouse\": \"SELECT leadInFrame(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_sal FROM employees\",\n                \"oracle\": \"SELECT LEAD(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_sal FROM employees\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(STR_TO_DATE('05 12 2000', '%d %m %Y') AS Nullable(DATE))\",\n            read={\n                \"clickhouse\": \"SELECT CAST(STR_TO_DATE('05 12 2000', '%d %m %Y') AS Nullable(DATE))\",\n                \"postgres\": \"SELECT TO_DATE('05 12 2000', 'DD MM YYYY')\",\n            },\n            write={\n                \"clickhouse\": \"SELECT CAST(STR_TO_DATE('05 12 2000', '%d %m %Y') AS Nullable(DATE))\",\n                \"postgres\": \"SELECT CAST(CAST(TO_DATE('05 12 2000', 'DD MM YYYY') AS TIMESTAMP) AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x PREWHERE y = 1 WHERE z = 2\",\n            write={\n                \"\": \"SELECT * FROM x WHERE z = 2\",\n                \"clickhouse\": \"SELECT * FROM x PREWHERE y = 1 WHERE z = 2\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x AS prewhere\",\n            read={\n                \"clickhouse\": \"SELECT * FROM x AS prewhere\",\n                \"duckdb\": \"SELECT * FROM x prewhere\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a, b FROM (SELECT * FROM x) AS t(a, b)\",\n            read={\n                \"clickhouse\": \"SELECT a, b FROM (SELECT * FROM x) AS t(a, b)\",\n                \"duckdb\": \"SELECT a, b FROM (SELECT * FROM x) AS t(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT arrayJoin([1,2,3])\",\n            write={\n                \"clickhouse\": \"SELECT arrayJoin([1, 2, 3])\",\n                \"postgres\": \"SELECT UNNEST(ARRAY[1, 2, 3])\",\n            },\n        )\n        self.validate_all(\n            \"has([1], x)\",\n            read={\n                \"postgres\": \"x = any(array[1])\",\n            },\n        )\n        self.validate_all(\n            \"NOT has([1], x)\",\n            read={\n                \"postgres\": \"any(array[1]) <> x\",\n            },\n        )\n        self.validate_all(\n            \"has([1], x)\",\n            read={\n                \"clickhouse\": \"has([1], x)\",\n                \"presto\": \"CONTAINS(ARRAY[1], x)\",\n                \"spark\": \"ARRAY_CONTAINS(ARRAY(1), x)\",\n            },\n            write={\n                \"presto\": \"CONTAINS(ARRAY[1], x)\",\n                \"spark\": \"ARRAY_CONTAINS(ARRAY(1), x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('2020-01-01' AS Nullable(DateTime)) + INTERVAL '500' MICROSECOND\",\n            read={\n                \"duckdb\": \"SELECT TIMESTAMP '2020-01-01' + INTERVAL '500 us'\",\n                \"postgres\": \"SELECT TIMESTAMP '2020-01-01' + INTERVAL '500 us'\",\n            },\n            write={\n                \"clickhouse\": \"SELECT CAST('2020-01-01' AS Nullable(DateTime)) + INTERVAL '500' MICROSECOND\",\n                \"duckdb\": \"SELECT CAST('2020-01-01' AS TIMESTAMP) + INTERVAL '500' MICROSECOND\",\n                \"postgres\": \"SELECT CAST('2020-01-01' AS TIMESTAMP) + INTERVAL '500 MICROSECOND'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CURRENT_DATE()\",\n            read={\n                \"clickhouse\": \"SELECT CURRENT_DATE()\",\n                \"postgres\": \"SELECT CURRENT_DATE\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CURRENT_TIMESTAMP()\",\n            read={\n                \"clickhouse\": \"SELECT CURRENT_TIMESTAMP()\",\n                \"postgres\": \"SELECT CURRENT_TIMESTAMP\",\n            },\n        )\n        self.validate_all(\n            \"SELECT match('ThOmAs', CONCAT('(?i)', 'thomas'))\",\n            read={\n                \"postgres\": \"SELECT 'ThOmAs' ~* 'thomas'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT match('ThOmAs', CONCAT('(?i)', x)) FROM t\",\n            read={\n                \"postgres\": \"SELECT 'ThOmAs' ~* x FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '\\\\0'\",\n            read={\n                \"mysql\": \"SELECT '\\0'\",\n            },\n            write={\n                \"clickhouse\": \"SELECT '\\\\0'\",\n                \"mysql\": \"SELECT '\\0'\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD(DAY, 1, x)\",\n            read={\n                \"clickhouse\": \"dateAdd(DAY, 1, x)\",\n                \"presto\": \"DATE_ADD('DAY', 1, x)\",\n            },\n            write={\n                \"clickhouse\": \"DATE_ADD(DAY, 1, x)\",\n                \"presto\": \"DATE_ADD('DAY', 1, x)\",\n                \"\": \"DATE_ADD(x, 1, 'DAY')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_DIFF(DAY, a, b)\",\n            read={\n                \"clickhouse\": \"dateDiff(DAY, a, b)\",\n                \"presto\": \"DATE_DIFF('DAY', a, b)\",\n            },\n            write={\n                \"clickhouse\": \"DATE_DIFF(DAY, a, b)\",\n                \"presto\": \"DATE_DIFF('DAY', a, b)\",\n                \"\": \"DATEDIFF(b, a, DAY)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT xor(1, 0)\",\n            read={\n                \"clickhouse\": \"SELECT xor(1, 0)\",\n                \"mysql\": \"SELECT 1 XOR 0\",\n            },\n            write={\n                \"mysql\": \"SELECT 1 XOR 0\",\n            },\n        )\n        self.validate_all(\n            \"SELECT xor(0, 1, xor(1, 0, 0))\",\n            write={\n                \"clickhouse\": \"SELECT xor(0, 1, xor(1, 0, 0))\",\n                \"mysql\": \"SELECT 0 XOR 1 XOR 1 XOR 0 XOR 0\",\n            },\n        )\n        self.validate_all(\n            \"SELECT xor(xor(1, 0), 1)\",\n            read={\n                \"clickhouse\": \"SELECT xor(xor(1, 0), 1)\",\n                \"mysql\": \"SELECT 1 XOR 0 XOR 1\",\n            },\n            write={\n                \"clickhouse\": \"SELECT xor(xor(1, 0), 1)\",\n                \"mysql\": \"SELECT 1 XOR 0 XOR 1\",\n            },\n        )\n        self.validate_identity(\"SELECT xor(0, 1, 1, 0)\")\n        self.validate_all(\n            \"CONCAT(a, b)\",\n            read={\n                \"clickhouse\": \"CONCAT(a, b)\",\n                \"mysql\": \"CONCAT(a, b)\",\n            },\n            write={\n                \"mysql\": \"CONCAT(a, b)\",\n                \"postgres\": \"a || b\",\n            },\n        )\n        self.validate_all(\n            r\"'Enum8(\\'Sunday\\' = 0)'\", write={\"clickhouse\": \"'Enum8(''Sunday'' = 0)'\"}\n        )\n        self.validate_all(\n            \"SELECT uniq(x) FROM (SELECT any(y) AS x FROM (SELECT 1 AS y))\",\n            read={\n                \"bigquery\": \"SELECT APPROX_COUNT_DISTINCT(x) FROM (SELECT ANY_VALUE(y) x FROM (SELECT 1 y))\",\n            },\n            write={\n                \"bigquery\": \"SELECT APPROX_COUNT_DISTINCT(x) FROM (SELECT ANY_VALUE(y) AS x FROM (SELECT 1 AS y))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"clickhouse\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname\",\n                \"spark\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname NULLS LAST\",\n            },\n        )\n        self.validate_all(\n            \"CAST(1 AS NULLABLE(Int64))\",\n            write={\n                \"clickhouse\": \"CAST(1 AS Nullable(Int64))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(1 AS Nullable(DateTime64(6, 'UTC')))\",\n            write={\"clickhouse\": \"CAST(1 AS Nullable(DateTime64(6, 'UTC')))\"},\n        )\n        self.validate_all(\n            \"SELECT x #! comment\",\n            write={\"\": \"SELECT x /* comment */\"},\n        )\n        self.validate_all(\n            \"SELECT quantileIf(0.5)(a, true)\",\n            write={\n                \"clickhouse\": \"SELECT quantileIf(0.5)(a, TRUE)\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT POSITION(needle IN haystack)\", \"SELECT POSITION(haystack, needle)\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM x LIMIT 10 SETTINGS max_results = 100, result = 'break'\"\n        )\n        self.validate_identity(\"SELECT * FROM x LIMIT 10 SETTINGS max_results = 100, result_\")\n        self.validate_identity(\"SELECT * FROM x FORMAT PrettyCompact\")\n        self.validate_identity(\n            \"SELECT * FROM x LIMIT 10 SETTINGS max_results = 100, result_ FORMAT PrettyCompact\"\n        )\n        self.validate_all(\n            \"SELECT * FROM foo JOIN bar USING id, name\",\n            write={\"clickhouse\": \"SELECT * FROM foo JOIN bar USING (id, name)\"},\n        )\n        self.validate_all(\n            \"SELECT * FROM foo ANY LEFT JOIN bla ON foo.c1 = bla.c2\",\n            write={\"clickhouse\": \"SELECT * FROM foo LEFT ANY JOIN bla ON foo.c1 = bla.c2\"},\n        )\n        self.validate_all(\n            \"SELECT * FROM foo GLOBAL ANY LEFT JOIN bla ON foo.c1 = bla.c2\",\n            write={\"clickhouse\": \"SELECT * FROM foo GLOBAL LEFT ANY JOIN bla ON foo.c1 = bla.c2\"},\n        )\n        self.validate_all(\n            \"\"\"\n            SELECT\n                loyalty,\n                count()\n            FROM hits SEMI LEFT JOIN users USING (UserID)\n            GROUP BY loyalty\n            ORDER BY loyalty ASC\n            \"\"\",\n            write={\n                \"clickhouse\": \"SELECT loyalty, count() FROM hits LEFT SEMI JOIN users USING (UserID)\"\n                \" GROUP BY loyalty ORDER BY loyalty ASC\"\n            },\n        )\n        self.validate_all(\n            \"SELECT quantile(0.5)(a)\",\n            read={\n                \"duckdb\": \"SELECT quantile(a, 0.5)\",\n                \"clickhouse\": \"SELECT median(a)\",\n            },\n            write={\n                \"clickhouse\": \"SELECT quantile(0.5)(a)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT quantiles(0.5, 0.4)(a)\",\n            read={\"duckdb\": \"SELECT quantile(a, [0.5, 0.4])\"},\n            write={\"clickhouse\": \"SELECT quantiles(0.5, 0.4)(a)\"},\n        )\n        self.validate_all(\n            \"SELECT quantiles(0.5)(a)\",\n            read={\"duckdb\": \"SELECT quantile(a, [0.5])\"},\n            write={\"clickhouse\": \"SELECT quantiles(0.5)(a)\"},\n        )\n\n        self.validate_identity(\"SELECT isNaN(x)\")\n        self.validate_all(\n            \"SELECT IS_NAN(x), ISNAN(x)\",\n            write={\"clickhouse\": \"SELECT isNaN(x), isNaN(x)\"},\n        )\n\n        self.validate_identity(\"SELECT startsWith('a', 'b')\")\n        self.validate_all(\n            \"SELECT STARTS_WITH('a', 'b'), STARTSWITH('a', 'b')\",\n            write={\"clickhouse\": \"SELECT startsWith('a', 'b'), startsWith('a', 'b')\"},\n        )\n        self.validate_identity(\"SYSTEM STOP MERGES foo.bar\", check_command_warning=True)\n\n        self.validate_identity(\n            \"INSERT INTO FUNCTION s3('url', 'CSV', 'name String, value UInt32', 'gzip') SELECT name, value FROM existing_table\"\n        )\n        self.validate_identity(\n            \"INSERT INTO FUNCTION remote('localhost', default.simple_table) VALUES (100, 'inserted via remote()')\",\n            \"INSERT INTO FUNCTION remote('localhost', default.simple_table) VALUES ((100), ('inserted via remote()'))\",\n        )\n        self.validate_identity(\n            \"\"\"INSERT INTO TABLE FUNCTION hdfs('hdfs://hdfs1:9000/test', 'TSV', 'name String, column2 UInt32, column3 UInt32') VALUES ('test', 1, 2)\"\"\",\n            \"\"\"INSERT INTO FUNCTION hdfs('hdfs://hdfs1:9000/test', 'TSV', 'name String, column2 UInt32, column3 UInt32') VALUES (('test'), (1), (2))\"\"\",\n        )\n\n        self.validate_identity(\n            \"INSERT INTO t (n.a, n.b) VALUES (1, [1, 2])\",\n            \"INSERT INTO t (n.a, n.b) VALUES ((1), ([1, 2]))\",\n        )\n\n        self.validate_identity(\"SELECT 1 FORMAT TabSeparated\")\n        self.validate_identity(\"SELECT * FROM t FORMAT TabSeparated\")\n        self.validate_identity(\"SELECT FORMAT\")\n        self.validate_identity(\"1 AS FORMAT\").assert_is(exp.Alias)\n\n        self.validate_identity(\"SELECT formatDateTime(NOW(), '%Y-%m-%d', '%T')\")\n        self.validate_all(\n            \"SELECT formatDateTime(NOW(), '%Y-%m-%d')\",\n            read={\n                \"clickhouse\": \"SELECT formatDateTime(NOW(), '%Y-%m-%d')\",\n                \"mysql\": \"SELECT DATE_FORMAT(NOW(), '%Y-%m-%d')\",\n            },\n            write={\n                \"clickhouse\": \"SELECT formatDateTime(NOW(), '%Y-%m-%d')\",\n                \"mysql\": \"SELECT DATE_FORMAT(NOW(), '%Y-%m-%d')\",\n            },\n        )\n\n        self.validate_identity(\"ALTER TABLE visits DROP PARTITION 201901\")\n        self.validate_identity(\"ALTER TABLE visits DROP PARTITION ALL\")\n        self.validate_identity(\n            \"ALTER TABLE visits DROP PARTITION tuple(toYYYYMM(toDate('2019-01-25')))\"\n        )\n        self.validate_identity(\"ALTER TABLE visits DROP PARTITION ID '201901'\")\n\n        self.validate_identity(\"ALTER TABLE visits REPLACE PARTITION 201901 FROM visits_tmp\")\n        self.validate_identity(\"ALTER TABLE visits REPLACE PARTITION ALL FROM visits_tmp\")\n        self.validate_identity(\n            \"ALTER TABLE visits REPLACE PARTITION tuple(toYYYYMM(toDate('2019-01-25'))) FROM visits_tmp\"\n        )\n        self.validate_identity(\"ALTER TABLE visits REPLACE PARTITION ID '201901' FROM visits_tmp\")\n        self.validate_identity(\"ALTER TABLE visits ON CLUSTER test_cluster DROP COLUMN col1\")\n        self.validate_identity(\"ALTER TABLE visits ON CLUSTER '{cluster}' DROP COLUMN col1\")\n        self.validate_identity(\"DELETE FROM tbl ON CLUSTER test_cluster WHERE date = '2019-01-01'\")\n        self.validate_identity(\"DELETE FROM tbl ON CLUSTER '{cluster}' WHERE date = '2019-01-01'\")\n\n        self.assertIsInstance(\n            parse_one(\"Tuple(select Int64)\", into=exp.DataType, read=\"clickhouse\"), exp.DataType\n        )\n\n        self.validate_identity(\n            \"INSERT INTO t (col1, col2) VALUES ('abcd', 1234)\",\n            \"INSERT INTO t (col1, col2) VALUES (('abcd'), (1234))\",\n        )\n        self.validate_all(\n            \"INSERT INTO t (col1, col2) VALUES ('abcd', 1234)\",\n            write={\n                \"clickhouse\": \"INSERT INTO t (col1, col2) VALUES (('abcd'), (1234))\",\n                \"postgres\": \"INSERT INTO t (col1, col2) VALUES (('abcd'), (1234))\",\n            },\n        )\n        self.validate_identity(\"SELECT TRIM(TRAILING ')' FROM '(   Hello, world!   )')\")\n        self.validate_identity(\"SELECT TRIM(LEADING '(' FROM '(   Hello, world!   )')\")\n        self.validate_identity(\"current_timestamp\").assert_is(exp.Column)\n\n        self.validate_identity(\"SELECT * APPLY(sum) FROM columns_transformers\")\n        self.validate_identity(\"SELECT COLUMNS('[jk]') APPLY(toString) FROM columns_transformers\")\n        self.validate_identity(\n            \"SELECT COLUMNS('[jk]') APPLY(toString) APPLY(length) APPLY(max) FROM columns_transformers\"\n        )\n        self.validate_identity(\"SELECT * APPLY(sum), COLUMNS('col') APPLY(sum) APPLY(avg) FROM t\")\n        self.validate_identity(\n            \"SELECT * FROM ABC WHERE hasAny(COLUMNS('.*field') APPLY(toUInt64) APPLY(to), (SELECT groupUniqArray(toUInt64(field))))\"\n        )\n        self.validate_identity(\"SELECT col apply\", \"SELECT col AS apply\")\n        self.validate_identity(\n            \"SELECT name FROM data WHERE (SELECT DISTINCT name FROM data) IS NOT NULL\",\n            \"SELECT name FROM data WHERE NOT ((SELECT DISTINCT name FROM data) IS NULL)\",\n        )\n\n        self.validate_identity(\"SELECT 1_2_3_4_5\", \"SELECT 12345\")\n        self.validate_identity(\"SELECT 1_b\", \"SELECT 1_b\")\n        self.validate_identity(\n            \"SELECT COUNT(1) FROM table SETTINGS additional_table_filters = {'a': 'b', 'c': 'd'}\"\n        )\n        self.validate_identity(\"SELECT arrayConcat([1, 2], [3, 4])\")\n\n        self.validate_identity(\"SELECT parseDateTime('2021-01-04+23:00:00', '%Y-%m-%d+%H:%i:%s')\")\n        self.validate_identity(\n            \"SELECT parseDateTime('2021-01-04+23:00:00', '%Y-%m-%d+%H:%i:%s', 'Asia/Istanbul')\"\n        )\n\n        self.validate_identity(\"farmFingerprint64(x1, x2, x3)\")\n\n        self.validate_identity(\"cityHash64()\")\n        self.validate_identity(\"cityHash64(x)\")\n        self.validate_identity(\"cityHash64(x, y, z)\")\n\n        self.validate_identity(\"cosineDistance(x, y)\")\n        self.validate_identity(\"L2Distance(x, y)\")\n        self.validate_identity(\"tuple(1 = 1, 'foo' = 'foo')\")\n\n        self.validate_identity(\"SELECT LIKE(a, b)\", \"SELECT a LIKE b\")\n        self.validate_identity(\"SELECT notLike(a, b)\", \"SELECT NOT a LIKE b\")\n        self.validate_identity(\"SELECT ilike(a, b)\", \"SELECT a ILIKE b\")\n\n        self.validate_identity(\"currentDatabase()\", \"CURRENT_DATABASE()\")\n        self.validate_identity(\"currentSchemas(TRUE)\", \"CURRENT_SCHEMAS(TRUE)\")\n\n        self.validate_identity(\"VERSION()\")\n\n        self.validate_identity(\n            \"SELECT quantilesExactExclusive(0.25, 0.5, 0.75)(x) AS y FROM (SELECT number AS x FROM num)\"\n        )\n\n        self.validate_identity(\"SELECT or(0, 1, -2)\", \"SELECT 0 OR 1 OR -2\")\n        self.validate_identity(\"SELECT and(1, 2, 3)\", \"SELECT 1 AND 2 AND 3\")\n        self.validate_identity(\"SELECT or(and(3, 0), 5)\", \"SELECT (3 AND 0) OR 5\")\n\n        self.validate_identity(\"arrayCompact([1, 1, nan, nan, 2, 3, 3, 3])\").assert_is(\n            exp.ArrayCompact\n        )\n        self.validate_identity(\"arrayConcat([1, 2], [3, 4])\").assert_is(exp.ArrayConcat)\n        self.validate_identity(\"arrayDistinct([1, 2, 2, 3, 1])\").assert_is(exp.ArrayDistinct)\n        self.validate_identity(\"arrayExcept([1, 2, 3, 2, 4], [3, 5])\").assert_is(exp.ArrayExcept)\n        self.validate_identity(\"SELECT UTCTimestamp()\", \"SELECT CURRENT_TIMESTAMP('UTC')\")\n\n        for global_ in [\"\", \"GLOBAL \"]:\n            for side in [\"\", \"LEFT \", \"RIGHT \", \"FULL \"]:\n                for strictness in [\"ANY \", \"ALL \"]:\n                    sql = f\"SELECT * FROM foo1 {global_}{side}{strictness}JOIN foo2 ON foo1.id = foo2.id\"\n                    with self.subTest(sql=sql):\n                        self.validate_identity(sql)\n\n        self.validate_identity(\"SELECT []\")\n\n    def test_clickhouse_values(self):\n        ast = self.parse_one(\"SELECT * FROM VALUES (1, 2, 3)\")\n        self.assertEqual(len(list(ast.find_all(exp.Tuple))), 4)\n\n        values = exp.select(\"*\").from_(\n            exp.values([exp.tuple_(1, 2, 3)], alias=\"subq\", columns=[\"a\", \"b\", \"c\"])\n        )\n        self.assertEqual(\n            values.sql(\"clickhouse\"),\n            \"SELECT * FROM (SELECT 1 AS a, 2 AS b, 3 AS c) AS subq\",\n        )\n\n        self.validate_identity(\"SELECT * FROM VALUES ((1, 1), (2, 1), (3, 1), (4, 1))\")\n        self.validate_identity(\n            \"SELECT type, id FROM VALUES ('id Int, type Int', (1, 1), (2, 1), (3, 1), (4, 1))\"\n        )\n\n        self.validate_identity(\n            \"INSERT INTO t (col1, col2) VALUES ('abcd', 1234)\",\n            \"INSERT INTO t (col1, col2) VALUES (('abcd'), (1234))\",\n        )\n        self.validate_identity(\n            \"INSERT INTO t (col1, col2) FORMAT Values('abcd', 1234)\",\n            \"INSERT INTO t (col1, col2) VALUES (('abcd'), (1234))\",\n        )\n\n        self.validate_all(\n            \"SELECT col FROM (SELECT 1 AS col) AS _t\",\n            read={\n                \"duckdb\": \"SELECT col FROM (VALUES (1)) AS _t(col)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT col1, col2 FROM (SELECT 1 AS col1, 2 AS col2 UNION ALL SELECT 3, 4) AS _t\",\n            read={\n                \"duckdb\": \"SELECT col1, col2 FROM (VALUES (1, 2), (3, 4)) AS _t(col1, col2)\",\n            },\n        )\n\n    def test_cte(self):\n        self.validate_identity(\"WITH 'x' AS foo SELECT foo\")\n        self.validate_identity(\"WITH ['c'] AS field_names SELECT field_names\")\n        self.validate_identity(\"WITH SUM(bytes) AS foo SELECT foo FROM system.parts\")\n        self.validate_identity(\"WITH (SELECT foo) AS bar SELECT bar + 5\")\n        self.validate_identity(\"WITH test1 AS (SELECT i + 1, j + 1 FROM test1) SELECT * FROM test1\")\n\n        query = parse_one(\"\"\"WITH (SELECT 1) AS y SELECT * FROM y\"\"\", read=\"clickhouse\")\n        self.assertIsInstance(query.args[\"with_\"].expressions[0].this, exp.Subquery)\n        self.assertEqual(query.args[\"with_\"].expressions[0].alias, \"y\")\n\n        query = \"WITH 1 AS var SELECT var\"\n        for error_level in [ErrorLevel.IGNORE, ErrorLevel.RAISE, ErrorLevel.IMMEDIATE]:\n            self.assertEqual(\n                self.parse_one(query, error_level=error_level).sql(dialect=self.dialect),\n                query,\n            )\n\n        self.validate_identity(\"arraySlice(x, 1)\")\n\n    def test_ternary(self):\n        self.validate_all(\"x ? 1 : 2\", write={\"clickhouse\": \"CASE WHEN x THEN 1 ELSE 2 END\"})\n        self.validate_all(\n            \"IF(BAR(col), sign > 0 ? FOO() : 0, 1)\",\n            write={\n                \"clickhouse\": \"CASE WHEN BAR(col) THEN CASE WHEN sign > 0 THEN FOO() ELSE 0 END ELSE 1 END\"\n            },\n        )\n        self.validate_all(\n            \"x AND FOO() > 3 + 2 ? 1 : 2\",\n            write={\"clickhouse\": \"CASE WHEN x AND FOO() > 3 + 2 THEN 1 ELSE 2 END\"},\n        )\n        self.validate_all(\n            \"x ? (y ? 1 : 2) : 3\",\n            write={\"clickhouse\": \"CASE WHEN x THEN (CASE WHEN y THEN 1 ELSE 2 END) ELSE 3 END\"},\n        )\n        self.validate_all(\n            \"x AND (foo() ? FALSE : TRUE) ? (y ? 1 : 2) : 3\",\n            write={\n                \"clickhouse\": \"CASE WHEN x AND (CASE WHEN foo() THEN FALSE ELSE TRUE END) THEN (CASE WHEN y THEN 1 ELSE 2 END) ELSE 3 END\"\n            },\n        )\n\n        ternary = parse_one(\"x ? (y ? 1 : 2) : 3\", read=\"clickhouse\")\n\n        self.assertIsInstance(ternary, exp.If)\n        self.assertIsInstance(ternary.this, exp.Column)\n        self.assertIsInstance(ternary.args[\"true\"], exp.Paren)\n        self.assertIsInstance(ternary.args[\"false\"], exp.Literal)\n\n        nested_ternary = ternary.args[\"true\"].this\n\n        self.assertIsInstance(nested_ternary.this, exp.Column)\n        self.assertIsInstance(nested_ternary.args[\"true\"], exp.Literal)\n        self.assertIsInstance(nested_ternary.args[\"false\"], exp.Literal)\n\n        parse_one(\"a and b ? 1 : 2\", read=\"clickhouse\").assert_is(exp.If).this.assert_is(exp.And)\n\n    def test_parameterization(self):\n        self.validate_all(\n            \"SELECT {abc: UInt32}, {b: String}, {c: DateTime},{d: Map(String, Array(UInt8))}, {e: Tuple(UInt8, String)}\",\n            write={\n                \"clickhouse\": \"SELECT {abc: UInt32}, {b: String}, {c: DateTime}, {d: Map(String, Array(UInt8))}, {e: Tuple(UInt8, String)}\",\n                \"\": \"SELECT :abc, :b, :c, :d, :e\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM {table: Identifier}\",\n            write={\"clickhouse\": \"SELECT * FROM {table: Identifier}\"},\n        )\n\n    def test_signed_and_unsigned_types(self):\n        data_types = [\n            \"UInt8\",\n            \"UInt16\",\n            \"UInt32\",\n            \"UInt64\",\n            \"UInt128\",\n            \"UInt256\",\n            \"Int8\",\n            \"Int16\",\n            \"Int32\",\n            \"Int64\",\n            \"Int128\",\n            \"Int256\",\n        ]\n        for data_type in data_types:\n            self.validate_all(\n                f\"pow(2, 32)::{data_type}\",\n                write={\"clickhouse\": f\"CAST(pow(2, 32) AS {data_type})\"},\n            )\n\n    def test_geom_types(self):\n        data_types = [\"Point\", \"Ring\", \"LineString\", \"MultiLineString\", \"Polygon\", \"MultiPolygon\"]\n        for data_type in data_types:\n            with self.subTest(f\"Casting to ClickHouse {data_type}\"):\n                self.validate_identity(f\"SELECT CAST(val AS {data_type})\")\n\n    def test_nothing_type(self):\n        data_types = [\"Nothing\", \"Nullable(Nothing)\"]\n        for data_type in data_types:\n            with self.subTest(f\"Casting to ClickHouse {data_type}\"):\n                self.validate_identity(f\"SELECT CAST(val AS {data_type})\")\n\n    def test_json_type(self):\n        data_types = [\n            \"JSON\",\n            \"JSON(col1 String, SKIP col2)\",\n            \"JSON(col1 String, SKIP REGEXP 'col[0-9]+')\",\n            \"JSON(col1 String, max_dynamic_paths = 2)\",\n            \"JSON(col1.nested String, SKIP col2.nested)\",\n        ]\n        for i, data_type in enumerate(data_types):\n            with self.subTest(f\"Casting to ClickHouse JSON[{i}]\"):\n                self.validate_identity(f\"SELECT CAST(val AS {data_type})\")\n\n        data_types_non_idempotent = [\n            (\"JSON()\", \"JSON\"),\n        ]\n        for i, (dt_in, dt_out) in enumerate(data_types_non_idempotent):\n            with self.subTest(f\"Casting to ClickHouse JSON[{i}]\"):\n                self.validate_identity(\n                    f\"SELECT CAST(val as {dt_in})\", write_sql=f\"SELECT CAST(val AS {dt_out})\"\n                )\n\n        # Multiline JSON type and non-case-sensitive SKIP\n        self.validate_identity(\n            \"\"\"SELECT CAST(val AS JSON(\n                col1 String,\n                skip col2,\n                max_dynamic_paths=2\n            ))\"\"\",\n            \"SELECT CAST(val AS JSON(col1 String, SKIP col2, max_dynamic_paths = 2))\",\n        )\n\n        self.validate_identity(\n            \"SELECT CAST(val AS JSON(col1 String, col2 JSON(colA String, SKIP colB)))\"\n        )\n\n    def test_aggregate_function_column_with_any_keyword(self):\n        # Regression test for https://github.com/tobymao/sqlglot/issues/4723\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE my_db.my_table\n            (\n                someId UUID,\n                aggregatedColumn AggregateFunction(any, String),\n                aggregatedColumnWithParams AggregateFunction(any(somecolumn), String),\n            )\n            ENGINE = AggregatingMergeTree()\n            ORDER BY (someId)\n                    \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE my_db.my_table (\n  someId UUID,\n  aggregatedColumn AggregateFunction(any, String),\n  aggregatedColumnWithParams AggregateFunction(any(somecolumn), String)\n)\nENGINE=AggregatingMergeTree()\nORDER BY (\n  someId\n)\"\"\",\n            },\n            pretty=True,\n        )\n\n    def test_create_table_as_alias(self):\n        ctas_alias = \"CREATE TABLE my_db.my_table AS another_db.another_table\"\n\n        expected = exp.Create(\n            this=exp.to_table(\"my_db.my_table\"),\n            kind=\"TABLE\",\n            expression=exp.to_table(\"another_db.another_table\"),\n        )\n        self.assertEqual(self.parse_one(ctas_alias), expected)\n        self.validate_identity(ctas_alias)\n\n    def test_ddl(self):\n        db_table_expr = exp.Table(this=None, db=exp.to_identifier(\"foo\"), catalog=None)\n        create_with_cluster = exp.Create(\n            this=db_table_expr,\n            kind=\"DATABASE\",\n            properties=exp.Properties(expressions=[exp.OnCluster(this=exp.to_identifier(\"c\"))]),\n        )\n        self.assertEqual(create_with_cluster.sql(\"clickhouse\"), \"CREATE DATABASE foo ON CLUSTER c\")\n\n        # Transpiled CREATE SCHEMA may have OnCluster property set\n        create_with_cluster = exp.Create(\n            this=db_table_expr,\n            kind=\"SCHEMA\",\n            properties=exp.Properties(expressions=[exp.OnCluster(this=exp.to_identifier(\"c\"))]),\n        )\n        self.assertEqual(create_with_cluster.sql(\"clickhouse\"), \"CREATE DATABASE foo ON CLUSTER c\")\n\n        ctas_with_comment = exp.Create(\n            this=exp.table_(\"foo\"),\n            kind=\"TABLE\",\n            expression=exp.select(\"*\").from_(\"db.other_table\"),\n            properties=exp.Properties(\n                expressions=[\n                    exp.EngineProperty(this=exp.var(\"Memory\")),\n                    exp.SchemaCommentProperty(this=exp.Literal.string(\"foo\")),\n                ],\n            ),\n        )\n        self.assertEqual(\n            ctas_with_comment.sql(\"clickhouse\"),\n            \"CREATE TABLE foo ENGINE=Memory AS (SELECT * FROM db.other_table) COMMENT 'foo'\",\n        )\n\n        self.validate_identity(\"CREATE FUNCTION linear_equation AS (x, k, b) -> k * x + b\")\n        self.validate_identity(\"CREATE MATERIALIZED VIEW a.b TO a.c (c Int32) AS SELECT * FROM a.d\")\n        self.validate_identity(\"\"\"CREATE TABLE ip_data (ip4 IPv4, ip6 IPv6) ENGINE=TinyLog()\"\"\")\n        self.validate_identity(\"\"\"CREATE TABLE dates (dt1 Date32) ENGINE=TinyLog()\"\"\")\n        self.validate_identity(\"CREATE TABLE named_tuples (a Tuple(select String, i Int64))\")\n        self.validate_identity(\"\"\"CREATE TABLE t (a String) EMPTY AS SELECT * FROM dummy\"\"\")\n        self.validate_identity(\n            \"CREATE TABLE t1 (a String EPHEMERAL, b String EPHEMERAL func(), c String MATERIALIZED func(), d String ALIAS func()) ENGINE=TinyLog()\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (a String, b String, c UInt64, PROJECTION p1 (SELECT a, sum(c) GROUP BY a, b), PROJECTION p2 (SELECT b, sum(c) GROUP BY b)) ENGINE=MergeTree()\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE TABLE xyz (ts DateTime, data String) ENGINE=MergeTree() ORDER BY ts SETTINGS index_granularity = 8192 COMMENT '{\"key\": \"value\"}'\"\"\"\n        )\n        self.validate_identity(\n            \"INSERT INTO FUNCTION s3('a', 'b', 'c', 'd', 'e') PARTITION BY CONCAT(s1, s2, s3, s4) SETTINGS set1 = 1, set2 = '2' SELECT * FROM some_table SETTINGS foo = 3\"\n        )\n        self.validate_identity(\n            'CREATE TABLE data5 (\"x\" UInt32, \"y\" UInt32) ENGINE=MergeTree ORDER BY (round(y / 1000000000), cityHash64(x)) SAMPLE BY cityHash64(x)'\n        )\n        self.validate_identity(\n            \"CREATE TABLE foo (x UInt32) TTL time_column + INTERVAL '1' MONTH DELETE WHERE column = 'value'\"\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION parity_str AS (n) -> IF(n % 2, 'odd', 'even')\",\n            \"CREATE FUNCTION parity_str AS n -> CASE WHEN n % 2 THEN 'odd' ELSE 'even' END\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE a ENGINE=Memory AS SELECT 1 AS c COMMENT 'foo'\",\n            \"CREATE TABLE a ENGINE=Memory AS (SELECT 1 AS c) COMMENT 'foo'\",\n        )\n        self.validate_identity(\n            'CREATE TABLE t1 (\"x\" UInt32, \"y\" Dynamic, \"z\" Dynamic(max_types = 10)) ENGINE=MergeTree ORDER BY x'\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (id Int32, name String) ENGINE=MergeTree PRIMARY KEY id\",\n            \"CREATE TABLE test_table (id Int32, name String) ENGINE=MergeTree PRIMARY KEY (id)\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (id Int32, name String) ENGINE=MergeTree PRIMARY KEY tuple()\",\n            \"CREATE TABLE test_table (id Int32, name String) ENGINE=MergeTree PRIMARY KEY (tuple())\",\n        )\n\n        self.validate_identity(\n            \"CREATE TABLE t (a UInt32, CONSTRAINT a_constraint CHECK (a < 10)) ENGINE=MergeTree ORDER BY a\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (a UInt32, CONSTRAINT c1 ASSUME (a > 5)) ENGINE=MergeTree ORDER BY a\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (a UInt32, CONSTRAINT a_constraint CHECK a < 10) ENGINE=MergeTree ORDER BY a\",\n            \"CREATE TABLE t (a UInt32, CONSTRAINT a_constraint CHECK (a < 10)) ENGINE=MergeTree ORDER BY a\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (a UInt32, CONSTRAINT c1 ASSUME a > 5) ENGINE=MergeTree ORDER BY a\",\n            \"CREATE TABLE t (a UInt32, CONSTRAINT c1 ASSUME (a > 5)) ENGINE=MergeTree ORDER BY a\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (a UInt32, CONSTRAINT t CHECK (SELECT 1)) ENGINE=MergeTree ORDER BY a\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (a UInt32, CONSTRAINT t ASSUME (SELECT 1)) ENGINE=MergeTree ORDER BY a\"\n        )\n        self.validate_identity(\"CREATE TABLE t (check UInt32)\")\n        self.validate_identity(\"CREATE TABLE t (assume UInt32)\")\n\n        self.validate_all(\n            \"CREATE DATABASE x\",\n            read={\n                \"duckdb\": \"CREATE SCHEMA x\",\n            },\n            write={\n                \"clickhouse\": \"CREATE DATABASE x\",\n                \"duckdb\": \"CREATE SCHEMA x\",\n            },\n        )\n        self.validate_all(\n            \"DROP DATABASE x\",\n            read={\n                \"duckdb\": \"DROP SCHEMA x\",\n            },\n            write={\n                \"clickhouse\": \"DROP DATABASE x\",\n                \"duckdb\": \"DROP SCHEMA x\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE example1 (\n               timestamp DateTime,\n               x UInt32 TTL now() + INTERVAL 1 MONTH,\n               y String TTL timestamp + INTERVAL 1 DAY,\n               z String\n            )\n            ENGINE = MergeTree\n            ORDER BY tuple()\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE example1 (\n  timestamp DateTime,\n  x UInt32 TTL now() + INTERVAL '1' MONTH,\n  y String TTL timestamp + INTERVAL '1' DAY,\n  z String\n)\nENGINE=MergeTree\nORDER BY tuple()\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE test (id UInt64, timestamp DateTime64, data String, max_hits UInt64, sum_hits UInt64) ENGINE = MergeTree\n            PRIMARY KEY (id, toStartOfDay(timestamp), timestamp)\n            TTL timestamp + INTERVAL 1 DAY\n            GROUP BY id, toStartOfDay(timestamp)\n            SET\n               max_hits = max(max_hits),\n               sum_hits = sum(sum_hits)\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE test (\n  id UInt64,\n  timestamp DateTime64,\n  data String,\n  max_hits UInt64,\n  sum_hits UInt64\n)\nENGINE=MergeTree\nPRIMARY KEY (id, dateTrunc('DAY', timestamp), timestamp)\nTTL\n  timestamp + INTERVAL '1' DAY\nGROUP BY\n  id,\n  dateTrunc('DAY', timestamp)\nSET\n  max_hits = max(max_hits),\n  sum_hits = sum(sum_hits)\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE test (id String, data String) ENGINE = AggregatingMergeTree()\n                ORDER BY tuple()\n            SETTINGS\n                max_suspicious_broken_parts=500,\n                parts_to_throw_insert=100\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE test (\n  id String,\n  data String\n)\nENGINE=AggregatingMergeTree()\nORDER BY tuple()\nSETTINGS\n  max_suspicious_broken_parts = 500,\n  parts_to_throw_insert = 100\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE example_table\n            (\n                d DateTime,\n                a Int\n            )\n            ENGINE = MergeTree\n            PARTITION BY toYYYYMM(d)\n            ORDER BY d\n            TTL d + INTERVAL 1 MONTH DELETE,\n                d + INTERVAL 1 WEEK TO VOLUME 'aaa',\n                d + INTERVAL 2 WEEK TO DISK 'bbb';\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE example_table (\n  d DateTime,\n  a Int32\n)\nENGINE=MergeTree\nPARTITION BY toYYYYMM(d)\nORDER BY d\nTTL\n  d + INTERVAL '1' MONTH DELETE,\n  d + INTERVAL '1' WEEK TO VOLUME 'aaa',\n  d + INTERVAL '2' WEEK TO DISK 'bbb'\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE table_with_where\n            (\n                d DateTime,\n                a Int\n            )\n            ENGINE = MergeTree\n            PARTITION BY toYYYYMM(d)\n            ORDER BY d\n            TTL d + INTERVAL 1 MONTH DELETE WHERE toDayOfWeek(d) = 1;\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE table_with_where (\n  d DateTime,\n  a Int32\n)\nENGINE=MergeTree\nPARTITION BY toYYYYMM(d)\nORDER BY d\nTTL\n  d + INTERVAL '1' MONTH DELETE\nWHERE\n  toDayOfWeek(d) = 1\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE table_for_recompression\n            (\n                d DateTime,\n                key UInt64,\n                value String\n            ) ENGINE MergeTree()\n            ORDER BY tuple()\n            PARTITION BY key\n            TTL d + INTERVAL 1 MONTH RECOMPRESS CODEC(ZSTD(17)), d + INTERVAL 1 YEAR RECOMPRESS CODEC(LZ4HC(10))\n            SETTINGS min_rows_for_wide_part = 0, min_bytes_for_wide_part = 0;\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE table_for_recompression (\n  d DateTime,\n  key UInt64,\n  value String\n)\nENGINE=MergeTree()\nORDER BY tuple()\nPARTITION BY key\nTTL\n  d + INTERVAL '1' MONTH RECOMPRESS CODEC(ZSTD(17)),\n  d + INTERVAL '1' YEAR RECOMPRESS CODEC(LZ4HC(10))\nSETTINGS\n  min_rows_for_wide_part = 0,\n  min_bytes_for_wide_part = 0\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE table_for_aggregation\n            (\n                d DateTime,\n                k1 Int,\n                k2 Int,\n                x Int,\n                y Int\n            )\n            ENGINE = MergeTree\n            ORDER BY (k1, k2)\n            TTL d + INTERVAL 1 MONTH GROUP BY k1, k2 SET x = max(x), y = min(y);\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE table_for_aggregation (\n  d DateTime,\n  k1 Int32,\n  k2 Int32,\n  x Int32,\n  y Int32\n)\nENGINE=MergeTree\nORDER BY (k1, k2)\nTTL\n  d + INTERVAL '1' MONTH\nGROUP BY\n  k1,\n  k2\nSET\n  x = max(x),\n  y = min(y)\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE DICTIONARY discounts_dict (\n                advertiser_id UInt64,\n                discount_start_date Date,\n                discount_end_date Date,\n                amount Float64\n            )\n            PRIMARY KEY id\n            SOURCE(CLICKHOUSE(TABLE 'discounts'))\n            LIFETIME(MIN 1 MAX 1000)\n            LAYOUT(RANGE_HASHED(range_lookup_strategy 'max'))\n            RANGE(MIN discount_start_date MAX discount_end_date)\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE DICTIONARY discounts_dict (\n  advertiser_id UInt64,\n  discount_start_date DATE,\n  discount_end_date DATE,\n  amount Float64\n)\nPRIMARY KEY (id)\nSOURCE(CLICKHOUSE(\n  TABLE 'discounts'\n))\nLIFETIME(MIN 1 MAX 1000)\nLAYOUT(RANGE_HASHED(\n  range_lookup_strategy 'max'\n))\nRANGE(MIN discount_start_date MAX discount_end_date)\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE DICTIONARY my_ip_trie_dictionary (\n                prefix String,\n                asn UInt32,\n                cca2 String DEFAULT '??'\n            )\n            PRIMARY KEY prefix\n            SOURCE(CLICKHOUSE(TABLE 'my_ip_addresses'))\n            LAYOUT(IP_TRIE)\n            LIFETIME(3600);\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE DICTIONARY my_ip_trie_dictionary (\n  prefix String,\n  asn UInt32,\n  cca2 String DEFAULT '??'\n)\nPRIMARY KEY (prefix)\nSOURCE(CLICKHOUSE(\n  TABLE 'my_ip_addresses'\n))\nLAYOUT(IP_TRIE())\nLIFETIME(MIN 0 MAX 3600)\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE DICTIONARY polygons_test_dictionary\n            (\n                key Array(Array(Array(Tuple(Float64, Float64)))),\n                name String\n            )\n            PRIMARY KEY key\n            SOURCE(CLICKHOUSE(TABLE 'polygons_test_table'))\n            LAYOUT(POLYGON(STORE_POLYGON_KEY_COLUMN 1))\n            LIFETIME(0);\n            \"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE DICTIONARY polygons_test_dictionary (\n  key Array(Array(Array(Tuple(Float64, Float64)))),\n  name String\n)\nPRIMARY KEY (key)\nSOURCE(CLICKHOUSE(\n  TABLE 'polygons_test_table'\n))\nLAYOUT(POLYGON(\n  STORE_POLYGON_KEY_COLUMN 1\n))\nLIFETIME(MIN 0 MAX 0)\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_identity(\n            \"CREATE DICTIONARY dict1 (key UInt64) PRIMARY KEY (key) SOURCE(CLICKHOUSE(HOST 'localhost' PORT tcpPort() USER 'default' DB CURRENT_DATABASE())) LIFETIME(MIN 1 MAX 10) LAYOUT(FLAT())\"\n        )\n        self.validate_identity(\n            \"CREATE DICTIONARY dict1 (key UInt64) PRIMARY KEY (key) SOURCE(FILE(PATH '/tmp/test.csv' FORMAT CSVWithNames)) LIFETIME(MIN 0 MAX 1) LAYOUT(FLAT())\"\n        )\n        self.validate_identity(\n            \"CREATE DICTIONARY dict1 (key UInt64) PRIMARY KEY (key) SOURCE(NULL()) LAYOUT(CACHE(SIZE_IN_CELLS 1000)) LIFETIME(MIN 0 MAX 1)\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE DICTIONARY dict1 (key UInt64) PRIMARY KEY (key) SOURCE(EXECUTABLE(COMMAND 'echo \"1\"' FORMAT TabSeparated)) LIFETIME(MIN 0 MAX 1) LAYOUT(FLAT())\"\"\"\n        )\n\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE t (\n                a AggregateFunction(quantiles(0.5, 0.9), UInt64),\n                b AggregateFunction(quantiles, UInt64),\n                c SimpleAggregateFunction(sum, Float64),\n                d AggregateFunction(count)\n            )\"\"\",\n            write={\n                \"clickhouse\": \"\"\"CREATE TABLE t (\n  a AggregateFunction(quantiles(0.5, 0.9), UInt64),\n  b AggregateFunction(quantiles, UInt64),\n  c SimpleAggregateFunction(sum, Float64),\n  d AggregateFunction(count)\n)\"\"\"\n            },\n            pretty=True,\n        )\n\n        self.assertIsNotNone(\n            self.validate_identity(\"CREATE TABLE t1 (a String MATERIALIZED func())\").find(\n                exp.ColumnConstraint\n            )\n        )\n\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE session_log\n            (\n                UserID UInt64,\n                SessionID UUID\n            )\n            ENGINE = MergeTree\n            PARTITION BY sipHash64(UserID) % 16\n            ORDER BY tuple();\n            \"\"\",\n            pretty=True,\n        )\n\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE visits\n            (\n                VisitDate Date,\n                Hour UInt8,\n                ClientID UUID\n            )\n            ENGINE = MergeTree()\n            PARTITION BY (toYYYYMM(VisitDate), Hour)\n            ORDER BY Hour;\n            \"\"\",\n            pretty=True,\n        )\n\n        self.validate_identity(\"DROP TABLE t SYNC\")\n        self.validate_identity(\"DROP DATABASE IF EXISTS d SYNC\")\n\n    def test_agg_functions(self):\n        def extract_agg_func(query):\n            return parse_one(query, read=\"clickhouse\").selects[0].this\n\n        self.assertIsInstance(\n            extract_agg_func(\"select quantileGK(100, 0.95) OVER (PARTITION BY id) FROM table\"),\n            exp.AnonymousAggFunc,\n        )\n        self.assertIsInstance(\n            extract_agg_func(\n                \"select quantileGK(100, 0.95)(reading) OVER (PARTITION BY id) FROM table\"\n            ),\n            exp.ParameterizedAgg,\n        )\n        self.assertIsInstance(\n            extract_agg_func(\"select quantileGKIf(100, 0.95) OVER (PARTITION BY id) FROM table\"),\n            exp.CombinedAggFunc,\n        )\n        self.assertIsInstance(\n            extract_agg_func(\n                \"select quantileGKIf(100, 0.95)(reading) OVER (PARTITION BY id) FROM table\"\n            ),\n            exp.CombinedParameterizedAgg,\n        )\n\n        parse_one(\"foobar(x)\").assert_is(exp.Anonymous)\n\n        self.validate_identity(\"SELECT approx_top_sum(column, weight) FROM t\").selects[0].assert_is(\n            exp.AnonymousAggFunc\n        )\n        self.validate_identity(\"SELECT approx_top_sum(N)(column, weight) FROM t\").selects[\n            0\n        ].assert_is(exp.ParameterizedAgg)\n        self.validate_identity(\"SELECT approx_top_sum(N, reserved)(column, weight) FROM t\").selects[\n            0\n        ].assert_is(exp.ParameterizedAgg)\n\n    def test_agg_functions_multiple_suffixes(self):\n        # Regression test: single-suffix\n        self.validate_identity(\"SELECT uniqExactIf(x, y) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n\n        # Double suffix: If + Merge\n        self.validate_identity(\"SELECT countIfMerge(state) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n        self.validate_identity(\"SELECT uniqExactIfMerge(state) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n\n        # Triple suffix: ArgMin + If + State (#4814)\n        self.validate_identity(\"SELECT avgArgMinIfState(x, y) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n\n        # Double suffix + parameters: If + State with quantile parameter\n        self.validate_identity(\"SELECT quantileIfState(0.5)(col, cond) FROM t\").selects[\n            0\n        ].assert_is(exp.CombinedParameterizedAgg)\n\n        # Collision-prone bases: \"Map\" is both a valid suffix and part of the function name.\n        # These must parse as the base function (AnonymousAggFunc), not as sum/min/max + Map suffix.\n        self.validate_identity(\"SELECT sumMap(k, v) FROM t\").selects[0].assert_is(\n            exp.AnonymousAggFunc\n        )\n        self.validate_identity(\"SELECT minMap(k, v) FROM t\").selects[0].assert_is(\n            exp.AnonymousAggFunc\n        )\n        self.validate_identity(\"SELECT maxMap(k, v) FROM t\").selects[0].assert_is(\n            exp.AnonymousAggFunc\n        )\n\n        # Single-suffix chains on collision-prone bases\n        self.validate_identity(\"SELECT sumMapIf(k, v, cond) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n        self.validate_identity(\"SELECT minMapIf(k, v, cond) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n        self.validate_identity(\"SELECT maxMapIf(k, v, cond) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n        self.validate_identity(\"SELECT sumMapState(k, v) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n\n        # Multi-suffix chain on a collision-prone base\n        self.validate_identity(\"SELECT sumMapIfState(k, v, cond) FROM t\").selects[0].assert_is(\n            exp.CombinedAggFunc\n        )\n\n        # example of a nontrivial query:\n        sum_merge_if_merge = (\n            self.validate_identity(\n                \"SELECT sumMergeIfMerge(s) FROM (SELECT sumMergeIfState(agg, 1 = 1) AS s \"\n                \"FROM (SELECT sumState(toFloat64(number)) AS agg FROM numbers(10)))\"\n            )\n            .selects[0]\n            .assert_is(exp.CombinedAggFunc)\n        )\n        assert sum_merge_if_merge.name == \"sumMergeIfMerge\"\n\n    def test_detach(self):\n        for kind in (\"TABLE\", \"VIEW\", \"DICTIONARY\", \"DATABASE\"):\n            with self.subTest(f\"Test DETACH with {kind}\"):\n                self.validate_identity(f\"DETACH {kind} t\")\n                self.validate_identity(f\"DETACH {kind} IF EXISTS t\")\n                self.validate_identity(f\"DETACH {kind} IF EXISTS db.t\")\n                self.validate_identity(f\"DETACH {kind} t ON CLUSTER c\")\n                self.validate_identity(f\"DETACH {kind} t PERMANENTLY\")\n                self.validate_identity(f\"DETACH {kind} t SYNC\")\n                self.validate_identity(\n                    f\"DETACH {kind} IF EXISTS db.t ON CLUSTER c PERMANENTLY SYNC\"\n                )\n\n    def test_drop_on_cluster(self):\n        for creatable in (\"DATABASE\", \"TABLE\", \"VIEW\", \"DICTIONARY\", \"FUNCTION\"):\n            with self.subTest(f\"Test DROP {creatable} ON CLUSTER\"):\n                self.validate_identity(f\"DROP {creatable} test ON CLUSTER test_cluster\")\n                self.validate_identity(f\"DROP {creatable} test ON CLUSTER '{{cluster}}'\")\n\n    def test_datetime_funcs(self):\n        # Each datetime func has an alias that is roundtripped to the original name e.g. (DATE_SUB, DATESUB) -> DATE_SUB\n        datetime_funcs = ((\"DATE_SUB\", \"DATESUB\"), (\"DATE_ADD\", \"DATEADD\"))\n\n        # 2-arg functions of type <func>(date, unit)\n        for func in (*datetime_funcs, (\"TIMESTAMP_ADD\", \"TIMESTAMPADD\")):\n            func_name = func[0]\n            for func_alias in func:\n                self.validate_identity(\n                    f\"\"\"SELECT {func_alias}(date, INTERVAL '3' YEAR)\"\"\",\n                    f\"\"\"SELECT {func_name}(date, INTERVAL '3' YEAR)\"\"\",\n                )\n\n        # 3-arg functions of type <func>(unit, value, date)\n        for func in (*datetime_funcs, (\"DATE_DIFF\", \"DATEDIFF\"), (\"TIMESTAMP_SUB\", \"TIMESTAMPSUB\")):\n            func_name = func[0]\n            for func_alias in func:\n                with self.subTest(f\"Test 3-arg date-time function {func_alias}\"):\n                    self.validate_identity(\n                        f\"SELECT {func_alias}(SECOND, 1, bar)\",\n                        f\"SELECT {func_name}(SECOND, 1, bar)\",\n                    )\n        # 4-arg functions of type <func>(unit, value, date, timezone)\n        for func in ((\"DATE_DIFF\", \"DATEDIFF\"),):\n            func_name = func[0]\n            for func_alias in func:\n                with self.subTest(f\"Test 4-arg date-time function {func_alias}\"):\n                    self.validate_identity(\n                        f\"SELECT {func_alias}(SECOND, 1, bar, 'UTC')\",\n                        f\"SELECT {func_name}(SECOND, 1, bar, 'UTC')\",\n                    )\n\n    def test_convert(self):\n        self.assertEqual(\n            convert(date(2020, 1, 1)).sql(dialect=self.dialect), \"toDate('2020-01-01')\"\n        )\n\n        # no fractional seconds\n        self.assertEqual(\n            convert(datetime(2020, 1, 1, 0, 0, 1)).sql(dialect=self.dialect),\n            \"CAST('2020-01-01 00:00:01' AS DateTime64(6))\",\n        )\n        self.assertEqual(\n            convert(datetime(2020, 1, 1, 0, 0, 1, tzinfo=timezone.utc)).sql(dialect=self.dialect),\n            \"CAST('2020-01-01 00:00:01' AS DateTime64(6, 'UTC'))\",\n        )\n\n        # with fractional seconds\n        self.assertEqual(\n            convert(datetime(2020, 1, 1, 0, 0, 1, 1)).sql(dialect=self.dialect),\n            \"CAST('2020-01-01 00:00:01.000001' AS DateTime64(6))\",\n        )\n        self.assertEqual(\n            convert(datetime(2020, 1, 1, 0, 0, 1, 1, tzinfo=timezone.utc)).sql(\n                dialect=self.dialect\n            ),\n            \"CAST('2020-01-01 00:00:01.000001' AS DateTime64(6, 'UTC'))\",\n        )\n\n    def test_timestr_to_time(self):\n        # no fractional seconds\n        time_strings = [\n            \"2020-01-01 00:00:01\",\n            \"2020-01-01 00:00:01+01:00\",\n            \" 2020-01-01 00:00:01-01:00 \",\n            \"2020-01-01T00:00:01+01:00\",\n        ]\n        for time_string in time_strings:\n            with self.subTest(f\"'{time_string}'\"):\n                self.assertEqual(\n                    exp.TimeStrToTime(this=exp.Literal.string(time_string)).sql(\n                        dialect=self.dialect\n                    ),\n                    f\"CAST('{time_string}' AS DateTime64(6))\",\n                )\n\n        time_strings_no_utc = [\"2020-01-01 00:00:01\" for i in range(4)]\n        for utc, no_utc in zip(time_strings, time_strings_no_utc):\n            with self.subTest(f\"'{time_string}' with UTC timezone\"):\n                self.assertEqual(\n                    exp.TimeStrToTime(\n                        this=exp.Literal.string(utc), zone=exp.Literal.string(\"UTC\")\n                    ).sql(dialect=self.dialect),\n                    f\"CAST('{no_utc}' AS DateTime64(6, 'UTC'))\",\n                )\n\n        # with fractional seconds\n        time_strings = [\n            \"2020-01-01 00:00:01.001\",\n            \"2020-01-01 00:00:01.000001\",\n            \"2020-01-01 00:00:01.001+00:00\",\n            \"2020-01-01 00:00:01.000001-00:00\",\n            \"2020-01-01 00:00:01.0001\",\n            \"2020-01-01 00:00:01.1+00:00\",\n        ]\n\n        for time_string in time_strings:\n            with self.subTest(f\"'{time_string}'\"):\n                self.assertEqual(\n                    exp.TimeStrToTime(this=exp.Literal.string(time_string[0])).sql(\n                        dialect=self.dialect\n                    ),\n                    f\"CAST('{time_string[0]}' AS DateTime64(6))\",\n                )\n\n        time_strings_no_utc = [\n            \"2020-01-01 00:00:01.001000\",\n            \"2020-01-01 00:00:01.000001\",\n            \"2020-01-01 00:00:01.001000\",\n            \"2020-01-01 00:00:01.000001\",\n            \"2020-01-01 00:00:01.000100\",\n            \"2020-01-01 00:00:01.100000\",\n        ]\n\n        for utc, no_utc in zip(time_strings, time_strings_no_utc):\n            with self.subTest(f\"'{time_string}' with UTC timezone\"):\n                self.assertEqual(\n                    exp.TimeStrToTime(\n                        this=exp.Literal.string(utc), zone=exp.Literal.string(\"UTC\")\n                    ).sql(dialect=self.dialect),\n                    f\"CAST('{no_utc}' AS DateTime64(6, 'UTC'))\",\n                )\n\n    def test_grant(self):\n        self.validate_identity(\"GRANT SELECT(x, y) ON db.table TO john WITH GRANT OPTION\")\n        self.validate_identity(\"GRANT INSERT(x, y) ON db.table TO john\")\n\n    def test_revoke(self):\n        self.validate_identity(\"REVOKE SELECT(x, y) ON db.table FROM john\")\n        self.validate_identity(\"REVOKE INSERT(x, y) ON db.table FROM john\")\n\n    def test_array_join(self):\n        expr = self.validate_identity(\n            \"SELECT * FROM arrays_test ARRAY JOIN arr1, arrays_test.arr2 AS foo, ['a', 'b', 'c'] AS elem\"\n        )\n        joins = expr.args[\"joins\"]\n        self.assertEqual(len(joins), 1)\n\n        join = joins[0]\n        self.assertEqual(join.kind, \"ARRAY\")\n        self.assertIsInstance(join.this, exp.Column)\n\n        self.assertEqual(len(join.expressions), 2)\n        self.assertIsInstance(join.expressions[0], exp.Alias)\n        self.assertIsInstance(join.expressions[0].this, exp.Column)\n\n        self.assertIsInstance(join.expressions[1], exp.Alias)\n        self.assertIsInstance(join.expressions[1].this, exp.Array)\n\n        self.validate_identity(\"SELECT s, arr FROM arrays_test ARRAY JOIN arr\")\n        self.validate_identity(\"SELECT s, arr, a FROM arrays_test LEFT ARRAY JOIN arr AS a\")\n        self.validate_identity(\n            \"SELECT s, arr_external FROM arrays_test ARRAY JOIN [1, 2, 3] AS arr_external\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM arrays_test ARRAY JOIN [1, 2, 3] AS arr_external1, ['a', 'b', 'c'] AS arr_external2, splitByString(',', 'asd,qwerty,zxc') AS arr_external3\"\n        )\n\n    def test_traverse_scope(self):\n        sql = \"SELECT * FROM t FINAL\"\n        scopes = traverse_scope(parse_one(sql, dialect=self.dialect))\n        self.assertEqual(len(scopes), 1)\n        self.assertEqual(set(scopes[0].sources), {\"t\"})\n\n    def test_window_functions(self):\n        self.validate_identity(\n            \"SELECT row_number(column1) OVER (PARTITION BY column2 ORDER BY column3) FROM table\"\n        )\n        self.validate_identity(\n            \"SELECT row_number() OVER (PARTITION BY column2 ORDER BY column3) FROM table\"\n        )\n\n    def test_functions(self):\n        self.validate_identity(\"SELECT TRANSFORM(foo, [1, 2], ['first', 'second']) FROM table\")\n        self.validate_identity(\n            \"SELECT TRANSFORM(foo, [1, 2], ['first', 'second'], 'default') FROM table\"\n        )\n\n    def test_array_offset(self):\n        with self.assertLogs(helper_logger) as cm:\n            self.validate_all(\n                \"SELECT col[1]\",\n                write={\n                    \"bigquery\": \"SELECT col[0]\",\n                    \"duckdb\": \"SELECT col[1]\",\n                    \"hive\": \"SELECT col[0]\",\n                    \"clickhouse\": \"SELECT col[1]\",\n                    \"presto\": \"SELECT col[1]\",\n                },\n            )\n\n            self.assertEqual(\n                cm.output,\n                [\n                    \"INFO:sqlglot:Applying array index offset (-1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                ],\n            )\n\n    def test_to_start_of(self):\n        for unit in (\"SECOND\", \"DAY\", \"MONTH\", \"YEAR\"):\n            self.validate_all(\n                f\"toStartOf{unit}(x)\",\n                write={\n                    \"clickhouse, version=23.8\": f\"dateTrunc('{unit.lower()}', x)\",\n                    \"clickhouse, version=24.1\": f\"dateTrunc('{unit}', x)\",\n                    \"databricks\": f\"DATE_TRUNC('{unit}', x)\",\n                    \"duckdb\": f\"DATE_TRUNC('{unit}', x)\",\n                    \"doris\": f\"DATE_TRUNC(x, '{unit}')\",\n                    \"presto\": f\"DATE_TRUNC('{unit}', x)\",\n                    \"spark\": f\"DATE_TRUNC('{unit}', x)\",\n                },\n            )\n\n        self.validate_all(\n            \"toMonday(x)\",\n            write={\n                \"clickhouse, version=23.8\": \"dateTrunc('week', x)\",\n                \"clickhouse, version=24.1\": \"dateTrunc('WEEK', x)\",\n                \"databricks\": \"DATE_TRUNC('WEEK', x)\",\n                \"duckdb\": \"DATE_TRUNC('WEEK', x)\",\n                \"doris\": \"DATE_TRUNC(x, 'WEEK')\",\n                \"presto\": \"DATE_TRUNC('WEEK', x)\",\n                \"spark\": \"DATE_TRUNC('WEEK', x)\",\n            },\n        )\n\n    def test_string_split(self):\n        self.validate_all(\n            \"splitByString('s', x)\",\n            read={\n                \"bigquery\": \"SPLIT(x, 's')\",\n                \"duckdb\": \"STRING_SPLIT(x, 's')\",\n            },\n            write={\n                \"clickhouse\": \"splitByString('s', x)\",\n                \"doris\": \"SPLIT_BY_STRING(x, 's')\",\n                \"duckdb\": \"STR_SPLIT(x, 's')\",\n                \"hive\": r\"SPLIT(x, CONCAT('\\\\Q', 's', '\\\\E'))\",\n            },\n        )\n        self.validate_all(\n            r\"splitByRegexp('\\\\d+', x)\",\n            read={\n                \"duckdb\": r\"STRING_SPLIT_REGEX(x, '\\d+')\",\n                \"hive\": r\"SPLIT(x, '\\\\d+')\",\n            },\n            write={\n                \"clickhouse\": r\"splitByRegexp('\\\\d+', x)\",\n                \"duckdb\": r\"STR_SPLIT_REGEX(x, '\\d+')\",\n                \"hive\": r\"SPLIT(x, '\\\\d+')\",\n            },\n        )\n        self.validate_identity(\"splitByChar('', x)\")\n\n    def test_sql_security(self):\n        stmts = [\n            \"CREATE VIEW v DEFINER='alice' SQL SECURITY DEFINER AS SELECT 1\",\n            \"CREATE VIEW v SQL SECURITY DEFINER DEFINER='alice' AS SELECT 1\",\n            \"CREATE VIEW v SQL SECURITY DEFINER DEFINER=CURRENT_USER AS SELECT 1\",\n            \"CREATE VIEW v SQL SECURITY INVOKER AS SELECT 1\",\n            \"CREATE VIEW v SQL SECURITY NONE AS SELECT 1\",\n            \"CREATE MATERIALIZED VIEW v TO t SQL SECURITY DEFINER DEFINER='alice' AS SELECT 1\",\n            \"CREATE MATERIALIZED VIEW v TO t SQL SECURITY INVOKER AS SELECT 1\",\n            \"CREATE MATERIALIZED VIEW v TO t SQL SECURITY NONE AS SELECT 1\",\n            \"ALTER TABLE v MODIFY SQL SECURITY DEFINER DEFINER='alice'\",\n            \"ALTER TABLE v MODIFY SQL SECURITY DEFINER DEFINER=CURRENT_USER\",\n        ]\n        for stmt in stmts:\n            with self.subTest(stmt):\n                self.validate_identity(stmt)\n"
  },
  {
    "path": "tests/dialects/test_databricks.py",
    "content": "from sqlglot import exp, transpile, parse_one\nfrom sqlglot.errors import ParseError\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestDatabricks(Validator):\n    dialect = \"databricks\"\n\n    def test_databricks(self):\n        self.validate_identity(\"SELECT COSH(1.5)\")\n        null_type = exp.DataType.build(\"VOID\", dialect=\"databricks\")\n        self.assertEqual(null_type.sql(), \"NULL\")\n        self.assertEqual(null_type.sql(\"databricks\"), \"VOID\")\n\n        self.validate_identity(\"DESCRIBE EXTENDED staging.onetrade_startb AS JSON\")\n        self.validate_identity(\"SELECT BITMAP_BIT_POSITION(10)\")\n        self.validate_identity(\"SELECT BITMAP_BUCKET_NUMBER(32769)\")\n        self.validate_identity(\"SELECT BITMAP_CONSTRUCT_AGG(value)\")\n        self.validate_identity(\"SELECT EXP(1)\")\n        self.validate_identity(\"SELECT MODE(category)\")\n        self.validate_identity(\"SELECT MODE(price, TRUE) AS deterministic_mode FROM products\")\n        self.validate_identity(\"REGEXP_LIKE(x, y)\")\n        self.validate_identity(\"SELECT CAST(NULL AS VOID)\")\n        self.validate_identity(\"SELECT void FROM t\")\n        self.validate_identity(\"SELECT * FROM stream\")\n        self.validate_identity(\"SELECT * FROM STREAM t\")\n        self.validate_identity(\"SELECT t.current_time FROM t\")\n        self.validate_identity(\"ALTER TABLE labels ADD COLUMN label_score FLOAT\")\n        self.validate_identity(\"DESCRIBE HISTORY a.b\")\n        self.validate_identity(\"DESCRIBE history.tbl\")\n        self.validate_identity(\"CREATE TABLE t (a STRUCT<c: MAP<STRING, STRING>>)\")\n        self.validate_identity(\"CREATE TABLE t (c STRUCT<interval: DOUBLE COMMENT 'aaa'>)\")\n        self.validate_identity(\"CREATE TABLE my_table TBLPROPERTIES (a.b=15)\")\n        self.validate_identity(\"CREATE TABLE my_table TBLPROPERTIES ('a.b'=15)\")\n        self.validate_identity(\"SELECT CAST('11 23:4:0' AS INTERVAL DAY TO HOUR)\")\n        self.validate_identity(\"SELECT CAST('11 23:4:0' AS INTERVAL DAY TO MINUTE)\")\n        self.validate_identity(\"SELECT CAST('11 23:4:0' AS INTERVAL DAY TO SECOND)\")\n        self.validate_identity(\"SELECT CAST('23:00:00' AS INTERVAL HOUR TO MINUTE)\")\n        self.validate_identity(\"SELECT CAST('23:00:00' AS INTERVAL HOUR TO SECOND)\")\n        self.validate_identity(\"SELECT CAST('23:00:00' AS INTERVAL MINUTE TO SECOND)\")\n        self.validate_identity(\"CREATE TABLE target SHALLOW CLONE source\")\n        self.validate_identity(\"INSERT INTO a REPLACE WHERE cond VALUES (1), (2)\")\n        self.validate_identity(\"CREATE FUNCTION a.b(x INT) RETURNS INT RETURN x + 1\")\n        self.validate_identity(\"CREATE FUNCTION a AS b\")\n        self.validate_identity(\"SELECT ${x} FROM ${y} WHERE ${z} > 1\")\n        self.validate_identity(\"CREATE TABLE foo (x DATE GENERATED ALWAYS AS (CAST(y AS DATE)))\")\n        self.validate_identity(\"TRUNCATE TABLE t1 PARTITION(age = 10, name = 'test', address)\")\n        self.validate_identity(\"SELECT PARSE_JSON('{}')\")\n        self.validate_identity(\"SELECT RANDSTR(123)\")\n        self.validate_identity(\"SELECT RANDSTR(123, 456)\")\n\n        self.validate_identity(\"PARSE_URL('https://example.com/path')\")\n        self.validate_identity(\"PARSE_URL('https://example.com/path', 'HOST')\")\n        self.validate_identity(\"PARSE_URL('https://example.com/path', 'QUERY', 'param')\")\n        self.validate_identity(\n            \"CREATE TABLE IF NOT EXISTS db.table (a TIMESTAMP, b BOOLEAN GENERATED ALWAYS AS (NOT a IS NULL)) USING DELTA\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM sales UNPIVOT INCLUDE NULLS (sales FOR quarter IN (q1 AS `Jan-Mar`))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM sales UNPIVOT EXCLUDE NULLS (sales FOR quarter IN (q1 AS `Jan-Mar`))\"\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION add_one(x INT) RETURNS INT LANGUAGE PYTHON AS $$def add_one(x):\\n  return x+1$$\"\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION add_one(x INT) RETURNS INT LANGUAGE PYTHON AS $FOO$def add_one(x):\\n  return x+1$FOO$\"\n        )\n        self.validate_identity(\n            \"TRUNCATE TABLE t1 PARTITION(age = 10, name = 'test', city LIKE 'LA')\"\n        )\n        self.validate_identity(\n            \"COPY INTO target FROM `s3://link` FILEFORMAT = AVRO VALIDATE = ALL FILES = ('file1', 'file2') FORMAT_OPTIONS ('opt1'='true', 'opt2'='test') COPY_OPTIONS ('mergeSchema'='true')\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1, t2\",\n            \"SELECT * FROM t1 CROSS JOIN t2\",\n        )\n        self.validate_identity(\n            \"SELECT TIMESTAMP '2025-04-29 18.47.18'::DATE\",\n            \"SELECT CAST(CAST('2025-04-29 18.47.18' AS DATE) AS TIMESTAMP)\",\n        )\n        self.validate_identity(\n            \"SELECT DATE_FORMAT(CAST(FROM_UTC_TIMESTAMP(foo, 'America/Los_Angeles') AS TIMESTAMP), 'yyyy-MM-dd HH:mm:ss') AS foo FROM t\",\n            \"SELECT DATE_FORMAT(CAST(FROM_UTC_TIMESTAMP(CAST(foo AS TIMESTAMP), 'America/Los_Angeles') AS TIMESTAMP), 'yyyy-MM-dd HH:mm:ss') AS foo FROM t\",\n        )\n        self.validate_identity(\n            \"DATE_DIFF(day, created_at, current_date())\",\n            \"DATEDIFF(DAY, created_at, CURRENT_DATE)\",\n        ).args[\"unit\"].assert_is(exp.Var)\n        self.validate_identity(\n            r'SELECT r\"\\\\foo.bar\\\"',\n            r\"SELECT '\\\\\\\\foo.bar\\\\'\",\n        )\n        self.validate_identity(\n            \"FROM_UTC_TIMESTAMP(x::TIMESTAMP, tz)\",\n            \"FROM_UTC_TIMESTAMP(CAST(x AS TIMESTAMP), tz)\",\n        )\n\n        self.validate_identity(\"SELECT SUBSTRING_INDEX(str, delim, count)\")\n        self.validate_identity(\"BITMAP_OR_AGG(x)\")\n\n        self.validate_all(\n            \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n            write={\n                \"databricks\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n                \"spark\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n                \"mysql\": \"SELECT SUBSTRING_INDEX('a.b.c.d', '.', 2)\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT SUBSTR('Spark' FROM 5 FOR 1)\", \"SELECT SUBSTRING('Spark', 5, 1)\"\n        )\n        self.validate_identity(\"SELECT SUBSTR('Spark SQL', 5)\", \"SELECT SUBSTRING('Spark SQL', 5)\")\n        self.validate_identity(\n            \"SELECT SUBSTR(ENCODE('Spark SQL', 'utf-8'), 5)\",\n            \"SELECT SUBSTRING(ENCODE('Spark SQL', 'utf-8'), 5)\",\n        )\n        self.validate_all(\n            \"SELECT TYPEOF(1)\",\n            read={\n                \"databricks\": \"SELECT TYPEOF(1)\",\n                \"snowflake\": \"SELECT TYPEOF(1)\",\n                \"hive\": \"SELECT TYPEOF(1)\",\n                \"clickhouse\": \"SELECT toTypeName(1)\",\n            },\n            write={\n                \"clickhouse\": \"SELECT toTypeName(1)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT c1:item[1].price\",\n            read={\n                \"spark\": \"SELECT GET_JSON_OBJECT(c1, '$.item[1].price')\",\n            },\n            write={\n                \"databricks\": \"SELECT c1:item[1].price\",\n                \"spark\": \"SELECT GET_JSON_OBJECT(c1, '$.item[1].price')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT GET_JSON_OBJECT(c1, '$.item[1].price')\",\n            write={\n                \"databricks\": \"SELECT c1:item[1].price\",\n                \"spark\": \"SELECT GET_JSON_OBJECT(c1, '$.item[1].price')\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE foo (x INT GENERATED ALWAYS AS (YEAR(y)))\",\n            write={\n                \"databricks\": \"CREATE TABLE foo (x INT GENERATED ALWAYS AS (YEAR(y)))\",\n                \"tsql\": \"CREATE TABLE foo (x AS YEAR(CAST(y AS DATE)))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE t1 AS (SELECT c FROM t2)\",\n            read={\n                \"teradata\": \"CREATE TABLE t1 AS (SELECT c FROM t2) WITH DATA\",\n            },\n        )\n        self.validate_all(\n            \"SELECT X'1A2B'\",\n            read={\n                \"spark2\": \"SELECT X'1A2B'\",\n                \"spark\": \"SELECT X'1A2B'\",\n                \"databricks\": \"SELECT x'1A2B'\",\n            },\n            write={\n                \"spark2\": \"SELECT X'1A2B'\",\n                \"spark\": \"SELECT X'1A2B'\",\n                \"databricks\": \"SELECT X'1A2B'\",\n            },\n        )\n\n        with self.assertRaises(ParseError):\n            transpile(\n                \"CREATE FUNCTION add_one(x INT) RETURNS INT LANGUAGE PYTHON AS $foo$def add_one(x):\\n  return x+1$$\",\n                read=\"databricks\",\n            )\n\n        with self.assertRaises(ParseError):\n            transpile(\n                \"CREATE FUNCTION add_one(x INT) RETURNS INT LANGUAGE PYTHON AS $foo bar$def add_one(x):\\n  return x+1$foo bar$\",\n                read=\"databricks\",\n            )\n\n        self.validate_all(\n            \"CREATE OR REPLACE FUNCTION func(a BIGINT, b BIGINT) RETURNS TABLE (a INT) RETURN SELECT a\",\n            write={\n                \"databricks\": \"CREATE OR REPLACE FUNCTION func(a BIGINT, b BIGINT) RETURNS TABLE (a INT) RETURN SELECT a\",\n                \"duckdb\": \"CREATE OR REPLACE FUNCTION func(a, b) AS TABLE SELECT a\",\n            },\n        )\n\n        self.validate_all(\n            \"CREATE OR REPLACE FUNCTION func(a BIGINT, b BIGINT) RETURNS BIGINT RETURN a\",\n            write={\n                \"databricks\": \"CREATE OR REPLACE FUNCTION func(a BIGINT, b BIGINT) RETURNS BIGINT RETURN a\",\n                \"duckdb\": \"CREATE OR REPLACE FUNCTION func(a, b) AS a\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ANY(col) FROM VALUES (TRUE), (FALSE) AS tab(col)\",\n            read={\n                \"databricks\": \"SELECT ANY(col) FROM VALUES (TRUE), (FALSE) AS tab(col)\",\n                \"spark\": \"SELECT ANY(col) FROM VALUES (TRUE), (FALSE) AS tab(col)\",\n            },\n            write={\n                \"spark\": \"SELECT ANY(col) FROM VALUES (TRUE), (FALSE) AS tab(col)\",\n            },\n        )\n\n        for option in (\"\", \" (foo)\", \" MATCH FULL\", \" NOT ENFORCED\"):\n            with self.subTest(f\"Databricks foreign key REFERENCES option: {option}.\"):\n                self.validate_identity(\n                    f\"CREATE TABLE t1 (foo BIGINT NOT NULL CONSTRAINT foo_c FOREIGN KEY REFERENCES t2{option})\"\n                )\n        self.validate_identity(\n            \"SELECT test, LISTAGG(email, '') AS Email FROM organizations GROUP BY test\",\n        )\n\n        self.validate_identity(\n            \"WITH t AS (VALUES ('foo_val') AS t(foo1)) SELECT foo1 FROM t\",\n            \"WITH t AS (SELECT * FROM VALUES ('foo_val') AS t(foo1)) SELECT foo1 FROM t\",\n        )\n        self.validate_identity(\"NTILE() OVER (ORDER BY 1)\")\n        self.validate_identity(\"CURRENT_VERSION()\")\n        self.validate_all(\n            \"UNIFORM(1, 10, 5)\",\n            write={\n                \"snowflake\": \"UNIFORM(1, 10, RANDOM(5))\",\n                \"databricks\": \"UNIFORM(1, 10, 5)\",\n            },\n        )\n        self.validate_all(\n            \"UNIFORM(1, 10)\",\n            write={\n                \"databricks\": \"UNIFORM(1, 10)\",\n                \"snowflake\": \"UNIFORM(1, 10, RANDOM())\",\n            },\n        )\n        self.validate_identity(\"SELECT ELT(2, 'foo', 'bar', 'baz') AS Result\")\n        self.validate_identity(\"GETDATE()\", \"CURRENT_TIMESTAMP()\")\n        self.validate_identity(\"NOW()\", \"CURRENT_TIMESTAMP()\")\n        self.validate_identity(\"CURRENT_TIMEZONE()\")\n        self.validate_identity(\"CURDATE()\", \"CURRENT_DATE\")\n        self.validate_identity(\"CURDATE\", \"CURRENT_DATE\")\n        self.validate_identity(\"SELECT MAKE_INTERVAL(100, 11, 12, 13, 14, 14, 15)\")\n        self.validate_identity(\"SELECT name, GROUPING_ID() FROM customer GROUP BY ROLLUP (name)\")\n        self.validate_identity(\"BIT_GET(11, 0)\", \"GETBIT(11, 0)\")\n        self.validate_identity(\"SELECT CURDATE()\", \"SELECT CURRENT_DATE\")\n        self.validate_identity(\n            \"CREATE TABLE tbl (id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1 INCREMENT BY 1))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE tbl (id BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1))\"\n        )\n\n        self.validate_identity(\n            \"\"\"WITH t AS (SELECT '{\"x-y\": \"z\"}' AS c) SELECT get_json_object(c, '$.x-y') FROM t\"\"\",\n            \"\"\"WITH t AS (SELECT '{\"x-y\": \"z\"}' AS c) SELECT c:[\"x-y\"] FROM t\"\"\",\n        ).selects[0].expression.assert_is(exp.JSONPath)\n\n    # https://docs.databricks.com/sql/language-manual/functions/colonsign.html\n    def test_json(self):\n        self.validate_identity(\"SELECT c1:price, c1:price.foo, c1:price.bar[1]\")\n        self.validate_identity(\"SELECT TRY_CAST(c1:price AS ARRAY<VARIANT>)\")\n        self.validate_identity(\"\"\"SELECT TRY_CAST(c1:[\"foo bar\"][\"baz qux\"] AS ARRAY<VARIANT>)\"\"\")\n        self.validate_identity(\n            \"\"\"SELECT c1:item[1].price FROM VALUES ('{ \"item\": [ { \"model\" : \"basic\", \"price\" : 6.12 }, { \"model\" : \"medium\", \"price\" : 9.24 } ] }') AS T(c1)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT c1:item[*].price FROM VALUES ('{ \"item\": [ { \"model\" : \"basic\", \"price\" : 6.12 }, { \"model\" : \"medium\", \"price\" : 9.24 } ] }') AS T(c1)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT FROM_JSON(c1:item[*].price, 'ARRAY<DOUBLE>')[0] FROM VALUES ('{ \"item\": [ { \"model\" : \"basic\", \"price\" : 6.12 }, { \"model\" : \"medium\", \"price\" : 9.24 } ] }') AS T(c1)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT INLINE(FROM_JSON(c1:item[*], 'ARRAY<STRUCT<model STRING, price DOUBLE>>')) FROM VALUES ('{ \"item\": [ { \"model\" : \"basic\", \"price\" : 6.12 }, { \"model\" : \"medium\", \"price\" : 9.24 } ] }') AS T(c1)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT c1:['price'] FROM VALUES ('{ \"price\": 5 }') AS T(c1)\"\"\",\n            \"\"\"SELECT c1:price FROM VALUES ('{ \"price\": 5 }') AS T(c1)\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT GET_JSON_OBJECT(c1, '$.price') FROM VALUES ('{ \"price\": 5 }') AS T(c1)\"\"\",\n            \"\"\"SELECT c1:price FROM VALUES ('{ \"price\": 5 }') AS T(c1)\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT raw:`zip code`, raw:`fb:testid`, raw:store['bicycle'], raw:store[\"zip code\"]\"\"\",\n            \"\"\"SELECT raw:[\"zip code\"], raw:[\"fb:testid\"], raw:store.bicycle, raw:store[\"zip code\"]\"\"\",\n        )\n        self.validate_all(\n            \"SELECT col:`fr'uit`\",\n            write={\n                \"databricks\": \"\"\"SELECT col:[\"fr'uit\"]\"\"\",\n                \"postgres\": \"SELECT JSON_EXTRACT_PATH(col, 'fr''uit')\",\n            },\n        )\n\n    def test_datediff(self):\n        self.validate_all(\n            \"SELECT DATEDIFF(year, 'start', 'end')\",\n            write={\n                \"tsql\": \"SELECT DATEDIFF(YEAR, 'start', 'end')\",\n                \"databricks\": \"SELECT DATEDIFF(YEAR, 'start', 'end')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(microsecond, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(MICROSECOND, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(epoch FROM CAST('end' AS TIMESTAMP) - CAST('start' AS TIMESTAMP)) * 1000000 AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(millisecond, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(MILLISECOND, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(epoch FROM CAST('end' AS TIMESTAMP) - CAST('start' AS TIMESTAMP)) * 1000 AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(second, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(SECOND, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(epoch FROM CAST('end' AS TIMESTAMP) - CAST('start' AS TIMESTAMP)) AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(minute, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(MINUTE, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(epoch FROM CAST('end' AS TIMESTAMP) - CAST('start' AS TIMESTAMP)) / 60 AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(hour, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(HOUR, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(epoch FROM CAST('end' AS TIMESTAMP) - CAST('start' AS TIMESTAMP)) / 3600 AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(day, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(DAY, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(epoch FROM CAST('end' AS TIMESTAMP) - CAST('start' AS TIMESTAMP)) / 86400 AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(week, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(WEEK, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(days FROM (CAST('end' AS TIMESTAMP) - CAST('start' AS TIMESTAMP))) / 7 AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(month, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(MONTH, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(year FROM AGE(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP))) * 12 + EXTRACT(month FROM AGE(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP))) AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(quarter, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(QUARTER, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(year FROM AGE(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP))) * 4 + EXTRACT(month FROM AGE(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP))) / 3 AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(year, 'start', 'end')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(YEAR, 'start', 'end')\",\n                \"postgres\": \"SELECT CAST(EXTRACT(year FROM AGE(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP))) AS BIGINT)\",\n            },\n        )\n\n    def test_add_date(self):\n        self.validate_all(\n            \"SELECT DATEADD(year, 1, '2020-01-01')\",\n            write={\n                \"tsql\": \"SELECT DATEADD(YEAR, 1, '2020-01-01')\",\n                \"databricks\": \"SELECT DATEADD(YEAR, 1, '2020-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF('end', 'start')\",\n            write={\"databricks\": \"SELECT DATEDIFF(DAY, 'start', 'end')\"},\n        )\n        self.validate_all(\n            \"SELECT DATE_ADD('2020-01-01', 1)\",\n            write={\n                \"tsql\": \"SELECT DATEADD(DAY, 1, '2020-01-01')\",\n                \"databricks\": \"SELECT DATEADD(DAY, 1, '2020-01-01')\",\n            },\n        )\n\n    def test_without_as(self):\n        self.validate_all(\n            \"CREATE TABLE x (SELECT 1)\",\n            write={\n                \"databricks\": \"CREATE TABLE x AS (SELECT 1)\",\n            },\n        )\n\n        self.validate_all(\n            \"WITH x (select 1) SELECT * FROM x\",\n            write={\n                \"databricks\": \"WITH x AS (SELECT 1) SELECT * FROM x\",\n            },\n        )\n\n    def test_streaming_tables(self):\n        self.validate_identity(\n            \"CREATE STREAMING TABLE raw_data AS SELECT * FROM STREAM READ_FILES('abfss://container@storageAccount.dfs.core.windows.net/base/path')\"\n        )\n        self.validate_identity(\n            \"CREATE OR REFRESH STREAMING TABLE csv_data (id INT, ts TIMESTAMP, event STRING) AS SELECT * FROM STREAM READ_FILES('s3://bucket/path', format => 'csv', schema => 'id int, ts timestamp, event string')\"\n        )\n\n    def test_grant(self):\n        self.validate_identity(\"GRANT CREATE ON SCHEMA my_schema TO `alf@melmak.et`\")\n        self.validate_identity(\"GRANT SELECT ON TABLE sample_data TO `alf@melmak.et`\")\n        self.validate_identity(\"GRANT ALL PRIVILEGES ON TABLE forecasts TO finance\")\n        self.validate_identity(\"GRANT SELECT ON TABLE t TO `fab9e00e-ca35-11ec-9d64-0242ac120002`\")\n\n    def test_revoke(self):\n        self.validate_identity(\"REVOKE CREATE ON SCHEMA my_schema FROM `alf@melmak.et`\")\n        self.validate_identity(\"REVOKE SELECT ON TABLE sample_data FROM `alf@melmak.et`\")\n        self.validate_identity(\"REVOKE ALL PRIVILEGES ON TABLE forecasts FROM finance\")\n        self.validate_identity(\n            \"REVOKE SELECT ON TABLE t FROM `fab9e00e-ca35-11ec-9d64-0242ac120002`\"\n        )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE TABLE tbl COMPUTE DELTA STATISTICS NOSCAN\")\n        self.validate_identity(\"ANALYZE TABLE tbl COMPUTE DELTA STATISTICS FOR ALL COLUMNS\")\n        self.validate_identity(\"ANALYZE TABLE tbl COMPUTE DELTA STATISTICS FOR COLUMNS foo, bar\")\n        self.validate_identity(\"ANALYZE TABLE ctlg.db.tbl COMPUTE DELTA STATISTICS NOSCAN\")\n        self.validate_identity(\"ANALYZE TABLES COMPUTE STATISTICS NOSCAN\")\n        self.validate_identity(\"ANALYZE TABLES FROM db COMPUTE STATISTICS\")\n        self.validate_identity(\"ANALYZE TABLES IN db COMPUTE STATISTICS\")\n        self.validate_identity(\n            \"ANALYZE TABLE ctlg.db.tbl PARTITION(foo = 'foo', bar = 'bar') COMPUTE STATISTICS NOSCAN\"\n        )\n\n    def test_udf_environment_property(self):\n        self.validate_identity(\n            \"\"\"CREATE FUNCTION a() ENVIRONMENT (dependencies = '[\"foo1==1\", \"foo2==2\"]', environment_version = 'None')\"\"\"\n        )\n\n    def test_udf_handler_property(self):\n        self.validate_identity(\"\"\"CREATE FUNCTION a() HANDLER 'handler_function'\"\"\")\n\n    def test_udf_parameter_style_property(self):\n        self.validate_identity(\"\"\"CREATE FUNCTION a() PARAMETER STYLE PANDAS\"\"\")\n\n    def test_to_char_is_numeric_transpile_to_cast(self):\n        # The input SQL simulates a TO_CHAR with is_numeric flag set (from dremio dialect)\n        sql = \"SELECT TO_CHAR(12345, '#')\"\n        expression = parse_one(sql, read=\"dremio\")\n\n        to_char_exp = expression.find(exp.ToChar)\n        assert to_char_exp is not None\n        assert to_char_exp.args.get(\"is_numeric\") is True\n\n        result = transpile(sql, read=\"dremio\", write=\"databricks\")[0]\n        assert \"CAST(12345 AS STRING)\" in result\n\n    def test_qdcolon(self):\n        self.validate_identity(\"SELECT '20'?::INTEGER\", \"SELECT TRY_CAST('20' AS INT)\")\n\n    def test_overlay(self):\n        self.validate_identity(\n            \"SELECT OVERLAY('Spark SQL', 'ANSI ', 7, 0)\",\n            \"SELECT OVERLAY('Spark SQL' PLACING 'ANSI ' FROM 7 FOR 0)\",\n        )\n        self.validate_identity(\n            \"SELECT OVERLAY('Spark SQL' PLACING 'CORE' FROM 7)\",\n        )\n        self.validate_identity(\n            \"SELECT OVERLAY(ENCODE('Spark SQL', 'utf-8') PLACING ENCODE('_', 'utf-8') FROM 6)\",\n        )\n        self.validate_identity(\n            \"SELECT OVERLAY('Spark SQL' PLACING 'ANSI ' FROM 7 FOR 0)\",\n        )\n\n    def test_declare(self):\n        self.validate_identity(\"DECLARE VAR x INT\", \"DECLARE x INT\")\n        self.validate_identity(\"DECLARE x INT\")\n        self.validate_identity(\"DECLARE VARIABLE myvar INT DEFAULT 1\", \"DECLARE myvar INT = 1\")\n        self.validate_identity(\"DECLARE x, y, z INT DEFAULT 1\", \"DECLARE x, y, z INT = 1\")\n        self.validate_identity(\"DECLARE x INT = 1\")\n        self.validate_identity(\"DECLARE OR REPLACE x INT = 1\")\n"
  },
  {
    "path": "tests/dialects/test_dialect.py",
    "content": "import typing as t\nimport unittest\n\n\nfrom sqlglot import (\n    Dialect,\n    Dialects,\n    ErrorLevel,\n    ParseError,\n    TokenError,\n    UnsupportedError,\n    exp,\n    parse_one,\n)\nfrom sqlglot.dialects import BigQuery, Hive, Snowflake, Spark2\nfrom sqlglot.dialects.duckdb import WS_CONTROL_CHARS_TO_DUCK\nfrom sqlglot.generator import logger as generator_logger\nfrom sqlglot.parser import logger as parser_logger\nfrom sqlglot.parsers.snowflake import SnowflakeParser\nfrom collections.abc import Iterable\nimport sqlglot.parsers.base as _base_module\n\n_PARSER_IS_COMPILED = getattr(_base_module, \"__file__\", \"\").endswith(\".so\")\n\n\nclass Validator(unittest.TestCase):\n    dialect = None\n\n    def parse_one(self, sql, **kwargs):\n        return parse_one(sql, read=self.dialect, **kwargs)\n\n    def assert_duckdb_sql(\n        self,\n        expression: exp.Expr,\n        *,\n        includes: t.Optional[Iterable[str]] = None,\n        excludes: t.Optional[Iterable[str]] = None,\n        chr_chars: t.Optional[Iterable[str]] = None,\n    ) -> str:\n        duckdb_sql = expression.sql(\"duckdb\")\n\n        for fragment in includes or ():\n            self.assertIn(fragment, duckdb_sql)\n        for fragment in excludes or ():\n            self.assertNotIn(fragment, duckdb_sql)\n        for char in chr_chars or ():\n            code = WS_CONTROL_CHARS_TO_DUCK.get(char)\n            self.assertIsNotNone(code, f\"missing DuckDB code for {repr(char)}\")\n            self.assertIn(f\"CHR({code})\", duckdb_sql)\n\n        return duckdb_sql\n\n    def validate_identity(\n        self, sql, write_sql=None, pretty=False, check_command_warning=False, identify=False\n    ):\n        if check_command_warning:\n            with self.assertLogs(parser_logger) as cm:\n                expression = self.parse_one(sql)\n                assert f\"'{sql[:100]}' contains unsupported syntax\" in cm.output[0]\n        else:\n            expression = self.parse_one(sql)\n\n        self.assertEqual(\n            write_sql or sql, expression.sql(dialect=self.dialect, pretty=pretty, identify=identify)\n        )\n        return expression\n\n    def validate_all(self, sql, read=None, write=None, pretty=False, identify=False):\n        \"\"\"\n        Validate that:\n        1. Everything in `read` transpiles to `sql`\n        2. `sql` transpiles to everything in `write`\n\n        Args:\n            sql (str): Main SQL expression\n            read (dict): Mapping of dialect -> SQL\n            write (dict): Mapping of dialect -> SQL\n            pretty (bool): prettify both read and write\n            identify (bool): quote identifiers in both read and write\n        \"\"\"\n        expression = self.parse_one(sql)\n\n        for read_dialect, read_sql in (read or {}).items():\n            with self.subTest(f\"{read_dialect} -> {sql}\"):\n                self.assertEqual(\n                    parse_one(read_sql, read_dialect).sql(\n                        self.dialect,\n                        unsupported_level=ErrorLevel.IGNORE,\n                        pretty=pretty,\n                        identify=identify,\n                    ),\n                    sql,\n                )\n\n        for write_dialect, write_sql in (write or {}).items():\n            with self.subTest(f\"{sql} -> {write_dialect}\"):\n                if write_sql is UnsupportedError:\n                    with self.assertRaises(UnsupportedError):\n                        expression.sql(write_dialect, unsupported_level=ErrorLevel.RAISE)\n                else:\n                    self.assertEqual(\n                        expression.sql(\n                            write_dialect,\n                            unsupported_level=ErrorLevel.IGNORE,\n                            pretty=pretty,\n                            identify=identify,\n                        ),\n                        write_sql,\n                    )\n\n\nclass TestDialect(Validator):\n    maxDiff = None\n\n    def test_enum(self):\n        dialect_by_key = Dialect.classes\n        for dialect in Dialects:\n            self.assertIsNotNone(Dialect[dialect])\n            self.assertIsNotNone(Dialect.get(dialect))\n            self.assertIsNotNone(Dialect.get_or_raise(dialect))\n            self.assertIsNotNone(Dialect[dialect.value])\n            self.assertIn(dialect, dialect_by_key)\n\n    def test_lazy_load(self):\n        import subprocess\n\n        code = \"import sqlglot; assert len(sqlglot.Dialect._classes) == 1; print('Success')\"\n        result = subprocess.run([\"python\", \"-c\", code], capture_output=True, text=True)\n        assert \"Success\" in result.stdout\n\n    def test_get_or_raise(self):\n        self.assertIsInstance(Dialect.get_or_raise(Hive), Hive)\n        self.assertIsInstance(Dialect.get_or_raise(Hive()), Hive)\n        self.assertIsInstance(Dialect.get_or_raise(\"hive\"), Hive)\n\n        with self.assertRaises(ValueError):\n            Dialect.get_or_raise(1)\n\n        default_mysql = Dialect.get_or_raise(\"mysql\")\n        self.assertEqual(default_mysql.normalization_strategy, \"CASE_SENSITIVE\")\n\n        lowercase_mysql = Dialect.get_or_raise(\"mysql,normalization_strategy=lowercase\")\n        self.assertEqual(lowercase_mysql.normalization_strategy, \"LOWERCASE\")\n\n        lowercase_mysql = Dialect.get_or_raise(\"mysql, normalization_strategy = lowercase\")\n        self.assertEqual(lowercase_mysql.normalization_strategy.value, \"LOWERCASE\")\n\n        with self.assertRaises(AttributeError) as cm:\n            Dialect.get_or_raise(\"mysql, normalization_strategy\")\n\n        self.assertEqual(str(cm.exception), \"'bool' object has no attribute 'upper'\")\n\n        with self.assertRaises(ValueError) as cm:\n            Dialect.get_or_raise(\"myqsl\")\n\n        self.assertEqual(str(cm.exception), \"Unknown dialect 'myqsl'. Did you mean mysql?\")\n\n        with self.assertRaises(ValueError) as cm:\n            Dialect.get_or_raise(\"asdfjasodiufjsd\")\n\n        self.assertEqual(str(cm.exception), \"Unknown dialect 'asdfjasodiufjsd'.\")\n\n        oracle_with_settings = Dialect.get_or_raise(\n            \"oracle, normalization_strategy = lowercase, version = 19.5\"\n        )\n        self.assertEqual(oracle_with_settings.normalization_strategy.value, \"LOWERCASE\")\n        self.assertEqual(oracle_with_settings.version, (19, 5, 0))\n\n        class MyDialect(Dialect):\n            SUPPORTED_SETTINGS = {\"s1\", \"s2\", \"s3\", \"s4\", \"s5\"}\n\n        bool_settings = Dialect.get_or_raise(\"mydialect, s1=TruE, s2=1, s3=FaLse, s4=0, s5=nonbool\")\n        self.assertEqual(\n            bool_settings.settings,\n            {\"s1\": True, \"s2\": True, \"s3\": False, \"s4\": False, \"s5\": \"nonbool\"},\n        )\n\n        with self.assertRaises(ValueError) as cm:\n            Dialect.get_or_raise(\"tsql,normalisation_strategy=case_sensitive\")\n\n        self.assertEqual(\n            \"Unknown setting 'normalisation_strategy'. Did you mean normalization_strategy?\",\n            str(cm.exception),\n        )\n\n    def test_compare_dialects(self):\n        bigquery_class = Dialect[\"bigquery\"]\n        bigquery_object = BigQuery()\n        bigquery_string = \"bigquery\"\n\n        snowflake_class = Dialect[\"snowflake\"]\n        snowflake_object = Snowflake()\n        snowflake_string = \"snowflake\"\n\n        self.assertEqual(snowflake_class, snowflake_class)\n        self.assertEqual(snowflake_class, snowflake_object)\n        self.assertEqual(snowflake_class, snowflake_string)\n        self.assertEqual(snowflake_object, snowflake_object)\n        self.assertEqual(snowflake_object, snowflake_string)\n\n        self.assertNotEqual(snowflake_class, bigquery_class)\n        self.assertNotEqual(snowflake_class, bigquery_object)\n        self.assertNotEqual(snowflake_class, bigquery_string)\n        self.assertNotEqual(snowflake_object, bigquery_object)\n        self.assertNotEqual(snowflake_object, bigquery_string)\n\n        self.assertTrue(snowflake_class in {\"snowflake\", \"bigquery\"})\n        self.assertTrue(snowflake_object in {\"snowflake\", \"bigquery\"})\n        self.assertFalse(snowflake_class in {\"bigquery\", \"redshift\"})\n        self.assertFalse(snowflake_object in {\"bigquery\", \"redshift\"})\n\n    def test_compare_dialect_versions(self):\n        ddb_v1 = Dialect.get_or_raise(\"duckdb, version=1.0\")\n        ddb_v1_2 = Dialect.get_or_raise(\n            \"duckdb, normalization_strategy=case_sensitive, version=1.0\"\n        )\n        ddb_v2 = Dialect.get_or_raise(\"duckdb, version=2.2.4\")\n        ddb_latest = Dialect.get_or_raise(\"duckdb\")\n\n        self.assertTrue(ddb_latest.version > ddb_v2.version)\n        self.assertTrue(ddb_v1.version < ddb_v2.version)\n\n        self.assertTrue(ddb_v1.version == ddb_v1_2.version)\n        self.assertTrue(ddb_latest.version == Dialect.get_or_raise(\"duckdb\").version)\n\n    def test_cast(self):\n        self.validate_all(\n            \"CAST(a AS TEXT)\",\n            write={\n                \"bigquery\": \"CAST(a AS STRING)\",\n                \"clickhouse\": \"CAST(a AS Nullable(String))\",\n                \"drill\": \"CAST(a AS VARCHAR)\",\n                \"duckdb\": \"CAST(a AS TEXT)\",\n                \"materialize\": \"CAST(a AS TEXT)\",\n                \"mysql\": \"CAST(a AS CHAR)\",\n                \"hive\": \"CAST(a AS STRING)\",\n                \"oracle\": \"CAST(a AS CLOB)\",\n                \"postgres\": \"CAST(a AS TEXT)\",\n                \"presto\": \"CAST(a AS VARCHAR)\",\n                \"redshift\": \"CAST(a AS VARCHAR(MAX))\",\n                \"snowflake\": \"CAST(a AS VARCHAR)\",\n                \"spark\": \"CAST(a AS STRING)\",\n                \"starrocks\": \"CAST(a AS STRING)\",\n                \"tsql\": \"CAST(a AS VARCHAR(MAX))\",\n                \"doris\": \"CAST(a AS STRING)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS BINARY(4))\",\n            write={\n                \"bigquery\": \"CAST(a AS BYTES)\",\n                \"clickhouse\": \"CAST(a AS Nullable(BINARY(4)))\",\n                \"drill\": \"CAST(a AS VARBINARY(4))\",\n                \"duckdb\": \"CAST(a AS BLOB(4))\",\n                \"materialize\": \"CAST(a AS BYTEA(4))\",\n                \"mysql\": \"CAST(a AS BINARY(4))\",\n                \"hive\": \"CAST(a AS BINARY(4))\",\n                \"oracle\": \"CAST(a AS BLOB(4))\",\n                \"postgres\": \"CAST(a AS BYTEA(4))\",\n                \"presto\": \"CAST(a AS VARBINARY(4))\",\n                \"redshift\": \"CAST(a AS VARBYTE(4))\",\n                \"snowflake\": \"CAST(a AS BINARY(4))\",\n                \"sqlite\": \"CAST(a AS BLOB(4))\",\n                \"spark\": \"CAST(a AS BINARY(4))\",\n                \"starrocks\": \"CAST(a AS BINARY(4))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS VARBINARY(4))\",\n            write={\n                \"bigquery\": \"CAST(a AS BYTES)\",\n                \"clickhouse\": \"CAST(a AS Nullable(String))\",\n                \"duckdb\": \"CAST(a AS BLOB(4))\",\n                \"materialize\": \"CAST(a AS BYTEA(4))\",\n                \"mysql\": \"CAST(a AS VARBINARY(4))\",\n                \"hive\": \"CAST(a AS BINARY(4))\",\n                \"oracle\": \"CAST(a AS BLOB(4))\",\n                \"postgres\": \"CAST(a AS BYTEA(4))\",\n                \"presto\": \"CAST(a AS VARBINARY(4))\",\n                \"redshift\": \"CAST(a AS VARBYTE(4))\",\n                \"snowflake\": \"CAST(a AS VARBINARY(4))\",\n                \"sqlite\": \"CAST(a AS BLOB(4))\",\n                \"spark\": \"CAST(a AS BINARY(4))\",\n                \"starrocks\": \"CAST(a AS VARBINARY(4))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(MAP('a', '1') AS MAP(TEXT, TEXT))\",\n            write={\n                \"clickhouse\": \"CAST(map('a', '1') AS Map(String, Nullable(String)))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(ARRAY(1, 2) AS ARRAY<TINYINT>)\",\n            write={\n                \"clickhouse\": \"CAST([1, 2] AS Array(Nullable(Int8)))\",\n            },\n        )\n        self.validate_all(\n            \"CAST((1, 2, 3, 4) AS STRUCT<a: TINYINT, b: SMALLINT, c: INT, d: BIGINT>)\",\n            write={\n                \"clickhouse\": \"CAST((1, 2, 3, 4) AS Tuple(a Nullable(Int8), b Nullable(Int16), c Nullable(Int32), d Nullable(Int64)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_DISTINCT(x)\",\n            write={\n                \"clickhouse\": \"SELECT arrayDistinct(x)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS DATETIME)\",\n            write={\n                \"postgres\": \"CAST(a AS TIMESTAMP)\",\n                \"sqlite\": \"CAST(a AS DATETIME)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS STRING)\",\n            write={\n                \"bigquery\": \"CAST(a AS STRING)\",\n                \"drill\": \"CAST(a AS VARCHAR)\",\n                \"duckdb\": \"CAST(a AS TEXT)\",\n                \"materialize\": \"CAST(a AS TEXT)\",\n                \"mysql\": \"CAST(a AS CHAR)\",\n                \"hive\": \"CAST(a AS STRING)\",\n                \"oracle\": \"CAST(a AS CLOB)\",\n                \"postgres\": \"CAST(a AS TEXT)\",\n                \"presto\": \"CAST(a AS VARCHAR)\",\n                \"redshift\": \"CAST(a AS VARCHAR(MAX))\",\n                \"snowflake\": \"CAST(a AS VARCHAR)\",\n                \"spark\": \"CAST(a AS STRING)\",\n                \"starrocks\": \"CAST(a AS STRING)\",\n                \"tsql\": \"CAST(a AS VARCHAR(MAX))\",\n                \"doris\": \"CAST(a AS STRING)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS VARCHAR)\",\n            write={\n                \"bigquery\": \"CAST(a AS STRING)\",\n                \"drill\": \"CAST(a AS VARCHAR)\",\n                \"duckdb\": \"CAST(a AS TEXT)\",\n                \"materialize\": \"CAST(a AS VARCHAR)\",\n                \"mysql\": \"CAST(a AS CHAR)\",\n                \"hive\": \"CAST(a AS STRING)\",\n                \"oracle\": \"CAST(a AS VARCHAR2)\",\n                \"postgres\": \"CAST(a AS VARCHAR)\",\n                \"presto\": \"CAST(a AS VARCHAR)\",\n                \"redshift\": \"CAST(a AS VARCHAR)\",\n                \"snowflake\": \"CAST(a AS VARCHAR)\",\n                \"spark\": \"CAST(a AS STRING)\",\n                \"starrocks\": \"CAST(a AS VARCHAR)\",\n                \"tsql\": \"CAST(a AS VARCHAR)\",\n                \"doris\": \"CAST(a AS VARCHAR)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS VARCHAR(3))\",\n            write={\n                \"bigquery\": \"CAST(a AS STRING)\",\n                \"drill\": \"CAST(a AS VARCHAR(3))\",\n                \"duckdb\": \"CAST(a AS TEXT(3))\",\n                \"materialize\": \"CAST(a AS VARCHAR(3))\",\n                \"mysql\": \"CAST(a AS CHAR(3))\",\n                \"hive\": \"CAST(a AS VARCHAR(3))\",\n                \"oracle\": \"CAST(a AS VARCHAR2(3))\",\n                \"postgres\": \"CAST(a AS VARCHAR(3))\",\n                \"presto\": \"CAST(a AS VARCHAR(3))\",\n                \"redshift\": \"CAST(a AS VARCHAR(3))\",\n                \"snowflake\": \"CAST(a AS VARCHAR(3))\",\n                \"spark\": \"CAST(a AS VARCHAR(3))\",\n                \"starrocks\": \"CAST(a AS VARCHAR(3))\",\n                \"tsql\": \"CAST(a AS VARCHAR(3))\",\n                \"doris\": \"CAST(a AS VARCHAR(3))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS CHARACTER VARYING)\",\n            write={\n                \"bigquery\": \"CAST(a AS STRING)\",\n                \"drill\": \"CAST(a AS VARCHAR)\",\n                \"duckdb\": \"CAST(a AS TEXT)\",\n                \"materialize\": \"CAST(a AS VARCHAR)\",\n                \"mysql\": \"CAST(a AS CHAR)\",\n                \"hive\": \"CAST(a AS STRING)\",\n                \"oracle\": \"CAST(a AS VARCHAR2)\",\n                \"postgres\": \"CAST(a AS VARCHAR)\",\n                \"presto\": \"CAST(a AS VARCHAR)\",\n                \"redshift\": \"CAST(a AS VARCHAR)\",\n                \"snowflake\": \"CAST(a AS VARCHAR)\",\n                \"spark\": \"CAST(a AS STRING)\",\n                \"starrocks\": \"CAST(a AS VARCHAR)\",\n                \"tsql\": \"CAST(a AS VARCHAR)\",\n                \"doris\": \"CAST(a AS VARCHAR)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS CHARACTER VARYING(3))\",\n            write={\n                \"bigquery\": \"CAST(a AS STRING)\",\n                \"drill\": \"CAST(a AS VARCHAR(3))\",\n                \"duckdb\": \"CAST(a AS TEXT(3))\",\n                \"materialize\": \"CAST(a AS VARCHAR(3))\",\n                \"mysql\": \"CAST(a AS CHAR(3))\",\n                \"hive\": \"CAST(a AS VARCHAR(3))\",\n                \"oracle\": \"CAST(a AS VARCHAR2(3))\",\n                \"postgres\": \"CAST(a AS VARCHAR(3))\",\n                \"presto\": \"CAST(a AS VARCHAR(3))\",\n                \"redshift\": \"CAST(a AS VARCHAR(3))\",\n                \"snowflake\": \"CAST(a AS VARCHAR(3))\",\n                \"spark\": \"CAST(a AS VARCHAR(3))\",\n                \"starrocks\": \"CAST(a AS VARCHAR(3))\",\n                \"tsql\": \"CAST(a AS VARCHAR(3))\",\n                \"doris\": \"CAST(a AS VARCHAR(3))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS SMALLINT)\",\n            write={\n                \"bigquery\": \"CAST(a AS INT64)\",\n                \"drill\": \"CAST(a AS INTEGER)\",\n                \"duckdb\": \"CAST(a AS SMALLINT)\",\n                \"materialize\": \"CAST(a AS SMALLINT)\",\n                \"mysql\": \"CAST(a AS SIGNED)\",\n                \"hive\": \"CAST(a AS SMALLINT)\",\n                \"oracle\": \"CAST(a AS SMALLINT)\",\n                \"postgres\": \"CAST(a AS SMALLINT)\",\n                \"presto\": \"CAST(a AS SMALLINT)\",\n                \"redshift\": \"CAST(a AS SMALLINT)\",\n                \"snowflake\": \"CAST(a AS SMALLINT)\",\n                \"spark\": \"CAST(a AS SMALLINT)\",\n                \"sqlite\": \"CAST(a AS INTEGER)\",\n                \"starrocks\": \"CAST(a AS SMALLINT)\",\n                \"doris\": \"CAST(a AS SMALLINT)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS DOUBLE)\",\n            read={\n                \"postgres\": \"CAST(a AS DOUBLE PRECISION)\",\n                \"redshift\": \"CAST(a AS DOUBLE PRECISION)\",\n            },\n            write={\n                \"bigquery\": \"CAST(a AS FLOAT64)\",\n                \"clickhouse\": \"CAST(a AS Nullable(Float64))\",\n                \"doris\": \"CAST(a AS DOUBLE)\",\n                \"drill\": \"CAST(a AS DOUBLE)\",\n                \"duckdb\": \"CAST(a AS DOUBLE)\",\n                \"materialize\": \"CAST(a AS DOUBLE PRECISION)\",\n                \"mysql\": \"CAST(a AS DOUBLE)\",\n                \"hive\": \"CAST(a AS DOUBLE)\",\n                \"oracle\": \"CAST(a AS DOUBLE PRECISION)\",\n                \"postgres\": \"CAST(a AS DOUBLE PRECISION)\",\n                \"presto\": \"CAST(a AS DOUBLE)\",\n                \"redshift\": \"CAST(a AS DOUBLE PRECISION)\",\n                \"snowflake\": \"CAST(a AS DOUBLE)\",\n                \"spark\": \"CAST(a AS DOUBLE)\",\n                \"starrocks\": \"CAST(a AS DOUBLE)\",\n            },\n        )\n        self.validate_all(\n            \"CAST('1 DAY' AS INTERVAL)\",\n            write={\n                \"postgres\": \"CAST('1 DAY' AS INTERVAL)\",\n                \"redshift\": \"CAST('1 DAY' AS INTERVAL)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS TIMESTAMP)\",\n            write={\n                \"starrocks\": \"CAST(a AS DATETIME)\",\n                \"redshift\": \"CAST(a AS TIMESTAMP)\",\n                \"doris\": \"CAST(a AS DATETIME)\",\n                \"mysql\": \"CAST(a AS DATETIME)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS TIMESTAMPTZ)\",\n            write={\n                \"starrocks\": \"TIMESTAMP(a)\",\n                \"redshift\": \"CAST(a AS TIMESTAMP WITH TIME ZONE)\",\n                \"doris\": \"CAST(a AS DATETIME)\",\n                \"mysql\": \"TIMESTAMP(a)\",\n            },\n        )\n        self.validate_all(\"CAST(a AS TINYINT)\", write={\"oracle\": \"CAST(a AS SMALLINT)\"})\n        self.validate_all(\"CAST(a AS SMALLINT)\", write={\"oracle\": \"CAST(a AS SMALLINT)\"})\n        self.validate_all(\"CAST(a AS BIGINT)\", write={\"oracle\": \"CAST(a AS INT)\"})\n        self.validate_all(\"CAST(a AS INT)\", write={\"oracle\": \"CAST(a AS INT)\"})\n        self.validate_all(\n            \"CAST(a AS DECIMAL)\",\n            read={\"oracle\": \"CAST(a AS NUMBER)\"},\n            write={\"oracle\": \"CAST(a AS NUMBER)\"},\n        )\n        self.validate_all(\n            \"CAST('127.0.0.1/32' AS INET)\",\n            read={\"postgres\": \"INET '127.0.0.1/32'\"},\n        )\n        self.assertIsNotNone(\n            self.validate_identity(\"CREATE TABLE foo (bar INT AS (foo))\").find(\n                exp.ComputedColumnConstraint\n            )\n        )\n        self.assertIsNotNone(\n            self.validate_identity(\n                \"CREATE TABLE foo (t1 INT, t2 INT, bar INT AS (t1 * t2 * 2))\"\n            ).find(exp.ComputedColumnConstraint)\n        )\n\n    def test_ddl(self):\n        self.validate_all(\n            \"CREATE TABLE a LIKE b\",\n            write={\n                \"\": \"CREATE TABLE a LIKE b\",\n                \"bigquery\": \"CREATE TABLE a LIKE b\",\n                \"clickhouse\": \"CREATE TABLE a AS b\",\n                \"databricks\": \"CREATE TABLE a LIKE b\",\n                \"doris\": \"CREATE TABLE a LIKE b\",\n                \"drill\": \"CREATE TABLE a AS SELECT * FROM b LIMIT 0\",\n                \"duckdb\": \"CREATE TABLE a AS SELECT * FROM b LIMIT 0\",\n                \"hive\": \"CREATE TABLE a LIKE b\",\n                \"mysql\": \"CREATE TABLE a LIKE b\",\n                \"oracle\": \"CREATE TABLE a LIKE b\",\n                \"postgres\": \"CREATE TABLE a (LIKE b)\",\n                \"presto\": \"CREATE TABLE a (LIKE b)\",\n                \"redshift\": \"CREATE TABLE a (LIKE b)\",\n                \"snowflake\": \"CREATE TABLE a LIKE b\",\n                \"spark\": \"CREATE TABLE a LIKE b\",\n                \"sqlite\": \"CREATE TABLE a AS SELECT * FROM b LIMIT 0\",\n                \"trino\": \"CREATE TABLE a (LIKE b)\",\n                \"tsql\": \"SELECT TOP 0 * INTO a FROM b AS temp\",\n            },\n        )\n\n    def test_heredoc_strings(self):\n        for dialect in (\"clickhouse\", \"postgres\", \"redshift\"):\n            # Invalid matching tag\n            with self.assertRaises(TokenError):\n                parse_one(\"SELECT $tag1$invalid heredoc string$tag2$\", dialect=dialect)\n\n            # Unmatched tag\n            with self.assertRaises(TokenError):\n                parse_one(\"SELECT $tag1$invalid heredoc string\", dialect=dialect)\n\n            # Without tag\n            self.validate_all(\n                \"SELECT 'this is a heredoc string'\",\n                read={\n                    dialect: \"SELECT $$this is a heredoc string$$\",\n                },\n            )\n            self.validate_all(\n                \"SELECT ''\",\n                read={\n                    dialect: \"SELECT $$$$\",\n                },\n            )\n\n            # With tag\n            self.validate_all(\n                \"SELECT 'this is also a heredoc string'\",\n                read={\n                    dialect: \"SELECT $foo$this is also a heredoc string$foo$\",\n                },\n            )\n            self.validate_all(\n                \"SELECT ''\",\n                read={\n                    dialect: \"SELECT $foo$$foo$\",\n                },\n            )\n\n    def test_decode(self):\n        self.validate_identity(\"DECODE(bin, charset)\")\n\n        self.validate_all(\n            \"SELECT DECODE(a, 1, 'one')\",\n            write={\n                \"\": \"SELECT DECODE(a, 1, 'one')\",\n                \"duckdb\": \"SELECT CASE WHEN a = 1 THEN 'one' END\",\n                \"oracle\": \"SELECT DECODE(a, 1, 'one')\",\n                \"redshift\": \"SELECT DECODE(a, 1, 'one')\",\n                \"snowflake\": \"SELECT DECODE(a, 1, 'one')\",\n                \"spark\": \"SELECT DECODE(a, 1, 'one')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DECODE(a, 1, 'one', 'default')\",\n            write={\n                \"\": \"SELECT DECODE(a, 1, 'one', 'default')\",\n                \"duckdb\": \"SELECT CASE WHEN a = 1 THEN 'one' ELSE 'default' END\",\n                \"oracle\": \"SELECT DECODE(a, 1, 'one', 'default')\",\n                \"redshift\": \"SELECT DECODE(a, 1, 'one', 'default')\",\n                \"snowflake\": \"SELECT DECODE(a, 1, 'one', 'default')\",\n                \"spark\": \"SELECT DECODE(a, 1, 'one', 'default')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DECODE(a, NULL, 'null')\",\n            write={\n                \"\": \"SELECT DECODE(a, NULL, 'null')\",\n                \"duckdb\": \"SELECT CASE WHEN a IS NULL THEN 'null' END\",\n                \"oracle\": \"SELECT DECODE(a, NULL, 'null')\",\n                \"redshift\": \"SELECT DECODE(a, NULL, 'null')\",\n                \"snowflake\": \"SELECT DECODE(a, NULL, 'null')\",\n                \"spark\": \"SELECT DECODE(a, NULL, 'null')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DECODE(a, b, c)\",\n            write={\n                \"\": \"SELECT DECODE(a, b, c)\",\n                \"duckdb\": \"SELECT CASE WHEN a = b OR (a IS NULL AND b IS NULL) THEN c END\",\n                \"oracle\": \"SELECT DECODE(a, b, c)\",\n                \"redshift\": \"SELECT DECODE(a, b, c)\",\n                \"snowflake\": \"SELECT DECODE(a, b, c)\",\n                \"spark\": \"SELECT DECODE(a, b, c)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DECODE(tbl.col, 'some_string', 'foo')\",\n            write={\n                \"\": \"SELECT DECODE(tbl.col, 'some_string', 'foo')\",\n                \"duckdb\": \"SELECT CASE WHEN tbl.col = 'some_string' THEN 'foo' END\",\n                \"oracle\": \"SELECT DECODE(tbl.col, 'some_string', 'foo')\",\n                \"redshift\": \"SELECT DECODE(tbl.col, 'some_string', 'foo')\",\n                \"snowflake\": \"SELECT DECODE(tbl.col, 'some_string', 'foo')\",\n                \"spark\": \"SELECT DECODE(tbl.col, 'some_string', 'foo')\",\n            },\n        )\n\n    def test_to_binary(self):\n        self.validate_all(\n            \"TO_BINARY('1C')\",\n            read={\n                \"\": \"TO_BINARY('1C')\",\n                \"snowflake\": \"TO_BINARY('1C')\",\n                \"starrocks\": \"TO_BINARY('1C')\",\n                \"duckdb\": \"TO_BINARY('1C')\",\n                \"spark\": \"TO_BINARY('1C')\",\n                \"databricks\": \"TO_BINARY('1C')\",\n            },\n            write={\n                \"snowflake\": \"TO_BINARY('1C')\",\n                \"starrocks\": \"TO_BINARY('1C')\",\n                \"duckdb\": \"TO_BINARY('1C')\",\n                \"spark\": \"TO_BINARY('1C')\",\n                \"databricks\": \"TO_BINARY('1C')\",\n            },\n        )\n        self.validate_all(\n            \"TO_BINARY('1C', 'HEX')\",\n            read={\n                \"\": \"TO_BINARY('1C', 'HEX')\",\n                \"snowflake\": \"TO_BINARY('1C', 'HEX')\",\n                \"starrocks\": \"TO_BINARY('1C', 'HEX')\",\n                \"spark\": \"TO_BINARY('1C', 'HEX')\",\n                \"databricks\": \"TO_BINARY('1C', 'HEX')\",\n            },\n            write={\n                \"snowflake\": \"TO_BINARY('1C', 'HEX')\",\n                \"starrocks\": \"TO_BINARY('1C', 'HEX')\",\n                \"spark\": \"TO_BINARY('1C', 'HEX')\",\n                \"databricks\": \"TO_BINARY('1C', 'HEX')\",\n            },\n        )\n\n    def test_if_null(self):\n        self.validate_all(\n            \"SELECT IFNULL(1, NULL) FROM foo\",\n            write={\n                \"\": \"SELECT COALESCE(1, NULL) FROM foo\",\n                \"redshift\": \"SELECT COALESCE(1, NULL) FROM foo\",\n                \"postgres\": \"SELECT COALESCE(1, NULL) FROM foo\",\n                \"mysql\": \"SELECT COALESCE(1, NULL) FROM foo\",\n                \"duckdb\": \"SELECT COALESCE(1, NULL) FROM foo\",\n                \"spark\": \"SELECT COALESCE(1, NULL) FROM foo\",\n                \"bigquery\": \"SELECT COALESCE(1, NULL) FROM foo\",\n                \"presto\": \"SELECT COALESCE(1, NULL) FROM foo\",\n            },\n        )\n\n    def test_is_ascii(self):\n        self.validate_all(\n            \"SELECT IS_ASCII(x)\",\n            write={\n                \"\": \"SELECT IS_ASCII(x)\",\n                \"sqlite\": \"SELECT (NOT x GLOB CAST(x'2a5b5e012d7f5d2a' AS TEXT))\",\n                \"mysql\": \"SELECT REGEXP_LIKE(x, '^[[:ascii:]]*$')\",\n                \"postgres\": \"SELECT (x ~ '^[[:ascii:]]*$')\",\n                \"tsql\": \"SELECT (PATINDEX(CONVERT(VARCHAR(MAX), 0x255b5e002d7f5d25) COLLATE Latin1_General_BIN, x) = 0)\",\n                \"oracle\": \"SELECT NVL(REGEXP_LIKE(x, '^[' || CHR(1) || '-' || CHR(127) || ']*$'), TRUE)\",\n            },\n        )\n\n    def test_nvl2(self):\n        self.validate_all(\n            \"SELECT NVL2(a, b, c)\",\n            write={\n                \"\": \"SELECT NVL2(a, b, c)\",\n                \"bigquery\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"clickhouse\": \"SELECT CASE WHEN NOT (a IS NULL) THEN b ELSE c END\",\n                \"databricks\": \"SELECT NVL2(a, b, c)\",\n                \"doris\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"dremio\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"drill\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"duckdb\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"hive\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"mysql\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"oracle\": \"SELECT NVL2(a, b, c)\",\n                \"postgres\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"presto\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"redshift\": \"SELECT NVL2(a, b, c)\",\n                \"snowflake\": \"SELECT NVL2(a, b, c)\",\n                \"spark\": \"SELECT NVL2(a, b, c)\",\n                \"spark2\": \"SELECT NVL2(a, b, c)\",\n                \"sqlite\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"starrocks\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"teradata\": \"SELECT NVL2(a, b, c)\",\n                \"trino\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n                \"tsql\": \"SELECT CASE WHEN NOT a IS NULL THEN b ELSE c END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT NVL2(a, b)\",\n            write={\n                \"\": \"SELECT NVL2(a, b)\",\n                \"bigquery\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"clickhouse\": \"SELECT CASE WHEN NOT (a IS NULL) THEN b END\",\n                \"databricks\": \"SELECT NVL2(a, b)\",\n                \"doris\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"dremio\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"drill\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"duckdb\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"hive\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"mysql\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"oracle\": \"SELECT NVL2(a, b)\",\n                \"postgres\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"presto\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"redshift\": \"SELECT NVL2(a, b)\",\n                \"snowflake\": \"SELECT NVL2(a, b)\",\n                \"spark\": \"SELECT NVL2(a, b)\",\n                \"spark2\": \"SELECT NVL2(a, b)\",\n                \"sqlite\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"starrocks\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"teradata\": \"SELECT NVL2(a, b)\",\n                \"trino\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n                \"tsql\": \"SELECT CASE WHEN NOT a IS NULL THEN b END\",\n            },\n        )\n\n    def test_time(self):\n        self.validate_all(\n            \"STR_TO_TIME(x, '%Y-%m-%dT%H:%M:%S')\",\n            read={\n                \"duckdb\": \"STRPTIME(x, '%Y-%m-%dT%H:%M:%S')\",\n            },\n            write={\n                \"mysql\": \"STR_TO_DATE(x, '%Y-%m-%dT%T')\",\n                \"duckdb\": \"STRPTIME(x, '%Y-%m-%dT%H:%M:%S')\",\n                \"hive\": \"CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, 'yyyy-MM-ddTHH:mm:ss')) AS TIMESTAMP)\",\n                \"presto\": \"DATE_PARSE(x, '%Y-%m-%dT%T')\",\n                \"drill\": \"TO_TIMESTAMP(x, 'yyyy-MM-dd''T''HH:mm:ss')\",\n                \"redshift\": \"TO_TIMESTAMP(x, 'YYYY-MM-DDTHH24:MI:SS')\",\n                \"spark\": \"TO_TIMESTAMP(x, 'yyyy-MM-ddTHH:mm:ss')\",\n            },\n        )\n        self.validate_all(\n            \"STR_TO_TIME('2020-01-01', '%Y-%m-%d')\",\n            write={\n                \"drill\": \"TO_TIMESTAMP('2020-01-01', 'yyyy-MM-dd')\",\n                \"duckdb\": \"STRPTIME('2020-01-01', '%Y-%m-%d')\",\n                \"hive\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"oracle\": \"TO_TIMESTAMP('2020-01-01', 'YYYY-MM-DD')\",\n                \"postgres\": \"TO_TIMESTAMP('2020-01-01', 'YYYY-MM-DD')\",\n                \"presto\": \"DATE_PARSE('2020-01-01', '%Y-%m-%d')\",\n                \"redshift\": \"TO_TIMESTAMP('2020-01-01', 'YYYY-MM-DD')\",\n                \"spark\": \"TO_TIMESTAMP('2020-01-01', 'yyyy-MM-dd')\",\n            },\n        )\n        self.validate_all(\n            \"STR_TO_TIME(x, '%y')\",\n            write={\n                \"drill\": \"TO_TIMESTAMP(x, 'yy')\",\n                \"duckdb\": \"STRPTIME(x, '%y')\",\n                \"hive\": \"CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, 'yy')) AS TIMESTAMP)\",\n                \"materialize\": \"TO_TIMESTAMP(x, 'YY')\",\n                \"presto\": \"DATE_PARSE(x, '%y')\",\n                \"oracle\": \"TO_TIMESTAMP(x, 'YY')\",\n                \"postgres\": \"TO_TIMESTAMP(x, 'YY')\",\n                \"redshift\": \"TO_TIMESTAMP(x, 'YY')\",\n                \"spark\": \"TO_TIMESTAMP(x, 'yy')\",\n            },\n        )\n        self.validate_all(\n            \"STR_TO_UNIX('2020-01-01', '%Y-%m-%d')\",\n            write={\n                \"duckdb\": \"EPOCH(STRPTIME('2020-01-01', '%Y-%m-%d'))\",\n                \"hive\": \"UNIX_TIMESTAMP('2020-01-01', 'yyyy-MM-dd')\",\n                \"presto\": \"TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST('2020-01-01' AS VARCHAR), '%Y-%m-%d')), PARSE_DATETIME(DATE_FORMAT(CAST('2020-01-01' AS TIMESTAMP), '%Y-%m-%d'), 'yyyy-MM-dd')))\",\n                \"starrocks\": \"UNIX_TIMESTAMP('2020-01-01', '%Y-%m-%d')\",\n                \"doris\": \"UNIX_TIMESTAMP('2020-01-01', '%Y-%m-%d')\",\n            },\n        )\n        self.validate_all(\n            \"TIME_STR_TO_DATE('2020-01-01')\",\n            write={\n                \"drill\": \"CAST('2020-01-01' AS DATE)\",\n                \"duckdb\": \"CAST('2020-01-01' AS DATE)\",\n                \"hive\": \"TO_DATE('2020-01-01')\",\n                \"presto\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"starrocks\": \"TO_DATE('2020-01-01')\",\n                \"doris\": \"TO_DATE('2020-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"TIME_STR_TO_TIME('2020-01-01')\",\n            write={\n                \"bigquery\": \"CAST('2020-01-01' AS DATETIME)\",\n                \"databricks\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"duckdb\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"tsql\": \"CAST('2020-01-01' AS DATETIME2)\",\n                \"mysql\": \"CAST('2020-01-01' AS DATETIME)\",\n                \"postgres\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"redshift\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"snowflake\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"spark\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"trino\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"clickhouse\": \"CAST('2020-01-01' AS DateTime64(6))\",\n                \"drill\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"hive\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"presto\": \"CAST('2020-01-01' AS TIMESTAMP)\",\n                \"sqlite\": \"'2020-01-01'\",\n                \"doris\": \"CAST('2020-01-01' AS DATETIME)\",\n            },\n        )\n        self.validate_all(\n            \"TIME_STR_TO_TIME('2020-01-01 12:13:14.123456+00:00')\",\n            write={\n                \"mysql\": \"CAST('2020-01-01 12:13:14.123456+00:00' AS DATETIME(6))\",\n                \"trino\": \"CAST('2020-01-01 12:13:14.123456+00:00' AS TIMESTAMP(6))\",\n                \"presto\": \"CAST('2020-01-01 12:13:14.123456+00:00' AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"TIME_STR_TO_TIME('2020-01-01 12:13:14.123-08:00', 'America/Los_Angeles')\",\n            write={\n                \"mysql\": \"TIMESTAMP('2020-01-01 12:13:14.123-08:00')\",\n                \"trino\": \"CAST('2020-01-01 12:13:14.123-08:00' AS TIMESTAMP(3) WITH TIME ZONE)\",\n                \"presto\": \"CAST('2020-01-01 12:13:14.123-08:00' AS TIMESTAMP WITH TIME ZONE)\",\n            },\n        )\n        self.validate_all(\n            \"TIME_STR_TO_TIME('2020-01-01 12:13:14-08:00', 'America/Los_Angeles')\",\n            write={\n                \"bigquery\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMP)\",\n                \"databricks\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMP)\",\n                \"duckdb\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMPTZ)\",\n                \"tsql\": \"CAST('2020-01-01 12:13:14-08:00' AS DATETIMEOFFSET) AT TIME ZONE 'UTC'\",\n                \"mysql\": \"TIMESTAMP('2020-01-01 12:13:14-08:00')\",\n                \"postgres\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMPTZ)\",\n                \"redshift\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMP WITH TIME ZONE)\",\n                \"snowflake\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMPTZ)\",\n                \"spark\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMP)\",\n                \"trino\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMP WITH TIME ZONE)\",\n                \"clickhouse\": \"CAST('2020-01-01 12:13:14' AS DateTime64(6, 'America/Los_Angeles'))\",\n                \"drill\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMP)\",\n                \"hive\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMP)\",\n                \"presto\": \"CAST('2020-01-01 12:13:14-08:00' AS TIMESTAMP WITH TIME ZONE)\",\n                \"sqlite\": \"'2020-01-01 12:13:14-08:00'\",\n                \"doris\": \"CAST('2020-01-01 12:13:14-08:00' AS DATETIME)\",\n            },\n        )\n        self.validate_all(\n            \"TIME_STR_TO_TIME(col, 'America/Los_Angeles')\",\n            write={\n                \"bigquery\": \"CAST(col AS TIMESTAMP)\",\n                \"databricks\": \"CAST(col AS TIMESTAMP)\",\n                \"duckdb\": \"CAST(col AS TIMESTAMPTZ)\",\n                \"tsql\": \"CAST(col AS DATETIMEOFFSET) AT TIME ZONE 'UTC'\",\n                \"mysql\": \"TIMESTAMP(col)\",\n                \"postgres\": \"CAST(col AS TIMESTAMPTZ)\",\n                \"redshift\": \"CAST(col AS TIMESTAMP WITH TIME ZONE)\",\n                \"snowflake\": \"CAST(col AS TIMESTAMPTZ)\",\n                \"spark\": \"CAST(col AS TIMESTAMP)\",\n                \"trino\": \"CAST(col AS TIMESTAMP WITH TIME ZONE)\",\n                \"clickhouse\": \"CAST(col AS DateTime64(6, 'America/Los_Angeles'))\",\n                \"drill\": \"CAST(col AS TIMESTAMP)\",\n                \"hive\": \"CAST(col AS TIMESTAMP)\",\n                \"presto\": \"CAST(col AS TIMESTAMP WITH TIME ZONE)\",\n                \"sqlite\": \"col\",\n                \"doris\": \"CAST(col AS DATETIME)\",\n            },\n        )\n        self.validate_all(\n            \"TIME_STR_TO_UNIX('2020-01-01')\",\n            write={\n                \"duckdb\": \"EPOCH(CAST('2020-01-01' AS TIMESTAMP))\",\n                \"hive\": \"UNIX_TIMESTAMP('2020-01-01')\",\n                \"mysql\": \"UNIX_TIMESTAMP('2020-01-01')\",\n                \"presto\": \"TO_UNIXTIME(DATE_PARSE('2020-01-01', '%Y-%m-%d %T'))\",\n                \"doris\": \"UNIX_TIMESTAMP('2020-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"TIME_TO_STR(x, '%Y-%m-%d')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%F', x)\",\n                \"drill\": \"TO_CHAR(x, 'yyyy-MM-dd')\",\n                \"duckdb\": \"STRFTIME(x, '%Y-%m-%d')\",\n                \"hive\": \"DATE_FORMAT(x, 'yyyy-MM-dd')\",\n                \"materialize\": \"TO_CHAR(x, 'YYYY-MM-DD')\",\n                \"oracle\": \"TO_CHAR(x, 'YYYY-MM-DD')\",\n                \"postgres\": \"TO_CHAR(x, 'YYYY-MM-DD')\",\n                \"presto\": \"DATE_FORMAT(x, '%Y-%m-%d')\",\n                \"redshift\": \"TO_CHAR(x, 'YYYY-MM-DD')\",\n                \"doris\": \"DATE_FORMAT(x, '%Y-%m-%d')\",\n            },\n        )\n        self.validate_all(\n            \"TIME_TO_STR(a, '%Y-%m-%d %H:%M:%S.%f')\",\n            write={\n                \"redshift\": \"TO_CHAR(a, 'YYYY-MM-DD HH24:MI:SS.US')\",\n                \"tsql\": \"FORMAT(a, 'yyyy-MM-dd HH:mm:ss.ffffff')\",\n            },\n        )\n        self.validate_all(\n            \"TIME_TO_TIME_STR(x)\",\n            write={\n                \"drill\": \"CAST(x AS VARCHAR)\",\n                \"duckdb\": \"CAST(x AS TEXT)\",\n                \"hive\": \"CAST(x AS STRING)\",\n                \"presto\": \"CAST(x AS VARCHAR)\",\n                \"redshift\": \"CAST(x AS VARCHAR(MAX))\",\n                \"doris\": \"CAST(x AS STRING)\",\n            },\n        )\n        self.validate_all(\n            \"TIME_TO_UNIX(x)\",\n            write={\n                \"drill\": \"UNIX_TIMESTAMP(x)\",\n                \"duckdb\": \"EPOCH(x)\",\n                \"hive\": \"UNIX_TIMESTAMP(x)\",\n                \"presto\": \"TO_UNIXTIME(x)\",\n                \"doris\": \"UNIX_TIMESTAMP(x)\",\n            },\n        )\n        self.validate_all(\n            \"TS_OR_DS_TO_DATE_STR(x)\",\n            write={\n                \"duckdb\": \"SUBSTRING(CAST(x AS TEXT), 1, 10)\",\n                \"hive\": \"SUBSTRING(CAST(x AS STRING), 1, 10)\",\n                \"presto\": \"SUBSTRING(CAST(x AS VARCHAR), 1, 10)\",\n                \"doris\": \"SUBSTRING(CAST(x AS STRING), 1, 10)\",\n            },\n        )\n        self.validate_all(\n            \"TS_OR_DS_TO_DATE(x)\",\n            write={\n                \"bigquery\": \"CAST(x AS DATE)\",\n                \"duckdb\": \"CAST(x AS DATE)\",\n                \"hive\": \"TO_DATE(x)\",\n                \"materialize\": \"CAST(x AS DATE)\",\n                \"postgres\": \"CAST(x AS DATE)\",\n                \"presto\": \"CAST(CAST(x AS TIMESTAMP) AS DATE)\",\n                \"snowflake\": \"TO_DATE(x)\",\n                \"doris\": \"TO_DATE(x)\",\n                \"mysql\": \"DATE(x)\",\n            },\n        )\n        self.validate_all(\n            \"TS_OR_DS_TO_DATE(x, '%-d')\",\n            write={\n                \"duckdb\": \"CAST(STRPTIME(x, '%-d') AS DATE)\",\n                \"hive\": \"TO_DATE(x, 'd')\",\n                \"presto\": \"CAST(DATE_PARSE(x, '%e') AS DATE)\",\n                \"spark\": \"TO_DATE(x, 'd')\",\n            },\n        )\n        self.validate_all(\n            \"UNIX_TO_STR(x, y)\",\n            write={\n                \"duckdb\": \"STRFTIME(TO_TIMESTAMP(x), y)\",\n                \"hive\": \"FROM_UNIXTIME(x, y)\",\n                \"presto\": \"DATE_FORMAT(FROM_UNIXTIME(x), y)\",\n                \"starrocks\": \"FROM_UNIXTIME(x, y)\",\n                \"doris\": \"FROM_UNIXTIME(x, y)\",\n            },\n        )\n        self.validate_all(\n            \"UNIX_TO_TIME(x)\",\n            write={\n                \"duckdb\": \"TO_TIMESTAMP(x)\",\n                \"hive\": \"FROM_UNIXTIME(x)\",\n                \"oracle\": \"TO_DATE('1970-01-01', 'YYYY-MM-DD') + (x / 86400)\",\n                \"materialize\": \"TO_TIMESTAMP(x)\",\n                \"postgres\": \"TO_TIMESTAMP(x)\",\n                \"presto\": \"FROM_UNIXTIME(x)\",\n                \"starrocks\": \"FROM_UNIXTIME(x)\",\n                \"doris\": \"FROM_UNIXTIME(x)\",\n                \"exasol\": \"FROM_POSIX_TIME(x)\",\n            },\n        )\n        self.validate_all(\n            \"UNIX_TO_TIME_STR(x)\",\n            write={\n                \"duckdb\": \"CAST(TO_TIMESTAMP(x) AS TEXT)\",\n                \"hive\": \"FROM_UNIXTIME(x)\",\n                \"presto\": \"CAST(FROM_UNIXTIME(x) AS VARCHAR)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TO_DATE_STR(x)\",\n            write={\n                \"drill\": \"CAST(x AS VARCHAR)\",\n                \"duckdb\": \"CAST(x AS TEXT)\",\n                \"hive\": \"CAST(x AS STRING)\",\n                \"presto\": \"CAST(x AS VARCHAR)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TO_DI(x)\",\n            write={\n                \"drill\": \"CAST(TO_DATE(x, 'yyyyMMdd') AS INT)\",\n                \"duckdb\": \"CAST(STRFTIME(x, '%Y%m%d') AS INT)\",\n                \"hive\": \"CAST(DATE_FORMAT(x, 'yyyyMMdd') AS INT)\",\n                \"presto\": \"CAST(DATE_FORMAT(x, '%Y%m%d') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"DI_TO_DATE(x)\",\n            write={\n                \"drill\": \"TO_DATE(CAST(x AS VARCHAR), 'yyyyMMdd')\",\n                \"duckdb\": \"CAST(STRPTIME(CAST(x AS TEXT), '%Y%m%d') AS DATE)\",\n                \"hive\": \"TO_DATE(CAST(x AS STRING), 'yyyyMMdd')\",\n                \"presto\": \"CAST(DATE_PARSE(CAST(x AS VARCHAR), '%Y%m%d') AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"TS_OR_DI_TO_DI(x)\",\n            write={\n                \"duckdb\": \"CAST(SUBSTR(REPLACE(CAST(x AS TEXT), '-', ''), 1, 8) AS INT)\",\n                \"hive\": \"CAST(SUBSTR(REPLACE(CAST(x AS STRING), '-', ''), 1, 8) AS INT)\",\n                \"presto\": \"CAST(SUBSTR(REPLACE(CAST(x AS VARCHAR), '-', ''), 1, 8) AS INT)\",\n                \"spark\": \"CAST(SUBSTR(REPLACE(CAST(x AS STRING), '-', ''), 1, 8) AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD(x, 1, 'DAY')\",\n            read={\n                \"snowflake\": \"DATEADD('DAY', 1, x)\",\n                \"dremio\": \"DATE_ADD(x, 1)\",\n            },\n            write={\n                \"bigquery\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"drill\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"duckdb\": \"x + INTERVAL 1 DAY\",\n                \"hive\": \"DATE_ADD(x, 1)\",\n                \"materialize\": \"x + INTERVAL '1 DAY'\",\n                \"mysql\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"postgres\": \"x + INTERVAL '1 DAY'\",\n                \"presto\": \"DATE_ADD('DAY', 1, x)\",\n                \"snowflake\": \"DATEADD(DAY, 1, x)\",\n                \"spark\": \"DATE_ADD(x, 1)\",\n                \"sqlite\": \"DATE(x, '1 DAY')\",\n                \"starrocks\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"tsql\": \"DATEADD(DAY, 1, x)\",\n                \"doris\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"dremio\": \"DATE_ADD(x, 1)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD(x, 1)\",\n            write={\n                \"bigquery\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"drill\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"duckdb\": \"x + INTERVAL 1 DAY\",\n                \"hive\": \"DATE_ADD(x, 1)\",\n                \"mysql\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"presto\": \"DATE_ADD('DAY', 1, x)\",\n                \"spark\": \"DATE_ADD(x, 1)\",\n                \"starrocks\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"doris\": \"DATE_ADD(x, INTERVAL 1 DAY)\",\n                \"dremio\": \"DATE_ADD(x, 1)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('DAY', x)\",\n            read={\n                \"bigquery\": \"DATE_TRUNC(x, day)\",\n                \"spark\": \"TRUNC(x, 'day')\",\n            },\n            write={\n                \"bigquery\": \"DATE_TRUNC(x, DAY)\",\n                \"duckdb\": \"DATE_TRUNC('DAY', x)\",\n                \"mysql\": \"DATE(x)\",\n                \"presto\": \"DATE_TRUNC('DAY', x)\",\n                \"materialize\": \"DATE_TRUNC('DAY', x)\",\n                \"postgres\": \"DATE_TRUNC('DAY', x)\",\n                \"snowflake\": \"DATE_TRUNC('DAY', x)\",\n                \"starrocks\": \"DATE_TRUNC('DAY', x)\",\n                \"spark\": \"TRUNC(x, 'DAY')\",\n                \"doris\": \"DATE_TRUNC(x, 'DAY')\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(x, DAY)\",\n            read={\n                \"bigquery\": \"TIMESTAMP_TRUNC(x, day)\",\n                \"duckdb\": \"DATE_TRUNC('day', x)\",\n                \"materialize\": \"DATE_TRUNC('day', x)\",\n                \"presto\": \"DATE_TRUNC('day', x)\",\n                \"postgres\": \"DATE_TRUNC('day', x)\",\n                \"snowflake\": \"DATE_TRUNC('day', x)\",\n                \"starrocks\": \"DATE_TRUNC('day', x)\",\n                \"spark\": \"DATE_TRUNC('day', x)\",\n                \"doris\": \"DATE_TRUNC('day', x)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('DAY', CAST(x AS DATE))\",\n            read={\n                \"presto\": \"DATE_TRUNC('DAY', x::DATE)\",\n                \"snowflake\": \"DATE_TRUNC('DAY', x::DATE)\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(CAST(x AS DATE), DAY)\",\n            read={\"postgres\": \"DATE_TRUNC('day', x::DATE)\"},\n        )\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(CAST(x AS DATE), DAY)\",\n            read={\"starrocks\": \"DATE_TRUNC('day', x::DATE)\"},\n        )\n        self.validate_all(\n            \"DATE_TRUNC('week', x)\",\n            write={\n                \"mysql\": \"STR_TO_DATE(CONCAT(YEAR(x), ' ', WEEK(x, 1), ' 1'), '%Y %u %w')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('month', x)\",\n            write={\n                \"mysql\": \"STR_TO_DATE(CONCAT(YEAR(x), ' ', MONTH(x), ' 1'), '%Y %c %e')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('quarter', x)\",\n            write={\n                \"mysql\": \"STR_TO_DATE(CONCAT(YEAR(x), ' ', QUARTER(x) * 3 - 2, ' 1'), '%Y %c %e')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('year', x)\",\n            write={\n                \"mysql\": \"STR_TO_DATE(CONCAT(YEAR(x), ' 1 1'), '%Y %c %e')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('millennium', x)\",\n            write={\n                \"mysql\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('YEAR', x)\",\n            read={\n                \"bigquery\": \"DATE_TRUNC(x, year)\",\n                \"spark\": \"TRUNC(x, 'year')\",\n            },\n            write={\n                \"bigquery\": \"DATE_TRUNC(x, YEAR)\",\n                \"materialize\": \"DATE_TRUNC('YEAR', x)\",\n                \"mysql\": \"STR_TO_DATE(CONCAT(YEAR(x), ' 1 1'), '%Y %c %e')\",\n                \"postgres\": \"DATE_TRUNC('YEAR', x)\",\n                \"snowflake\": \"DATE_TRUNC('YEAR', x)\",\n                \"starrocks\": \"DATE_TRUNC('YEAR', x)\",\n                \"spark\": \"TRUNC(x, 'YEAR')\",\n                \"doris\": \"DATE_TRUNC(x, 'YEAR')\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(x, YEAR)\",\n            read={\n                \"bigquery\": \"TIMESTAMP_TRUNC(x, year)\",\n                \"materialize\": \"DATE_TRUNC('YEAR', x)\",\n                \"postgres\": \"DATE_TRUNC(year, x)\",\n                \"spark\": \"DATE_TRUNC('year', x)\",\n                \"snowflake\": \"DATE_TRUNC(year, x)\",\n                \"starrocks\": \"DATE_TRUNC('year', x)\",\n            },\n            write={\n                \"bigquery\": \"TIMESTAMP_TRUNC(x, YEAR)\",\n                \"spark\": \"DATE_TRUNC('YEAR', x)\",\n                \"doris\": \"DATE_TRUNC(x, 'YEAR')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('millennium', x)\",\n            write={\n                \"mysql\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"NEXT_DAY(x, y)\",\n            write={\n                \"snowflake\": \"NEXT_DAY(x, y)\",\n                \"databricks\": \"NEXT_DAY(x, y)\",\n                \"oracle\": \"NEXT_DAY(x, y)\",\n                \"redshift\": \"NEXT_DAY(x, y)\",\n            },\n        )\n        self.validate_all(\n            \"STR_TO_DATE(x, '%Y-%m-%dT%H:%M:%S')\",\n            write={\n                \"drill\": \"TO_DATE(x, 'yyyy-MM-dd''T''HH:mm:ss')\",\n                \"mysql\": \"STR_TO_DATE(x, '%Y-%m-%dT%T')\",\n                \"starrocks\": \"STR_TO_DATE(x, '%Y-%m-%dT%T')\",\n                \"hive\": \"CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, 'yyyy-MM-ddTHH:mm:ss')) AS DATE)\",\n                \"presto\": \"CAST(DATE_PARSE(x, '%Y-%m-%dT%T') AS DATE)\",\n                \"spark\": \"TO_DATE(x, 'yyyy-MM-ddTHH:mm:ss')\",\n                \"doris\": \"STR_TO_DATE(x, '%Y-%m-%dT%T')\",\n            },\n        )\n        self.validate_all(\n            \"STR_TO_DATE(x, '%Y-%m-%d')\",\n            write={\n                \"drill\": \"CAST(x AS DATE)\",\n                \"mysql\": \"STR_TO_DATE(x, '%Y-%m-%d')\",\n                \"starrocks\": \"STR_TO_DATE(x, '%Y-%m-%d')\",\n                \"hive\": \"CAST(x AS DATE)\",\n                \"presto\": \"CAST(DATE_PARSE(x, '%Y-%m-%d') AS DATE)\",\n                \"spark\": \"TO_DATE(x)\",\n                \"doris\": \"STR_TO_DATE(x, '%Y-%m-%d')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_STR_TO_DATE(x)\",\n            write={\n                \"drill\": \"CAST(x AS DATE)\",\n                \"duckdb\": \"CAST(x AS DATE)\",\n                \"hive\": \"CAST(x AS DATE)\",\n                \"presto\": \"CAST(x AS DATE)\",\n                \"spark\": \"CAST(x AS DATE)\",\n                \"sqlite\": \"x\",\n                \"tsql\": \"CAST(x AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"TS_OR_DS_ADD('2021-02-01', 1, 'DAY')\",\n            write={\n                \"drill\": \"DATE_ADD(CAST('2021-02-01' AS DATE), INTERVAL 1 DAY)\",\n                \"duckdb\": \"CAST('2021-02-01' AS DATE) + INTERVAL 1 DAY\",\n                \"hive\": \"DATE_ADD('2021-02-01', 1)\",\n                \"presto\": \"DATE_ADD('DAY', 1, CAST(CAST('2021-02-01' AS TIMESTAMP) AS DATE))\",\n                \"spark\": \"DATE_ADD('2021-02-01', 1)\",\n                \"mysql\": \"DATE_ADD('2021-02-01', INTERVAL 1 DAY)\",\n            },\n        )\n        self.validate_all(\n            \"TS_OR_DS_ADD(x, 1, 'DAY')\",\n            write={\n                \"presto\": \"DATE_ADD('DAY', 1, CAST(CAST(x AS TIMESTAMP) AS DATE))\",\n                \"hive\": \"DATE_ADD(x, 1)\",\n            },\n        )\n        self.validate_all(\n            \"TS_OR_DS_ADD(CURRENT_DATE, 1, 'DAY')\",\n            write={\n                \"presto\": \"DATE_ADD('DAY', 1, CAST(CAST(CURRENT_DATE AS TIMESTAMP) AS DATE))\",\n                \"hive\": \"DATE_ADD(CURRENT_DATE, 1)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD(CAST('2020-01-01' AS DATE), 1)\",\n            write={\n                \"drill\": \"DATE_ADD(CAST('2020-01-01' AS DATE), INTERVAL 1 DAY)\",\n                \"duckdb\": \"CAST('2020-01-01' AS DATE) + INTERVAL 1 DAY\",\n                \"hive\": \"DATE_ADD(CAST('2020-01-01' AS DATE), 1)\",\n                \"presto\": \"DATE_ADD('DAY', 1, CAST('2020-01-01' AS DATE))\",\n                \"spark\": \"DATE_ADD(CAST('2020-01-01' AS DATE), 1)\",\n                \"dremio\": \"DATE_ADD(CAST('2020-01-01' AS DATE), 1)\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP '2022-01-01'\",\n            write={\n                \"drill\": \"CAST('2022-01-01' AS TIMESTAMP)\",\n                \"mysql\": \"CAST('2022-01-01' AS DATETIME)\",\n                \"starrocks\": \"CAST('2022-01-01' AS DATETIME)\",\n                \"hive\": \"CAST('2022-01-01' AS TIMESTAMP)\",\n                \"doris\": \"CAST('2022-01-01' AS DATETIME)\",\n            },\n        )\n        self.validate_all(\n            \"TIMESTAMP('2022-01-01')\",\n            write={\n                \"mysql\": \"TIMESTAMP('2022-01-01')\",\n                \"starrocks\": \"TIMESTAMP('2022-01-01')\",\n                \"hive\": \"TIMESTAMP('2022-01-01')\",\n                \"doris\": \"TIMESTAMP('2022-01-01')\",\n            },\n        )\n\n        self.validate_all(\n            \"TIMESTAMP_TRUNC(x, DAY, 'UTC')\",\n            write={\n                \"\": \"TIMESTAMP_TRUNC(x, DAY, 'UTC')\",\n                \"duckdb\": \"DATE_TRUNC('DAY', x AT TIME ZONE 'UTC') AT TIME ZONE 'UTC'\",\n                \"materialize\": \"DATE_TRUNC('DAY', x, 'UTC')\",\n                \"presto\": \"DATE_TRUNC('DAY', x)\",\n                \"postgres\": \"DATE_TRUNC('DAY', x, 'UTC')\",\n                \"snowflake\": \"DATE_TRUNC('DAY', x)\",\n                \"databricks\": \"DATE_TRUNC('DAY', x)\",\n                \"clickhouse\": \"dateTrunc('DAY', x, 'UTC')\",\n            },\n        )\n\n        for unit in (\"DAY\", \"MONTH\", \"YEAR\"):\n            self.validate_all(\n                f\"{unit}(x)\",\n                read={\n                    dialect: f\"{unit}(x)\"\n                    for dialect in (\n                        \"bigquery\",\n                        \"drill\",\n                        \"duckdb\",\n                        \"presto\",\n                    )\n                },\n                write={\n                    dialect: f\"{unit}(x)\"\n                    for dialect in (\n                        \"bigquery\",\n                        \"drill\",\n                        \"duckdb\",\n                        \"mysql\",\n                        \"presto\",\n                        \"hive\",\n                        \"spark\",\n                    )\n                },\n            )\n            self.validate_all(\n                f\"{unit}(TS_OR_DS_TO_DATE(x))\",\n                write={\n                    dialect: f\"{unit}(x)\"\n                    for dialect in (\n                        \"mysql\",\n                        \"doris\",\n                        \"starrocks\",\n                    )\n                },\n            )\n            self.validate_all(\n                f\"{unit}(CAST(x AS DATE))\",\n                read={\n                    dialect: f\"{unit}(x)\"\n                    for dialect in (\n                        \"mysql\",\n                        \"doris\",\n                        \"starrocks\",\n                    )\n                },\n            )\n\n    def test_array(self):\n        self.validate_all(\n            \"ARRAY(0, 1, 2)\",\n            write={\n                \"bigquery\": \"[0, 1, 2]\",\n                \"duckdb\": \"[0, 1, 2]\",\n                \"presto\": \"ARRAY[0, 1, 2]\",\n                \"spark\": \"ARRAY(0, 1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_SIZE(x)\",\n            write={\n                \"bigquery\": \"ARRAY_LENGTH(x)\",\n                \"duckdb\": \"ARRAY_LENGTH(x)\",\n                \"drill\": \"REPEATED_COUNT(x)\",\n                \"presto\": \"CARDINALITY(x)\",\n                \"spark\": \"SIZE(x)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_SUM(ARRAY(1, 2))\",\n            write={\n                \"trino\": \"REDUCE(ARRAY[1, 2], 0, (acc, x) -> acc + x, acc -> acc)\",\n                \"duckdb\": \"LIST_SUM([1, 2])\",\n                \"hive\": \"ARRAY_SUM(ARRAY(1, 2))\",\n                \"presto\": \"ARRAY_SUM(ARRAY[1, 2])\",\n                \"spark\": \"AGGREGATE(ARRAY(1, 2), 0, (acc, x) -> acc + x, acc -> acc)\",\n            },\n        )\n        self.validate_all(\n            \"REDUCE(x, 0, (acc, x) -> acc + x, acc -> acc)\",\n            write={\n                \"trino\": \"REDUCE(x, 0, (acc, x) -> acc + x, acc -> acc)\",\n                \"duckdb\": \"REDUCE(x, 0, (acc, x) -> acc + x, acc -> acc)\",\n                \"hive\": \"REDUCE(x, 0, (acc, x) -> acc + x, acc -> acc)\",\n                \"spark\": \"AGGREGATE(x, 0, (acc, x) -> acc + x, acc -> acc)\",\n                \"presto\": \"REDUCE(x, 0, (acc, x) -> acc + x, acc -> acc)\",\n            },\n        )\n\n        self.validate_all(\n            \"ARRAY_INTERSECT(x, y)\",\n            read={\n                \"hive\": \"ARRAY_INTERSECT(x, y)\",\n                \"spark2\": \"ARRAY_INTERSECT(x, y)\",\n                \"spark\": \"ARRAY_INTERSECT(x, y)\",\n                \"databricks\": \"ARRAY_INTERSECT(x, y)\",\n                \"presto\": \"ARRAY_INTERSECT(x, y)\",\n                \"trino\": \"ARRAY_INTERSECT(x, y)\",\n                \"snowflake\": \"ARRAY_INTERSECTION(x, y)\",\n                \"starrocks\": \"ARRAY_INTERSECT(x, y)\",\n                \"duckdb\": \"ARRAY_INTERSECT(x, y)\",\n            },\n            write={\n                \"hive\": \"ARRAY_INTERSECT(x, y)\",\n                \"spark2\": \"ARRAY_INTERSECT(x, y)\",\n                \"spark\": \"ARRAY_INTERSECT(x, y)\",\n                \"databricks\": \"ARRAY_INTERSECT(x, y)\",\n                \"presto\": \"ARRAY_INTERSECT(x, y)\",\n                \"trino\": \"ARRAY_INTERSECT(x, y)\",\n                \"snowflake\": \"ARRAY_INTERSECTION(x, y)\",\n                \"starrocks\": \"ARRAY_INTERSECT(x, y)\",\n                \"duckdb\": \"ARRAY_INTERSECT(x, y)\",\n            },\n        )\n\n        self.validate_identity(\"SELECT ARRAY_INTERSECT(x, y, z)\")\n\n        self.validate_all(\n            \"ARRAY_REVERSE(x)\",\n            read={\n                \"clickhouse\": \"arrayReverse(x)\",\n                \"bigquery\": \"ARRAY_REVERSE(x)\",\n                \"snowflake\": \"ARRAY_REVERSE(x)\",\n                \"duckdb\": \"ARRAY_REVERSE(x)\",\n            },\n            write={\n                \"clickhouse\": \"arrayReverse(x)\",\n                \"bigquery\": \"ARRAY_REVERSE(x)\",\n                \"snowflake\": \"ARRAY_REVERSE(x)\",\n                \"duckdb\": \"ARRAY_REVERSE(x)\",\n            },\n        )\n\n        self.validate_all(\n            \"ARRAY_SLICE(x, 1, 3)\",\n            read={\n                \"clickhouse\": \"arraySlice(x, 1, 3)\",\n                \"bigquery\": \"ARRAY_SLICE(x, 1, 3)\",\n                \"snowflake\": \"ARRAY_SLICE(x, 1, 3)\",\n                \"duckdb\": \"ARRAY_SLICE(x, 1, 3)\",\n                \"spark2\": \"SLICE(x, 1, 3)\",\n                \"spark\": \"SLICE(x, 1, 3)\",\n                \"databricks\": \"SLICE(x, 1, 3)\",\n                \"presto\": \"SLICE(x, 1, 3)\",\n                \"trino\": \"SLICE(x, 1, 3)\",\n            },\n            write={\n                \"clickhouse\": \"arraySlice(x, 1, 3)\",\n                \"bigquery\": \"ARRAY_SLICE(x, 1, 3)\",\n                \"snowflake\": \"ARRAY_SLICE(x, 1, 3)\",\n                \"duckdb\": \"ARRAY_SLICE(x, 1, 3)\",\n                \"spark2\": \"SLICE(x, 1, 3)\",\n                \"spark\": \"SLICE(x, 1, 3)\",\n                \"databricks\": \"SLICE(x, 1, 3)\",\n                \"presto\": \"SLICE(x, 1, 3)\",\n                \"trino\": \"SLICE(x, 1, 3)\",\n            },\n        )\n\n        self.validate_all(\n            \"SORT_ARRAY(x)\",\n            write={\n                \"duckdb\": \"LIST_SORT(x)\",\n                \"hive\": \"SORT_ARRAY(x)\",\n                \"presto\": \"ARRAY_SORT(x)\",\n                \"snowflake\": \"ARRAY_SORT(x)\",\n                \"spark\": \"SORT_ARRAY(x)\",\n            },\n        )\n\n        # Test basic syntax transpilation for ARRAY_PREPEND\n        self.validate_all(\n            \"ARRAY_PREPEND(arr, x)\",\n            read={\n                \"duckdb\": \"LIST_PREPEND(x, arr)\",\n                \"postgres\": \"ARRAY_PREPEND(x, arr)\",\n            },\n            write={\n                \"duckdb\": \"LIST_PREPEND(x, arr)\",\n                \"postgres\": \"ARRAY_PREPEND(x, arr)\",\n            },\n        )\n\n        # Test basic syntax transpilation for array creation semantics\n        self.validate_all(\n            \"ARRAY_APPEND(arr, x)\",\n            read={\n                \"duckdb\": \"LIST_APPEND(arr, x)\",\n                \"postgres\": \"ARRAY_APPEND(arr, x)\",\n            },\n            write={\n                \"duckdb\": \"LIST_APPEND(arr, x)\",\n                \"postgres\": \"ARRAY_APPEND(arr, x)\",\n            },\n        )\n\n        # Test NULL propagation semantics: NULL-propagating dialects → array-creating dialects\n        for source_dialect in (\"snowflake\", \"databricks\", \"spark\"):\n            with self.subTest(f\"NULL propagation: {source_dialect} → DuckDB/PostgreSQL\"):\n                expr = parse_one(\"ARRAY_APPEND(arr, x)\", dialect=source_dialect)\n                self.assertEqual(\n                    expr.sql(\"duckdb\"),\n                    \"CASE WHEN arr IS NULL THEN NULL ELSE LIST_APPEND(arr, x) END\",\n                )\n                self.assertEqual(\n                    expr.sql(\"postgres\"),\n                    \"CASE WHEN arr IS NULL THEN NULL ELSE ARRAY_APPEND(arr, x) END\",\n                )\n\n        # Test array creation semantics: array-creating dialects → NULL-propagating dialects\n        for source_dialect, source_sql in (\n            (\"duckdb\", \"LIST_APPEND(arr, x)\"),\n            (\"postgres\", \"ARRAY_APPEND(arr, x)\"),\n        ):\n            with self.subTest(f\"Array creation: {source_dialect} → Snowflake/Databricks/Spark\"):\n                expr = parse_one(source_sql, dialect=source_dialect)\n                self.assertEqual(\n                    expr.sql(\"snowflake\"),\n                    \"ARRAY_APPEND(COALESCE(arr, []), x)\",\n                )\n                self.assertEqual(\n                    expr.sql(\"databricks\"),\n                    \"ARRAY_APPEND(COALESCE(arr, ARRAY()), x)\",\n                )\n                self.assertEqual(\n                    expr.sql(\"spark\"),\n                    \"ARRAY_APPEND(COALESCE(arr, ARRAY()), x)\",\n                )\n\n        # Test identity transpilation (should NOT add wrappers)\n        for dialect, sql in (\n            (\"duckdb\", \"LIST_APPEND(arr, x)\"),\n            (\"postgres\", \"ARRAY_APPEND(arr, x)\"),\n            (\"snowflake\", \"ARRAY_APPEND(arr, x)\"),\n            (\"databricks\", \"ARRAY_APPEND(arr, x)\"),\n            (\"spark\", \"ARRAY_APPEND(arr, x)\"),\n        ):\n            with self.subTest(f\"Identity: {dialect} → {dialect}\"):\n                expr = parse_one(sql, dialect=dialect)\n                self.assertEqual(expr.sql(dialect), sql)\n\n        # Test NULL propagation semantics for ARRAY_PREPEND: NULL-propagating dialects → array-creating dialects\n        for source_dialect in (\"snowflake\", \"databricks\", \"spark\"):\n            with self.subTest(\n                f\"ARRAY_PREPEND NULL propagation: {source_dialect} → DuckDB/PostgreSQL\"\n            ):\n                expr = parse_one(\"ARRAY_PREPEND(arr, x)\", dialect=source_dialect)\n                self.assertEqual(\n                    expr.sql(\"duckdb\"),\n                    \"CASE WHEN arr IS NULL THEN NULL ELSE LIST_PREPEND(x, arr) END\",\n                )\n                self.assertEqual(\n                    expr.sql(\"postgres\"),\n                    \"CASE WHEN arr IS NULL THEN NULL ELSE ARRAY_PREPEND(x, arr) END\",\n                )\n\n        # Test ARRAY_PREPEND array creation semantics: array-creating dialects → NULL-propagating dialects\n        for source_dialect, source_sql in (\n            (\"duckdb\", \"LIST_PREPEND(x, arr)\"),\n            (\"postgres\", \"ARRAY_PREPEND(x, arr)\"),\n        ):\n            with self.subTest(\n                f\"ARRAY_PREPEND array creation: {source_dialect} → Snowflake/Databricks/Spark\"\n            ):\n                expr = parse_one(source_sql, dialect=source_dialect)\n                self.assertEqual(\n                    expr.sql(\"snowflake\"),\n                    \"ARRAY_PREPEND(COALESCE(arr, []), x)\",\n                )\n                self.assertEqual(\n                    expr.sql(\"databricks\"),\n                    \"ARRAY_PREPEND(COALESCE(arr, ARRAY()), x)\",\n                )\n                self.assertEqual(\n                    expr.sql(\"spark\"),\n                    \"ARRAY_PREPEND(COALESCE(arr, ARRAY()), x)\",\n                )\n\n        # Test ARRAY_PREPEND identity transpilation (should NOT add wrappers)\n        for dialect, sql in (\n            (\"duckdb\", \"LIST_PREPEND(x, arr)\"),\n            (\"postgres\", \"ARRAY_PREPEND(x, arr)\"),\n            (\"snowflake\", \"ARRAY_PREPEND(arr, x)\"),\n            (\"databricks\", \"ARRAY_PREPEND(arr, x)\"),\n            (\"spark\", \"ARRAY_PREPEND(arr, x)\"),\n        ):\n            with self.subTest(f\"ARRAY_PREPEND identity: {dialect} → {dialect}\"):\n                expr = parse_one(sql, dialect=dialect)\n                self.assertEqual(expr.sql(dialect), sql)\n\n        # Test NULL propagation semantics for ARRAY_CAT: NULL-propagating dialects → NULL-skipping dialects\n        for source_dialect, source_sql in (\n            (\"snowflake\", \"ARRAY_CAT(arr1, arr2)\"),\n            (\"redshift\", \"ARRAY_CONCAT(arr1, arr2)\"),\n        ):\n            with self.subTest(f\"ARRAY_CAT NULL propagation: {source_dialect} → DuckDB/PostgreSQL\"):\n                expr = parse_one(source_sql, dialect=source_dialect)\n                self.assertEqual(\n                    expr.sql(\"duckdb\"),\n                    \"CASE WHEN arr1 IS NULL OR arr2 IS NULL THEN NULL ELSE LIST_CONCAT(arr1, arr2) END\",\n                )\n                self.assertEqual(\n                    expr.sql(\"postgres\"),\n                    \"CASE WHEN arr1 IS NULL OR arr2 IS NULL THEN NULL ELSE ARRAY_CAT(arr1, arr2) END\",\n                )\n\n        # Test NULL skipping semantics: NULL-skipping dialects → NULL-propagating dialects\n        for source_dialect, source_sql in (\n            (\"duckdb\", \"LIST_CONCAT(arr1, arr2)\"),\n            (\"postgres\", \"ARRAY_CAT(arr1, arr2)\"),\n        ):\n            with self.subTest(f\"ARRAY_CAT NULL skipping: {source_dialect} → Snowflake/Redshift\"):\n                expr = parse_one(source_sql, dialect=source_dialect)\n                self.assertEqual(\n                    expr.sql(\"snowflake\"),\n                    \"ARRAY_CAT(COALESCE(arr1, []), COALESCE(arr2, []))\",\n                )\n                self.assertEqual(\n                    expr.sql(\"redshift\"),\n                    \"ARRAY_CONCAT(COALESCE(arr1, ARRAY()), COALESCE(arr2, ARRAY()))\",\n                )\n\n        # Test ARRAY_CAT identity transpilation (should NOT add wrappers)\n        for dialect, sql in (\n            (\"duckdb\", \"LIST_CONCAT(arr1, arr2)\"),\n            (\"postgres\", \"ARRAY_CAT(arr1, arr2)\"),\n            (\"snowflake\", \"ARRAY_CAT(arr1, arr2)\"),\n            (\"redshift\", \"ARRAY_CONCAT(arr1, arr2)\"),\n        ):\n            with self.subTest(f\"ARRAY_CAT identity: {dialect} → {dialect}\"):\n                expr = parse_one(sql, dialect=dialect)\n                self.assertEqual(expr.sql(dialect), sql)\n\n        # Test ARRAY_CAT with variadic arguments (3+ arrays)\n        # Verify that ALL arguments are checked in NULL condition\n        for source_dialect, source_sql, expected in (\n            (\n                \"snowflake\",\n                \"ARRAY_CAT(arr1, arr2, arr3)\",\n                \"CASE WHEN arr1 IS NULL OR arr2 IS NULL OR arr3 IS NULL THEN NULL ELSE LIST_CONCAT(arr1, arr2, arr3) END\",\n            ),\n            (\n                \"redshift\",\n                \"ARRAY_CONCAT(arr1, arr2, arr3)\",\n                \"CASE WHEN arr1 IS NULL OR arr2 IS NULL OR arr3 IS NULL THEN NULL ELSE LIST_CONCAT(arr1, arr2, arr3) END\",\n            ),\n        ):\n            with self.subTest(f\"ARRAY_CAT variadic NULL propagation: {source_dialect} → DuckDB\"):\n                expr = parse_one(source_sql, dialect=source_dialect)\n                self.assertEqual(expr.sql(\"duckdb\"), expected)\n\n        # Test variadic COALESCE wrapping: ALL args should be wrapped\n        for source_dialect, source_sql, expected_snowflake, expected_redshift in (\n            (\n                \"duckdb\",\n                \"LIST_CONCAT(arr1, arr2, arr3)\",\n                \"ARRAY_CAT(COALESCE(arr1, []), ARRAY_CAT(COALESCE(arr2, []), COALESCE(arr3, [])))\",\n                \"ARRAY_CONCAT(COALESCE(arr1, ARRAY()), ARRAY_CONCAT(COALESCE(arr2, ARRAY()), COALESCE(arr3, ARRAY())))\",\n            ),\n            (\n                \"postgres\",\n                \"ARRAY_CAT(arr1, arr2, arr3)\",\n                \"ARRAY_CAT(COALESCE(arr1, []), ARRAY_CAT(COALESCE(arr2, []), COALESCE(arr3, [])))\",\n                \"ARRAY_CONCAT(COALESCE(arr1, ARRAY()), ARRAY_CONCAT(COALESCE(arr2, ARRAY()), COALESCE(arr3, ARRAY())))\",\n            ),\n        ):\n            with self.subTest(\n                f\"ARRAY_CAT variadic COALESCE wrapping: {source_dialect} → Snowflake/Redshift\"\n            ):\n                expr = parse_one(source_sql, dialect=source_dialect)\n                self.assertEqual(expr.sql(\"snowflake\"), expected_snowflake)\n                self.assertEqual(expr.sql(\"redshift\"), expected_redshift)\n\n        # Test PostgreSQL → Snowflake (2 args)\n        expr = parse_one(\"ARRAY_CAT(arr1, arr2)\", dialect=\"postgres\")\n        self.assertEqual(\n            expr.sql(\"snowflake\"),\n            \"ARRAY_CAT(COALESCE(arr1, []), COALESCE(arr2, []))\",\n        )\n\n        # Test edge case: array literal optimization (no wrapper needed)\n        expr = parse_one(\"ARRAY_CAT([1, 2], arr2)\", dialect=\"snowflake\")\n        self.assertEqual(expr.sql(\"duckdb\"), \"LIST_CONCAT([1, 2], arr2)\")\n\n        # Test edge case: single argument\n        expr = parse_one(\"ARRAY_CAT(arr1)\", dialect=\"snowflake\")\n        self.assertEqual(expr.sql(\"duckdb\"), \"LIST_CONCAT(arr1)\")\n\n        # Test ARRAY_MAX transpilation across dialects\n        self.validate_all(\n            \"ARRAY_MAX(x)\",\n            read={\n                \"athena\": \"array_max(x)\",\n                \"clickhouse\": \"arrayMax(x)\",\n                \"databricks\": \"array_max(x)\",\n                \"duckdb\": \"list_max(x)\",\n                \"presto\": \"array_max(x)\",\n                \"snowflake\": \"ARRAY_MAX(x)\",\n                \"spark\": \"array_max(x)\",\n                \"trino\": \"array_max(x)\",\n            },\n            write={\n                \"athena\": \"ARRAY_MAX(x)\",\n                \"clickhouse\": \"arrayMax(x)\",\n                \"databricks\": \"ARRAY_MAX(x)\",\n                \"duckdb\": \"LIST_MAX(x)\",\n                \"presto\": \"ARRAY_MAX(x)\",\n                \"snowflake\": \"ARRAY_MAX(x)\",\n                \"spark\": \"ARRAY_MAX(x)\",\n                \"trino\": \"ARRAY_MAX(x)\",\n            },\n        )\n\n        # Test ARRAY_MIN transpilation across dialects\n        self.validate_all(\n            \"ARRAY_MIN(x)\",\n            read={\n                \"athena\": \"array_min(x)\",\n                \"clickhouse\": \"arrayMin(x)\",\n                \"databricks\": \"array_min(x)\",\n                \"duckdb\": \"list_min(x)\",\n                \"presto\": \"array_min(x)\",\n                \"snowflake\": \"ARRAY_MIN(x)\",\n                \"spark\": \"array_min(x)\",\n                \"trino\": \"array_min(x)\",\n            },\n            write={\n                \"athena\": \"ARRAY_MIN(x)\",\n                \"clickhouse\": \"arrayMin(x)\",\n                \"databricks\": \"ARRAY_MIN(x)\",\n                \"duckdb\": \"LIST_MIN(x)\",\n                \"presto\": \"ARRAY_MIN(x)\",\n                \"snowflake\": \"ARRAY_MIN(x)\",\n                \"spark\": \"ARRAY_MIN(x)\",\n                \"trino\": \"ARRAY_MIN(x)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))\",\n            read={\n                \"spark\": \"SELECT array_except(array(1, 2, 3), array(2))\",\n                \"databricks\": \"SELECT array_except(array(1, 2, 3), array(2))\",\n            },\n            write={\n                \"snowflake\": \"SELECT ARRAY_EXCEPT([1, 2, 3], [2])\",\n                \"spark\": \"SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))\",\n                \"databricks\": \"SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))\",\n                \"trino\": \"SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])\",\n                \"presto\": \"SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])\",\n                \"athena\": \"SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])\",\n                \"duckdb\": \"SELECT CASE WHEN [1, 2, 3] IS NULL OR [2] IS NULL THEN NULL ELSE LIST_FILTER(LIST_DISTINCT([1, 2, 3]), e -> LENGTH(LIST_FILTER([2], x -> x IS NOT DISTINCT FROM e)) = 0) END\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ARRAY_POSITION(ARRAY(1, 2, 3), 2)\",\n            read={\n                \"spark\": \"SELECT array_position(array(1, 2, 3), 2)\",\n                \"databricks\": \"SELECT array_position(array(1, 2, 3), 2)\",\n                \"trino\": \"SELECT array_position(array[1, 2, 3], 2)\",\n                \"presto\": \"SELECT array_position(array[1, 2, 3], 2)\",\n                \"athena\": \"SELECT array_position(array[1, 2, 3], 2)\",\n            },\n            write={\n                \"snowflake\": \"SELECT ARRAY_POSITION(2, [1, 2, 3])\",\n                \"spark\": \"SELECT ARRAY_POSITION(ARRAY(1, 2, 3), 2)\",\n                \"databricks\": \"SELECT ARRAY_POSITION(ARRAY(1, 2, 3), 2)\",\n                \"trino\": \"SELECT ARRAY_POSITION(ARRAY[1, 2, 3], 2)\",\n                \"presto\": \"SELECT ARRAY_POSITION(ARRAY[1, 2, 3], 2)\",\n                \"athena\": \"SELECT ARRAY_POSITION(ARRAY[1, 2, 3], 2)\",\n                \"duckdb\": \"SELECT ARRAY_POSITION([1, 2, 3], 2)\",\n            },\n        )\n\n    def test_order_by(self):\n        self.validate_identity(\n            \"SELECT c FROM t ORDER BY a, b,\",\n            \"SELECT c FROM t ORDER BY a, b\",\n        )\n\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"bigquery\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n                \"duckdb\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname NULLS FIRST\",\n                \"presto\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname NULLS FIRST\",\n                \"hive\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n                \"spark\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            },\n        )\n\n        order_by_all_sql = \"SELECT * FROM t ORDER BY ALL\"\n        self.validate_identity(order_by_all_sql).find(exp.Ordered).this.assert_is(exp.Column)\n\n        for dialect in (\"duckdb\", \"spark\", \"databricks\"):\n            with self.subTest(f\"Testing ORDER BY ALL in {dialect}\"):\n                parse_one(order_by_all_sql, read=dialect).find(exp.Ordered).this.assert_is(exp.Var)\n\n    def test_json(self):\n        self.validate_all(\n            \"\"\"JSON_EXTRACT(x, '$[\"a b\"]')\"\"\",\n            write={\n                \"\": \"\"\"JSON_EXTRACT(x, '$[\"a b\"]')\"\"\",\n                \"bigquery\": \"\"\"JSON_EXTRACT(x, '$[\\\\'a b\\\\']')\"\"\",\n                \"clickhouse\": \"JSONExtractString(x, 'a b')\",\n                \"duckdb\": \"\"\"x -> '$.\"a b\"'\"\"\",\n                \"mysql\": \"\"\"JSON_EXTRACT(x, '$.\"a b\"')\"\"\",\n                \"postgres\": \"JSON_EXTRACT_PATH(x, 'a b')\",\n                \"presto\": \"\"\"JSON_EXTRACT(x, '$[\"a b\"]')\"\"\",\n                \"redshift\": \"JSON_EXTRACT_PATH_TEXT(x, 'a b')\",\n                \"snowflake\": \"\"\"GET_PATH(PARSE_JSON(x), '[\"a b\"]')\"\"\",\n                \"spark\": \"\"\"GET_JSON_OBJECT(x, '$[\\\\'a b\\\\']')\"\"\",\n                \"sqlite\": \"\"\"x -> '$.\"a b\"'\"\"\",\n                \"trino\": \"\"\"JSON_EXTRACT(x, '$[\"a b\"]')\"\"\",\n                \"tsql\": \"\"\"ISNULL(JSON_QUERY(x, '$.\"a b\"'), JSON_VALUE(x, '$.\"a b\"'))\"\"\",\n            },\n        )\n        self.validate_all(\n            \"JSON_EXTRACT(x, '$.y')\",\n            read={\n                \"bigquery\": \"JSON_EXTRACT(x, '$.y')\",\n                \"duckdb\": \"x -> 'y'\",\n                \"doris\": \"JSON_EXTRACT(x, '$.y')\",\n                \"mysql\": \"JSON_EXTRACT(x, '$.y')\",\n                \"postgres\": \"x->'y'\",\n                \"presto\": \"JSON_EXTRACT(x, '$.y')\",\n                \"snowflake\": \"GET_PATH(x, 'y')\",\n                \"sqlite\": \"x -> '$.y'\",\n                \"starrocks\": \"x -> '$.y'\",\n            },\n            write={\n                \"bigquery\": \"JSON_EXTRACT(x, '$.y')\",\n                \"clickhouse\": \"JSONExtractString(x, 'y')\",\n                \"doris\": \"JSON_EXTRACT(x, '$.y')\",\n                \"duckdb\": \"x -> '$.y'\",\n                \"mysql\": \"JSON_EXTRACT(x, '$.y')\",\n                \"oracle\": \"JSON_EXTRACT(x, '$.y')\",\n                \"postgres\": \"JSON_EXTRACT_PATH(x, 'y')\",\n                \"presto\": \"JSON_EXTRACT(x, '$.y')\",\n                \"snowflake\": \"GET_PATH(PARSE_JSON(x), 'y')\",\n                \"spark\": \"GET_JSON_OBJECT(x, '$.y')\",\n                \"sqlite\": \"x -> '$.y'\",\n                \"starrocks\": \"x -> '$.y'\",\n                \"tsql\": \"ISNULL(JSON_QUERY(x, '$.y'), JSON_VALUE(x, '$.y'))\",\n            },\n        )\n        self.validate_all(\n            \"JSON_EXTRACT_SCALAR(x, '$.y')\",\n            read={\n                \"bigquery\": \"JSON_EXTRACT_SCALAR(x, '$.y')\",\n                \"clickhouse\": \"JSONExtractString(x, 'y')\",\n                \"duckdb\": \"x ->> 'y'\",\n                \"postgres\": \"x ->> 'y'\",\n                \"presto\": \"JSON_EXTRACT_SCALAR(x, '$.y')\",\n                \"redshift\": \"JSON_EXTRACT_PATH_TEXT(x, 'y')\",\n                \"spark\": \"GET_JSON_OBJECT(x, '$.y')\",\n                \"snowflake\": \"JSON_EXTRACT_PATH_TEXT(x, 'y')\",\n                \"sqlite\": \"x ->> '$.y'\",\n            },\n            write={\n                \"bigquery\": \"JSON_EXTRACT_SCALAR(x, '$.y')\",\n                \"clickhouse\": \"JSONExtractString(x, 'y')\",\n                \"duckdb\": \"x ->> '$.y'\",\n                \"postgres\": \"JSON_EXTRACT_PATH_TEXT(x, 'y')\",\n                \"presto\": \"JSON_EXTRACT_SCALAR(x, '$.y')\",\n                \"redshift\": \"JSON_EXTRACT_PATH_TEXT(x, 'y')\",\n                \"snowflake\": \"JSON_EXTRACT_PATH_TEXT(x, 'y')\",\n                \"spark\": \"GET_JSON_OBJECT(x, '$.y')\",\n                \"sqlite\": \"x ->> '$.y'\",\n                \"tsql\": \"ISNULL(JSON_QUERY(x, '$.y'), JSON_VALUE(x, '$.y'))\",\n            },\n        )\n        self.validate_all(\n            \"JSON_EXTRACT(x, '$.y[0].z')\",\n            read={\n                \"bigquery\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"duckdb\": \"x -> '$.y[0].z'\",\n                \"doris\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"mysql\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"presto\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"snowflake\": \"GET_PATH(x, 'y[0].z')\",\n                \"sqlite\": \"x -> '$.y[0].z'\",\n                \"starrocks\": \"x -> '$.y[0].z'\",\n            },\n            write={\n                \"bigquery\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"clickhouse\": \"JSONExtractString(x, 'y', 1, 'z')\",\n                \"doris\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"duckdb\": \"x -> '$.y[0].z'\",\n                \"mysql\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"oracle\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"postgres\": \"JSON_EXTRACT_PATH(x, 'y', '0', 'z')\",\n                \"presto\": \"JSON_EXTRACT(x, '$.y[0].z')\",\n                \"redshift\": \"JSON_EXTRACT_PATH_TEXT(x, 'y', '0', 'z')\",\n                \"snowflake\": \"GET_PATH(PARSE_JSON(x), 'y[0].z')\",\n                \"spark\": \"GET_JSON_OBJECT(x, '$.y[0].z')\",\n                \"sqlite\": \"x -> '$.y[0].z'\",\n                \"starrocks\": \"x -> '$.y[0].z'\",\n                \"tsql\": \"ISNULL(JSON_QUERY(x, '$.y[0].z'), JSON_VALUE(x, '$.y[0].z'))\",\n            },\n        )\n        self.validate_all(\n            \"JSON_EXTRACT_SCALAR(x, '$.y[0].z')\",\n            read={\n                \"bigquery\": \"JSON_EXTRACT_SCALAR(x, '$.y[0].z')\",\n                \"clickhouse\": \"JSONExtractString(x, 'y', 1, 'z')\",\n                \"duckdb\": \"x ->> '$.y[0].z'\",\n                \"presto\": \"JSON_EXTRACT_SCALAR(x, '$.y[0].z')\",\n                \"snowflake\": \"JSON_EXTRACT_PATH_TEXT(x, 'y[0].z')\",\n                \"spark\": 'GET_JSON_OBJECT(x, \"$.y[0].z\")',\n                \"sqlite\": \"x ->> '$.y[0].z'\",\n            },\n            write={\n                \"bigquery\": \"JSON_EXTRACT_SCALAR(x, '$.y[0].z')\",\n                \"clickhouse\": \"JSONExtractString(x, 'y', 1, 'z')\",\n                \"duckdb\": \"x ->> '$.y[0].z'\",\n                \"postgres\": \"JSON_EXTRACT_PATH_TEXT(x, 'y', '0', 'z')\",\n                \"presto\": \"JSON_EXTRACT_SCALAR(x, '$.y[0].z')\",\n                \"redshift\": \"JSON_EXTRACT_PATH_TEXT(x, 'y', '0', 'z')\",\n                \"snowflake\": \"JSON_EXTRACT_PATH_TEXT(x, 'y[0].z')\",\n                \"spark\": \"GET_JSON_OBJECT(x, '$.y[0].z')\",\n                \"sqlite\": \"x ->> '$.y[0].z'\",\n                \"tsql\": \"ISNULL(JSON_QUERY(x, '$.y[0].z'), JSON_VALUE(x, '$.y[0].z'))\",\n            },\n        )\n        self.validate_all(\n            \"JSON_EXTRACT(x, '$.y[*]')\",\n            write={\n                \"bigquery\": UnsupportedError,\n                \"clickhouse\": UnsupportedError,\n                \"duckdb\": \"x -> '$.y[*]'\",\n                \"mysql\": \"JSON_EXTRACT(x, '$.y[*]')\",\n                \"postgres\": UnsupportedError,\n                \"presto\": \"JSON_EXTRACT(x, '$.y[*]')\",\n                \"redshift\": UnsupportedError,\n                \"snowflake\": UnsupportedError,\n                \"spark\": \"GET_JSON_OBJECT(x, '$.y[*]')\",\n                \"sqlite\": UnsupportedError,\n                \"tsql\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"JSON_EXTRACT(x, '$.y[*]')\",\n            write={\n                \"bigquery\": \"JSON_EXTRACT(x, '$.y')\",\n                \"clickhouse\": \"JSONExtractString(x, 'y')\",\n                \"postgres\": \"JSON_EXTRACT_PATH(x, 'y')\",\n                \"redshift\": \"JSON_EXTRACT_PATH_TEXT(x, 'y')\",\n                \"snowflake\": \"GET_PATH(PARSE_JSON(x), 'y')\",\n                \"sqlite\": \"x -> '$.y'\",\n                \"tsql\": \"ISNULL(JSON_QUERY(x, '$.y'), JSON_VALUE(x, '$.y'))\",\n            },\n        )\n        self.validate_all(\n            \"JSON_EXTRACT(x, '$.y.*')\",\n            write={\n                \"bigquery\": UnsupportedError,\n                \"clickhouse\": UnsupportedError,\n                \"duckdb\": \"x -> '$.y.*'\",\n                \"mysql\": \"JSON_EXTRACT(x, '$.y.*')\",\n                \"postgres\": UnsupportedError,\n                \"presto\": \"JSON_EXTRACT(x, '$.y.*')\",\n                \"redshift\": UnsupportedError,\n                \"snowflake\": UnsupportedError,\n                \"spark\": UnsupportedError,\n                \"sqlite\": UnsupportedError,\n                \"tsql\": UnsupportedError,\n            },\n        )\n\n        for dialect in (\"duckdb\", \"starrocks\"):\n            with self.subTest(f\"Generating json extraction with digit-prefixed key ({dialect})\"):\n                self.assertEqual(\n                    parse_one(\"\"\"select '{\"0\": \"v\"}' -> '0'\"\"\", read=dialect).sql(dialect=dialect),\n                    \"\"\"SELECT '{\"0\": \"v\"}' -> '0'\"\"\",\n                )\n\n    def test_cross_join(self):\n        self.validate_all(\n            \"SELECT a FROM x CROSS JOIN UNNEST(y) AS t (a)\",\n            write={\n                \"drill\": \"SELECT a FROM x CROSS JOIN UNNEST(y) AS t(a)\",\n                \"presto\": \"SELECT a FROM x CROSS JOIN UNNEST(y) AS t(a)\",\n                \"spark\": \"SELECT a FROM x LATERAL VIEW EXPLODE(y) t AS a\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a, b FROM x CROSS JOIN UNNEST(y, z) AS t (a, b)\",\n            write={\n                \"drill\": \"SELECT a, b FROM x CROSS JOIN UNNEST(y, z) AS t(a, b)\",\n                \"presto\": \"SELECT a, b FROM x CROSS JOIN UNNEST(y, z) AS t(a, b)\",\n                \"spark\": \"SELECT a, b FROM x LATERAL VIEW INLINE(ARRAYS_ZIP(y, z)) t AS a, b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM x CROSS JOIN UNNEST(y) WITH ORDINALITY AS t (a)\",\n            write={\n                \"presto\": \"SELECT a FROM x CROSS JOIN UNNEST(y) WITH ORDINALITY AS t(a)\",\n                \"spark2\": \"SELECT a FROM x LATERAL VIEW POSEXPLODE(y) t AS pos, a\",\n                \"spark\": \"SELECT a FROM x LATERAL VIEW POSEXPLODE(y) t AS pos, a\",\n                \"databricks\": \"SELECT a FROM x LATERAL VIEW POSEXPLODE(y) t AS pos, a\",\n            },\n        )\n\n        # UNNEST without column alias\n        self.validate_all(\n            \"SELECT * FROM x CROSS JOIN UNNEST(y) AS t\",\n            write={\n                \"presto\": \"SELECT * FROM x CROSS JOIN UNNEST(y) AS t\",\n                \"spark\": UnsupportedError,\n                \"databricks\": UnsupportedError,\n            },\n        )\n\n        # UNNEST MAP Object into multiple columns, using single alias\n        self.validate_all(\n            \"SELECT a, b FROM x CROSS JOIN UNNEST(y) AS t (a, b)\",\n            write={\n                \"presto\": \"SELECT a, b FROM x CROSS JOIN UNNEST(y) AS t(a, b)\",\n                \"spark\": \"SELECT a, b FROM x LATERAL VIEW EXPLODE(y) t AS a, b\",\n                \"hive\": \"SELECT a, b FROM x LATERAL VIEW EXPLODE(y) t AS a, b\",\n            },\n        )\n\n        # Unnest multiple Expr into respective mapped alias\n        self.validate_all(\n            \"SELECT numbers, animals, n, a FROM (SELECT ARRAY(2, 5) AS numbers, ARRAY('dog', 'cat', 'bird') AS animals UNION ALL SELECT ARRAY(7, 8, 9), ARRAY('cow', 'pig')) AS x CROSS JOIN UNNEST(numbers, animals) AS t(n, a)\",\n            write={\n                \"presto\": \"SELECT numbers, animals, n, a FROM (SELECT ARRAY[2, 5] AS numbers, ARRAY['dog', 'cat', 'bird'] AS animals UNION ALL SELECT ARRAY[7, 8, 9], ARRAY['cow', 'pig']) AS x CROSS JOIN UNNEST(numbers, animals) AS t(n, a)\",\n                \"spark\": \"SELECT numbers, animals, n, a FROM (SELECT ARRAY(2, 5) AS numbers, ARRAY('dog', 'cat', 'bird') AS animals UNION ALL SELECT ARRAY(7, 8, 9), ARRAY('cow', 'pig')) AS x LATERAL VIEW INLINE(ARRAYS_ZIP(numbers, animals)) t AS n, a\",\n                \"hive\": UnsupportedError,\n            },\n        )\n\n        # Unnest column to more then 2 alias (STRUCT)\n        self.validate_all(\n            \"SELECT a, b, c, d, e FROM x CROSS JOIN UNNEST(y) AS t(a, b, c, d)\",\n            write={\n                \"presto\": \"SELECT a, b, c, d, e FROM x CROSS JOIN UNNEST(y) AS t(a, b, c, d)\",\n                \"spark\": UnsupportedError,\n                \"hive\": UnsupportedError,\n            },\n        )\n\n    def test_multiple_chained_unnest(self):\n        self.validate_all(\n            \"SELECT * FROM x CROSS JOIN UNNEST(a) AS j(lista) CROSS JOIN UNNEST(b) AS k(listb) CROSS JOIN UNNEST(c) AS l(listc)\",\n            write={\n                \"presto\": \"SELECT * FROM x CROSS JOIN UNNEST(a) AS j(lista) CROSS JOIN UNNEST(b) AS k(listb) CROSS JOIN UNNEST(c) AS l(listc)\",\n                \"spark\": \"SELECT * FROM x LATERAL VIEW EXPLODE(a) j AS lista LATERAL VIEW EXPLODE(b) k AS listb LATERAL VIEW EXPLODE(c) l AS listc\",\n                \"hive\": \"SELECT * FROM x LATERAL VIEW EXPLODE(a) j AS lista LATERAL VIEW EXPLODE(b) k AS listb LATERAL VIEW EXPLODE(c) l AS listc\",\n            },\n        )\n\n    def test_lateral_subquery(self):\n        self.validate_identity(\n            \"SELECT art FROM tbl1 INNER JOIN LATERAL (SELECT art FROM tbl2) AS tbl2 ON tbl1.art = tbl2.art\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM tbl AS t LEFT JOIN LATERAL (SELECT * FROM b WHERE b.t_id = t.t_id) AS t ON TRUE\"\n        )\n\n    def test_set_operators(self):\n        self.validate_all(\n            \"SELECT * FROM a UNION SELECT * FROM b ORDER BY x LIMIT 1\",\n            write={\n                \"\": \"SELECT * FROM a UNION SELECT * FROM b ORDER BY x LIMIT 1\",\n                \"clickhouse\": \"SELECT * FROM (SELECT * FROM a UNION DISTINCT SELECT * FROM b) AS _l_0 ORDER BY x NULLS FIRST LIMIT 1\",\n                \"tsql\": \"SELECT TOP 1 * FROM (SELECT * FROM a UNION SELECT * FROM b) AS _l_0 ORDER BY x\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM a UNION SELECT * FROM b\",\n            read={\n                \"bigquery\": \"SELECT * FROM a UNION DISTINCT SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a UNION DISTINCT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a UNION SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a UNION SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a UNION SELECT * FROM b\",\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM a UNION DISTINCT SELECT * FROM b\",\n                \"drill\": \"SELECT * FROM a UNION SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a UNION SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a UNION SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a UNION SELECT * FROM b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n            read={\n                \"bigquery\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a UNION ALL SELECT * FROM b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n            read={\n                \"bigquery\": \"SELECT * FROM a INTERSECT DISTINCT SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a INTERSECT DISTINCT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM a INTERSECT DISTINCT SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a INTERSECT DISTINCT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n            read={\n                \"bigquery\": \"SELECT * FROM a EXCEPT DISTINCT SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a EXCEPT DISTINCT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n            },\n            write={\n                \"bigquery\": \"SELECT * FROM a EXCEPT DISTINCT SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a EXCEPT DISTINCT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a UNION DISTINCT SELECT * FROM b\",\n            write={\n                \"bigquery\": \"SELECT * FROM a UNION DISTINCT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a UNION SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a UNION SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a UNION SELECT * FROM b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a INTERSECT DISTINCT SELECT * FROM b\",\n            write={\n                \"bigquery\": \"SELECT * FROM a INTERSECT DISTINCT SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a INTERSECT DISTINCT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a INTERSECT ALL SELECT * FROM b\",\n            write={\n                \"bigquery\": \"SELECT * FROM a INTERSECT ALL SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a INTERSECT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a INTERSECT ALL SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a INTERSECT ALL SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a INTERSECT ALL SELECT * FROM b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a EXCEPT DISTINCT SELECT * FROM b\",\n            write={\n                \"bigquery\": \"SELECT * FROM a EXCEPT DISTINCT SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a EXCEPT DISTINCT SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a EXCEPT SELECT * FROM b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a EXCEPT ALL SELECT * FROM b\",\n            read={\n                \"bigquery\": \"SELECT * FROM a EXCEPT ALL SELECT * FROM b\",\n                \"clickhouse\": \"SELECT * FROM a EXCEPT ALL SELECT * FROM b\",\n                \"duckdb\": \"SELECT * FROM a EXCEPT ALL SELECT * FROM b\",\n                \"presto\": \"SELECT * FROM a EXCEPT ALL SELECT * FROM b\",\n                \"spark\": \"SELECT * FROM a EXCEPT ALL SELECT * FROM b\",\n            },\n        )\n\n    def test_operators(self):\n        self.validate_identity(\"some.column LIKE 'foo' || another.column || 'bar' || LOWER(x)\")\n        self.validate_identity(\"some.column LIKE 'foo' + another.column + 'bar'\")\n\n        self.validate_all(\"LIKE(x, 'z')\", write={\"\": \"'z' LIKE x\"})\n        self.validate_all(\n            \"CONCAT(a, b, c)\",\n            write={\n                \"\": \"CONCAT(a, b, c)\",\n                \"redshift\": \"a || b || c\",\n                \"sqlite\": \"a || b || c\",\n            },\n        )\n        self.validate_all(\n            \"x ILIKE '%y'\",\n            read={\n                \"clickhouse\": \"x ILIKE '%y'\",\n                \"duckdb\": \"x ILIKE '%y'\",\n                \"postgres\": \"x ILIKE '%y'\",\n                \"snowflake\": \"x ILIKE '%y'\",\n            },\n            write={\n                \"bigquery\": \"LOWER(x) LIKE LOWER('%y')\",\n                \"clickhouse\": \"x ILIKE '%y'\",\n                \"drill\": \"x `ILIKE` '%y'\",\n                \"duckdb\": \"x ILIKE '%y'\",\n                \"hive\": \"LOWER(x) LIKE LOWER('%y')\",\n                \"mysql\": \"LOWER(x) LIKE LOWER('%y')\",\n                \"oracle\": \"LOWER(x) LIKE LOWER('%y')\",\n                \"postgres\": \"x ILIKE '%y'\",\n                \"presto\": \"LOWER(x) LIKE LOWER('%y')\",\n                \"snowflake\": \"x ILIKE '%y'\",\n                \"spark\": \"x ILIKE '%y'\",\n                \"sqlite\": \"LOWER(x) LIKE LOWER('%y')\",\n                \"starrocks\": \"LOWER(x) LIKE LOWER('%y')\",\n                \"trino\": \"LOWER(x) LIKE LOWER('%y')\",\n                \"doris\": \"LOWER(x) LIKE LOWER('%y')\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle)\",\n            read={\n                \"athena\": \"POSITION(needle in haystack)\",\n                \"clickhouse\": \"POSITION(needle in haystack)\",\n                \"databricks\": \"POSITION(needle in haystack)\",\n                \"drill\": \"POSITION(needle in haystack)\",\n                \"duckdb\": \"POSITION(needle in haystack)\",\n                \"materialize\": \"POSITION(needle in haystack)\",\n                \"mysql\": \"POSITION(needle in haystack)\",\n                \"postgres\": \"POSITION(needle in haystack)\",\n                \"presto\": \"POSITION(needle in haystack)\",\n                \"redshift\": \"POSITION(needle in haystack)\",\n                \"risingwave\": \"POSITION(needle in haystack)\",\n                \"snowflake\": \"POSITION(needle in haystack)\",\n                \"spark\": \"POSITION(needle in haystack)\",\n                \"spark2\": \"POSITION(needle in haystack)\",\n                \"teradata\": \"POSITION(needle in haystack)\",\n                \"trino\": \"POSITION(needle in haystack)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle)\",\n            read={\n                \"clickhouse\": \"POSITION(haystack, needle)\",\n                \"databricks\": \"POSITION(needle, haystack)\",\n                \"snowflake\": \"POSITION(needle, haystack)\",\n                \"spark2\": \"POSITION(needle, haystack)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle)\",\n            read={\n                \"athena\": \"STRPOS(haystack, needle)\",\n                \"bigquery\": \"STRPOS(haystack, needle)\",\n                \"drill\": \"STRPOS(haystack, needle)\",\n                \"duckdb\": \"STRPOS(haystack, needle)\",\n                \"postgres\": \"STRPOS(haystack, needle)\",\n                \"presto\": \"STRPOS(haystack, needle)\",\n                \"redshift\": \"STRPOS(haystack, needle)\",\n                \"trino\": \"STRPOS(haystack, needle)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle)\",\n            read={\n                \"bigquery\": \"INSTR(haystack, needle)\",\n                \"databricks\": \"INSTR(haystack, needle)\",\n                \"doris\": \"INSTR(haystack, needle)\",\n                \"duckdb\": \"INSTR(haystack, needle)\",\n                \"hive\": \"INSTR(haystack, needle)\",\n                \"mysql\": \"INSTR(haystack, needle)\",\n                \"oracle\": \"INSTR(haystack, needle)\",\n                \"spark\": \"INSTR(haystack, needle)\",\n                \"spark2\": \"INSTR(haystack, needle)\",\n                \"sqlite\": \"INSTR(haystack, needle)\",\n                \"starrocks\": \"INSTR(haystack, needle)\",\n                \"teradata\": \"INSTR(haystack, needle)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle)\",\n            read={\n                \"clickhouse\": \"LOCATE(needle, haystack)\",\n                \"databricks\": \"LOCATE(needle, haystack)\",\n                \"doris\": \"LOCATE(needle, haystack)\",\n                \"hive\": \"LOCATE(needle, haystack)\",\n                \"mysql\": \"LOCATE(needle, haystack)\",\n                \"spark\": \"LOCATE(needle, haystack)\",\n                \"spark2\": \"LOCATE(needle, haystack)\",\n                \"starrocks\": \"LOCATE(needle, haystack)\",\n                \"teradata\": \"LOCATE(needle, haystack)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle)\",\n            read={\n                \"athena\": \"CHARINDEX(needle, haystack)\",\n                \"databricks\": \"CHARINDEX(needle, haystack)\",\n                \"snowflake\": \"CHARINDEX(needle, haystack)\",\n                \"tsql\": \"CHARINDEX(needle, haystack)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle)\",\n            read={\n                \"tableau\": \"FIND(haystack, needle)\",\n            },\n            write={\n                \"athena\": \"STRPOS(haystack, needle)\",\n                \"bigquery\": \"INSTR(haystack, needle)\",\n                \"clickhouse\": \"POSITION(haystack, needle)\",\n                \"databricks\": \"LOCATE(needle, haystack)\",\n                \"doris\": \"LOCATE(needle, haystack)\",\n                \"drill\": \"STRPOS(haystack, needle)\",\n                \"duckdb\": \"STRPOS(haystack, needle)\",\n                \"hive\": \"LOCATE(needle, haystack)\",\n                \"materialize\": \"POSITION(needle IN haystack)\",\n                \"mysql\": \"LOCATE(needle, haystack)\",\n                \"oracle\": \"INSTR(haystack, needle)\",\n                \"postgres\": \"POSITION(needle IN haystack)\",\n                \"presto\": \"STRPOS(haystack, needle)\",\n                \"redshift\": \"POSITION(needle IN haystack)\",\n                \"risingwave\": \"POSITION(needle IN haystack)\",\n                \"snowflake\": \"CHARINDEX(needle, haystack)\",\n                \"spark\": \"LOCATE(needle, haystack)\",\n                \"spark2\": \"LOCATE(needle, haystack)\",\n                \"sqlite\": \"INSTR(haystack, needle)\",\n                \"tableau\": \"FIND(haystack, needle)\",\n                \"teradata\": \"INSTR(haystack, needle)\",\n                \"trino\": \"STRPOS(haystack, needle)\",\n                \"tsql\": \"CHARINDEX(needle, haystack)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle, position)\",\n            read={\n                \"clickhouse\": \"POSITION(haystack, needle, position)\",\n                \"databricks\": \"POSITION(needle, haystack, position)\",\n                \"snowflake\": \"POSITION(needle, haystack, position)\",\n                \"spark\": \"POSITION(needle, haystack, position)\",\n                \"spark2\": \"POSITION(needle, haystack, position)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle, position)\",\n            read={\n                \"doris\": \"LOCATE(needle, haystack, position)\",\n                \"hive\": \"LOCATE(needle, haystack, position)\",\n                \"mysql\": \"LOCATE(needle, haystack, position)\",\n                \"spark\": \"LOCATE(needle, haystack, position)\",\n                \"spark2\": \"LOCATE(needle, haystack, position)\",\n                \"starrocks\": \"LOCATE(needle, haystack, position)\",\n                \"teradata\": \"LOCATE(needle, haystack, position)\",\n                \"clickhouse\": \"LOCATE(needle, haystack, position)\",\n                \"databricks\": \"LOCATE(needle, haystack, position)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle, position)\",\n            read={\n                \"bigquery\": \"INSTR(haystack, needle, position)\",\n                \"doris\": \"INSTR(haystack, needle, position)\",\n                \"oracle\": \"INSTR(haystack, needle, position)\",\n                \"teradata\": \"INSTR(haystack, needle, position)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle, position)\",\n            read={\n                \"databricks\": \"CHARINDEX(needle, haystack, position)\",\n                \"snowflake\": \"CHARINDEX(needle, haystack, position)\",\n                \"tsql\": \"CHARINDEX(needle, haystack, position)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle, position)\",\n            write={\n                \"athena\": \"IF(STRPOS(SUBSTRING(haystack, position), needle) = 0, 0, STRPOS(SUBSTRING(haystack, position), needle) + position - 1)\",\n                \"bigquery\": \"INSTR(haystack, needle, position)\",\n                \"clickhouse\": \"POSITION(haystack, needle, position)\",\n                \"databricks\": \"LOCATE(needle, haystack, position)\",\n                \"doris\": \"LOCATE(needle, haystack, position)\",\n                \"drill\": \"`IF`(STRPOS(SUBSTRING(haystack, position), needle) = 0, 0, STRPOS(SUBSTRING(haystack, position), needle) + position - 1)\",\n                \"duckdb\": \"CASE WHEN STRPOS(SUBSTRING(haystack, position), needle) = 0 THEN 0 ELSE STRPOS(SUBSTRING(haystack, position), needle) + position - 1 END\",\n                \"hive\": \"LOCATE(needle, haystack, position)\",\n                \"materialize\": \"CASE WHEN POSITION(needle IN SUBSTRING(haystack FROM position)) = 0 THEN 0 ELSE POSITION(needle IN SUBSTRING(haystack FROM position)) + position - 1 END\",\n                \"mysql\": \"LOCATE(needle, haystack, position)\",\n                \"oracle\": \"INSTR(haystack, needle, position)\",\n                \"postgres\": \"CASE WHEN POSITION(needle IN SUBSTRING(haystack FROM position)) = 0 THEN 0 ELSE POSITION(needle IN SUBSTRING(haystack FROM position)) + position - 1 END\",\n                \"presto\": \"IF(STRPOS(SUBSTRING(haystack, position), needle) = 0, 0, STRPOS(SUBSTRING(haystack, position), needle) + position - 1)\",\n                \"redshift\": \"CASE WHEN POSITION(needle IN SUBSTRING(haystack FROM position)) = 0 THEN 0 ELSE POSITION(needle IN SUBSTRING(haystack FROM position)) + position - 1 END\",\n                \"risingwave\": \"CASE WHEN POSITION(needle IN SUBSTRING(haystack FROM position)) = 0 THEN 0 ELSE POSITION(needle IN SUBSTRING(haystack FROM position)) + position - 1 END\",\n                \"snowflake\": \"CHARINDEX(needle, haystack, position)\",\n                \"spark\": \"LOCATE(needle, haystack, position)\",\n                \"spark2\": \"LOCATE(needle, haystack, position)\",\n                \"sqlite\": \"IIF(INSTR(SUBSTRING(haystack, position), needle) = 0, 0, INSTR(SUBSTRING(haystack, position), needle) + position - 1)\",\n                \"tableau\": \"IF FIND(SUBSTRING(haystack, position), needle) = 0 THEN 0 ELSE FIND(SUBSTRING(haystack, position), needle) + position - 1 END\",\n                \"teradata\": \"INSTR(haystack, needle, position)\",\n                \"trino\": \"IF(STRPOS(SUBSTRING(haystack, position), needle) = 0, 0, STRPOS(SUBSTRING(haystack, position), needle) + position - 1)\",\n                \"tsql\": \"CHARINDEX(needle, haystack, position)\",\n            },\n        )\n        self.validate_all(\n            \"STR_POSITION(haystack, needle, position, occurrence)\",\n            read={\n                \"bigquery\": \"INSTR(haystack, needle, position, occurrence)\",\n                \"oracle\": \"INSTR(haystack, needle, position, occurrence)\",\n                \"teradata\": \"INSTR(haystack, needle, position, occurrence)\",\n            },\n            write={\n                \"bigquery\": \"INSTR(haystack, needle, position, occurrence)\",\n                \"oracle\": \"INSTR(haystack, needle, position, occurrence)\",\n                \"presto\": \"IF(STRPOS(SUBSTRING(haystack, position), needle, occurrence) = 0, 0, STRPOS(SUBSTRING(haystack, position), needle, occurrence) + position - 1)\",\n                \"tableau\": \"IF FINDNTH(SUBSTRING(haystack, position), needle, occurrence) = 0 THEN 0 ELSE FINDNTH(SUBSTRING(haystack, position), needle, occurrence) + position - 1 END\",\n                \"teradata\": \"INSTR(haystack, needle, position, occurrence)\",\n                \"trino\": \"IF(STRPOS(SUBSTRING(haystack, position), needle, occurrence) = 0, 0, STRPOS(SUBSTRING(haystack, position), needle, occurrence) + position - 1)\",\n            },\n        )\n        self.validate_all(\n            \"CONCAT_WS('-', 'a', 'b')\",\n            write={\n                \"clickhouse\": \"CONCAT_WS('-', 'a', 'b')\",\n                \"duckdb\": \"CONCAT_WS('-', 'a', 'b')\",\n                \"presto\": \"CONCAT_WS('-', CAST('a' AS VARCHAR), CAST('b' AS VARCHAR))\",\n                \"hive\": \"CONCAT_WS('-', 'a', 'b')\",\n                \"spark\": \"CONCAT_WS('-', 'a', 'b')\",\n                \"trino\": \"CONCAT_WS('-', CAST('a' AS VARCHAR), CAST('b' AS VARCHAR))\",\n            },\n        )\n\n        self.validate_all(\n            \"CONCAT_WS('-', x)\",\n            write={\n                \"clickhouse\": \"CONCAT_WS('-', x)\",\n                \"duckdb\": \"CONCAT_WS('-', x)\",\n                \"hive\": \"CONCAT_WS('-', x)\",\n                \"presto\": \"CONCAT_WS('-', CAST(x AS VARCHAR))\",\n                \"spark\": \"CONCAT_WS('-', x)\",\n                \"trino\": \"CONCAT_WS('-', CAST(x AS VARCHAR))\",\n            },\n        )\n        self.validate_all(\n            \"CONCAT(a)\",\n            write={\n                \"clickhouse\": \"CONCAT(a)\",\n                \"presto\": \"CAST(a AS VARCHAR)\",\n                \"trino\": \"CAST(a AS VARCHAR)\",\n                \"tsql\": \"a\",\n            },\n        )\n        self.validate_all(\n            \"CONCAT(COALESCE(a, ''))\",\n            read={\n                \"drill\": \"CONCAT(a)\",\n                \"duckdb\": \"CONCAT(a)\",\n                \"postgres\": \"CONCAT(a)\",\n                \"tsql\": \"CONCAT(a)\",\n            },\n        )\n        self.validate_all(\n            \"IF(x > 1, 1, 0)\",\n            write={\n                \"drill\": \"`IF`(x > 1, 1, 0)\",\n                \"duckdb\": \"CASE WHEN x > 1 THEN 1 ELSE 0 END\",\n                \"presto\": \"IF(x > 1, 1, 0)\",\n                \"hive\": \"IF(x > 1, 1, 0)\",\n                \"spark\": \"IF(x > 1, 1, 0)\",\n                \"tableau\": \"IF x > 1 THEN 1 ELSE 0 END\",\n            },\n        )\n        self.validate_all(\n            \"CASE WHEN 1 THEN x ELSE 0 END\",\n            write={\n                \"drill\": \"CASE WHEN 1 THEN x ELSE 0 END\",\n                \"duckdb\": \"CASE WHEN 1 THEN x ELSE 0 END\",\n                \"presto\": \"CASE WHEN 1 THEN x ELSE 0 END\",\n                \"hive\": \"CASE WHEN 1 THEN x ELSE 0 END\",\n                \"spark\": \"CASE WHEN 1 THEN x ELSE 0 END\",\n                \"tableau\": \"CASE WHEN 1 THEN x ELSE 0 END\",\n            },\n        )\n        self.validate_all(\n            \"x[y]\",\n            write={\n                \"drill\": \"x[y]\",\n                \"duckdb\": \"x[y]\",\n                \"presto\": \"x[y]\",\n                \"hive\": \"x[y]\",\n                \"spark\": \"x[y]\",\n            },\n        )\n        self.validate_all(\n            \"\"\"'[\"x\"]'\"\"\",\n            write={\n                \"duckdb\": \"\"\"'[\"x\"]'\"\"\",\n                \"presto\": \"\"\"'[\"x\"]'\"\"\",\n                \"hive\": \"\"\"'[\"x\"]'\"\"\",\n                \"spark\": \"\"\"'[\"x\"]'\"\"\",\n            },\n        )\n\n        self.validate_all(\n            'true or null as \"foo\"',\n            write={\n                \"bigquery\": \"TRUE OR NULL AS `foo`\",\n                \"drill\": \"TRUE OR NULL AS `foo`\",\n                \"duckdb\": 'TRUE OR NULL AS \"foo\"',\n                \"presto\": 'TRUE OR NULL AS \"foo\"',\n                \"hive\": \"TRUE OR NULL AS `foo`\",\n                \"spark\": \"TRUE OR NULL AS `foo`\",\n            },\n        )\n        self.validate_all(\n            \"SELECT IF(COALESCE(bar, 0) = 1, TRUE, FALSE) as foo FROM baz\",\n            write={\n                \"bigquery\": \"SELECT IF(COALESCE(bar, 0) = 1, TRUE, FALSE) AS foo FROM baz\",\n                \"duckdb\": \"SELECT CASE WHEN COALESCE(bar, 0) = 1 THEN TRUE ELSE FALSE END AS foo FROM baz\",\n                \"presto\": \"SELECT IF(COALESCE(bar, 0) = 1, TRUE, FALSE) AS foo FROM baz\",\n                \"hive\": \"SELECT IF(COALESCE(bar, 0) = 1, TRUE, FALSE) AS foo FROM baz\",\n                \"spark\": \"SELECT IF(COALESCE(bar, 0) = 1, TRUE, FALSE) AS foo FROM baz\",\n            },\n        )\n        self.validate_all(\n            \"LEVENSHTEIN(col1, col2)\",\n            read={\n                \"bigquery\": \"EDIT_DISTANCE(col1, col2)\",\n                \"clickhouse\": \"editDistance(col1, col2)\",\n                \"drill\": \"LEVENSHTEIN_DISTANCE(col1, col2)\",\n                \"duckdb\": \"LEVENSHTEIN(col1, col2)\",\n                \"hive\": \"LEVENSHTEIN(col1, col2)\",\n                \"spark\": \"LEVENSHTEIN(col1, col2)\",\n                \"postgres\": \"LEVENSHTEIN(col1, col2)\",\n                \"presto\": \"LEVENSHTEIN_DISTANCE(col1, col2)\",\n                \"snowflake\": \"EDITDISTANCE(col1, col2)\",\n                \"sqlite\": \"EDITDIST3(col1, col2)\",\n                \"trino\": \"LEVENSHTEIN_DISTANCE(col1, col2)\",\n            },\n            write={\n                \"bigquery\": \"EDIT_DISTANCE(col1, col2)\",\n                \"clickhouse\": \"editDistance(col1, col2)\",\n                \"drill\": \"LEVENSHTEIN_DISTANCE(col1, col2)\",\n                \"duckdb\": \"LEVENSHTEIN(col1, col2)\",\n                \"hive\": \"LEVENSHTEIN(col1, col2)\",\n                \"spark\": \"LEVENSHTEIN(col1, col2)\",\n                \"postgres\": \"LEVENSHTEIN(col1, col2)\",\n                \"presto\": \"LEVENSHTEIN_DISTANCE(col1, col2)\",\n                \"snowflake\": \"EDITDISTANCE(col1, col2)\",\n                \"sqlite\": \"EDITDIST3(col1, col2)\",\n                \"trino\": \"LEVENSHTEIN_DISTANCE(col1, col2)\",\n            },\n        )\n\n        self.validate_all(\n            \"LEVENSHTEIN(col1, col2, 1, 2, 3)\",\n            write={\n                \"bigquery\": UnsupportedError,\n                \"clickhouse\": UnsupportedError,\n                \"drill\": UnsupportedError,\n                \"duckdb\": UnsupportedError,\n                \"hive\": UnsupportedError,\n                \"spark\": UnsupportedError,\n                \"postgres\": \"LEVENSHTEIN(col1, col2, 1, 2, 3)\",\n                \"presto\": UnsupportedError,\n                \"snowflake\": UnsupportedError,\n                \"sqlite\": UnsupportedError,\n                \"trino\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"LEVENSHTEIN(col1, col2, 1, 2, 3, 4)\",\n            write={\n                \"bigquery\": UnsupportedError,\n                \"clickhouse\": UnsupportedError,\n                \"drill\": UnsupportedError,\n                \"duckdb\": UnsupportedError,\n                \"hive\": UnsupportedError,\n                \"spark\": UnsupportedError,\n                \"postgres\": \"LEVENSHTEIN_LESS_EQUAL(col1, col2, 1, 2, 3, 4)\",\n                \"presto\": UnsupportedError,\n                \"snowflake\": UnsupportedError,\n                \"sqlite\": UnsupportedError,\n                \"trino\": UnsupportedError,\n            },\n        )\n\n        self.validate_all(\n            \"LEVENSHTEIN(coalesce(col1, col2), coalesce(col2, col1))\",\n            write={\n                \"bigquery\": \"EDIT_DISTANCE(COALESCE(col1, col2), COALESCE(col2, col1))\",\n                \"duckdb\": \"LEVENSHTEIN(COALESCE(col1, col2), COALESCE(col2, col1))\",\n                \"drill\": \"LEVENSHTEIN_DISTANCE(COALESCE(col1, col2), COALESCE(col2, col1))\",\n                \"presto\": \"LEVENSHTEIN_DISTANCE(COALESCE(col1, col2), COALESCE(col2, col1))\",\n                \"hive\": \"LEVENSHTEIN(COALESCE(col1, col2), COALESCE(col2, col1))\",\n                \"spark\": \"LEVENSHTEIN(COALESCE(col1, col2), COALESCE(col2, col1))\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_FILTER(the_array, x -> x > 0)\",\n            write={\n                \"presto\": \"FILTER(the_array, x -> x > 0)\",\n                \"hive\": \"FILTER(the_array, x -> x > 0)\",\n                \"spark\": \"FILTER(the_array, x -> x > 0)\",\n            },\n        )\n        self.validate_all(\n            \"FILTER(the_array, x -> x > 0)\",\n            write={\n                \"presto\": \"FILTER(the_array, x -> x > 0)\",\n                \"starrocks\": \"ARRAY_FILTER(the_array, x -> x > 0)\",\n            },\n        )\n        self.validate_all(\n            \"a / b\",\n            write={\n                \"bigquery\": \"a / b\",\n                \"clickhouse\": \"a / b\",\n                \"databricks\": \"a / b\",\n                \"duckdb\": \"a / b\",\n                \"hive\": \"a / b\",\n                \"mysql\": \"a / b\",\n                \"oracle\": \"a / b\",\n                \"snowflake\": \"a / b\",\n                \"spark\": \"a / b\",\n                \"starrocks\": \"a / b\",\n                \"drill\": \"CAST(a AS DOUBLE) / b\",\n                \"postgres\": \"CAST(a AS DOUBLE PRECISION) / b\",\n                \"presto\": \"CAST(a AS DOUBLE) / b\",\n                \"redshift\": \"CAST(a AS DOUBLE PRECISION) / b\",\n                \"sqlite\": \"CAST(a AS REAL) / b\",\n                \"teradata\": \"CAST(a AS DOUBLE PRECISION) / b\",\n                \"trino\": \"CAST(a AS DOUBLE) / b\",\n                \"tsql\": \"CAST(a AS FLOAT) / b\",\n            },\n        )\n        self.validate_all(\n            \"MOD(8 - 1 + 7, 7)\",\n            write={\n                \"\": \"(8 - 1 + 7) % 7\",\n                \"hive\": \"(8 - 1 + 7) % 7\",\n                \"presto\": \"(8 - 1 + 7) % 7\",\n                \"snowflake\": \"(8 - 1 + 7) % 7\",\n                \"bigquery\": \"MOD(8 - 1 + 7, 7)\",\n            },\n        )\n        self.validate_all(\n            \"MOD(a, b + 1)\",\n            write={\n                \"\": \"a % (b + 1)\",\n                \"hive\": \"a % (b + 1)\",\n                \"presto\": \"a % (b + 1)\",\n                \"snowflake\": \"a % (b + 1)\",\n                \"bigquery\": \"MOD(a, b + 1)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_REMOVE(the_array, target)\",\n            write={\n                \"\": \"ARRAY_REMOVE(the_array, target)\",\n                \"clickhouse\": \"arrayFilter(_u -> _u <> target, the_array)\",\n                \"duckdb\": \"LIST_FILTER(the_array, _u -> _u <> target)\",\n                \"bigquery\": \"ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)\",\n                \"hive\": \"ARRAY_REMOVE(the_array, target)\",\n                \"postgres\": \"ARRAY_REMOVE(the_array, target)\",\n                \"presto\": \"ARRAY_REMOVE(the_array, target)\",\n                \"starrocks\": \"ARRAY_REMOVE(the_array, target)\",\n                \"databricks\": \"ARRAY_REMOVE(the_array, target)\",\n                \"snowflake\": \"ARRAY_REMOVE(the_array, target)\",\n            },\n        )\n\n    def test_typeddiv(self):\n        typed_div = exp.Div(this=exp.column(\"a\"), expression=exp.column(\"b\"), typed=True)\n        div = exp.Div(this=exp.column(\"a\"), expression=exp.column(\"b\"))\n        typed_div_dialect = \"presto\"\n        div_dialect = \"hive\"\n        INT = exp.DataType.Type.INT\n        FLOAT = exp.DataType.Type.FLOAT\n\n        for expression, types, dialect, expected in [\n            (typed_div, (None, None), typed_div_dialect, \"a / b\"),\n            (typed_div, (None, None), div_dialect, \"a / b\"),\n            (div, (None, None), typed_div_dialect, \"CAST(a AS DOUBLE) / b\"),\n            (div, (None, None), div_dialect, \"a / b\"),\n            (typed_div, (INT, INT), typed_div_dialect, \"a / b\"),\n            (typed_div, (INT, INT), div_dialect, \"CAST(a / b AS BIGINT)\"),\n            (div, (INT, INT), typed_div_dialect, \"CAST(a AS DOUBLE) / b\"),\n            (div, (INT, INT), div_dialect, \"a / b\"),\n            (typed_div, (FLOAT, FLOAT), typed_div_dialect, \"a / b\"),\n            (typed_div, (FLOAT, FLOAT), div_dialect, \"a / b\"),\n            (div, (FLOAT, FLOAT), typed_div_dialect, \"a / b\"),\n            (div, (FLOAT, FLOAT), div_dialect, \"a / b\"),\n            (typed_div, (INT, FLOAT), typed_div_dialect, \"a / b\"),\n            (typed_div, (INT, FLOAT), div_dialect, \"a / b\"),\n            (div, (INT, FLOAT), typed_div_dialect, \"a / b\"),\n            (div, (INT, FLOAT), div_dialect, \"a / b\"),\n        ]:\n            with self.subTest(f\"{expression.__class__.__name__} {types} {dialect} -> {expected}\"):\n                expression = expression.copy()\n                expression.left.type = types[0]\n                expression.right.type = types[1]\n                self.assertEqual(expected, expression.sql(dialect=dialect))\n\n    def test_safediv(self):\n        safe_div = exp.Div(this=exp.column(\"a\"), expression=exp.column(\"b\"), safe=True)\n        div = exp.Div(this=exp.column(\"a\"), expression=exp.column(\"b\"))\n        safe_div_dialect = \"mysql\"\n        div_dialect = \"snowflake\"\n\n        for expression, dialect, expected in [\n            (safe_div, safe_div_dialect, \"a / b\"),\n            (safe_div, div_dialect, \"a / NULLIF(b, 0)\"),\n            (div, safe_div_dialect, \"a / b\"),\n            (div, div_dialect, \"a / b\"),\n        ]:\n            with self.subTest(f\"{expression.__class__.__name__} {dialect} -> {expected}\"):\n                self.assertEqual(expected, expression.sql(dialect=dialect))\n\n        self.assertEqual(\n            parse_one(\"CAST(x AS DECIMAL) / y\", read=\"mysql\").sql(dialect=\"postgres\"),\n            \"CAST(x AS DECIMAL) / NULLIF(y, 0)\",\n        )\n\n    def test_limit(self):\n        self.validate_all(\n            \"SELECT * FROM data LIMIT 10, 20\",\n            write={\"sqlite\": \"SELECT * FROM data LIMIT 20 OFFSET 10\"},\n        )\n        self.validate_all(\n            \"SELECT x FROM y LIMIT 10\",\n            read={\n                \"teradata\": \"SELECT TOP 10 x FROM y\",\n                \"tsql\": \"SELECT TOP 10 x FROM y\",\n                \"snowflake\": \"SELECT TOP 10 x FROM y\",\n            },\n            write={\n                \"sqlite\": \"SELECT x FROM y LIMIT 10\",\n                \"oracle\": \"SELECT x FROM y FETCH FIRST 10 ROWS ONLY\",\n                \"tsql\": \"SELECT TOP 10 x FROM y\",\n            },\n        )\n        self.validate_all(\n            \"SELECT x FROM y LIMIT 10 OFFSET 5\",\n            write={\n                \"sqlite\": \"SELECT x FROM y LIMIT 10 OFFSET 5\",\n                \"oracle\": \"SELECT x FROM y OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY\",\n            },\n        )\n        self.validate_all(\n            \"SELECT x FROM y OFFSET 10 FETCH FIRST 3 ROWS ONLY\",\n            write={\n                \"sqlite\": \"SELECT x FROM y LIMIT 3 OFFSET 10\",\n                \"oracle\": \"SELECT x FROM y OFFSET 10 ROWS FETCH FIRST 3 ROWS ONLY\",\n            },\n        )\n        self.validate_all(\n            \"SELECT x FROM y OFFSET 10 ROWS FETCH FIRST 3 ROWS ONLY\",\n            write={\n                \"oracle\": \"SELECT x FROM y OFFSET 10 ROWS FETCH FIRST 3 ROWS ONLY\",\n            },\n        )\n        self.validate_all(\n            '\"x\" + \"y\"',\n            read={\n                \"clickhouse\": '`x` + \"y\"',\n                \"sqlite\": '`x` + \"y\"',\n                \"redshift\": '\"x\" + \"y\"',\n            },\n        )\n        self.validate_all(\n            \"[1, 2]\",\n            write={\n                \"bigquery\": \"[1, 2]\",\n                \"clickhouse\": \"[1, 2]\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM VALUES ('x'), ('y') AS t(z)\",\n            write={\n                \"spark\": \"SELECT * FROM VALUES ('x'), ('y') AS t(z)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE t (c CHAR, nc NCHAR, v1 VARCHAR, v2 VARCHAR2, nv NVARCHAR, nv2 NVARCHAR2)\",\n            write={\n                \"duckdb\": \"CREATE TABLE t (c TEXT, nc TEXT, v1 TEXT, v2 TEXT, nv TEXT, nv2 TEXT)\",\n                \"hive\": \"CREATE TABLE t (c STRING, nc STRING, v1 STRING, v2 STRING, nv STRING, nv2 STRING)\",\n                \"oracle\": \"CREATE TABLE t (c CHAR, nc NCHAR, v1 VARCHAR2, v2 VARCHAR2, nv NVARCHAR2, nv2 NVARCHAR2)\",\n                \"postgres\": \"CREATE TABLE t (c CHAR, nc CHAR, v1 VARCHAR, v2 VARCHAR, nv VARCHAR, nv2 VARCHAR)\",\n                \"sqlite\": \"CREATE TABLE t (c TEXT, nc TEXT, v1 TEXT, v2 TEXT, nv TEXT, nv2 TEXT)\",\n            },\n        )\n        self.validate_all(\n            \"POWER(1.2, 3.4)\",\n            read={\n                \"hive\": \"pow(1.2, 3.4)\",\n                \"postgres\": \"power(1.2, 3.4)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE INDEX my_idx ON tbl(a, b)\",\n            read={\n                \"hive\": \"CREATE INDEX my_idx ON TABLE tbl(a, b)\",\n                \"sqlite\": \"CREATE INDEX my_idx ON tbl(a, b)\",\n            },\n            write={\n                \"hive\": \"CREATE INDEX my_idx ON TABLE tbl(a, b)\",\n                \"postgres\": \"CREATE INDEX my_idx ON tbl(a NULLS FIRST, b NULLS FIRST)\",\n                \"sqlite\": \"CREATE INDEX my_idx ON tbl(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE UNIQUE INDEX my_idx ON tbl(a, b)\",\n            read={\n                \"hive\": \"CREATE UNIQUE INDEX my_idx ON TABLE tbl(a, b)\",\n                \"sqlite\": \"CREATE UNIQUE INDEX my_idx ON tbl(a, b)\",\n            },\n            write={\n                \"hive\": \"CREATE UNIQUE INDEX my_idx ON TABLE tbl(a, b)\",\n                \"postgres\": \"CREATE UNIQUE INDEX my_idx ON tbl(a NULLS FIRST, b NULLS FIRST)\",\n                \"sqlite\": \"CREATE UNIQUE INDEX my_idx ON tbl(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE t (b1 BINARY, b2 BINARY(1024), c1 TEXT, c2 TEXT(1024))\",\n            write={\n                \"duckdb\": \"CREATE TABLE t (b1 BLOB, b2 BLOB(1024), c1 TEXT, c2 TEXT(1024))\",\n                \"hive\": \"CREATE TABLE t (b1 BINARY, b2 BINARY(1024), c1 STRING, c2 VARCHAR(1024))\",\n                \"oracle\": \"CREATE TABLE t (b1 BLOB, b2 BLOB(1024), c1 CLOB, c2 CLOB(1024))\",\n                \"postgres\": \"CREATE TABLE t (b1 BYTEA, b2 BYTEA(1024), c1 TEXT, c2 TEXT(1024))\",\n                \"sqlite\": \"CREATE TABLE t (b1 BLOB, b2 BLOB(1024), c1 TEXT, c2 TEXT(1024))\",\n                \"redshift\": \"CREATE TABLE t (b1 VARBYTE, b2 VARBYTE(1024), c1 VARCHAR(MAX), c2 VARCHAR(1024))\",\n            },\n        )\n\n    def test_alias(self):\n        self.validate_all(\n            \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT x AS x FROM t GROUP BY x\",\n            write={\n                \"\": \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT x AS x FROM t GROUP BY x\",\n                \"hive\": \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT x AS x FROM t GROUP BY x\",\n                \"oracle\": \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT x AS x FROM t GROUP BY x\",\n                \"presto\": \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT x AS x FROM t GROUP BY x\",\n            },\n        )\n        self.validate_all(\n            \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT SUM(x) AS y, y AS x FROM t GROUP BY y\",\n            write={\n                \"\": \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT SUM(x) AS y, y AS x FROM t GROUP BY y\",\n                \"hive\": \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT SUM(x) AS y, y AS x FROM t GROUP BY y\",\n                \"oracle\": \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT SUM(x) AS y, y AS x FROM t GROUP BY y\",\n                \"presto\": \"WITH t AS (SELECT 1 AS x, 2 AS y) SELECT SUM(x) AS y, y AS x FROM t GROUP BY y\",\n            },\n        )\n\n        self.validate_all(\n            'SELECT 1 AS \"foo\"',\n            read={\n                \"mysql\": \"SELECT 1 'foo'\",\n                \"sqlite\": \"SELECT 1 'foo'\",\n                \"tsql\": \"SELECT 1 'foo'\",\n            },\n        )\n\n        for dialect in (\n            \"presto\",\n            \"hive\",\n            \"postgres\",\n            \"clickhouse\",\n            \"bigquery\",\n            \"snowflake\",\n            \"duckdb\",\n        ):\n            with self.subTest(f\"string alias: {dialect}\"):\n                with self.assertRaises(ParseError):\n                    parse_one(\"SELECT 1 'foo'\", dialect=dialect)\n\n        self.validate_all(\n            \"SELECT y x FROM my_table t\",\n            write={\n                \"drill\": \"SELECT y AS x FROM my_table AS t\",\n                \"hive\": \"SELECT y AS x FROM my_table AS t\",\n                \"oracle\": \"SELECT y AS x FROM my_table t\",\n                \"postgres\": \"SELECT y AS x FROM my_table AS t\",\n                \"sqlite\": \"SELECT y AS x FROM my_table AS t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (SELECT * FROM my_table AS t) AS tbl\",\n            write={\n                \"drill\": \"SELECT * FROM (SELECT * FROM my_table AS t) AS tbl\",\n                \"hive\": \"SELECT * FROM (SELECT * FROM my_table AS t) AS tbl\",\n                \"oracle\": \"SELECT * FROM (SELECT * FROM my_table t) tbl\",\n                \"postgres\": \"SELECT * FROM (SELECT * FROM my_table AS t) AS tbl\",\n                \"sqlite\": \"SELECT * FROM (SELECT * FROM my_table AS t) AS tbl\",\n            },\n        )\n        self.validate_all(\n            \"WITH cte1 AS (SELECT a, b FROM table1), cte2 AS (SELECT c, e AS d FROM table2) SELECT b, d AS dd FROM cte1 AS t CROSS JOIN cte2 WHERE cte1.a = cte2.c\",\n            write={\n                \"hive\": \"WITH cte1 AS (SELECT a, b FROM table1), cte2 AS (SELECT c, e AS d FROM table2) SELECT b, d AS dd FROM cte1 AS t CROSS JOIN cte2 WHERE cte1.a = cte2.c\",\n                \"oracle\": \"WITH cte1 AS (SELECT a, b FROM table1), cte2 AS (SELECT c, e AS d FROM table2) SELECT b, d AS dd FROM cte1 t CROSS JOIN cte2 WHERE cte1.a = cte2.c\",\n                \"postgres\": \"WITH cte1 AS (SELECT a, b FROM table1), cte2 AS (SELECT c, e AS d FROM table2) SELECT b, d AS dd FROM cte1 AS t CROSS JOIN cte2 WHERE cte1.a = cte2.c\",\n                \"sqlite\": \"WITH cte1 AS (SELECT a, b FROM table1), cte2 AS (SELECT c, e AS d FROM table2) SELECT b, d AS dd FROM cte1 AS t CROSS JOIN cte2 WHERE cte1.a = cte2.c\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM (SELECT 1 AS col) AS apply\",\n            read={\n                \"\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"hive\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"postgres\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"duckdb\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"presto\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"spark\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"spark2\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"trino\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"snowflake\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"bigquery\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n                \"athena\": \"SELECT * FROM (SELECT 1 AS col) apply\",\n            },\n        )\n\n    def test_nullsafe_eq(self):\n        self.validate_all(\n            \"SELECT a IS NOT DISTINCT FROM b\",\n            read={\n                \"mysql\": \"SELECT a <=> b\",\n                \"postgres\": \"SELECT a IS NOT DISTINCT FROM b\",\n            },\n            write={\n                \"mysql\": \"SELECT a <=> b\",\n                \"postgres\": \"SELECT a IS NOT DISTINCT FROM b\",\n            },\n        )\n\n    def test_nullsafe_neq(self):\n        self.validate_all(\n            \"SELECT a IS DISTINCT FROM b\",\n            read={\n                \"postgres\": \"SELECT a IS DISTINCT FROM b\",\n            },\n            write={\n                \"mysql\": \"SELECT NOT a <=> b\",\n                \"postgres\": \"SELECT a IS DISTINCT FROM b\",\n            },\n        )\n\n    def test_hash_comments(self):\n        self.validate_all(\n            \"SELECT 1 /* arbitrary content,,, until end-of-line */\",\n            read={\n                \"mysql\": \"SELECT 1 # arbitrary content,,, until end-of-line\",\n                \"bigquery\": \"SELECT 1 # arbitrary content,,, until end-of-line\",\n                \"clickhouse\": \"SELECT 1 #! arbitrary content,,, until end-of-line\",\n            },\n        )\n        self.validate_all(\n            \"\"\"/* comment1 */\nSELECT\n  x, /* comment2 */\n  y /* comment3 */\"\"\",\n            read={\n                \"mysql\": \"\"\"SELECT # comment1\n  x, # comment2\n  y # comment3\"\"\",\n                \"bigquery\": \"\"\"SELECT # comment1\n  x, # comment2\n  y # comment3\"\"\",\n                \"clickhouse\": \"\"\"SELECT # comment1\n  x, # comment2\n  y # comment3\"\"\",\n            },\n            pretty=True,\n        )\n\n    def test_transactions(self):\n        self.validate_all(\n            \"BEGIN TRANSACTION\",\n            write={\n                \"bigquery\": \"BEGIN TRANSACTION\",\n                \"mysql\": \"BEGIN\",\n                \"postgres\": \"BEGIN\",\n                \"presto\": \"START TRANSACTION\",\n                \"trino\": \"START TRANSACTION\",\n                \"redshift\": \"BEGIN\",\n                \"snowflake\": \"BEGIN\",\n                \"sqlite\": \"BEGIN TRANSACTION\",\n                \"tsql\": \"BEGIN TRANSACTION\",\n            },\n        )\n        self.validate_all(\n            \"BEGIN READ WRITE, ISOLATION LEVEL SERIALIZABLE\",\n            read={\n                \"presto\": \"START TRANSACTION READ WRITE, ISOLATION LEVEL SERIALIZABLE\",\n                \"trino\": \"START TRANSACTION READ WRITE, ISOLATION LEVEL SERIALIZABLE\",\n            },\n        )\n        self.validate_all(\n            \"BEGIN ISOLATION LEVEL REPEATABLE READ\",\n            read={\n                \"presto\": \"START TRANSACTION ISOLATION LEVEL REPEATABLE READ\",\n                \"trino\": \"START TRANSACTION ISOLATION LEVEL REPEATABLE READ\",\n            },\n        )\n        self.validate_all(\n            \"BEGIN IMMEDIATE TRANSACTION\",\n            write={\"sqlite\": \"BEGIN IMMEDIATE TRANSACTION\"},\n        )\n\n    def test_merge(self):\n        self.validate_all(\n            \"\"\"\n            MERGE INTO target USING source ON target.id = source.id\n                WHEN NOT MATCHED THEN INSERT (id) values (source.id)\n            \"\"\",\n            write={\n                \"bigquery\": \"MERGE INTO target USING source ON target.id = source.id WHEN NOT MATCHED THEN INSERT (id) VALUES (source.id)\",\n                \"snowflake\": \"MERGE INTO target USING source ON target.id = source.id WHEN NOT MATCHED THEN INSERT (id) VALUES (source.id)\",\n                \"spark\": \"MERGE INTO target USING source ON target.id = source.id WHEN NOT MATCHED THEN INSERT (id) VALUES (source.id)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\n            MERGE INTO target USING source ON target.id = source.id\n                WHEN MATCHED AND source.is_deleted = 1 THEN DELETE\n                WHEN MATCHED THEN UPDATE SET val = source.val\n                WHEN NOT MATCHED THEN INSERT (id, val) VALUES (source.id, source.val)\n            \"\"\",\n            write={\n                \"bigquery\": \"MERGE INTO target USING source ON target.id = source.id WHEN MATCHED AND source.is_deleted = 1 THEN DELETE WHEN MATCHED THEN UPDATE SET val = source.val WHEN NOT MATCHED THEN INSERT (id, val) VALUES (source.id, source.val)\",\n                \"snowflake\": \"MERGE INTO target USING source ON target.id = source.id WHEN MATCHED AND source.is_deleted = 1 THEN DELETE WHEN MATCHED THEN UPDATE SET val = source.val WHEN NOT MATCHED THEN INSERT (id, val) VALUES (source.id, source.val)\",\n                \"spark\": \"MERGE INTO target USING source ON target.id = source.id WHEN MATCHED AND source.is_deleted = 1 THEN DELETE WHEN MATCHED THEN UPDATE SET val = source.val WHEN NOT MATCHED THEN INSERT (id, val) VALUES (source.id, source.val)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\n            MERGE INTO target USING source ON target.id = source.id\n                WHEN MATCHED THEN UPDATE *\n                WHEN NOT MATCHED THEN INSERT *\n            \"\"\",\n            write={\n                \"spark\": \"MERGE INTO target USING source ON target.id = source.id WHEN MATCHED THEN UPDATE * WHEN NOT MATCHED THEN INSERT *\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\n            MERGE a b USING c d ON b.id = d.id\n            WHEN MATCHED AND EXISTS (\n                SELECT b.name\n                EXCEPT\n                SELECT d.name\n            )\n            THEN UPDATE SET b.name = d.name\n            \"\"\",\n            write={\n                \"bigquery\": \"MERGE INTO a AS b USING c AS d ON b.id = d.id WHEN MATCHED AND EXISTS(SELECT b.name EXCEPT DISTINCT SELECT d.name) THEN UPDATE SET b.name = d.name\",\n                \"snowflake\": \"MERGE INTO a AS b USING c AS d ON b.id = d.id WHEN MATCHED AND EXISTS(SELECT b.name EXCEPT SELECT d.name) THEN UPDATE SET b.name = d.name\",\n                \"spark\": \"MERGE INTO a AS b USING c AS d ON b.id = d.id WHEN MATCHED AND EXISTS(SELECT b.name EXCEPT SELECT d.name) THEN UPDATE SET b.name = d.name\",\n            },\n        )\n\n        # needs to preserve the target alias in then WHEN condition but not in the THEN clause\n        self.validate_all(\n            \"\"\"MERGE INTO foo AS target USING (SELECT a, b FROM tbl) AS src ON src.a = target.a\n            WHEN MATCHED AND target.a <> src.a THEN UPDATE SET target.b = 'FOO'\n            WHEN NOT MATCHED THEN INSERT (target.a, target.b) VALUES (src.a, src.b)\"\"\",\n            write={\n                \"trino\": \"\"\"MERGE INTO foo AS target USING (SELECT a, b FROM tbl) AS src ON src.a = target.a WHEN MATCHED AND target.a <> src.a THEN UPDATE SET b = 'FOO' WHEN NOT MATCHED THEN INSERT (a, b) VALUES (src.a, src.b)\"\"\",\n                \"postgres\": \"\"\"MERGE INTO foo AS target USING (SELECT a, b FROM tbl) AS src ON src.a = target.a WHEN MATCHED AND target.a <> src.a THEN UPDATE SET b = 'FOO' WHEN NOT MATCHED THEN INSERT (a, b) VALUES (src.a, src.b)\"\"\",\n            },\n        )\n\n        # needs to preserve the target alias in then WHEN condition and function but not in the THEN clause\n        self.validate_all(\n            \"\"\"MERGE INTO foo AS target USING (SELECT a, b FROM tbl) AS src ON src.a = target.a\n            WHEN MATCHED THEN UPDATE SET target.b = COALESCE(src.b, target.b)\n            WHEN NOT MATCHED THEN INSERT (target.a, target.b) VALUES (src.a, src.b)\"\"\",\n            write={\n                \"trino\": \"\"\"MERGE INTO foo AS target USING (SELECT a, b FROM tbl) AS src ON src.a = target.a WHEN MATCHED THEN UPDATE SET b = COALESCE(src.b, target.b) WHEN NOT MATCHED THEN INSERT (a, b) VALUES (src.a, src.b)\"\"\",\n                \"postgres\": \"\"\"MERGE INTO foo AS target USING (SELECT a, b FROM tbl) AS src ON src.a = target.a WHEN MATCHED THEN UPDATE SET b = COALESCE(src.b, target.b) WHEN NOT MATCHED THEN INSERT (a, b) VALUES (src.a, src.b)\"\"\",\n            },\n        )\n\n    def test_substring(self):\n        self.validate_all(\n            \"SUBSTR('123456', 2, 3)\",\n            write={\n                \"bigquery\": \"SUBSTRING('123456', 2, 3)\",\n                \"oracle\": \"SUBSTR('123456', 2, 3)\",\n                \"postgres\": \"SUBSTRING('123456' FROM 2 FOR 3)\",\n            },\n        )\n        self.validate_all(\n            \"SUBSTRING('123456', 2, 3)\",\n            write={\n                \"bigquery\": \"SUBSTRING('123456', 2, 3)\",\n                \"oracle\": \"SUBSTR('123456', 2, 3)\",\n                \"postgres\": \"SUBSTRING('123456' FROM 2 FOR 3)\",\n            },\n        )\n\n    def test_logarithm(self):\n        for base in (2, 10):\n            with self.subTest(f\"Transpiling LOG base {base}\"):\n                self.validate_all(\n                    f\"LOG({base}, a)\",\n                    read={\n                        \"\": f\"LOG{base}(a)\",\n                        \"bigquery\": f\"LOG{base}(a)\",\n                        \"clickhouse\": f\"LOG{base}(a)\",\n                        \"databricks\": f\"LOG{base}(a)\",\n                        \"dremio\": f\"LOG{base}(a)\",\n                        \"duckdb\": f\"LOG{base}(a)\",\n                        \"mysql\": f\"LOG{base}(a)\",\n                        \"postgres\": f\"LOG{base}(a)\",\n                        \"presto\": f\"LOG{base}(a)\",\n                        \"spark\": f\"LOG{base}(a)\",\n                        \"sqlite\": f\"LOG{base}(a)\",\n                        \"trino\": f\"LOG{base}(a)\",\n                        \"tsql\": f\"LOG{base}(a)\",\n                    },\n                    write={\n                        \"bigquery\": f\"LOG(a, {base})\",\n                        \"clickhouse\": f\"LOG{base}(a)\",\n                        \"dremio\": f\"LOG({base}, a)\",\n                        \"duckdb\": f\"LOG({base}, a)\",\n                        \"mysql\": f\"LOG({base}, a)\",\n                        \"oracle\": f\"LOG({base}, a)\",\n                        \"postgres\": f\"LOG({base}, a)\",\n                        \"presto\": f\"LOG{base}(a)\",\n                        \"redshift\": f\"LOG({base}, a)\",\n                        \"snowflake\": f\"LOG({base}, a)\",\n                        \"spark2\": f\"LOG({base}, a)\",\n                        \"spark\": f\"LOG({base}, a)\",\n                        \"sqlite\": f\"LOG({base}, a)\",\n                        \"starrocks\": f\"LOG({base}, a)\",\n                        \"tableau\": f\"LOG(a, {base})\",\n                        \"trino\": f\"LOG({base}, a)\",\n                        \"tsql\": f\"LOG(a, {base})\",\n                    },\n                )\n\n        self.validate_all(\n            \"LOG(x)\",\n            read={\n                \"duckdb\": \"LOG(x)\",\n                \"postgres\": \"LOG(x)\",\n                \"redshift\": \"LOG(x)\",\n                \"sqlite\": \"LOG(x)\",\n                \"teradata\": \"LOG(x)\",\n            },\n        )\n        self.validate_all(\n            \"LN(x)\",\n            read={\n                \"dremio\": \"LOG(x)\",\n                \"bigquery\": \"LOG(x)\",\n                \"clickhouse\": \"LOG(x)\",\n                \"databricks\": \"LOG(x)\",\n                \"drill\": \"LOG(x)\",\n                \"hive\": \"LOG(x)\",\n                \"mysql\": \"LOG(x)\",\n                \"tsql\": \"LOG(x)\",\n            },\n        )\n        self.validate_all(\n            \"LOG(b, n)\",\n            read={\n                \"bigquery\": \"LOG(n, b)\",\n                \"databricks\": \"LOG(b, n)\",\n                \"drill\": \"LOG(b, n)\",\n                \"duckdb\": \"LOG(b, n)\",\n                \"hive\": \"LOG(b, n)\",\n                \"mysql\": \"LOG(b, n)\",\n                \"oracle\": \"LOG(b, n)\",\n                \"postgres\": \"LOG(b, n)\",\n                \"snowflake\": \"LOG(b, n)\",\n                \"spark\": \"LOG(b, n)\",\n                \"sqlite\": \"LOG(b, n)\",\n                \"trino\": \"LOG(b, n)\",\n                \"tsql\": \"LOG(n, b)\",\n            },\n            write={\n                \"clickhouse\": UnsupportedError,\n                \"presto\": UnsupportedError,\n            },\n        )\n\n    def test_count_if(self):\n        self.validate_identity(\"COUNT_IF(DISTINCT cond)\")\n\n        self.validate_all(\n            \"SELECT COUNT_IF(cond) FILTER\", write={\"\": \"SELECT COUNT_IF(cond) AS FILTER\"}\n        )\n        self.validate_all(\n            \"SELECT COUNT_IF(col % 2 = 0) FROM foo\",\n            write={\n                \"\": \"SELECT COUNT_IF(col % 2 = 0) FROM foo\",\n                \"databricks\": \"SELECT COUNT_IF(col % 2 = 0) FROM foo\",\n                \"presto\": \"SELECT COUNT_IF(col % 2 = 0) FROM foo\",\n                \"snowflake\": \"SELECT COUNT_IF(col % 2 = 0) FROM foo\",\n                \"sqlite\": \"SELECT SUM(IIF(col % 2 = 0, 1, 0)) FROM foo\",\n                \"tsql\": \"SELECT COUNT_IF(col % 2 = 0) FROM foo\",\n                \"postgres\": \"SELECT SUM(CASE WHEN col % 2 = 0 THEN 1 ELSE 0 END) FROM foo\",\n                \"redshift\": \"SELECT SUM(CASE WHEN col % 2 = 0 THEN 1 ELSE 0 END) FROM foo\",\n            },\n        )\n        self.validate_all(\n            \"SELECT COUNT_IF(col % 2 = 0) FILTER(WHERE col < 1000) FROM foo\",\n            read={\n                \"\": \"SELECT COUNT_IF(col % 2 = 0) FILTER(WHERE col < 1000) FROM foo\",\n                \"databricks\": \"SELECT COUNT_IF(col % 2 = 0) FILTER(WHERE col < 1000) FROM foo\",\n                \"tsql\": \"SELECT COUNT_IF(col % 2 = 0) FILTER(WHERE col < 1000) FROM foo\",\n            },\n            write={\n                \"\": \"SELECT COUNT_IF(col % 2 = 0) FILTER(WHERE col < 1000) FROM foo\",\n                \"databricks\": \"SELECT COUNT_IF(col % 2 = 0) FILTER(WHERE col < 1000) FROM foo\",\n                \"presto\": \"SELECT COUNT_IF(col % 2 = 0) FILTER(WHERE col < 1000) FROM foo\",\n                \"sqlite\": \"SELECT SUM(IIF(col % 2 = 0, 1, 0)) FILTER(WHERE col < 1000) FROM foo\",\n                \"tsql\": \"SELECT COUNT_IF(col % 2 = 0) FILTER(WHERE col < 1000) FROM foo\",\n            },\n        )\n\n    def test_cast_to_user_defined_type(self):\n        self.validate_identity(\"CAST(x AS some_udt(1234))\")\n\n        self.validate_all(\n            \"CAST(x AS some_udt)\",\n            write={\n                \"\": \"CAST(x AS some_udt)\",\n                \"oracle\": \"CAST(x AS some_udt)\",\n                \"postgres\": \"CAST(x AS some_udt)\",\n                \"presto\": \"CAST(x AS some_udt)\",\n                \"teradata\": \"CAST(x AS some_udt)\",\n                \"tsql\": \"CAST(x AS some_udt)\",\n            },\n        )\n\n        with self.assertRaises(ParseError):\n            parse_one(\"CAST(x AS some_udt)\", read=\"bigquery\")\n\n    def test_qualify(self):\n        self.validate_all(\n            \"SELECT * FROM t QUALIFY COUNT(*) OVER () > 1\",\n            write={\n                \"duckdb\": \"SELECT * FROM t QUALIFY COUNT(*) OVER () > 1\",\n                \"snowflake\": \"SELECT * FROM t QUALIFY COUNT(*) OVER () > 1\",\n                \"clickhouse\": \"SELECT * FROM t QUALIFY COUNT(*) OVER () > 1\",\n                \"mysql\": \"SELECT * FROM (SELECT *, COUNT(*) OVER () AS _w FROM t) AS _t WHERE _w > 1\",\n                \"oracle\": \"SELECT * FROM (SELECT *, COUNT(*) OVER () AS _w FROM t) _t WHERE _w > 1\",\n                \"postgres\": \"SELECT * FROM (SELECT *, COUNT(*) OVER () AS _w FROM t) AS _t WHERE _w > 1\",\n                \"tsql\": \"SELECT * FROM (SELECT *, COUNT_BIG(*) OVER () AS _w FROM t) AS _t WHERE _w > 1\",\n            },\n        )\n        self.validate_all(\n            'SELECT \"user id\", some_id, 1 as other_id, 2 as \"2 nd id\" FROM t QUALIFY COUNT(*) OVER () > 1',\n            write={\n                \"duckdb\": 'SELECT \"user id\", some_id, 1 AS other_id, 2 AS \"2 nd id\" FROM t QUALIFY COUNT(*) OVER () > 1',\n                \"snowflake\": 'SELECT \"user id\", some_id, 1 AS other_id, 2 AS \"2 nd id\" FROM t QUALIFY COUNT(*) OVER () > 1',\n                \"clickhouse\": 'SELECT \"user id\", some_id, 1 AS other_id, 2 AS \"2 nd id\" FROM t QUALIFY COUNT(*) OVER () > 1',\n                \"mysql\": \"SELECT `user id`, some_id, other_id, `2 nd id` FROM (SELECT `user id`, some_id, 1 AS other_id, 2 AS `2 nd id`, COUNT(*) OVER () AS _w FROM t) AS _t WHERE _w > 1\",\n                \"oracle\": 'SELECT \"user id\", some_id, other_id, \"2 nd id\" FROM (SELECT \"user id\", some_id, 1 AS other_id, 2 AS \"2 nd id\", COUNT(*) OVER () AS _w FROM t) _t WHERE _w > 1',\n                \"postgres\": 'SELECT \"user id\", some_id, other_id, \"2 nd id\" FROM (SELECT \"user id\", some_id, 1 AS other_id, 2 AS \"2 nd id\", COUNT(*) OVER () AS _w FROM t) AS _t WHERE _w > 1',\n                \"tsql\": \"SELECT [user id], some_id, other_id, [2 nd id] FROM (SELECT [user id] AS [user id], some_id AS some_id, 1 AS other_id, 2 AS [2 nd id], COUNT_BIG(*) OVER () AS _w FROM t) AS _t WHERE _w > 1\",\n            },\n        )\n\n    def test_window_exclude(self):\n        for option in (\"CURRENT ROW\", \"TIES\", \"GROUP\"):\n            self.validate_all(\n                f\"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE {option})\",\n                write={\n                    \"duckdb\": f\"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE {option})\",\n                    \"postgres\": f\"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE {option})\",\n                    \"sqlite\": f\"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE {option})\",\n                    \"oracle\": f\"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE {option})\",\n                },\n            )\n\n        # EXCLUDE NO OTHERS is the default behaviour\n        self.validate_all(\n            \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n            read={\n                \"duckdb\": \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS)\",\n                \"postgres\": \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS)\",\n                \"sqlite\": \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS)\",\n                \"oracle\": \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS)\",\n            },\n            write={\n                \"duckdb\": \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n                \"postgres\": \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n                \"sqlite\": \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n                \"oracle\": \"SELECT SUM(X) OVER (PARTITION BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n            },\n        )\n\n    def test_nested_ctes(self):\n        self.validate_all(\n            \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n            write={\n                \"bigquery\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"clickhouse\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"databricks\": \"WITH t AS (SELECT 1 AS c) SELECT * FROM (SELECT c FROM t) AS subq\",\n                \"duckdb\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"hive\": \"WITH t AS (SELECT 1 AS c) SELECT * FROM (SELECT c FROM t) AS subq\",\n                \"mysql\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"postgres\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"presto\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"redshift\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"snowflake\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"spark\": \"WITH t AS (SELECT 1 AS c) SELECT * FROM (SELECT c FROM t) AS subq\",\n                \"spark2\": \"WITH t AS (SELECT 1 AS c) SELECT * FROM (SELECT c FROM t) AS subq\",\n                \"sqlite\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"trino\": \"SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq\",\n                \"tsql\": \"WITH t AS (SELECT 1 AS c) SELECT * FROM (SELECT c AS c FROM t) AS subq\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq1) AS subq2\",\n            write={\n                \"bigquery\": \"SELECT * FROM (SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq1) AS subq2\",\n                \"duckdb\": \"SELECT * FROM (SELECT * FROM (WITH t AS (SELECT 1 AS c) SELECT c FROM t) AS subq1) AS subq2\",\n                \"hive\": \"WITH t AS (SELECT 1 AS c) SELECT * FROM (SELECT * FROM (SELECT c FROM t) AS subq1) AS subq2\",\n                \"tsql\": \"WITH t AS (SELECT 1 AS c) SELECT * FROM (SELECT * FROM (SELECT c AS c FROM t) AS subq1) AS subq2\",\n            },\n        )\n        self.validate_all(\n            \"WITH t1(x) AS (SELECT 1) SELECT * FROM (WITH t2(y) AS (SELECT 2) SELECT y FROM t2) AS subq\",\n            write={\n                \"duckdb\": \"WITH t1(x) AS (SELECT 1) SELECT * FROM (WITH t2(y) AS (SELECT 2) SELECT y FROM t2) AS subq\",\n                \"tsql\": \"WITH t1(x) AS (SELECT 1), t2(y) AS (SELECT 2) SELECT * FROM (SELECT y AS y FROM t2) AS subq\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\nWITH c AS (\n  WITH b AS (\n    WITH a1 AS (\n      SELECT 1\n    ), a2 AS (\n      SELECT 2\n    )\n    SELECT * FROM a1, a2\n  )\n  SELECT *\n  FROM b\n)\nSELECT *\nFROM c\"\"\",\n            write={\n                \"duckdb\": \"WITH c AS (WITH b AS (WITH a1 AS (SELECT 1), a2 AS (SELECT 2) SELECT * FROM a1, a2) SELECT * FROM b) SELECT * FROM c\",\n                \"hive\": \"WITH a1 AS (SELECT 1), a2 AS (SELECT 2), b AS (SELECT * FROM a1, a2), c AS (SELECT * FROM b) SELECT * FROM c\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\nWITH subquery1 AS (\n  WITH tmp AS (\n    SELECT\n      *\n    FROM table0\n  )\n  SELECT\n    *\n  FROM tmp\n), subquery2 AS (\n  WITH tmp2 AS (\n    SELECT\n      *\n    FROM table1\n    WHERE\n      a IN subquery1\n  )\n  SELECT\n    *\n  FROM tmp2\n)\nSELECT\n  *\nFROM subquery2\n\"\"\",\n            write={\n                \"hive\": \"\"\"WITH tmp AS (\n  SELECT\n    *\n  FROM table0\n), subquery1 AS (\n  SELECT\n    *\n  FROM tmp\n), tmp2 AS (\n  SELECT\n    *\n  FROM table1\n  WHERE\n    a IN subquery1\n), subquery2 AS (\n  SELECT\n    *\n  FROM tmp2\n)\nSELECT\n  *\nFROM subquery2\"\"\",\n            },\n            pretty=True,\n        )\n\n    def test_unsupported_null_ordering(self):\n        # We'll transpile a portable query from the following dialects to MySQL / T-SQL, which\n        # both treat NULLs as small values, so the expected output queries should be equivalent\n        with_last_nulls = \"duckdb\"\n        with_small_nulls = \"spark\"\n        with_large_nulls = \"postgres\"\n\n        sql = \"SELECT * FROM t ORDER BY c\"\n        sql_nulls_last = \"SELECT * FROM t ORDER BY CASE WHEN c IS NULL THEN 1 ELSE 0 END, c\"\n        sql_nulls_first = \"SELECT * FROM t ORDER BY CASE WHEN c IS NULL THEN 1 ELSE 0 END DESC, c\"\n\n        for read_dialect, desc, nulls_first, expected_sql in (\n            (with_last_nulls, False, None, sql_nulls_last),\n            (with_last_nulls, True, None, sql),\n            (with_last_nulls, False, True, sql),\n            (with_last_nulls, True, True, sql_nulls_first),\n            (with_last_nulls, False, False, sql_nulls_last),\n            (with_last_nulls, True, False, sql),\n            (with_small_nulls, False, None, sql),\n            (with_small_nulls, True, None, sql),\n            (with_small_nulls, False, True, sql),\n            (with_small_nulls, True, True, sql_nulls_first),\n            (with_small_nulls, False, False, sql_nulls_last),\n            (with_small_nulls, True, False, sql),\n            (with_large_nulls, False, None, sql_nulls_last),\n            (with_large_nulls, True, None, sql_nulls_first),\n            (with_large_nulls, False, True, sql),\n            (with_large_nulls, True, True, sql_nulls_first),\n            (with_large_nulls, False, False, sql_nulls_last),\n            (with_large_nulls, True, False, sql),\n        ):\n            with self.subTest(\n                f\"read: {read_dialect}, descending: {desc}, nulls first: {nulls_first}\"\n            ):\n                sort_order = \" DESC\" if desc else \"\"\n                null_order = (\n                    \" NULLS FIRST\"\n                    if nulls_first\n                    else (\" NULLS LAST\" if nulls_first is not None else \"\")\n                )\n\n                expected_sql = f\"{expected_sql}{sort_order}\"\n                expression = parse_one(f\"{sql}{sort_order}{null_order}\", read=read_dialect)\n\n                self.assertEqual(expression.sql(dialect=\"mysql\"), expected_sql)\n                self.assertEqual(expression.sql(dialect=\"tsql\"), expected_sql)\n\n    def test_random(self):\n        self.validate_all(\n            \"RAND()\",\n            write={\n                \"bigquery\": \"RAND()\",\n                \"clickhouse\": \"randCanonical()\",\n                \"databricks\": \"RAND()\",\n                \"doris\": \"RAND()\",\n                \"drill\": \"RAND()\",\n                \"duckdb\": \"RANDOM()\",\n                \"hive\": \"RAND()\",\n                \"mysql\": \"RAND()\",\n                \"oracle\": \"DBMS_RANDOM.VALUE()\",\n                \"postgres\": \"RANDOM()\",\n                \"presto\": \"RAND()\",\n                \"spark\": \"RAND()\",\n                \"sqlite\": \"RANDOM()\",\n                \"tsql\": \"RAND()\",\n            },\n            read={\n                \"bigquery\": \"RAND()\",\n                \"clickhouse\": \"randCanonical()\",\n                \"databricks\": \"RAND()\",\n                \"doris\": \"RAND()\",\n                \"drill\": \"RAND()\",\n                \"duckdb\": \"RANDOM()\",\n                \"hive\": \"RAND()\",\n                \"mysql\": \"RAND()\",\n                \"oracle\": \"DBMS_RANDOM.VALUE()\",\n                \"postgres\": \"RANDOM()\",\n                \"presto\": \"RAND()\",\n                \"spark\": \"RAND()\",\n                \"sqlite\": \"RANDOM()\",\n                \"tsql\": \"RAND()\",\n            },\n        )\n\n    def test_array_any(self):\n        self.validate_all(\n            \"ARRAY_ANY(arr, x -> pred)\",\n            write={\n                \"\": \"ARRAY_ANY(arr, x -> pred)\",\n                \"bigquery\": \"(ARRAY_LENGTH(arr) = 0 OR ARRAY_LENGTH(ARRAY(SELECT x FROM UNNEST(arr) AS x WHERE pred)) <> 0)\",\n                \"clickhouse\": \"(LENGTH(arr) = 0 OR LENGTH(arrayFilter(x -> pred, arr)) <> 0)\",\n                \"databricks\": \"(SIZE(arr) = 0 OR SIZE(FILTER(arr, x -> pred)) <> 0)\",\n                \"doris\": UnsupportedError,\n                \"drill\": UnsupportedError,\n                \"duckdb\": \"(ARRAY_LENGTH(arr) = 0 OR ARRAY_LENGTH(LIST_FILTER(arr, x -> pred)) <> 0)\",\n                \"hive\": UnsupportedError,\n                \"mysql\": UnsupportedError,\n                \"oracle\": UnsupportedError,\n                \"postgres\": \"(ARRAY_LENGTH(arr, 1) = 0 OR ARRAY_LENGTH(ARRAY(SELECT x FROM UNNEST(arr) AS _t0(x) WHERE pred), 1) <> 0)\",\n                \"presto\": \"ANY_MATCH(arr, x -> pred)\",\n                \"redshift\": UnsupportedError,\n                \"snowflake\": UnsupportedError,\n                \"spark\": \"(SIZE(arr) = 0 OR SIZE(FILTER(arr, x -> pred)) <> 0)\",\n                \"spark2\": \"(SIZE(arr) = 0 OR SIZE(FILTER(arr, x -> pred)) <> 0)\",\n                \"sqlite\": UnsupportedError,\n                \"starrocks\": UnsupportedError,\n                \"tableau\": UnsupportedError,\n                \"teradata\": \"(CARDINALITY(arr) = 0 OR CARDINALITY(FILTER(arr, x -> pred)) <> 0)\",\n                \"trino\": \"ANY_MATCH(arr, x -> pred)\",\n                \"tsql\": UnsupportedError,\n            },\n        )\n\n    def test_truncate(self):\n        self.validate_identity(\"TRUNCATE TABLE table\")\n        self.validate_identity(\"TRUNCATE TABLE db.schema.test\")\n        self.validate_identity(\"TRUNCATE TABLE IF EXISTS db.schema.test\")\n        self.validate_identity(\"TRUNCATE TABLE t1, t2, t3\")\n\n    def test_create_sequence(self):\n        self.validate_identity(\"CREATE SEQUENCE seq\")\n        self.validate_identity(\n            \"CREATE TEMPORARY SEQUENCE seq AS SMALLINT START WITH 3 INCREMENT BY 2 MINVALUE 1 MAXVALUE 10 CACHE 1 NO CYCLE OWNED BY table.col\"\n        )\n        self.validate_identity(\n            \"CREATE SEQUENCE seq START WITH 1 NO MINVALUE NO MAXVALUE CYCLE NO CACHE\"\n        )\n        self.validate_identity(\"CREATE OR REPLACE TEMPORARY SEQUENCE seq INCREMENT BY 1 NO CYCLE\")\n        self.validate_identity(\n            \"CREATE OR REPLACE SEQUENCE IF NOT EXISTS seq COMMENT='test comment' ORDER\"\n        )\n        self.validate_identity(\n            \"CREATE SEQUENCE schema.seq SHARING=METADATA NOORDER NOKEEP SCALE EXTEND SHARD EXTEND SESSION\"\n        )\n        self.validate_identity(\n            \"CREATE SEQUENCE schema.seq SHARING=DATA ORDER KEEP NOSCALE NOSHARD GLOBAL\"\n        )\n        self.validate_identity(\n            \"CREATE SEQUENCE schema.seq SHARING=DATA NOCACHE NOCYCLE SCALE NOEXTEND\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE TEMPORARY SEQUENCE seq AS BIGINT INCREMENT BY 2 MINVALUE 1 CACHE 1 NOMAXVALUE NO CYCLE OWNED BY NONE\"\"\",\n            \"\"\"CREATE TEMPORARY SEQUENCE seq AS BIGINT INCREMENT BY 2 MINVALUE 1 CACHE 1 NOMAXVALUE NO CYCLE\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"CREATE TEMPORARY SEQUENCE seq START 1\"\"\",\n            \"\"\"CREATE TEMPORARY SEQUENCE seq START WITH 1\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"CREATE TEMPORARY SEQUENCE seq START WITH = 1 INCREMENT BY = 2\"\"\",\n            \"\"\"CREATE TEMPORARY SEQUENCE seq START WITH 1 INCREMENT BY 2\"\"\",\n        )\n\n    def test_reserved_keywords(self):\n        order = exp.select(\"*\").from_(\"order\")\n\n        for dialect in (\"duckdb\", \"presto\", \"redshift\"):\n            dialect = Dialect.get_or_raise(dialect)\n            self.assertEqual(\n                order.sql(dialect=dialect),\n                f\"SELECT * FROM {dialect.IDENTIFIER_START}order{dialect.IDENTIFIER_END}\",\n            )\n\n        self.validate_identity(\n            \"\"\"SELECT partition.d FROM t PARTITION (d)\"\"\",\n            \"\"\"SELECT partition.d FROM t AS PARTITION(d)\"\"\",\n        )\n\n    def test_string_functions(self):\n        for pad_func in (\"LPAD\", \"RPAD\"):\n            ch_alias = \"LEFTPAD\" if pad_func == \"LPAD\" else \"RIGHTPAD\"\n            for fill_pattern in (\"\", \", ' '\"):\n                with self.subTest(f\"Testing {pad_func}() with pattern {fill_pattern}\"):\n                    self.validate_all(\n                        f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                        read={\n                            \"snowflake\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"databricks\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"spark\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"postgres\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"clickhouse\": f\"SELECT {ch_alias}('bar', 5{fill_pattern})\",\n                        },\n                        write={\n                            \"\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"spark\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"postgres\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"clickhouse\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"snowflake\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"databricks\": f\"SELECT {pad_func}('bar', 5{fill_pattern})\",\n                            \"duckdb\": f\"SELECT {pad_func}('bar', 5, ' ')\",\n                            \"mysql\": f\"SELECT {pad_func}('bar', 5, ' ')\",\n                            \"hive\": f\"SELECT {pad_func}('bar', 5, ' ')\",\n                            \"spark2\": f\"SELECT {pad_func}('bar', 5, ' ')\",\n                            \"presto\": f\"SELECT {pad_func}('bar', 5, ' ')\",\n                            \"trino\": f\"SELECT {pad_func}('bar', 5, ' ')\",\n                        },\n                    )\n\n    def test_generate_date_array(self):\n        self.validate_all(\n            \"SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))\",\n            write={\n                \"bigquery\": \"SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), INTERVAL '1' WEEK))\",\n                \"databricks\": \"SELECT * FROM EXPLODE(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), INTERVAL '1' WEEK))\",\n                \"duckdb\": \"SELECT * FROM UNNEST(CAST(GENERATE_SERIES(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), INTERVAL '1' WEEK) AS DATE[]))\",\n                \"mysql\": \"WITH RECURSIVE _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATE_ADD(date_value, INTERVAL 1 WEEK) AS DATE) FROM _generated_dates WHERE CAST(DATE_ADD(date_value, INTERVAL 1 WEEK) AS DATE) <= CAST('2020-02-01' AS DATE)) SELECT * FROM (SELECT date_value FROM _generated_dates) AS _generated_dates\",\n                \"postgres\": \"SELECT * FROM (SELECT CAST(value AS DATE) FROM GENERATE_SERIES(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), INTERVAL '1 WEEK') AS _t(value)) AS _unnested_generate_series\",\n                \"presto\": \"SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))\",\n                \"redshift\": \"WITH RECURSIVE _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATEADD(WEEK, 1, date_value) AS DATE) FROM _generated_dates WHERE CAST(DATEADD(WEEK, 1, date_value) AS DATE) <= CAST('2020-02-01' AS DATE)) SELECT * FROM (SELECT date_value FROM _generated_dates) AS _generated_dates\",\n                \"snowflake\": \"SELECT * FROM (SELECT DATEADD(WEEK, CAST(value AS INT), CAST('2020-01-01' AS DATE)) AS value FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(WEEK, CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE)) + 1))) AS _t0(seq, key, path, index, value, this))\",\n                \"spark\": \"SELECT * FROM EXPLODE(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), INTERVAL '1' WEEK))\",\n                \"trino\": \"SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))\",\n                \"tsql\": \"WITH _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATEADD(WEEK, 1, date_value) AS DATE) FROM _generated_dates WHERE CAST(DATEADD(WEEK, 1, date_value) AS DATE) <= CAST('2020-02-01' AS DATE)) SELECT * FROM (SELECT date_value AS date_value FROM _generated_dates) AS _generated_dates\",\n            },\n        )\n        self.validate_all(\n            \"WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates\",\n            write={\n                \"mysql\": \"WITH RECURSIVE _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATE_ADD(date_value, INTERVAL 1 WEEK) AS DATE) FROM _generated_dates WHERE CAST(DATE_ADD(date_value, INTERVAL 1 WEEK) AS DATE) <= CAST('2020-02-01' AS DATE)), dates AS (SELECT * FROM (SELECT date_value FROM _generated_dates) AS _generated_dates) SELECT * FROM dates\",\n                \"redshift\": \"WITH RECURSIVE _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATEADD(WEEK, 1, date_value) AS DATE) FROM _generated_dates WHERE CAST(DATEADD(WEEK, 1, date_value) AS DATE) <= CAST('2020-02-01' AS DATE)), dates AS (SELECT * FROM (SELECT date_value FROM _generated_dates) AS _generated_dates) SELECT * FROM dates\",\n                \"tsql\": \"WITH _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATEADD(WEEK, 1, date_value) AS DATE) FROM _generated_dates WHERE CAST(DATEADD(WEEK, 1, date_value) AS DATE) <= CAST('2020-02-01' AS DATE)), dates AS (SELECT * FROM (SELECT date_value AS date_value FROM _generated_dates) AS _generated_dates) SELECT * FROM dates\",\n            },\n        )\n        self.validate_all(\n            \"WITH dates1 AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))), dates2 AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-03-01', INTERVAL 1 MONTH))) SELECT * FROM dates1 CROSS JOIN dates2\",\n            write={\n                \"mysql\": \"WITH RECURSIVE _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATE_ADD(date_value, INTERVAL 1 WEEK) AS DATE) FROM _generated_dates WHERE CAST(DATE_ADD(date_value, INTERVAL 1 WEEK) AS DATE) <= CAST('2020-02-01' AS DATE)), _generated_dates_1(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATE_ADD(date_value, INTERVAL 1 MONTH) AS DATE) FROM _generated_dates_1 WHERE CAST(DATE_ADD(date_value, INTERVAL 1 MONTH) AS DATE) <= CAST('2020-03-01' AS DATE)), dates1 AS (SELECT * FROM (SELECT date_value FROM _generated_dates) AS _generated_dates), dates2 AS (SELECT * FROM (SELECT date_value FROM _generated_dates_1) AS _generated_dates_1) SELECT * FROM dates1 CROSS JOIN dates2\",\n                \"redshift\": \"WITH RECURSIVE _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATEADD(WEEK, 1, date_value) AS DATE) FROM _generated_dates WHERE CAST(DATEADD(WEEK, 1, date_value) AS DATE) <= CAST('2020-02-01' AS DATE)), _generated_dates_1(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATEADD(MONTH, 1, date_value) AS DATE) FROM _generated_dates_1 WHERE CAST(DATEADD(MONTH, 1, date_value) AS DATE) <= CAST('2020-03-01' AS DATE)), dates1 AS (SELECT * FROM (SELECT date_value FROM _generated_dates) AS _generated_dates), dates2 AS (SELECT * FROM (SELECT date_value FROM _generated_dates_1) AS _generated_dates_1) SELECT * FROM dates1 CROSS JOIN dates2\",\n                \"tsql\": \"WITH _generated_dates(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATEADD(WEEK, 1, date_value) AS DATE) FROM _generated_dates WHERE CAST(DATEADD(WEEK, 1, date_value) AS DATE) <= CAST('2020-02-01' AS DATE)), _generated_dates_1(date_value) AS (SELECT CAST('2020-01-01' AS DATE) AS date_value UNION ALL SELECT CAST(DATEADD(MONTH, 1, date_value) AS DATE) FROM _generated_dates_1 WHERE CAST(DATEADD(MONTH, 1, date_value) AS DATE) <= CAST('2020-03-01' AS DATE)), dates1 AS (SELECT * FROM (SELECT date_value AS date_value FROM _generated_dates) AS _generated_dates), dates2 AS (SELECT * FROM (SELECT date_value AS date_value FROM _generated_dates_1) AS _generated_dates_1) SELECT * FROM dates1 CROSS JOIN dates2\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK)) AS _q(date_week)\",\n            write={\n                \"mysql\": \"WITH RECURSIVE _generated_dates(date_week) AS (SELECT CAST('2020-01-01' AS DATE) AS date_week UNION ALL SELECT CAST(DATE_ADD(date_week, INTERVAL 1 WEEK) AS DATE) FROM _generated_dates WHERE CAST(DATE_ADD(date_week, INTERVAL 1 WEEK) AS DATE) <= CAST('2020-02-01' AS DATE)) SELECT * FROM (SELECT date_week FROM _generated_dates) AS _generated_dates\",\n                \"redshift\": \"WITH RECURSIVE _generated_dates(date_week) AS (SELECT CAST('2020-01-01' AS DATE) AS date_week UNION ALL SELECT CAST(DATEADD(WEEK, 1, date_week) AS DATE) FROM _generated_dates WHERE CAST(DATEADD(WEEK, 1, date_week) AS DATE) <= CAST('2020-02-01' AS DATE)) SELECT * FROM (SELECT date_week FROM _generated_dates) AS _generated_dates\",\n                \"snowflake\": \"SELECT * FROM (SELECT DATEADD(WEEK, CAST(date_week AS INT), CAST('2020-01-01' AS DATE)) AS date_week FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(WEEK, CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE)) + 1))) AS _q(seq, key, path, index, date_week, this)) AS _q(date_week)\",\n                \"tsql\": \"WITH _generated_dates(date_week) AS (SELECT CAST('2020-01-01' AS DATE) AS date_week UNION ALL SELECT CAST(DATEADD(WEEK, 1, date_week) AS DATE) FROM _generated_dates WHERE CAST(DATEADD(WEEK, 1, date_week) AS DATE) <= CAST('2020-02-01' AS DATE)) SELECT * FROM (SELECT date_week AS date_week FROM _generated_dates) AS _generated_dates\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ARRAY_LENGTH(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM (SELECT DATEADD(WEEK, CAST(value AS INT), CAST('2020-01-01' AS DATE)) AS value FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(WEEK, CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE)) + 1))) AS _t0(seq, key, path, index, value, this))))\",\n            },\n        )\n\n    def test_set_operation_specifiers(self):\n        self.validate_all(\n            \"SELECT 1 EXCEPT ALL SELECT 1\",\n            write={\n                \"\": \"SELECT 1 EXCEPT ALL SELECT 1\",\n                \"bigquery\": UnsupportedError,\n                \"clickhouse\": \"SELECT 1 EXCEPT SELECT 1\",\n                \"databricks\": \"SELECT 1 EXCEPT ALL SELECT 1\",\n                \"duckdb\": \"SELECT 1 EXCEPT ALL SELECT 1\",\n                \"mysql\": \"SELECT 1 EXCEPT ALL SELECT 1\",\n                \"oracle\": \"SELECT 1 EXCEPT ALL SELECT 1\",\n                \"postgres\": \"SELECT 1 EXCEPT ALL SELECT 1\",\n                \"presto\": UnsupportedError,\n                \"redshift\": UnsupportedError,\n                \"snowflake\": UnsupportedError,\n                \"spark\": \"SELECT 1 EXCEPT ALL SELECT 1\",\n                \"sqlite\": UnsupportedError,\n                \"starrocks\": UnsupportedError,\n                \"trino\": \"SELECT 1 EXCEPT ALL SELECT 1\",\n                \"tsql\": UnsupportedError,\n            },\n        )\n\n    def test_normalize(self):\n        for form in (\"\", \", nfkc\"):\n            with self.subTest(f\"Testing NORMALIZE('str'{form}) roundtrip\"):\n                self.validate_all(\n                    f\"SELECT NORMALIZE('str'{form})\",\n                    read={\n                        \"presto\": f\"SELECT NORMALIZE('str'{form})\",\n                        \"trino\": f\"SELECT NORMALIZE('str'{form})\",\n                        \"bigquery\": f\"SELECT NORMALIZE('str'{form})\",\n                    },\n                    write={\n                        \"presto\": f\"SELECT NORMALIZE('str'{form})\",\n                        \"trino\": f\"SELECT NORMALIZE('str'{form})\",\n                        \"bigquery\": f\"SELECT NORMALIZE('str'{form})\",\n                    },\n                )\n\n        self.assertIsInstance(parse_one(\"NORMALIZE('str', NFD)\").args.get(\"form\"), exp.Var)\n\n    def test_coalesce(self):\n        \"\"\"\n        Validate that \"expressions\" is a list for all the exp.Coalesce instances; This is important\n        as some optimizer rules are coalesce specific and will iterate on \"expressions\"\n        \"\"\"\n\n        # Check the 2-arg aliases\n        for func in (\"COALESCE\", \"IFNULL\", \"NVL\"):\n            self.assertIsInstance(self.parse_one(f\"{func}(1, 2)\").expressions, list)\n\n        # Check the varlen case\n        coalesce = self.parse_one(\"COALESCE(x, y, z)\")\n        self.assertIsInstance(coalesce.expressions, list)\n        self.assertIsNone(coalesce.args.get(\"is_nvl\"))\n\n        # Check Oracle's NVL which is decoupled from COALESCE\n        oracle_nvl = parse_one(\"NVL(x, y)\", read=\"oracle\")\n        self.assertIsInstance(oracle_nvl.expressions, list)\n        self.assertTrue(oracle_nvl.args.get(\"is_nvl\"))\n\n        # Check T-SQL's ISNULL which is parsed into exp.Coalesce\n        self.assertIsInstance(parse_one(\"ISNULL(x, y)\", read=\"tsql\").expressions, list)\n\n    def test_trim(self):\n        self.validate_all(\n            \"TRIM('abc', 'a')\",\n            read={\n                \"bigquery\": \"TRIM('abc', 'a')\",\n                \"snowflake\": \"TRIM('abc', 'a')\",\n                \"hive\": \"TRIM('abc', 'a')\",\n                \"spark2\": \"TRIM('a', 'abc')\",\n                \"spark\": \"TRIM('a', 'abc')\",\n                \"databricks\": \"TRIM('a', 'abc')\",\n            },\n            write={\n                \"bigquery\": \"TRIM('abc', 'a')\",\n                \"snowflake\": \"TRIM('abc', 'a')\",\n                \"hive\": \"TRIM('a' FROM 'abc')\",\n                \"spark2\": \"TRIM('a' FROM 'abc')\",\n                \"spark\": \"TRIM('a' FROM 'abc')\",\n                \"databricks\": \"TRIM('a' FROM 'abc')\",\n            },\n        )\n\n        self.validate_all(\n            \"LTRIM('Hello World', 'H')\",\n            read={\n                \"\": \"LTRIM('Hello World', 'H')\",\n                \"oracle\": \"LTRIM('Hello World', 'H')\",\n                \"clickhouse\": \"TRIM(LEADING 'H' FROM 'Hello World')\",\n                \"snowflake\": \"LTRIM('Hello World', 'H')\",\n                \"bigquery\": \"LTRIM('Hello World', 'H')\",\n                \"hive\": \"LTRIM('Hello World', 'H')\",\n                \"spark2\": \"LTRIM('H', 'Hello World')\",\n                \"spark\": \"LTRIM('H', 'Hello World')\",\n                \"databricks\": \"LTRIM('H', 'Hello World')\",\n            },\n            write={\n                \"clickhouse\": \"TRIM(LEADING 'H' FROM 'Hello World')\",\n                \"oracle\": \"LTRIM('Hello World', 'H')\",\n                \"snowflake\": \"LTRIM('Hello World', 'H')\",\n                \"bigquery\": \"LTRIM('Hello World', 'H')\",\n                \"hive\": \"TRIM(LEADING 'H' FROM 'Hello World')\",\n                \"spark2\": \"TRIM(LEADING 'H' FROM 'Hello World')\",\n                \"spark\": \"TRIM(LEADING 'H' FROM 'Hello World')\",\n                \"databricks\": \"TRIM(LEADING 'H' FROM 'Hello World')\",\n            },\n        )\n\n        self.validate_all(\n            \"RTRIM('Hello World', 'd')\",\n            read={\n                \"\": \"RTRIM('Hello World', 'd')\",\n                \"clickhouse\": \"TRIM(TRAILING 'd' FROM 'Hello World')\",\n                \"oracle\": \"RTRIM('Hello World', 'd')\",\n                \"snowflake\": \"RTRIM('Hello World', 'd')\",\n                \"bigquery\": \"RTRIM('Hello World', 'd')\",\n                \"hive\": \"RTRIM('Hello World', 'd')\",\n                \"spark2\": \"RTRIM('d', 'Hello World')\",\n                \"spark\": \"RTRIM('d', 'Hello World')\",\n                \"databricks\": \"RTRIM('d', 'Hello World')\",\n            },\n            write={\n                \"clickhouse\": \"TRIM(TRAILING 'd' FROM 'Hello World')\",\n                \"oracle\": \"RTRIM('Hello World', 'd')\",\n                \"snowflake\": \"RTRIM('Hello World', 'd')\",\n                \"bigquery\": \"RTRIM('Hello World', 'd')\",\n                \"hive\": \"TRIM(TRAILING 'd' FROM 'Hello World')\",\n                \"spark2\": \"TRIM(TRAILING 'd' FROM 'Hello World')\",\n                \"spark\": \"TRIM(TRAILING 'd' FROM 'Hello World')\",\n                \"databricks\": \"TRIM(TRAILING 'd' FROM 'Hello World')\",\n            },\n        )\n\n        self.validate_all(\n            \"LTRIM('Hello World')\",\n            read={\n                \"\": \"LTRIM('Hello World')\",\n                \"clickhouse\": \"LTRIM('Hello World')\",\n                \"oracle\": \"LTRIM('Hello World')\",\n                \"snowflake\": \"LTRIM('Hello World')\",\n                \"bigquery\": \"LTRIM('Hello World')\",\n                \"hive\": \"LTRIM('Hello World')\",\n                \"spark2\": \"LTRIM('Hello World')\",\n                \"spark\": \"LTRIM('Hello World')\",\n                \"databricks\": \"LTRIM('Hello World')\",\n            },\n            write={\n                \"clickhouse\": \"LTRIM('Hello World')\",\n                \"oracle\": \"LTRIM('Hello World')\",\n                \"snowflake\": \"LTRIM('Hello World')\",\n                \"bigquery\": \"LTRIM('Hello World')\",\n                \"hive\": \"LTRIM('Hello World')\",\n                \"spark2\": \"LTRIM('Hello World')\",\n                \"spark\": \"LTRIM('Hello World')\",\n                \"databricks\": \"LTRIM('Hello World')\",\n            },\n        )\n\n        self.validate_all(\n            \"RTRIM('Hello World')\",\n            read={\n                \"\": \"RTRIM('Hello World')\",\n                \"clickhouse\": \"RTRIM('Hello World')\",\n                \"oracle\": \"RTRIM('Hello World')\",\n                \"snowflake\": \"RTRIM('Hello World')\",\n                \"bigquery\": \"RTRIM('Hello World')\",\n                \"hive\": \"RTRIM('Hello World')\",\n                \"spark2\": \"RTRIM('Hello World')\",\n                \"spark\": \"RTRIM('Hello World')\",\n                \"databricks\": \"RTRIM('Hello World')\",\n            },\n            write={\n                \"clickhouse\": \"RTRIM('Hello World')\",\n                \"oracle\": \"RTRIM('Hello World')\",\n                \"snowflake\": \"RTRIM('Hello World')\",\n                \"bigquery\": \"RTRIM('Hello World')\",\n                \"hive\": \"RTRIM('Hello World')\",\n                \"spark2\": \"RTRIM('Hello World')\",\n                \"spark\": \"RTRIM('Hello World')\",\n                \"databricks\": \"RTRIM('Hello World')\",\n            },\n        )\n\n    def test_uuid(self):\n        self.validate_all(\n            \"UUID()\",\n            read={\n                \"hive\": \"UUID()\",\n                \"spark2\": \"UUID()\",\n                \"spark\": \"UUID()\",\n                \"databricks\": \"UUID()\",\n                \"duckdb\": \"UUID()\",\n                \"presto\": \"UUID()\",\n                \"trino\": \"UUID()\",\n                \"mysql\": \"UUID()\",\n                \"postgres\": \"GEN_RANDOM_UUID()\",\n                \"snowflake\": \"UUID_STRING()\",\n                \"tsql\": \"NEWID()\",\n            },\n            write={\n                \"hive\": \"UUID()\",\n                \"spark2\": \"UUID()\",\n                \"spark\": \"UUID()\",\n                \"databricks\": \"UUID()\",\n                \"duckdb\": \"UUID()\",\n                \"presto\": \"UUID()\",\n                \"trino\": \"UUID()\",\n                \"mysql\": \"UUID()\",\n                \"postgres\": \"GEN_RANDOM_UUID()\",\n                \"bigquery\": \"GENERATE_UUID()\",\n                \"snowflake\": \"UUID_STRING()\",\n                \"tsql\": \"NEWID()\",\n            },\n        )\n\n    def test_escaped_identifier_delimiter(self):\n        for dialect in (\"databricks\", \"hive\", \"mysql\", \"spark2\", \"spark\"):\n            with self.subTest(f\"Testing escaped backtick in identifier name for {dialect}\"):\n                self.validate_all(\n                    'SELECT 1 AS \"x`\"',\n                    read={\n                        dialect: \"SELECT 1 AS `x```\",\n                    },\n                    write={\n                        dialect: \"SELECT 1 AS `x```\",\n                    },\n                )\n\n        for dialect in (\n            \"\",\n            \"clickhouse\",\n            \"duckdb\",\n            \"postgres\",\n            \"presto\",\n            \"trino\",\n            \"redshift\",\n            \"snowflake\",\n            \"sqlite\",\n        ):\n            with self.subTest(f\"Testing escaped double-quote in identifier name for {dialect}\"):\n                self.validate_all(\n                    'SELECT 1 AS \"x\"\"\"',\n                    read={\n                        dialect: 'SELECT 1 AS \"x\"\"\"',\n                    },\n                    write={\n                        dialect: 'SELECT 1 AS \"x\"\"\"',\n                    },\n                )\n\n        for dialect in (\"clickhouse\", \"sqlite\"):\n            with self.subTest(f\"Testing escaped backtick in identifier name for {dialect}\"):\n                self.validate_all(\n                    'SELECT 1 AS \"x`\"',\n                    read={\n                        dialect: \"SELECT 1 AS `x```\",\n                    },\n                    write={\n                        dialect: 'SELECT 1 AS \"x`\"',\n                    },\n                )\n\n        self.validate_all(\n            'SELECT 1 AS \"x`\"',\n            read={\n                \"clickhouse\": \"SELECT 1 AS `x\\\\``\",\n            },\n            write={\n                \"clickhouse\": 'SELECT 1 AS \"x`\"',\n            },\n        )\n        for name in ('\"x\\\\\"\"', '`x\"`'):\n            with self.subTest(f\"Testing ClickHouse delimiter escaping: {name}\"):\n                self.validate_all(\n                    'SELECT 1 AS \"x\"\"\"',\n                    read={\n                        \"clickhouse\": f\"SELECT 1 AS {name}\",\n                    },\n                    write={\n                        \"clickhouse\": 'SELECT 1 AS \"x\"\"\"',\n                    },\n                )\n\n        for name in (\"[[x]]]\", '\"[x]\"'):\n            with self.subTest(f\"Testing T-SQL delimiter escaping: {name}\"):\n                self.validate_all(\n                    'SELECT 1 AS \"[x]\"',\n                    read={\n                        \"tsql\": f\"SELECT 1 AS {name}\",\n                    },\n                    write={\n                        \"tsql\": \"SELECT 1 AS [[x]]]\",\n                    },\n                )\n        for name in ('[x\"]', '\"x\"\"\"'):\n            with self.subTest(f\"Testing T-SQL delimiter escaping: {name}\"):\n                self.validate_all(\n                    'SELECT 1 AS \"x\"\"\"',\n                    read={\n                        \"tsql\": f\"SELECT 1 AS {name}\",\n                    },\n                    write={\n                        \"tsql\": 'SELECT 1 AS [x\"]',\n                    },\n                )\n\n    def test_median(self):\n        for suffix in (\n            \"\",\n            \" OVER ()\",\n        ):\n            self.validate_all(\n                f\"MEDIAN(x){suffix}\",\n                read={\n                    \"snowflake\": f\"MEDIAN(x){suffix}\",\n                    \"duckdb\": f\"MEDIAN(x){suffix}\",\n                    \"spark\": f\"MEDIAN(x){suffix}\",\n                    \"databricks\": f\"MEDIAN(x){suffix}\",\n                    \"redshift\": f\"MEDIAN(x){suffix}\",\n                    \"oracle\": f\"MEDIAN(x){suffix}\",\n                },\n                write={\n                    \"snowflake\": f\"MEDIAN(x){suffix}\",\n                    \"duckdb\": f\"MEDIAN(x){suffix}\",\n                    \"spark\": f\"MEDIAN(x){suffix}\",\n                    \"databricks\": f\"MEDIAN(x){suffix}\",\n                    \"redshift\": f\"MEDIAN(x){suffix}\",\n                    \"oracle\": f\"MEDIAN(x){suffix}\",\n                    \"clickhouse\": f\"median(x){suffix}\",\n                    \"postgres\": f\"PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x){suffix}\",\n                },\n            )\n\n    def test_current_schema(self):\n        self.validate_all(\n            \"CURRENT_SCHEMA()\",\n            read={\n                \"mysql\": \"SCHEMA()\",\n                \"postgres\": \"CURRENT_SCHEMA()\",\n                \"tsql\": \"SCHEMA_NAME()\",\n            },\n            write={\n                \"sqlite\": \"'main'\",\n                \"mysql\": \"SCHEMA()\",\n                \"postgres\": \"CURRENT_SCHEMA\",\n                \"tsql\": \"SCHEMA_NAME()\",\n            },\n        )\n\n    def test_integer_hex_strings(self):\n        # Hex strings such as 0xCC represent INTEGER values in the read dialects\n        integer_dialects = (\"bigquery\", \"clickhouse\")\n        for read_dialect in integer_dialects:\n            for write_dialect in (\n                \"\",\n                \"duckdb\",\n                \"databricks\",\n                \"snowflake\",\n                \"spark\",\n                \"redshift\",\n            ):\n                with self.subTest(f\"Testing hex string -> INTEGER evaluation for {read_dialect}\"):\n                    self.assertEqual(\n                        parse_one(\"SELECT 0xCC\", read=read_dialect).sql(write_dialect), \"SELECT 204\"\n                    )\n\n            for other_integer_dialects in integer_dialects:\n                self.assertEqual(\n                    parse_one(\"SELECT 0xCC\", read=read_dialect).sql(other_integer_dialects),\n                    \"SELECT 0xCC\",\n                )\n\n    def test_ascii(self):\n        self.validate_all(\n            \"ASCII('A')\",\n            read={\n                \"bigquery\": \"ASCII('A')\",\n                \"clickhouse\": \"ASCII('A')\",\n                \"databricks\": \"ASCII('A')\",\n                \"hive\": \"ASCII('A')\",\n                \"mysql\": \"ASCII('A')\",\n                \"postgres\": \"ASCII('A')\",\n                \"redshift\": \"ASCII('A')\",\n                \"snowflake\": \"ASCII('A')\",\n                \"tsql\": \"ASCII('A')\",\n            },\n            write={\n                \"bigquery\": \"ASCII('A')\",\n                \"clickhouse\": \"ASCII('A')\",\n                \"databricks\": \"ASCII('A')\",\n                \"hive\": \"ASCII('A')\",\n                \"mysql\": \"ASCII('A')\",\n                \"postgres\": \"ASCII('A')\",\n                \"redshift\": \"ASCII('A')\",\n                \"snowflake\": \"ASCII('A')\",\n                \"tsql\": \"ASCII('A')\",\n            },\n        )\n\n    def test_between(self):\n        between = exp.column(\"x\").between(1, 2)\n        self.assertEqual(between.sql(\"postgres\"), \"x BETWEEN 1 AND 2\")\n        self.assertEqual(between.sql(\"redshift\"), \"x BETWEEN 1 AND 2\")\n        self.assertFalse(\"symmetric\" in between.args)\n\n        self.validate_all(\n            \"SELECT x BETWEEN 2 AND 10\",\n            read={\n                \"\": \"SELECT x BETWEEN 2 AND 10\",\n                \"clickhouse\": \"SELECT x BETWEEN 2 AND 10\",\n                \"dremio\": \"SELECT x BETWEEN 2 AND 10\",\n                \"duckdb\": \"SELECT x BETWEEN 2 AND 10\",\n                \"materialize\": \"SELECT x BETWEEN 2 AND 10\",\n                \"mysql\": \"SELECT x BETWEEN 2 AND 10\",\n                \"oracle\": \"SELECT x BETWEEN 2 AND 10\",\n                \"postgres\": \"SELECT x BETWEEN 2 AND 10\",\n                \"redshift\": \"SELECT x BETWEEN 2 AND 10\",\n                \"risingwave\": \"SELECT x BETWEEN 2 AND 10\",\n                \"tsql\": \"SELECT x BETWEEN 2 AND 10\",\n            },\n            write={\n                \"\": \"SELECT x BETWEEN 2 AND 10\",\n                \"clickhouse\": \"SELECT x BETWEEN 2 AND 10\",\n                \"dremio\": \"SELECT x BETWEEN 2 AND 10\",\n                \"duckdb\": \"SELECT x BETWEEN 2 AND 10\",\n                \"materialize\": \"SELECT x BETWEEN 2 AND 10\",\n                \"mysql\": \"SELECT x BETWEEN 2 AND 10\",\n                \"oracle\": \"SELECT x BETWEEN 2 AND 10\",\n                \"postgres\": \"SELECT x BETWEEN 2 AND 10\",\n                \"redshift\": \"SELECT x BETWEEN 2 AND 10\",\n                \"risingwave\": \"SELECT x BETWEEN 2 AND 10\",\n                \"tsql\": \"SELECT x BETWEEN 2 AND 10\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT x BETWEEN SYMMETRIC 10 AND 2\",\n            write={\n                \"\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n                \"clickhouse\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n                \"dremio\": \"SELECT x BETWEEN SYMMETRIC 10 AND 2\",\n                \"duckdb\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n                \"materialize\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n                \"mysql\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n                \"oracle\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n                \"postgres\": \"SELECT x BETWEEN SYMMETRIC 10 AND 2\",\n                \"redshift\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n                \"risingwave\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n                \"tsql\": \"SELECT (x BETWEEN 10 AND 2 OR x BETWEEN 2 AND 10)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT x BETWEEN ASYMMETRIC 10 AND 2\",\n            write={\n                \"\": \"SELECT x BETWEEN 10 AND 2\",\n                \"clickhouse\": \"SELECT x BETWEEN 10 AND 2\",\n                \"dremio\": \"SELECT x BETWEEN ASYMMETRIC 10 AND 2\",\n                \"duckdb\": \"SELECT x BETWEEN 10 AND 2\",\n                \"materialize\": \"SELECT x BETWEEN 10 AND 2\",\n                \"mysql\": \"SELECT x BETWEEN 10 AND 2\",\n                \"oracle\": \"SELECT x BETWEEN 10 AND 2\",\n                \"postgres\": \"SELECT x BETWEEN ASYMMETRIC 10 AND 2\",\n                \"redshift\": \"SELECT x BETWEEN 10 AND 2\",\n                \"risingwave\": \"SELECT x BETWEEN 10 AND 2\",\n                \"tsql\": \"SELECT x BETWEEN 10 AND 2\",\n            },\n        )\n\n    def test_like_quantifiers(self):\n        for quantifier in (\"ANY\", \"ALL\"):\n            connector = \"OR\" if quantifier == \"ANY\" else \"AND\"\n\n            with self.subTest(f\"Testing LIKE {quantifier}\"):\n                self.validate_all(\n                    f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                    read={\n                        \"\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                        \"bigquery\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                        \"snowflake\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                        \"spark\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                        \"databricks\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                    },\n                    write={\n                        \"bigquery\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                        \"snowflake\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                        \"spark\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                        \"databricks\": f\"SELECT col LIKE {quantifier} (x, y, z)\",\n                        \"duckdb\": f\"SELECT (col LIKE x {connector} col LIKE y) {connector} col LIKE z\",\n                    },\n                )\n\n            with self.subTest(f\"Testing ILIKE {quantifier}\"):\n                self.validate_all(\n                    f\"SELECT col ILIKE {quantifier} (x, y, z)\",\n                    write={\n                        \"\": f\"SELECT col ILIKE {quantifier} (x, y, z)\",\n                        \"duckdb\": f\"SELECT (col ILIKE x {connector} col ILIKE y) {connector} col ILIKE z\",\n                    },\n                )\n\n        self.validate_all(\n            \"SELECT 'foo' LIKE ANY((('bar', 'fo%')))\",\n            write={\n                \"\": \"SELECT 'foo' LIKE ANY((('bar', 'fo%')))\",\n                \"duckdb\": \"SELECT 'foo' LIKE 'bar' OR 'foo' LIKE 'fo%'\",\n            },\n        )\n\n    def test_date_to_unix_date(self):\n        self.validate_all(\n            \"DATE_FROM_UNIX_DATE(1)\",\n            write={\n                \"\": \"DATE_ADD(CAST('1970-01-01' AS DATE), 1, 'DAY')\",\n                \"bigquery\": \"DATE_FROM_UNIX_DATE(1)\",\n                \"spark\": \"DATE_FROM_UNIX_DATE(1)\",\n                \"databricks\": \"DATE_FROM_UNIX_DATE(1)\",\n                \"snowflake\": \"DATEADD(DAY, 1, CAST('1970-01-01' AS DATE))\",\n                \"duckdb\": \"CAST('1970-01-01' AS DATE) + INTERVAL 1 DAY\",\n                \"redshift\": \"DATEADD(DAY, 1, CAST('1970-01-01' AS DATE))\",\n                \"presto\": \"DATE_ADD('DAY', 1, CAST('1970-01-01' AS DATE))\",\n                \"trino\": \"DATE_ADD('DAY', 1, CAST('1970-01-01' AS DATE))\",\n            },\n        )\n\n    def test_week_of_year(self):\n        self.validate_all(\n            \"WEEKOFYEAR(CAST('2025-01-01' AS DATE))\",\n            write={\n                \"duckdb\": \"WEEKOFYEAR(CAST('2025-01-01' AS DATE))\",\n                \"exasol\": \"WEEK(CAST('2025-01-01' AS DATE))\",\n                \"hive\": \"WEEKOFYEAR(CAST('2025-01-01' AS DATE))\",\n                \"mysql\": \"WEEKOFYEAR(CAST('2025-01-01' AS DATE))\",\n                \"spark\": \"WEEKOFYEAR(CAST('2025-01-01' AS DATE))\",\n                \"snowflake\": \"WEEKISO(CAST('2025-01-01' AS DATE))\",\n            },\n        )\n\n    def test_justify(self):\n        self.validate_all(\n            \"JUSTIFY_DAYS(INTERVAL '1' DAY)\",\n            read={\n                \"\": \"JUSTIFY_DAYS(INTERVAL '1' DAY)\",\n                \"bigquery\": \"JUSTIFY_DAYS(INTERVAL '1' DAY)\",\n                \"postgres\": \"JUSTIFY_DAYS(INTERVAL '1 DAY')\",\n                \"materialize\": \"JUSTIFY_DAYS(INTERVAL '1 DAY')\",\n            },\n            write={\n                \"bigquery\": \"JUSTIFY_DAYS(INTERVAL '1' DAY)\",\n                \"postgres\": \"JUSTIFY_DAYS(INTERVAL '1 DAY')\",\n                \"materialize\": \"JUSTIFY_DAYS(INTERVAL '1 DAY')\",\n            },\n        )\n        self.validate_all(\n            \"JUSTIFY_HOURS(INTERVAL '1' HOUR)\",\n            read={\n                \"\": \"JUSTIFY_HOURS(INTERVAL '1' HOUR)\",\n                \"bigquery\": \"JUSTIFY_HOURS(INTERVAL '1' HOUR)\",\n                \"postgres\": \"JUSTIFY_HOURS(INTERVAL '1 HOUR')\",\n                \"materialize\": \"JUSTIFY_HOURS(INTERVAL '1 HOUR')\",\n            },\n            write={\n                \"bigquery\": \"JUSTIFY_HOURS(INTERVAL '1' HOUR)\",\n                \"postgres\": \"JUSTIFY_HOURS(INTERVAL '1 HOUR')\",\n                \"materialize\": \"JUSTIFY_HOURS(INTERVAL '1 HOUR')\",\n            },\n        )\n        self.validate_all(\n            \"JUSTIFY_INTERVAL(INTERVAL '1' HOUR)\",\n            read={\n                \"\": \"JUSTIFY_INTERVAL(INTERVAL '1' HOUR)\",\n                \"bigquery\": \"JUSTIFY_INTERVAL(INTERVAL '1' HOUR)\",\n                \"postgres\": \"JUSTIFY_INTERVAL(INTERVAL '1 HOUR')\",\n                \"materialize\": \"JUSTIFY_INTERVAL(INTERVAL '1 HOUR')\",\n            },\n            write={\n                \"bigquery\": \"JUSTIFY_INTERVAL(INTERVAL '1' HOUR)\",\n                \"postgres\": \"JUSTIFY_INTERVAL(INTERVAL '1 HOUR')\",\n                \"materialize\": \"JUSTIFY_INTERVAL(INTERVAL '1 HOUR')\",\n            },\n        )\n\n    def test_unix_time(self):\n        self.validate_all(\n            \"UNIX_MICROS(foo)\",\n            read={\n                \"\": \"UNIX_MICROS(foo)\",\n                \"bigquery\": \"UNIX_MICROS(foo)\",\n                \"spark\": \"UNIX_MICROS(foo)\",\n                \"databricks\": \"UNIX_MICROS(foo)\",\n            },\n            write={\n                \"bigquery\": \"UNIX_MICROS(foo)\",\n                \"spark\": \"UNIX_MICROS(foo)\",\n                \"databricks\": \"UNIX_MICROS(foo)\",\n            },\n        )\n        self.validate_all(\n            \"UNIX_MILLIS(foo)\",\n            read={\n                \"\": \"UNIX_MILLIS(foo)\",\n                \"bigquery\": \"UNIX_MILLIS(foo)\",\n                \"spark\": \"UNIX_MILLIS(foo)\",\n                \"databricks\": \"UNIX_MILLIS(foo)\",\n            },\n            write={\n                \"bigquery\": \"UNIX_MILLIS(foo)\",\n                \"spark\": \"UNIX_MILLIS(foo)\",\n                \"databricks\": \"UNIX_MILLIS(foo)\",\n            },\n        )\n\n    def test_reverse(self):\n        self.validate_all(\n            \"REVERSE(x)\",\n            read={\n                \"\": \"REVERSE(x)\",\n                \"bigquery\": \"REVERSE(x)\",\n                \"hive\": \"REVERSE(x)\",\n                \"spark2\": \"REVERSE(x)\",\n                \"spark\": \"REVERSE(x)\",\n                \"databricks\": \"REVERSE(x)\",\n                \"mysql\": \"REVERSE(x)\",\n                \"postgres\": \"REVERSE(x)\",\n                \"tsql\": \"REVERSE(x)\",\n                \"snowflake\": \"REVERSE(x)\",\n                \"doris\": \"REVERSE(x)\",\n                \"presto\": \"REVERSE(x)\",\n                \"trino\": \"REVERSE(x)\",\n                \"clickhouse\": \"REVERSE(x)\",\n                \"redshift\": \"REVERSE(x)\",\n            },\n            write={\n                \"bigquery\": \"REVERSE(x)\",\n                \"hive\": \"REVERSE(x)\",\n                \"spark2\": \"REVERSE(x)\",\n                \"spark\": \"REVERSE(x)\",\n                \"databricks\": \"REVERSE(x)\",\n                \"mysql\": \"REVERSE(x)\",\n                \"postgres\": \"REVERSE(x)\",\n                \"tsql\": \"REVERSE(x)\",\n                \"snowflake\": \"REVERSE(x)\",\n                \"doris\": \"REVERSE(x)\",\n                \"presto\": \"REVERSE(x)\",\n                \"trino\": \"REVERSE(x)\",\n                \"clickhouse\": \"REVERSE(x)\",\n                \"redshift\": \"REVERSE(x)\",\n            },\n        )\n\n    def test_regr_count(self):\n        self.validate_all(\n            \"REGR_COUNT(x, y)\",\n            read={\n                \"\": \"REGR_COUNT(x, y)\",\n                \"databricks\": \"REGR_COUNT(x, y)\",\n                \"duckdb\": \"REGR_COUNT(x, y)\",\n                \"exasol\": \"REGR_COUNT(x, y)\",\n                \"hive\": \"REGR_COUNT(x, y)\",\n                \"oracle\": \"REGR_COUNT(x, y)\",\n                \"postgres\": \"REGR_COUNT(x, y)\",\n                \"presto\": \"REGR_COUNT(x, y)\",\n                \"snowflake\": \"REGR_COUNT(x, y)\",\n                \"spark\": \"REGR_COUNT(x, y)\",\n                \"teradata\": \"REGR_COUNT(x, y)\",\n                \"trino\": \"REGR_COUNT(x, y)\",\n            },\n            write={\n                \"\": \"REGR_COUNT(x, y)\",\n                \"databricks\": \"REGR_COUNT(x, y)\",\n                \"duckdb\": \"REGR_COUNT(x, y)\",\n                \"exasol\": \"REGR_COUNT(x, y)\",\n                \"hive\": \"REGR_COUNT(x, y)\",\n                \"oracle\": \"REGR_COUNT(x, y)\",\n                \"postgres\": \"REGR_COUNT(x, y)\",\n                \"presto\": \"REGR_COUNT(x, y)\",\n                \"snowflake\": \"REGR_COUNT(x, y)\",\n                \"spark\": \"REGR_COUNT(x, y)\",\n                \"teradata\": \"REGR_COUNT(x, y)\",\n                \"trino\": \"REGR_COUNT(x, y)\",\n            },\n        )\n\n    def test_regr_intercept(self):\n        self.validate_all(\n            \"REGR_INTERCEPT(x, y)\",\n            read={\n                \"\": \"REGR_INTERCEPT(x, y)\",\n                \"databricks\": \"REGR_INTERCEPT(x, y)\",\n                \"duckdb\": \"REGR_INTERCEPT(x, y)\",\n                \"exasol\": \"REGR_INTERCEPT(x, y)\",\n                \"hive\": \"REGR_INTERCEPT(x, y)\",\n                \"oracle\": \"REGR_INTERCEPT(x, y)\",\n                \"postgres\": \"REGR_INTERCEPT(x, y)\",\n                \"presto\": \"REGR_INTERCEPT(x, y)\",\n                \"snowflake\": \"REGR_INTERCEPT(x, y)\",\n                \"spark\": \"REGR_INTERCEPT(x, y)\",\n                \"teradata\": \"REGR_INTERCEPT(x, y)\",\n            },\n            write={\n                \"\": \"REGR_INTERCEPT(x, y)\",\n                \"databricks\": \"REGR_INTERCEPT(x, y)\",\n                \"duckdb\": \"REGR_INTERCEPT(x, y)\",\n                \"exasol\": \"REGR_INTERCEPT(x, y)\",\n                \"hive\": \"REGR_INTERCEPT(x, y)\",\n                \"oracle\": \"REGR_INTERCEPT(x, y)\",\n                \"postgres\": \"REGR_INTERCEPT(x, y)\",\n                \"presto\": \"REGR_INTERCEPT(x, y)\",\n                \"snowflake\": \"REGR_INTERCEPT(x, y)\",\n                \"spark\": \"REGR_INTERCEPT(x, y)\",\n                \"teradata\": \"REGR_INTERCEPT(x, y)\",\n            },\n        )\n\n    def test_regr_r2(self):\n        self.validate_all(\n            \"REGR_R2(x, y)\",\n            read={\n                \"\": \"REGR_R2(x, y)\",\n                \"databricks\": \"REGR_R2(x, y)\",\n                \"duckdb\": \"REGR_R2(x, y)\",\n                \"exasol\": \"REGR_R2(x, y)\",\n                \"hive\": \"REGR_R2(x, y)\",\n                \"oracle\": \"REGR_R2(x, y)\",\n                \"postgres\": \"REGR_R2(x, y)\",\n                \"presto\": \"REGR_R2(x, y)\",\n                \"snowflake\": \"REGR_R2(x, y)\",\n                \"spark\": \"REGR_R2(x, y)\",\n                \"teradata\": \"REGR_R2(x, y)\",\n            },\n            write={\n                \"\": \"REGR_R2(x, y)\",\n                \"databricks\": \"REGR_R2(x, y)\",\n                \"duckdb\": \"REGR_R2(x, y)\",\n                \"exasol\": \"REGR_R2(x, y)\",\n                \"hive\": \"REGR_R2(x, y)\",\n                \"oracle\": \"REGR_R2(x, y)\",\n                \"postgres\": \"REGR_R2(x, y)\",\n                \"presto\": \"REGR_R2(x, y)\",\n                \"snowflake\": \"REGR_R2(x, y)\",\n                \"spark\": \"REGR_R2(x, y)\",\n                \"teradata\": \"REGR_R2(x, y)\",\n            },\n        )\n\n    def test_regr_slope(self):\n        self.validate_all(\n            \"REGR_SLOPE(x, y)\",\n            read={\n                \"\": \"REGR_SLOPE(x, y)\",\n                \"databricks\": \"REGR_SLOPE(x, y)\",\n                \"duckdb\": \"REGR_SLOPE(x, y)\",\n                \"exasol\": \"REGR_SLOPE(x, y)\",\n                \"oracle\": \"REGR_SLOPE(x, y)\",\n                \"postgres\": \"REGR_SLOPE(x, y)\",\n                \"presto\": \"REGR_SLOPE(x, y)\",\n                \"snowflake\": \"REGR_SLOPE(x, y)\",\n                \"spark\": \"REGR_SLOPE(x, y)\",\n                \"teradata\": \"REGR_SLOPE(x, y)\",\n                \"trino\": \"REGR_SLOPE(x, y)\",\n            },\n            write={\n                \"\": \"REGR_SLOPE(x, y)\",\n                \"databricks\": \"REGR_SLOPE(x, y)\",\n                \"duckdb\": \"REGR_SLOPE(x, y)\",\n                \"exasol\": \"REGR_SLOPE(x, y)\",\n                \"oracle\": \"REGR_SLOPE(x, y)\",\n                \"postgres\": \"REGR_SLOPE(x, y)\",\n                \"presto\": \"REGR_SLOPE(x, y)\",\n                \"snowflake\": \"REGR_SLOPE(x, y)\",\n                \"spark\": \"REGR_SLOPE(x, y)\",\n                \"teradata\": \"REGR_SLOPE(x, y)\",\n                \"trino\": \"REGR_SLOPE(x, y)\",\n            },\n        )\n\n    def test_regr_sxx(self):\n        self.validate_all(\n            \"REGR_SXX(x, y)\",\n            read={\n                \"\": \"REGR_SXX(x, y)\",\n                \"databricks\": \"REGR_SXX(x, y)\",\n                \"duckdb\": \"REGR_SXX(x, y)\",\n                \"exasol\": \"REGR_SXX(x, y)\",\n                \"hive\": \"REGR_SXX(x, y)\",\n                \"oracle\": \"REGR_SXX(x, y)\",\n                \"postgres\": \"REGR_SXX(x, y)\",\n                \"presto\": \"REGR_SXX(x, y)\",\n                \"snowflake\": \"REGR_SXX(x, y)\",\n                \"spark\": \"REGR_SXX(x, y)\",\n                \"teradata\": \"REGR_SXX(x, y)\",\n            },\n            write={\n                \"\": \"REGR_SXX(x, y)\",\n                \"databricks\": \"REGR_SXX(x, y)\",\n                \"duckdb\": \"REGR_SXX(x, y)\",\n                \"exasol\": \"REGR_SXX(x, y)\",\n                \"hive\": \"REGR_SXX(x, y)\",\n                \"oracle\": \"REGR_SXX(x, y)\",\n                \"postgres\": \"REGR_SXX(x, y)\",\n                \"presto\": \"REGR_SXX(x, y)\",\n                \"snowflake\": \"REGR_SXX(x, y)\",\n                \"spark\": \"REGR_SXX(x, y)\",\n                \"teradata\": \"REGR_SXX(x, y)\",\n            },\n        )\n\n    def test_regr_sxy(self):\n        self.validate_all(\n            \"REGR_SXY(x, y)\",\n            read={\n                \"\": \"REGR_SXY(x, y)\",\n                \"databricks\": \"REGR_SXY(x, y)\",\n                \"duckdb\": \"REGR_SXY(x, y)\",\n                \"exasol\": \"REGR_SXY(x, y)\",\n                \"hive\": \"REGR_SXY(x, y)\",\n                \"oracle\": \"REGR_SXY(x, y)\",\n                \"postgres\": \"REGR_SXY(x, y)\",\n                \"presto\": \"REGR_SXY(x, y)\",\n                \"snowflake\": \"REGR_SXY(x, y)\",\n                \"spark\": \"REGR_SXY(x, y)\",\n                \"teradata\": \"REGR_SXY(x, y)\",\n            },\n            write={\n                \"\": \"REGR_SXY(x, y)\",\n                \"databricks\": \"REGR_SXY(x, y)\",\n                \"duckdb\": \"REGR_SXY(x, y)\",\n                \"exasol\": \"REGR_SXY(x, y)\",\n                \"hive\": \"REGR_SXY(x, y)\",\n                \"oracle\": \"REGR_SXY(x, y)\",\n                \"postgres\": \"REGR_SXY(x, y)\",\n                \"presto\": \"REGR_SXY(x, y)\",\n                \"snowflake\": \"REGR_SXY(x, y)\",\n                \"spark\": \"REGR_SXY(x, y)\",\n                \"teradata\": \"REGR_SXY(x, y)\",\n            },\n        )\n\n    def test_regr_syy(self):\n        self.validate_all(\n            \"REGR_SYY(x, y)\",\n            read={\n                \"\": \"REGR_SYY(x, y)\",\n                \"databricks\": \"REGR_SYY(x, y)\",\n                \"duckdb\": \"REGR_SYY(x, y)\",\n                \"exasol\": \"REGR_SYY(x, y)\",\n                \"hive\": \"REGR_SYY(x, y)\",\n                \"oracle\": \"REGR_SYY(x, y)\",\n                \"postgres\": \"REGR_SYY(x, y)\",\n                \"presto\": \"REGR_SYY(x, y)\",\n                \"snowflake\": \"REGR_SYY(x, y)\",\n                \"spark\": \"REGR_SYY(x, y)\",\n                \"teradata\": \"REGR_SYY(x, y)\",\n            },\n            write={\n                \"\": \"REGR_SYY(x, y)\",\n                \"databricks\": \"REGR_SYY(x, y)\",\n                \"duckdb\": \"REGR_SYY(x, y)\",\n                \"exasol\": \"REGR_SYY(x, y)\",\n                \"hive\": \"REGR_SYY(x, y)\",\n                \"oracle\": \"REGR_SYY(x, y)\",\n                \"postgres\": \"REGR_SYY(x, y)\",\n                \"presto\": \"REGR_SYY(x, y)\",\n                \"snowflake\": \"REGR_SYY(x, y)\",\n                \"spark\": \"REGR_SYY(x, y)\",\n                \"teradata\": \"REGR_SYY(x, y)\",\n            },\n        )\n\n    def test_translate(self):\n        self.validate_all(\n            \"TRANSLATE(x, y, z)\",\n            read={\n                \"\": \"TRANSLATE(x, y, z)\",\n                \"bigquery\": \"TRANSLATE(x, y, z)\",\n                \"hive\": \"TRANSLATE(x, y, z)\",\n                \"spark2\": \"TRANSLATE(x, y, z)\",\n                \"spark\": \"TRANSLATE(x, y, z)\",\n                \"databricks\": \"TRANSLATE(x, y, z)\",\n                \"postgres\": \"TRANSLATE(x, y, z)\",\n                \"tsql\": \"TRANSLATE(x, y, z)\",\n                \"snowflake\": \"TRANSLATE(x, y, z)\",\n                \"doris\": \"TRANSLATE(x, y, z)\",\n                \"trino\": \"TRANSLATE(x, y, z)\",\n                \"clickhouse\": \"TRANSLATE(x, y, z)\",\n                \"redshift\": \"TRANSLATE(x, y, z)\",\n                \"oracle\": \"TRANSLATE(x, y, z)\",\n            },\n            write={\n                \"\": \"TRANSLATE(x, y, z)\",\n                \"bigquery\": \"TRANSLATE(x, y, z)\",\n                \"hive\": \"TRANSLATE(x, y, z)\",\n                \"spark2\": \"TRANSLATE(x, y, z)\",\n                \"spark\": \"TRANSLATE(x, y, z)\",\n                \"databricks\": \"TRANSLATE(x, y, z)\",\n                \"postgres\": \"TRANSLATE(x, y, z)\",\n                \"tsql\": \"TRANSLATE(x, y, z)\",\n                \"snowflake\": \"TRANSLATE(x, y, z)\",\n                \"doris\": \"TRANSLATE(x, y, z)\",\n                \"trino\": \"TRANSLATE(x, y, z)\",\n                \"clickhouse\": \"TRANSLATE(x, y, z)\",\n                \"redshift\": \"TRANSLATE(x, y, z)\",\n                \"oracle\": \"TRANSLATE(x, y, z)\",\n            },\n        )\n\n    def test_soundex(self):\n        self.validate_all(\n            \"SOUNDEX(x)\",\n            read={\n                \"\": \"SOUNDEX(x)\",\n                \"bigquery\": \"SOUNDEX(x)\",\n                \"hive\": \"SOUNDEX(x)\",\n                \"spark2\": \"SOUNDEX(x)\",\n                \"spark\": \"SOUNDEX(x)\",\n                \"databricks\": \"SOUNDEX(x)\",\n                \"mysql\": \"SOUNDEX(x)\",\n                \"postgres\": \"SOUNDEX(x)\",\n                \"tsql\": \"SOUNDEX(x)\",\n                \"snowflake\": \"SOUNDEX(x)\",\n                \"dremio\": \"SOUNDEX(x)\",\n                \"trino\": \"SOUNDEX(x)\",\n                \"clickhouse\": \"SOUNDEX(x)\",\n                \"redshift\": \"SOUNDEX(x)\",\n                \"oracle\": \"SOUNDEX(x)\",\n            },\n            write={\n                \"bigquery\": \"SOUNDEX(x)\",\n                \"hive\": \"SOUNDEX(x)\",\n                \"spark2\": \"SOUNDEX(x)\",\n                \"spark\": \"SOUNDEX(x)\",\n                \"databricks\": \"SOUNDEX(x)\",\n                \"mysql\": \"SOUNDEX(x)\",\n                \"postgres\": \"SOUNDEX(x)\",\n                \"tsql\": \"SOUNDEX(x)\",\n                \"snowflake\": \"SOUNDEX(x)\",\n                \"dremio\": \"SOUNDEX(x)\",\n                \"trino\": \"SOUNDEX(x)\",\n                \"clickhouse\": \"SOUNDEX(x)\",\n                \"redshift\": \"SOUNDEX(x)\",\n                \"oracle\": \"SOUNDEX(x)\",\n            },\n        )\n\n    def test_grouping(self):\n        self.validate_all(\n            \"GROUPING(x)\",\n            read={\n                \"\": \"GROUPING(x)\",\n                \"bigquery\": \"GROUPING(x)\",\n                \"hive\": \"GROUPING(x)\",\n                \"spark2\": \"GROUPING(x)\",\n                \"spark\": \"GROUPING(x)\",\n                \"databricks\": \"GROUPING(x)\",\n                \"mysql\": \"GROUPING(x)\",\n                \"postgres\": \"GROUPING(x)\",\n                \"tsql\": \"GROUPING(x)\",\n                \"snowflake\": \"GROUPING(x)\",\n                \"clickhouse\": \"GROUPING(x)\",\n                \"redshift\": \"GROUPING(x)\",\n                \"oracle\": \"GROUPING(x)\",\n            },\n            write={\n                \"bigquery\": \"GROUPING(x)\",\n                \"hive\": \"GROUPING(x)\",\n                \"spark2\": \"GROUPING(x)\",\n                \"spark\": \"GROUPING(x)\",\n                \"databricks\": \"GROUPING(x)\",\n                \"mysql\": \"GROUPING(x)\",\n                \"postgres\": \"GROUPING(x)\",\n                \"tsql\": \"GROUPING(x)\",\n                \"snowflake\": \"GROUPING(x)\",\n                \"clickhouse\": \"GROUPING(x)\",\n                \"redshift\": \"GROUPING(x)\",\n                \"oracle\": \"GROUPING(x)\",\n            },\n        )\n        self.validate_all(\n            \"GROUPING(col1, col2, col3)\",\n            read={\n                \"\": \"GROUPING(col1, col2, col3)\",\n                \"snowflake\": \"GROUPING(col1, col2, col3)\",\n                \"mysql\": \"GROUPING(col1, col2, col3)\",\n                \"postgres\": \"GROUPING(col1, col2, col3)\",\n                \"clickhouse\": \"GROUPING(col1, col2, col3)\",\n                \"redshift\": \"GROUPING(col1, col2, col3)\",\n            },\n            write={\n                \"snowflake\": \"GROUPING(col1, col2, col3)\",\n                \"mysql\": \"GROUPING(col1, col2, col3)\",\n                \"postgres\": \"GROUPING(col1, col2, col3)\",\n                \"clickhouse\": \"GROUPING(col1, col2, col3)\",\n                \"redshift\": \"GROUPING(col1, col2, col3)\",\n            },\n        )\n\n    def test_farm_fingerprint(self):\n        self.validate_all(\n            \"FARM_FINGERPRINT(x)\",\n            read={\n                \"\": \"FARM_FINGERPRINT(x)\",\n                \"bigquery\": \"FARM_FINGERPRINT(x)\",\n                \"clickhouse\": \"farmFingerprint64(x)\",\n                \"redshift\": \"FARMFINGERPRINT64(x)\",\n            },\n            write={\n                \"bigquery\": \"FARM_FINGERPRINT(x)\",\n                \"clickhouse\": \"farmFingerprint64(x)\",\n                \"redshift\": \"FARMFINGERPRINT64(x)\",\n            },\n        )\n\n    def test_from_to_base32(self):\n        self.validate_all(\n            \"FROM_BASE32(x)\",\n            read={\n                \"\": \"FROM_BASE32(x)\",\n                \"bigquery\": \"FROM_BASE32(x)\",\n                \"presto\": \"FROM_BASE32(x)\",\n                \"trino\": \"FROM_BASE32(x)\",\n            },\n            write={\n                \"bigquery\": \"FROM_BASE32(x)\",\n                \"presto\": \"FROM_BASE32(x)\",\n                \"trino\": \"FROM_BASE32(x)\",\n            },\n        )\n        self.validate_all(\n            \"TO_BASE32(x)\",\n            read={\n                \"\": \"TO_BASE32(x)\",\n                \"bigquery\": \"TO_BASE32(x)\",\n                \"presto\": \"TO_BASE32(x)\",\n                \"trino\": \"TO_BASE32(x)\",\n            },\n            write={\n                \"bigquery\": \"TO_BASE32(x)\",\n                \"presto\": \"TO_BASE32(x)\",\n                \"trino\": \"TO_BASE32(x)\",\n            },\n        )\n\n    def test_regexp_instr(self):\n        self.validate_all(\n            \"REGEXP_INSTR(src, reg)\",\n            read={\n                \"\": \"REGEXP_INSTR(src, reg)\",\n                \"bigquery\": \"REGEXP_INSTR(src, reg)\",\n                \"snowflake\": \"REGEXP_INSTR(src, reg)\",\n                \"oracle\": \"REGEXP_INSTR(src, reg)\",\n                \"spark\": \"REGEXP_INSTR(src, reg)\",\n                \"databricks\": \"REGEXP_INSTR(src, reg)\",\n                \"tsql\": \"REGEXP_INSTR(src, reg)\",\n                \"mysql\": \"REGEXP_INSTR(src, reg)\",\n                \"postgres\": \"REGEXP_INSTR(src, reg)\",\n                \"redshift\": \"REGEXP_INSTR(src, reg)\",\n            },\n            write={\n                \"bigquery\": \"REGEXP_INSTR(src, reg)\",\n                \"snowflake\": \"REGEXP_INSTR(src, reg)\",\n                \"oracle\": \"REGEXP_INSTR(src, reg)\",\n                \"spark\": \"REGEXP_INSTR(src, reg)\",\n                \"databricks\": \"REGEXP_INSTR(src, reg)\",\n                \"tsql\": \"REGEXP_INSTR(src, reg)\",\n                \"mysql\": \"REGEXP_INSTR(src, reg)\",\n                \"postgres\": \"REGEXP_INSTR(src, reg)\",\n                \"redshift\": \"REGEXP_INSTR(src, reg)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n            read={\n                \"\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"bigquery\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"snowflake\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"oracle\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"tsql\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"mysql\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"postgres\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"redshift\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n            },\n            write={\n                \"bigquery\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"snowflake\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"oracle\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"tsql\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"mysql\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"postgres\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n                \"redshift\": \"REGEXP_INSTR(src, reg, pos, occ, opt)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n            read={\n                \"\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"snowflake\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"oracle\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"tsql\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"mysql\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"postgres\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"redshift\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n            },\n            write={\n                \"snowflake\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"oracle\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"tsql\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"mysql\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"postgres\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n                \"redshift\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n            read={\n                \"\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n                \"snowflake\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n                \"oracle\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n                \"tsql\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n                \"postgres\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n            },\n            write={\n                \"snowflake\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n                \"oracle\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n                \"tsql\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n                \"postgres\": \"REGEXP_INSTR(src, reg, pos, occ, opt, par, grp)\",\n            },\n        )\n\n    def test_format(self):\n        self.validate_all(\n            \"FORMAT('str fmt1 fmt2', 1, 'a')\",\n            read={\n                \"\": \"FORMAT('str fmt1 fmt2', 1, 'a')\",\n                \"bigquery\": \"FORMAT('str fmt1 fmt2', 1, 'a')\",\n                \"postgres\": \"FORMAT('str fmt1 fmt2', 1, 'a')\",\n                \"duckdb\": \"FORMAT('str fmt1 fmt2', 1, 'a')\",\n            },\n            write={\n                \"bigquery\": \"FORMAT('str fmt1 fmt2', 1, 'a')\",\n                \"postgres\": \"FORMAT('str fmt1 fmt2', 1, 'a')\",\n                \"spark2\": \"FORMAT_STRING('str fmt1 fmt2', 1, 'a')\",\n                \"spark\": \"FORMAT_STRING('str fmt1 fmt2', 1, 'a')\",\n                \"databricks\": \"FORMAT_STRING('str fmt1 fmt2', 1, 'a')\",\n                \"duckdb\": \"FORMAT('str fmt1 fmt2', 1, 'a')\",\n            },\n        )\n\n    def test_json_array_append(self):\n        self.validate_all(\n            \"\"\"JSON_ARRAY_APPEND(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$', 1)\"\"\",\n            read={\n                \"\": \"\"\"JSON_ARRAY_APPEND(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$', 1)\"\"\",\n                \"bigquery\": \"\"\"JSON_ARRAY_APPEND(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$', 1)\"\"\",\n            },\n            write={\n                \"bigquery\": \"\"\"JSON_ARRAY_APPEND(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$', 1)\"\"\",\n                \"mysql\": \"\"\"JSON_ARRAY_APPEND('[\"a\", \"b\", \"c\"]', '$', 1)\"\"\",\n            },\n        )\n\n    def test_json_array_insert(self):\n        self.validate_all(\n            \"\"\"JSON_ARRAY_INSERT(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', 1)\"\"\",\n            read={\n                \"\": \"\"\"JSON_ARRAY_INSERT(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', 1)\"\"\",\n                \"bigquery\": \"\"\"JSON_ARRAY_INSERT(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', 1)\"\"\",\n            },\n            write={\n                \"bigquery\": \"\"\"JSON_ARRAY_INSERT(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', 1)\"\"\",\n                \"mysql\": \"\"\"JSON_ARRAY_INSERT('[\"a\", [\"b\", \"c\"], \"d\"]', '$[1]', 1)\"\"\",\n            },\n        )\n\n    def test_json_remove(self):\n        self.validate_all(\n            \"\"\"JSON_REMOVE(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', '$[1]')\"\"\",\n            read={\n                \"\": \"\"\"JSON_REMOVE(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', '$[1]')\"\"\",\n                \"bigquery\": \"\"\"JSON_REMOVE(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', '$[1]')\"\"\",\n            },\n            write={\n                \"bigquery\": \"\"\"JSON_REMOVE(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', '$[1]')\"\"\",\n                \"mysql\": \"\"\"JSON_REMOVE('[\"a\", [\"b\", \"c\"], \"d\"]', '$[1]', '$[1]')\"\"\",\n                \"sqlite\": \"\"\"JSON_REMOVE('[\"a\", [\"b\", \"c\"], \"d\"]', '$[1]', '$[1]')\"\"\",\n            },\n        )\n\n    def test_json_set(self):\n        self.validate_all(\n            \"\"\"JSON_SET(PARSE_JSON('{\"a\": 1}'), '$', PARSE_JSON('{\"b\": 2, \"c\": 3}'))\"\"\",\n            read={\n                \"\": \"\"\"JSON_SET(PARSE_JSON('{\"a\": 1}'), '$', PARSE_JSON('{\"b\": 2, \"c\": 3}'))\"\"\",\n                \"bigquery\": \"\"\"JSON_SET(PARSE_JSON('{\"a\": 1}'), '$', PARSE_JSON('{\"b\": 2, \"c\": 3}'))\"\"\",\n            },\n            write={\n                \"bigquery\": \"\"\"JSON_SET(PARSE_JSON('{\"a\": 1}'), '$', PARSE_JSON('{\"b\": 2, \"c\": 3}'))\"\"\",\n                \"mysql\": \"\"\"JSON_SET('{\"a\": 1}', '$', '{\"b\": 2, \"c\": 3}')\"\"\",\n                \"sqlite\": \"\"\"JSON_SET('{\"a\": 1}', '$', '{\"b\": 2, \"c\": 3}')\"\"\",\n                \"doris\": \"\"\"JSON_SET('{\"a\": 1}', '$', '{\"b\": 2, \"c\": 3}')\"\"\",\n            },\n        )\n\n    def test_json_strip_nulls(self):\n        self.validate_all(\n            \"\"\"JSON_STRIP_NULLS(PARSE_JSON('[{\"f1\":1,\"f2\":null},2,null,3]'))\"\"\",\n            read={\n                \"\": \"\"\"JSON_STRIP_NULLS(PARSE_JSON('[{\"f1\":1,\"f2\":null},2,null,3]'))\"\"\",\n                \"bigquery\": \"\"\"JSON_STRIP_NULLS(PARSE_JSON('[{\"f1\":1,\"f2\":null},2,null,3]'))\"\"\",\n            },\n            write={\n                \"bigquery\": \"\"\"JSON_STRIP_NULLS(PARSE_JSON('[{\"f1\":1,\"f2\":null},2,null,3]'))\"\"\",\n                \"postgres\": \"\"\"JSON_STRIP_NULLS(CAST('[{\"f1\":1,\"f2\":null},2,null,3]' AS JSON))\"\"\",\n            },\n        )\n\n    def test_is_unknown(self):\n        # In many dialects `<...> IS UNKNOWN` is equivalent to `<...> IS NULL`\n        self.validate_all(\n            \"x IS NULL\",\n            read={\n                \"\": \"x IS UNKNOWN\",\n                \"bigquery\": \"x IS UNKNOWN\",\n                \"mysql\": \"x IS UNKNOWN\",\n                \"postgres\": \"x IS UNKNOWN\",\n                \"redshift\": \"x IS UNKNOWN\",\n                \"duckdb\": \"x IS UNKNOWN\",\n                \"spark\": \"x IS UNKNOWN\",\n                \"databricks\": \"x IS UNKNOWN\",\n            },\n        )\n\n        self.validate_all(\n            \"NOT x IS NULL\",\n            read={\n                \"\": \"x IS NOT UNKNOWN\",\n                \"bigquery\": \"x IS NOT UNKNOWN\",\n                \"mysql\": \"x IS NOT UNKNOWN\",\n                \"postgres\": \"x IS NOT UNKNOWN\",\n                \"redshift\": \"x IS NOT UNKNOWN\",\n                \"duckdb\": \"x IS NOT UNKNOWN\",\n                \"spark\": \"x IS NOT UNKNOWN\",\n                \"databricks\": \"x IS NOT UNKNOWN\",\n            },\n        )\n\n    def test_is_with_dcolon(self):\n        self.validate_all(\n            \"SELECT CAST(col IS NULL AS BOOLEAN) FROM (SELECT 1 AS col) AS t\",\n            read={\n                \"\": \"SELECT col IS NULL::BOOLEAN FROM (SELECT 1 AS col) AS t\",\n                \"duckdb\": \"SELECT col IS NULL::BOOLEAN FROM (SELECT 1 AS col) AS t\",\n                \"redshift\": \"SELECT col IS NULL::BOOLEAN FROM (SELECT 1 AS col) AS t\",\n                \"postgres\": \"SELECT col IS NULL::BOOLEAN FROM (SELECT 1 AS col) AS t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(NOT col IS NULL AS BOOLEAN) FROM (SELECT 1 AS col) AS t\",\n            read={\n                \"\": \"SELECT col IS NOT NULL::BOOLEAN FROM (SELECT 1 AS col) AS t\",\n                \"duckdb\": \"SELECT col IS NOT NULL::BOOLEAN FROM (SELECT 1 AS col) AS t\",\n                \"redshift\": \"SELECT col IS NOT NULL::BOOLEAN FROM (SELECT 1 AS col) AS t\",\n                \"postgres\": \"SELECT col IS NOT NULL::BOOLEAN FROM (SELECT 1 AS col) AS t\",\n            },\n        )\n\n    def test_regexp_replace(self):\n        for target_dialect in (\"postgres\", \"duckdb\"):\n            # Transpilations from other dialects to Postgres or DuckDB should append 'g'\n            # since their semantics is to replace all occurrences of the pattern.\n            for read_dialect in (\"\", \"bigquery\", \"presto\", \"trino\", \"spark\", \"databricks\"):\n                with self.subTest(\n                    f\"Testing REGEXP_REPLACE appending 'g' flag from {read_dialect} to {target_dialect}\"\n                ):\n                    sql = parse_one(\"REGEXP_REPLACE('aaa', 'a', 'b')\", read=read_dialect).sql(\n                        target_dialect\n                    )\n                    self.assertEqual(sql, \"REGEXP_REPLACE('aaa', 'a', 'b', 'g')\")\n\n    def test_subquery_unwrap(self):\n        self.validate_identity(\n            \"WITH sub_query AS (SELECT a FROM table) (SELECT a FROM sub_query)\",\n            \"WITH sub_query AS (SELECT a FROM table) SELECT a FROM sub_query\",\n        )\n\n        self.validate_identity(\n            \"WITH sub_query AS (SELECT a FROM table) ((((SELECT a FROM sub_query))))\",\n            \"WITH sub_query AS (SELECT a FROM table) SELECT a FROM sub_query\",\n        )\n\n    def test_initcap(self):\n        delimiter_chars = {\n            \"\": Dialect.INITCAP_DEFAULT_DELIMITER_CHARS,\n            \"bigquery\": BigQuery.INITCAP_DEFAULT_DELIMITER_CHARS,\n            \"snowflake\": Snowflake.INITCAP_DEFAULT_DELIMITER_CHARS,\n            \"spark\": Spark2.INITCAP_DEFAULT_DELIMITER_CHARS,\n        }\n\n        with self.subTest(\"INITCAP without explicit delimiters\"):\n            self.assertEqual(exp.Initcap(this=exp.Literal.string(\"col\")).sql(), \"INITCAP('col')\")\n            self.assertEqual(exp.Initcap(this=exp.column(\"col\")).sql(), \"INITCAP(col)\")\n\n        for dialect in delimiter_chars:\n            with self.subTest(f\"Round-tripping default delimiters for {dialect or 'default'}\"):\n                self.assertEqual(\n                    parse_one(\"INITCAP(col)\", read=dialect).sql(dialect), \"INITCAP(col)\"\n                )\n\n        for read_dialect in (\"\", \"spark\"):\n            for write_dialect in (\"bigquery\", \"snowflake\"):\n                with self.subTest(\n                    f\"Default delimiters emitted from {read_dialect or 'default'} to {write_dialect}\"\n                ):\n                    escaped_delimiters = exp.Literal.string(delimiter_chars[read_dialect]).sql(\n                        write_dialect\n                    )\n                    self.assertEqual(\n                        parse_one(\"INITCAP(col)\", read=read_dialect).sql(write_dialect),\n                        f\"INITCAP(col, {escaped_delimiters})\",\n                    )\n\n        def assert_default_duckdb_sql(read_dialect: str, default_chars: str) -> None:\n            chr_chars = [char for char in WS_CONTROL_CHARS_TO_DUCK if char in default_chars]\n            expression = parse_one(\"INITCAP(col)\", read=read_dialect)\n            self.assert_duckdb_sql(\n                expression,\n                includes=(\"ARRAY_TO_STRING(\", \"REGEXP_MATCHES(\", \"LIST_TRANSFORM(\"),\n                chr_chars=chr_chars,\n            )\n\n        for dialect, default_chars in delimiter_chars.items():\n            with self.subTest(f\"DuckDB rewrite for {dialect or 'default'} default delimiters\"):\n                assert_default_duckdb_sql(dialect, default_chars)\n\n        def assert_custom_duckdb_sql(\n            query: str,\n            *,\n            includes: t.Optional[Iterable[str]] = None,\n            excludes: t.Optional[Iterable[str]] = None,\n            chr_chars: t.Optional[Iterable[str]] = None,\n        ) -> None:\n            for dialect in (\"bigquery\", \"snowflake\"):\n                with self.subTest(f\"DuckDB generation for {query} from {dialect}\"):\n                    expression = parse_one(query, read=dialect)\n                    self.assert_duckdb_sql(\n                        expression, includes=includes, excludes=excludes, chr_chars=chr_chars\n                    )\n\n        assert_custom_duckdb_sql(\n            \"INITCAP(col, '')\", includes=(\"UPPER(LEFT(\",), excludes=(\"REGEXP_MATCHES(\",)\n        )\n        assert_custom_duckdb_sql(\"INITCAP(col, NULL)\", includes=(\"REGEXP_MATCHES(\", \"REPLACE(\"))\n        assert_custom_duckdb_sql(\"INITCAP(col, ' ')\", includes=(\"' '\",))\n        assert_custom_duckdb_sql(\"INITCAP(col, '@')\", includes=(\"'@'\",), excludes=(\"CHR(\",))\n        assert_custom_duckdb_sql(\"INITCAP(col, '_@')\", includes=(\"'_@'\",))\n        assert_custom_duckdb_sql(r\"INITCAP(col, '\\\\\\\\')\", includes=(\"\\\\\\\\\",))\n        assert_custom_duckdb_sql(\n            \"INITCAP(col, '\\u000b')\",\n            chr_chars=(\"\\u000b\",),\n        )\n        assert_custom_duckdb_sql(\n            \"INITCAP(col, (SELECT delimiter FROM settings LIMIT 1))\",\n            includes=(\"SELECT delimiter FROM settings\", \"REPLACE(\"),\n        )\n\n    def test_initcap_custom_delimiter_warning(self):\n        expression = parse_one(\"INITCAP(col, '_')\", read=\"bigquery\")\n        for dialect in (\"postgres\", \"presto\"):\n            with self.subTest(f\"INITCAP unsupported custom delimiters warning for {dialect}\"):\n                with self.assertLogs(generator_logger, level=\"WARNING\") as cm:\n                    expression.sql(dialect)\n                self.assertIn(\"INITCAP does not support custom delimiters\", cm.output[0])\n\n    def test_parse_at_time_zone(self):\n        parsed_expr = self.validate_identity(\n            \"SELECT CAST('2001-02-17 08:38:40' AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Tokyo'\"\n        ).expressions[0]\n        self.assertEqual(parsed_expr.args.get(\"zone\").sql(), \"'Asia/Tokyo'\")\n        self.assertEqual(parsed_expr.this.args.get(\"zone\").sql(), \"'UTC'\")\n\n        parsed_expr = self.validate_identity(\n            \"SELECT CAST('2001-02-17 08:38:40' AS TIMESTAMP) AT TIME ZONE INTERVAL '3' HOURS AT TIME ZONE 'Asia/Tokyo'\"\n        ).expressions[0]\n        self.assertEqual(parsed_expr.args.get(\"zone\").sql(\"postgres\"), \"'Asia/Tokyo'\")\n        self.assertEqual(parsed_expr.this.args.get(\"zone\").sql(\"postgres\"), \"INTERVAL '3 HOURS'\")\n\n    def test_underscore_scientific_notation(self):\n        for dialect in (\"duckdb\", \"clickhouse\"):\n            for notation in (\"e\", \"E\"):\n                for sign in (\"\", \"-\", \"+\"):\n                    with self.subTest(f\"Testing notation: {notation}, sign: {sign} for {dialect}\"):\n                        number = f\"1_2{notation}{sign}1_0\"\n                        expected = f\"12{notation}{sign}10\"\n                        self.assertEqual(parse_one(number, read=dialect).sql(dialect), expected)\n\n                        number = f\"12.3_4{notation}{sign}5_6_7\"\n                        expected = f\"12.34{notation}{sign}567\"\n                        self.assertEqual(parse_one(number, read=dialect).sql(dialect), expected)\n\n            with self.subTest(f\"Testing underscore separated numbers for {dialect}\"):\n                ast = parse_one(\"1_2_3_4_5\", read=dialect)\n                self.assertTrue(ast.is_int)\n                self.assertEqual(ast.to_py(), 12345)\n                self.assertEqual(ast.sql(dialect), \"12345\")\n\n    def test_localtime_and_localtimestamp(self):\n        for func in (\"LOCALTIME\", \"LOCALTIMESTAMP\"):\n            with self.subTest(f\"Testing {func}\"):\n                dialects = {\n                    \"postgres\": f\"SELECT {func}\",\n                    \"duckdb\": f\"SELECT {func}\",\n                    \"redshift\": f\"SELECT {func}\",\n                    \"presto\": f\"SELECT {func}\",\n                    \"trino\": f\"SELECT {func}\",\n                    \"mysql\": f\"SELECT {func}\",\n                    \"singlestore\": f\"SELECT {func}\",\n                }\n\n                if func == \"LOCALTIMESTAMP\":\n                    dialects[\"oracle\"] = f\"SELECT {func}\"\n\n                self.validate_all(\n                    f\"SELECT {func}\",\n                    read=dialects,\n                    write=dialects,\n                )\n\n            with self.subTest(f\"Testing {func} with precision\"):\n                dialects = {\n                    \"postgres\": f\"SELECT {func}(2)\",\n                    \"redshift\": f\"SELECT {func}(2)\",\n                    \"presto\": f\"SELECT {func}(2)\",\n                    \"trino\": f\"SELECT {func}(2)\",\n                    \"mysql\": f\"SELECT {func}(2)\",\n                    \"singlestore\": f\"SELECT {func}(2)\",\n                }\n\n                if func == \"LOCALTIMESTAMP\":\n                    dialects[\"oracle\"] = f\"SELECT {func}(2)\"\n\n                self.validate_all(\n                    f\"SELECT {func}(2)\",\n                    read=dialects,\n                    write=dialects,\n                )\n\n            exp_type = exp.Localtime if func == \"LOCALTIME\" else exp.Localtimestamp\n\n            for func_variant in (func, f\"{func}(2)\"):\n                with self.subTest(f\"Testing {func_variant} function node\"):\n                    self.validate_identity(f\"SELECT {func_variant}\").selects[0].assert_is(exp_type)\n\n        for dialect in (\n            \"tsql\",\n            \"oracle\",\n            \"sqlite\",\n            \"hive\",\n            \"spark2\",\n            \"spark\",\n            \"databricks\",\n            \"bigquery\",\n        ):\n            for func in (\"localtime\", \"localtimestamp\"):\n                # oracle supports localtimestamp but not localtime\n                if func == \"localtimestamp\" and dialect == \"oracle\":\n                    continue\n\n                with self.subTest(f\"Testing {func} identifier in {dialect}\"):\n                    sql = f\"SELECT {func}\"\n                    select = parse_one(sql, dialect=dialect)\n                    select.selects[0].assert_is(exp.Column)\n                    self.assertEqual(select.sql(dialect), sql)\n\n    def test_current_catalog(self):\n        sql = \"SELECT CURRENT_CATALOG\"\n\n        unsupported_dialects = [\n            \"bigquery\",\n            \"mysql\",\n            \"oracle\",\n            \"clickhouse\",\n            \"snowflake\",\n            \"spark\",\n            \"databricks\",\n            \"presto\",\n        ]\n\n        for dialect in unsupported_dialects:\n            with self.subTest(f\"Testing CURRENT_CATALOG as Column in {dialect}\"):\n                select = parse_one(sql, dialect=dialect)\n                select.selects[0].assert_is(exp.Column)\n                self.assertEqual(select.sql(dialect), sql)\n\n        supported_dialects = [\n            \"postgres\",\n            \"duckdb\",\n            \"trino\",\n            \"databricks\",\n        ]\n\n        for dialect in supported_dialects:\n            with self.subTest(f\"Testing CURRENT_CATALOG expression in {dialect}\"):\n                if dialect == \"databricks\":\n                    sql = \"SELECT CURRENT_CATALOG()\"\n                select = parse_one(sql, dialect=dialect)\n                select.selects[0].assert_is(exp.CurrentCatalog)\n\n                self.assertEqual(select.sql(dialect), sql)\n\n    def test_session_user(self):\n        no_paren_sql = \"SELECT SESSION_USER\"\n        func_sql = \"SELECT SESSION_USER()\"\n\n        # These dialects support only SESSION_USER()\n        for dialect in (\"bigquery\", \"mysql\"):\n            with self.subTest(f\"Testing that SESSION_USER is parsed as a Column in {dialect}\"):\n                select = parse_one(no_paren_sql, dialect=dialect)\n                select.selects[0].assert_is(exp.Column)\n                self.assertEqual(select.sql(dialect), no_paren_sql)\n\n                select = parse_one(func_sql, dialect=dialect)\n                select.selects[0].assert_is(exp.SessionUser)\n                self.assertEqual(select.sql(dialect), func_sql)\n\n        # These dialects support either only SESSION_USER or both\n        no_paren_dialects = [\n            \"postgres\",\n            \"duckdb\",\n            \"databricks\",\n            \"tsql\",\n            \"spark\",\n        ]\n\n        for dialect in no_paren_dialects:\n            with self.subTest(\n                f\"Testing that SESSION_USER is parsed as a SessionUser expression in {dialect}\"\n            ):\n                select = parse_one(no_paren_sql, dialect=dialect)\n                select.selects[0].assert_is(exp.SessionUser)\n                self.assertEqual(select.sql(dialect), no_paren_sql)\n\n                # These dialects support both SESSION_USER and SESSION_USER()\n                if dialect in (\"databricks\", \"spark\", \"duckdb\"):\n                    self.assertEqual(\n                        parse_one(func_sql, dialect=dialect).sql(dialect), no_paren_sql\n                    )\n\n    def test_operator(self):\n        expr = self.validate_identity(\"1 OPERATOR(+) 2 OPERATOR(*) 3\")\n\n        expr.left.assert_is(exp.Operator)\n        expr.left.left.assert_is(exp.Literal)\n        expr.left.right.assert_is(exp.Literal)\n        expr.right.assert_is(exp.Literal)\n        self.assertEqual(expr.sql(dialect=\"postgres\"), \"1 OPERATOR(+) 2 OPERATOR(*) 3\")\n\n        self.validate_identity(\"SELECT operator FROM t\")\n        self.validate_identity(\"SELECT 1 OPERATOR(+) 2\")\n        self.validate_identity(\"SELECT 1 OPERATOR(+) /* foo */ 2\")\n        self.validate_identity(\"SELECT 1 OPERATOR(pg_catalog.+) 2\")\n\n    def test_json_keys(self):\n        self.validate_all(\n            \"JSON_KEYS(foo)\",\n            read={\n                \"\": \"JSON_KEYS(foo)\",\n                \"spark\": \"JSON_OBJECT_KEYS(foo)\",\n                \"databricks\": \"JSON_OBJECT_KEYS(foo)\",\n                \"mysql\": \"JSON_KEYS(foo)\",\n                \"starrocks\": \"JSON_KEYS(foo)\",\n                \"duckdb\": \"JSON_KEYS(foo)\",\n                \"snowflake\": \"OBJECT_KEYS(foo)\",\n                \"doris\": \"JSON_KEYS(foo)\",\n                \"singlestore\": \"JSON_KEYS(foo)\",\n            },\n            write={\n                \"spark\": \"JSON_OBJECT_KEYS(foo)\",\n                \"databricks\": \"JSON_OBJECT_KEYS(foo)\",\n                \"mysql\": \"JSON_KEYS(foo)\",\n                \"starrocks\": \"JSON_KEYS(foo)\",\n                \"duckdb\": \"JSON_KEYS(foo)\",\n                \"snowflake\": \"OBJECT_KEYS(foo)\",\n                \"doris\": \"JSON_KEYS(foo)\",\n                \"singlestore\": \"JSON_KEYS(foo)\",\n            },\n        )\n\n        self.validate_all(\n            \"JSON_KEYS(foo, '$.a')\",\n            read={\n                \"\": \"JSON_KEYS(foo, '$.a')\",\n                \"mysql\": \"JSON_KEYS(foo, '$.a')\",\n                \"starrocks\": \"JSON_KEYS(foo, '$.a')\",\n                \"duckdb\": \"JSON_KEYS(foo, '$.a')\",\n                \"doris\": \"JSON_KEYS(foo, '$.a')\",\n            },\n            write={\n                \"mysql\": \"JSON_KEYS(foo, '$.a')\",\n                \"starrocks\": \"JSON_KEYS(foo, '$.a')\",\n                \"duckdb\": \"JSON_KEYS(foo, '$.a')\",\n                \"doris\": \"JSON_KEYS(foo, '$.a')\",\n            },\n        )\n\n    def test_interval_with_units_dcolon(self):\n        self.validate_identity(\n            \"SELECT interval '00:00:01'::interval AS foo\",\n            \"SELECT CAST(INTERVAL '00:00:01' AS INTERVAL) AS foo\",\n        )\n        self.validate_identity(\n            \"SELECT ROW_NUMBER() OVER(PARTITION BY event_time + interval '00:00:01'::interval) AS foo FROM t\",\n            \"SELECT ROW_NUMBER() OVER (PARTITION BY event_time + CAST(INTERVAL '00:00:01' AS INTERVAL)) AS foo FROM t\",\n        )\n\n    @unittest.skipIf(_PARSER_IS_COMPILED, \"mypyc compiled parsers cannot be subclassed\")\n    def test_patch_dialect_parser(self):\n        class CustomSnowflakeParser(SnowflakeParser):\n            FUNCTIONS = {\n                **SnowflakeParser.FUNCTIONS,\n                \"MY_CUSTOM_FUNC\": exp.Length.from_arg_list,\n            }\n\n        original = Snowflake.parser_class\n        try:\n            Snowflake.parser_class = CustomSnowflakeParser\n\n            result = parse_one(\"SELECT 1\", dialect=\"snowflake\")\n            self.assertIsInstance(result, exp.Select)\n\n            result = parse_one(\"SELECT MY_CUSTOM_FUNC(a)\", dialect=\"snowflake\")\n            self.assertIsInstance(result.find(exp.Length), exp.Length)\n        finally:\n            Snowflake.parser_class = original\n\n    @unittest.skipIf(_PARSER_IS_COMPILED, \"mypyc compiled parsers cannot be subclassed\")\n    def test_custom_dialect(self):\n        class MyDialect(Dialect):\n            class Parser(SnowflakeParser):\n                FUNCTIONS = {\n                    **SnowflakeParser.FUNCTIONS,\n                    \"DOUBLE_IT\": lambda args: exp.Mul(\n                        this=exp.Literal.number(2),\n                        expression=args[0] if args else exp.Null(),\n                    ),\n                }\n\n        result = parse_one(\"SELECT DOUBLE_IT(5)\", dialect=MyDialect)\n        self.assertIsInstance(result.expressions[0], exp.Mul)\n        self.assertEqual(result.sql(), \"SELECT 2 * 5\")\n"
  },
  {
    "path": "tests/dialects/test_doris.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestDoris(Validator):\n    dialect = \"doris\"\n\n    def test_doris(self):\n        self.validate_all(\n            \"SELECT TO_DATE('2020-02-02 00:00:00')\",\n            write={\n                \"doris\": \"SELECT TO_DATE('2020-02-02 00:00:00')\",\n                \"oracle\": \"SELECT CAST('2020-02-02 00:00:00' AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MAX_BY(a, b), MIN_BY(c, d)\",\n            read={\n                \"clickhouse\": \"SELECT argMax(a, b), argMin(c, d)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_SUM(x -> x * x, ARRAY(2, 3))\",\n            read={\n                \"clickhouse\": \"SELECT arraySum(x -> x*x, [2, 3])\",\n            },\n            write={\n                \"clickhouse\": \"SELECT arraySum(x -> x * x, [2, 3])\",\n                \"doris\": \"SELECT ARRAY_SUM(x -> x * x, ARRAY(2, 3))\",\n            },\n        )\n        self.validate_all(\n            \"MONTHS_ADD(d, n)\",\n            read={\n                \"oracle\": \"ADD_MONTHS(d, n)\",\n            },\n            write={\n                \"doris\": \"MONTHS_ADD(d, n)\",\n                \"oracle\": \"ADD_MONTHS(d, n)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT JSON_EXTRACT(CAST('{\"key\": 1}' AS JSONB), '$.key')\"\"\",\n            read={\n                \"postgres\": \"\"\"SELECT '{\"key\": 1}'::jsonb ->> 'key'\"\"\",\n            },\n            write={\n                \"doris\": \"\"\"SELECT JSON_EXTRACT(CAST('{\"key\": 1}' AS JSONB), '$.key')\"\"\",\n                \"postgres\": \"\"\"SELECT JSON_EXTRACT_PATH(CAST('{\"key\": 1}' AS JSONB), 'key')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT GROUP_CONCAT('aa', ',')\",\n            read={\n                \"doris\": \"SELECT GROUP_CONCAT('aa', ',')\",\n                \"mysql\": \"SELECT GROUP_CONCAT('aa' SEPARATOR ',')\",\n                \"postgres\": \"SELECT STRING_AGG('aa', ',')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LAG(1, 1, NULL) OVER (ORDER BY 1)\",\n            read={\n                \"doris\": \"SELECT LAG(1, 1, NULL) OVER (ORDER BY 1)\",\n                \"postgres\": \"SELECT LAG(1) OVER (ORDER BY 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LAG(1, 2, NULL) OVER (ORDER BY 1)\",\n            read={\n                \"doris\": \"SELECT LAG(1, 2, NULL) OVER (ORDER BY 1)\",\n                \"postgres\": \"SELECT LAG(1, 2) OVER (ORDER BY 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LEAD(1, 1, NULL) OVER (ORDER BY 1)\",\n            read={\n                \"doris\": \"SELECT LEAD(1, 1, NULL) OVER (ORDER BY 1)\",\n                \"postgres\": \"SELECT LEAD(1) OVER (ORDER BY 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LEAD(1, 2, NULL) OVER (ORDER BY 1)\",\n            read={\n                \"doris\": \"SELECT LEAD(1, 2, NULL) OVER (ORDER BY 1)\",\n                \"postgres\": \"SELECT LEAD(1, 2) OVER (ORDER BY 1)\",\n            },\n        )\n        self.validate_identity(\"\"\"JSON_TYPE('{\"foo\": \"1\" }', '$.foo')\"\"\")\n\n        self.validate_identity(\"L2_DISTANCE(x, y)\")\n\n    def test_identity(self):\n        self.validate_identity(\"CREATE TABLE t (c INT) PROPERTIES ('x'='y')\")\n        self.validate_identity(\"CREATE TABLE t (c INT) COMMENT 'c'\")\n        self.validate_identity(\"COALECSE(a, b, c, d)\")\n        self.validate_identity(\"SELECT CAST(`a`.`b` AS INT) FROM foo\")\n        self.validate_identity(\"SELECT APPROX_COUNT_DISTINCT(a) FROM x\")\n        self.validate_identity(\n            \"CREATE TABLE IF NOT EXISTS example_tbl_unique (user_id BIGINT NOT NULL, user_name VARCHAR(50) NOT NULL, city VARCHAR(20), age SMALLINT, sex TINYINT) UNIQUE KEY (user_id, user_name) DISTRIBUTED BY HASH (user_id) BUCKETS 10 PROPERTIES ('enable_unique_key_merge_on_write'='true')\"\n        )\n        self.validate_identity(\"INSERT OVERWRITE TABLE test PARTITION(p1, p2) VALUES (1, 2)\")\n\n    def test_time(self):\n        self.validate_identity(\"TIMESTAMP('2022-01-01')\")\n        self.validate_identity(\"DATE_TRUNC(event_date, 'DAY')\")\n        self.validate_identity(\"DATE_TRUNC('2010-12-02 19:28:30', 'HOUR')\")\n        self.validate_identity(\"CURRENT_DATE()\")\n\n    def test_regex(self):\n        self.validate_all(\n            \"SELECT REGEXP_LIKE(abc, '%foo%')\",\n            write={\n                \"doris\": \"SELECT REGEXP(abc, '%foo%')\",\n            },\n        )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE TABLE tbl\")\n        self.validate_identity(\"ANALYZE DATABASE db\")\n        self.validate_identity(\"ANALYZE TABLE TBL(c1, c2)\")\n\n    def test_key(self):\n        self.validate_identity(\"CREATE TABLE test_table (c1 INT, c2 INT) UNIQUE KEY (c1)\")\n        self.validate_identity(\"CREATE TABLE test_table (c1 INT, c2 INT) DUPLICATE KEY (c1)\")\n        self.validate_identity(\"CREATE MATERIALIZED VIEW test_table (c1 INT, c2 INT) KEY (c1)\")\n\n    def test_distributed(self):\n        self.validate_identity(\n            \"CREATE TABLE test_table (c1 INT, c2 INT) UNIQUE KEY (c1) DISTRIBUTED BY HASH (c1)\"\n        )\n        self.validate_identity(\"CREATE TABLE test_table (c1 INT, c2 INT) DISTRIBUTED BY RANDOM\")\n        self.validate_identity(\n            \"CREATE TABLE test_table (c1 INT, c2 INT) DISTRIBUTED BY RANDOM BUCKETS 1\"\n        )\n\n    def test_partition(self):\n        self.validate_identity(\n            \"CREATE TABLE test_table (c1 INT, c2 DATE) PARTITION BY RANGE (`c2`) (PARTITION `p201701` VALUES LESS THAN ('2017-02-01'), PARTITION `p201702` VALUES LESS THAN ('2017-03-01'))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (c1 INT, c2 DATE) PARTITION BY RANGE (`c2`) (PARTITION `p201701` VALUES [('2017-01-01'), ('2017-02-01')), PARTITION `other` VALUES LESS THAN (MAXVALUE))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (c1 INT, c2 DATE) PARTITION BY RANGE (`c2`) (FROM ('2000-11-14') TO ('2021-11-14') INTERVAL 2 YEAR)\"\n        )\n        self.validate_identity(\"CREATE TABLE test_table (c1 INT, c2 DATE) PARTITION BY (c2)\")\n        self.validate_identity(\"CREATE TABLE test_table (c1 INT, c2 DATE) PARTITION BY (c1, c2)\")\n        self.validate_identity(\n            \"CREATE TABLE test_table (c1 INT, c2 DATE) PARTITION BY (DATE_TRUNC(c2, 'MONTH'))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (c1 INT) PARTITION BY LIST (`c1`) (PARTITION p1 VALUES IN (1, 2), PARTITION p2 VALUES IN (3))\"\n        )\n\n    def test_table_alias_conversion(self):\n        \"\"\"Test conversion from postgres to Doris for DELETE/UPDATE statements with table aliases.\"\"\"\n\n        # Test cases for DELETE statements with table aliases\n        self.validate_all(\n            \"DELETE FROM sales s WHERE s.id = 1\",\n            read={\n                \"postgres\": \"DELETE FROM sales AS s WHERE s.id = 1\",\n            },\n            write={\n                \"doris\": \"DELETE FROM sales s WHERE s.id = 1\",\n                \"postgres\": \"DELETE FROM sales AS s WHERE s.id = 1\",\n            },\n        )\n\n        # DELETE with multiple table references\n        self.validate_all(\n            \"DELETE FROM orders o WHERE o.customer_id IN (SELECT c.id FROM customers AS c WHERE c.status_code = 'inactive')\",\n            read={\n                \"postgres\": \"DELETE FROM orders AS o WHERE o.customer_id IN (SELECT c.id FROM customers AS c WHERE c.status_code = 'inactive')\",\n            },\n            write={\n                \"doris\": \"DELETE FROM orders o WHERE o.customer_id IN (SELECT c.id FROM customers AS c WHERE c.status_code = 'inactive')\",\n                \"postgres\": \"DELETE FROM orders AS o WHERE o.customer_id IN (SELECT c.id FROM customers AS c WHERE c.status_code = 'inactive')\",\n            },\n        )\n\n        # DELETE with EXISTS clause\n        self.validate_all(\n            \"DELETE FROM temp_data t WHERE NOT EXISTS(SELECT 1 FROM main_data AS m WHERE m.id = t.id)\",\n            read={\n                \"postgres\": \"DELETE FROM temp_data AS t WHERE NOT EXISTS(SELECT 1 FROM main_data AS m WHERE m.id = t.id)\",\n            },\n            write={\n                \"doris\": \"DELETE FROM temp_data t WHERE NOT EXISTS(SELECT 1 FROM main_data AS m WHERE m.id = t.id)\",\n                \"postgres\": \"DELETE FROM temp_data AS t WHERE NOT EXISTS(SELECT 1 FROM main_data AS m WHERE m.id = t.id)\",\n            },\n        )\n\n        # UPDATE statements with table aliases\n        self.validate_all(\n            \"UPDATE employees e SET e.salary = e.salary * 1.1 WHERE e.department = 'IT'\",\n            read={\n                \"postgres\": \"UPDATE employees AS e SET e.salary = e.salary * 1.1 WHERE e.department = 'IT'\",\n            },\n            write={\n                \"doris\": \"UPDATE employees e SET e.salary = e.salary * 1.1 WHERE e.department = 'IT'\",\n                \"postgres\": \"UPDATE employees AS e SET e.salary = e.salary * 1.1 WHERE e.department = 'IT'\",\n            },\n        )\n\n        # UPDATE with multiple columns\n        self.validate_all(\n            \"UPDATE accounts a SET a.balance = a.balance + 100, a.status_code = 'active' WHERE a.account_type = 'savings'\",\n            read={\n                \"postgres\": \"UPDATE accounts AS a SET a.balance = a.balance + 100, a.status_code = 'active' WHERE a.account_type = 'savings'\",\n            },\n            write={\n                \"doris\": \"UPDATE accounts a SET a.balance = a.balance + 100, a.status_code = 'active' WHERE a.account_type = 'savings'\",\n                \"postgres\": \"UPDATE accounts AS a SET a.balance = a.balance + 100, a.status_code = 'active' WHERE a.account_type = 'savings'\",\n            },\n        )\n\n        # UPDATE with multiple table references in subquery\n        self.validate_all(\n            \"UPDATE prices p SET p.amount = p.amount * 0.9 WHERE p.product_id IN (SELECT pr.id FROM products AS pr JOIN categories AS c ON pr.category_id = c.id WHERE c.foo = 'Electronics')\",\n            read={\n                \"postgres\": \"UPDATE prices AS p SET p.amount = p.amount * 0.9 WHERE p.product_id IN (SELECT pr.id FROM products AS pr JOIN categories AS c ON pr.category_id = c.id WHERE c.foo = 'Electronics')\",\n            },\n            write={\n                \"doris\": \"UPDATE prices p SET p.amount = p.amount * 0.9 WHERE p.product_id IN (SELECT pr.id FROM products AS pr JOIN categories AS c ON pr.category_id = c.id WHERE c.foo = 'Electronics')\",\n                \"postgres\": \"UPDATE prices AS p SET p.amount = p.amount * 0.9 WHERE p.product_id IN (SELECT pr.id FROM products AS pr JOIN categories AS c ON pr.category_id = c.id WHERE c.foo = 'Electronics')\",\n            },\n        )\n\n    def test_rename_table(self):\n        self.validate_all(\n            \"ALTER TABLE db.t1 RENAME TO db.t2\",\n            write={\n                \"snowflake\": \"ALTER TABLE db.t1 RENAME TO db.t2\",\n                \"duckdb\": \"ALTER TABLE db.t1 RENAME TO t2\",\n                \"doris\": \"ALTER TABLE db.t1 RENAME t2\",\n            },\n        )\n\n    def test_materialized_view_properties(self):\n        # BUILD modes\n        self.validate_identity(\"CREATE MATERIALIZED VIEW mv BUILD IMMEDIATE AS SELECT 1\")\n        self.validate_identity(\"CREATE MATERIALIZED VIEW mv BUILD DEFERRED AS SELECT 1\")\n\n        # REFRESH methods with triggers\n        self.validate_identity(\"CREATE MATERIALIZED VIEW mv REFRESH COMPLETE ON MANUAL AS SELECT 1\")\n        self.validate_identity(\"CREATE MATERIALIZED VIEW mv REFRESH AUTO ON COMMIT AS SELECT 1\")\n        self.validate_identity(\n            \"CREATE MATERIALIZED VIEW mv REFRESH AUTO ON SCHEDULE EVERY 5 MINUTE STARTS '2025-01-01 00:00:00' AS SELECT 1\"\n        )\n\n        # Combined BUILD and REFRESH\n        self.validate_identity(\n            \"CREATE MATERIALIZED VIEW mv BUILD DEFERRED REFRESH AUTO ON SCHEDULE EVERY 10 MINUTE AS SELECT 1\"\n        )\n"
  },
  {
    "path": "tests/dialects/test_dremio.py",
    "content": "from tests.dialects.test_dialect import Validator\nfrom sqlglot import parse_one, exp, UnsupportedError, ErrorLevel, transpile, ParseError\nfrom sqlglot.optimizer.annotate_types import annotate_types\n\n\nclass TestDremio(Validator):\n    dialect = \"dremio\"\n    maxDiff = None\n\n    def test_type_mappings(self):\n        self.validate_identity(\"CAST(x AS SMALLINT)\", \"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS TINYINT)\", \"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS BINARY)\", \"CAST(x AS VARBINARY)\")\n        self.validate_identity(\"CAST(x AS TEXT)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS NCHAR)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS CHAR)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS TIMESTAMPNTZ)\", \"CAST(x AS TIMESTAMP)\")\n        self.validate_identity(\"CAST(x AS DATETIME)\", \"CAST(x AS TIMESTAMP)\")\n        self.validate_identity(\"CAST(x AS ARRAY)\", \"CAST(x AS LIST)\")\n        self.validate_identity(\"CAST(x AS BIT)\", \"CAST(x AS BOOLEAN)\")\n\n        # unsupported types\n        with self.assertRaises(UnsupportedError):\n            transpile(\n                \"CAST(x AS TIMESTAMPTZ)\",\n                read=\"oracle\",\n                write=\"dremio\",\n                unsupported_level=ErrorLevel.IMMEDIATE,\n            )\n        with self.assertRaises(UnsupportedError):\n            transpile(\n                \"CAST(x AS TIMESTAMPLTZ)\",\n                read=\"oracle\",\n                write=\"dremio\",\n                unsupported_level=ErrorLevel.IMMEDIATE,\n            )\n\n    def test_concat_coalesce(self):\n        self.validate_all(\n            \"SELECT CONCAT('a', NULL)\",\n            write={\n                \"dremio\": \"SELECT CONCAT('a', NULL)\",\n                \"\": \"SELECT CONCAT('a', COALESCE(NULL, ''))\",\n            },\n        )\n\n    def test_typed_division(self):\n        def _div_result_type(sql: str, dialect: str):\n            tree = parse_one(sql, read=dialect)\n            annotate_types(tree, dialect=dialect)\n            return tree.find(exp.Div).type.this\n\n        assert _div_result_type(\"SELECT 5 / 2\", \"dremio\") == exp.DataType.Type.BIGINT\n        assert _div_result_type(\"SELECT 5 / 2\", \"oracle\") == exp.DataType.Type.DOUBLE\n\n    def test_user_defined_types_unsupported(self):\n        with self.assertRaises(ParseError):\n            self.parse_one(\"CAST(x AS MY_CUSTOM_TYPE)\")\n\n    def test_null_ordering(self):\n        # NULLS LAST is the default, so generator can drop the clause\n        self.validate_identity(\n            \"SELECT * FROM t ORDER BY a NULLS LAST\", \"SELECT * FROM t ORDER BY a\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM t ORDER BY a DESC NULLS LAST\", \"SELECT * FROM t ORDER BY a DESC\"\n        )\n\n        # If the clause is not the default, it must be kept\n        self.validate_identity(\n            \"SELECT * FROM t ORDER BY a NULLS FIRST\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t ORDER BY a DESC NULLS FIRST\",\n        )\n\n    def test_convert_timezone(self):\n        self.validate_all(\n            \"SELECT CONVERT_TIMEZONE('America/Chicago', DateColumn)\",\n            write={\n                \"dremio\": \"SELECT CONVERT_TIMEZONE('America/Chicago', DateColumn)\",\n                \"\": \"SELECT DateColumn AT TIME ZONE 'America/Chicago'\",\n            },\n        )\n\n    def test_interval_plural(self):\n        self.validate_identity(\"INTERVAL '7' DAYS\", \"INTERVAL '7' DAY\")\n\n    def test_limit_only_literals(self):\n        self.validate_identity(\"SELECT * FROM t LIMIT 1 + 1\", \"SELECT * FROM t LIMIT 2\")\n\n    def test_multi_arg_distinct_unsupported(self):\n        self.validate_identity(\n            \"SELECT COUNT(DISTINCT a, b) FROM t\",\n            \"SELECT COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END) FROM t\",\n        )\n\n    def test_time_mapping(self):\n        ts = \"CAST('2025-06-24 12:34:56' AS TIMESTAMP)\"\n\n        self.validate_all(\n            f\"SELECT TO_CHAR({ts}, 'yyyy-mm-dd hh24:mi:ss')\",\n            read={\n                \"dremio\": f\"SELECT TO_CHAR({ts}, 'yyyy-mm-dd hh24:mi:ss')\",\n                \"postgres\": f\"SELECT TO_CHAR({ts}, 'YYYY-MM-DD HH24:MI:SS')\",\n                \"oracle\": f\"SELECT TO_CHAR({ts}, 'YYYY-MM-DD HH24:MI:SS')\",\n                \"duckdb\": f\"SELECT STRFTIME({ts}, '%Y-%m-%d %H:%M:%S')\",\n            },\n            write={\n                \"dremio\": f\"SELECT TO_CHAR({ts}, 'yyyy-mm-dd hh24:mi:ss')\",\n                \"postgres\": f\"SELECT TO_CHAR({ts}, 'YYYY-MM-DD HH24:MI:SS')\",\n                \"oracle\": f\"SELECT TO_CHAR({ts}, 'YYYY-MM-DD HH24:MI:SS')\",\n                \"duckdb\": f\"SELECT STRFTIME({ts}, '%Y-%m-%d %H:%M:%S')\",\n            },\n        )\n\n        self.validate_all(\n            f\"SELECT TO_CHAR({ts}, 'yy-ddd hh24:mi:ss.fff tzd')\",\n            read={\n                \"dremio\": f\"SELECT TO_CHAR({ts}, 'yy-ddd hh24:mi:ss.fff tzd')\",\n                \"postgres\": f\"SELECT TO_CHAR({ts}, 'YY-DDD HH24:MI:SS.US TZ')\",\n                \"oracle\": f\"SELECT TO_CHAR({ts}, 'YY-DDD HH24:MI:SS.FF6 %Z')\",\n                \"duckdb\": f\"SELECT STRFTIME({ts}, '%y-%j %H:%M:%S.%f %Z')\",\n            },\n            write={\n                \"dremio\": f\"SELECT TO_CHAR({ts}, 'yy-ddd hh24:mi:ss.fff tzd')\",\n                \"postgres\": f\"SELECT TO_CHAR({ts}, 'YY-DDD HH24:MI:SS.US TZ')\",\n                \"oracle\": f\"SELECT TO_CHAR({ts}, 'YY-DDD HH24:MI:SS.FF6 %Z')\",\n                \"duckdb\": f\"SELECT STRFTIME({ts}, '%y-%j %H:%M:%S.%f %Z')\",\n            },\n        )\n\n    def test_to_char_special(self):\n        # Numeric formats should have is_numeric=True\n        to_char = self.validate_identity(\"TO_CHAR(5555, '#')\").assert_is(exp.ToChar)\n        assert to_char.args[\"is_numeric\"] is True\n\n        to_char = self.validate_identity(\"TO_CHAR(3.14, '#.#')\").assert_is(exp.ToChar)\n        assert to_char.args[\"is_numeric\"] is True\n\n        to_char = self.validate_identity(\"TO_CHAR(columnname, '#.##')\").assert_is(exp.ToChar)\n        assert to_char.args[\"is_numeric\"] is True\n\n        # Non-numeric formats or columns should have is_numeric=None or False\n        to_char = self.validate_identity(\"TO_CHAR(5555)\").assert_is(exp.ToChar)\n        assert not to_char.args.get(\"is_numeric\")\n\n        to_char = self.validate_identity(\"TO_CHAR(3.14, columnname)\").assert_is(exp.ToChar)\n        assert not to_char.args.get(\"is_numeric\")\n\n        to_char = self.validate_identity(\"TO_CHAR(123, 'abcd')\").assert_is(exp.ToChar)\n        assert not to_char.args.get(\"is_numeric\")\n\n        to_char = self.validate_identity(\"TO_CHAR(3.14, UPPER('abcd'))\").assert_is(exp.ToChar)\n        assert not to_char.args.get(\"is_numeric\")\n\n    def test_date_add(self):\n        self.validate_identity(\"SELECT DATE_ADD(col, 1)\")\n        self.validate_identity(\"SELECT DATE_ADD(col, CAST(1 AS INTERVAL HOUR))\")\n        self.validate_identity(\n            \"SELECT DATE_ADD(TIMESTAMP '2022-01-01 12:00:00', CAST(-1 AS INTERVAL HOUR))\",\n            \"SELECT DATE_ADD(CAST('2022-01-01 12:00:00' AS TIMESTAMP), CAST(-1 AS INTERVAL HOUR))\",\n        )\n\n    def test_date_sub(self):\n        self.validate_identity(\"SELECT DATE_SUB(col, 1)\")\n        self.validate_identity(\"SELECT DATE_SUB(col, CAST(1 AS INTERVAL HOUR))\")\n        self.validate_identity(\n            \"SELECT DATE_SUB(TIMESTAMP '2022-01-01 12:00:00', CAST(-1 AS INTERVAL HOUR))\",\n            \"SELECT DATE_SUB(CAST('2022-01-01 12:00:00' AS TIMESTAMP), CAST(-1 AS INTERVAL HOUR))\",\n        )\n\n    def test_datetime_parsing(self):\n        self.validate_identity(\n            \"SELECT DATE_FORMAT(CAST('2025-08-18 15:30:00' AS TIMESTAMP), 'yyyy-mm-dd')\",\n            \"SELECT TO_CHAR(CAST('2025-08-18 15:30:00' AS TIMESTAMP), 'yyyy-mm-dd')\",\n        )\n\n    def test_array_generate_range(self):\n        self.validate_all(\n            \"ARRAY_GENERATE_RANGE(1, 4)\",\n            read={\"dremio\": \"ARRAY_GENERATE_RANGE(1, 4)\"},\n            write={\"duckdb\": \"GENERATE_SERIES(1, 4)\"},\n        )\n\n    def test_current_date_utc(self):\n        self.validate_identity(\"SELECT CURRENT_DATE_UTC\")\n        self.validate_identity(\n            \"SELECT CURRENT_DATE_UTC()\",\n            \"SELECT CURRENT_DATE_UTC\",\n        )\n\n    def test_repeatstr(self):\n        self.validate_identity(\"SELECT REPEAT(x, 5)\")\n        self.validate_identity(\"SELECT REPEATSTR(x, 5)\", \"SELECT REPEAT(x, 5)\")\n\n    def test_regexp_like(self):\n        self.validate_all(\n            \"REGEXP_MATCHES(x, y)\",\n            write={\n                \"dremio\": \"REGEXP_LIKE(x, y)\",\n                \"duckdb\": \"REGEXP_MATCHES(x, y)\",\n                \"presto\": \"REGEXP_LIKE(x, y)\",\n                \"hive\": \"x RLIKE y\",\n                \"spark\": \"x RLIKE y\",\n            },\n        )\n        self.validate_identity(\"REGEXP_MATCHES(x, y)\", \"REGEXP_LIKE(x, y)\")\n\n    def test_date_part(self):\n        self.validate_identity(\n            \"SELECT DATE_PART('YEAR', date '2021-04-01')\",\n            \"SELECT EXTRACT('YEAR' FROM CAST('2021-04-01' AS DATE))\",\n        )\n\n    def test_datetype(self):\n        self.validate_identity(\"DATETYPE(2024,2,2)\", \"DATE('2024-02-02')\")\n        self.validate_identity(\"DATETYPE(x,y,z)\", \"CAST(CONCAT(x, '-', y, '-', z) AS DATE)\")\n\n    def test_try_cast(self):\n        self.validate_all(\n            \"CAST(a AS FLOAT)\",\n            read={\n                \"dremio\": \"CAST(a AS FLOAT)\",\n                \"\": \"TRY_CAST(a AS FLOAT)\",\n                \"hive\": \"CAST(a AS FLOAT)\",\n            },\n        )\n"
  },
  {
    "path": "tests/dialects/test_drill.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestDrill(Validator):\n    dialect = \"drill\"\n\n    def test_drill(self):\n        self.validate_identity(\n            \"SELECT * FROM table(dfs.`test_data.xlsx`(type => 'excel', sheetName => 'secondSheet'))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM (SELECT * FROM t) PIVOT(avg(c1) AS ac1 FOR c2 IN ('V' AS v))\",\n        )\n\n        self.validate_all(\n            \"SELECT '2021-01-01' + INTERVAL 1 MONTH\",\n            write={\n                \"drill\": \"SELECT '2021-01-01' + INTERVAL '1' MONTH\",\n                \"mysql\": \"SELECT '2021-01-01' + INTERVAL '1' MONTH\",\n            },\n        )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE TABLE tbl COMPUTE STATISTICS\")\n        self.validate_identity(\"ANALYZE TABLE tbl COMPUTE STATISTICS SAMPLE 5 PERCENT\")\n"
  },
  {
    "path": "tests/dialects/test_druid.py",
    "content": "from sqlglot.dialects.dialect import Dialects\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestDruid(Validator):\n    dialect = \"druid\"\n\n    def test_druid(self):\n        self.validate_identity(\"SELECT MOD(1000, 60)\")\n        self.validate_identity(\"SELECT CEIL(__time TO WEEK) FROM t\")\n        self.validate_identity(\"SELECT CEIL(col) FROM t\")\n        self.validate_identity(\"SELECT CEIL(price, 2) AS rounded_price FROM t\")\n        self.validate_identity(\"SELECT FLOOR(__time TO WEEK) FROM t\")\n        self.validate_identity(\"SELECT FLOOR(col) FROM t\")\n        self.validate_identity(\"SELECT FLOOR(price, 2) AS rounded_price FROM t\")\n        self.validate_identity(\"SELECT CURRENT_TIMESTAMP\")\n        self.validate_identity(\"SELECT ARRAY[1, 2, 3]\")\n\n        # validate across all dialects\n        write = {dialect.value: \"FLOOR(__time TO WEEK)\" for dialect in Dialects}\n        self.validate_all(\n            \"FLOOR(__time TO WEEK)\",\n            write=write,\n        )\n"
  },
  {
    "path": "tests/dialects/test_duckdb.py",
    "content": "from sqlglot import ParseError, UnsupportedError, exp, parse_one\nfrom sqlglot.generator import logger as generator_logger\nfrom sqlglot.helper import logger as helper_logger\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestDuckDB(Validator):\n    dialect = \"duckdb\"\n\n    def test_duckdb(self):\n        # Numeric TRUNC - DuckDB only supports TRUNC(x), no decimals parameter\n        self.validate_identity(\"TRUNC(3.14)\").assert_is(exp.Trunc)\n        self.validate_all(\n            \"TRUNC(3.14159)\",\n            read={\"postgres\": \"TRUNC(3.14159, 2)\"},\n        )\n\n        self.validate_identity(\"SELECT ([1,2,3])[:-:-1]\", \"SELECT ([1, 2, 3])[:-1:-1]\")\n        self.validate_identity(\n            \"SELECT INTERVAL '1 hour'::VARCHAR\", \"SELECT CAST(INTERVAL '1' HOUR AS TEXT)\"\n        )\n        self.validate_identity(\n            \"PIVOT duckdb_functions() ON schema_name USING AVG(LENGTH(function_name))::INTEGER GROUP BY schema_name\",\n            \"PIVOT DUCKDB_FUNCTIONS() ON schema_name USING CAST(AVG(LENGTH(function_name)) AS INT) GROUP BY schema_name\",\n        )\n        self.validate_identity(\"SELECT str[0:1]\")\n        self.validate_identity(\"SELECT COSH(1.5)\")\n        self.validate_identity(\"SELECT MODE(category)\")\n        self.validate_identity(\"SELECT e'\\\\n'\")\n        self.validate_identity(\"SELECT e'\\\\t'\")\n        self.validate_identity(\n            \"SELECT e'update table_name set a = \\\\'foo\\\\' where 1 = 0' AS x FROM tab\",\n            \"SELECT e'update table_name set a = ''foo'' where 1 = 0' AS x FROM tab\",\n        )\n        with self.assertRaises(ParseError):\n            parse_one(\"1 //\", read=\"duckdb\")\n\n        expr = annotate_types(\n            self.validate_identity(\n                \"WITH _data AS (SELECT [{'a': 1, 'b': 2}, {'a': 2, 'b': 3}] AS col) SELECT t.col['b'] FROM _data, UNNEST(_data.col) AS t(col) WHERE t.col['a'] = 1\",\n                \"WITH _data AS (SELECT [{'a': 1, 'b': 2}, {'a': 2, 'b': 3}] AS col) SELECT t.col['b'] FROM _data JOIN UNNEST(_data.col) AS t(col) ON TRUE WHERE t.col['a'] = 1\",\n            )\n        )\n        self.assertEqual(\n            expr.sql(dialect=\"bigquery\"),\n            \"WITH _data AS (SELECT [STRUCT(1 AS a, 2 AS b), STRUCT(2 AS a, 3 AS b)] AS col) SELECT col.b FROM _data, UNNEST(_data.col) AS col WHERE col.a = 1\",\n        )\n\n        struct_array_type = exp.maybe_parse(\n            \"STRUCT(k TEXT, v STRUCT(v_str TEXT, v_int INT, v_int_arr INT[]))[]\",\n            into=exp.DataType,\n            dialect=\"duckdb\",\n        )\n        self.assertEqual(\n            struct_array_type.sql(\"duckdb\"),\n            \"STRUCT(k TEXT, v STRUCT(v_str TEXT, v_int INT, v_int_arr INT[]))[]\",\n        )\n\n        self.validate_all(\n            \"(c LIKE 'a' OR c LIKE 'b') AND other_cond\",\n            read={\n                \"databricks\": \"c LIKE ANY ('a', 'b') AND other_cond\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FIRST_VALUE(c IGNORE NULLS) OVER (PARTITION BY gb ORDER BY ob) FROM t\",\n            write={\n                \"duckdb\": \"SELECT FIRST_VALUE(c IGNORE NULLS) OVER (PARTITION BY gb ORDER BY ob) FROM t\",\n                \"sqlite\": UnsupportedError,\n                \"mysql\": UnsupportedError,\n                \"postgres\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"SELECT FIRST_VALUE(c RESPECT NULLS) OVER (PARTITION BY gb ORDER BY ob) FROM t\",\n            write={\n                \"duckdb\": \"SELECT FIRST_VALUE(c RESPECT NULLS) OVER (PARTITION BY gb ORDER BY ob) FROM t\",\n                \"sqlite\": \"SELECT FIRST_VALUE(c) OVER (PARTITION BY gb ORDER BY ob NULLS LAST) FROM t\",\n                \"mysql\": \"SELECT FIRST_VALUE(c) RESPECT NULLS OVER (PARTITION BY gb ORDER BY CASE WHEN ob IS NULL THEN 1 ELSE 0 END, ob) FROM t\",\n                \"postgres\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS UUID)\",\n            write={\n                \"bigquery\": \"CAST(x AS STRING)\",\n                \"duckdb\": \"CAST(x AS UUID)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT APPROX_TOP_K(category, 3) FROM t\",\n            write={\n                \"snowflake\": \"SELECT APPROX_TOP_K(category, 3) FROM t\",\n                \"duckdb\": UnsupportedError,\n            },\n        )\n\n        self.validate_all(\n            \"\"\"SELECT CASE WHEN JSON_VALID('{\"x: 1}') THEN '{\"x: 1}' ELSE NULL END\"\"\",\n            read={\n                \"duckdb\": \"\"\"SELECT CASE WHEN JSON_VALID('{\"x: 1}') THEN '{\"x: 1}' ELSE NULL END\"\"\",\n                \"snowflake\": \"\"\"SELECT TRY_PARSE_JSON('{\"x: 1}')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT straight_join\",\n            write={\n                \"duckdb\": \"SELECT straight_join\",\n                \"mysql\": \"SELECT `straight_join`\",\n            },\n        )\n        self.validate_all(\n            'STRUCT_PACK(\"a b\" := 1)',\n            write={\n                \"duckdb\": \"{'a b': 1}\",\n                \"spark\": \"STRUCT(1 AS `a b`)\",\n                \"snowflake\": \"OBJECT_CONSTRUCT('a b', 1)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_TO_STRING(arr, delim)\",\n            read={\n                \"bigquery\": \"ARRAY_TO_STRING(arr, delim)\",\n                \"postgres\": \"ARRAY_TO_STRING(arr, delim)\",\n                \"presto\": \"ARRAY_JOIN(arr, delim)\",\n                \"spark\": \"ARRAY_JOIN(arr, delim)\",\n            },\n            write={\n                \"bigquery\": \"ARRAY_TO_STRING(arr, delim)\",\n                \"duckdb\": \"ARRAY_TO_STRING(arr, delim)\",\n                \"postgres\": \"ARRAY_TO_STRING(arr, delim)\",\n                \"presto\": \"ARRAY_JOIN(arr, delim)\",\n                \"snowflake\": \"ARRAY_TO_STRING(arr, delim)\",\n                \"spark\": \"ARRAY_JOIN(arr, delim)\",\n                \"tsql\": \"STRING_AGG(arr, delim)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CASE WHEN delim IS NULL THEN NULL ELSE ARRAY_TO_STRING(LIST_TRANSFORM(arr, x -> COALESCE(CAST(x AS TEXT), '')), delim) END\",\n            read={\n                \"snowflake\": \"SELECT ARRAY_TO_STRING(arr, delim)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SUM(X) OVER (ORDER BY x)\",\n            write={\n                \"bigquery\": \"SELECT SUM(X) OVER (ORDER BY x)\",\n                \"duckdb\": \"SELECT SUM(X) OVER (ORDER BY x)\",\n                \"mysql\": \"SELECT SUM(X) OVER (ORDER BY CASE WHEN x IS NULL THEN 1 ELSE 0 END, x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SUM(X) OVER (ORDER BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n            write={\n                \"bigquery\": \"SELECT SUM(X) OVER (ORDER BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n                \"duckdb\": \"SELECT SUM(X) OVER (ORDER BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n                \"mysql\": \"SELECT SUM(X) OVER (ORDER BY x RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x ORDER BY 1 NULLS LAST\",\n            write={\n                \"duckdb\": \"SELECT * FROM x ORDER BY 1\",\n                \"mysql\": \"SELECT * FROM x ORDER BY 1\",\n            },\n        )\n\n        self.validate_all(\n            \"CREATE TEMPORARY FUNCTION f1(a, b) AS (a + b)\",\n            read={\n                \"bigquery\": \"CREATE TEMP FUNCTION f1(a INT64, b INT64) AS (a + b)\",\n            },\n        )\n        self.validate_identity(\"SELECT GET_BIT(CAST('0110010' AS BIT), 2)\")\n        self.validate_identity(\"SELECT 1 WHERE x > $1\")\n        self.validate_identity(\"SELECT 1 WHERE x > $name\")\n        self.validate_identity(\"\"\"SELECT '{\"x\": 1}' -> c FROM t\"\"\")\n\n        self.assertEqual(\n            parse_one(\"select * from t limit (select 5)\").sql(dialect=\"duckdb\"),\n            exp.select(\"*\").from_(\"t\").limit(exp.select(\"5\").subquery()).sql(dialect=\"duckdb\"),\n        )\n        self.assertEqual(\n            parse_one(\"select * from t offset (select 5)\").sql(dialect=\"duckdb\"),\n            exp.select(\"*\").from_(\"t\").offset(exp.select(\"5\").subquery()).sql(dialect=\"duckdb\"),\n        )\n\n        self.validate_all(\n            \"{'a': 1, 'b': '2'}\",\n            write={\n                \"presto\": \"CAST(ROW(1, '2') AS ROW(a INTEGER, b VARCHAR))\",\n            },\n        )\n        self.validate_all(\n            \"struct_pack(a := 1, b := 2)\",\n            write={\n                \"presto\": \"CAST(ROW(1, 2) AS ROW(a INTEGER, b INTEGER))\",\n            },\n        )\n        self.validate_all(\n            \"struct_pack(a := 1, b := x)\",\n            write={\n                \"duckdb\": \"{'a': 1, 'b': x}\",\n                \"presto\": UnsupportedError,\n            },\n        )\n\n        for join_type in (\"SEMI\", \"ANTI\"):\n            exists = \"EXISTS\" if join_type == \"SEMI\" else \"NOT EXISTS\"\n\n            self.validate_all(\n                f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                write={\n                    \"bigquery\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"clickhouse\": f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                    \"databricks\": f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                    \"doris\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"drill\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"duckdb\": f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                    \"hive\": f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                    \"mysql\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"oracle\": f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                    \"postgres\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"presto\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"redshift\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"snowflake\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"spark\": f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                    \"sqlite\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"starrocks\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"teradata\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"trino\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                    \"tsql\": f\"SELECT * FROM t1 WHERE {exists}(SELECT 1 FROM t2 WHERE t1.x = t2.x)\",\n                },\n            )\n            self.validate_all(\n                f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                read={\n                    \"duckdb\": f\"SELECT * FROM t1 {join_type} JOIN t2 ON t1.x = t2.x\",\n                    \"spark\": f\"SELECT * FROM t1 LEFT {join_type} JOIN t2 ON t1.x = t2.x\",\n                },\n            )\n\n        self.validate_identity(\"SELECT EXP(1)\")\n        self.validate_identity(\"\"\"SELECT '{\"duck\": [1, 2, 3]}' -> '$.duck[#-1]'\"\"\")\n\n        self.validate_all(\n            \"SELECT RANGE(1, 5)\",\n            write={\n                \"duckdb\": \"SELECT RANGE(1, 5)\",\n                \"spark\": \"SELECT SEQUENCE(1, 4)\",\n                \"snowflake\": \"SELECT ARRAY_GENERATE_RANGE(1, 5)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANGE(1, 5, 2)\",\n            write={\n                \"duckdb\": \"SELECT RANGE(1, 5, 2)\",\n                \"spark\": \"SELECT SEQUENCE(1, 3, 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANGE(1, 1)\",\n            write={\n                \"duckdb\": \"SELECT RANGE(1, 1)\",\n                \"spark\": \"SELECT ARRAY()\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANGE(5, 1, -1)\",\n            write={\n                \"duckdb\": \"SELECT RANGE(5, 1, -1)\",\n                \"spark\": \"SELECT SEQUENCE(5, 2, -1)\",\n                \"snowflake\": \"SELECT ARRAY_GENERATE_RANGE(5, 1, -1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANGE(5, 1, 0)\",\n            write={\n                \"duckdb\": \"SELECT RANGE(5, 1, 0)\",\n                \"spark\": \"SELECT ARRAY()\",\n            },\n        )\n        self.validate_all(\n            \"WITH t AS (SELECT 5 AS c) SELECT RANGE(1, c) FROM t\",\n            write={\n                \"duckdb\": \"WITH t AS (SELECT 5 AS c) SELECT RANGE(1, c) FROM t\",\n                \"spark\": \"WITH t AS (SELECT 5 AS c) SELECT IF((c - 1) < 1, ARRAY(), SEQUENCE(1, (c - 1))) FROM t\",\n            },\n        )\n        # Test edge case: RANGE(1, 2) should return [1], not []\n        self.validate_all(\n            \"WITH t AS (SELECT 2 AS c) SELECT RANGE(1, c) FROM t\",\n            write={\n                \"duckdb\": \"WITH t AS (SELECT 2 AS c) SELECT RANGE(1, c) FROM t\",\n                \"spark\": \"WITH t AS (SELECT 2 AS c) SELECT IF((c - 1) < 1, ARRAY(), SEQUENCE(1, (c - 1))) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANGE(1, 2)\",\n            write={\n                \"duckdb\": \"SELECT RANGE(1, 2)\",\n                \"spark\": \"SELECT SEQUENCE(1, 1)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT JSON_EXTRACT('{\"duck\": [1, 2, 3]}', '/duck/0')\"\"\",\n            write={\n                \"\": \"\"\"SELECT JSON_EXTRACT('{\"duck\": [1, 2, 3]}', '/duck/0')\"\"\",\n                \"duckdb\": \"\"\"SELECT '{\"duck\": [1, 2, 3]}' -> '/duck/0'\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT JSON('{\"fruit\":\"banana\"}') -> 'fruit'\"\"\",\n            write={\n                \"duckdb\": \"\"\"SELECT JSON('{\"fruit\":\"banana\"}') -> '$.fruit'\"\"\",\n                \"snowflake\": \"\"\"SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT JSON('{\"fruit\": {\"foo\": \"banana\"}}') -> 'fruit' -> 'foo'\"\"\",\n            write={\n                \"duckdb\": \"\"\"SELECT JSON('{\"fruit\": {\"foo\": \"banana\"}}') -> '$.fruit' -> '$.foo'\"\"\",\n                \"snowflake\": \"\"\"SELECT GET_PATH(GET_PATH(PARSE_JSON('{\"fruit\": {\"foo\": \"banana\"}}'), 'fruit'), 'foo')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT {'bla': column1, 'foo': column2, 'bar': column3} AS data FROM source_table\",\n            read={\n                \"bigquery\": \"SELECT STRUCT(column1 AS bla, column2 AS foo, column3 AS bar) AS data FROM source_table\",\n                \"duckdb\": \"SELECT {'bla': column1, 'foo': column2, 'bar': column3} AS data FROM source_table\",\n            },\n            write={\n                \"bigquery\": \"SELECT STRUCT(column1 AS bla, column2 AS foo, column3 AS bar) AS data FROM source_table\",\n            },\n        )\n        self.validate_all(\n            \"WITH cte(x) AS (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) SELECT AVG(x) FILTER (WHERE x > 1) FROM cte\",\n            write={\n                \"duckdb\": \"WITH cte(x) AS (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) SELECT AVG(x) FILTER(WHERE x > 1) FROM cte\",\n                \"snowflake\": \"WITH cte(x) AS (SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) SELECT AVG(IFF(x > 1, x, NULL)) FROM cte\",\n            },\n        )\n        self.validate_all(\n            \"SELECT AVG(x) FILTER (WHERE TRUE) FROM t\",\n            write={\n                \"duckdb\": \"SELECT AVG(x) FILTER(WHERE TRUE) FROM t\",\n                \"snowflake\": \"SELECT AVG(IFF(TRUE, x, NULL)) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNNEST(ARRAY[1, 2, 3]), UNNEST(ARRAY[4, 5]), UNNEST(ARRAY[6])\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, col, NULL) AS col, IF(pos = pos_3, col_2, NULL) AS col_2, IF(pos = pos_4, col_3, NULL) AS col_3 FROM UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH([1, 2, 3]), ARRAY_LENGTH([4, 5]), ARRAY_LENGTH([6])) - 1)) AS pos CROSS JOIN UNNEST([1, 2, 3]) AS col WITH OFFSET AS pos_2 CROSS JOIN UNNEST([4, 5]) AS col_2 WITH OFFSET AS pos_3 CROSS JOIN UNNEST([6]) AS col_3 WITH OFFSET AS pos_4 WHERE ((pos = pos_2 OR (pos > (ARRAY_LENGTH([1, 2, 3]) - 1) AND pos_2 = (ARRAY_LENGTH([1, 2, 3]) - 1))) AND (pos = pos_3 OR (pos > (ARRAY_LENGTH([4, 5]) - 1) AND pos_3 = (ARRAY_LENGTH([4, 5]) - 1)))) AND (pos = pos_4 OR (pos > (ARRAY_LENGTH([6]) - 1) AND pos_4 = (ARRAY_LENGTH([6]) - 1)))\",\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col, IF(_u.pos = _u_3.pos_3, _u_3.col_2) AS col_2, IF(_u.pos = _u_4.pos_4, _u_4.col_3) AS col_3 FROM UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(ARRAY[1, 2, 3]), CARDINALITY(ARRAY[4, 5]), CARDINALITY(ARRAY[6])))) AS _u(pos) CROSS JOIN UNNEST(ARRAY[1, 2, 3]) WITH ORDINALITY AS _u_2(col, pos_2) CROSS JOIN UNNEST(ARRAY[4, 5]) WITH ORDINALITY AS _u_3(col_2, pos_3) CROSS JOIN UNNEST(ARRAY[6]) WITH ORDINALITY AS _u_4(col_3, pos_4) WHERE ((_u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(ARRAY[1, 2, 3]) AND _u_2.pos_2 = CARDINALITY(ARRAY[1, 2, 3]))) AND (_u.pos = _u_3.pos_3 OR (_u.pos > CARDINALITY(ARRAY[4, 5]) AND _u_3.pos_3 = CARDINALITY(ARRAY[4, 5])))) AND (_u.pos = _u_4.pos_4 OR (_u.pos > CARDINALITY(ARRAY[6]) AND _u_4.pos_4 = CARDINALITY(ARRAY[6])))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT UNNEST(ARRAY[1, 2, 3]), UNNEST(ARRAY[4, 5]), UNNEST(ARRAY[6]) FROM x\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, col, NULL) AS col, IF(pos = pos_3, col_2, NULL) AS col_2, IF(pos = pos_4, col_3, NULL) AS col_3 FROM x CROSS JOIN UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH([1, 2, 3]), ARRAY_LENGTH([4, 5]), ARRAY_LENGTH([6])) - 1)) AS pos CROSS JOIN UNNEST([1, 2, 3]) AS col WITH OFFSET AS pos_2 CROSS JOIN UNNEST([4, 5]) AS col_2 WITH OFFSET AS pos_3 CROSS JOIN UNNEST([6]) AS col_3 WITH OFFSET AS pos_4 WHERE ((pos = pos_2 OR (pos > (ARRAY_LENGTH([1, 2, 3]) - 1) AND pos_2 = (ARRAY_LENGTH([1, 2, 3]) - 1))) AND (pos = pos_3 OR (pos > (ARRAY_LENGTH([4, 5]) - 1) AND pos_3 = (ARRAY_LENGTH([4, 5]) - 1)))) AND (pos = pos_4 OR (pos > (ARRAY_LENGTH([6]) - 1) AND pos_4 = (ARRAY_LENGTH([6]) - 1)))\",\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col, IF(_u.pos = _u_3.pos_3, _u_3.col_2) AS col_2, IF(_u.pos = _u_4.pos_4, _u_4.col_3) AS col_3 FROM x CROSS JOIN UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(ARRAY[1, 2, 3]), CARDINALITY(ARRAY[4, 5]), CARDINALITY(ARRAY[6])))) AS _u(pos) CROSS JOIN UNNEST(ARRAY[1, 2, 3]) WITH ORDINALITY AS _u_2(col, pos_2) CROSS JOIN UNNEST(ARRAY[4, 5]) WITH ORDINALITY AS _u_3(col_2, pos_3) CROSS JOIN UNNEST(ARRAY[6]) WITH ORDINALITY AS _u_4(col_3, pos_4) WHERE ((_u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(ARRAY[1, 2, 3]) AND _u_2.pos_2 = CARDINALITY(ARRAY[1, 2, 3]))) AND (_u.pos = _u_3.pos_3 OR (_u.pos > CARDINALITY(ARRAY[4, 5]) AND _u_3.pos_3 = CARDINALITY(ARRAY[4, 5])))) AND (_u.pos = _u_4.pos_4 OR (_u.pos > CARDINALITY(ARRAY[6]) AND _u_4.pos_4 = CARDINALITY(ARRAY[6])))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNNEST(x) + 1\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, col, NULL) + 1 AS col FROM UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(x)) - 1)) AS pos CROSS JOIN UNNEST(x) AS col WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH(x) - 1) AND pos_2 = (ARRAY_LENGTH(x) - 1))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNNEST(x) + 1 AS y\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, y, NULL) + 1 AS y FROM UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(x)) - 1)) AS pos CROSS JOIN UNNEST(x) AS y WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH(x) - 1) AND pos_2 = (ARRAY_LENGTH(x) - 1))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF('DAY', CAST('2020-01-01' AS DATE), CAST('2025-10-12' AS DATE))\",\n            read={\n                \"snowflake\": \"SELECT DATEDIFF('day', '2020-01-01', '2025-10-12')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF('SECOND', CAST('2020-01-01' AS DATE), CAST('2025-10-12 00:56:42.345' AS TIMESTAMP))\",\n            read={\n                \"duckdb\": \"SELECT DATE_DIFF('SECOND', CAST('2020-01-01' AS DATE), CAST('2025-10-12 00:56:42.345' AS TIMESTAMP))\",\n                \"snowflake\": \"SELECT DATEDIFF('second', '2020-01-01', '2025-10-12 00:56:42.345')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF('SECOND', CAST('2020-01-01' AS DATE), CAST('2025-10-12 00:56:42.345+07:00' AS TIMESTAMPTZ))\",\n            read={\n                \"duckdb\": \"SELECT DATE_DIFF('SECOND', CAST('2020-01-01' AS DATE), CAST('2025-10-12 00:56:42.345+07:00' AS TIMESTAMPTZ))\",\n                \"snowflake\": \"SELECT DATEDIFF('second', '2020-01-01', '2025-10-12 00:56:42.345+07:00')\",\n            },\n        )\n\n        # https://github.com/duckdb/duckdb/releases/tag/v0.8.0\n        self.assertEqual(\n            parse_one(\"a / b\", read=\"duckdb\").assert_is(exp.Div).sql(dialect=\"duckdb\"), \"a / b\"\n        )\n        self.assertEqual(\n            parse_one(\"a // b\", read=\"duckdb\").assert_is(exp.IntDiv).sql(dialect=\"duckdb\"), \"a // b\"\n        )\n\n        self.validate_identity(\n            \"SELECT tbl.x*1e4+tbl.y FROM tbl\",\n            \"SELECT tbl.x * 1e4 + tbl.y FROM tbl\",\n        )\n        self.validate_identity(\"DAYNAME(x)\")\n        self.validate_identity(\"MONTHNAME(x)\")\n        self.validate_identity(\n            \"SELECT LIST_TRANSFORM([5, NULL, 6], (x, y) -> COALESCE(x, y, 0) + 1)\"\n        )\n        self.validate_identity(\n            \"SELECT LIST_TRANSFORM([5, NULL, 6], LAMBDA x, y : COALESCE(x, y, 0) + 1)\"\n        )\n        self.validate_identity(\n            \"SELECT LIST_TRANSFORM(LIST_FILTER([0, 1, 2, 3, 4, 5], LAMBDA x : x % 2 = 0), LAMBDA y : y * y)\"\n        )\n        self.validate_identity(\n            \"\"\"ARG_MIN({'d': \"DATE\", 'ts': \"TIMESTAMP\", 'i': \"INT\", 'b': \"BIGINT\", 's': \"VARCHAR\"}, \"DOUBLE\")\"\"\"\n        )\n        self.validate_identity(\n            \"ARG_MAX(keyword_name, keyword_category, 3 ORDER BY keyword_name DESC)\"\n        )\n        self.validate_identity(\"INSERT INTO t DEFAULT VALUES RETURNING (c1)\")\n        self.validate_identity(\"CREATE TABLE notes (watermark TEXT)\")\n        self.validate_identity(\"SELECT LIST_TRANSFORM([5, NULL, 6], LAMBDA x : COALESCE(x, 0) + 1)\")\n        self.validate_identity(\"SELECT LIST_TRANSFORM(nbr, LAMBDA x : x + 1) FROM article AS a\")\n        self.validate_identity(\"SELECT * FROM my_ducklake.demo AT (VERSION => 2)\")\n        self.validate_identity(\"SELECT TO_BINARY('test')\")\n        self.validate_identity(\"SELECT UUIDV7()\")\n        self.validate_identity(\"SELECT TRY(LOG(0))\")\n        self.validate_identity(\"x::timestamp\", \"CAST(x AS TIMESTAMP)\")\n        self.validate_identity(\"x::timestamp without time zone\", \"CAST(x AS TIMESTAMP)\")\n        self.validate_identity(\"x::timestamp with time zone\", \"CAST(x AS TIMESTAMPTZ)\")\n        self.validate_identity(\"CAST(x AS FOO)\")\n        self.validate_identity(\"SELECT UNNEST([1, 2])\").selects[0].assert_is(exp.UDTF)\n        self.validate_identity(\"'red' IN flags\").args[\"field\"].assert_is(exp.Column)\n        self.validate_identity(\"'red' IN tbl.flags\")\n        self.validate_identity(\"CREATE TABLE tbl1 (u UNION(num INT, str TEXT))\")\n        self.validate_identity(\"INSERT INTO x BY NAME SELECT 1 AS y\")\n        self.validate_identity(\"SELECT 1 AS x UNION ALL BY NAME SELECT 2 AS x\")\n        self.validate_identity(\"SELECT SUM(x) FILTER (x = 1)\", \"SELECT SUM(x) FILTER(WHERE x = 1)\")\n        self.validate_identity(\"SELECT * FROM GLOB(x)\")\n        self.validate_identity(\"SELECT MAP(['key1', 'key2', 'key3'], [10, 20, 30])\")\n        self.validate_identity(\"SELECT MAP {'x': 1}\")\n        self.validate_identity(\"SELECT (MAP {'x': 1})['x']\")\n        self.validate_identity(\"SELECT df1.*, df2.* FROM df1 POSITIONAL JOIN df2\")\n        self.validate_identity(\"MAKE_TIMESTAMP(1992, 9, 20, 13, 34, 27.123456)\")\n        self.validate_identity(\"MAKE_TIMESTAMP(1667810584123456)\")\n        self.validate_identity(\"SELECT EPOCH_MS(10) AS t\")\n        self.validate_identity(\"SELECT MAKE_TIMESTAMP(10) AS t\")\n        self.validate_identity(\"SELECT TO_TIMESTAMP(10) AS t\")\n        self.validate_identity(\"SELECT UNNEST(col, recursive := TRUE) FROM t\")\n        self.validate_identity(\"VAR_POP(a)\")\n        self.validate_identity(\"SELECT * FROM foo ASOF LEFT JOIN bar ON a = b\")\n        self.validate_identity(\"SELECT {'a': 1} AS x\")\n        self.validate_identity(\"SELECT {'a': {'b': {'c': 1}}, 'd': {'e': 2}} AS x\")\n        self.validate_identity(\"SELECT {'x': 1, 'y': 2, 'z': 3}\")\n        self.validate_identity(\"SELECT {'key1': 'string', 'key2': 1, 'key3': 12.345}\")\n        self.validate_identity(\"SELECT ROW(x, x + 1, y) FROM (SELECT 1 AS x, 'a' AS y)\")\n        self.validate_identity(\"SELECT (x, x + 1, y) FROM (SELECT 1 AS x, 'a' AS y)\")\n        self.validate_identity(\"SELECT a.x FROM (SELECT {'x': 1, 'y': 2, 'z': 3} AS a)\")\n        self.validate_identity(\"FROM  x SELECT x UNION SELECT 1\", \"SELECT x FROM x UNION SELECT 1\")\n        self.validate_identity(\"FROM (FROM tbl)\", \"SELECT * FROM (SELECT * FROM tbl)\")\n        self.validate_identity(\"FROM tbl\", \"SELECT * FROM tbl\")\n        self.validate_identity(\n            \"SELECT * FROM t1 WHERE NOT EXISTS(FROM t2 WHERE t2.id = t1.id)\",\n            \"SELECT * FROM t1 WHERE NOT EXISTS(SELECT * FROM t2 WHERE t2.id = t1.id)\",\n        )\n        self.validate_identity(\"x -> '$.family'\")\n        self.validate_identity(\"CREATE TABLE color (name ENUM('RED', 'GREEN', 'BLUE'))\")\n        self.validate_identity(\"SELECT * FROM foo WHERE bar > $baz AND bla = $bob\")\n        self.validate_identity(\"SUMMARIZE tbl\").assert_is(exp.Summarize)\n        self.validate_identity(\"SUMMARIZE SELECT * FROM tbl\").assert_is(exp.Summarize)\n        self.validate_identity(\"CREATE TABLE tbl_summary AS SELECT * FROM (SUMMARIZE tbl)\")\n        self.validate_identity(\"SELECT STAR(tbl, exclude := [foo])\")\n        self.validate_identity(\"UNION_VALUE(k1 := 1)\").find(exp.PropertyEQ).this.assert_is(\n            exp.Identifier\n        )\n        self.validate_identity(\n            \"MERGE INTO people USING (SELECT 1 AS id, 98000.0 AS salary) AS salary_updates USING (id) WHEN MATCHED THEN UPDATE SET salary = salary_updates.salary\"\n        )\n        self.validate_identity(\n            \"MERGE INTO people USING (SELECT 1 AS id, 98000.0 AS salary) AS salary_updates USING (id) WHEN MATCHED THEN UPDATE\"\n        )\n        self.validate_identity(\n            \"SELECT species, island, COUNT(*) FROM t GROUP BY GROUPING SETS (species), GROUPING SETS (island)\"\n        )\n        self.validate_identity(\n            \"SELECT species, island, COUNT(*) FROM t GROUP BY CUBE (species), CUBE (island)\"\n        )\n        self.validate_identity(\n            \"SELECT species, island, COUNT(*) FROM t GROUP BY ROLLUP (species), ROLLUP (island)\"\n        )\n        self.validate_identity(\n            \"SUMMARIZE TABLE 'https://blobs.duckdb.org/data/Star_Trek-Season_1.csv'\"\n        ).assert_is(exp.Summarize)\n        self.validate_identity(\n            \"\"\"COPY (SELECT * FROM \"input.parquet\" USING SAMPLE RESERVOIR (5000 ROWS)) TO 'output.parquet' WITH (FORMAT PARQUET, KV_METADATA {'origin': 'Dagster', 'dagster_run_id': '98c85a11-d05c-4935-bfa2-198214c2204'})\"\"\"\n        )\n\n        for join_type in (\"LEFT\", \"LEFT OUTER\", \"INNER\"):\n            with self.subTest(f\"Testing transpilation of join {join_type} with UNNEST\"):\n                self.validate_all(\n                    f\"SELECT * FROM x {join_type} JOIN UNNEST(y) ON TRUE\",\n                    read={\n                        \"bigquery\": f\"SELECT * FROM x {join_type} JOIN UNNEST(y)\",\n                    },\n                    write={\n                        \"bigquery\": f\"SELECT * FROM x {join_type} JOIN UNNEST(y) ON TRUE\",\n                        \"duckdb\": f\"SELECT * FROM x {join_type} JOIN UNNEST(y) ON TRUE\",\n                    },\n                )\n\n        self.validate_identity(\n            \"\"\"SELECT '{ \"family\": \"anatidae\", \"species\": [ \"duck\", \"goose\", \"swan\", null ] }' ->> ['$.family', '$.species']\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT $🦆$foo$🦆$\",\n            \"SELECT 'foo'\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t PIVOT(FIRST(t) AS t, FOR quarter IN ('Q1', 'Q2'))\",\n            \"SELECT * FROM t PIVOT(FIRST(t) AS t FOR quarter IN ('Q1', 'Q2'))\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT JSON_EXTRACT_STRING('{ \"family\": \"anatidae\", \"species\": [ \"duck\", \"goose\", \"swan\", null ] }', ['$.family', '$.species'])\"\"\",\n            \"\"\"SELECT '{ \"family\": \"anatidae\", \"species\": [ \"duck\", \"goose\", \"swan\", null ] }' ->> ['$.family', '$.species']\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT col FROM t WHERE JSON_EXTRACT_STRING(col, '$.id') NOT IN ('b')\",\n            \"SELECT col FROM t WHERE NOT (col ->> '$.id') IN ('b')\",\n        )\n        self.validate_identity(\n            \"SELECT a, LOGICAL_OR(b) FROM foo GROUP BY a\",\n            \"SELECT a, BOOL_OR(CAST(b AS BOOLEAN)) FROM foo GROUP BY a\",\n        )\n        self.validate_identity(\n            \"SELECT JSON_EXTRACT_STRING(c, '$.k1') = 'v1'\",\n            \"SELECT (c ->> '$.k1') = 'v1'\",\n        )\n        self.validate_identity(\n            \"SELECT JSON_EXTRACT(c, '$.k1') = 'v1'\",\n            \"SELECT (c -> '$.k1') = 'v1'\",\n        )\n        self.validate_identity(\n            \"SELECT JSON_EXTRACT(c, '$[*].id')[0:2]\",\n            \"SELECT (c -> '$[*].id')[0:2]\",\n        )\n        self.validate_identity(\n            \"SELECT JSON_EXTRACT_STRING(c, '$[*].id')[0:2]\",\n            \"SELECT (c ->> '$[*].id')[0:2]\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT '{\"foo\": [1, 2, 3]}' -> 'foo' -> 0\"\"\",\n            \"\"\"SELECT '{\"foo\": [1, 2, 3]}' -> '$.foo' -> '$[0]'\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT ($$hello)'world$$)\",\n            \"SELECT ('hello)''world')\",\n        )\n        self.validate_identity(\n            \"SELECT $$foo$$\",\n            \"SELECT 'foo'\",\n        )\n        self.validate_identity(\n            \"SELECT $tag$foo$tag$\",\n            \"SELECT 'foo'\",\n        )\n        self.validate_identity(\n            \"JSON_EXTRACT(x, '$.family')\",\n            \"x -> '$.family'\",\n        )\n        self.validate_identity(\n            \"JSON_EXTRACT_PATH(x, '$.family')\",\n            \"x -> '$.family'\",\n        )\n        self.validate_identity(\n            \"JSON_EXTRACT_STRING(x, '$.family')\",\n            \"x ->> '$.family'\",\n        )\n        self.validate_identity(\n            \"JSON_EXTRACT_PATH_TEXT(x, '$.family')\",\n            \"x ->> '$.family'\",\n        )\n        self.validate_all(\n            \"SELECT NOT (data -> '$.value')\",\n            read={\n                \"snowflake\": \"SELECT NOT data:value\",\n            },\n        )\n        self.validate_all(\n            \"SELECT NOT (data -> '$.value.nested')\",\n            read={\n                \"snowflake\": \"SELECT NOT data:value:nested\",\n            },\n        )\n        self.validate_all(\n            \"SELECT (data -> '$.value') = 1\",\n            read={\n                \"snowflake\": \"SELECT data:value = 1\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT {'yes': 'duck', 'maybe': 'goose', 'huh': NULL, 'no': 'heron'}\"\n        )\n        self.validate_identity(\n            \"SELECT a['x space'] FROM (SELECT {'x space': 1, 'y': 2, 'z': 3} AS a)\"\n        )\n        self.validate_identity(\n            \"PIVOT Cities ON Year IN (2000, 2010) USING SUM(Population) GROUP BY Country\"\n        )\n        self.validate_identity(\n            \"PIVOT Cities ON Year USING SUM(Population) AS total, MAX(Population) AS max GROUP BY Country\"\n        )\n        self.validate_identity(\n            \"WITH pivot_alias AS (PIVOT Cities ON Year USING SUM(Population) GROUP BY Country) SELECT * FROM pivot_alias\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM (PIVOT Cities ON Year USING SUM(Population) GROUP BY Country) AS pivot_alias\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM cities PIVOT(SUM(population) FOR year IN (2000, 2010, 2020) GROUP BY country)\"\n        )\n        self.validate_identity(\n            # QUALIFY comes after WINDOW\n            \"SELECT schema_name, function_name, ROW_NUMBER() OVER my_window AS function_rank FROM DUCKDB_FUNCTIONS() WINDOW my_window AS (PARTITION BY schema_name ORDER BY function_name) QUALIFY ROW_NUMBER() OVER my_window < 3\"\n        )\n        self.validate_identity(\"DATE_SUB('YEAR', col, '2020-01-01')\").assert_is(exp.Anonymous)\n        self.validate_identity(\"DATESUB('YEAR', col, '2020-01-01')\").assert_is(exp.Anonymous)\n        self.validate_identity(\"SELECT SHA256('abc')\")\n\n        self.validate_all(\"0b1010\", write={\"\": \"0 AS b1010\"})\n        self.validate_all(\"0x1010\", write={\"\": \"0 AS x1010\"})\n        self.validate_identity(\"x ~ y\", \"REGEXP_FULL_MATCH(x, y)\")\n        self.validate_identity(\"x !~ y\", \"NOT REGEXP_FULL_MATCH(x, y)\")\n        self.validate_identity(\"REGEXP_FULL_MATCH(x, y, 'i')\")\n        self.validate_all(\"SELECT * FROM 'x.y'\", write={\"duckdb\": 'SELECT * FROM \"x.y\"'})\n        self.validate_all(\n            \"SELECT LIST(DISTINCT sample_col) FROM sample_table\",\n            read={\n                \"duckdb\": \"SELECT LIST(DISTINCT sample_col) FROM sample_table\",\n                \"spark\": \"SELECT COLLECT_SET(sample_col) FROM sample_table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LIST_TRANSFORM(STR_SPLIT_REGEX('abc , dfg ', ','), x -> TRIM(x))\",\n            write={\n                \"duckdb\": \"SELECT LIST_TRANSFORM(STR_SPLIT_REGEX('abc , dfg ', ','), x -> TRIM(x))\",\n                \"spark\": \"SELECT TRANSFORM(SPLIT('abc , dfg ', ','), x -> TRIM(x))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LIST_FILTER([4, 5, 6], x -> x > 4)\",\n            write={\n                \"duckdb\": \"SELECT LIST_FILTER([4, 5, 6], x -> x > 4)\",\n                \"spark\": \"SELECT FILTER(ARRAY(4, 5, 6), x -> x > 4)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_COMPACT([1, NULL, 2, NULL, 3])\",\n            write={\n                \"duckdb\": \"LIST_FILTER([1, NULL, 2, NULL, 3], _u -> NOT _u IS NULL)\",\n                \"snowflake\": \"ARRAY_COMPACT([1, NULL, 2, NULL, 3])\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_COMPACT(NULL)\",\n            write={\n                \"duckdb\": \"LIST_FILTER(NULL, _u -> NOT _u IS NULL)\",\n                \"snowflake\": \"ARRAY_COMPACT(NULL)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_COMPACT([])\",\n            write={\n                \"duckdb\": \"LIST_FILTER([], _u -> NOT _u IS NULL)\",\n                \"snowflake\": \"ARRAY_COMPACT([])\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_COMPACT(['a', NULL, 'b', NULL, 'c'])\",\n            write={\n                \"duckdb\": \"LIST_FILTER(['a', NULL, 'b', NULL, 'c'], _u -> NOT _u IS NULL)\",\n                \"snowflake\": \"ARRAY_COMPACT(['a', NULL, 'b', NULL, 'c'])\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_COMPACT([[1, 2], NULL, [3, 4]])\",\n            write={\n                \"duckdb\": \"LIST_FILTER([[1, 2], NULL, [3, 4]], _u -> NOT _u IS NULL)\",\n                \"snowflake\": \"ARRAY_COMPACT([[1, 2], NULL, [3, 4]])\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_CONSTRUCT_COMPACT(1, 2, 3, 4, 5)\",\n            write={\n                \"duckdb\": \"LIST_FILTER([1, 2, 3, 4, 5], _u -> NOT _u IS NULL)\",\n                \"snowflake\": \"ARRAY_CONSTRUCT_COMPACT(1, 2, 3, 4, 5)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_CONSTRUCT_COMPACT()\",\n            write={\n                \"duckdb\": \"LIST_FILTER([], _u -> NOT _u IS NULL)\",\n                \"snowflake\": \"ARRAY_CONSTRUCT_COMPACT()\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_CONSTRUCT_COMPACT('a', NULL, 'b', NULL, 'c')\",\n            write={\n                \"duckdb\": \"LIST_FILTER(['a', NULL, 'b', NULL, 'c'], _u -> NOT _u IS NULL)\",\n                \"snowflake\": \"ARRAY_CONSTRUCT_COMPACT('a', NULL, 'b', NULL, 'c')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ANY_VALUE(sample_column) FROM sample_table\",\n            write={\n                \"duckdb\": \"SELECT ANY_VALUE(sample_column) FROM sample_table\",\n                \"spark\": \"SELECT ANY_VALUE(sample_column) IGNORE NULLS FROM sample_table\",\n            },\n        )\n        self.validate_all(\n            \"COUNT_IF(x)\",\n            write={\n                \"duckdb\": \"COUNT_IF(x)\",\n                \"duckdb, version=1.0\": \"SUM(CASE WHEN x THEN 1 ELSE 0 END)\",\n                \"duckdb, version=1.2\": \"COUNT_IF(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT STRFTIME(CAST('2020-01-01' AS TIMESTAMP), CONCAT('%Y', '%m'))\",\n            write={\n                \"duckdb\": \"SELECT STRFTIME(CAST('2020-01-01' AS TIMESTAMP), CONCAT('%Y', '%m'))\",\n                \"spark\": \"SELECT DATE_FORMAT(CAST('2020-01-01' AS TIMESTAMP_NTZ), CONCAT('yyyy', 'MM'))\",\n                \"tsql\": \"SELECT FORMAT(CAST('2020-01-01' AS DATETIME2), CONCAT('yyyy', 'MM'))\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT CAST('{\"x\": 1}' AS JSON)\"\"\",\n            read={\n                \"duckdb\": \"\"\"SELECT '{\"x\": 1}'::JSON\"\"\",\n                \"postgres\": \"\"\"SELECT '{\"x\": 1}'::JSONB\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM produce PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2'))\",\n            read={\n                \"duckdb\": \"SELECT * FROM produce PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2'))\",\n                \"snowflake\": \"SELECT * FROM produce PIVOT(SUM(produce.sales) FOR produce.quarter IN ('Q1', 'Q2'))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNNEST([1, 2, 3])\",\n            write={\n                \"duckdb\": \"SELECT UNNEST([1, 2, 3])\",\n                \"snowflake\": \"SELECT IFF(_u.pos = _u_2.pos_2, _u_2.col, NULL) AS col FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE([1, 2, 3])) - 1) + 1))) AS _u(seq, key, path, index, pos, this) CROSS JOIN TABLE(FLATTEN(INPUT => [1, 2, 3])) AS _u_2(seq, key, path, pos_2, col, this) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > (ARRAY_SIZE([1, 2, 3]) - 1) AND _u_2.pos_2 = (ARRAY_SIZE([1, 2, 3]) - 1))\",\n            },\n        )\n        self.validate_all(\n            \"VAR_POP(x)\",\n            read={\n                \"\": \"VARIANCE_POP(x)\",\n            },\n            write={\n                \"\": \"VARIANCE_POP(x)\",\n                \"duckdb\": \"VAR_POP(x)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_DIFF('DAY', CAST(b AS DATE), CAST(a AS DATE))\",\n            read={\n                \"duckdb\": \"DATE_DIFF('day', CAST(b AS DATE), CAST(a AS DATE))\",\n                \"hive\": \"DATEDIFF(a, b)\",\n                \"spark\": \"DATEDIFF(a, b)\",\n                \"spark2\": \"DATEDIFF(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"XOR(a, b)\",\n            read={\n                \"\": \"a ^ b\",\n                \"bigquery\": \"a ^ b\",\n                \"presto\": \"BITWISE_XOR(a, b)\",\n                \"postgres\": \"a # b\",\n            },\n            write={\n                \"\": \"a ^ b\",\n                \"bigquery\": \"a ^ b\",\n                \"duckdb\": \"XOR(a, b)\",\n                \"presto\": \"BITWISE_XOR(a, b)\",\n                \"postgres\": \"a # b\",\n            },\n        )\n        self.validate_all(\n            \"PIVOT_WIDER Cities ON Year USING SUM(Population)\",\n            write={\"duckdb\": \"PIVOT Cities ON Year USING SUM(Population)\"},\n        )\n        self.validate_all(\n            \"WITH t AS (SELECT 1) FROM t\", write={\"duckdb\": \"WITH t AS (SELECT 1) SELECT * FROM t\"}\n        )\n        self.validate_all(\n            \"WITH t AS (SELECT 1) SELECT * FROM (FROM t)\",\n            write={\"duckdb\": \"WITH t AS (SELECT 1) SELECT * FROM (SELECT * FROM t)\"},\n        )\n        self.validate_all(\n            \"\"\"SELECT DATEDIFF('day', t1.\"A\", t1.\"B\") FROM \"table\" AS t1\"\"\",\n            write={\n                \"duckdb\": \"\"\"SELECT DATE_DIFF('DAY', t1.\"A\", t1.\"B\") FROM \"table\" AS t1\"\"\",\n                \"trino\": \"\"\"SELECT DATE_DIFF('DAY', t1.\"A\", t1.\"B\") FROM \"table\" AS t1\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_DIFF('day', DATE '2020-01-01', DATE '2020-01-05')\",\n            write={\n                \"duckdb\": \"SELECT DATE_DIFF('DAY', CAST('2020-01-01' AS DATE), CAST('2020-01-05' AS DATE))\",\n                \"trino\": \"SELECT DATE_DIFF('DAY', CAST('2020-01-01' AS DATE), CAST('2020-01-05' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"WITH 'x' AS (SELECT 1) SELECT * FROM x\",\n            write={\"duckdb\": 'WITH \"x\" AS (SELECT 1) SELECT * FROM x'},\n        )\n        self.validate_all(\n            \"CREATE TABLE IF NOT EXISTS t (cola INT, colb STRING) USING ICEBERG PARTITIONED BY (colb)\",\n            write={\n                \"duckdb\": \"CREATE TABLE IF NOT EXISTS t (cola INT, colb TEXT)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE IF NOT EXISTS t (cola INT COMMENT 'cola', colb STRING) USING ICEBERG PARTITIONED BY (colb)\",\n            write={\n                \"duckdb\": \"CREATE TABLE IF NOT EXISTS t (cola INT, colb TEXT)\",\n            },\n        )\n        self.validate_all(\n            \"[0, 1, 2]\",\n            read={\n                \"spark\": \"ARRAY(0, 1, 2)\",\n            },\n            write={\n                \"bigquery\": \"[0, 1, 2]\",\n                \"duckdb\": \"[0, 1, 2]\",\n                \"presto\": \"ARRAY[0, 1, 2]\",\n                \"spark\": \"ARRAY(0, 1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_LENGTH([0], 1) AS x\",\n            write={\"duckdb\": \"SELECT ARRAY_LENGTH([0], 1) AS x\"},\n        )\n        self.validate_identity(\"REGEXP_REPLACE(this, pattern, replacement)\")\n        self.validate_identity(\"REGEXP_REPLACE(this, pattern, replacement, 'g')\")\n        self.validate_identity(\"REGEXP_REPLACE(this, pattern, replacement, 'gi')\")\n        self.validate_identity(\"REGEXP_REPLACE(this, pattern, replacement, 'ims')\")\n        self.validate_identity(\"SELECT SPLIT_PART('11.22.33', '.', 1)\")\n        self.validate_identity(\n            \"SELECT NTH_VALUE(is_deleted, 2) OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\"\n        )\n        self.validate_identity(\n            \"SELECT NTH_VALUE(is_deleted, 2 IGNORE NULLS) OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\"\n        )\n\n        self.validate_all(\n            \"REGEXP_MATCHES(x, y)\",\n            write={\n                \"duckdb\": \"REGEXP_MATCHES(x, y)\",\n                \"presto\": \"REGEXP_LIKE(x, y)\",\n                \"hive\": \"x RLIKE y\",\n                \"spark\": \"x RLIKE y\",\n            },\n        )\n        self.validate_all(\n            \"STR_SPLIT(x, 'a')\",\n            write={\n                \"duckdb\": \"STR_SPLIT(x, 'a')\",\n                \"presto\": \"SPLIT(x, 'a')\",\n                \"hive\": \"SPLIT(x, CONCAT('\\\\\\\\Q', 'a', '\\\\\\\\E'))\",\n                \"spark\": \"SPLIT(x, CONCAT('\\\\\\\\Q', 'a', '\\\\\\\\E'))\",\n            },\n        )\n        self.validate_all(\n            \"STRING_TO_ARRAY(x, 'a')\",\n            read={\n                \"snowflake\": \"STRTOK_TO_ARRAY(x, 'a')\",\n            },\n            write={\n                \"duckdb\": \"STR_SPLIT(x, 'a')\",\n                \"presto\": \"SPLIT(x, 'a')\",\n                \"hive\": \"SPLIT(x, CONCAT('\\\\\\\\Q', 'a', '\\\\\\\\E'))\",\n                \"spark\": \"SPLIT(x, CONCAT('\\\\\\\\Q', 'a', '\\\\\\\\E'))\",\n            },\n        )\n        self.validate_all(\n            \"STR_SPLIT_REGEX(x, 'a')\",\n            write={\n                \"duckdb\": \"STR_SPLIT_REGEX(x, 'a')\",\n                \"presto\": \"REGEXP_SPLIT(x, 'a')\",\n                \"hive\": \"SPLIT(x, 'a')\",\n                \"spark\": \"SPLIT(x, 'a')\",\n            },\n        )\n        self.validate_all(\n            \"STRUCT_EXTRACT(x, 'abc')\",\n            write={\n                \"duckdb\": \"STRUCT_EXTRACT(x, 'abc')\",\n                \"presto\": \"x.abc\",\n                \"hive\": \"x.abc\",\n                \"postgres\": \"x.abc\",\n                \"redshift\": \"x.abc\",\n                \"spark\": \"x.abc\",\n            },\n        )\n        self.validate_all(\n            \"STRUCT_EXTRACT(STRUCT_EXTRACT(x, 'y'), 'abc')\",\n            write={\n                \"duckdb\": \"STRUCT_EXTRACT(STRUCT_EXTRACT(x, 'y'), 'abc')\",\n                \"presto\": \"x.y.abc\",\n                \"hive\": \"x.y.abc\",\n                \"spark\": \"x.y.abc\",\n            },\n        )\n        self.validate_all(\n            \"QUANTILE(x, 0.5)\",\n            write={\n                \"duckdb\": \"QUANTILE(x, 0.5)\",\n                \"presto\": \"APPROX_PERCENTILE(x, 0.5)\",\n                \"hive\": \"PERCENTILE(x, 0.5)\",\n                \"spark\": \"PERCENTILE(x, 0.5)\",\n            },\n        )\n        self.validate_all(\n            \"UNNEST(x)\",\n            read={\n                \"spark\": \"EXPLODE(x)\",\n            },\n            write={\n                \"duckdb\": \"UNNEST(x)\",\n                \"spark\": \"EXPLODE(x)\",\n            },\n        )\n        self.validate_all(\n            \"1d\",\n            write={\n                \"duckdb\": \"1 AS d\",\n                \"spark\": \"1 AS d\",\n            },\n        )\n        self.validate_all(\n            \"POWER(TRY_CAST(2 AS SMALLINT), 3)\",\n            read={\n                \"hive\": \"POW(2S, 3)\",\n                \"spark\": \"POW(2S, 3)\",\n            },\n        )\n        self.validate_all(\n            \"LIST_SUM([1, 2])\",\n            read={\n                \"spark\": \"ARRAY_SUM(ARRAY(1, 2))\",\n            },\n        )\n        self.validate_identity(\"SELECT LIST_MAX(values) FROM table1\")\n        self.validate_identity(\"SELECT LIST_MIN(values) FROM table1\")\n        self.validate_all(\n            \"STRUCT_PACK(x := 1, y := '2')\",\n            write={\n                \"bigquery\": \"STRUCT(1 AS x, '2' AS y)\",\n                \"duckdb\": \"{'x': 1, 'y': '2'}\",\n                \"spark\": \"STRUCT(1 AS x, '2' AS y)\",\n            },\n        )\n        self.validate_all(\n            \"STRUCT_PACK(key1 := 'value1', key2 := 42)\",\n            write={\n                \"bigquery\": \"STRUCT('value1' AS key1, 42 AS key2)\",\n                \"duckdb\": \"{'key1': 'value1', 'key2': 42}\",\n                \"spark\": \"STRUCT('value1' AS key1, 42 AS key2)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_REVERSE_SORT(x)\",\n            write={\n                \"duckdb\": \"ARRAY_REVERSE_SORT(x)\",\n                \"presto\": \"ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)\",\n                \"hive\": \"SORT_ARRAY(x, FALSE)\",\n                \"spark\": \"SORT_ARRAY(x, FALSE)\",\n            },\n        )\n        self.validate_all(\n            \"LIST_REVERSE_SORT(x)\",\n            write={\n                \"duckdb\": \"ARRAY_REVERSE_SORT(x)\",\n                \"presto\": \"ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)\",\n                \"hive\": \"SORT_ARRAY(x, FALSE)\",\n                \"spark\": \"SORT_ARRAY(x, FALSE)\",\n            },\n        )\n        self.validate_all(\n            \"LIST_SORT(x)\",\n            write={\n                \"duckdb\": \"LIST_SORT(x)\",\n                \"presto\": \"ARRAY_SORT(x)\",\n                \"hive\": \"SORT_ARRAY(x)\",\n                \"spark\": \"SORT_ARRAY(x)\",\n            },\n        )\n        self.validate_identity(\"SELECT LIST_SORT(x, 'ASC')\")\n        self.validate_identity(\"SELECT LIST_SORT(x, 'DESC')\")\n        self.validate_identity(\"SELECT LIST_SORT(x, 'ASC', 'NULLS FIRST')\")\n        self.validate_identity(\"SELECT LIST_SORT(x, 'ASC', 'NULLS LAST')\")\n        self.validate_identity(\"SELECT LIST_SORT(x, 'DESC', 'NULLS FIRST')\")\n        self.validate_identity(\"SELECT LIST_SORT(x, 'DESC', 'NULLS LAST')\")\n        self.validate_identity(\"SELECT LIST_SORT(x, 'DE' || 'SC')\")\n        self.validate_identity(\"SELECT LIST_SORT(x, 'DESC', 'NULLS' || ' FIRST')\")\n        self.validate_identity(\"SELECT LIST_SORT(x, 'DE' || 'SC', 'NULLS' || ' FIRST')\")\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname NULLS LAST\",\n                \"duckdb\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname\",\n            },\n        )\n        self.validate_all(\n            \"MONTH('2021-03-01')\",\n            write={\n                \"duckdb\": \"MONTH('2021-03-01')\",\n                \"presto\": \"MONTH('2021-03-01')\",\n                \"hive\": \"MONTH('2021-03-01')\",\n                \"spark\": \"MONTH('2021-03-01')\",\n            },\n        )\n        self.validate_all(\n            \"LIST_CONCAT([1, 2], [3, 4])\",\n            read={\n                \"bigquery\": \"ARRAY_CONCAT([1, 2], [3, 4])\",\n                \"postgres\": \"ARRAY_CAT(ARRAY[1, 2], ARRAY[3, 4])\",\n                \"snowflake\": \"ARRAY_CAT([1, 2], [3, 4])\",\n            },\n            write={\n                \"bigquery\": \"ARRAY_CONCAT([1, 2], [3, 4])\",\n                \"duckdb\": \"LIST_CONCAT([1, 2], [3, 4])\",\n                \"hive\": \"CONCAT(ARRAY(1, 2), ARRAY(3, 4))\",\n                \"postgres\": \"ARRAY_CAT(ARRAY[1, 2], ARRAY[3, 4])\",\n                \"presto\": \"CONCAT(ARRAY[1, 2], ARRAY[3, 4])\",\n                \"snowflake\": \"ARRAY_CAT([1, 2], [3, 4])\",\n                \"spark\": \"CONCAT(ARRAY(1, 2), ARRAY(3, 4))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(TRY_CAST(x AS DATE) AS DATE) + INTERVAL 1 DAY\",\n            read={\n                \"hive\": \"SELECT DATE_ADD(TO_DATE(x), 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('2018-01-01 00:00:00' AS DATE) + INTERVAL 3 DAY\",\n            read={\n                \"hive\": \"SELECT DATE_ADD('2018-01-01 00:00:00', 3)\",\n            },\n            write={\n                \"duckdb\": \"SELECT CAST('2018-01-01 00:00:00' AS DATE) + INTERVAL '3' DAY\",\n                \"hive\": \"SELECT CAST('2018-01-01 00:00:00' AS DATE) + INTERVAL '3' DAY\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('2020-05-06' AS DATE) - INTERVAL '5' DAY\",\n            read={\"bigquery\": \"SELECT DATE_SUB(CAST('2020-05-06' AS DATE), INTERVAL 5 DAY)\"},\n        )\n        self.validate_all(\n            \"SELECT CAST('2020-05-06' AS DATE) + INTERVAL '5' DAY\",\n            read={\"bigquery\": \"SELECT DATE_ADD(CAST('2020-05-06' AS DATE), INTERVAL 5 DAY)\"},\n        )\n        self.validate_identity(\n            \"SELECT PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY y DESC) FROM t\",\n            \"SELECT QUANTILE_CONT(y, 0.25 ORDER BY y DESC) FROM t\",\n        )\n        self.validate_identity(\n            \"SELECT PERCENTILE_DISC(0.25) WITHIN GROUP (ORDER BY y DESC) FROM t\",\n            \"SELECT QUANTILE_DISC(y, 0.25 ORDER BY y DESC) FROM t\",\n        )\n        self.validate_all(\n            \"SELECT QUANTILE_CONT(x, q) FROM t\",\n            write={\n                \"duckdb\": \"SELECT QUANTILE_CONT(x, q) FROM t\",\n                \"postgres\": \"SELECT PERCENTILE_CONT(q) WITHIN GROUP (ORDER BY x) FROM t\",\n                \"snowflake\": \"SELECT PERCENTILE_CONT(q) WITHIN GROUP (ORDER BY x) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT QUANTILE_DISC(x, q) FROM t\",\n            write={\n                \"duckdb\": \"SELECT QUANTILE_DISC(x, q) FROM t\",\n                \"postgres\": \"SELECT PERCENTILE_DISC(q) WITHIN GROUP (ORDER BY x) FROM t\",\n                \"snowflake\": \"SELECT PERCENTILE_DISC(q) WITHIN GROUP (ORDER BY x) FROM t\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT REGEXP_EXTRACT(a, 'pattern') FROM t\",\n            read={\n                \"duckdb\": \"SELECT REGEXP_EXTRACT(a, 'pattern') FROM t\",\n                \"bigquery\": \"SELECT REGEXP_EXTRACT(a, 'pattern') FROM t\",\n                \"snowflake\": \"SELECT REGEXP_SUBSTR(a, 'pattern') FROM t\",\n            },\n            write={\n                \"duckdb\": \"SELECT REGEXP_EXTRACT(a, 'pattern') FROM t\",\n                \"bigquery\": \"SELECT REGEXP_EXTRACT(a, 'pattern') FROM t\",\n                \"snowflake\": \"SELECT REGEXP_SUBSTR(a, 'pattern') FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT REGEXP_EXTRACT(a, 'pattern', 2, 'i') FROM t\",\n            read={\n                \"snowflake\": \"SELECT REGEXP_SUBSTR(a, 'pattern', 1, 1, 'i', 2) FROM t\",\n            },\n            write={\n                \"duckdb\": \"SELECT REGEXP_EXTRACT(a, 'pattern', 2, 'i') FROM t\",\n                \"snowflake\": \"SELECT REGEXP_SUBSTR(a, 'pattern', 1, 1, 'i', 2) FROM t\",\n            },\n        )\n        # group=0 is the default and gets normalized away when no following params\n        self.validate_identity(\n            \"SELECT REGEXP_EXTRACT(a, 'pattern', 0)\",\n            \"SELECT REGEXP_EXTRACT(a, 'pattern')\",\n        )\n        # group is kept when there are following params (flags)\n        self.validate_identity(\"SELECT REGEXP_EXTRACT(a, 'pattern', 0, 'i')\")\n        self.validate_identity(\"SELECT REGEXP_EXTRACT(a, 'pattern', 1, 'i')\")\n\n        # REGEXP_EXTRACT_ALL round-trip tests (same normalization rules as REGEXP_EXTRACT)\n        self.validate_identity(\n            \"SELECT REGEXP_EXTRACT_ALL(s, 'pattern', 0)\",\n            \"SELECT REGEXP_EXTRACT_ALL(s, 'pattern')\",\n        )\n        self.validate_identity(\"SELECT REGEXP_EXTRACT_ALL(s, 'pattern', 1)\")\n        self.validate_identity(\"SELECT REGEXP_EXTRACT_ALL(s, 'pattern', 0, 'i')\")\n        self.validate_identity(\"SELECT REGEXP_EXTRACT_ALL(s, 'pattern', 1, 'im')\")\n        # Array slicing for occurrence\n        self.validate_identity(\n            \"SELECT REGEXP_EXTRACT_ALL(s, 'pattern', 0)[2:]\",\n            \"SELECT REGEXP_EXTRACT_ALL(s, 'pattern')[2:]\",\n        )\n\n        self.validate_identity(\"SELECT ISNAN(x)\")\n\n        self.validate_all(\n            \"SELECT COUNT_IF(x)\",\n            write={\n                \"duckdb\": \"SELECT COUNT_IF(x)\",\n                \"bigquery\": \"SELECT COUNTIF(x)\",\n            },\n        )\n\n        self.validate_identity(\"SELECT * FROM RANGE(1, 5, 10)\")\n        self.validate_identity(\"SELECT * FROM GENERATE_SERIES(2, 13, 4)\")\n\n        self.validate_all(\n            \"WITH t AS (SELECT i, i * i * i * i * i AS i5 FROM RANGE(1, 5) t(i)) SELECT * FROM t\",\n            write={\n                \"duckdb\": \"WITH t AS (SELECT i, i * i * i * i * i AS i5 FROM RANGE(1, 5) AS t(i)) SELECT * FROM t\",\n                \"sqlite\": \"WITH t AS (SELECT i, i * i * i * i * i AS i5 FROM (SELECT value AS i FROM GENERATE_SERIES(1, 5)) AS t) SELECT * FROM t\",\n            },\n        )\n\n        self.validate_identity(\n            \"\"\"SELECT i FROM RANGE(5) AS _(i) ORDER BY i ASC\"\"\",\n            \"\"\"SELECT i FROM RANGE(0, 5) AS _(i) ORDER BY i ASC\"\"\",\n        )\n\n        self.validate_identity(\n            \"\"\"SELECT i FROM GENERATE_SERIES(12) AS _(i) ORDER BY i ASC\"\"\",\n            \"\"\"SELECT i FROM GENERATE_SERIES(0, 12) AS _(i) ORDER BY i ASC\"\"\",\n        )\n\n        self.validate_identity(\n            \"COPY lineitem FROM 'lineitem.ndjson' WITH (FORMAT JSON, DELIMITER ',', AUTO_DETECT TRUE, COMPRESSION SNAPPY, CODEC ZSTD, FORCE_NOT_NULL (col1, col2))\"\n        )\n        self.validate_identity(\n            \"COPY (SELECT 42 AS a, 'hello' AS b) TO 'query.json' WITH (FORMAT JSON, ARRAY TRUE)\"\n        )\n        self.validate_identity(\"COPY lineitem (l_orderkey) TO 'orderkey.tbl' WITH (DELIMITER '|')\")\n\n        self.validate_all(\n            \"VARIANCE(a)\",\n            write={\n                \"duckdb\": \"VARIANCE(a)\",\n                \"clickhouse\": \"varSamp(a)\",\n            },\n        )\n        self.validate_all(\n            \"STDDEV(a)\",\n            write={\n                \"duckdb\": \"STDDEV(a)\",\n                \"clickhouse\": \"stddevSamp(a)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('DAY', x)\",\n            write={\n                \"duckdb\": \"DATE_TRUNC('DAY', x)\",\n                \"clickhouse\": \"dateTrunc('DAY', x)\",\n            },\n        )\n        self.validate_identity(\"EDITDIST3(col1, col2)\", \"LEVENSHTEIN(col1, col2)\")\n        self.validate_identity(\"JARO_WINKLER_SIMILARITY('hello', 'world')\")\n\n        self.validate_identity(\"SELECT LENGTH(foo)\")\n        self.validate_identity(\"SELECT ARRAY[1, 2, 3]\", \"SELECT [1, 2, 3]\")\n\n        self.validate_identity(\"SELECT * FROM (DESCRIBE t)\")\n\n        self.validate_identity(\"SELECT UNNEST([*COLUMNS('alias_.*')]) AS column_name\")\n        self.validate_identity(\n            \"SELECT COALESCE(*COLUMNS(*)) FROM (SELECT NULL, 2, 3) AS t(a, b, c)\"\n        )\n        self.validate_identity(\n            \"SELECT id, STRUCT_PACK(*COLUMNS('m\\\\d')) AS measurements FROM many_measurements\",\n            \"\"\"SELECT id, {'_0': *COLUMNS('m\\\\d')} AS measurements FROM many_measurements\"\"\",\n        )\n        self.validate_identity(\"SELECT COLUMNS(c -> c LIKE '%num%') FROM numbers\")\n        self.validate_identity(\n            \"SELECT MIN(COLUMNS(* REPLACE (number + id AS number))), COUNT(COLUMNS(* EXCLUDE (number))) FROM numbers\"\n        )\n        self.validate_identity(\"SELECT COLUMNS(*) + COLUMNS(*) FROM numbers\")\n        self.validate_identity(\"SELECT COLUMNS('(id|numbers?)') FROM numbers\")\n        self.validate_identity(\n            \"SELECT COALESCE(COLUMNS(['a', 'b', 'c'])) AS result FROM (SELECT NULL AS a, 42 AS b, TRUE AS c)\"\n        )\n        self.validate_identity(\n            \"SELECT COALESCE(*COLUMNS(['a', 'b', 'c'])) AS result FROM (SELECT NULL AS a, 42 AS b, TRUE AS c)\"\n        )\n        self.validate_all(\n            \"SELECT UNNEST(foo) AS x\",\n            write={\n                \"redshift\": UnsupportedError,\n            },\n        )\n        self.validate_identity(\"a ^ b\", \"POWER(a, b)\")\n        self.validate_identity(\"a ** b\", \"POWER(a, b)\")\n        self.validate_identity(\"a ~~~ b\", \"a GLOB b\")\n        self.validate_identity(\"a ~~ b\", \"a LIKE b\")\n        self.validate_identity(\"a @> b\")\n        self.validate_identity(\"a <@ b\", \"b @> a\")\n        self.validate_identity(\"a && b\").assert_is(exp.ArrayOverlaps)\n        self.validate_identity(\"a ^@ b\", \"STARTS_WITH(a, b)\")\n        self.validate_identity(\n            \"a !~~ b\",\n            \"NOT a LIKE b\",\n        )\n        self.validate_identity(\n            \"a !~~* b\",\n            \"NOT a ILIKE b\",\n        )\n\n        self.validate_all(\n            \"SELECT e'Hello\\nworld'\",\n            write={\n                \"duckdb\": \"SELECT e'Hello\\\\nworld'\",\n                \"bigquery\": \"SELECT CAST(b'Hello\\\\nworld' AS STRING)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT REGEXP_MATCHES('ThOmAs', 'thomas', 'i')\",\n            read={\n                \"postgres\": \"SELECT 'ThOmAs' ~* 'thomas'\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT DATE_ADD(CAST('2020-01-01' AS DATE), INTERVAL 1 DAY)\",\n            \"SELECT CAST('2020-01-01' AS DATE) + INTERVAL '1' DAY\",\n        )\n\n        self.validate_identity(\"ARRAY_SLICE(x, 1, 3, 2)\")\n        self.validate_identity(\"SELECT #2, #1 FROM (VALUES (1, 'foo'))\")\n        self.validate_identity(\"SELECT #2 AS a, #1 AS b FROM (VALUES (1, 'foo'))\")\n\n        self.validate_all(\n            \"LIST_CONTAINS([1, 2, NULL], 1)\",\n            write={\n                \"duckdb\": \"ARRAY_CONTAINS([1, 2, NULL], 1)\",\n                \"postgres\": \"CASE WHEN 1 IS NULL THEN NULL ELSE COALESCE(1 = ANY(ARRAY[1, 2, NULL]), FALSE) END\",\n            },\n        )\n        self.validate_all(\n            \"LIST_CONTAINS([1, 2, NULL], NULL)\",\n            write={\n                \"duckdb\": \"ARRAY_CONTAINS([1, 2, NULL], NULL)\",\n                \"postgres\": \"CASE WHEN NULL IS NULL THEN NULL ELSE COALESCE(NULL = ANY(ARRAY[1, 2, NULL]), FALSE) END\",\n            },\n        )\n        self.validate_all(\n            \"LIST_HAS_ANY([1, 2, 3], [1,2])\",\n            write={\n                \"duckdb\": \"[1, 2, 3] && [1, 2]\",\n                \"postgres\": \"ARRAY[1, 2, 3] && ARRAY[1, 2]\",\n            },\n        )\n        self.validate_identity(\"LISTAGG(x, ', ')\")\n        self.validate_identity(\"STRING_AGG(x, ', ')\", \"LISTAGG(x, ', ')\")\n\n        self.validate_all(\n            \"SELECT CONCAT(foo)\",\n            write={\n                \"duckdb\": \"SELECT CONCAT(foo)\",\n                \"spark\": \"SELECT CONCAT(COALESCE(foo, ''))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CONCAT(COALESCE(['abc'], []), ['bcg'])\",\n            write={\n                \"duckdb\": \"SELECT CONCAT(COALESCE(['abc'], []), ['bcg'])\",\n                \"spark\": \"SELECT CONCAT(COALESCE(ARRAY('abc'), ARRAY()), ARRAY('bcg'))\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT CUME_DIST( ORDER BY foo) OVER (ORDER BY 1) FROM (SELECT 1 AS foo)\"\n        )\n        self.validate_identity(\n            \"SELECT NTILE(1 ORDER BY foo) OVER (ORDER BY 1) FROM (SELECT 1 AS foo)\"\n        )\n        self.validate_identity(\n            \"SELECT RANK( ORDER BY foo) OVER (ORDER BY 1) FROM (SELECT 1 AS foo)\"\n        )\n        self.validate_identity(\n            \"SELECT PERCENT_RANK( ORDER BY foo) OVER (ORDER BY 1) FROM (SELECT 1 AS foo)\"\n        )\n        self.validate_identity(\"LIST_COSINE_DISTANCE(x, y)\")\n        self.validate_identity(\"LIST_DISTANCE(x, y)\")\n\n        self.validate_identity(\"SELECT * FROM t LIMIT 10 PERCENT\")\n        self.validate_identity(\"SELECT * FROM t LIMIT 10%\", \"SELECT * FROM t LIMIT 10 PERCENT\")\n\n        self.validate_identity(\"SELECT * FROM t LIMIT 10 PERCENT OFFSET 1\")\n        self.validate_identity(\n            \"SELECT * FROM t LIMIT 10% OFFSET 1\", \"SELECT * FROM t LIMIT 10 PERCENT OFFSET 1\"\n        )\n\n        self.validate_identity(\n            \"SELECT CAST(ROW(1, 2) AS ROW(a INTEGER, b INTEGER))\",\n            \"SELECT CAST(ROW(1, 2) AS STRUCT(a INT, b INT))\",\n        )\n\n        self.validate_identity(\"SELECT row\")\n\n        self.validate_identity(\n            \"SELECT TRY_STRPTIME('2013-04-28T20:57:01.123456789+07:00', '%Y-%m-%dT%H:%M:%S.%n%z')\"\n        )\n\n        self.validate_identity(\n            \"DELETE FROM t USING (VALUES (1)) AS t1(c), (VALUES (1), (2)) AS t2(c) WHERE t.c = t1.c AND t.c = t2.c\"\n        )\n\n        self.validate_identity(\n            \"FROM (FROM t1 UNION FROM t2)\",\n            \"SELECT * FROM (SELECT * FROM t1 UNION SELECT * FROM t2)\",\n        )\n        self.validate_identity(\n            \"FROM (FROM (SELECT 1) AS t2(c), (SELECT t2.c AS c0))\",\n            \"SELECT * FROM (SELECT * FROM (SELECT 1) AS t2(c), (SELECT t2.c AS c0))\",\n        )\n        self.validate_identity(\n            \"FROM (FROM (SELECT 2000 as amount) t GROUP BY amount HAVING SUM(amount) > 1000)\",\n            \"SELECT * FROM (SELECT * FROM (SELECT 2000 AS amount) AS t GROUP BY amount HAVING SUM(amount) > 1000)\",\n        )\n        self.validate_identity(\n            \"(FROM (SELECT 1) t1(c) EXCEPT FROM (SELECT 2) t2(c)) UNION ALL (FROM (SELECT 3) t3(c) EXCEPT FROM (SELECT 4) t4(c))\",\n            \"(SELECT * FROM (SELECT 1) AS t1(c) EXCEPT SELECT * FROM (SELECT 2) AS t2(c)) UNION ALL (SELECT * FROM (SELECT 3) AS t3(c) EXCEPT SELECT * FROM (SELECT 4) AS t4(c))\",\n        )\n\n        for option in (\n            \"ORDER BY 1\",\n            \"LIMIT 1\",\n            \"OFFSET 1\",\n            \"ORDER BY 1 LIMIT 1\",\n            \"ORDER BY 1 OFFSET 1\",\n            \"ORDER BY 1 LIMIT 1 OFFSET 1\",\n            \"LIMIT 1 OFFSET 1\",\n        ):\n            with self.subTest(f\"Testing DuckDB VALUES with modifier option: {option}\"):\n                self.validate_identity(\n                    f\"SELECT 1 FROM (SELECT 1) AS t(c) WHERE ((VALUES (1), (c) {option}) INTERSECT (SELECT 1))\"\n                )\n\n        self.validate_identity(\"FORMAT('foo')\")\n        self.validate_identity(\"FORMAT('foo', 'foo2', 'foo3')\")\n        self.assertEqual(\n            annotate_types(self.parse_one(\"LOWER('HELLO')\")).sql(\"duckdb\"), \"LOWER('HELLO')\"\n        )\n        self.assertEqual(\n            annotate_types(self.parse_one(\"UPPER('hello')\")).sql(\"duckdb\"), \"UPPER('hello')\"\n        )\n        self.validate_all(\n            \"SELECT UUID()\",\n            write={\n                \"duckdb\": \"SELECT UUID()\",\n                \"bigquery\": \"SELECT GENERATE_UUID()\",\n            },\n        )\n        self.assertEqual(\n            annotate_types(\n                self.parse_one(\"SELECT REPLACE('apple pie', 'pie', 'cobbler') AS result\")\n            ).sql(\"duckdb\"),\n            \"SELECT REPLACE('apple pie', 'pie', 'cobbler') AS result\",\n        )\n        self.validate_identity(\"SELECT REPLACE('apple pie', 'pie', 'cobbler') AS result\")\n        self.validate_identity(\n            \"SELECT REPLACE(CAST(CAST('apple pie' AS BLOB) AS TEXT), CAST(CAST('pie' AS BLOB) AS TEXT), CAST(CAST('cobbler' AS BLOB) AS TEXT)) AS result\"\n        )\n        self.assertEqual(\n            annotate_types(self.parse_one(\"SELECT TRIM('***apple***', '*') AS result\")).sql(\n                \"duckdb\"\n            ),\n            \"SELECT TRIM('***apple***', '*') AS result\",\n        )\n        self.validate_identity(\"SELECT TRIM('***apple***', '*') AS result\")\n        self.validate_identity(\n            \"SELECT CAST(TRIM(CAST(CAST('***apple***' AS BLOB) AS TEXT), CAST(CAST('*' AS BLOB) AS TEXT)) AS BLOB) AS result\"\n        )\n        self.validate_identity(\"SELECT GREATEST(1.0, 2.5, NULL, 3.7)\")\n        self.validate_identity(\"FROM t1, t2 SELECT *\", \"SELECT * FROM t1, t2\")\n        self.validate_identity(\"ROUND(2.256, 1)\")\n\n        # TODO: This is incorrect AST, DATE_PART creates a STRUCT of values but it's stored in 'year' arg\n        self.validate_identity(\n            \"SELECT MAKE_DATE(DATE_PART(['year', 'month', 'day'], CURRENT_DATE))\"\n        )\n\n        self.validate_identity(\"SELECT * FROM t PIVOT(SUM(y) FOR foo IN y_enum)\")\n        self.validate_identity(\n            \"SELECT 20_000 AS literal\",\n            \"SELECT 20000 AS literal\",\n        )\n        self.validate_identity(\"SELECT 1_2E+1_0::FLOAT\", \"SELECT CAST(12E+10 AS REAL)\")\n\n        # Test BITMAP_BUCKET_NUMBER transpilation from Snowflake to DuckDB\n        self.validate_all(\n            \"CASE WHEN 2500 > 0 THEN ((2500 - 1) // 32768) + 1 ELSE 2500 // 32768 END\",\n            read={\n                \"snowflake\": \"BITMAP_BUCKET_NUMBER(2500)\",\n            },\n        )\n        self.validate_all(\n            \"CASE WHEN 32768 > 0 THEN ((32768 - 1) // 32768) + 1 ELSE 32768 // 32768 END\",\n            read={\n                \"snowflake\": \"BITMAP_BUCKET_NUMBER(32768)\",\n            },\n        )\n        self.validate_all(\n            \"CASE WHEN 32769 > 0 THEN ((32769 - 1) // 32768) + 1 ELSE 32769 // 32768 END\",\n            read={\n                \"snowflake\": \"BITMAP_BUCKET_NUMBER(32769)\",\n            },\n        )\n        self.validate_all(\n            \"CASE WHEN -100 > 0 THEN ((-100 - 1) // 32768) + 1 ELSE -100 // 32768 END\",\n            read={\n                \"snowflake\": \"BITMAP_BUCKET_NUMBER(-100)\",\n            },\n        )\n        self.validate_all(\n            \"CASE WHEN NULL > 0 THEN ((NULL - 1) // 32768) + 1 ELSE NULL // 32768 END\",\n            read={\n                \"snowflake\": \"BITMAP_BUCKET_NUMBER(NULL)\",\n            },\n        )\n\n        self.validate_all(\n            \"ARRAY_CONTAINS(MAP_KEYS(CAST({'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} AS MAP(TEXT, TEXT))), 'k1')\",\n            read={\n                \"snowflake\": \"MAP_CONTAINS_KEY('k1', {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}::MAP(VARCHAR, VARCHAR))\",\n            },\n        )\n\n        self.validate_identity(\"SELECT [1, 2, 3][1 + 1:LENGTH([1, 2, 3]) + -1]\")\n        self.validate_identity(\"VERSION()\")\n        self.validate_identity(\"SELECT TODAY()\", \"SELECT CURRENT_DATE\")\n        self.validate_identity(\"SELECT GET_CURRENT_TIME()\", \"SELECT CURRENT_TIME\")\n        self.validate_identity(\"CURRENT_LOCALTIMESTAMP()\", \"LOCALTIMESTAMP\").assert_is(\n            exp.Localtimestamp\n        )\n\n        self.validate_identity(\n            \"SELECT SUM(x) OVER (ORDER BY x GROUPS BETWEEN 1 PRECEDING AND CURRENT ROW) FROM t\"\n        )\n\n        self.validate_identity(\"SELECT file[:256] FROM GLOB('*')\").selects[0].this.assert_is(\n            exp.Column\n        )\n        self.validate_identity(\"SELECT file[256] FROM GLOB('*')\").selects[0].this.assert_is(\n            exp.Column\n        )\n\n        self.validate_identity(\n            \"SELECT LAST_VALUE(x ORDER BY x IGNORE NULLS) OVER (ORDER BY x) FROM t\"\n        )\n        self.validate_identity(\n            \"SELECT LAST_VALUE(x ORDER BY x RESPECT NULLS) OVER (ORDER BY x) FROM t\"\n        )\n\n    def test_array_index(self):\n        with self.assertLogs(helper_logger) as cm:\n            self.validate_all(\n                \"SELECT some_arr[1] AS first FROM blah\",\n                read={\n                    \"bigquery\": \"SELECT some_arr[0] AS first FROM blah\",\n                },\n                write={\n                    \"bigquery\": \"SELECT some_arr[0] AS first FROM blah\",\n                    \"duckdb\": \"SELECT some_arr[1] AS first FROM blah\",\n                    \"presto\": \"SELECT some_arr[1] AS first FROM blah\",\n                },\n            )\n            self.validate_identity(\n                \"[x.STRING_SPLIT(' ')[i] FOR x IN ['1', '2', 3] IF x.CONTAINS('1')]\"\n            )\n            self.validate_identity(\"SELECT [4, 5, 6] AS l, [x FOR x, i IN l IF i = 2] AS filtered\")\n            self.validate_identity(\n                \"\"\"SELECT LIST_VALUE(1)[i]\"\"\",\n                \"\"\"SELECT [1][i]\"\"\",\n            )\n            self.validate_identity(\n                \"\"\"{'x': LIST_VALUE(1)[i]}\"\"\",\n                \"\"\"{'x': [1][i]}\"\"\",\n            )\n            self.validate_identity(\n                \"\"\"SELECT LIST_APPLY(RANGE(1, 4), i -> {'f1': LIST_VALUE(1, 2, 3)[i], 'f2': LIST_VALUE(1, 2, 3)[i]})\"\"\",\n                \"\"\"SELECT LIST_APPLY(RANGE(1, 4), i -> {'f1': [1, 2, 3][i], 'f2': [1, 2, 3][i]})\"\"\",\n            )\n\n            self.assertEqual(\n                cm.output,\n                [\n                    \"INFO:sqlglot:Applying array index offset (-1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                ],\n            )\n\n    def test_array_insert(self):\n        # Test ARRAY_INSERT inserts at beginning\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE LIST_CONCAT([99], [1, 2, 3]) END\",\n            read={\n                \"\": \"ARRAY_INSERT([1, 2, 3], 0, 99)\",\n                \"snowflake\": \"ARRAY_INSERT([1, 2, 3], 0, 99)\",\n                \"spark\": \"ARRAY_INSERT(ARRAY(1, 2, 3), 1, 99)\",\n            },\n        )\n\n        # Test ARRAY_INSERT inserts after first element\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE LIST_CONCAT([1, 2, 3][1:1], [99], [1, 2, 3][2:]) END\",\n            read={\n                \"\": \"ARRAY_INSERT([1, 2, 3], 1, 99)\",\n                \"snowflake\": \"ARRAY_INSERT([1, 2, 3], 1, 99)\",\n                \"spark\": \"ARRAY_INSERT(ARRAY(1, 2, 3), 2, 99)\",\n            },\n        )\n\n        # Test ARRAY_INSERT inserts at end\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE LIST_CONCAT([1, 2, 3][1:3], [99], [1, 2, 3][4:]) END\",\n            read={\n                \"\": \"ARRAY_INSERT([1, 2, 3], 3, 99)\",\n                \"snowflake\": \"ARRAY_INSERT([1, 2, 3], 3, 99)\",\n                \"spark\": \"ARRAY_INSERT(ARRAY(1, 2, 3), 4, 99)\",\n            },\n        )\n\n        # Test ARRAY_INSERT inserts before last element using negative position\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE LIST_CONCAT([1, 2, 3][1:LENGTH([1, 2, 3]) + -1], [99], [1, 2, 3][LENGTH([1, 2, 3]) + -1 + 1:]) END\",\n            read={\n                \"\": \"ARRAY_INSERT([1, 2, 3], -1, 99)\",\n                \"snowflake\": \"ARRAY_INSERT([1, 2, 3], -1, 99)\",\n                \"spark\": \"ARRAY_INSERT(ARRAY(1, 2, 3), -2, 99)\",\n            },\n        )\n\n    def test_array_remove(self):\n        # Test NULL propagation with column reference: Snowflake → DuckDB\n        self.validate_all(\n            \"CASE WHEN target IS NULL THEN NULL ELSE LIST_FILTER(the_array, _u -> _u <> target) END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE(the_array, target)\",\n            },\n        )\n\n        # Test literal values: Snowflake → DuckDB\n        self.validate_all(\n            \"LIST_FILTER([1, 2, 3], _u -> _u <> 2)\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE([1, 2, 3], 2)\",\n            },\n        )\n\n        # Test NULL literal: Snowflake → DuckDB\n        self.validate_all(\n            \"CASE WHEN NULL IS NULL THEN NULL ELSE LIST_FILTER([1, 2, 3], _u -> _u <> NULL) END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE([1, 2, 3], NULL)\",\n            },\n        )\n\n    def test_array_remove_at(self):\n        # Test remove first element (position 0)\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE [1, 2, 3][2:] END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE_AT([1, 2, 3], 0)\",\n            },\n        )\n\n        # Test remove middle element (position 1)\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE LIST_CONCAT([1, 2, 3][1:1], [1, 2, 3][3:]) END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE_AT([1, 2, 3], 1)\",\n            },\n        )\n\n        # Test remove last element with positive index (position 2 for 3-element array)\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE LIST_CONCAT([1, 2, 3][1:2], [1, 2, 3][4:]) END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE_AT([1, 2, 3], 2)\",\n            },\n        )\n\n        # Test remove last element with negative index (position -1)\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE [1, 2, 3][1:LENGTH([1, 2, 3]) + -1] END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE_AT([1, 2, 3], -1)\",\n            },\n        )\n\n        # Test remove second-to-last element (position -2)\n        self.validate_all(\n            \"CASE WHEN [1, 2, 3] IS NULL THEN NULL ELSE LIST_CONCAT([1, 2, 3][1:LENGTH([1, 2, 3]) + -2], [1, 2, 3][LENGTH([1, 2, 3]) + -2 + 2:]) END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE_AT([1, 2, 3], -2)\",\n            },\n        )\n\n        # Test single element array\n        self.validate_all(\n            \"CASE WHEN [99] IS NULL THEN NULL ELSE [99][2:] END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE_AT([99], 0)\",\n            },\n        )\n\n        # Test NULL array with column reference\n        self.validate_all(\n            \"CASE WHEN arr IS NULL THEN NULL ELSE arr[2:] END\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE_AT(arr, 0)\",\n            },\n        )\n\n        # Test non-literal position (should remain untranspiled)\n        self.validate_all(\n            \"ARRAY_REMOVE_AT([1, 2, 3], pos)\",\n            read={\n                \"snowflake\": \"ARRAY_REMOVE_AT([1, 2, 3], pos)\",\n            },\n        )\n\n    def test_time(self):\n        self.validate_identity(\"SELECT CURRENT_DATE\")\n        self.validate_identity(\"SELECT CURRENT_TIMESTAMP\")\n\n        self.validate_all(\n            \"SELECT CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE)\",\n            read={\n                \"bigquery\": \"SELECT CURRENT_DATE('UTC')\",\n                \"duckdb\": \"SELECT CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MAKE_DATE(2016, 12, 25)\",\n            read={\n                \"bigquery\": \"SELECT DATE(2016, 12, 25)\",\n            },\n            write={\n                \"bigquery\": \"SELECT DATE(2016, 12, 25)\",\n                \"duckdb\": \"SELECT MAKE_DATE(2016, 12, 25)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(CAST('2016-12-25 23:59:59' AS TIMESTAMP) AS DATE)\",\n            read={\"bigquery\": \"SELECT DATE(DATETIME '2016-12-25 23:59:59')\"},\n        )\n        self.validate_all(\n            \"SELECT CAST(CAST(CAST('2016-12-25' AS TIMESTAMPTZ) AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE 'America/Los_Angeles' AS DATE)\",\n            read={\n                \"bigquery\": \"SELECT DATE(TIMESTAMP '2016-12-25', 'America/Los_Angeles')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(CAST('2024-01-15 23:30:00' AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE 'Europe/Berlin' AS DATE)\",\n            read={\n                \"bigquery\": \"SELECT DATE('2024-01-15 23:30:00', 'Europe/Berlin')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(CAST(STRPTIME('05/06/2020', '%m/%d/%Y') AS DATE) AS DATE)\",\n            read={\"bigquery\": \"SELECT DATE(PARSE_DATE('%m/%d/%Y', '05/06/2020'))\"},\n        )\n        self.validate_all(\n            \"SELECT CAST('2020-01-01' AS DATE) + INTERVAL '-1' DAY\",\n            read={\"mysql\": \"SELECT DATE '2020-01-01' + INTERVAL -1 DAY\"},\n        )\n        self.validate_all(\n            \"SELECT INTERVAL '1 quarter'\",\n            write={\"duckdb\": \"SELECT INTERVAL '1' QUARTER\"},\n        )\n        self.validate_all(\n            \"SELECT ((DATE_TRUNC('DAY', CAST(CAST(DATE_TRUNC('DAY', CURRENT_TIMESTAMP) AS DATE) AS TIMESTAMP) + INTERVAL (0 - ((ISODOW(CAST(CAST(DATE_TRUNC('DAY', CURRENT_TIMESTAMP) AS DATE) AS TIMESTAMP)) % 7) - 1 + 7) % 7) DAY) + INTERVAL (-5) WEEK)) AS t1\",\n            read={\n                \"presto\": \"SELECT ((DATE_ADD('week', -5, DATE_TRUNC('DAY', DATE_ADD('day', (0 - MOD((DAY_OF_WEEK(CAST(CAST(DATE_TRUNC('DAY', NOW()) AS DATE) AS TIMESTAMP)) % 7) - 1 + 7, 7)), CAST(CAST(DATE_TRUNC('DAY', NOW()) AS DATE) AS TIMESTAMP)))))) AS t1\",\n            },\n        )\n        self.validate_all(\n            \"EPOCH(x)\",\n            read={\n                \"presto\": \"TO_UNIXTIME(x)\",\n            },\n            write={\n                \"bigquery\": \"TIME_TO_UNIX(x)\",\n                \"duckdb\": \"EPOCH(x)\",\n                \"presto\": \"TO_UNIXTIME(x)\",\n                \"spark\": \"UNIX_TIMESTAMP(x)\",\n            },\n        )\n        self.validate_all(\n            \"EPOCH_MS(x)\",\n            write={\n                \"bigquery\": \"TIMESTAMP_MILLIS(x)\",\n                \"clickhouse\": \"fromUnixTimestamp64Milli(CAST(x AS Nullable(Int64)))\",\n                \"duckdb\": \"EPOCH_MS(x)\",\n                \"mysql\": \"FROM_UNIXTIME(x / POWER(10, 3))\",\n                \"postgres\": \"TO_TIMESTAMP(CAST(x AS DOUBLE PRECISION) / POWER(10, 3))\",\n                \"presto\": \"FROM_UNIXTIME(CAST(x AS DOUBLE) / POW(10, 3))\",\n                \"spark\": \"TIMESTAMP_MILLIS(x)\",\n            },\n        )\n        self.validate_all(\n            \"STRFTIME(x, '%y-%-m-%S')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%y-%-m-%S', x)\",\n                \"duckdb\": \"STRFTIME(x, '%y-%-m-%S')\",\n                \"postgres\": \"TO_CHAR(x, 'YY-FMMM-SS')\",\n                \"presto\": \"DATE_FORMAT(x, '%y-%c-%s')\",\n                \"spark\": \"DATE_FORMAT(x, 'yy-M-ss')\",\n            },\n        )\n\n        self.validate_all(\n            \"SHA1(x)\",\n            write={\n                \"duckdb\": \"SHA1(x)\",\n                \"\": \"SHA(x)\",\n            },\n        )\n\n        self.validate_all(\n            \"STRFTIME(x, '%Y-%m-%d %H:%M:%S')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%F %T', x)\",\n                \"duckdb\": \"STRFTIME(x, '%Y-%m-%d %H:%M:%S')\",\n                \"presto\": \"DATE_FORMAT(x, '%Y-%m-%d %T')\",\n                \"hive\": \"DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss')\",\n            },\n        )\n        self.validate_all(\n            \"STRPTIME(x, '%y-%-m')\",\n            write={\n                \"bigquery\": \"PARSE_TIMESTAMP('%y-%-m', x)\",\n                \"duckdb\": \"STRPTIME(x, '%y-%-m')\",\n                \"presto\": \"DATE_PARSE(x, '%y-%c')\",\n                \"hive\": \"CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, 'yy-M')) AS TIMESTAMP)\",\n                \"spark\": \"TO_TIMESTAMP(x, 'yy-M')\",\n            },\n        )\n        self.validate_all(\n            \"TO_TIMESTAMP(x)\",\n            write={\n                \"bigquery\": \"TIMESTAMP_SECONDS(x)\",\n                \"duckdb\": \"TO_TIMESTAMP(x)\",\n                \"presto\": \"FROM_UNIXTIME(x)\",\n                \"hive\": \"FROM_UNIXTIME(x)\",\n            },\n        )\n        self.validate_all(\n            \"STRPTIME(x, '%-m/%-d/%y %-I:%M %p')\",\n            write={\n                \"bigquery\": \"PARSE_TIMESTAMP('%-m/%e/%y %-I:%M %p', x)\",\n                \"duckdb\": \"STRPTIME(x, '%-m/%-d/%y %-I:%M %p')\",\n                \"presto\": \"DATE_PARSE(x, '%c/%e/%y %l:%i %p')\",\n                \"hive\": \"CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, 'M/d/yy h:mm a')) AS TIMESTAMP)\",\n                \"spark\": \"TO_TIMESTAMP(x, 'M/d/yy h:mm a')\",\n            },\n        )\n        self.validate_all(\n            \"CAST(start AS TIMESTAMPTZ) AT TIME ZONE 'America/New_York'\",\n            read={\n                \"snowflake\": \"CONVERT_TIMEZONE('America/New_York', CAST(start AS TIMESTAMPTZ))\",\n            },\n            write={\n                \"bigquery\": \"TIMESTAMP(DATETIME(CAST(start AS TIMESTAMP), 'America/New_York'))\",\n                \"duckdb\": \"CAST(start AS TIMESTAMPTZ) AT TIME ZONE 'America/New_York'\",\n                \"snowflake\": \"CONVERT_TIMEZONE('America/New_York', CAST(start AS TIMESTAMPTZ))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT TIMESTAMP 'foo'\",\n            write={\n                \"duckdb\": \"SELECT CAST('foo' AS TIMESTAMP)\",\n                \"hive\": \"SELECT CAST('foo' AS TIMESTAMP)\",\n                \"spark2\": \"SELECT CAST('foo' AS TIMESTAMP)\",\n                \"spark\": \"SELECT CAST('foo' AS TIMESTAMP_NTZ)\",\n                \"postgres\": \"SELECT CAST('foo' AS TIMESTAMP)\",\n                \"mysql\": \"SELECT CAST('foo' AS DATETIME)\",\n                \"clickhouse\": \"SELECT CAST('foo' AS Nullable(DateTime))\",\n                \"databricks\": \"SELECT CAST('foo' AS TIMESTAMP_NTZ)\",\n                \"snowflake\": \"SELECT CAST('foo' AS TIMESTAMPNTZ)\",\n                \"redshift\": \"SELECT CAST('foo' AS TIMESTAMP)\",\n                \"tsql\": \"SELECT CAST('foo' AS DATETIME2)\",\n                \"presto\": \"SELECT CAST('foo' AS TIMESTAMP)\",\n                \"trino\": \"SELECT CAST('foo' AS TIMESTAMP)\",\n                \"oracle\": \"SELECT CAST('foo' AS TIMESTAMP)\",\n                \"bigquery\": \"SELECT CAST('foo' AS DATETIME)\",\n                \"starrocks\": \"SELECT CAST('foo' AS DATETIME)\",\n            },\n        )\n\n    def test_sample(self):\n        self.validate_identity(\n            \"SELECT * FROM tbl USING SAMPLE 5\",\n            \"SELECT * FROM tbl USING SAMPLE RESERVOIR (5 ROWS)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM tbl USING SAMPLE 10%\",\n            \"SELECT * FROM tbl USING SAMPLE SYSTEM (10 PERCENT)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM tbl USING SAMPLE 10 PERCENT (bernoulli)\",\n            \"SELECT * FROM tbl USING SAMPLE BERNOULLI (10 PERCENT)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM tbl USING SAMPLE reservoir(50 ROWS) REPEATABLE (100)\",\n            \"SELECT * FROM tbl USING SAMPLE RESERVOIR (50 ROWS) REPEATABLE (100)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM tbl USING SAMPLE 10% (system, 377)\",\n            \"SELECT * FROM tbl USING SAMPLE SYSTEM (10 PERCENT) REPEATABLE (377)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM tbl TABLESAMPLE RESERVOIR(20%), tbl2 WHERE tbl.i=tbl2.i\",\n            \"SELECT * FROM tbl TABLESAMPLE RESERVOIR (20 PERCENT), tbl2 WHERE tbl.i = tbl2.i\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM tbl, tbl2 WHERE tbl.i=tbl2.i USING SAMPLE RESERVOIR(20%)\",\n            \"SELECT * FROM tbl, tbl2 WHERE tbl.i = tbl2.i USING SAMPLE RESERVOIR (20 PERCENT)\",\n        )\n\n        self.validate_all(\n            \"SELECT * FROM example TABLESAMPLE RESERVOIR (3 ROWS) REPEATABLE (82)\",\n            read={\n                \"duckdb\": \"SELECT * FROM example TABLESAMPLE (3) REPEATABLE (82)\",\n                \"snowflake\": \"SELECT * FROM example SAMPLE (3 ROWS) SEED (82)\",\n            },\n            write={\n                \"duckdb\": \"SELECT * FROM example TABLESAMPLE RESERVOIR (3 ROWS) REPEATABLE (82)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (SELECT * FROM t) AS t1 TABLESAMPLE (1 ROWS), (SELECT * FROM t) AS t2 TABLESAMPLE (2 ROWS)\",\n            write={\n                \"duckdb\": \"SELECT * FROM (SELECT * FROM t) AS t1 TABLESAMPLE RESERVOIR (1 ROWS), (SELECT * FROM t) AS t2 TABLESAMPLE RESERVOIR (2 ROWS)\",\n                \"spark\": \"SELECT * FROM (SELECT * FROM t) TABLESAMPLE (1 ROWS) AS t1, (SELECT * FROM t) TABLESAMPLE (2 ROWS) AS t2\",\n            },\n        )\n\n    def test_array(self):\n        self.validate_identity(\"ARRAY(SELECT id FROM t)\")\n        self.validate_identity(\"ARRAY((SELECT id FROM t))\")\n\n    def test_cast(self):\n        self.validate_identity(\"x::int[3]\", \"CAST(x AS INT[3])\")\n        self.validate_identity(\"CAST(x AS REAL)\")\n        self.validate_identity(\"CAST(x AS UINTEGER)\")\n        self.validate_identity(\"CAST(x AS UBIGINT)\")\n        self.validate_identity(\"CAST(x AS USMALLINT)\")\n        self.validate_identity(\"CAST(x AS UTINYINT)\")\n        self.validate_identity(\"CAST(x AS TEXT)\")\n        self.validate_identity(\"CAST(x AS INT128)\")\n        self.validate_identity(\"CAST(x AS DOUBLE)\")\n        self.validate_identity(\"CAST(x AS DECIMAL(15, 4))\")\n        self.validate_identity(\"CAST(x AS STRUCT(number BIGINT))\")\n        self.validate_identity(\"CAST(x AS INT64)\", \"CAST(x AS BIGINT)\")\n        self.validate_identity(\"CAST(x AS INT32)\", \"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS INT16)\", \"CAST(x AS SMALLINT)\")\n        self.validate_identity(\"CAST(x AS INT8)\", \"CAST(x AS BIGINT)\")\n        self.validate_identity(\"CAST(x AS NUMERIC(1, 2))\", \"CAST(x AS DECIMAL(1, 2))\")\n        self.validate_identity(\"CAST(x AS HUGEINT)\", \"CAST(x AS INT128)\")\n        self.validate_identity(\"CAST(x AS UHUGEINT)\", \"CAST(x AS UINT128)\")\n        self.validate_identity(\"CAST(x AS CHAR)\", \"CAST(x AS TEXT)\")\n        self.validate_identity(\"CAST(x AS BPCHAR)\", \"CAST(x AS TEXT)\")\n        self.validate_identity(\"CAST(x AS STRING)\", \"CAST(x AS TEXT)\")\n        self.validate_identity(\"CAST(x AS VARCHAR)\", \"CAST(x AS TEXT)\")\n        self.validate_identity(\"CAST(x AS INT1)\", \"CAST(x AS TINYINT)\")\n        self.validate_identity(\"CAST(x AS FLOAT4)\", \"CAST(x AS REAL)\")\n        self.validate_identity(\"CAST(x AS FLOAT)\", \"CAST(x AS REAL)\")\n        self.validate_identity(\"CAST(x AS INT4)\", \"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS INTEGER)\", \"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS SIGNED)\", \"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS BLOB)\", \"CAST(x AS BLOB)\")\n        self.validate_identity(\"CAST(x AS BYTEA)\", \"CAST(x AS BLOB)\")\n        self.validate_identity(\"CAST(x AS BINARY)\", \"CAST(x AS BLOB)\")\n        self.validate_identity(\"CAST(x AS VARBINARY)\", \"CAST(x AS BLOB)\")\n        self.validate_identity(\"CAST(x AS LOGICAL)\", \"CAST(x AS BOOLEAN)\")\n        self.validate_identity(\"\"\"CAST({'i': 1, 's': 'foo'} AS STRUCT(\"s\" TEXT, \"i\" INT))\"\"\")\n        self.validate_identity(\n            \"CAST(ROW(1, ROW(1)) AS STRUCT(number BIGINT, row STRUCT(number BIGINT)))\"\n        )\n        self.validate_identity(\n            \"123::CHARACTER VARYING\",\n            \"CAST(123 AS TEXT)\",\n        )\n        self.validate_identity(\n            \"CAST([[STRUCT_PACK(a := 1)]] AS STRUCT(a BIGINT)[][])\",\n            \"CAST([[{'a': 1}]] AS STRUCT(a BIGINT)[][])\",\n        )\n        self.validate_identity(\n            \"CAST([STRUCT_PACK(a := 1)] AS STRUCT(a BIGINT)[])\",\n            \"CAST([{'a': 1}] AS STRUCT(a BIGINT)[])\",\n        )\n        self.validate_identity(\n            \"STRUCT_PACK(a := 'b')::json\",\n            \"CAST({'a': 'b'} AS JSON)\",\n        )\n        self.validate_identity(\n            \"STRUCT_PACK(a := 'b')::STRUCT(a TEXT)\",\n            \"CAST({'a': 'b'} AS STRUCT(a TEXT))\",\n        )\n\n        self.validate_all(\n            \"CAST(x AS TIME)\",\n            read={\n                \"duckdb\": \"CAST(x AS TIME)\",\n                \"presto\": \"CAST(x AS TIME(6))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('2020-01-01 12:05:01' AS TIMESTAMP)\",\n            read={\n                \"duckdb\": \"SELECT CAST('2020-01-01 12:05:01' AS TIMESTAMP)\",\n                \"snowflake\": \"SELECT CAST('2020-01-01 12:05:01' AS TIMESTAMPNTZ)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('2020-01-01' AS DATE) + INTERVAL (day_offset) DAY FROM t\",\n            read={\n                \"duckdb\": \"SELECT CAST('2020-01-01' AS DATE) + INTERVAL (day_offset) DAY FROM t\",\n                \"mysql\": \"SELECT DATE '2020-01-01' + INTERVAL day_offset DAY FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('09:05:03' AS TIME) + INTERVAL 2 HOUR\",\n            read={\n                \"snowflake\": \"SELECT TIMEADD(HOUR, 2, TO_TIME('09:05:03'))\",\n            },\n            write={\n                \"duckdb\": \"SELECT CAST('09:05:03' AS TIME) + INTERVAL '2' HOUR\",\n                \"snowflake\": \"SELECT CAST('09:05:03' AS TIME) + INTERVAL '2 HOUR'\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS VARCHAR(5))\",\n            write={\n                \"duckdb\": \"CAST(x AS TEXT)\",\n                \"postgres\": \"CAST(x AS TEXT)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS DECIMAL(38, 0))\",\n            read={\n                \"snowflake\": \"CAST(x AS NUMBER)\",\n                \"duckdb\": \"CAST(x AS DECIMAL(38, 0))\",\n            },\n            write={\n                \"snowflake\": \"CAST(x AS DECIMAL(38, 0))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS NUMERIC)\",\n            write={\n                \"duckdb\": \"CAST(x AS DECIMAL(18, 3))\",\n                \"postgres\": \"CAST(x AS DECIMAL(18, 3))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS DECIMAL)\",\n            write={\n                \"duckdb\": \"CAST(x AS DECIMAL(18, 3))\",\n                \"postgres\": \"CAST(x AS DECIMAL(18, 3))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS BIT)\",\n            read={\n                \"duckdb\": \"CAST(x AS BITSTRING)\",\n            },\n            write={\n                \"duckdb\": \"CAST(x AS BIT)\",\n                \"tsql\": \"CAST(x AS BIT)\",\n            },\n        )\n        self.validate_all(\n            \"cast([[1]] as int[][])\",\n            write={\n                \"duckdb\": \"CAST([[1]] AS INT[][])\",\n                \"spark\": \"CAST(ARRAY(ARRAY(1)) AS ARRAY<ARRAY<INT>>)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS DATE) + INTERVAL (7 * -1) DAY\",\n            read={\n                \"spark\": \"DATE_SUB(x, 7)\",\n            },\n        )\n        self.validate_all(\n            \"TRY_CAST(1 AS DOUBLE)\",\n            read={\n                \"hive\": \"1d\",\n                \"spark\": \"1d\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS DATE)\",\n            write={\n                \"duckdb\": \"CAST(x AS DATE)\",\n                \"\": \"CAST(x AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"COL::BIGINT[]\",\n            write={\n                \"duckdb\": \"CAST(COL AS BIGINT[])\",\n                \"presto\": \"CAST(COL AS ARRAY(BIGINT))\",\n                \"hive\": \"CAST(COL AS ARRAY<BIGINT>)\",\n                \"spark\": \"CAST(COL AS ARRAY<BIGINT>)\",\n                \"postgres\": \"CAST(COL AS BIGINT[])\",\n                \"snowflake\": \"CAST(COL AS ARRAY(BIGINT))\",\n            },\n        )\n\n        self.validate_identity(\"SELECT x::INT[3][3]\", \"SELECT CAST(x AS INT[3][3])\")\n        self.validate_identity(\n            \"\"\"SELECT ARRAY[[[1]]]::INT[1][1][1]\"\"\",\n            \"\"\"SELECT CAST([[[1]]] AS INT[1][1][1])\"\"\",\n        )\n\n    def test_encode_decode(self):\n        self.validate_all(\n            \"ENCODE(x)\",\n            read={\n                \"spark\": \"ENCODE(x, 'utf-8')\",\n                \"presto\": \"TO_UTF8(x)\",\n            },\n            write={\n                \"duckdb\": \"ENCODE(x)\",\n                \"spark\": \"ENCODE(x, 'utf-8')\",\n                \"presto\": \"TO_UTF8(x)\",\n            },\n        )\n        self.validate_all(\n            \"DECODE(x)\",\n            read={\n                \"spark\": \"DECODE(x, 'utf-8')\",\n                \"presto\": \"FROM_UTF8(x)\",\n            },\n            write={\n                \"duckdb\": \"DECODE(x)\",\n                \"spark\": \"DECODE(x, 'utf-8')\",\n                \"presto\": \"FROM_UTF8(x)\",\n            },\n        )\n        self.validate_all(\n            \"DECODE(x)\",\n            read={\n                \"presto\": \"FROM_UTF8(x, y)\",\n            },\n        )\n\n    def test_sha(self):\n        # Round-trip: DuckDB SHA1 should not add unnecessary casts\n        self.validate_identity(\"SHA1('foo')\")\n        self.validate_identity(\"SHA1(x)\")\n        self.validate_identity(\"SHA256('foo')\")\n        self.validate_identity(\"SHA256(x)\")\n\n    def test_rename_table(self):\n        self.validate_all(\n            \"ALTER TABLE db.t1 RENAME TO db.t2\",\n            write={\n                \"snowflake\": \"ALTER TABLE db.t1 RENAME TO db.t2\",\n                \"duckdb\": \"ALTER TABLE db.t1 RENAME TO t2\",\n                \"tsql\": \"EXEC sp_rename 'db.t1', 't2'\",\n            },\n        )\n        self.validate_all(\n            'ALTER TABLE \"db\".\"t1\" RENAME TO \"db\".\"t2\"',\n            write={\n                \"snowflake\": 'ALTER TABLE \"db\".\"t1\" RENAME TO \"db\".\"t2\"',\n                \"duckdb\": 'ALTER TABLE \"db\".\"t1\" RENAME TO \"t2\"',\n                \"tsql\": \"EXEC sp_rename '[db].[t1]', 't2'\",\n            },\n        )\n\n    def test_timestamps_with_units(self):\n        self.validate_all(\n            \"SELECT w::TIMESTAMP_S, x::TIMESTAMP_MS, y::TIMESTAMP_US, z::TIMESTAMP_NS\",\n            write={\n                \"duckdb\": \"SELECT CAST(w AS TIMESTAMP_S), CAST(x AS TIMESTAMP_MS), CAST(y AS TIMESTAMP), CAST(z AS TIMESTAMP_NS)\",\n            },\n        )\n\n    def test_isnan(self):\n        self.validate_all(\n            \"ISNAN(x)\",\n            read={\"bigquery\": \"IS_NAN(x)\"},\n            write={\"bigquery\": \"IS_NAN(x)\", \"duckdb\": \"ISNAN(x)\"},\n        )\n\n    def test_isinf(self):\n        self.validate_all(\n            \"ISINF(x)\",\n            read={\"bigquery\": \"IS_INF(x)\"},\n            write={\"bigquery\": \"IS_INF(x)\", \"duckdb\": \"ISINF(x)\"},\n        )\n\n    def test_parameter_token(self):\n        self.validate_all(\n            \"SELECT $foo\",\n            read={\"bigquery\": \"SELECT @foo\"},\n            write={\"bigquery\": \"SELECT @foo\", \"duckdb\": \"SELECT $foo\"},\n        )\n\n    def test_ignore_nulls(self):\n        # Note that DuckDB differentiates window functions (e.g. LEAD, LAG) from aggregate functions (e.g. SUM)\n        from sqlglot.dialects.duckdb import DuckDB\n\n        agg_funcs = (exp.Sum, exp.Max, exp.Min)\n\n        for func_type in DuckDB.Generator.IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS + agg_funcs:\n            func = func_type(this=exp.to_identifier(\"col\"))\n            ignore_null = exp.IgnoreNulls(this=func)\n            windowed_ignore_null = exp.Window(this=ignore_null)\n\n            if func_type in DuckDB.Generator.IGNORE_RESPECT_NULLS_WINDOW_FUNCTIONS:\n                self.assertIn(\"IGNORE NULLS\", windowed_ignore_null.sql(\"duckdb\"))\n            else:\n                with self.assertLogs(generator_logger) as cm:\n                    self.assertEqual(ignore_null.sql(\"duckdb\"), func.sql(\"duckdb\"))\n                    self.assertNotIn(\"IGNORE NULLS\", windowed_ignore_null.sql(\"duckdb\"))\n\n                self.assertEqual(\n                    str(cm.output[0]),\n                    \"WARNING:sqlglot:IGNORE NULLS is not supported for non-window functions.\",\n                )\n\n    def test_attach_detach(self):\n        # ATTACH\n        self.validate_identity(\"ATTACH 'file.db'\")\n        self.validate_identity(\"ATTACH ':memory:' AS db_alias\")\n        self.validate_identity(\"ATTACH IF NOT EXISTS 'file.db' AS db_alias\")\n        self.validate_identity(\"ATTACH 'file.db' AS db_alias (READ_ONLY)\")\n        self.validate_identity(\"ATTACH 'file.db' (READ_ONLY FALSE, TYPE sqlite)\")\n        self.validate_identity(\"ATTACH 'file.db' (TYPE POSTGRES, SCHEMA 'public')\")\n\n        self.validate_identity(\"ATTACH DATABASE 'file.db'\", \"ATTACH 'file.db'\")\n\n        # DETACH\n        self.validate_identity(\"DETACH new_database\")\n\n        # when 'if exists' is set, the syntax is DETACH DATABASE, not DETACH\n        # ref: https://duckdb.org/docs/stable/sql/statements/attach.html#detach-syntax\n        self.validate_identity(\"DETACH IF EXISTS file\", \"DETACH DATABASE IF EXISTS file\")\n        self.validate_identity(\"DETACH DATABASE IF EXISTS file\", \"DETACH DATABASE IF EXISTS file\")\n\n        self.validate_identity(\"DETACH DATABASE db\", \"DETACH db\")\n\n    def test_simplified_pivot_unpivot(self):\n        self.validate_identity(\"PIVOT Cities ON Year USING SUM(Population)\")\n        self.validate_identity(\"PIVOT Cities ON Year USING FIRST(Population)\")\n        self.validate_identity(\"PIVOT Cities ON Year USING SUM(Population) GROUP BY Country\")\n        self.validate_identity(\"PIVOT Cities ON Country, Name USING SUM(Population)\")\n        self.validate_identity(\"PIVOT Cities ON Country || '_' || Name USING SUM(Population)\")\n        self.validate_identity(\"PIVOT Cities ON Year USING SUM(Population) GROUP BY Country, Name\")\n\n        self.validate_identity(\"UNPIVOT (SELECT 1 AS col1, 2 AS col2) ON foo, bar\")\n        self.validate_identity(\n            \"UNPIVOT monthly_sales ON jan, feb, mar, apr, may, jun INTO NAME month VALUE sales\"\n        )\n        self.validate_identity(\n            \"UNPIVOT monthly_sales ON COLUMNS(* EXCLUDE (empid, dept)) INTO NAME month VALUE sales\"\n        )\n        self.validate_identity(\n            \"UNPIVOT monthly_sales ON (jan, feb, mar) AS q1, (apr, may, jun) AS q2 INTO NAME quarter VALUE month_1_sales, month_2_sales, month_3_sales\"\n        )\n        self.validate_identity(\n            \"WITH unpivot_alias AS (UNPIVOT monthly_sales ON COLUMNS(* EXCLUDE (empid, dept)) INTO NAME month VALUE sales) SELECT * FROM unpivot_alias\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM (UNPIVOT monthly_sales ON COLUMNS(* EXCLUDE (empid, dept)) INTO NAME month VALUE sales) AS unpivot_alias\"\n        )\n        self.validate_identity(\n            \"WITH cities(country, name, year, population) AS (SELECT 'NL', 'Amsterdam', 2000, 1005 UNION ALL SELECT 'US', 'Seattle', 2020, 738) PIVOT cities ON year USING SUM(population)\"\n        )\n\n    def test_from_first_with_parentheses(self):\n        self.validate_identity(\n            \"CREATE TABLE t1 AS (FROM t2 SELECT foo1, foo2)\",\n            \"CREATE TABLE t1 AS (SELECT foo1, foo2 FROM t2)\",\n        )\n        self.validate_identity(\n            \"FROM (FROM t1 SELECT foo1, foo2)\",\n            \"SELECT * FROM (SELECT foo1, foo2 FROM t1)\",\n        )\n        self.validate_identity(\n            \"WITH t1 AS (FROM (FROM t2 SELECT foo1, foo2)) FROM t1\",\n            \"WITH t1 AS (SELECT * FROM (SELECT foo1, foo2 FROM t2)) SELECT * FROM t1\",\n        )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE\")\n\n    def test_prefix_aliases(self):\n        # https://duckdb.org/2025/02/25/prefix-aliases-in-sql.html\n        self.validate_identity(\n            \"SELECT foo: 1\",\n            \"SELECT 1 AS foo\",\n        )\n        self.validate_identity(\n            \"SELECT foo: bar\",\n            \"SELECT bar AS foo\",\n        )\n        self.validate_identity(\n            \"SELECT foo: t.col FROM t\",\n            \"SELECT t.col AS foo FROM t\",\n        )\n        self.validate_identity(\n            'SELECT \"foo\" /* bla */: 1',\n            'SELECT 1 AS \"foo\" /* bla */',\n        )\n        self.validate_identity(\n            'SELECT \"foo\": 1 /* bla */',\n            'SELECT 1 AS \"foo\" /* bla */',\n        )\n        self.validate_identity(\n            'SELECT \"foo\": /* bla */ 1',\n            'SELECT 1 AS \"foo\" /* bla */',\n        )\n        self.validate_identity(\n            'SELECT \"foo\": /* bla */ 1 /* foo */',\n            'SELECT 1 AS \"foo\" /* bla */ /* foo */',\n        )\n        self.validate_identity(\n            'SELECT \"foo\": 1',\n            'SELECT 1 AS \"foo\"',\n        )\n        self.validate_identity(\n            \"SELECT foo: 1, bar: 2, baz: 3\",\n            \"SELECT 1 AS foo, 2 AS bar, 3 AS baz\",\n        )\n        self.validate_identity(\n            \"SELECT e: 1 + 2, f: len('asdf'), s: (SELECT 42)\",\n            \"SELECT 1 + 2 AS e, LENGTH('asdf') AS f, (SELECT 42) AS s\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM foo: bar\",\n            \"SELECT * FROM bar AS foo\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM foo: c.db.tbl\",\n            \"SELECT * FROM c.db.tbl AS foo\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM foo /* bla */: bar\",\n            \"SELECT * FROM bar AS foo /* bla */\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM foo /* bla */: bar /* baz */\",\n            \"SELECT * FROM bar AS foo /* bla */ /* baz */\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM foo /* bla */: /* baz */ bar /* boo */\",\n            \"SELECT * FROM bar AS foo /* bla */ /* baz */ /* boo */\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM r: range(10), v: (VALUES (42)), s: (FROM range(10))\",\n            \"SELECT * FROM RANGE(0, 10) AS r, (VALUES (42)) AS v, (SELECT * FROM RANGE(0, 10)) AS s\",\n        )\n        self.validate_identity(\n            \"\"\"\n            SELECT\n                l_returnflag,\n                l_linestatus,\n                sum_qty:        sum(l_quantity),\n                sum_base_price: sum(l_extendedprice),\n                sum_disc_price: sum(l_extendedprice * (1-l_discount)),\n                sum_charge:     sum(l_extendedprice * (1-l_discount) * (1+l_tax)),\n                avg_qty:        avg(l_quantity),\n                avg_price:      avg(l_extendedprice),\n                avg_disc:       avg(l_discount),\n                count_order:    count(*)\n            \"\"\",\n            \"SELECT l_returnflag, l_linestatus, SUM(l_quantity) AS sum_qty, SUM(l_extendedprice) AS sum_base_price, SUM(l_extendedprice * (1 - l_discount)) AS sum_disc_price, SUM(l_extendedprice * (1 - l_discount) * (1 + l_tax)) AS sum_charge, AVG(l_quantity) AS avg_qty, AVG(l_extendedprice) AS avg_price, AVG(l_discount) AS avg_disc, COUNT(*) AS count_order\",\n        )\n\n    def test_at_sign_to_abs(self):\n        self.validate_identity(\n            \"SELECT @col FROM t\",\n            \"SELECT ABS(col) FROM t\",\n        )\n        self.validate_identity(\n            \"SELECT @col + 1 FROM t\",\n            \"SELECT ABS(col + 1) FROM t\",\n        )\n        self.validate_identity(\n            \"SELECT (@col) + 1 FROM t\",\n            \"SELECT (ABS(col)) + 1 FROM t\",\n        )\n        self.validate_identity(\n            \"SELECT @(-1)\",\n            \"SELECT ABS((-1))\",\n        )\n        self.validate_identity(\n            \"SELECT @(-1) + 1\",\n            \"SELECT ABS((-1) + 1)\",\n        )\n        self.validate_identity(\n            \"SELECT (@-1) + 1\",\n            \"SELECT (ABS(-1)) + 1\",\n        )\n\n    def test_show_tables(self):\n        self.validate_identity(\"SHOW TABLES\").assert_is(exp.Show)\n        self.validate_identity(\"SHOW ALL TABLES\").assert_is(exp.Show)\n\n    def test_extract_date_parts(self):\n        for part in (\"WEEK\", \"WEEKOFYEAR\"):\n            # Both are synonyms for ISO week\n            self.validate_identity(f\"EXTRACT({part} FROM foo)\", \"EXTRACT(WEEK FROM foo)\")\n\n        for part in (\n            \"WEEKDAY\",\n            \"ISOYEAR\",\n            \"ISODOW\",\n            \"YEARWEEK\",\n            \"TIMEZONE_HOUR\",\n            \"TIMEZONE_MINUTE\",\n        ):\n            with self.subTest(f\"Testing DuckDB EXTRACT({part} FROM foo)\"):\n                # All of these should remain as is, they don't have synonyms\n                self.validate_identity(f\"EXTRACT({part} FROM foo)\")\n\n    def test_set_item(self):\n        self.validate_identity(\"SET memory_limit = '10GB'\")\n        self.validate_identity(\"SET SESSION default_collation = 'nocase'\")\n        self.validate_identity(\"SET GLOBAL sort_order = 'desc'\")\n        self.validate_identity(\"SET VARIABLE my_var = 30\")\n        self.validate_identity(\"SET VARIABLE location_map = (SELECT foo FROM bar)\")\n\n        self.validate_identity(\"SET VARIABLE my_var TO 30\", \"SET VARIABLE my_var = 30\")\n\n        self.validate_all(\n            \"SET VARIABLE a = 1\",\n            write={\n                \"duckdb\": \"SET VARIABLE a = 1\",\n                \"bigquery\": \"SET a = 1\",\n                \"snowflake\": \"SET a = 1\",\n            },\n        )\n\n    def test_reset(self):\n        self.validate_identity(\"RESET threads\", check_command_warning=True)\n        self.validate_identity(\"RESET memory_limit\", check_command_warning=True)\n        self.validate_identity(\"RESET default_collation\", check_command_warning=True)\n\n        # Test RESET with scope modifiers\n        self.validate_identity(\"RESET SESSION threads\", check_command_warning=True)\n        self.validate_identity(\"RESET GLOBAL memory_limit\", check_command_warning=True)\n        self.validate_identity(\"RESET LOCAL threads\", check_command_warning=True)\n        self.validate_identity(\"RESET SESSION default_collation\", check_command_warning=True)\n\n    def test_map_struct(self):\n        self.validate_identity(\"MAP {1: 'a', 2: 'b'}\")\n        self.validate_identity(\"MAP {'1': 'a', '2': 'b'}\")\n        self.validate_identity(\"MAP {[1, 2]: 'a', [3, 4]: 'b'}\")\n\n    def test_create_sequence(self):\n        self.validate_identity(\n            \"CREATE SEQUENCE serial START 101\", \"CREATE SEQUENCE serial START WITH 101\"\n        )\n        self.validate_identity(\"CREATE SEQUENCE serial START WITH 1 INCREMENT BY 2\")\n        self.validate_identity(\"CREATE SEQUENCE serial START WITH 99 INCREMENT BY -1 MAXVALUE 99\")\n        self.validate_identity(\"CREATE SEQUENCE serial START WITH 1 MAXVALUE 10 NO CYCLE\")\n        self.validate_identity(\"CREATE SEQUENCE serial START WITH 1 MAXVALUE 10 CYCLE\")\n\n    def test_install(self):\n        ast = self.validate_identity(\"INSTALL httpfs\")\n        ast.assert_is(exp.Install).name == \"httpfs\"\n        assert isinstance(ast.this, exp.Identifier)\n\n        self.validate_identity(\"INSTALL httpfs FROM community\")\n        self.validate_identity(\"INSTALL httpfs FROM 'https://extensions.duckdb.org'\")\n        self.validate_identity(\"FORCE INSTALL httpfs\").assert_is(exp.Install).name == \"httpfs\"\n        self.validate_identity(\"FORCE INSTALL httpfs FROM community\")\n        self.validate_identity(\"FORCE INSTALL httpfs FROM 'https://extensions.duckdb.org'\")\n        self.validate_identity(\"FORCE CHECKPOINT db\", check_command_warning=True)\n\n    def test_cte_using_key(self):\n        self.validate_identity(\n            \"WITH RECURSIVE tbl(a, b) USING KEY (a) AS (SELECT a, b FROM (VALUES (1, 3), (2, 4)) AS t(a, b) UNION SELECT a + 1, b FROM tbl WHERE a < 3) SELECT * FROM tbl\"\n        )\n        self.validate_identity(\n            \"WITH RECURSIVE tbl(a, b) USING KEY (a, b) AS (SELECT a, b FROM (VALUES (1, 3), (2, 4)) AS t(a, b) UNION SELECT a + 1, b FROM tbl WHERE a < 3) SELECT * FROM tbl\"\n        )\n\n    def test_udf(self):\n        for keyword in (\"FUNCTION\", \"MACRO\"):\n            with self.subTest(f\"Testing DuckDB's UDF for keyword: {keyword}\"):\n                self.validate_identity(f\"SELECT {keyword}\")\n\n                self.validate_identity(f\"CREATE {keyword} add(a, b) AS a + b\")\n                self.validate_identity(\n                    f\"CREATE {keyword} ifelse(a, b, c) AS CASE WHEN a THEN b ELSE c END\"\n                )\n\n    def test_bitwise_agg(self):\n        self.validate_all(\n            \"SELECT BIT_OR(int_value) FROM t\",\n            read={\n                \"snowflake\": \"SELECT BITOR_AGG(int_value) FROM t\",\n                \"duckdb\": \"SELECT BIT_OR(int_value) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_AND(int_value) FROM t\",\n            read={\n                \"snowflake\": \"SELECT BITAND_AGG(int_value) FROM t\",\n                \"duckdb\": \"SELECT BIT_AND(int_value) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_XOR(int_value) FROM t\",\n            read={\n                \"snowflake\": \"SELECT BITXOR_AGG(int_value) FROM t\",\n                \"duckdb\": \"SELECT BIT_XOR(int_value) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_OR(CAST(val AS FLOAT)) FROM t\",\n            write={\n                \"duckdb\": \"SELECT BIT_OR(CAST(ROUND(CAST(val AS REAL)) AS INT)) FROM t\",\n                \"snowflake\": \"SELECT BITORAGG(CAST(val AS FLOAT)) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_AND(CAST(val AS DOUBLE)) FROM t\",\n            write={\n                \"duckdb\": \"SELECT BIT_AND(CAST(ROUND(CAST(val AS DOUBLE)) AS INT)) FROM t\",\n                \"snowflake\": \"SELECT BITANDAGG(CAST(val AS DOUBLE)) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_OR(CAST(val AS DECIMAL(10, 2))) FROM t\",\n            write={\n                \"duckdb\": \"SELECT BIT_OR(CAST(CAST(val AS DECIMAL(10, 2)) AS INT)) FROM t\",\n                \"snowflake\": \"SELECT BITORAGG(CAST(val AS DECIMAL(10, 2))) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_XOR(CAST(val AS DECIMAL)) FROM t\",\n            write={\n                \"duckdb\": \"SELECT BIT_XOR(CAST(CAST(val AS DECIMAL(18, 3)) AS INT)) FROM t\",\n                \"snowflake\": \"SELECT BITXORAGG(CAST(val AS DECIMAL(18, 3))) FROM t\",\n            },\n        )\n\n    def test_approx_percentile(self):\n        self.validate_all(\n            \"SELECT APPROX_QUANTILE(a, 0.5) FROM t\",\n            read={\n                \"snowflake\": \"SELECT APPROX_PERCENTILE(a, 0.5) FROM t\",\n            },\n            write={\n                \"duckdb\": \"SELECT APPROX_QUANTILE(a, 0.5) FROM t\",\n                \"snowflake\": \"SELECT APPROX_PERCENTILE(a, 0.5) FROM t\",\n            },\n        )\n        expr = annotate_types(\n            parse_one(\"SELECT APPROX_PERCENTILE(CAST(a AS DOUBLE), 0.5) FROM t\", read=\"snowflake\")\n        )\n        self.assertEqual(\n            expr.sql(dialect=\"duckdb\"),\n            \"SELECT CAST(APPROX_QUANTILE(CAST(a AS DOUBLE), 0.5) AS DOUBLE) FROM t\",\n        )\n\n    def test_current_database(self):\n        self.validate_all(\n            \"SELECT CURRENT_DATABASE()\",\n            read={\n                \"snowflake\": \"SELECT CURRENT_DATABASE()\",\n            },\n            write={\n                \"duckdb\": \"SELECT CURRENT_DATABASE()\",\n                \"snowflake\": \"SELECT CURRENT_DATABASE()\",\n            },\n        )\n\n    def test_current_schema(self):\n        self.validate_all(\n            \"SELECT CURRENT_SCHEMA()\",\n            read={\n                \"snowflake\": \"SELECT CURRENT_SCHEMA()\",\n            },\n            write={\n                \"duckdb\": \"SELECT CURRENT_SCHEMA()\",\n                \"snowflake\": \"SELECT CURRENT_SCHEMA()\",\n            },\n        )\n\n    def test_current_schemas(self):\n        self.validate_all(\n            \"SELECT CURRENT_SCHEMAS(TRUE)\",\n            read={\n                \"snowflake\": \"SELECT CURRENT_SCHEMAS()\",\n            },\n            write={\n                \"duckdb\": \"SELECT CURRENT_SCHEMAS(TRUE)\",\n                \"snowflake\": \"SELECT CURRENT_SCHEMAS()\",\n            },\n        )\n\n    def test_map_delete(self):\n        self.validate_all(\n            \"SELECT MAP_FROM_ENTRIES(LIST_FILTER(MAP_ENTRIES(CAST({'a': 1, 'b': 2, 'c': 3} AS MAP(TEXT, DECIMAL(38, 0)))), x -> NOT x.key IN ('a', 'b')))\",\n            read={\n                \"snowflake\": \"SELECT MAP_DELETE({'a':1,'b':2,'c':3}::MAP(VARCHAR,NUMBER),'a','b')\",\n            },\n            write={\n                \"duckdb\": \"SELECT MAP_FROM_ENTRIES(LIST_FILTER(MAP_ENTRIES(CAST({'a': 1, 'b': 2, 'c': 3} AS MAP(TEXT, DECIMAL(38, 0)))), x -> NOT x.key IN ('a', 'b')))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT id, MAP_FROM_ENTRIES(LIST_FILTER(MAP_ENTRIES(attrs), x -> NOT x.key IN (del_key1, del_key2))) AS attrs_after_delete FROM demo_maps\",\n            read={\n                \"snowflake\": \"SELECT id, MAP_DELETE(attrs, del_key1, del_key2) AS attrs_after_delete FROM demo_maps\",\n            },\n            write={\n                \"duckdb\": \"SELECT id, MAP_FROM_ENTRIES(LIST_FILTER(MAP_ENTRIES(attrs), x -> NOT x.key IN (del_key1, del_key2))) AS attrs_after_delete FROM demo_maps\",\n            },\n        )\n\n    def test_map_size(self):\n        self.validate_all(\n            \"SELECT CARDINALITY(CAST({'a': 1, 'b': 2, 'c': 3} AS MAP(TEXT, DECIMAL(38, 0)))) AS map_size\",\n            read={\n                \"snowflake\": \"SELECT MAP_SIZE({'a':1,'b':2,'c':3}::MAP(VARCHAR,NUMBER)) AS map_size\",\n            },\n            write={\n                \"duckdb\": \"SELECT CARDINALITY(CAST({'a': 1, 'b': 2, 'c': 3} AS MAP(TEXT, DECIMAL(38, 0)))) AS map_size\",\n            },\n        )\n        self.validate_all(\n            \"SELECT id, CARDINALITY(attrs) AS attr_count FROM demo_maps\",\n            read={\n                \"snowflake\": \"SELECT id, MAP_SIZE(attrs) AS attr_count FROM demo_maps\",\n            },\n            write={\n                \"duckdb\": \"SELECT id, CARDINALITY(attrs) AS attr_count FROM demo_maps\",\n            },\n        )\n\n    def test_map_pick(self):\n        sql = \"SELECT MAP_PICK(t.t_map, t.t_key) FROM t\"\n\n        annotated = annotate_types(\n            parse_one(sql, dialect=\"snowflake\"),\n            schema={\"t\": {\"t_map\": \"MAP(VARCHAR, INT)\", \"t_key\": \"VARCHAR\"}},\n            dialect=\"snowflake\",\n        )\n        self.assertEqual(\n            annotated.sql(dialect=\"duckdb\"),\n            \"SELECT MAP_FROM_ENTRIES(LIST_FILTER(MAP_ENTRIES(t.t_map), x -> x.key IN (t.t_key))) FROM t\",\n        )\n\n        annotated = annotate_types(\n            parse_one(sql, dialect=\"snowflake\"),\n            schema={\"t\": {\"t_map\": \"MAP(VARCHAR, INT)\", \"t_key\": \"ARRAY(VARCHAR)\"}},\n            dialect=\"snowflake\",\n        )\n        self.assertEqual(\n            annotated.sql(dialect=\"duckdb\"),\n            \"SELECT MAP_FROM_ENTRIES(LIST_FILTER(MAP_ENTRIES(t.t_map), x -> ARRAY_CONTAINS(t.t_key, x.key))) FROM t\",\n        )\n\n        sql = \"SELECT MAP_PICK(t.t_map, t.t_key1, t.t_key2) FROM t\"\n\n        annotated = annotate_types(\n            parse_one(sql, dialect=\"snowflake\"),\n            schema={\"t\": {\"t_map\": \"MAP(VARCHAR, INT)\", \"t_key1\": \"VARCHAR\", \"t_key2\": \"VARCHAR\"}},\n            dialect=\"snowflake\",\n        )\n        self.assertEqual(\n            annotated.sql(dialect=\"duckdb\"),\n            \"SELECT MAP_FROM_ENTRIES(LIST_FILTER(MAP_ENTRIES(t.t_map), x -> x.key IN (t.t_key1, t.t_key2))) FROM t\",\n        )\n\n    def test_to_array(self):\n        self.validate_all(\n            \"SELECT CASE WHEN 'hello, snowman' IS NULL THEN NULL ELSE ['hello, snowman'] END AS result\",\n            read={\n                \"snowflake\": \"SELECT TO_ARRAY('hello, snowman') AS result\",\n            },\n            write={\n                \"duckdb\": \"SELECT CASE WHEN 'hello, snowman' IS NULL THEN NULL ELSE ['hello, snowman'] END AS result\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CASE WHEN 4.2 IS NULL THEN NULL ELSE [4.2] END AS result\",\n            read={\n                \"snowflake\": \"SELECT TO_ARRAY(4.2) AS result\",\n            },\n            write={\n                \"duckdb\": \"SELECT CASE WHEN 4.2 IS NULL THEN NULL ELSE [4.2] END AS result\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ['a', 'b'] AS result\",\n            read={\n                \"snowflake\": \"SELECT TO_ARRAY(ARRAY_CONSTRUCT('a', 'b')) AS result\",\n            },\n            write={\n                \"duckdb\": \"SELECT ['a', 'b'] AS result\",\n            },\n        )\n\n    def test_map_insert(self):\n        self.validate_all(\n            \"SELECT MAP_CONCAT(CAST({'a': 1, 'b': 2} AS MAP(TEXT, DECIMAL(38, 0))), MAP {'c': CAST(3 AS DECIMAL(38, 0))})\",\n            read={\n                \"snowflake\": \"SELECT MAP_INSERT({'a':1,'b':2}::MAP(VARCHAR,NUMBER),'c',3)\",\n            },\n            write={\n                \"duckdb\": \"SELECT MAP_CONCAT(CAST({'a': 1, 'b': 2} AS MAP(TEXT, DECIMAL(38, 0))), MAP {'c': CAST(3 AS DECIMAL(38, 0))})\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MAP_CONCAT(CAST({'a': 1} AS MAP(TEXT, DECIMAL(38, 0))), MAP {'a': CAST(99 AS DECIMAL(38, 0))})\",\n            read={\n                \"snowflake\": \"SELECT MAP_INSERT({'a':1}::MAP(VARCHAR,NUMBER),'a',99,TRUE)\",\n            },\n            write={\n                \"duckdb\": \"SELECT MAP_CONCAT(CAST({'a': 1} AS MAP(TEXT, DECIMAL(38, 0))), MAP {'a': CAST(99 AS DECIMAL(38, 0))})\",\n            },\n        )\n        self.validate_all(\n            \"SELECT id, MAP_CONCAT(attrs, MAP {'new_key': 'new_value'}) AS attrs_with_insert FROM demo_maps\",\n            read={\n                \"snowflake\": \"SELECT id, MAP_INSERT(attrs, 'new_key', 'new_value') AS attrs_with_insert FROM demo_maps\",\n            },\n            write={\n                \"duckdb\": \"SELECT id, MAP_CONCAT(attrs, MAP {'new_key': 'new_value'}) AS attrs_with_insert FROM demo_maps\",\n            },\n        )\n\n        self.assertEqual(\n            annotate_types(\n                parse_one(\"SELECT MAP_INSERT(my_map, 'key', 42) FROM my_table\", read=\"snowflake\"),\n                dialect=\"snowflake\",\n            ).sql(\"duckdb\"),\n            \"SELECT MAP_CONCAT(my_map, MAP {'key': 42}) FROM my_table\",\n        )\n\n        self.validate_all(\n            \"SELECT TO_VARIANT('1')\",\n            write={\n                \"duckdb\": \"SELECT CAST('1' AS VARIANT)\",\n                \"snowflake\": \"SELECT TO_VARIANT('1')\",\n            },\n        )\n"
  },
  {
    "path": "tests/dialects/test_dune.py",
    "content": "from sqlglot import exp\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestDune(Validator):\n    dialect = \"dune\"\n\n    def test_dune(self):\n        self.validate_identity(\"CAST(x AS INT256)\")\n        self.validate_identity(\"CAST(x AS UINT256)\")\n\n        for hex_literal in (\n            \"deadbeef\",\n            \"deadbeefdead\",\n            \"deadbeefdeadbeef\",\n            \"deadbeefdeadbeefde\",\n            \"deadbeefdeadbeefdead\",\n            \"deadbeefdeadbeefdeadbeef\",\n            \"deadbeefdeadbeefdeadbeefdeadbeef\",\n        ):\n            with self.subTest(f\"Transpiling hex literal {hex_literal}\"):\n                self.parse_one(f\"0x{hex_literal}\").assert_is(exp.HexString)\n\n                self.validate_all(\n                    f\"SELECT 0x{hex_literal}\",\n                    read={\n                        \"dune\": f\"SELECT X'{hex_literal}'\",\n                        \"postgres\": f\"SELECT x'{hex_literal}'\",\n                        \"trino\": f\"SELECT X'{hex_literal}'\",\n                    },\n                    write={\n                        \"dune\": f\"SELECT 0x{hex_literal}\",\n                        \"postgres\": f\"SELECT x'{hex_literal}'\",\n                        \"trino\": f\"SELECT x'{hex_literal}'\",\n                    },\n                )\n"
  },
  {
    "path": "tests/dialects/test_exasol.py",
    "content": "from sqlglot import exp, transpile, UnsupportedError, ErrorLevel\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestExasol(Validator):\n    dialect = \"exasol\"\n    maxDiff = None\n\n    def test_exasol(self):\n        self.validate_identity(\n            \"SELECT 1 AS [x]\",\n            'SELECT 1 AS \"x\"',\n        )\n        self.validate_identity(\"SYSTIMESTAMP\", \"SYSTIMESTAMP()\")\n        self.validate_identity(\"SELECT SYSTIMESTAMP()\")\n        self.validate_identity(\"SELECT SYSTIMESTAMP(6)\")\n        self.validate_identity(\"SELECT CURDATE()\", \"SELECT CURRENT_DATE\")\n        self.validate_identity(\"SELECT USER\", \"SELECT CURRENT_USER\")\n        self.validate_identity(\"SELECT USER()\", \"SELECT CURRENT_USER\")\n        self.validate_identity(\"SELECT CURRENT_USER\", \"SELECT CURRENT_USER\")\n        self.validate_identity(\"CURRENT_SCHEMA\").assert_is(exp.CurrentSchema)\n        self.validate_identity(\"SELECT NOW()\", \"SELECT CURRENT_TIMESTAMP()\")\n        self.validate_identity(\"SELECT FROM_POSIX_TIME(1234567890)\")\n        self.validate_all(\n            \"SELECT FROM_POSIX_TIME(col)\",\n            read={\n                \"mysql\": \"SELECT FROM_UNIXTIME(col)\",\n            },\n            write={\n                \"exasol\": \"SELECT FROM_POSIX_TIME(col)\",\n                \"mysql\": \"SELECT FROM_UNIXTIME(col)\",\n            },\n        )\n\n    def test_exasol_keywords(self):\n        keywords = [\"CS\", \"ADD\", \"BOOLEAN\", \"CALL\", \"CONTROL\"]\n\n        for keyword in keywords:\n            with self.subTest(keyword=keyword):\n                self.validate_identity(f\"SELECT 1 AS {keyword}\", f'SELECT 1 AS \"{keyword}\"')\n\n    def test_qualify_unscoped_star(self):\n        self.validate_all(\n            \"SELECT TEST.*, 1 FROM TEST\",\n            read={\n                \"\": \"SELECT *, 1 FROM TEST\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT t.*, 1 FROM t\",\n        )\n        self.validate_identity(\n            \"SELECT t.* FROM t\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t\",\n        )\n        self.validate_identity(\n            \"WITH t AS (SELECT 1 AS x) SELECT t.*, 3 FROM t\",\n        )\n        self.validate_all(\n            \"WITH t1 AS (SELECT 1 AS c1), t2 AS (SELECT 2 AS c2) SELECT t1.*, t2.*, 3 FROM t1, t2\",\n            read={\n                \"\": \"WITH t1 AS (SELECT 1 AS c1), t2 AS (SELECT 2 AS c2) SELECT *, 3 FROM t1, t2\",\n            },\n        )\n        self.validate_all(\n            'SELECT \"A\".*, \"B\".*, 3 FROM \"A\" JOIN \"B\" ON 1 = 1',\n            read={\n                \"\": 'SELECT *, 3 FROM \"A\" JOIN \"B\" ON 1=1',\n            },\n        )\n        self.validate_all(\n            \"SELECT s.*, q.*, 7 FROM (SELECT 1 AS x) AS s CROSS JOIN (SELECT 2 AS y) AS q\",\n            read={\n                \"\": \"SELECT *, 7 FROM (SELECT 1 AS x) s CROSS JOIN (SELECT 2 AS y) q\",\n            },\n        )\n\n    def test_type_mappings(self):\n        self.validate_identity(\"CAST(x AS BLOB)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS LONGBLOB)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS LONGTEXT)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS MEDIUMBLOB)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS MEDIUMTEXT)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS TINYBLOB)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS TINYTEXT)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS TEXT)\", \"CAST(x AS LONG VARCHAR)\")\n        self.validate_identity(\n            \"SELECT CAST((CAST(202305 AS INT) - 100) AS LONG VARCHAR) AS CAL_YEAR_WEEK_ADJUSTED\"\n        )\n        self.validate_identity(\"CAST(x AS VARBINARY)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS VARCHAR)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS CHAR)\", \"CAST(x AS CHAR)\")\n        self.validate_identity(\"CAST(x AS TINYINT)\", \"CAST(x AS SMALLINT)\")\n        self.validate_identity(\"CAST(x AS SMALLINT)\")\n        self.validate_identity(\"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS MEDIUMINT)\", \"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS BIGINT)\")\n        self.validate_identity(\"CAST(x AS FLOAT)\")\n        self.validate_identity(\"CAST(x AS DOUBLE)\")\n        self.validate_identity(\"CAST(x AS DECIMAL32)\", \"CAST(x AS DECIMAL)\")\n        self.validate_identity(\"CAST(x AS DECIMAL64)\", \"CAST(x AS DECIMAL)\")\n        self.validate_identity(\"CAST(x AS DECIMAL128)\", \"CAST(x AS DECIMAL)\")\n        self.validate_identity(\"CAST(x AS DECIMAL256)\", \"CAST(x AS DECIMAL)\")\n        self.validate_identity(\"CAST(x AS DATE)\")\n        self.validate_identity(\"CAST(x AS DATETIME)\", \"CAST(x AS TIMESTAMP)\")\n        self.validate_identity(\"CAST(x AS TIMESTAMP)\")\n        self.validate_all(\n            \"CAST(x AS TIMESTAMP)\",\n            read={\n                \"tsql\": \"CAST(x AS DATETIME2)\",\n            },\n            write={\n                \"exasol\": \"CAST(x AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS TIMESTAMP)\",\n            read={\n                \"tsql\": \"CAST(x AS SMALLDATETIME)\",\n            },\n            write={\n                \"exasol\": \"CAST(x AS TIMESTAMP)\",\n            },\n        )\n        self.validate_identity(\"CAST(x AS BOOLEAN)\")\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPLTZ)\", \"CAST(x AS TIMESTAMP WITH LOCAL TIME ZONE)\"\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMP(3) WITH LOCAL TIME ZONE)\",\n            \"CAST(x AS TIMESTAMP WITH LOCAL TIME ZONE)\",\n        )\n\n    def test_mod(self):\n        self.validate_all(\n            \"SELECT MOD(x, 10)\",\n            read={\"exasol\": \"SELECT MOD(x, 10)\"},\n            write={\n                \"teradata\": \"SELECT x MOD 10\",\n                \"mysql\": \"SELECT x % 10\",\n                \"exasol\": \"SELECT MOD(x, 10)\",\n            },\n        )\n\n    def test_bits(self):\n        self.validate_all(\n            \"SELECT BIT_AND(x, 1)\",\n            read={\n                \"exasol\": \"SELECT BIT_AND(x, 1)\",\n                \"duckdb\": \"SELECT x & 1\",\n                \"presto\": \"SELECT BITWISE_AND(x, 1)\",\n                \"spark\": \"SELECT x & 1\",\n            },\n            write={\n                \"exasol\": \"SELECT BIT_AND(x, 1)\",\n                \"duckdb\": \"SELECT x & 1\",\n                \"hive\": \"SELECT x & 1\",\n                \"presto\": \"SELECT BITWISE_AND(x, 1)\",\n                \"spark\": \"SELECT x & 1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_OR(x, 1)\",\n            read={\n                \"exasol\": \"SELECT BIT_OR(x, 1)\",\n                \"duckdb\": \"SELECT x | 1\",\n                \"presto\": \"SELECT BITWISE_OR(x, 1)\",\n                \"spark\": \"SELECT x | 1\",\n            },\n            write={\n                \"exasol\": \"SELECT BIT_OR(x, 1)\",\n                \"duckdb\": \"SELECT x | 1\",\n                \"hive\": \"SELECT x | 1\",\n                \"presto\": \"SELECT BITWISE_OR(x, 1)\",\n                \"spark\": \"SELECT x | 1\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT BIT_XOR(x, 1)\",\n            read={\n                \"\": \"SELECT x ^ 1\",\n                \"exasol\": \"SELECT BIT_XOR(x, 1)\",\n                \"bigquery\": \"SELECT x ^ 1\",\n                \"presto\": \"SELECT BITWISE_XOR(x, 1)\",\n                \"postgres\": \"SELECT x # 1\",\n            },\n            write={\n                \"\": \"SELECT x ^ 1\",\n                \"exasol\": \"SELECT BIT_XOR(x, 1)\",\n                \"bigquery\": \"SELECT x ^ 1\",\n                \"duckdb\": \"SELECT XOR(x, 1)\",\n                \"presto\": \"SELECT BITWISE_XOR(x, 1)\",\n                \"postgres\": \"SELECT x # 1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_NOT(x)\",\n            read={\n                \"exasol\": \"SELECT BIT_NOT(x)\",\n                \"duckdb\": \"SELECT ~x\",\n                \"presto\": \"SELECT BITWISE_NOT(x)\",\n                \"spark\": \"SELECT ~x\",\n            },\n            write={\n                \"exasol\": \"SELECT BIT_NOT(x)\",\n                \"duckdb\": \"SELECT ~x\",\n                \"hive\": \"SELECT ~x\",\n                \"presto\": \"SELECT BITWISE_NOT(x)\",\n                \"spark\": \"SELECT ~x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_LSHIFT(x, 1)\",\n            read={\n                \"exasol\": \"SELECT BIT_LSHIFT(x, 1)\",\n                \"spark\": \"SELECT SHIFTLEFT(x, 1)\",\n                \"duckdb\": \"SELECT x << 1\",\n                \"hive\": \"SELECT x << 1\",\n            },\n            write={\n                \"exasol\": \"SELECT BIT_LSHIFT(x, 1)\",\n                \"duckdb\": \"SELECT x << 1\",\n                \"presto\": \"SELECT BITWISE_ARITHMETIC_SHIFT_LEFT(x, 1)\",\n                \"hive\": \"SELECT x << 1\",\n                \"spark\": \"SELECT SHIFTLEFT(x, 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BIT_RSHIFT(x, 1)\",\n            read={\n                \"exasol\": \"SELECT BIT_RSHIFT(x, 1)\",\n                \"spark\": \"SELECT SHIFTRIGHT(x, 1)\",\n                \"duckdb\": \"SELECT x >> 1\",\n                \"hive\": \"SELECT x >> 1\",\n            },\n            write={\n                \"exasol\": \"SELECT BIT_RSHIFT(x, 1)\",\n                \"duckdb\": \"SELECT x >> 1\",\n                \"presto\": \"SELECT BITWISE_ARITHMETIC_SHIFT_RIGHT(x, 1)\",\n                \"hive\": \"SELECT x >> 1\",\n                \"spark\": \"SELECT SHIFTRIGHT(x, 1)\",\n            },\n        )\n\n    def test_aggregateFunctions(self):\n        self.validate_all(\n            \"SELECT department, EVERY(age >= 30) AS EVERY FROM employee_table GROUP BY department\",\n            read={\n                \"exasol\": \"SELECT department, EVERY(age >= 30) AS EVERY FROM employee_table GROUP BY department\",\n            },\n            write={\n                \"exasol\": \"SELECT department, EVERY(age >= 30) AS EVERY FROM employee_table GROUP BY department\",\n                \"duckdb\": \"SELECT department, ALL (age >= 30) AS EVERY FROM employee_table GROUP BY department\",\n            },\n        )\n        (\n            self.validate_all(\n                \"SELECT VAR_POP(current_salary)\",\n                write={\n                    \"exasol\": \"SELECT VAR_POP(current_salary)\",\n                    \"duckdb\": \"SELECT VAR_POP(current_salary)\",\n                    \"presto\": \"SELECT VAR_POP(current_salary)\",\n                },\n                read={\n                    \"exasol\": \"SELECT VAR_POP(current_salary)\",\n                    \"duckdb\": \"SELECT VAR_POP(current_salary)\",\n                    \"presto\": \"SELECT VAR_POP(current_salary)\",\n                },\n            ),\n        )\n        self.validate_all(\n            \"SELECT APPROXIMATE_COUNT_DISTINCT(y)\",\n            read={\n                \"spark\": \"SELECT APPROX_COUNT_DISTINCT(y)\",\n                \"exasol\": \"SELECT APPROXIMATE_COUNT_DISTINCT(y)\",\n            },\n            write={\n                \"redshift\": \"SELECT APPROXIMATE COUNT(DISTINCT y)\",\n                \"spark\": \"SELECT APPROX_COUNT_DISTINCT(y)\",\n                \"exasol\": \"SELECT APPROXIMATE_COUNT_DISTINCT(y)\",\n            },\n        )\n\n        for func in (\"RANK\", \"DENSE_RANK\"):\n            with self.subTest(func=func):\n                self.validate_all(\n                    f\"SELECT a, b, {func}(b) OVER (ORDER BY b) FROM (VALUES ('A1', 2), ('A1', 1), ('A2', 3), ('A1', 1)) AS tab(a, b)\",\n                    write={\n                        \"exasol\": f\"SELECT a, b, {func}() OVER (ORDER BY b) FROM (VALUES ('A1', 2), ('A1', 1), ('A2', 3), ('A1', 1)) AS tab(a, b)\",\n                        \"databricks\": f\"SELECT a, b, {func}(b) OVER (ORDER BY b NULLS LAST) FROM VALUES ('A1', 2), ('A1', 1), ('A2', 3), ('A1', 1) AS tab(a, b)\",\n                        \"spark\": f\"SELECT a, b, {func}(b) OVER (ORDER BY b NULLS LAST) FROM VALUES ('A1', 2), ('A1', 1), ('A2', 3), ('A1', 1) AS tab(a, b)\",\n                    },\n                )\n\n    def test_stringFunctions(self):\n        self.validate_identity(\n            \"TO_CHAR(CAST(TO_DATE(date, 'YYYYMMDD') AS TIMESTAMP), 'DY') AS day_of_week\"\n        )\n        self.validate_identity(\"SELECT TO_CHAR(12345.67890, '9999999.999999999') AS TO_CHAR\")\n        self.validate_identity(\n            \"SELECT TO_CHAR(DATE '1999-12-31') AS TO_CHAR\",\n            \"SELECT TO_CHAR(CAST('1999-12-31' AS DATE)) AS TO_CHAR\",\n        )\n        self.validate_identity(\n            \"SELECT TO_CHAR(TIMESTAMP '1999-12-31 23:59:00', 'HH24:MI:SS DD-MM-YYYY') AS TO_CHAR\",\n            \"SELECT TO_CHAR(CAST('1999-12-31 23:59:00' AS TIMESTAMP), 'HH24:MI:SS DD-MM-YYYY') AS TO_CHAR\",\n        )\n        self.validate_identity(\"SELECT TO_CHAR(12345.6789) AS TO_CHAR\")\n        self.validate_identity(\"SELECT TO_CHAR(-12345.67890, '000G000G000D000000MI') AS TO_CHAR\")\n        self.validate_all(\n            \"SELECT TO_CHAR(CAST('2009-10-04 22:23:00' AS TIMESTAMP), 'DAY MONTH YYYY')\",\n            read={\n                \"mysql\": \"SELECT DATE_FORMAT('2009-10-04 22:23:00', '%W %M %Y')\",\n            },\n        )\n\n        self.validate_identity(\n            \"SELECT id, department, hire_date, GROUP_CONCAT(id ORDER BY hire_date SEPARATOR ',') OVER (PARTITION BY department rows between 1 preceding and 1 following) GROUP_CONCAT_RESULT from employee_table ORDER BY department, hire_date\",\n            \"SELECT id, department, hire_date, LISTAGG(id, ',') WITHIN GROUP (ORDER BY hire_date) OVER (PARTITION BY department rows BETWEEN 1 preceding AND 1 following) AS GROUP_CONCAT_RESULT FROM employee_table ORDER BY department, hire_date\",\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(DISTINCT x ORDER BY y DESC)\",\n            write={\n                \"exasol\": \"LISTAGG(DISTINCT x, ',') WITHIN GROUP (ORDER BY y DESC)\",\n                \"mysql\": \"GROUP_CONCAT(DISTINCT x ORDER BY y DESC SEPARATOR ',')\",\n                \"tsql\": \"STRING_AGG(x, ',') WITHIN GROUP (ORDER BY y DESC)\",\n                \"databricks\": \"LISTAGG(DISTINCT x, ',') WITHIN GROUP (ORDER BY y DESC)\",\n            },\n        )\n        self.validate_all(\n            \"EDIT_DISTANCE(col1, col2)\",\n            read={\n                \"exasol\": \"EDIT_DISTANCE(col1, col2)\",\n                \"bigquery\": \"EDIT_DISTANCE(col1, col2)\",\n                \"clickhouse\": \"editDistance(col1, col2)\",\n                \"drill\": \"LEVENSHTEIN_DISTANCE(col1, col2)\",\n                \"duckdb\": \"LEVENSHTEIN(col1, col2)\",\n                \"hive\": \"LEVENSHTEIN(col1, col2)\",\n            },\n            write={\n                \"exasol\": \"EDIT_DISTANCE(col1, col2)\",\n                \"bigquery\": \"EDIT_DISTANCE(col1, col2)\",\n                \"clickhouse\": \"editDistance(col1, col2)\",\n                \"drill\": \"LEVENSHTEIN_DISTANCE(col1, col2)\",\n                \"duckdb\": \"LEVENSHTEIN(col1, col2)\",\n                \"hive\": \"LEVENSHTEIN(col1, col2)\",\n            },\n        )\n        (\n            self.validate_all(\n                \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence)\",\n                write={\n                    \"bigquery\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                    \"exasol\": \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence)\",\n                    \"duckdb\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                    \"hive\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                    \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence)\",\n                    \"spark\": \"REGEXP_REPLACE(subject, pattern, replacement, position)\",\n                },\n                read={\n                    \"exasol\": \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence)\",\n                    \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence)\",\n                    \"spark\": \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence)\",\n                },\n            ),\n        )\n        (\n            self.validate_all(\n                \"SELECT TO_CHAR(CAST('1999-12-31' AS DATE)) AS TO_CHAR\",\n                write={\n                    \"exasol\": \"SELECT TO_CHAR(CAST('1999-12-31' AS DATE)) AS TO_CHAR\",\n                    \"presto\": \"SELECT DATE_FORMAT(CAST('1999-12-31' AS DATE)) AS TO_CHAR\",\n                    \"oracle\": \"SELECT TO_CHAR(CAST('1999-12-31' AS DATE)) AS TO_CHAR\",\n                    \"redshift\": \"SELECT CAST(CAST('1999-12-31' AS DATE) AS VARCHAR(MAX)) AS TO_CHAR\",\n                    \"postgres\": \"SELECT CAST(CAST('1999-12-31' AS DATE) AS TEXT) AS TO_CHAR\",\n                },\n                read={\n                    \"exasol\": \"SELECT TO_CHAR(DATE '1999-12-31') AS TO_CHAR\",\n                },\n            ),\n        )\n        self.validate_all(\n            \"STRPOS(haystack, needle)\",\n            write={\n                \"exasol\": \"INSTR(haystack, needle)\",\n                \"bigquery\": \"INSTR(haystack, needle)\",\n                \"databricks\": \"LOCATE(needle, haystack)\",\n                \"oracle\": \"INSTR(haystack, needle)\",\n                \"presto\": \"STRPOS(haystack, needle)\",\n            },\n        )\n        self.validate_all(\n            r\"SELECT REGEXP_SUBSTR('My mail address is my_mail@yahoo.com', '(?i)[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}') AS EMAIL\",\n            write={\n                \"exasol\": r\"SELECT REGEXP_SUBSTR('My mail address is my_mail@yahoo.com', '(?i)[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}') AS EMAIL\",\n                \"bigquery\": r\"SELECT REGEXP_EXTRACT('My mail address is my_mail@yahoo.com', '(?i)[a-z0-9._%+-]+@[a-z0-9.-]+\\\\.[a-z]{2,4}') AS EMAIL\",\n                \"snowflake\": r\"SELECT REGEXP_SUBSTR('My mail address is my_mail@yahoo.com', '(?i)[a-z0-9._%+-]+@[a-z0-9.-]+\\\\.[a-z]{2,4}') AS EMAIL\",\n                \"presto\": r\"SELECT REGEXP_EXTRACT('My mail address is my_mail@yahoo.com', '(?i)[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}') AS EMAIL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SUBSTR('www.apache.org', 1, NVL(NULLIF(INSTR('www.apache.org', '.', 1, 2), 0) - 1, LENGTH('www.apache.org')))\",\n            read={\n                \"databricks\": \"SELECT substring_index('www.apache.org', '.', 2)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT SUBSTR('555A66A777', 1, NVL(NULLIF(INSTR('555A66A777', 'a', 1, 2), 0) - 1, LENGTH('555A66A777')))\",\n            read={\n                \"databricks\": \"SELECT substring_index('555A66A777' COLLATE UTF8_BINARY, 'a', 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SUBSTR('555A66A777', 1, NVL(NULLIF(INSTR(LOWER('555A66A777'), 'a', 1, 2), 0) - 1, LENGTH('555A66A777')))\",\n            read={\n                \"databricks\": \"SELECT substring_index('555A66A777' COLLATE UTF8_LCASE, 'a', 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SUBSTR('A|a|A', 1, NVL(NULLIF(INSTR(LOWER('A|a|A'), LOWER('A'), 1, 2), 0) - 1, LENGTH('A|a|A')))\",\n            read={\n                \"databricks\": \"SELECT substring_index('A|a|A' COLLATE UTF8_LCASE, 'A' COLLATE UTF8_LCASE, 2)\",\n            },\n        )\n\n    def test_datetime_functions(self):\n        formats = {\n            \"HH12\": \"hour_12\",\n            \"HH24\": \"hour_24\",\n            \"ID\": \"iso_weekday\",\n            \"IW\": \"iso_week_number\",\n            \"uW\": \"week_number_uW\",\n            \"VW\": \"week_number_VW\",\n            \"IYYY\": \"iso_year\",\n            \"MI\": \"minutes\",\n            \"SS\": \"seconds\",\n            \"DAY\": \"day_full\",\n            \"DY\": \"day_abbr\",\n        }\n        self.validate_identity(\n            \"SELECT TO_DATE('31-12-1999', 'dd-mm-yyyy') AS TO_DATE\",\n            \"SELECT TO_DATE('31-12-1999', 'DD-MM-YYYY') AS TO_DATE\",\n        )\n        self.validate_identity(\n            \"SELECT TO_DATE('31-12-1999', 'dd-mm-YY') AS TO_DATE\",\n            \"SELECT TO_DATE('31-12-1999', 'DD-MM-YY') AS TO_DATE\",\n        )\n        self.validate_identity(\"SELECT TO_DATE('31-DECEMBER-1999', 'DD-MONTH-YYYY') AS TO_DATE\")\n        self.validate_identity(\"SELECT TO_DATE('31-DEC-1999', 'DD-MON-YYYY') AS TO_DATE\")\n        self.validate_identity(\"SELECT WEEKOFYEAR('2024-05-22')\", \"SELECT WEEK('2024-05-22')\")\n\n        for fmt, alias in formats.items():\n            with self.subTest(f\"Testing TO_CHAR with format '{fmt}'\"):\n                self.validate_identity(\n                    f\"SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), '{fmt}') AS {alias}\"\n                )\n\n        self.validate_all(\n            \"SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'DY')\",\n            write={\n                \"exasol\": \"SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'DY')\",\n                \"oracle\": \"SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'DY')\",\n                \"postgres\": \"SELECT TO_CHAR(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'TMDy')\",\n                \"databricks\": \"SELECT DATE_FORMAT(CAST('2024-07-08 13:45:00' AS TIMESTAMP), 'EEE')\",\n            },\n        )\n\n        self.validate_all(\n            \"TO_DATE(x, 'YYYY-MM-DD')\",\n            write={\n                \"exasol\": \"TO_DATE(x, 'YYYY-MM-DD')\",\n                \"duckdb\": \"CAST(x AS DATE)\",\n                \"hive\": \"TO_DATE(x)\",\n                \"presto\": \"CAST(CAST(x AS TIMESTAMP) AS DATE)\",\n                \"spark\": \"TO_DATE(x)\",\n                \"snowflake\": \"TO_DATE(x, 'yyyy-mm-DD')\",\n                \"databricks\": \"TO_DATE(x)\",\n            },\n        )\n        self.validate_all(\n            \"TO_DATE(x, 'YYYY')\",\n            write={\n                \"exasol\": \"TO_DATE(x, 'YYYY')\",\n                \"duckdb\": \"CAST(STRPTIME(x, '%Y') AS DATE)\",\n                \"hive\": \"TO_DATE(x, 'yyyy')\",\n                \"presto\": \"CAST(DATE_PARSE(x, '%Y') AS DATE)\",\n                \"spark\": \"TO_DATE(x, 'yyyy')\",\n                \"snowflake\": \"TO_DATE(x, 'yyyy')\",\n                \"databricks\": \"TO_DATE(x, 'yyyy')\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT CONVERT_TZ(CAST('2012-03-25 02:30:00' AS TIMESTAMP), 'Europe/Berlin', 'UTC', 'INVALID REJECT AMBIGUOUS REJECT') AS CONVERT_TZ\"\n        )\n        self.validate_all(\n            \"SELECT CONVERT_TZ('2012-05-10 12:00:00', 'Europe/Berlin', 'America/New_York')\",\n            read={\n                \"exasol\": \"SELECT CONVERT_TZ('2012-05-10 12:00:00', 'Europe/Berlin', 'America/New_York')\",\n                \"mysql\": \"SELECT CONVERT_TZ('2012-05-10 12:00:00', 'Europe/Berlin', 'America/New_York')\",\n                \"databricks\": \"SELECT CONVERT_TIMEZONE('Europe/Berlin', 'America/New_York', '2012-05-10 12:00:00')\",\n            },\n            write={\n                \"exasol\": \"SELECT CONVERT_TZ('2012-05-10 12:00:00', 'Europe/Berlin', 'America/New_York')\",\n                \"mysql\": \"SELECT CONVERT_TZ('2012-05-10 12:00:00', 'Europe/Berlin', 'America/New_York')\",\n                \"databricks\": \"SELECT CONVERT_TIMEZONE('Europe/Berlin', 'America/New_York', '2012-05-10 12:00:00')\",\n                \"snowflake\": \"SELECT CONVERT_TIMEZONE('Europe/Berlin', 'America/New_York', '2012-05-10 12:00:00')\",\n                \"spark\": \"SELECT CONVERT_TIMEZONE('Europe/Berlin', 'America/New_York', '2012-05-10 12:00:00')\",\n                \"redshift\": \"SELECT CONVERT_TIMEZONE('Europe/Berlin', 'America/New_York', '2012-05-10 12:00:00')\",\n                \"duckdb\": \"SELECT CAST('2012-05-10 12:00:00' AS TIMESTAMP) AT TIME ZONE 'Europe/Berlin' AT TIME ZONE 'America/New_York'\",\n            },\n        )\n        self.validate_identity(\n            \"TIME_TO_STR(b, '%Y-%m-%d %H:%M:%S')\",\n            \"TO_CHAR(b, 'YYYY-MM-DD HH:MI:SS')\",\n        )\n        self.validate_identity(\n            \"SELECT TIME_TO_STR(CAST(STR_TO_TIME(date, '%Y%m%d') AS DATE), '%a') AS day_of_week\",\n            \"SELECT TO_CHAR(CAST(TO_DATE(date, 'YYYYMMDD') AS DATE), 'DY') AS day_of_week\",\n        )\n        self.validate_identity(\n            \"SELECT CAST(CAST(CURRENT_TIMESTAMP() AS TIMESTAMP) AT TIME ZONE 'CET' AS DATE) - 1\",\n            \"SELECT CAST(CONVERT_TZ(CAST(CURRENT_TIMESTAMP() AS TIMESTAMP), 'UTC', 'CET') AS DATE) - 1\",\n        )\n        units = [\"MM\", \"QUARTER\", \"WEEK\", \"MINUTE\", \"YEAR\"]\n        for unit in units:\n            with self.subTest(f\"Testing DATE_TRUNC with format '{unit}'\"):\n                self.validate_all(\n                    f\"SELECT TRUNC(CAST('2006-12-31' AS DATE), '{unit}') AS TRUNC\",\n                    write={\n                        \"exasol\": f\"SELECT DATE_TRUNC('{unit}', DATE '2006-12-31') AS TRUNC\",\n                        \"presto\": f\"SELECT DATE_TRUNC('{unit}', CAST('2006-12-31' AS DATE)) AS TRUNC\",\n                        \"databricks\": f\"SELECT TRUNC(CAST('2006-12-31' AS DATE), '{unit}') AS TRUNC\",\n                    },\n                )\n\n                self.validate_all(\n                    f\"SELECT DATE_TRUNC('{unit}', TIMESTAMP '2006-12-31T23:59:59') DATE_TRUNC\",\n                    write={\n                        \"exasol\": f\"SELECT DATE_TRUNC('{unit}', TIMESTAMP '2006-12-31 23:59:59') AS DATE_TRUNC\",\n                        \"presto\": f\"SELECT DATE_TRUNC('{unit}', CAST('2006-12-31T23:59:59' AS TIMESTAMP)) AS DATE_TRUNC\",\n                        \"databricks\": f\"SELECT DATE_TRUNC('{unit}', CAST('2006-12-31T23:59:59' AS TIMESTAMP)) AS DATE_TRUNC\",\n                    },\n                )\n                self.validate_all(\n                    f\"SELECT DATE_TRUNC('{unit}', CURRENT_TIMESTAMP) DATE_TRUNC\",\n                    write={\n                        \"exasol\": f\"SELECT DATE_TRUNC('{unit}', CURRENT_TIMESTAMP()) AS DATE_TRUNC\",\n                        \"presto\": f\"SELECT DATE_TRUNC('{unit}', CURRENT_TIMESTAMP) AS DATE_TRUNC\",\n                        \"databricks\": f\"SELECT DATE_TRUNC('{unit}', CURRENT_TIMESTAMP()) AS DATE_TRUNC\",\n                    },\n                )\n\n        from sqlglot.dialects.exasol import DATE_UNITS\n\n        for unit in DATE_UNITS:\n            with self.subTest(f\"Testing ADD_{unit}S\"):\n                self.validate_all(\n                    f\"SELECT ADD_{unit}S(DATE '2000-02-28', 1)\",\n                    write={\n                        \"exasol\": f\"SELECT ADD_{unit}S(CAST('2000-02-28' AS DATE), 1)\",\n                        \"bigquery\": f\"SELECT DATE_ADD(CAST('2000-02-28' AS DATE), INTERVAL 1 {unit})\",\n                        \"duckdb\": f\"SELECT CAST('2000-02-28' AS DATE) + INTERVAL 1 {unit}\",\n                        \"presto\": f\"SELECT DATE_ADD('{unit}', 1, CAST('2000-02-28' AS DATE))\",\n                        \"redshift\": f\"SELECT DATEADD({unit}, 1, CAST('2000-02-28' AS DATE))\",\n                        \"snowflake\": f\"SELECT DATEADD({unit}, 1, CAST('2000-02-28' AS DATE))\",\n                        \"tsql\": f\"SELECT DATEADD({unit}, 1, CAST('2000-02-28' AS DATE))\",\n                    },\n                )\n\n                self.validate_all(\n                    f\"SELECT ADD_{unit}S('2000-02-28', -'1')\",\n                    read={\n                        \"sqlite\": f\"SELECT DATE_SUB('2000-02-28', INTERVAL 1 {unit})\",\n                        \"bigquery\": f\"SELECT DATE_SUB('2000-02-28', INTERVAL 1 {unit})\",\n                        \"presto\": f\"SELECT DATE_SUB('2000-02-28', INTERVAL 1 {unit})\",\n                        \"redshift\": f\"SELECT DATE_SUB('2000-02-28', INTERVAL 1 {unit})\",\n                        \"snowflake\": f\"SELECT DATE_SUB('2000-02-28', INTERVAL 1 {unit})\",\n                        \"tsql\": f\"SELECT DATE_SUB('2000-02-28', INTERVAL 1 {unit})\",\n                    },\n                )\n\n                self.validate_all(\n                    \"SELECT CAST(ADD_DAYS(ADD_MONTHS(DATE_TRUNC('MONTH', DATE '2008-11-25'), 1), -1) AS DATE)\",\n                    read={\n                        \"snowflake\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE), MONTH)\",\n                        \"databricks\": \"SELECT LAST_DAY('2008-11-25')\",\n                        \"spark\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE))\",\n                        \"presto\": \"SELECT LAST_DAY_OF_MONTH(CAST('2008-11-25' AS DATE))\",\n                    },\n                )\n\n            with self.subTest(f\"Testing {unit}S_BETWEEN\"):\n                self.validate_all(\n                    f\"SELECT {unit}S_BETWEEN(TIMESTAMP '2000-02-28 00:00:00', CURRENT_TIMESTAMP)\",\n                    write={\n                        \"exasol\": f\"SELECT {unit}S_BETWEEN(CAST('2000-02-28 00:00:00' AS TIMESTAMP), CURRENT_TIMESTAMP())\",\n                        \"bigquery\": f\"SELECT DATE_DIFF(CAST('2000-02-28 00:00:00' AS DATETIME), CURRENT_TIMESTAMP(), {unit})\",\n                        \"duckdb\": f\"SELECT DATE_DIFF('{unit}', CURRENT_TIMESTAMP, CAST('2000-02-28 00:00:00' AS TIMESTAMP))\",\n                        \"presto\": f\"SELECT DATE_DIFF('{unit}', CURRENT_TIMESTAMP, CAST('2000-02-28 00:00:00' AS TIMESTAMP))\",\n                        \"redshift\": f\"SELECT DATEDIFF({unit}, GETDATE(), CAST('2000-02-28 00:00:00' AS TIMESTAMP))\",\n                        \"snowflake\": f\"SELECT DATEDIFF({unit}, CURRENT_TIMESTAMP(), CAST('2000-02-28 00:00:00' AS TIMESTAMP))\",\n                        \"tsql\": f\"SELECT DATEDIFF({unit}, GETDATE(), CAST('2000-02-28 00:00:00' AS DATETIME2))\",\n                    },\n                )\n        self.validate_all(\n            \"SELECT quarter('2016-08-31')\",\n            write={\n                \"exasol\": \"SELECT CEIL(MONTH(TO_DATE('2016-08-31'))/3)\",\n                \"databricks\": \"SELECT QUARTER('2016-08-31')\",\n            },\n        )\n\n    def test_number_functions(self):\n        self.validate_identity(\"SELECT TRUNC(123.456, 2) AS TRUNC\")\n        self.validate_identity(\"SELECT DIV(1234, 2) AS DIV\")\n\n        # Numeric truncation identity\n        self.validate_identity(\"TRUNC(123.456, 2)\").assert_is(exp.Trunc)\n        self.validate_identity(\"TRUNC(3.14159)\").assert_is(exp.Trunc)\n\n        # Date truncation with typed column and unit\n        # (parse_one because DateTrunc generates as DATE_TRUNC, not TRUNC)\n        self.parse_one(\"TRUNC(CAST(x AS DATE), 'MONTH')\").assert_is(exp.DateTrunc)\n        self.parse_one(\"TRUNC(CAST(x AS TIMESTAMP), 'MONTH')\").assert_is(exp.DateTrunc)\n        self.parse_one(\"TRUNC(CAST(x AS DATETIME), 'MONTH')\").assert_is(exp.DateTrunc)\n\n        # Fallback to Anonymous (Exasol requires unit for date truncation)\n        self.validate_identity(\"TRUNC(CAST(x AS DATE))\").assert_is(exp.Anonymous)\n\n        # Cross-dialect numeric truncation transpilation\n        self.validate_all(\n            \"TRUNC(price, 2)\",\n            write={\n                \"exasol\": \"TRUNC(price, 2)\",\n                \"oracle\": \"TRUNC(price, 2)\",\n                \"postgres\": \"TRUNC(price, 2)\",\n                \"mysql\": \"TRUNCATE(price, 2)\",\n                \"tsql\": \"ROUND(price, 2, 1)\",\n            },\n        )\n\n        # Date truncation with various units (Exasol-specific unit names)\n        for unit in (\"YYYY\", \"MM\", \"DD\", \"HH\", \"MI\", \"SS\", \"WW\"):\n            with self.subTest(f\"Date/time TRUNC with {unit}\"):\n                self.validate_all(\n                    f\"TRUNC(CAST(x AS TIMESTAMP), '{unit}')\",\n                    write={\n                        \"exasol\": f\"DATE_TRUNC('{unit}', x)\",\n                        \"oracle\": f\"TRUNC(CAST(x AS TIMESTAMP), '{unit}')\",\n                    },\n                )\n\n        # Q gets normalized to QUARTER\n        self.validate_all(\n            \"TRUNC(CAST(x AS TIMESTAMP), 'Q')\",\n            write={\n                \"exasol\": \"DATE_TRUNC('QUARTER', x)\",\n                \"oracle\": \"TRUNC(CAST(x AS TIMESTAMP), 'QUARTER')\",\n            },\n        )\n\n    def test_scalar(self):\n        self.validate_all(\n            \"SELECT CURRENT_USER\",\n            read={\n                \"exasol\": \"SELECT USER\",\n                \"spark\": \"SELECT CURRENT_USER()\",\n                \"trino\": \"SELECT CURRENT_USER\",\n                \"snowflake\": \"SELECT CURRENT_USER()\",\n            },\n            write={\n                \"exasol\": \"SELECT CURRENT_USER\",\n                \"spark\": \"SELECT CURRENT_USER()\",\n                \"trino\": \"SELECT CURRENT_USER\",\n                \"snowflake\": \"SELECT CURRENT_USER()\",\n            },\n        )\n        self.validate_all(\n            'CREATE OR REPLACE VIEW \"schema\".\"v\" (\"col\" COMMENT IS \\'desc\\') AS SELECT \"src_col\" AS \"col\"',\n            write={\n                \"databricks\": \"CREATE OR REPLACE VIEW `schema`.`v` (`col` COMMENT 'desc') AS SELECT `src_col` AS `col`\",\n                \"exasol\": 'CREATE OR REPLACE VIEW \"schema\".\"v\" (\"col\" COMMENT IS \\'desc\\') AS SELECT \"src_col\" AS \"col\"',\n            },\n        )\n        self.validate_all(\n            \"HASH_SHA(x)\",\n            read={\n                \"clickhouse\": \"SHA1(x)\",\n                \"exasol\": \"HASH_SHA1(x)\",\n                \"presto\": \"SHA1(x)\",\n                \"trino\": \"SHA1(x)\",\n            },\n            write={\n                \"exasol\": \"HASH_SHA(x)\",\n                \"clickhouse\": \"SHA1(x)\",\n                \"bigquery\": \"SHA1(x)\",\n                \"\": \"SHA(x)\",\n                \"presto\": \"SHA1(x)\",\n                \"trino\": \"SHA1(x)\",\n            },\n        )\n        self.validate_all(\n            \"HASH_MD5(x)\",\n            write={\n                \"exasol\": \"HASH_MD5(x)\",\n                \"\": \"MD5(x)\",\n                \"bigquery\": \"TO_HEX(MD5(x))\",\n                \"clickhouse\": \"LOWER(HEX(MD5(x)))\",\n                \"hive\": \"MD5(x)\",\n                \"presto\": \"LOWER(TO_HEX(MD5(x)))\",\n                \"spark\": \"MD5(x)\",\n                \"trino\": \"LOWER(TO_HEX(MD5(x)))\",\n            },\n        )\n        self.validate_all(\n            \"HASHTYPE_MD5(x)\",\n            write={\n                \"exasol\": \"HASHTYPE_MD5(x)\",\n                \"\": \"MD5_DIGEST(x)\",\n                \"bigquery\": \"MD5(x)\",\n                \"clickhouse\": \"MD5(x)\",\n                \"hive\": \"UNHEX(MD5(x))\",\n                \"presto\": \"MD5(x)\",\n                \"spark\": \"UNHEX(MD5(x))\",\n                \"trino\": \"MD5(x)\",\n            },\n        )\n\n        self.validate_all(\n            \"HASH_SHA256(x)\",\n            read={\n                \"clickhouse\": \"SHA256(x)\",\n                \"presto\": \"SHA256(x)\",\n                \"trino\": \"SHA256(x)\",\n                \"postgres\": \"SHA256(x)\",\n                \"duckdb\": \"SHA256(x)\",\n            },\n            write={\n                \"exasol\": \"HASH_SHA256(x)\",\n                \"bigquery\": \"SHA256(x)\",\n                \"spark2\": \"SHA2(x, 256)\",\n                \"clickhouse\": \"SHA256(x)\",\n                \"postgres\": \"SHA256(x)\",\n                \"presto\": \"SHA256(x)\",\n                \"redshift\": \"SHA2(x, 256)\",\n                \"trino\": \"SHA256(x)\",\n                \"duckdb\": \"SHA256(x)\",\n                \"snowflake\": \"SHA2(x, 256)\",\n            },\n        )\n        self.validate_all(\n            \"HASH_SHA512(x)\",\n            read={\n                \"clickhouse\": \"SHA512(x)\",\n                \"presto\": \"SHA512(x)\",\n                \"trino\": \"SHA512(x)\",\n            },\n            write={\n                \"exasol\": \"HASH_SHA512(x)\",\n                \"clickhouse\": \"SHA512(x)\",\n                \"bigquery\": \"SHA512(x)\",\n                \"spark2\": \"SHA2(x, 512)\",\n                \"presto\": \"SHA512(x)\",\n                \"trino\": \"SHA512(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT NULLIFZERO(1) NIZ1\",\n            write={\n                \"exasol\": \"SELECT IF 1 = 0 THEN NULL ELSE 1 ENDIF AS NIZ1\",\n                \"snowflake\": \"SELECT IFF(1 = 0, NULL, 1) AS NIZ1\",\n                \"sqlite\": \"SELECT IIF(1 = 0, NULL, 1) AS NIZ1\",\n                \"presto\": \"SELECT IF(1 = 0, NULL, 1) AS NIZ1\",\n                \"spark\": \"SELECT IF(1 = 0, NULL, 1) AS NIZ1\",\n                \"hive\": \"SELECT IF(1 = 0, NULL, 1) AS NIZ1\",\n                \"duckdb\": \"SELECT CASE WHEN 1 = 0 THEN NULL ELSE 1 END AS NIZ1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ZEROIFNULL(NULL) NIZ1\",\n            write={\n                \"exasol\": \"SELECT IF NULL IS NULL THEN 0 ELSE NULL ENDIF AS NIZ1\",\n                \"snowflake\": \"SELECT IFF(NULL IS NULL, 0, NULL) AS NIZ1\",\n                \"sqlite\": \"SELECT IIF(NULL IS NULL, 0, NULL) AS NIZ1\",\n                \"presto\": \"SELECT IF(NULL IS NULL, 0, NULL) AS NIZ1\",\n                \"spark\": \"SELECT IF(NULL IS NULL, 0, NULL) AS NIZ1\",\n                \"hive\": \"SELECT IF(NULL IS NULL, 0, NULL) AS NIZ1\",\n                \"duckdb\": \"SELECT CASE WHEN NULL IS NULL THEN 0 ELSE NULL END AS NIZ1\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT name, age, IF age < 18 THEN 'underaged' ELSE 'adult' ENDIF AS LEGALITY FROM persons\"\n        )\n\n        self.validate_identity(\"SELECT HASHTYPE_MD5(a, b, c, d)\")\n\n    def test_odbc_date_literals(self):\n        self.validate_identity(\"SELECT {d'2024-01-01'}\", \"SELECT TO_DATE('2024-01-01')\")\n        self.validate_identity(\n            \"SELECT {ts'2024-01-01 12:00:00'}\",\n            \"SELECT TO_TIMESTAMP('2024-01-01 12:00:00')\",\n        )\n\n    def test_local_prefix_for_alias(self):\n        self.validate_identity(\n            'SELECT ID FROM local WHERE \"LOCAL\".ID IS NULL',\n            'SELECT ID FROM \"LOCAL\" WHERE \"LOCAL\".ID IS NULL',\n        )\n        self.validate_identity(\n            'SELECT YEAR(a_date) AS \"a_year\" FROM MY_SUMMARY_TABLE GROUP BY LOCAL.\"a_year\"',\n        )\n        self.validate_identity(\n            'SELECT a_year AS a_year FROM \"LOCAL\" GROUP BY \"LOCAL\".a_year',\n        )\n\n        test_cases = [\n            (\n                \"GROUP BY alias\",\n                \"SELECT YEAR(a_date) AS a_year FROM my_table GROUP BY LOCAL.a_year\",\n                \"SELECT YEAR(a_date) AS a_year FROM my_table GROUP BY a_year\",\n            ),\n            (\n                \"HAVING alias\",\n                \"SELECT SUM(amount) AS total FROM my_table HAVING LOCAL.total > 10000\",\n                \"SELECT SUM(amount) AS total FROM my_table HAVING total > 10000\",\n            ),\n            (\n                \"WHERE alias\",\n                \"SELECT YEAR(a_date) AS a_year FROM my_table WHERE LOCAL.a_year > 2020\",\n                \"SELECT YEAR(a_date) AS a_year FROM my_table WHERE a_year > 2020\",\n            ),\n            (\n                \"Multiple aliases\",\n                \"SELECT YEAR(a_date) AS a_year, MONTH(a_date) AS a_month FROM my_table WHERE LOCAL.a_year > 2020 AND LOCAL.a_month < 6\",\n                \"SELECT YEAR(a_date) AS a_year, MONTH(a_date) AS a_month FROM my_table WHERE a_year > 2020 AND a_month < 6\",\n            ),\n            (\n                \"Select list aliases\",\n                \"SELECT YR AS THE_YEAR, ID AS YR, LOCAL.THE_YEAR + 1 AS NEXT_YEAR FROM my_table\",\n                \"SELECT YR AS THE_YEAR, ID AS YR, THE_YEAR + 1 AS NEXT_YEAR FROM my_table\",\n            ),\n            (\n                \"Select list aliases without Local keyword\",\n                \"SELECT YEAR(CURRENT_DATE) AS current_year, LOCAL.current_year + 1 AS next_year\",\n                \"SELECT YEAR(CURRENT_DATE) AS current_year, current_year + 1 AS next_year\",\n            ),\n        ]\n        for title, exasol_sql, dbx_sql in test_cases:\n            with self.subTest(clause=title):\n                self.validate_all(\n                    exasol_sql,\n                    write={\"exasol\": exasol_sql, \"databricks\": dbx_sql},\n                )\n\n    def test_regexp_like(self):\n        # Exasol uses binary predicate syntax: col REGEXP_LIKE pattern\n        self.validate_identity(\"SELECT x REGEXP_LIKE '.*pattern.*'\")\n\n        # Cross-dialect: partial match semantics from other dialects get .* wrapping\n        self.validate_all(\n            \"SELECT a REGEXP_LIKE '.*x.*'\",\n            read={\n                \"hive\": \"SELECT a RLIKE 'x'\",\n                \"presto\": \"SELECT REGEXP_LIKE(a, 'x')\",\n            },\n            write={\n                \"exasol\": \"SELECT a REGEXP_LIKE '.*x.*'\",\n                \"hive\": \"SELECT a RLIKE '.*x.*'\",\n                \"presto\": \"SELECT REGEXP_LIKE(a, '.*x.*')\",\n            },\n        )\n\n    def test_json(self):\n        self.validate_identity(\"\"\"SELECT JSON_VALUE('{\"d\":\"a\"}', '$.d' NULL ON ERROR) AS x\"\"\")\n        self.validate_all(\n            \"\"\"SELECT JSON_VALUE('{\"d\":\"a\"}', '$.d' NULL ON ERROR) AS x\"\"\",\n            write={\n                \"exasol\": \"\"\"SELECT JSON_VALUE('{\"d\":\"a\"}', '$.d' NULL ON ERROR) AS x\"\"\",\n                \"trino\": \"\"\"SELECT JSON_VALUE('{\"d\":\"a\"}', '$.d' NULL ON ERROR) AS x\"\"\",\n            },\n        )\n        self.validate_identity(\n            \"\"\"SELECT JSON_EXTRACT('{\"firstname\" : \"Ann\", \"surname\" : \"Smith\", \"age\" : 29}', '$.firstname', '$.surname', '$.age') EMITS (firstname VARCHAR(100), surname VARCHAR(100), age INT)\"\"\"\n        )\n\n    def test_group_by_all(self):\n        self.validate_all(\n            \"SELECT id, city, COUNT(*) FROM dealer GROUP BY ALL\",\n            write={\n                \"exasol\": \"SELECT id, city, COUNT(*) FROM dealer GROUP BY 1, 2\",\n                \"databricks\": \"SELECT id, city, COUNT(*) FROM dealer GROUP BY ALL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT car_model, COUNT(DISTINCT city) FROM dealer GROUP BY ALL\",\n            write={\n                \"exasol\": \"SELECT car_model, COUNT(DISTINCT city) FROM dealer GROUP BY 1\",\n                \"databricks\": \"SELECT car_model, COUNT(DISTINCT city) FROM dealer GROUP BY ALL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT car_model, city FROM dealer GROUP BY ALL\",\n            write={\n                \"exasol\": \"SELECT car_model, city FROM dealer GROUP BY 1, 2\",\n                \"databricks\": \"SELECT car_model, city FROM dealer GROUP BY ALL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT COUNT(*) FROM dealer GROUP BY ALL\",\n            write={\n                \"exasol\": \"SELECT COUNT(*) FROM dealer\",\n                \"databricks\": \"SELECT COUNT(*) FROM dealer GROUP BY ALL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UPPER(city), COUNT(*) FROM dealer GROUP BY ALL\",\n            write={\n                \"exasol\": \"SELECT UPPER(city), COUNT(*) FROM dealer GROUP BY 1\",\n                \"databricks\": \"SELECT UPPER(city), COUNT(*) FROM dealer GROUP BY ALL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT city AS c, COUNT(*) + 1 FROM dealer GROUP BY ALL\",\n            write={\n                \"exasol\": \"SELECT city AS c, COUNT(*) + 1 FROM dealer GROUP BY 1\",\n                \"databricks\": \"SELECT city AS c, COUNT(*) + 1 FROM dealer GROUP BY ALL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT city, COUNT(*) OVER () FROM dealer GROUP BY ALL\",\n            write={\n                \"exasol\": \"SELECT city, COUNT(*) OVER () FROM dealer GROUP BY 1\",\n                \"databricks\": \"SELECT city, COUNT(*) OVER () FROM dealer GROUP BY ALL\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t GROUP BY ALL\",\n            write={\n                \"exasol\": \"SELECT DISTINCT * FROM t\",\n                \"databricks\": \"SELECT * FROM t GROUP BY ALL\",\n            },\n        )\n        with self.assertRaises(UnsupportedError):\n            transpile(\n                \"SELECT *, COUNT(*) FROM t GROUP BY ALL\",\n                write=\"exasol\",\n                unsupported_level=ErrorLevel.RAISE,\n            )\n"
  },
  {
    "path": "tests/dialects/test_fabric.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestFabric(Validator):\n    dialect = \"fabric\"\n    maxDiff = None\n\n    def test_type_mappings(self):\n        \"\"\"Test that types are correctly mapped to their alternatives\"\"\"\n        self.validate_identity(\"CAST(x AS BOOLEAN)\", \"CAST(x AS BIT)\")\n        self.validate_identity(\"CAST(x AS DATE)\", \"CAST(x AS DATE)\")\n        self.validate_identity(\"CAST(x AS DATETIME)\", \"CAST(x AS DATETIME2(6))\")\n        self.validate_identity(\"CAST(x AS DECIMAL)\", \"CAST(x AS DECIMAL)\")\n        self.validate_identity(\"CAST(x AS DOUBLE)\", \"CAST(x AS FLOAT)\")\n        self.validate_identity(\"CAST(x AS IMAGE)\", \"CAST(x AS VARBINARY)\")\n        self.validate_identity(\"CAST(x AS INT)\", \"CAST(x AS INT)\")\n        self.validate_identity(\"CAST(x AS JSON)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS MONEY)\", \"CAST(x AS DECIMAL)\")\n        self.validate_identity(\"CAST(x AS NCHAR)\", \"CAST(x AS CHAR)\")\n        self.validate_identity(\"CAST(x AS NVARCHAR)\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"CAST(x AS ROWVERSION)\", \"CAST(x AS ROWVERSION)\")\n        self.validate_identity(\"CAST(x AS SMALLDATETIME)\", \"CAST(x AS DATETIME2(6))\")\n        self.validate_identity(\"CAST(x AS SMALLMONEY)\", \"CAST(x AS DECIMAL)\")\n        self.validate_identity(\"CAST(x AS TEXT)\", \"CAST(x AS VARCHAR(MAX))\")\n        self.validate_identity(\"CAST(x AS TIMESTAMP)\", \"CAST(x AS DATETIME2(6))\")\n        self.validate_identity(\"CAST(x AS TIMESTAMPNTZ)\", \"CAST(x AS DATETIME2(6))\")\n        self.validate_identity(\"CAST(x AS TINYINT)\", \"CAST(x AS SMALLINT)\")\n        self.validate_identity(\"CAST(x AS UTINYINT)\", \"CAST(x AS SMALLINT)\")\n        self.validate_identity(\"CAST(x AS UUID)\", \"CAST(x AS UNIQUEIDENTIFIER)\")\n        self.validate_identity(\"CAST(x AS VARIANT)\", \"CAST(x AS SQL_VARIANT)\")\n        self.validate_identity(\"CAST(x AS XML)\", \"CAST(x AS VARCHAR)\")\n\n    def test_precision_capping(self):\n        \"\"\"Test that TIME, DATETIME2 & DATETIMEOFFSET precision is capped at 6 digits\"\"\"\n        # Default precision should be 6\n        self.validate_identity(\"CAST(x AS TIME)\", \"CAST(x AS TIME(6))\")\n        self.validate_identity(\"CAST(x AS DATETIME2)\", \"CAST(x AS DATETIME2(6))\")\n\n        # Precision <= 6 should be preserved\n        self.validate_identity(\"CAST(x AS TIME(3))\", \"CAST(x AS TIME(3))\")\n        self.validate_identity(\"CAST(x AS DATETIME2(3))\", \"CAST(x AS DATETIME2(3))\")\n\n        self.validate_identity(\"CAST(x AS TIME(6))\", \"CAST(x AS TIME(6))\")\n        self.validate_identity(\"CAST(x AS DATETIME2(6))\", \"CAST(x AS DATETIME2(6))\")\n\n        # Precision > 6 should be capped at 6\n        self.validate_identity(\"CAST(x AS TIME(7))\", \"CAST(x AS TIME(6))\")\n        self.validate_identity(\"CAST(x AS DATETIME2(7))\", \"CAST(x AS DATETIME2(6))\")\n\n        self.validate_identity(\"CAST(x AS TIME(9))\", \"CAST(x AS TIME(6))\")\n        self.validate_identity(\"CAST(x AS DATETIME2(9))\", \"CAST(x AS DATETIME2(6))\")\n\n    def test_timestamptz_without_at_time_zone(self):\n        # TIMESTAMPTZ should be cast to TIMESTAMP when not in an AT TIME ZONE\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ)\",\n            \"CAST(x AS DATETIME2(6))\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ(3))\",\n            \"CAST(x AS DATETIME2(3))\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ(6))\",\n            \"CAST(x AS DATETIME2(6))\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ(9))\",\n            \"CAST(x AS DATETIME2(6))\",\n        )\n\n    def test_timestamptz_with_at_time_zone(self):\n        # TIMESTAMPTZ should be DATETIMEOFFSET when in an AT TIME ZONE expression and then cast to TIMESTAMP\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ) AT TIME ZONE 'Pacific Standard Time'\",\n            \"CAST(CAST(x AS DATETIMEOFFSET(6)) AT TIME ZONE 'Pacific Standard Time' AS DATETIME2(6))\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ(3)) AT TIME ZONE 'Pacific Standard Time'\",\n            \"CAST(CAST(x AS DATETIMEOFFSET(3)) AT TIME ZONE 'Pacific Standard Time' AS DATETIME2(3))\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ(6)) AT TIME ZONE 'Pacific Standard Time'\",\n            \"CAST(CAST(x AS DATETIMEOFFSET(6)) AT TIME ZONE 'Pacific Standard Time' AS DATETIME2(6))\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ(9)) AT TIME ZONE 'Pacific Standard Time'\",\n            \"CAST(CAST(x AS DATETIMEOFFSET(6)) AT TIME ZONE 'Pacific Standard Time' AS DATETIME2(6))\",\n        )\n\n    def test_unix_to_time(self):\n        \"\"\"Test UnixToTime transformation to DATEADD with microseconds\"\"\"\n\n        self.validate_identity(\n            \"UNIX_TO_TIME(column)\",\n            \"DATEADD(MICROSECONDS, CAST(ROUND(column * 1e6, 0) AS BIGINT), CAST('1970-01-01' AS DATETIME2(6)))\",\n        )\n\n    def test_varchar_precision_inference(self):\n        # Test VARCHAR without precision conversion to VARCHAR(1)\n        self.validate_identity(\n            \"CREATE TABLE t (col VARCHAR)\",\n            \"CREATE TABLE t (col VARCHAR(1))\",\n        )\n\n        # Test VARCHAR with existing precision should remain unchanged\n        self.validate_identity(\"CREATE TABLE t (col VARCHAR(50))\")\n\n        # Test CHAR without precision conversion to CHAR(1)\n        self.validate_identity(\n            \"CREATE TABLE t (col CHAR)\",\n            \"CREATE TABLE t (col CHAR(1))\",\n        )\n\n        # Test CHAR with existing precision should remain unchanged\n        self.validate_identity(\"CREATE TABLE t (col CHAR(10))\")\n\n        # Test cross-dialect conversion: non-TSQL VARCHAR -> TSQL VARCHAR(MAX)\n        self.validate_all(\n            \"CREATE TABLE t (col VARCHAR(MAX))\",\n            read={\n                \"postgres\": \"CREATE TABLE t (col VARCHAR)\",\n                \"tsql\": \"CREATE TABLE t (col VARCHAR(MAX))\",\n            },\n        )\n\n        # Test cross-dialect conversion: non-TSQL CHAR -> TSQL CHAR(MAX)\n        self.validate_all(\n            \"CREATE TABLE t (col CHAR(MAX))\",\n            read={\n                \"postgres\": \"CREATE TABLE t (col CHAR)\",\n                \"tsql\": \"CREATE TABLE t (col CHAR(MAX))\",\n            },\n        )\n"
  },
  {
    "path": "tests/dialects/test_hive.py",
    "content": "from tests.dialects.test_dialect import Validator\nfrom sqlglot import exp\n\n\nclass TestHive(Validator):\n    dialect = \"hive\"\n\n    def test_bits(self):\n        self.validate_all(\n            \"x & 1\",\n            read={\n                \"duckdb\": \"x & 1\",\n                \"presto\": \"BITWISE_AND(x, 1)\",\n                \"spark\": \"x & 1\",\n            },\n            write={\n                \"duckdb\": \"x & 1\",\n                \"hive\": \"x & 1\",\n                \"presto\": \"BITWISE_AND(x, 1)\",\n                \"spark\": \"x & 1\",\n            },\n        )\n        self.validate_all(\n            \"x & 1 > 0\",\n            read={\n                \"duckdb\": \"x & 1 > 0\",\n                \"presto\": \"BITWISE_AND(x, 1) > 0\",\n                \"spark\": \"x & 1 > 0\",\n            },\n            write={\n                \"duckdb\": \"x & 1 > 0\",\n                \"presto\": \"BITWISE_AND(x, 1) > 0\",\n                \"hive\": \"x & 1 > 0\",\n                \"spark\": \"x & 1 > 0\",\n            },\n        )\n        self.validate_all(\n            \"~x\",\n            read={\n                \"duckdb\": \"~x\",\n                \"presto\": \"BITWISE_NOT(x)\",\n                \"spark\": \"~x\",\n            },\n            write={\n                \"duckdb\": \"~x\",\n                \"hive\": \"~x\",\n                \"presto\": \"BITWISE_NOT(x)\",\n                \"spark\": \"~x\",\n            },\n        )\n        self.validate_all(\n            \"x | 1\",\n            read={\n                \"duckdb\": \"x | 1\",\n                \"presto\": \"BITWISE_OR(x, 1)\",\n                \"spark\": \"x | 1\",\n            },\n            write={\n                \"duckdb\": \"x | 1\",\n                \"hive\": \"x | 1\",\n                \"presto\": \"BITWISE_OR(x, 1)\",\n                \"spark\": \"x | 1\",\n            },\n        )\n        self.validate_all(\n            \"x << 1\",\n            read={\n                \"spark\": \"SHIFTLEFT(x, 1)\",\n            },\n            write={\n                \"duckdb\": \"x << 1\",\n                \"presto\": \"BITWISE_ARITHMETIC_SHIFT_LEFT(x, 1)\",\n                \"hive\": \"x << 1\",\n                \"spark\": \"SHIFTLEFT(x, 1)\",\n            },\n        )\n        self.validate_all(\n            \"x >> 1\",\n            read={\n                \"spark\": \"SHIFTRIGHT(x, 1)\",\n            },\n            write={\n                \"duckdb\": \"x >> 1\",\n                \"presto\": \"BITWISE_ARITHMETIC_SHIFT_RIGHT(x, 1)\",\n                \"hive\": \"x >> 1\",\n                \"spark\": \"SHIFTRIGHT(x, 1)\",\n            },\n        )\n\n    def test_cast(self):\n        self.validate_all(\n            \"1s\",\n            write={\n                \"duckdb\": \"TRY_CAST(1 AS SMALLINT)\",\n                \"presto\": \"TRY_CAST(1 AS SMALLINT)\",\n                \"hive\": \"CAST(1 AS SMALLINT)\",\n                \"spark\": \"CAST(1 AS SMALLINT)\",\n            },\n        )\n        self.validate_all(\n            \"1S\",\n            write={\n                \"duckdb\": \"TRY_CAST(1 AS SMALLINT)\",\n                \"presto\": \"TRY_CAST(1 AS SMALLINT)\",\n                \"hive\": \"CAST(1 AS SMALLINT)\",\n                \"spark\": \"CAST(1 AS SMALLINT)\",\n            },\n        )\n        self.validate_all(\n            \"1Y\",\n            write={\n                \"duckdb\": \"TRY_CAST(1 AS TINYINT)\",\n                \"presto\": \"TRY_CAST(1 AS TINYINT)\",\n                \"hive\": \"CAST(1 AS TINYINT)\",\n                \"spark\": \"CAST(1 AS TINYINT)\",\n            },\n        )\n        self.validate_all(\n            \"1L\",\n            write={\n                \"duckdb\": \"TRY_CAST(1 AS BIGINT)\",\n                \"presto\": \"TRY_CAST(1 AS BIGINT)\",\n                \"hive\": \"CAST(1 AS BIGINT)\",\n                \"spark\": \"CAST(1 AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"1.0bd\",\n            write={\n                \"duckdb\": \"TRY_CAST(1.0 AS DECIMAL)\",\n                \"presto\": \"TRY_CAST(1.0 AS DECIMAL)\",\n                \"hive\": \"CAST(1.0 AS DECIMAL)\",\n                \"spark\": \"CAST(1.0 AS DECIMAL)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(1 AS INT)\",\n            read={\n                \"presto\": \"TRY_CAST(1 AS INT)\",\n            },\n            write={\n                \"duckdb\": \"TRY_CAST(1 AS INT)\",\n                \"presto\": \"TRY_CAST(1 AS INTEGER)\",\n                \"hive\": \"CAST(1 AS INT)\",\n                \"spark\": \"CAST(1 AS INT)\",\n            },\n        )\n\n    def test_ddl(self):\n        self.validate_all(\n            \"CREATE TABLE x (w STRING) PARTITIONED BY (y INT, z INT)\",\n            write={\n                \"duckdb\": \"CREATE TABLE x (w TEXT)\",  # Partition columns should exist in table\n                \"presto\": \"CREATE TABLE x (w VARCHAR, y INTEGER, z INTEGER) WITH (PARTITIONED_BY=ARRAY['y', 'z'])\",\n                \"hive\": \"CREATE TABLE x (w STRING) PARTITIONED BY (y INT, z INT)\",\n                \"spark\": \"CREATE TABLE x (w STRING, y INT, z INT) PARTITIONED BY (y, z)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE test STORED AS parquet TBLPROPERTIES ('x'='1', 'Z'='2') AS SELECT 1\",\n            write={\n                \"duckdb\": \"CREATE TABLE test AS SELECT 1\",\n                \"presto\": \"CREATE TABLE test WITH (format='parquet', x='1', Z='2') AS SELECT 1\",\n                \"hive\": \"CREATE TABLE test STORED AS PARQUET TBLPROPERTIES ('x'='1', 'Z'='2') AS SELECT 1\",\n                \"spark\": \"CREATE TABLE test STORED AS PARQUET TBLPROPERTIES ('x'='1', 'Z'='2') AS SELECT 1\",\n            },\n        )\n\n        self.validate_all(\n            \"CREATE TABLE test STORED AS INPUTFORMAT 'foo1' OUTPUTFORMAT 'foo2'\",\n            write={\n                \"hive\": \"CREATE TABLE test STORED AS INPUTFORMAT 'foo1' OUTPUTFORMAT 'foo2'\",\n                \"spark\": \"CREATE TABLE test STORED AS INPUTFORMAT 'foo1' OUTPUTFORMAT 'foo2'\",\n                \"databricks\": \"CREATE TABLE test STORED AS INPUTFORMAT 'foo1' OUTPUTFORMAT 'foo2'\",\n            },\n        )\n\n        self.validate_identity(\"ALTER TABLE x PARTITION(y = z) ADD COLUMN a VARCHAR(10)\")\n        self.validate_identity(\n            \"ALTER TABLE x CHANGE a a VARCHAR(10)\",\n            \"ALTER TABLE x CHANGE COLUMN a a VARCHAR(10)\",\n        )\n\n        self.validate_all(\n            \"ALTER TABLE x CHANGE COLUMN a a VARCHAR(10)\",\n            write={\n                \"hive\": \"ALTER TABLE x CHANGE COLUMN a a VARCHAR(10)\",\n                \"spark\": \"ALTER TABLE x ALTER COLUMN a TYPE VARCHAR(10)\",\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE x CHANGE COLUMN a a VARCHAR(10) COMMENT 'comment'\",\n            write={\n                \"hive\": \"ALTER TABLE x CHANGE COLUMN a a VARCHAR(10) COMMENT 'comment'\",\n                \"spark\": \"ALTER TABLE x ALTER COLUMN a COMMENT 'comment'\",\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE x CHANGE COLUMN a b VARCHAR(10)\",\n            write={\n                \"hive\": \"ALTER TABLE x CHANGE COLUMN a b VARCHAR(10)\",\n                \"spark\": \"ALTER TABLE x RENAME COLUMN a TO b\",\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE x CHANGE COLUMN a a VARCHAR(10) CASCADE\",\n            write={\n                \"hive\": \"ALTER TABLE x CHANGE COLUMN a a VARCHAR(10) CASCADE\",\n                \"spark\": \"ALTER TABLE x ALTER COLUMN a TYPE VARCHAR(10)\",\n            },\n        )\n\n        self.validate_identity(\"ALTER TABLE X ADD COLUMNS (y INT, z STRING)\")\n        self.validate_identity(\"ALTER TABLE X ADD COLUMNS (y INT, z STRING) CASCADE\")\n\n        self.validate_identity(\n            \"\"\"CREATE EXTERNAL TABLE x (y INT) ROW FORMAT SERDE 'serde' ROW FORMAT DELIMITED FIELDS TERMINATED BY '1' WITH SERDEPROPERTIES ('input.regex'='')\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"CREATE EXTERNAL TABLE `my_table` (`a7` ARRAY<DATE>) ROW FORMAT SERDE 'a' STORED AS INPUTFORMAT 'b' OUTPUTFORMAT 'c' LOCATION 'd' TBLPROPERTIES ('e'='f')\"\"\"\n        )\n        self.validate_identity(\"CREATE EXTERNAL TABLE X (y INT) STORED BY 'x'\")\n        self.validate_identity(\"ALTER VIEW v1 AS SELECT x, UPPER(s) AS s FROM t2\")\n        self.validate_identity(\"ALTER VIEW v1 (c1, c2) AS SELECT x, UPPER(s) AS s FROM t2\")\n        self.validate_identity(\n            \"ALTER VIEW v7 (c1 COMMENT 'Comment for c1', c2) AS SELECT t1.c1, t1.c2 FROM t1\"\n        )\n        self.validate_identity(\"ALTER VIEW db1.v1 RENAME TO db2.v2\")\n        self.validate_identity(\"ALTER VIEW v1 SET TBLPROPERTIES ('tblp1'='1', 'tblp2'='2')\")\n        self.validate_identity(\n            \"ALTER VIEW v1 UNSET TBLPROPERTIES ('tblp1', 'tblp2')\", check_command_warning=True\n        )\n        self.validate_identity(\"CREATE TABLE foo (col STRUCT<struct_col_a: VARCHAR((50))>)\")\n\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a struct<struct_col_a:int, struct_col_b:string>)\",\n            write={\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a STRUCT(struct_col_a INT, struct_col_b TEXT))\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b VARCHAR))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRING>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRING>)\",\n            },\n        )\n\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a struct<struct_col_a:int, struct_col_b:struct<nested_col_a:string, nested_col_b:string>>)\",\n            write={\n                \"bigquery\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a INT64, struct_col_b STRUCT<nested_col_a STRING, nested_col_b STRING>>)\",\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a STRUCT(struct_col_a INT, struct_col_b STRUCT(nested_col_a TEXT, nested_col_b TEXT)))\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b ROW(nested_col_a VARCHAR, nested_col_b VARCHAR)))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRUCT<nested_col_a: STRING, nested_col_b: STRING>>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRUCT<nested_col_a: STRING, nested_col_b: STRING>>)\",\n            },\n        )\n\n        self.validate_all(\n            \"ALTER TABLE db.example_table ADD PARTITION(col_a = 'a') LOCATION 'b'\",\n            read={\n                \"spark2\": \"ALTER TABLE db.example_table ADD PARTITION(col_a = 'a') LOCATION 'b'\",\n                \"spark\": \"ALTER TABLE db.example_table ADD PARTITION(col_a = 'a') LOCATION 'b'\",\n                \"databricks\": \"ALTER TABLE db.example_table ADD PARTITION(col_a = 'a') LOCATION 'b'\",\n            },\n            write={\n                \"hive\": \"ALTER TABLE db.example_table ADD PARTITION(col_a = 'a') LOCATION 'b'\",\n                \"spark2\": \"ALTER TABLE db.example_table ADD PARTITION(col_a = 'a') LOCATION 'b'\",\n                \"spark\": \"ALTER TABLE db.example_table ADD PARTITION(col_a = 'a') LOCATION 'b'\",\n                \"databricks\": \"ALTER TABLE db.example_table ADD PARTITION(col_a = 'a') LOCATION 'b'\",\n            },\n        )\n\n    def test_lateral_view(self):\n        self.validate_all(\n            \"SELECT a, b FROM x LATERAL VIEW EXPLODE(y) t AS a LATERAL VIEW EXPLODE(z) u AS b\",\n            write={\n                \"presto\": \"SELECT a, b FROM x CROSS JOIN UNNEST(y) AS t(a) CROSS JOIN UNNEST(z) AS u(b)\",\n                \"duckdb\": \"SELECT a, b FROM x CROSS JOIN UNNEST(y) AS t(a) CROSS JOIN UNNEST(z) AS u(b)\",\n                \"hive\": \"SELECT a, b FROM x LATERAL VIEW EXPLODE(y) t AS a LATERAL VIEW EXPLODE(z) u AS b\",\n                \"spark\": \"SELECT a, b FROM x LATERAL VIEW EXPLODE(y) t AS a LATERAL VIEW EXPLODE(z) u AS b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM x LATERAL VIEW EXPLODE(y) t AS a\",\n            write={\n                \"presto\": \"SELECT a FROM x CROSS JOIN UNNEST(y) AS t(a)\",\n                \"duckdb\": \"SELECT a FROM x CROSS JOIN UNNEST(y) AS t(a)\",\n                \"hive\": \"SELECT a FROM x LATERAL VIEW EXPLODE(y) t AS a\",\n                \"spark\": \"SELECT a FROM x LATERAL VIEW EXPLODE(y) t AS a\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM x LATERAL VIEW POSEXPLODE(y) t AS pos, col\",\n            write={\n                \"presto\": \"SELECT a FROM x CROSS JOIN LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))\",\n                \"trino\": \"SELECT a FROM x CROSS JOIN LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))\",\n                \"duckdb\": \"SELECT a FROM x CROSS JOIN LATERAL (SELECT pos - 1 AS pos, col FROM UNNEST(y) WITH ORDINALITY AS t(col, pos))\",\n                \"hive\": \"SELECT a FROM x LATERAL VIEW POSEXPLODE(y) t AS pos, col\",\n                \"spark\": \"SELECT a FROM x LATERAL VIEW POSEXPLODE(y) t AS pos, col\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x LATERAL VIEW POSEXPLODE(MAP(col, 'val')) t AS pos, key, value\",\n            write={\n                \"presto\": \"SELECT * FROM x CROSS JOIN LATERAL (SELECT pos - 1 AS pos, key, value FROM UNNEST(MAP(ARRAY[col], ARRAY['val'])) WITH ORDINALITY AS t(key, value, pos))\",\n                \"trino\": \"SELECT * FROM x CROSS JOIN LATERAL (SELECT pos - 1 AS pos, key, value FROM UNNEST(MAP(ARRAY[col], ARRAY['val'])) WITH ORDINALITY AS t(key, value, pos))\",\n                \"hive\": \"SELECT * FROM x LATERAL VIEW POSEXPLODE(MAP(col, 'val')) t AS pos, key, value\",\n                \"spark\": \"SELECT * FROM x LATERAL VIEW POSEXPLODE(MAP(col, 'val')) t AS pos, key, value\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM x LATERAL VIEW EXPLODE(ARRAY(y)) t AS a\",\n            write={\n                \"presto\": \"SELECT a FROM x CROSS JOIN UNNEST(ARRAY[y]) AS t(a)\",\n                \"duckdb\": \"SELECT a FROM x CROSS JOIN UNNEST([y]) AS t(a)\",\n                \"hive\": \"SELECT a FROM x LATERAL VIEW EXPLODE(ARRAY(y)) t AS a\",\n                \"spark\": \"SELECT a FROM x LATERAL VIEW EXPLODE(ARRAY(y)) t AS a\",\n            },\n        )\n\n    def test_quotes(self):\n        self.validate_all(\n            \"'\\\\''\",\n            write={\n                \"duckdb\": \"''''\",\n                \"presto\": \"''''\",\n                \"hive\": \"'\\\\''\",\n                \"spark\": \"'\\\\''\",\n            },\n        )\n        self.validate_all(\n            \"'\\\"x\\\"'\",\n            write={\n                \"duckdb\": \"'\\\"x\\\"'\",\n                \"presto\": \"'\\\"x\\\"'\",\n                \"hive\": \"'\\\"x\\\"'\",\n                \"spark\": \"'\\\"x\\\"'\",\n            },\n        )\n        self.validate_all(\n            \"\\\"'x'\\\"\",\n            write={\n                \"duckdb\": \"'''x'''\",\n                \"presto\": \"'''x'''\",\n                \"hive\": \"'\\\\'x\\\\''\",\n                \"spark\": \"'\\\\'x\\\\''\",\n            },\n        )\n        self.validate_all(\n            \"'\\\\\\\\\\\\\\\\a'\",\n            read={\n                \"drill\": \"'\\\\\\\\\\\\\\\\a'\",\n                \"duckdb\": \"'\\\\\\\\a'\",\n                \"presto\": \"'\\\\\\\\a'\",\n            },\n            write={\n                \"drill\": \"'\\\\\\\\\\\\\\\\a'\",\n                \"duckdb\": \"'\\\\\\\\a'\",\n                \"hive\": \"'\\\\\\\\\\\\\\\\a'\",\n                \"presto\": \"'\\\\\\\\a'\",\n                \"spark\": \"'\\\\\\\\\\\\\\\\a'\",\n            },\n        )\n\n    def test_regex(self):\n        self.validate_all(\n            \"a RLIKE 'x'\",\n            write={\n                \"duckdb\": \"REGEXP_MATCHES(a, 'x')\",\n                \"exasol\": \"a REGEXP_LIKE '.*x.*'\",\n                \"presto\": \"REGEXP_LIKE(a, 'x')\",\n                \"hive\": \"a RLIKE 'x'\",\n                \"spark\": \"a RLIKE 'x'\",\n            },\n        )\n\n        self.validate_all(\n            \"a REGEXP 'x'\",\n            write={\n                \"duckdb\": \"REGEXP_MATCHES(a, 'x')\",\n                \"exasol\": \"a REGEXP_LIKE '.*x.*'\",\n                \"presto\": \"REGEXP_LIKE(a, 'x')\",\n                \"hive\": \"a RLIKE 'x'\",\n                \"spark\": \"a RLIKE 'x'\",\n            },\n        )\n\n    def test_time(self):\n        self.validate_all(\n            \"(UNIX_TIMESTAMP(y) - UNIX_TIMESTAMP(x)) * 1000\",\n            read={\n                \"presto\": \"DATE_DIFF('millisecond', x, y)\",\n            },\n        )\n        self.validate_all(\n            \"UNIX_TIMESTAMP(y) - UNIX_TIMESTAMP(x)\",\n            read={\n                \"presto\": \"DATE_DIFF('second', x, y)\",\n            },\n        )\n        self.validate_all(\n            \"(UNIX_TIMESTAMP(y) - UNIX_TIMESTAMP(x)) / 60\",\n            read={\n                \"presto\": \"DATE_DIFF('minute', x, y)\",\n            },\n        )\n        self.validate_all(\n            \"(UNIX_TIMESTAMP(y) - UNIX_TIMESTAMP(x)) / 3600\",\n            read={\n                \"presto\": \"DATE_DIFF('hour', x, y)\",\n            },\n        )\n        self.validate_all(\n            \"DATEDIFF(a, b)\",\n            write={\n                \"duckdb\": \"DATE_DIFF('DAY', CAST(b AS DATE), CAST(a AS DATE))\",\n                \"presto\": \"DATE_DIFF('DAY', CAST(CAST(b AS TIMESTAMP) AS DATE), CAST(CAST(a AS TIMESTAMP) AS DATE))\",\n                \"hive\": \"DATEDIFF(a, b)\",\n                \"spark\": \"DATEDIFF(a, b)\",\n                \"\": \"DATEDIFF(CAST(a AS DATE), CAST(b AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"\"\"from_unixtime(x, \"yyyy-MM-dd'T'HH\")\"\"\",\n            write={\n                \"duckdb\": \"STRFTIME(TO_TIMESTAMP(x), '%Y-%m-%d''T''%H')\",\n                \"presto\": \"DATE_FORMAT(FROM_UNIXTIME(x), '%Y-%m-%d''T''%H')\",\n                \"hive\": \"FROM_UNIXTIME(x, 'yyyy-MM-dd\\\\'T\\\\'HH')\",\n                \"spark\": \"FROM_UNIXTIME(x, 'yyyy-MM-dd\\\\'T\\\\'HH')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_FORMAT('2020-01-01', 'yyyy-MM-dd HH:mm:ss')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%F %T', CAST('2020-01-01' AS DATETIME))\",\n                \"duckdb\": \"STRFTIME(CAST('2020-01-01' AS TIMESTAMP), '%Y-%m-%d %H:%M:%S')\",\n                \"presto\": \"DATE_FORMAT(CAST('2020-01-01' AS TIMESTAMP), '%Y-%m-%d %T')\",\n                \"hive\": \"DATE_FORMAT('2020-01-01', 'yyyy-MM-dd HH:mm:ss')\",\n                \"spark\": \"DATE_FORMAT('2020-01-01', 'yyyy-MM-dd HH:mm:ss')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD('2020-01-01', 1)\",\n            write={\n                \"\": \"TS_OR_DS_ADD('2020-01-01', 1, DAY)\",\n                \"bigquery\": \"DATE_ADD(CAST(CAST('2020-01-01' AS DATETIME) AS DATE), INTERVAL 1 DAY)\",\n                \"duckdb\": \"CAST('2020-01-01' AS DATE) + INTERVAL 1 DAY\",\n                \"hive\": \"DATE_ADD('2020-01-01', 1)\",\n                \"presto\": \"DATE_ADD('DAY', 1, CAST(CAST('2020-01-01' AS TIMESTAMP) AS DATE))\",\n                \"redshift\": \"DATEADD(DAY, 1, '2020-01-01')\",\n                \"snowflake\": \"DATEADD(DAY, 1, CAST(CAST('2020-01-01' AS TIMESTAMP) AS DATE))\",\n                \"spark\": \"DATE_ADD('2020-01-01', 1)\",\n                \"tsql\": \"DATEADD(DAY, 1, CAST(CAST('2020-01-01' AS DATETIME2) AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"DATE_SUB('2020-01-01', 1)\",\n            write={\n                \"\": \"TS_OR_DS_ADD('2020-01-01', 1 * -1, DAY)\",\n                \"bigquery\": \"DATE_ADD(CAST(CAST('2020-01-01' AS DATETIME) AS DATE), INTERVAL (1 * -1) DAY)\",\n                \"duckdb\": \"CAST('2020-01-01' AS DATE) + INTERVAL (1 * -1) DAY\",\n                \"hive\": \"DATE_ADD('2020-01-01', 1 * -1)\",\n                \"presto\": \"DATE_ADD('DAY', 1 * -1, CAST(CAST('2020-01-01' AS TIMESTAMP) AS DATE))\",\n                \"redshift\": \"DATEADD(DAY, 1 * -1, '2020-01-01')\",\n                \"snowflake\": \"DATEADD(DAY, 1 * -1, CAST(CAST('2020-01-01' AS TIMESTAMP) AS DATE))\",\n                \"spark\": \"DATE_ADD('2020-01-01', 1 * -1)\",\n                \"tsql\": \"DATEADD(DAY, 1 * -1, CAST(CAST('2020-01-01' AS DATETIME2) AS DATE))\",\n            },\n        )\n        self.validate_all(\"DATE_ADD('2020-01-01', -1)\", read={\"\": \"DATE_SUB('2020-01-01', 1)\"})\n        self.validate_all(\"DATE_ADD(a, b * -1)\", read={\"\": \"DATE_SUB(a, b)\"})\n        self.validate_all(\n            \"ADD_MONTHS('2020-01-01', -2)\", read={\"\": \"DATE_SUB('2020-01-01', 2, month)\"}\n        )\n        self.validate_all(\n            \"DATEDIFF(TO_DATE(y), x)\",\n            write={\n                \"duckdb\": \"DATE_DIFF('DAY', CAST(x AS DATE), TRY_CAST(y AS DATE))\",\n                \"presto\": \"DATE_DIFF('DAY', CAST(CAST(x AS TIMESTAMP) AS DATE), CAST(CAST(CAST(CAST(y AS TIMESTAMP) AS DATE) AS TIMESTAMP) AS DATE))\",\n                \"hive\": \"DATEDIFF(TO_DATE(y), x)\",\n                \"spark\": \"DATEDIFF(TO_DATE(y), x)\",\n                \"\": \"DATEDIFF(TRY_CAST(y AS DATE), CAST(x AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"UNIX_TIMESTAMP(x)\",\n            write={\n                \"duckdb\": \"EPOCH(STRPTIME(x, '%Y-%m-%d %H:%M:%S'))\",\n                \"presto\": \"TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(x AS VARCHAR), '%Y-%m-%d %T')), PARSE_DATETIME(DATE_FORMAT(x, '%Y-%m-%d %T'), 'yyyy-MM-dd HH:mm:ss')))\",\n                \"hive\": \"UNIX_TIMESTAMP(x)\",\n                \"spark\": \"UNIX_TIMESTAMP(x)\",\n                \"\": \"STR_TO_UNIX(x, '%Y-%m-%d %H:%M:%S')\",\n            },\n        )\n\n        for unit in (\"DAY\", \"MONTH\", \"YEAR\"):\n            self.validate_all(\n                f\"{unit}(x)\",\n                write={\n                    \"duckdb\": f\"{unit}(CAST(x AS DATE))\",\n                    \"presto\": f\"{unit}(CAST(CAST(x AS TIMESTAMP) AS DATE))\",\n                    \"hive\": f\"{unit}(x)\",\n                    \"spark\": f\"{unit}(x)\",\n                },\n            )\n\n    def test_order_by(self):\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"duckdb\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname NULLS FIRST\",\n                \"presto\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname NULLS FIRST\",\n                \"hive\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n                \"spark\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            },\n        )\n\n    def test_hive(self):\n        self.validate_identity(\"TO_DATE(TO_DATE(x))\")\n        self.validate_identity(\"DAY(TO_DATE(x))\")\n        self.validate_identity(\"SELECT * FROM t WHERE col IN ('stream')\")\n        self.validate_identity(\"SET hiveconf:some_var = 5\", check_command_warning=True)\n        self.validate_identity(\"(VALUES (1 AS a, 2 AS b, 3))\")\n        self.validate_identity(\"SELECT * FROM my_table TIMESTAMP AS OF DATE_ADD(CURRENT_DATE, -1)\")\n        self.validate_identity(\"SELECT * FROM my_table VERSION AS OF DATE_ADD(CURRENT_DATE, -1)\")\n        self.validate_identity(\n            \"SELECT WEEKOFYEAR('2024-05-22'), DAYOFMONTH('2024-05-22'), DAYOFWEEK('2024-05-22')\"\n        )\n        self.validate_identity(\n            \"SELECT ROW() OVER (DISTRIBUTE BY x SORT BY y)\",\n            \"SELECT ROW() OVER (PARTITION BY x ORDER BY y)\",\n        )\n        self.validate_identity(\"SELECT transform\")\n        self.validate_identity(\"SELECT * FROM test DISTRIBUTE BY y SORT BY x DESC ORDER BY l\")\n        self.validate_identity(\n            \"SELECT * FROM test WHERE RAND() <= 0.1 DISTRIBUTE BY RAND() SORT BY RAND()\"\n        )\n        self.validate_identity(\"(SELECT 1 UNION SELECT 2) DISTRIBUTE BY z\")\n        self.validate_identity(\"(SELECT 1 UNION SELECT 2) DISTRIBUTE BY z SORT BY x\")\n        self.validate_identity(\"(SELECT 1 UNION SELECT 2) CLUSTER BY y DESC\")\n        self.validate_identity(\"SELECT * FROM test CLUSTER BY y\")\n\n        self.validate_identity(\"(SELECT 1 UNION SELECT 2) SORT BY z\")\n        self.validate_identity(\n            \"INSERT OVERWRITE TABLE zipcodes PARTITION(state = '0') VALUES (896, 'US', 'TAMPA', 33607)\"\n        )\n        self.validate_identity(\n            \"INSERT OVERWRITE TABLE zipcodes PARTITION(state = 0) VALUES (896, 'US', 'TAMPA', 33607)\"\n        )\n        self.validate_identity(\n            \"INSERT OVERWRITE DIRECTORY 'x' ROW FORMAT DELIMITED FIELDS TERMINATED BY '\\001' COLLECTION ITEMS TERMINATED BY ',' MAP KEYS TERMINATED BY ':' LINES TERMINATED BY '' STORED AS TEXTFILE SELECT * FROM `a`.`b`\"\n        )\n        self.validate_identity(\n            \"SELECT a, b, SUM(c) FROM tabl AS t GROUP BY a, b, GROUPING SETS ((a, b), a)\"\n        )\n        self.validate_identity(\n            \"SELECT a, b, SUM(c) FROM tabl AS t GROUP BY a, b, GROUPING SETS ((t.a, b), a)\"\n        )\n        self.validate_identity(\n            \"SELECT a, b, SUM(c) FROM tabl AS t GROUP BY a, FOO(b), GROUPING SETS ((a, FOO(b)), a)\"\n        )\n        self.validate_identity(\n            \"SELECT key, value, GROUPING__ID, COUNT(*) FROM T1 GROUP BY key, value WITH CUBE\"\n        )\n        self.validate_identity(\n            \"SELECT key, value, GROUPING__ID, COUNT(*) FROM T1 GROUP BY key, value WITH ROLLUP\"\n        )\n        self.validate_identity(\n            \"TRUNCATE TABLE t1 PARTITION(age = 10, name = 'test', address = 'abc')\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1, t2\",\n            \"SELECT * FROM t1 CROSS JOIN t2\",\n        )\n\n        self.validate_all(\n            \"SELECT ${hiveconf:some_var}\",\n            write={\n                \"hive\": \"SELECT ${hiveconf:some_var}\",\n                \"spark\": \"SELECT ${hiveconf:some_var}\",\n            },\n        )\n        self.validate_all(\n            \"SELECT A.1a AS b FROM test_a AS A\",\n            write={\n                \"spark\": \"SELECT A.1a AS b FROM test_a AS A\",\n            },\n        )\n        self.validate_all(\n            \"SELECT 1_a AS a FROM test_table\",\n            write={\n                \"spark\": \"SELECT 1_a AS a FROM test_table\",\n                \"trino\": 'SELECT \"1_a\" AS a FROM test_table',\n            },\n        )\n        self.validate_all(\n            \"SELECT a_b AS 1_a FROM test_table\",\n            write={\n                \"spark\": \"SELECT a_b AS 1_a FROM test_table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT 1a_1a FROM test_a\",\n            write={\n                \"spark\": \"SELECT 1a_1a FROM test_a\",\n            },\n        )\n        self.validate_all(\n            \"SELECT 1a AS 1a_1a FROM test_a\",\n            write={\n                \"spark\": \"SELECT 1a AS 1a_1a FROM test_a\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE test_table (1a STRING)\",\n            write={\n                \"spark\": \"CREATE TABLE test_table (1a STRING)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE test_table2 (1a_1a STRING)\",\n            write={\n                \"spark\": \"CREATE TABLE test_table2 (1a_1a STRING)\",\n            },\n        )\n        self.validate_all(\n            \"PERCENTILE_APPROX(x, 0.5)\",\n            read={\n                \"hive\": \"PERCENTILE_APPROX(x, 0.5)\",\n                \"presto\": \"APPROX_PERCENTILE(x, 0.5)\",\n                \"duckdb\": \"APPROX_QUANTILE(x, 0.5)\",\n                \"spark\": \"PERCENTILE_APPROX(x, 0.5)\",\n            },\n            write={\n                \"hive\": \"PERCENTILE_APPROX(x, 0.5)\",\n                \"presto\": \"APPROX_PERCENTILE(x, 0.5)\",\n                \"duckdb\": \"APPROX_QUANTILE(x, 0.5)\",\n                \"spark\": \"PERCENTILE_APPROX(x, 0.5)\",\n            },\n        )\n        self.validate_all(\n            \"PERCENTILE_APPROX(x, 0.5)\",\n            read={\n                \"hive\": \"PERCENTILE_APPROX(ALL x, 0.5)\",\n                \"spark2\": \"PERCENTILE_APPROX(ALL x, 0.5)\",\n                \"spark\": \"PERCENTILE_APPROX(ALL x, 0.5)\",\n                \"databricks\": \"PERCENTILE_APPROX(ALL x, 0.5)\",\n            },\n        )\n        self.validate_all(\n            \"PERCENTILE_APPROX(x, 0.5, 200)\",\n            read={\n                \"hive\": \"PERCENTILE_APPROX(ALL x, 0.5, 200)\",\n                \"spark2\": \"PERCENTILE_APPROX(ALL x, 0.5, 200)\",\n                \"spark\": \"PERCENTILE_APPROX(ALL x, 0.5, 200)\",\n                \"databricks\": \"PERCENTILE_APPROX(ALL x, 0.5, 200)\",\n            },\n        )\n        self.validate_all(\n            \"APPROX_COUNT_DISTINCT(a)\",\n            write={\n                \"bigquery\": \"APPROX_COUNT_DISTINCT(a)\",\n                \"duckdb\": \"APPROX_COUNT_DISTINCT(a)\",\n                \"presto\": \"APPROX_DISTINCT(a)\",\n                \"hive\": \"APPROX_COUNT_DISTINCT(a)\",\n                \"snowflake\": \"APPROX_COUNT_DISTINCT(a)\",\n                \"spark\": \"APPROX_COUNT_DISTINCT(a)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_CONTAINS(x, 1)\",\n            read={\n                \"duckdb\": \"LIST_HAS(x, 1)\",\n                \"snowflake\": \"ARRAY_CONTAINS(1, x)\",\n            },\n            write={\n                \"duckdb\": \"ARRAY_CONTAINS(x, 1)\",\n                \"presto\": \"CONTAINS(x, 1)\",\n                \"hive\": \"ARRAY_CONTAINS(x, 1)\",\n                \"spark\": \"ARRAY_CONTAINS(x, 1)\",\n                \"snowflake\": \"ARRAY_CONTAINS(CAST(1 AS VARIANT), x)\",\n            },\n        )\n        self.validate_all(\n            \"SIZE(x)\",\n            write={\n                \"duckdb\": \"ARRAY_LENGTH(x)\",\n                \"presto\": \"CARDINALITY(x)\",\n                \"hive\": \"SIZE(x)\",\n                \"spark\": \"SIZE(x)\",\n            },\n        )\n        self.validate_all(\n            \"LOCATE('a', x)\",\n            write={\n                \"duckdb\": \"STRPOS(x, 'a')\",\n                \"presto\": \"STRPOS(x, 'a')\",\n                \"hive\": \"LOCATE('a', x)\",\n                \"spark\": \"LOCATE('a', x)\",\n            },\n        )\n        self.validate_all(\n            \"LOCATE('a', x, 3)\",\n            write={\n                \"duckdb\": \"CASE WHEN STRPOS(SUBSTRING(x, 3), 'a') = 0 THEN 0 ELSE STRPOS(SUBSTRING(x, 3), 'a') + 3 - 1 END\",\n                \"presto\": \"IF(STRPOS(SUBSTRING(x, 3), 'a') = 0, 0, STRPOS(SUBSTRING(x, 3), 'a') + 3 - 1)\",\n                \"hive\": \"LOCATE('a', x, 3)\",\n                \"spark\": \"LOCATE('a', x, 3)\",\n            },\n        )\n\n        self.validate_all(\n            \"INITCAP('new york')\",\n            write={\n                \"hive\": \"INITCAP('new york')\",\n                \"spark\": \"INITCAP('new york')\",\n            },\n        )\n        expression = self.parse_one(\"INITCAP('new york')\")\n        self.assert_duckdb_sql(\n            expression,\n            includes=(\"REGEXP_MATCHES(\", \"ARRAY_TO_STRING(\"),\n            chr_chars=(\"\\u000b\", \"\\u001c\", \"\\u001d\", \"\\u001e\", \"\\u001f\"),\n        )\n        self.validate_all(\n            \"SELECT * FROM x.z TABLESAMPLE(10 PERCENT) y\",\n            write={\n                \"hive\": \"SELECT * FROM x.z TABLESAMPLE (10 PERCENT) AS y\",\n                \"spark\": \"SELECT * FROM x.z TABLESAMPLE (10 PERCENT) AS y\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SORT_ARRAY(x, FALSE)\",\n            read={\n                \"duckdb\": \"SELECT ARRAY_REVERSE_SORT(x)\",\n                \"spark\": \"SELECT SORT_ARRAY(x, FALSE)\",\n            },\n            write={\n                \"duckdb\": \"SELECT ARRAY_REVERSE_SORT(x)\",\n                \"presto\": \"SELECT ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)\",\n                \"hive\": \"SELECT SORT_ARRAY(x, FALSE)\",\n                \"spark\": \"SELECT SORT_ARRAY(x, FALSE)\",\n            },\n        )\n        self.validate_all(\n            \"GET_JSON_OBJECT(x, '$.name')\",\n            write={\n                \"presto\": \"JSON_EXTRACT_SCALAR(x, '$.name')\",\n                \"hive\": \"GET_JSON_OBJECT(x, '$.name')\",\n                \"spark\": \"GET_JSON_OBJECT(x, '$.name')\",\n            },\n        )\n        self.validate_all(\n            \"MAP(a, b, c, d)\",\n            read={\n                \"\": \"VAR_MAP(a, b, c, d)\",\n                \"clickhouse\": \"map(a, b, c, d)\",\n                \"duckdb\": \"MAP([a, c], [b, d])\",\n                \"hive\": \"MAP(a, b, c, d)\",\n                \"presto\": \"MAP(ARRAY[a, c], ARRAY[b, d])\",\n                \"spark\": \"MAP(a, b, c, d)\",\n            },\n            write={\n                \"\": \"MAP(ARRAY(a, c), ARRAY(b, d))\",\n                \"clickhouse\": \"map(a, b, c, d)\",\n                \"duckdb\": \"MAP([a, c], [b, d])\",\n                \"presto\": \"MAP(ARRAY[a, c], ARRAY[b, d])\",\n                \"hive\": \"MAP(a, b, c, d)\",\n                \"spark\": \"MAP(a, b, c, d)\",\n                \"snowflake\": \"OBJECT_CONSTRUCT(a, b, c, d)\",\n            },\n        )\n        self.validate_all(\n            \"MAP(a, b)\",\n            write={\n                \"duckdb\": \"MAP([a], [b])\",\n                \"presto\": \"MAP(ARRAY[a], ARRAY[b])\",\n                \"hive\": \"MAP(a, b)\",\n                \"spark\": \"MAP(a, b)\",\n                \"snowflake\": \"OBJECT_CONSTRUCT(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"LOG(10)\",\n            write={\n                \"duckdb\": \"LN(10)\",\n                \"presto\": \"LN(10)\",\n                \"hive\": \"LN(10)\",\n                \"spark\": \"LN(10)\",\n            },\n        )\n        self.validate_all(\n            'ds = \"2020-01-01\"',\n            write={\n                \"duckdb\": \"ds = '2020-01-01'\",\n                \"presto\": \"ds = '2020-01-01'\",\n                \"hive\": \"ds = '2020-01-01'\",\n                \"spark\": \"ds = '2020-01-01'\",\n            },\n        )\n        self.validate_all(\n            \"ds = \\\"1''2\\\"\",\n            write={\n                \"duckdb\": \"ds = '1''''2'\",\n                \"presto\": \"ds = '1''''2'\",\n                \"hive\": \"ds = '1\\\\'\\\\'2'\",\n                \"spark\": \"ds = '1\\\\'\\\\'2'\",\n            },\n        )\n        self.validate_all(\n            \"x == 1\",\n            write={\n                \"duckdb\": \"x = 1\",\n                \"presto\": \"x = 1\",\n                \"hive\": \"x = 1\",\n                \"spark\": \"x = 1\",\n            },\n        )\n        self.validate_all(\n            \"x DIV y\",\n            read={\n                \"databricks\": \"x DIV y\",\n                \"duckdb\": \"x // y\",\n                \"hive\": \"x DIV y\",\n                \"spark2\": \"x DIV y\",\n                \"spark\": \"x DIV y\",\n            },\n            write={\n                \"duckdb\": \"x // y\",\n                \"databricks\": \"x DIV y\",\n                \"presto\": \"CAST(CAST(x AS DOUBLE) / y AS INTEGER)\",\n                \"spark2\": \"x DIV y\",\n                \"spark\": \"x DIV y\",\n            },\n        )\n        self.validate_all(\n            \"COLLECT_LIST(x)\",\n            read={\n                \"presto\": \"ARRAY_AGG(x)\",\n            },\n            write={\n                \"duckdb\": \"ARRAY_AGG(x) FILTER(WHERE x IS NOT NULL)\",\n                \"presto\": \"ARRAY_AGG(x) FILTER(WHERE x IS NOT NULL)\",\n                \"hive\": \"COLLECT_LIST(x)\",\n                \"spark\": \"COLLECT_LIST(x)\",\n            },\n        )\n        self.validate_all(\n            \"COLLECT_SET(x)\",\n            read={\n                \"doris\": \"COLLECT_SET(x)\",\n                \"presto\": \"SET_AGG(x)\",\n                \"snowflake\": \"ARRAY_UNIQUE_AGG(x)\",\n            },\n            write={\n                \"doris\": \"COLLECT_SET(x)\",\n                \"hive\": \"COLLECT_SET(x)\",\n                \"presto\": \"SET_AGG(x)\",\n                \"snowflake\": \"ARRAY_UNIQUE_AGG(x)\",\n                \"spark\": \"COLLECT_SET(x)\",\n                \"trino\": \"ARRAY_AGG(DISTINCT x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x TABLESAMPLE (1 PERCENT) AS foo\",\n            read={\n                \"presto\": \"SELECT * FROM x AS foo TABLESAMPLE BERNOULLI (1)\",\n                \"snowflake\": \"SELECT * FROM x AS foo TABLESAMPLE (1)\",\n            },\n            write={\n                \"hive\": \"SELECT * FROM x TABLESAMPLE (1 PERCENT) AS foo\",\n                \"snowflake\": \"SELECT * FROM x AS foo TABLESAMPLE (1)\",\n                \"spark\": \"SELECT * FROM x TABLESAMPLE (1 PERCENT) AS foo\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a, SUM(c) FROM t GROUP BY a, DATE_FORMAT(b, 'yyyy'), GROUPING SETS ((a, DATE_FORMAT(b, 'yyyy')), a)\",\n            write={\n                \"hive\": \"SELECT a, SUM(c) FROM t GROUP BY a, DATE_FORMAT(b, 'yyyy'), GROUPING SETS ((a, DATE_FORMAT(b, 'yyyy')), a)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRUNC(CAST(ds AS TIMESTAMP), 'MONTH')\",\n            read={\n                \"hive\": \"SELECT TRUNC(CAST(ds AS TIMESTAMP), 'MONTH')\",\n                \"presto\": \"SELECT DATE_TRUNC('MONTH', CAST(ds AS TIMESTAMP))\",\n            },\n            write={\n                \"presto\": \"SELECT DATE_TRUNC('MONTH', TRY_CAST(ds AS TIMESTAMP))\",\n            },\n        )\n\n        # Hive TRUNC is date-only, should parse to TimestampTrunc (not numeric Trunc)\n        self.validate_identity(\"TRUNC(date_col, 'MM')\").assert_is(exp.TimestampTrunc)\n\n        # Numeric TRUNC from other dialects - Hive has no native support, uses CAST to BIGINT\n        self.validate_all(\n            \"CAST(3.14159 AS BIGINT)\",\n            read={\"postgres\": \"TRUNC(3.14159, 2)\"},\n        )\n\n        self.validate_all(\n            \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n            read={\n                \"hive\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"spark2\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"spark\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"databricks\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n            },\n            write={\n                \"hive\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"spark2\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"spark\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"databricks\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"presto\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)', 1)\",\n                \"trino\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)', 1)\",\n                \"duckdb\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)', 1)\",\n            },\n        )\n\n        self.validate_identity(\"EXISTS(col, x -> x % 2 = 0)\").assert_is(exp.Exists)\n\n        self.validate_all(\n            \"SELECT EXISTS(ARRAY(2, 3), x -> x % 2 = 0)\",\n            read={\n                \"hive\": \"SELECT EXISTS(ARRAY(2, 3), x -> x % 2 = 0)\",\n                \"spark2\": \"SELECT EXISTS(ARRAY(2, 3), x -> x % 2 = 0)\",\n                \"spark\": \"SELECT EXISTS(ARRAY(2, 3), x -> x % 2 = 0)\",\n                \"databricks\": \"SELECT EXISTS(ARRAY(2, 3), x -> x % 2 = 0)\",\n            },\n            write={\n                \"spark2\": \"SELECT EXISTS(ARRAY(2, 3), x -> x % 2 = 0)\",\n                \"spark\": \"SELECT EXISTS(ARRAY(2, 3), x -> x % 2 = 0)\",\n                \"databricks\": \"SELECT EXISTS(ARRAY(2, 3), x -> x % 2 = 0)\",\n            },\n        )\n\n        self.validate_identity(\"SELECT 1_2\")\n\n        self.validate_all(\n            \"SELECT MAP(*), STRUCT(*) FROM t\",\n            read={\n                \"hive\": \"SELECT MAP(*), STRUCT(*) FROM t\",\n                \"spark2\": \"SELECT MAP(*), STRUCT(*) FROM t\",\n                \"spark\": \"SELECT MAP(*), STRUCT(*) FROM t\",\n                \"databricks\": \"SELECT MAP(*), STRUCT(*) FROM t\",\n            },\n            write={\n                \"spark2\": \"SELECT MAP(*), STRUCT(*) FROM t\",\n                \"spark\": \"SELECT MAP(*), STRUCT(*) FROM t\",\n                \"databricks\": \"SELECT MAP(*), STRUCT(*) FROM t\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT FIRST(sample_col, TRUE)\",\n            read={\n                \"spark\": \"SELECT FIRST(sample_col, TRUE)\",\n                \"databricks\": \"SELECT FIRST(sample_col, TRUE)\",\n            },\n            write={\n                \"hive\": \"SELECT FIRST(sample_col, TRUE)\",\n                \"spark2\": \"SELECT FIRST(sample_col, TRUE)\",\n                \"spark\": \"SELECT FIRST(sample_col) IGNORE NULLS\",\n                \"databricks\": \"SELECT FIRST(sample_col) IGNORE NULLS\",\n                \"duckdb\": \"SELECT ANY_VALUE(sample_col)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT FIRST_VALUE(sample_col, TRUE)\",\n            read={\n                \"spark\": \"SELECT FIRST_VALUE(sample_col, TRUE)\",\n                \"databricks\": \"SELECT FIRST_VALUE(sample_col, TRUE)\",\n            },\n            write={\n                \"hive\": \"SELECT FIRST_VALUE(sample_col, TRUE)\",\n                \"spark2\": \"SELECT FIRST_VALUE(sample_col, TRUE)\",\n                \"spark\": \"SELECT FIRST_VALUE(sample_col) IGNORE NULLS\",\n                \"databricks\": \"SELECT FIRST_VALUE(sample_col) IGNORE NULLS\",\n                \"duckdb\": \"SELECT FIRST_VALUE(sample_col IGNORE NULLS)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT LAST_VALUE(sample_col, TRUE)\",\n            read={\n                \"spark\": \"SELECT LAST_VALUE(sample_col, TRUE)\",\n                \"databricks\": \"SELECT LAST_VALUE(sample_col, TRUE)\",\n            },\n            write={\n                \"hive\": \"SELECT LAST_VALUE(sample_col, TRUE)\",\n                \"spark2\": \"SELECT LAST_VALUE(sample_col, TRUE)\",\n                \"spark\": \"SELECT LAST_VALUE(sample_col) IGNORE NULLS\",\n                \"databricks\": \"SELECT LAST_VALUE(sample_col) IGNORE NULLS\",\n                \"duckdb\": \"SELECT LAST_VALUE(sample_col IGNORE NULLS)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT LAST(sample_col, TRUE)\",\n            read={\n                \"spark\": \"SELECT LAST(sample_col, TRUE)\",\n                \"databricks\": \"SELECT LAST(sample_col, TRUE)\",\n            },\n            write={\n                \"hive\": \"SELECT LAST(sample_col, TRUE)\",\n                \"spark2\": \"SELECT LAST(sample_col, TRUE)\",\n                \"spark\": \"SELECT LAST(sample_col) IGNORE NULLS\",\n                \"databricks\": \"SELECT LAST(sample_col) IGNORE NULLS\",\n            },\n        )\n\n        self.validate_identity(\n            \"DATE_SUB(CURRENT_DATE, 1 + 1)\", \"DATE_ADD(CURRENT_DATE, (1 + 1) * -1)\"\n        )\n        self.validate_identity(\"SELECT ELT(2, 'foo', 'bar', 'baz') AS Result\")\n\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT '{\"x-y\": \"z\"}' AS c) SELECT GET_JSON_OBJECT(c, '$.x-y') FROM t\"\"\",\n            write={\n                \"hive\": \"\"\"WITH t AS (SELECT '{\"x-y\": \"z\"}' AS c) SELECT GET_JSON_OBJECT(c, '$.x-y') FROM t\"\"\",\n                \"spark2\": \"\"\"WITH t AS (SELECT '{\"x-y\": \"z\"}' AS c) SELECT GET_JSON_OBJECT(c, '$.x-y') FROM t\"\"\",\n                \"spark\": \"\"\"WITH t AS (SELECT '{\"x-y\": \"z\"}' AS c) SELECT GET_JSON_OBJECT(c, '$.x-y') FROM t\"\"\",\n                \"databricks\": \"\"\"WITH t AS (SELECT '{\"x-y\": \"z\"}' AS c) SELECT c:[\"x-y\"] FROM t\"\"\",\n            },\n        )\n\n    def test_escapes(self) -> None:\n        self.validate_identity(\"'\\n'\", \"'\\\\n'\")\n        self.validate_identity(\"'\\\\n'\")\n        self.validate_identity(\"'\\\\\\n'\", \"'\\\\\\\\\\\\n'\")\n        self.validate_identity(\"'\\\\\\\\n'\")\n        self.validate_identity(\"''\")\n        self.validate_identity(\"'\\\\\\\\'\")\n        self.validate_identity(\"'\\\\\\\\z'\")\n\n    def test_data_type(self):\n        self.validate_all(\n            \"CAST(a AS BIT)\",\n            write={\n                \"hive\": \"CAST(a AS BOOLEAN)\",\n            },\n        )\n\n    def test_joins_without_on(self):\n        for join in (\"FULL OUTER\", \"LEFT\", \"RIGHT\", \"LEFT OUTER\", \"RIGHT OUTER\", \"INNER\"):\n            with self.subTest(f\"Testing transpilation of {join} without ON\"):\n                self.validate_all(\n                    f\"SELECT * FROM t1 {join} JOIN t2 ON TRUE\",\n                    read={\n                        \"hive\": f\"SELECT * FROM t1 {join} JOIN t2\",\n                        \"spark2\": f\"SELECT * FROM t1 {join} JOIN t2\",\n                        \"spark\": f\"SELECT * FROM t1 {join} JOIN t2\",\n                        \"databricks\": f\"SELECT * FROM t1 {join} JOIN t2\",\n                        \"sqlite\": f\"SELECT * FROM t1 {join} JOIN t2\",\n                    },\n                    write={\n                        \"hive\": f\"SELECT * FROM t1 {join} JOIN t2 ON TRUE\",\n                        \"spark2\": f\"SELECT * FROM t1 {join} JOIN t2 ON TRUE\",\n                        \"spark\": f\"SELECT * FROM t1 {join} JOIN t2 ON TRUE\",\n                        \"databricks\": f\"SELECT * FROM t1 {join} JOIN t2 ON TRUE\",\n                        \"sqlite\": f\"SELECT * FROM t1 {join} JOIN t2 ON TRUE\",\n                        \"duckdb\": f\"SELECT * FROM t1 {join} JOIN t2 ON TRUE\",\n                    },\n                )\n\n    def test_percentile(self):\n        self.validate_all(\n            \"PERCENTILE(x, 0.5)\",\n            write={\n                \"duckdb\": \"QUANTILE(x, 0.5)\",\n                \"presto\": \"APPROX_PERCENTILE(x, 0.5)\",\n                \"hive\": \"PERCENTILE(x, 0.5)\",\n                \"spark2\": \"PERCENTILE(x, 0.5)\",\n                \"spark\": \"PERCENTILE(x, 0.5)\",\n                \"databricks\": \"PERCENTILE(x, 0.5)\",\n            },\n        )\n\n        self.validate_all(\n            \"PERCENTILE(DISTINCT x, 0.5)\",\n            read={\n                \"hive\": \"PERCENTILE(DISTINCT x, 0.5)\",\n                \"spark\": \"PERCENTILE(DISTINCT x, 0.5)\",\n                \"databricks\": \"PERCENTILE(DISTINCT x, 0.5)\",\n            },\n            write={\n                \"spark\": \"PERCENTILE(DISTINCT x, 0.5)\",\n                \"databricks\": \"PERCENTILE(DISTINCT x, 0.5)\",\n            },\n        )\n\n        self.validate_all(\n            \"PERCENTILE(x, 0.5)\",\n            read={\n                \"hive\": \"PERCENTILE(ALL x, 0.5)\",\n                \"spark2\": \"PERCENTILE(ALL x, 0.5)\",\n                \"spark\": \"PERCENTILE(ALL x, 0.5)\",\n                \"databricks\": \"PERCENTILE(ALL x, 0.5)\",\n            },\n        )\n\n        quantile_expr = self.validate_identity(\"PERCENTILE(DISTINCT x, 0.5)\")\n        quantile_expr.assert_is(exp.Quantile)\n        quantile_expr.this.assert_is(exp.Distinct)\n        quantile_expr.args.get(\"quantile\").assert_is(exp.Literal)\n\n        quantile_expr = self.validate_identity(\"PERCENTILE(ALL x, 0.5)\", \"PERCENTILE(x, 0.5)\")\n        quantile_expr.assert_is(exp.Quantile)\n        quantile_expr.this.assert_is(exp.Column)\n        quantile_expr.args.get(\"quantile\").assert_is(exp.Literal)\n"
  },
  {
    "path": "tests/dialects/test_materialize.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestMaterialize(Validator):\n    dialect = \"materialize\"\n\n    def test_materialize(self):\n        self.validate_all(\n            \"CREATE TABLE example (id INT PRIMARY KEY, name TEXT)\",\n            write={\n                \"materialize\": \"CREATE TABLE example (id INT, name TEXT)\",\n                \"postgres\": \"CREATE TABLE example (id INT PRIMARY KEY, name TEXT)\",\n            },\n        )\n        self.validate_all(\n            \"INSERT INTO example (id, name) VALUES (1, 'Alice') ON CONFLICT(id) DO NOTHING\",\n            write={\n                \"materialize\": \"INSERT INTO example (id, name) VALUES (1, 'Alice')\",\n                \"postgres\": \"INSERT INTO example (id, name) VALUES (1, 'Alice') ON CONFLICT(id) DO NOTHING\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE example (id SERIAL, name TEXT)\",\n            write={\n                \"materialize\": \"CREATE TABLE example (id INT NOT NULL, name TEXT)\",\n                \"postgres\": \"CREATE TABLE example (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name TEXT)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE example (id INT AUTO_INCREMENT, name TEXT)\",\n            write={\n                \"materialize\": \"CREATE TABLE example (id INT NOT NULL, name TEXT)\",\n                \"postgres\": \"CREATE TABLE example (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name TEXT)\",\n            },\n        )\n        self.validate_all(\n            'SELECT JSON_EXTRACT_PATH_TEXT(\\'{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}\\', \\'farm\\', \\'barn\\', \\'color\\')',\n            write={\n                \"materialize\": 'SELECT JSON_EXTRACT_PATH_TEXT(\\'{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}\\', \\'farm\\', \\'barn\\', \\'color\\')',\n                \"postgres\": 'SELECT JSON_EXTRACT_PATH_TEXT(\\'{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}\\', \\'farm\\', \\'barn\\', \\'color\\')',\n            },\n        )\n        self.validate_all(\n            \"SELECT MAP['a' => 1]\",\n            write={\n                \"duckdb\": \"SELECT MAP {'a': 1}\",\n                \"materialize\": \"SELECT MAP['a' => 1]\",\n            },\n        )\n\n        # Test now functions.\n        self.validate_identity(\"CURRENT_TIMESTAMP\")\n        self.validate_identity(\"NOW()\", write_sql=\"CURRENT_TIMESTAMP\")\n        self.validate_identity(\"MZ_NOW()\")\n\n        # Test custom timestamp type.\n        self.validate_identity(\"SELECT CAST(1 AS mz_timestamp)\")\n\n        # Test DDL.\n        self.validate_identity(\"CREATE TABLE example (id INT, name LIST)\")\n\n        # Test list types.\n        self.validate_identity(\"SELECT LIST[]\")\n        self.validate_identity(\"SELECT LIST[1, 2, 3]\")\n        self.validate_identity(\"SELECT LIST[LIST[1], LIST[2], NULL]\")\n        self.validate_identity(\"SELECT CAST(LIST[1, 2, 3] AS INT LIST)\")\n        self.validate_identity(\"SELECT CAST(NULL AS INT LIST)\")\n        self.validate_identity(\"SELECT CAST(NULL AS INT LIST LIST LIST)\")\n        self.validate_identity(\"SELECT LIST(SELECT 1)\")\n\n        # Test map types.\n        self.validate_identity(\"SELECT MAP[]\")\n        self.validate_identity(\"SELECT MAP['a' => MAP['b' => 'c']]\")\n        self.validate_identity(\"SELECT CAST(MAP['a' => 1] AS MAP[TEXT => INT])\")\n        self.validate_identity(\"SELECT CAST(NULL AS MAP[TEXT => INT])\")\n        self.validate_identity(\"SELECT CAST(NULL AS MAP[TEXT => MAP[TEXT => INT]])\")\n        self.validate_identity(\"SELECT MAP(SELECT 'a', 1)\")\n"
  },
  {
    "path": "tests/dialects/test_mysql.py",
    "content": "import unittest\nimport sys\n\nfrom sqlglot import UnsupportedError, expressions as exp\nfrom sqlglot.dialects.mysql import MySQL\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestMySQL(Validator):\n    dialect = \"mysql\"\n\n    def test_ddl(self):\n        for t in (\"BIGINT\", \"INT\", \"MEDIUMINT\", \"SMALLINT\", \"TINYINT\"):\n            self.validate_identity(f\"CREATE TABLE t (id {t} UNSIGNED)\")\n            self.validate_identity(f\"CREATE TABLE t (id {t}(10) UNSIGNED)\")\n\n        self.validate_identity(\"CREATE TABLE bar (abacate DOUBLE(10, 2) UNSIGNED)\")\n        self.validate_identity(\"CREATE TABLE t (id DECIMAL(20, 4) UNSIGNED)\")\n        self.validate_identity(\"CREATE TABLE foo (a BIGINT, UNIQUE (b) USING BTREE)\")\n        self.validate_identity(\"CREATE TABLE foo (a VARCHAR(32) NOT NULL UNIQUE COMMENT 'test')\")\n        self.validate_identity(\"CREATE TABLE foo (id BIGINT)\")\n        self.validate_identity(\"CREATE TABLE 00f (1d BIGINT)\")\n        self.validate_identity(\"CREATE TABLE temp (id SERIAL PRIMARY KEY)\")\n        self.validate_identity(\"UPDATE items SET items.price = 0 WHERE items.id >= 5 LIMIT 10\")\n        self.validate_identity(\"DELETE FROM t WHERE a <= 10 LIMIT 10\")\n        self.validate_identity(\"DELETE FROM t FORCE INDEX (idx) WHERE a > 5 ORDER BY id\")\n        self.validate_identity(\"CREATE TABLE foo (a BIGINT, INDEX USING BTREE (b))\")\n        self.validate_identity(\"CREATE TABLE foo (a BIGINT, FULLTEXT INDEX (b))\")\n        self.validate_identity(\"CREATE TABLE foo (a BIGINT, SPATIAL INDEX (b))\")\n        self.validate_identity(\"CREATE TABLE foo (a INT UNSIGNED ZEROFILL)\")\n        self.validate_identity(\"ALTER TABLE t1 ADD COLUMN x INT, ALGORITHM=INPLACE, LOCK=EXCLUSIVE\")\n        self.validate_identity(\"ALTER TABLE t ADD INDEX `i` (`c`)\")\n        self.validate_identity(\"ALTER TABLE t ADD UNIQUE `i` (`c`)\")\n        self.validate_identity(\"ALTER TABLE test_table MODIFY COLUMN test_column LONGTEXT\")\n        self.validate_identity(\"ALTER VIEW v AS SELECT a, b, c, d FROM foo\")\n        self.validate_identity(\"ALTER VIEW v AS SELECT * FROM foo WHERE c > 100\")\n        self.validate_identity(\n            \"ALTER ALGORITHM = MERGE VIEW v AS SELECT * FROM foo\", check_command_warning=True\n        )\n        self.validate_identity(\n            \"ALTER DEFINER = 'admin'@'localhost' VIEW v AS SELECT * FROM foo\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"CREATE SQL SECURITY INVOKER VIEW id_test (id, foo) AS SELECT 0, foo FROM test\"\n        )\n        self.validate_identity(\n            \"CREATE SQL SECURITY DEFINER VIEW id_test (id, foo) AS SELECT 0, foo FROM test\"\n        )\n        self.validate_identity(\n            \"ALTER SQL SECURITY = DEFINER VIEW v AS SELECT * FROM foo\", check_command_warning=True\n        )\n        self.validate_identity(\n            \"INSERT INTO things (a, b) VALUES (1, 2) AS new_data ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), a = new_data.a, b = new_data.b\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE `oauth_consumer` (`key` VARCHAR(32) NOT NULL, UNIQUE `OAUTH_CONSUMER_KEY` (`key`))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE `x` (`username` VARCHAR(200), PRIMARY KEY (`username`(16)))\"\n        )\n        self.validate_identity(\n            \"UPDATE items SET items.price = 0 WHERE items.id >= 5 ORDER BY items.id LIMIT 10\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE foo (a BIGINT, INDEX b USING HASH (c) COMMENT 'd' VISIBLE ENGINE_ATTRIBUTE = 'e' WITH PARSER foo)\"\n        )\n        self.validate_identity(\n            \"DELETE t1 FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.id IS NULL\"\n        )\n        self.validate_identity(\n            \"DELETE t1, t2 FROM t1 INNER JOIN t2 INNER JOIN t3 WHERE t1.id = t2.id AND t2.id = t3.id\"\n        )\n        self.validate_identity(\n            \"DELETE FROM t1, t2 USING t1 INNER JOIN t2 INNER JOIN t3 WHERE t1.id = t2.id AND t2.id = t3.id\"\n        )\n        self.validate_identity(\n            \"INSERT IGNORE INTO subscribers (email) VALUES ('john.doe@gmail.com'), ('jane.smith@ibm.com')\"\n        )\n        self.validate_identity(\n            \"INSERT INTO t1 (a, b, c) VALUES (1, 2, 3), (4, 5, 6) ON DUPLICATE KEY UPDATE c = VALUES(a) + VALUES(b)\"\n        )\n        self.validate_identity(\n            \"INSERT INTO t1 (a, b) SELECT c, d FROM t2 UNION SELECT e, f FROM t3 ON DUPLICATE KEY UPDATE b = b + c\"\n        )\n        self.validate_identity(\n            \"INSERT INTO t1 (a, b, c) VALUES (1, 2, 3) ON DUPLICATE KEY UPDATE c = c + 1\"\n        )\n        self.validate_identity(\n            \"INSERT INTO x VALUES (1, 'a', 2.0) ON DUPLICATE KEY UPDATE x.id = 1\"\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE VIEW my_view AS SELECT column1 AS `boo`, column2 AS `foo` FROM my_table WHERE column3 = 'some_value' UNION SELECT q.* FROM fruits_table, JSON_TABLE(Fruits, '$[*]' COLUMNS(id VARCHAR(255) PATH '$.$id', value VARCHAR(255) PATH '$.value')) AS q\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (id INT AUTO_INCREMENT, PRIMARY KEY (id) USING BTREE)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (id INT AUTO_INCREMENT, PRIMARY KEY (id) USING HASH)\"\n        )\n        self.validate_identity(\"CREATE TABLE test (id INT, PRIMARY KEY pk_name (id))\")\n        self.validate_identity(\"CREATE TABLE test (id INT, PRIMARY KEY `pk_name` (id))\")\n        self.validate_identity(\n            'CREATE TABLE test (id INT, PRIMARY KEY \"pk_name\" (id))',\n            \"CREATE TABLE test (id INT, PRIMARY KEY `pk_name` (id))\",\n        )\n        self.validate_identity(\"CREATE TABLE test (id INT, CONSTRAINT pk_name PRIMARY KEY (id))\")\n        self.validate_identity(\n            \"CREATE TABLE test (a INT, b INT GENERATED ALWAYS AS (a + a) STORED)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test (a INT, b INT GENERATED ALWAYS AS (a + a) VIRTUAL)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test (a INT, b INT AS (a + a) STORED)\",\n            \"CREATE TABLE test (a INT, b INT GENERATED ALWAYS AS (a + a) STORED)\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE test (a INT, b INT AS (a + a) VIRTUAL)\",\n            \"CREATE TABLE test (a INT, b INT GENERATED ALWAYS AS (a + a) VIRTUAL)\",\n        )\n        self.validate_identity(\n            \"/*left*/ EXPLAIN SELECT /*hint*/ col FROM t1 /*right*/\",\n            \"/* left */ DESCRIBE /* hint */ SELECT col FROM t1 /* right */\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (name VARCHAR)\",\n            \"CREATE TABLE t (name TEXT)\",\n        )\n        self.validate_identity(\n            \"ALTER TABLE t ADD KEY `i` (`c`)\",\n            \"ALTER TABLE t ADD INDEX `i` (`c`)\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE `foo` (`id` char(36) NOT NULL DEFAULT (uuid()), PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`))\",\n            \"CREATE TABLE `foo` (`id` CHAR(36) NOT NULL DEFAULT (UUID()), PRIMARY KEY (`id`), UNIQUE `id` (`id`))\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE IF NOT EXISTS industry_info (a BIGINT(20) NOT NULL AUTO_INCREMENT, b BIGINT(20) NOT NULL, c VARCHAR(1000), PRIMARY KEY (a), UNIQUE KEY d (b), KEY e (b))\",\n            \"CREATE TABLE IF NOT EXISTS industry_info (a BIGINT(20) NOT NULL AUTO_INCREMENT, b BIGINT(20) NOT NULL, c VARCHAR(1000), PRIMARY KEY (a), UNIQUE d (b), INDEX e (b))\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE test (ts TIMESTAMP, ts_tz TIMESTAMPTZ, ts_ltz TIMESTAMPLTZ)\",\n            \"CREATE TABLE test (ts TIMESTAMP, ts_tz TIMESTAMP, ts_ltz TIMESTAMP)\",\n        )\n        self.validate_identity(\n            \"ALTER TABLE test_table ALTER COLUMN test_column SET DATA TYPE LONGTEXT\",\n            \"ALTER TABLE test_table MODIFY COLUMN test_column LONGTEXT\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (c DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP) DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC\",\n            \"CREATE TABLE t (c DATETIME DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP()) DEFAULT CHARACTER SET=utf8 ROW_FORMAT=DYNAMIC\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE `foo` (a VARCHAR(10), KEY idx_a (a DESC))\",\n            \"CREATE TABLE `foo` (a VARCHAR(10), INDEX idx_a (a DESC))\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE `foo` (a VARCHAR(10), UNIQUE INDEX idx_a (a))\",\n            \"CREATE TABLE `foo` (a VARCHAR(10), UNIQUE idx_a (a))\",\n        )\n\n        self.validate_all(\n            \"insert into t(i) values (default)\",\n            write={\n                \"duckdb\": \"INSERT INTO t (i) VALUES (DEFAULT)\",\n                \"mysql\": \"INSERT INTO t (i) VALUES (DEFAULT)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE t (id INT UNSIGNED)\",\n            write={\n                \"duckdb\": \"CREATE TABLE t (id UINTEGER)\",\n                \"mysql\": \"CREATE TABLE t (id INT UNSIGNED)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE z (a INT) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_bin COMMENT='x'\",\n            write={\n                \"duckdb\": \"CREATE TABLE z (a INT)\",\n                \"mysql\": \"CREATE TABLE z (a INT) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_bin COMMENT='x'\",\n                \"spark\": \"CREATE TABLE z (a INT) COMMENT 'x'\",\n                \"sqlite\": \"CREATE TABLE z (a INTEGER)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE x (id int not null auto_increment, primary key (id))\",\n            write={\n                \"mysql\": \"CREATE TABLE x (id INT NOT NULL AUTO_INCREMENT, PRIMARY KEY (id))\",\n                \"sqlite\": \"CREATE TABLE x (id INTEGER NOT NULL AUTOINCREMENT PRIMARY KEY)\",\n            },\n        )\n        self.validate_identity(\"ALTER TABLE t ALTER INDEX i INVISIBLE\")\n        self.validate_identity(\"ALTER TABLE t ALTER INDEX i VISIBLE\")\n        self.validate_identity(\"ALTER TABLE t ALTER COLUMN c SET INVISIBLE\")\n        self.validate_identity(\"ALTER TABLE t ALTER COLUMN c SET VISIBLE\")\n        self.validate_identity(\n            \"UPDATE foo JOIN bar ON TRUE SET foo.a = bar.a WHERE foo.id = bar.id\"\n        )\n\n        # PARTITION BY RANGE - simple column\n        self.validate_identity(\n            \"CREATE TABLE t (id INT, created_at DATE) PARTITION BY RANGE (id) (PARTITION p0 VALUES LESS THAN (10), PARTITION p1 VALUES LESS THAN (20), PARTITION p2 VALUES LESS THAN (MAXVALUE))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (id INT, name VARCHAR(50)) PARTITION BY RANGE (id) (PARTITION p0 VALUES LESS THAN (100))\"\n        )\n        # PARTITION BY RANGE - with expression\n        self.validate_identity(\n            \"CREATE TABLE orders (id INT, order_date DATE) PARTITION BY RANGE (YEAR(order_date)) (PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION pmax VALUES LESS THAN (MAXVALUE))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE sales (id INT, sale_date DATE) PARTITION BY RANGE (MONTH(sale_date)) (PARTITION q1 VALUES LESS THAN (4), PARTITION q2 VALUES LESS THAN (7), PARTITION q3 VALUES LESS THAN (10), PARTITION q4 VALUES LESS THAN (13))\"\n        )\n        # PARTITION BY LIST - simple column\n        self.validate_identity(\n            \"CREATE TABLE t (id INT, region VARCHAR(10)) PARTITION BY LIST (id) (PARTITION p_east VALUES IN (1, 2, 3), PARTITION p_west VALUES IN (4, 5, 6))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (id INT) PARTITION BY LIST (id) (PARTITION p0 VALUES IN (1, 2))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE employees (id INT, store_id INT) PARTITION BY LIST (store_id) (PARTITION pNorth VALUES IN (3, 5, 6), PARTITION pSouth VALUES IN (1, 2, 10))\"\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION f () RETURNS VARCHAR LANGUAGE SQL SQL SECURITY INVOKER SELECT 'abc'\",\n            \"CREATE FUNCTION f() RETURNS TEXT LANGUAGE SQL SQL SECURITY INVOKER AS SELECT 'abc'\",\n        )\n\n    def test_identity(self):\n        self.validate_identity(\"SELECT HIGH_PRIORITY STRAIGHT_JOIN SQL_CALC_FOUND_ROWS * FROM t\")\n        self.validate_identity(\"SELECT CAST(COALESCE(`id`, 'NULL') AS CHAR CHARACTER SET binary)\")\n        self.validate_identity(\"SELECT e.* FROM e STRAIGHT_JOIN p ON e.x = p.y\")\n        self.validate_identity(\"ALTER TABLE test_table ALTER COLUMN test_column SET DEFAULT 1\")\n        self.validate_identity(\"SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:00.0000')\")\n        self.validate_identity(\"SELECT @var1 := 1, @var2\")\n        self.validate_identity(\"UNLOCK TABLES\")\n        self.validate_identity(\"LOCK TABLES `app_fields` WRITE\", check_command_warning=True)\n        self.validate_identity(\"SELECT 1 XOR 0\")\n        self.validate_identity(\"SELECT 1 && 0\", \"SELECT 1 AND 0\")\n        self.validate_identity(\"SELECT /*+ BKA(t1) NO_BKA(t2) */ * FROM t1 INNER JOIN t2\")\n        self.validate_identity(\"SELECT /*+ MERGE(dt) */ * FROM (SELECT * FROM t1) AS dt\")\n        self.validate_identity(\"SELECT /*+ INDEX(t, i) */ c1 FROM t WHERE c2 = 'value'\")\n        self.validate_identity(\"SELECT @a MEMBER OF(@c), @b MEMBER OF(@c)\")\n        self.validate_identity(\"SELECT JSON_ARRAY(4, 5) MEMBER OF('[[3,4],[4,5]]')\")\n        self.validate_identity(\"SELECT CAST('[4,5]' AS JSON) MEMBER OF('[[3,4],[4,5]]')\")\n        self.validate_identity(\"\"\"SELECT 'ab' MEMBER OF('[23, \"abc\", 17, \"ab\", 10]')\"\"\")\n        self.validate_identity(\"\"\"SELECT * FROM foo WHERE 'ab' MEMBER OF(content)\"\"\")\n        self.validate_identity(\"SELECT CURRENT_TIMESTAMP(6)\")\n        self.validate_identity(\"SELECT CURRENT_ROLE()\")\n        self.validate_identity(\"SELECT CURTIME()\", \"SELECT CURRENT_TIME()\")\n        self.validate_identity(\"x ->> '$.name'\")\n        self.validate_identity(\"SELECT CAST(`a`.`b` AS CHAR) FROM foo\")\n        self.validate_identity(\"SELECT TRIM(LEADING 'bla' FROM ' XXX ')\")\n        self.validate_identity(\"SELECT TRIM(TRAILING 'bla' FROM ' XXX ')\")\n        self.validate_identity(\"SELECT TRIM(BOTH 'bla' FROM ' XXX ')\")\n        self.validate_identity(\"SELECT TRIM('bla' FROM ' XXX ')\")\n        self.validate_identity(\"@@GLOBAL.max_connections\")\n        self.validate_identity(\"CREATE TABLE A LIKE B\")\n        self.validate_identity(\"SELECT * FROM t1, t2 FOR SHARE OF t1, t2 SKIP LOCKED\")\n        self.validate_identity(\"SELECT a || b\", \"SELECT a OR b\")\n        self.validate_identity(\n            \"SELECT * FROM source, JSON_TABLE(source.links, '$.org[*]' COLUMNS(row_id FOR ORDINALITY, link VARCHAR(255) PATH '$.link')) AS links\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM x ORDER BY BINARY a\", \"SELECT * FROM x ORDER BY CAST(a AS BINARY)\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT * FROM foo WHERE 3 MEMBER OF(JSON_EXTRACT(info, '$.value'))\"\"\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1, t2, t3 FOR SHARE OF t1 NOWAIT FOR UPDATE OF t2, t3 SKIP LOCKED\"\n        )\n        self.validate_identity(\n            \"REPLACE INTO table SELECT id FROM table2 WHERE cnt > 100\", check_command_warning=True\n        )\n        self.validate_identity(\n            \"CAST(x AS VARCHAR)\",\n            \"CAST(x AS CHAR)\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT * FROM foo WHERE 3 MEMBER OF(info->'$.value')\"\"\",\n            \"\"\"SELECT * FROM foo WHERE 3 MEMBER OF(JSON_EXTRACT(info, '$.value'))\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT 1 AS row\",\n            \"SELECT 1 AS `row`\",\n        )\n\n        # Index hints\n        self.validate_identity(\n            \"SELECT * FROM table1 USE INDEX (col1_index, col2_index) WHERE col1 = 1 AND col2 = 2 AND col3 = 3\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM table1 IGNORE INDEX (col3_index) WHERE col1 = 1 AND col2 = 2 AND col3 = 3\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1 USE INDEX (i1) IGNORE INDEX FOR ORDER BY (i2) ORDER BY a\"\n        )\n        self.validate_identity(\"SELECT * FROM t1 USE INDEX (i1) USE INDEX (i1, i1)\")\n        self.validate_identity(\"SELECT * FROM t1 USE INDEX FOR JOIN (i1) FORCE INDEX FOR JOIN (i2)\")\n        self.validate_identity(\n            \"SELECT * FROM t1 USE INDEX () IGNORE INDEX (i2) USE INDEX (i1) USE INDEX (i2)\"\n        )\n\n        # SET Commands\n        self.validate_identity(\"SET @var_name = expr\")\n        self.validate_identity(\"SET @name = 43\")\n        self.validate_identity(\"SET @total_tax = (SELECT SUM(tax) FROM taxable_transactions)\")\n        self.validate_identity(\"SET GLOBAL max_connections = 1000\")\n        self.validate_identity(\"SET @@GLOBAL.max_connections = 1000\")\n        self.validate_identity(\"SET SESSION sql_mode = 'TRADITIONAL'\")\n        self.validate_identity(\"SET LOCAL sql_mode = 'TRADITIONAL'\")\n        self.validate_identity(\"SET @@SESSION.sql_mode = 'TRADITIONAL'\")\n        self.validate_identity(\"SET @@LOCAL.sql_mode = 'TRADITIONAL'\")\n        self.validate_identity(\"SET @@sql_mode = 'TRADITIONAL'\")\n        self.validate_identity(\"SET sql_mode = 'TRADITIONAL'\")\n        self.validate_identity(\"SET PERSIST max_connections = 1000\")\n        self.validate_identity(\"SET @@PERSIST.max_connections = 1000\")\n        self.validate_identity(\"SET PERSIST_ONLY back_log = 100\")\n        self.validate_identity(\"SET @@PERSIST_ONLY.back_log = 100\")\n        self.validate_identity(\"SET @@SESSION.max_join_size = DEFAULT\")\n        self.validate_identity(\"SET @@SESSION.max_join_size = @@GLOBAL.max_join_size\")\n        self.validate_identity(\"SET @x = 1, SESSION sql_mode = ''\")\n        self.validate_identity(\"SET GLOBAL max_connections = 1000, sort_buffer_size = 1000000\")\n        self.validate_identity(\"SET @@GLOBAL.sort_buffer_size = 50000, sort_buffer_size = 1000000\")\n        self.validate_identity(\"SET CHARACTER SET 'utf8'\")\n        self.validate_identity(\"SET CHARACTER SET utf8\")\n        self.validate_identity(\"SET CHARACTER SET DEFAULT\")\n        self.validate_identity(\"SET NAMES 'utf8'\")\n        self.validate_identity(\"SET NAMES DEFAULT\")\n        self.validate_identity(\"SET NAMES 'utf8' COLLATE 'utf8_unicode_ci'\")\n        self.validate_identity(\"SET NAMES utf8 COLLATE utf8_unicode_ci\")\n        self.validate_identity(\"SET autocommit = ON\")\n        self.validate_identity(\"SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE\")\n        self.validate_identity(\"SET TRANSACTION READ ONLY\")\n        self.validate_identity(\"SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE\")\n        self.validate_identity(\"DATABASE()\", \"SCHEMA()\")\n        self.validate_identity(\n            \"SET GLOBAL sort_buffer_size = 1000000, SESSION sort_buffer_size = 1000000\"\n        )\n        self.validate_identity(\n            \"SET @@GLOBAL.sort_buffer_size = 1000000, @@LOCAL.sort_buffer_size = 1000000\"\n        )\n        self.validate_identity(\"INTERVAL '1' YEAR\")\n        self.validate_identity(\"DATE_ADD(x, INTERVAL '1' YEAR)\")\n        self.validate_identity(\"CHAR(0)\")\n        self.validate_identity(\"CHAR(77, 121, 83, 81, '76')\")\n        self.validate_identity(\"CHAR(77, 77.3, '77.3' USING utf8mb4)\")\n        self.validate_identity(\"SELECT * FROM t1 PARTITION(p0)\")\n        self.validate_identity(\"SELECT @var1 := 1, @var2\")\n        self.validate_identity(\"SELECT @var1, @var2 := @var1\")\n        self.validate_identity(\"SELECT @var1 := COUNT(*) FROM t1\")\n        self.validate_identity(\"SET @var1 := 1\", \"SET @var1 = 1\")\n\n        self.validate_identity(\n            \"SELECT DISTINCTROW tbl.col FROM tbl\", \"SELECT DISTINCT tbl.col FROM tbl\"\n        )\n        self.validate_identity(\"ATAN(y, x)\")\n\n        self.validate_identity(\n            \"SELECT 'foo' SOUNDS LIKE 'bar'\", \"SELECT SOUNDEX('foo') = SOUNDEX('bar')\"\n        )\n        self.validate_identity(\n            \"SELECT 'foo' NOT SOUNDS LIKE 'bar'\", \"SELECT NOT SOUNDEX('foo') = SOUNDEX('bar')\"\n        )\n        self.validate_identity(\"SELECT SUBSTR(1 FROM 2 FOR 3)\", \"SELECT SUBSTRING(1, 2, 3)\")\n        self.validate_identity(\"SELECT ELT(2, 'foo', 'bar', 'baz') AS Result\")\n        self.validate_identity(\"SELECT CHARSET(CHAR(100 USING utf8))\")\n        self.validate_identity(\"SELECT VERSION()\")\n\n    def test_types(self):\n        for char_type in MySQL.Generator.CHAR_CAST_MAPPING:\n            with self.subTest(f\"MySQL cast into {char_type}\"):\n                self.validate_identity(f\"CAST(x AS {char_type.value})\", \"CAST(x AS CHAR)\")\n\n        for signed_type in MySQL.Generator.SIGNED_CAST_MAPPING:\n            with self.subTest(f\"MySQL cast into {signed_type}\"):\n                self.validate_identity(f\"CAST(x AS {signed_type.value})\", \"CAST(x AS SIGNED)\")\n\n        self.validate_identity(\"CAST(x AS ENUM('a', 'b'))\")\n        self.validate_identity(\"CAST(x AS SET('a', 'b'))\")\n        self.validate_identity(\n            \"CAST(x AS MEDIUMINT) + CAST(y AS YEAR(4))\",\n            \"CAST(x AS SIGNED) + CAST(y AS YEAR(4))\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMP)\",\n            \"TIMESTAMP(x)\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPTZ)\",\n            \"TIMESTAMP(x)\",\n        )\n        self.validate_identity(\n            \"CAST(x AS TIMESTAMPLTZ)\",\n            \"TIMESTAMP(x)\",\n        )\n\n        self.validate_all(\n            \"CAST(x AS MEDIUMTEXT) + CAST(y AS LONGTEXT) + CAST(z AS TINYTEXT)\",\n            write={\n                \"mysql\": \"CAST(x AS CHAR) + CAST(y AS CHAR) + CAST(z AS CHAR)\",\n                \"spark\": \"CAST(x AS TEXT) + CAST(y AS TEXT) + CAST(z AS TEXT)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS MEDIUMBLOB) + CAST(y AS LONGBLOB) + CAST(z AS TINYBLOB)\",\n            write={\n                \"mysql\": \"CAST(x AS CHAR) + CAST(y AS CHAR) + CAST(z AS CHAR)\",\n                \"spark\": \"CAST(x AS BLOB) + CAST(y AS BLOB) + CAST(z AS BLOB)\",\n            },\n        )\n\n    def test_canonical_functions(self):\n        self.validate_identity(\"SELECT LEFT('str', 2)\", \"SELECT LEFT('str', 2)\")\n        self.validate_identity(\"SELECT INSTR('str', 'substr')\", \"SELECT LOCATE('substr', 'str')\")\n        self.validate_identity(\"SELECT UCASE('foo')\", \"SELECT UPPER('foo')\")\n        self.validate_identity(\"SELECT LCASE('foo')\", \"SELECT LOWER('foo')\")\n        self.validate_identity(\n            \"SELECT DAY_OF_MONTH('2023-01-01')\", \"SELECT DAYOFMONTH('2023-01-01')\"\n        )\n        self.validate_identity(\"SELECT DAY_OF_WEEK('2023-01-01')\", \"SELECT DAYOFWEEK('2023-01-01')\")\n        self.validate_identity(\"SELECT DAY_OF_YEAR('2023-01-01')\", \"SELECT DAYOFYEAR('2023-01-01')\")\n        self.validate_identity(\n            \"SELECT WEEK_OF_YEAR('2023-01-01')\", \"SELECT WEEKOFYEAR('2023-01-01')\"\n        )\n        self.validate_all(\n            \"CHAR(10)\",\n            write={\n                \"mysql\": \"CHAR(10)\",\n                \"presto\": \"CHR(10)\",\n                \"sqlite\": \"CHAR(10)\",\n                \"tsql\": \"CHAR(10)\",\n            },\n        )\n        self.validate_identity(\"CREATE TABLE t (foo VARBINARY(5))\")\n        self.validate_all(\n            \"CREATE TABLE t (foo BLOB)\",\n            write={\n                \"mysql\": \"CREATE TABLE t (foo BLOB)\",\n                \"oracle\": \"CREATE TABLE t (foo BLOB)\",\n                \"postgres\": \"CREATE TABLE t (foo BYTEA)\",\n                \"tsql\": \"CREATE TABLE t (foo VARBINARY)\",\n                \"sqlite\": \"CREATE TABLE t (foo BLOB)\",\n                \"duckdb\": \"CREATE TABLE t (foo VARBINARY)\",\n                \"hive\": \"CREATE TABLE t (foo BINARY)\",\n                \"bigquery\": \"CREATE TABLE t (foo BYTES)\",\n                \"redshift\": \"CREATE TABLE t (foo VARBYTE)\",\n                \"clickhouse\": \"CREATE TABLE t (foo Nullable(String))\",\n            },\n        )\n\n    def test_escape(self):\n        self.validate_identity(\"\"\"'\"abc\"'\"\"\")\n        self.validate_identity(\n            r\"'\\'a'\",\n            \"'''a'\",\n        )\n        self.validate_identity(\n            '''\"'abc'\"''',\n            \"'''abc'''\",\n        )\n        self.validate_all(\n            r\"'a \\' b '' '\",\n            write={\n                \"mysql\": r\"'a '' b '' '\",\n                \"spark\": r\"'a \\' b \\' '\",\n            },\n        )\n\n        self.validate_identity(\n            r\"'\\\"'\",\n            \"\"\"\\'\"\\'\"\"\",\n        )\n        self.validate_identity(\"'\\\\\\\\\\\"a'\")\n        self.validate_identity(\n            \"'\\t'\",\n            \"'\\\\t'\",\n        )\n        self.validate_identity(\n            r\"'\\j'\",\n            \"'j'\",\n        )\n\n    def test_introducers(self):\n        self.validate_all(\n            \"_utf8mb4 'hola'\",\n            read={\n                \"mysql\": \"_utf8mb4'hola'\",\n            },\n            write={\n                \"mysql\": \"_utf8mb4 'hola'\",\n            },\n        )\n        self.validate_all(\n            \"N'some text'\",\n            read={\n                \"mysql\": \"n'some text'\",\n            },\n            write={\n                \"mysql\": \"N'some text'\",\n            },\n        )\n        self.validate_all(\n            \"_latin1 x'4D7953514C'\",\n            read={\n                \"mysql\": \"_latin1 X'4D7953514C'\",\n            },\n            write={\n                \"mysql\": \"_latin1 x'4D7953514C'\",\n            },\n        )\n\n    def test_hexadecimal_literal(self):\n        write_CC = {\n            \"bigquery\": \"SELECT FROM_HEX('CC')\",\n            \"clickhouse\": UnsupportedError,\n            \"databricks\": \"SELECT X'CC'\",\n            \"drill\": \"SELECT 204\",\n            \"duckdb\": \"SELECT UNHEX('CC')\",\n            \"hive\": \"SELECT 204\",\n            \"mysql\": \"SELECT x'CC'\",\n            \"oracle\": \"SELECT 204\",\n            \"postgres\": \"SELECT x'CC'\",\n            \"presto\": \"SELECT x'CC'\",\n            \"redshift\": \"SELECT 204\",\n            \"snowflake\": \"SELECT x'CC'\",\n            \"spark\": \"SELECT X'CC'\",\n            \"sqlite\": \"SELECT x'CC'\",\n            \"starrocks\": \"SELECT x'CC'\",\n            \"tableau\": \"SELECT 204\",\n            \"teradata\": \"SELECT X'CC'\",\n            \"trino\": \"SELECT x'CC'\",\n            \"tsql\": \"SELECT 0xCC\",\n        }\n        write_CC_with_leading_zeros = {\n            \"bigquery\": \"SELECT FROM_HEX('0000CC')\",\n            \"clickhouse\": UnsupportedError,\n            \"databricks\": \"SELECT X'0000CC'\",\n            \"drill\": \"SELECT 204\",\n            \"duckdb\": \"SELECT UNHEX('0000CC')\",\n            \"hive\": \"SELECT 204\",\n            \"mysql\": \"SELECT x'0000CC'\",\n            \"oracle\": \"SELECT 204\",\n            \"postgres\": \"SELECT x'0000CC'\",\n            \"presto\": \"SELECT x'0000CC'\",\n            \"redshift\": \"SELECT 204\",\n            \"snowflake\": \"SELECT x'0000CC'\",\n            \"spark\": \"SELECT X'0000CC'\",\n            \"sqlite\": \"SELECT x'0000CC'\",\n            \"starrocks\": \"SELECT x'0000CC'\",\n            \"tableau\": \"SELECT 204\",\n            \"teradata\": \"SELECT X'0000CC'\",\n            \"trino\": \"SELECT x'0000CC'\",\n            \"tsql\": \"SELECT 0x0000CC\",\n        }\n\n        self.validate_all(\"SELECT X'1A'\", write={\"mysql\": \"SELECT x'1A'\"})\n        self.validate_all(\"SELECT 0xz\", write={\"mysql\": \"SELECT `0xz`\"})\n        self.validate_all(\"SELECT 0xCC\", write=write_CC)\n        self.validate_all(\"SELECT 0xCC \", write=write_CC)\n        self.validate_all(\"SELECT x'CC'\", write=write_CC)\n        self.validate_all(\"SELECT 0x0000CC\", write=write_CC_with_leading_zeros)\n        self.validate_all(\"SELECT x'0000CC'\", write=write_CC_with_leading_zeros)\n\n    def test_bits_literal(self):\n        write_1011 = {\n            \"bigquery\": \"SELECT 11\",\n            \"clickhouse\": \"SELECT 0b1011\",\n            \"databricks\": \"SELECT 11\",\n            \"drill\": \"SELECT 11\",\n            \"hive\": \"SELECT 11\",\n            \"mysql\": \"SELECT b'1011'\",\n            \"oracle\": \"SELECT 11\",\n            \"postgres\": \"SELECT b'1011'\",\n            \"presto\": \"SELECT 11\",\n            \"redshift\": \"SELECT 11\",\n            \"snowflake\": \"SELECT 11\",\n            \"spark\": \"SELECT 11\",\n            \"sqlite\": \"SELECT 11\",\n            \"tableau\": \"SELECT 11\",\n            \"teradata\": \"SELECT 11\",\n            \"trino\": \"SELECT 11\",\n            \"tsql\": \"SELECT 11\",\n        }\n\n        self.validate_all(\"SELECT 0b1011\", write=write_1011)\n        self.validate_all(\"SELECT b'1011'\", write=write_1011)\n\n    def test_string_literals(self):\n        self.validate_all(\n            'SELECT \"2021-01-01\" + INTERVAL 1 MONTH',\n            write={\n                \"mysql\": \"SELECT '2021-01-01' + INTERVAL '1' MONTH\",\n            },\n        )\n\n    def test_convert(self):\n        self.validate_all(\n            \"CONVERT(x USING latin1)\",\n            write={\n                \"mysql\": \"CAST(x AS CHAR CHARACTER SET latin1)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS CHAR CHARACTER SET latin1)\",\n            write={\n                \"mysql\": \"CAST(x AS CHAR CHARACTER SET latin1)\",\n            },\n        )\n        self.validate_identity(\n            \"CONVERT('a' USING binary)\", \"CAST('a' AS CHAR CHARACTER SET binary)\"\n        )\n\n    def test_match_against(self):\n        self.validate_all(\n            \"MATCH(col1, col2, col3) AGAINST('abc')\",\n            read={\n                \"\": \"MATCH(col1, col2, col3) AGAINST('abc')\",\n                \"mysql\": \"MATCH(col1, col2, col3) AGAINST('abc')\",\n            },\n            write={\n                \"\": \"MATCH(col1, col2, col3) AGAINST('abc')\",\n                \"mysql\": \"MATCH(col1, col2, col3) AGAINST('abc')\",\n                \"postgres\": \"(col1 @@ 'abc' OR col2 @@ 'abc' OR col3 @@ 'abc')\",  # not quite correct because it's not ts_query\n            },\n        )\n        self.validate_all(\n            \"MATCH(col1, col2) AGAINST('abc' IN NATURAL LANGUAGE MODE)\",\n            write={\"mysql\": \"MATCH(col1, col2) AGAINST('abc' IN NATURAL LANGUAGE MODE)\"},\n        )\n        self.validate_all(\n            \"MATCH(col1, col2) AGAINST('abc' IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION)\",\n            write={\n                \"mysql\": \"MATCH(col1, col2) AGAINST('abc' IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION)\"\n            },\n        )\n        self.validate_all(\n            \"MATCH(col1, col2) AGAINST('abc' IN BOOLEAN MODE)\",\n            write={\"mysql\": \"MATCH(col1, col2) AGAINST('abc' IN BOOLEAN MODE)\"},\n        )\n        self.validate_all(\n            \"MATCH(col1, col2) AGAINST('abc' WITH QUERY EXPANSION)\",\n            write={\"mysql\": \"MATCH(col1, col2) AGAINST('abc' WITH QUERY EXPANSION)\"},\n        )\n        self.validate_all(\n            \"MATCH(a.b) AGAINST('abc')\",\n            write={\"mysql\": \"MATCH(a.b) AGAINST('abc')\"},\n        )\n\n    def test_date_format(self):\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2017-06-15', '%Y')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2017-06-15', '%Y')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2017-06-15' AS TIMESTAMP), 'yyyy')\",\n                \"exasol\": \"SELECT TO_CHAR(CAST('2017-06-15' AS TIMESTAMP), 'YYYY')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2017-06-15', '%m')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2017-06-15', '%m')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2017-06-15' AS TIMESTAMP), 'mm')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2017-06-15', '%d')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2017-06-15', '%d')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2017-06-15' AS TIMESTAMP), 'DD')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2017-06-15', '%Y-%m-%d')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2017-06-15', '%Y-%m-%d')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2017-06-15' AS TIMESTAMP), 'yyyy-mm-DD')\",\n                \"exasol\": \"SELECT TO_CHAR(CAST('2017-06-15' AS TIMESTAMP), 'YYYY-MM-DD')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2017-06-15 22:23:34', '%H')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2017-06-15 22:23:34', '%H')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2017-06-15 22:23:34' AS TIMESTAMP), 'hh24')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2017-06-15', '%w')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2017-06-15', '%w')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2017-06-15' AS TIMESTAMP), 'dy')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2024-08-22 14:53:12', '%a')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2024-08-22 14:53:12', '%a')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2024-08-22 14:53:12' AS TIMESTAMP), 'DY')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2009-10-04 22:23:00', '%a %M %Y')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2009-10-04 22:23:00', '%a %M %Y')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2009-10-04 22:23:00' AS TIMESTAMP), 'DY mmmm yyyy')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('2007-10-04 22:23:00', '%H:%i:%s')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('2007-10-04 22:23:00', '%T')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('2007-10-04 22:23:00' AS TIMESTAMP), 'hh24:mi:ss')\",\n                \"exasol\": \"SELECT TO_CHAR(CAST('2007-10-04 22:23:00' AS TIMESTAMP), 'HH:MI:SS')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT('1900-10-04 22:23:00', '%d %y %a %d %m %b')\",\n            write={\n                \"mysql\": \"SELECT DATE_FORMAT('1900-10-04 22:23:00', '%d %y %a %d %m %b')\",\n                \"snowflake\": \"SELECT TO_CHAR(CAST('1900-10-04 22:23:00' AS TIMESTAMP), 'DD yy DY DD mm mon')\",\n            },\n        )\n\n    def test_mysql_time(self):\n        self.validate_identity(\"TIME_STR_TO_UNIX(x)\", \"UNIX_TIMESTAMP(x)\")\n        self.validate_identity(\"SELECT FROM_UNIXTIME(1711366265, '%Y %D %M')\")\n        self.validate_all(\n            \"SELECT TO_DAYS(x)\",\n            write={\n                \"mysql\": \"SELECT (DATEDIFF(x, '0000-01-01') + 1)\",\n                \"presto\": \"SELECT (DATE_DIFF('DAY', CAST(CAST('0000-01-01' AS TIMESTAMP) AS DATE), CAST(CAST(x AS TIMESTAMP) AS DATE)) + 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(x, y)\",\n            read={\n                \"exasol\": \"SELECT DAYS_BETWEEN(x, y)\",\n                \"presto\": \"SELECT DATE_DIFF('DAY', y, x)\",\n                \"redshift\": \"SELECT DATEDIFF(DAY, y, x)\",\n            },\n            write={\n                \"exasol\": \"SELECT DAYS_BETWEEN(x, y)\",\n                \"mysql\": \"SELECT DATEDIFF(x, y)\",\n                \"presto\": \"SELECT DATE_DIFF('DAY', y, x)\",\n                \"redshift\": \"SELECT DATEDIFF(DAY, y, x)\",\n            },\n        )\n        self.validate_all(\n            \"DAYOFYEAR(x)\",\n            write={\n                \"mysql\": \"DAYOFYEAR(x)\",\n                \"\": \"DAY_OF_YEAR(CAST(x AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"DAYOFMONTH(x)\",\n            write={\"mysql\": \"DAYOFMONTH(x)\", \"\": \"DAY_OF_MONTH(CAST(x AS DATE))\"},\n        )\n        self.validate_all(\n            \"DAYOFWEEK(x)\",\n            write={\"mysql\": \"DAYOFWEEK(x)\", \"\": \"DAY_OF_WEEK(CAST(x AS DATE))\"},\n        )\n        self.validate_all(\n            \"WEEKOFYEAR(x)\",\n            write={\"mysql\": \"WEEKOFYEAR(x)\", \"\": \"WEEK_OF_YEAR(CAST(x AS DATE))\"},\n        )\n        self.validate_all(\n            \"DAY(x)\",\n            write={\"mysql\": \"DAY(x)\", \"\": \"DAY(CAST(x AS DATE))\"},\n        )\n        self.validate_all(\n            \"WEEK(x)\",\n            write={\"mysql\": \"WEEK(x)\", \"\": \"WEEK(CAST(x AS DATE))\"},\n        )\n        self.validate_all(\n            \"YEAR(x)\",\n            write={\"mysql\": \"YEAR(x)\", \"\": \"YEAR(CAST(x AS DATE))\"},\n        )\n        self.validate_all(\n            \"DATE(x)\",\n            read={\"\": \"TS_OR_DS_TO_DATE(x)\"},\n        )\n        self.validate_all(\n            \"STR_TO_DATE(x, '%M')\",\n            read={\"\": \"TS_OR_DS_TO_DATE(x, '%B')\"},\n        )\n        self.validate_all(\n            \"STR_TO_DATE(x, '%Y-%m-%d')\",\n            write={\"presto\": \"CAST(DATE_PARSE(x, '%Y-%m-%d') AS DATE)\"},\n        )\n        self.validate_all(\n            \"STR_TO_DATE(x, '%Y-%m-%dT%T')\", write={\"presto\": \"DATE_PARSE(x, '%Y-%m-%dT%T')\"}\n        )\n        self.validate_all(\n            \"SELECT FROM_UNIXTIME(col)\",\n            read={\n                \"postgres\": \"SELECT TO_TIMESTAMP(col)\",\n            },\n            write={\n                \"mysql\": \"SELECT FROM_UNIXTIME(col)\",\n                \"postgres\": \"SELECT TO_TIMESTAMP(col)\",\n                \"redshift\": \"SELECT (TIMESTAMP 'epoch' + col * INTERVAL '1 SECOND')\",\n            },\n        )\n\n        # No timezone, make sure DATETIME captures the correct precision\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15.123456+00:00')\",\n            write_sql=\"SELECT CAST('2023-01-01 13:14:15.123456+00:00' AS DATETIME(6))\",\n        )\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15.123+00:00')\",\n            write_sql=\"SELECT CAST('2023-01-01 13:14:15.123+00:00' AS DATETIME(3))\",\n        )\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15+00:00')\",\n            write_sql=\"SELECT CAST('2023-01-01 13:14:15+00:00' AS DATETIME)\",\n        )\n\n        # With timezone, make sure the TIMESTAMP constructor is used\n        # also TIMESTAMP doesnt have the subsecond precision truncation issue that DATETIME does so we dont need to TIMESTAMP(6)\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15-08:00', 'America/Los_Angeles')\",\n            write_sql=\"SELECT TIMESTAMP('2023-01-01 13:14:15-08:00')\",\n        )\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15-08:00', 'America/Los_Angeles')\",\n            write_sql=\"SELECT TIMESTAMP('2023-01-01 13:14:15-08:00')\",\n        )\n\n    @unittest.skipUnless(\n        sys.version_info >= (3, 11),\n        \"Python 3.11 relaxed datetime.fromisoformat() parsing with regards to microseconds\",\n    )\n    def test_mysql_time_python311(self):\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15.12345+00:00')\",\n            write_sql=\"SELECT CAST('2023-01-01 13:14:15.12345+00:00' AS DATETIME(6))\",\n        )\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15.1234+00:00')\",\n            write_sql=\"SELECT CAST('2023-01-01 13:14:15.1234+00:00' AS DATETIME(6))\",\n        )\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15.12+00:00')\",\n            write_sql=\"SELECT CAST('2023-01-01 13:14:15.12+00:00' AS DATETIME(3))\",\n        )\n        self.validate_identity(\n            \"SELECT TIME_STR_TO_TIME('2023-01-01 13:14:15.1+00:00')\",\n            write_sql=\"SELECT CAST('2023-01-01 13:14:15.1+00:00' AS DATETIME(3))\",\n        )\n\n    def test_mysql(self):\n        for func in (\"CHAR_LENGTH\", \"CHARACTER_LENGTH\"):\n            with self.subTest(f\"Testing MySQL's {func}\"):\n                self.validate_all(\n                    f\"SELECT {func}('foo')\",\n                    write={\n                        \"duckdb\": \"SELECT LENGTH('foo')\",\n                        \"mysql\": \"SELECT CHAR_LENGTH('foo')\",\n                        \"postgres\": \"SELECT LENGTH('foo')\",\n                    },\n                )\n\n        self.validate_all(\n            \"CURDATE()\",\n            write={\n                \"mysql\": \"CURRENT_DATE\",\n                \"postgres\": \"CURRENT_DATE\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CONCAT('11', '22')\",\n            read={\n                \"postgres\": \"SELECT '11' || '22'\",\n            },\n            write={\n                \"mysql\": \"SELECT CONCAT('11', '22')\",\n                \"postgres\": \"SELECT '11' || '22'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT department, GROUP_CONCAT(name) AS employee_names FROM data GROUP BY department\",\n            read={\n                \"postgres\": \"SELECT department, array_agg(name) AS employee_names FROM data GROUP BY department\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNIX_TIMESTAMP(CAST('2024-04-29 12:00:00' AS DATETIME))\",\n            read={\n                \"mysql\": \"SELECT UNIX_TIMESTAMP(CAST('2024-04-29 12:00:00' AS DATETIME))\",\n                \"postgres\": \"SELECT EXTRACT(epoch FROM TIMESTAMP '2024-04-29 12:00:00')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT('[10, 20, [30, 40]]', '$[1]')\",\n            read={\n                \"sqlite\": \"SELECT JSON_EXTRACT('[10, 20, [30, 40]]', '$[1]')\",\n            },\n            write={\n                \"mysql\": \"SELECT JSON_EXTRACT('[10, 20, [30, 40]]', '$[1]')\",\n                \"sqlite\": \"SELECT '[10, 20, [30, 40]]' -> '$[1]'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT('[10, 20, [30, 40]]', '$[1]', '$[0]')\",\n            read={\n                \"sqlite\": \"SELECT JSON_EXTRACT('[10, 20, [30, 40]]', '$[1]', '$[0]')\",\n            },\n            write={\n                \"mysql\": \"SELECT JSON_EXTRACT('[10, 20, [30, 40]]', '$[1]', '$[0]')\",\n                \"sqlite\": \"SELECT JSON_EXTRACT('[10, 20, [30, 40]]', '$[1]', '$[0]')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x LEFT JOIN y ON x.id = y.id UNION ALL SELECT * FROM x RIGHT JOIN y ON x.id = y.id WHERE NOT EXISTS(SELECT 1 FROM x WHERE x.id = y.id) ORDER BY 1 LIMIT 0\",\n            read={\n                \"postgres\": \"SELECT * FROM x FULL JOIN y ON x.id = y.id ORDER BY 1 LIMIT 0\",\n            },\n        )\n        self.validate_all(\n            # MySQL doesn't support FULL OUTER joins\n            \"SELECT * FROM t1 LEFT OUTER JOIN t2 ON t1.x = t2.x UNION ALL SELECT * FROM t1 RIGHT OUTER JOIN t2 ON t1.x = t2.x WHERE NOT EXISTS(SELECT 1 FROM t1 WHERE t1.x = t2.x)\",\n            read={\n                \"postgres\": \"SELECT * FROM t1 FULL OUTER JOIN t2 ON t1.x = t2.x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t1 LEFT OUTER JOIN t2 USING (x) UNION ALL SELECT * FROM t1 RIGHT OUTER JOIN t2 USING (x) WHERE NOT EXISTS(SELECT 1 FROM t1 WHERE t1.x = t2.x)\",\n            read={\n                \"postgres\": \"SELECT * FROM t1 FULL OUTER JOIN t2 USING (x) \",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t1 LEFT OUTER JOIN t2 USING (x, y) UNION ALL SELECT * FROM t1 RIGHT OUTER JOIN t2 USING (x, y) WHERE NOT EXISTS(SELECT 1 FROM t1 WHERE t1.x = t2.x AND t1.y = t2.y)\",\n            read={\n                \"postgres\": \"SELECT * FROM t1 FULL OUTER JOIN t2 USING (x, y) \",\n            },\n        )\n        self.validate_all(\n            \"a XOR b\",\n            read={\n                \"mysql\": \"a XOR b\",\n                \"snowflake\": \"BOOLXOR(a, b)\",\n            },\n            write={\n                \"duckdb\": \"(a AND (NOT b)) OR ((NOT a) AND b)\",\n                \"mysql\": \"a XOR b\",\n                \"postgres\": \"(a AND (NOT b)) OR ((NOT a) AND b)\",\n                \"snowflake\": \"BOOLXOR(a, b)\",\n                \"trino\": \"(a AND (NOT b)) OR ((NOT a) AND b)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM test LIMIT 0 + 1, 0 + 1\",\n            write={\n                \"mysql\": \"SELECT * FROM test LIMIT 1 OFFSET 1\",\n                \"postgres\": \"SELECT * FROM test LIMIT 0 + 1 OFFSET 0 + 1\",\n                \"presto\": \"SELECT * FROM test OFFSET 1 LIMIT 1\",\n                \"snowflake\": \"SELECT * FROM test LIMIT 1 OFFSET 1\",\n                \"trino\": \"SELECT * FROM test OFFSET 1 LIMIT 1\",\n                \"bigquery\": \"SELECT * FROM test LIMIT 1 OFFSET 1\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS TEXT)\",\n            write={\n                \"mysql\": \"CAST(x AS CHAR)\",\n                \"presto\": \"CAST(x AS VARCHAR)\",\n                \"starrocks\": \"CAST(x AS STRING)\",\n            },\n        )\n        self.validate_all(\"CAST(x AS SIGNED)\", write={\"mysql\": \"CAST(x AS SIGNED)\"})\n        self.validate_all(\"CAST(x AS SIGNED INTEGER)\", write={\"mysql\": \"CAST(x AS SIGNED)\"})\n        self.validate_all(\"CAST(x AS UNSIGNED)\", write={\"mysql\": \"CAST(x AS UNSIGNED)\"})\n        self.validate_all(\"CAST(x AS UNSIGNED INTEGER)\", write={\"mysql\": \"CAST(x AS UNSIGNED)\"})\n        self.validate_all(\"TIME_STR_TO_TIME(x)\", write={\"mysql\": \"CAST(x AS DATETIME)\"})\n        self.validate_all(\n            \"\"\"SELECT 17 MEMBER OF('[23, \"abc\", 17, \"ab\", 10]')\"\"\",\n            write={\n                \"\": \"\"\"SELECT JSON_ARRAY_CONTAINS(17, '[23, \"abc\", 17, \"ab\", 10]')\"\"\",\n                \"mysql\": \"\"\"SELECT 17 MEMBER OF('[23, \"abc\", 17, \"ab\", 10]')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_ADD('2023-06-23 12:00:00', INTERVAL 2 * 2 MONTH) FROM foo\",\n            write={\n                \"mysql\": \"SELECT DATE_ADD('2023-06-23 12:00:00', INTERVAL (2 * 2) MONTH) FROM foo\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t LOCK IN SHARE MODE\", write={\"mysql\": \"SELECT * FROM t FOR SHARE\"}\n        )\n        self.validate_all(\n            \"SELECT DATE(DATE_SUB(`dt`, INTERVAL DAYOFMONTH(`dt`) - 1 DAY)) AS __timestamp FROM tableT\",\n            write={\n                \"mysql\": \"SELECT DATE(DATE_SUB(`dt`, INTERVAL (DAYOFMONTH(`dt`) - 1) DAY)) AS __timestamp FROM tableT\",\n            },\n        )\n        self.validate_identity(\"SELECT name FROM temp WHERE name = ? FOR UPDATE\")\n        self.validate_all(\n            \"SELECT a FROM tbl FOR UPDATE\",\n            write={\n                \"\": \"SELECT a FROM tbl\",\n                \"mysql\": \"SELECT a FROM tbl FOR UPDATE\",\n                \"oracle\": \"SELECT a FROM tbl FOR UPDATE\",\n                \"postgres\": \"SELECT a FROM tbl FOR UPDATE\",\n                \"redshift\": \"SELECT a FROM tbl\",\n                \"tsql\": \"SELECT a FROM tbl\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM tbl FOR SHARE\",\n            write={\n                \"\": \"SELECT a FROM tbl\",\n                \"mysql\": \"SELECT a FROM tbl FOR SHARE\",\n                \"oracle\": \"SELECT a FROM tbl FOR SHARE\",\n                \"postgres\": \"SELECT a FROM tbl FOR SHARE\",\n                \"tsql\": \"SELECT a FROM tbl\",\n            },\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(DISTINCT x ORDER BY y DESC)\",\n            write={\n                \"mysql\": \"GROUP_CONCAT(DISTINCT x ORDER BY y DESC SEPARATOR ',')\",\n                \"sqlite\": \"GROUP_CONCAT(DISTINCT x)\",\n                \"tsql\": \"STRING_AGG(x, ',') WITHIN GROUP (ORDER BY y DESC)\",\n                \"databricks\": \"LISTAGG(DISTINCT x, ',') WITHIN GROUP (ORDER BY y DESC)\",\n                \"postgres\": \"STRING_AGG(DISTINCT x, ',' ORDER BY y DESC NULLS LAST)\",\n            },\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(x ORDER BY y SEPARATOR z)\",\n            write={\n                \"mysql\": \"GROUP_CONCAT(x ORDER BY y SEPARATOR z)\",\n                \"sqlite\": \"GROUP_CONCAT(x, z)\",\n                \"tsql\": \"STRING_AGG(x, z) WITHIN GROUP (ORDER BY y)\",\n                \"databricks\": \"LISTAGG(x, z) WITHIN GROUP (ORDER BY y)\",\n                \"postgres\": \"STRING_AGG(x, z ORDER BY y NULLS FIRST)\",\n            },\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(DISTINCT x ORDER BY y DESC SEPARATOR '')\",\n            write={\n                \"mysql\": \"GROUP_CONCAT(DISTINCT x ORDER BY y DESC SEPARATOR '')\",\n                \"sqlite\": \"GROUP_CONCAT(DISTINCT x, '')\",\n                \"tsql\": \"STRING_AGG(x, '') WITHIN GROUP (ORDER BY y DESC)\",\n                \"databricks\": \"LISTAGG(DISTINCT x, '') WITHIN GROUP (ORDER BY y DESC)\",\n                \"postgres\": \"STRING_AGG(DISTINCT x, '' ORDER BY y DESC NULLS LAST)\",\n            },\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(a, b, c SEPARATOR ',')\",\n            write={\n                \"mysql\": \"GROUP_CONCAT(CONCAT(a, b, c) SEPARATOR ',')\",\n                \"sqlite\": \"GROUP_CONCAT(a || b || c, ',')\",\n                \"tsql\": \"STRING_AGG(a + b + c, ',')\",\n                \"postgres\": \"STRING_AGG(a || b || c, ',')\",\n                \"databricks\": \"LISTAGG(CONCAT(a, b, c), ',')\",\n                \"presto\": \"ARRAY_JOIN(ARRAY_AGG(CONCAT(CAST(a AS VARCHAR), CAST(b AS VARCHAR), CAST(c AS VARCHAR))), ',')\",\n            },\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(a, b, c SEPARATOR '')\",\n            write={\n                \"mysql\": \"GROUP_CONCAT(CONCAT(a, b, c) SEPARATOR '')\",\n                \"sqlite\": \"GROUP_CONCAT(a || b || c, '')\",\n                \"tsql\": \"STRING_AGG(a + b + c, '')\",\n                \"databricks\": \"LISTAGG(CONCAT(a, b, c), '')\",\n                \"postgres\": \"STRING_AGG(a || b || c, '')\",\n            },\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(DISTINCT a, b, c SEPARATOR '')\",\n            write={\n                \"mysql\": \"GROUP_CONCAT(DISTINCT CONCAT(a, b, c) SEPARATOR '')\",\n                \"sqlite\": \"GROUP_CONCAT(DISTINCT a || b || c, '')\",\n                \"tsql\": \"STRING_AGG(a + b + c, '')\",\n                \"databricks\": \"LISTAGG(DISTINCT CONCAT(a, b, c), '')\",\n                \"postgres\": \"STRING_AGG(DISTINCT a || b || c, '')\",\n            },\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(a, b, c ORDER BY d SEPARATOR '')\",\n            write={\n                \"mysql\": \"GROUP_CONCAT(CONCAT(a, b, c) ORDER BY d SEPARATOR '')\",\n                \"sqlite\": \"GROUP_CONCAT(a || b || c, '')\",\n                \"tsql\": \"STRING_AGG(a + b + c, '') WITHIN GROUP (ORDER BY d)\",\n                \"databricks\": \"LISTAGG(CONCAT(a, b, c), '') WITHIN GROUP (ORDER BY d)\",\n                \"postgres\": \"STRING_AGG(a || b || c, '' ORDER BY d NULLS FIRST)\",\n            },\n        )\n        self.validate_all(\n            \"GROUP_CONCAT(DISTINCT a, b, c ORDER BY d SEPARATOR '')\",\n            write={\n                \"mysql\": \"GROUP_CONCAT(DISTINCT CONCAT(a, b, c) ORDER BY d SEPARATOR '')\",\n                \"sqlite\": \"GROUP_CONCAT(DISTINCT a || b || c, '')\",\n                \"tsql\": \"STRING_AGG(a + b + c, '') WITHIN GROUP (ORDER BY d)\",\n                \"databricks\": \"LISTAGG(DISTINCT CONCAT(a, b, c), '') WITHIN GROUP (ORDER BY d)\",\n                \"postgres\": \"STRING_AGG(DISTINCT a || b || c, '' ORDER BY d NULLS FIRST)\",\n            },\n        )\n        self.validate_identity(\n            \"CREATE TABLE z (a INT) ENGINE=InnoDB AUTO_INCREMENT=1 CHARACTER SET=utf8 COLLATE=utf8_bin COMMENT='x'\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE z (a INT) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_bin COMMENT='x'\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE z (a INT DEFAULT NULL, PRIMARY KEY (a)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_bin COMMENT='x'\"\n        )\n\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE `t_customer_account` (\n              `id` int(11) NOT NULL AUTO_INCREMENT,\n              `customer_id` int(11) DEFAULT NULL COMMENT '客户id',\n              `bank` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '行别',\n              `account_no` varchar(100) COLLATE utf8_bin DEFAULT NULL COMMENT '账号',\n              PRIMARY KEY (`id`)\n            ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARACTER SET=utf8 COLLATE=utf8_bin COMMENT='客户账户表'\n            \"\"\",\n            write={\n                \"mysql\": \"\"\"CREATE TABLE `t_customer_account` (\n  `id` INT(11) NOT NULL AUTO_INCREMENT,\n  `customer_id` INT(11) DEFAULT NULL COMMENT '客户id',\n  `bank` VARCHAR(100) COLLATE utf8_bin DEFAULT NULL COMMENT '行别',\n  `account_no` VARCHAR(100) COLLATE utf8_bin DEFAULT NULL COMMENT '账号',\n  PRIMARY KEY (`id`)\n)\nENGINE=InnoDB\nAUTO_INCREMENT=1\nDEFAULT CHARACTER SET=utf8\nCOLLATE=utf8_bin\nCOMMENT='客户账户表'\"\"\"\n            },\n            pretty=True,\n        )\n\n    def test_show_simple(self):\n        for key, write_key in [\n            (\"BINARY LOGS\", \"BINARY LOGS\"),\n            (\"MASTER LOGS\", \"BINARY LOGS\"),\n            (\"STORAGE ENGINES\", \"ENGINES\"),\n            (\"ENGINES\", \"ENGINES\"),\n            (\"EVENTS\", \"EVENTS\"),\n            (\"MASTER STATUS\", \"MASTER STATUS\"),\n            (\"PLUGINS\", \"PLUGINS\"),\n            (\"PRIVILEGES\", \"PRIVILEGES\"),\n            (\"PROFILES\", \"PROFILES\"),\n            (\"REPLICAS\", \"REPLICAS\"),\n            (\"SLAVE HOSTS\", \"REPLICAS\"),\n        ]:\n            show = self.validate_identity(f\"SHOW {key}\", f\"SHOW {write_key}\")\n            self.assertIsInstance(show, exp.Show)\n            self.assertEqual(show.name, write_key)\n\n    def test_show_events(self):\n        for key in [\"BINLOG\", \"RELAYLOG\"]:\n            show = self.validate_identity(f\"SHOW {key} EVENTS\")\n            self.assertIsInstance(show, exp.Show)\n            self.assertEqual(show.name, f\"{key} EVENTS\")\n\n            show = self.validate_identity(f\"SHOW {key} EVENTS IN 'log' FROM 1 LIMIT 2, 3\")\n            self.assertEqual(show.text(\"log\"), \"log\")\n            self.assertEqual(show.text(\"position\"), \"1\")\n            self.assertEqual(show.text(\"limit\"), \"3\")\n            self.assertEqual(show.text(\"offset\"), \"2\")\n\n            show = self.validate_identity(f\"SHOW {key} EVENTS LIMIT 1\")\n            self.assertEqual(show.text(\"limit\"), \"1\")\n            self.assertIsNone(show.args.get(\"offset\"))\n\n    def test_show_like_or_where(self):\n        for key, write_key in [\n            (\"CHARSET\", \"CHARACTER SET\"),\n            (\"CHARACTER SET\", \"CHARACTER SET\"),\n            (\"COLLATION\", \"COLLATION\"),\n            (\"DATABASES\", \"DATABASES\"),\n            (\"SCHEMAS\", \"DATABASES\"),\n            (\"FUNCTION STATUS\", \"FUNCTION STATUS\"),\n            (\"PROCEDURE STATUS\", \"PROCEDURE STATUS\"),\n            (\"GLOBAL STATUS\", \"GLOBAL STATUS\"),\n            (\"SESSION STATUS\", \"STATUS\"),\n            (\"STATUS\", \"STATUS\"),\n            (\"GLOBAL VARIABLES\", \"GLOBAL VARIABLES\"),\n            (\"SESSION VARIABLES\", \"VARIABLES\"),\n            (\"VARIABLES\", \"VARIABLES\"),\n        ]:\n            expected_name = write_key.strip(\"GLOBAL\").strip()\n            template = \"SHOW {}\"\n            show = self.validate_identity(template.format(key), template.format(write_key))\n            self.assertIsInstance(show, exp.Show)\n            self.assertEqual(show.name, expected_name)\n\n            template = \"SHOW {} LIKE '%foo%'\"\n            show = self.validate_identity(template.format(key), template.format(write_key))\n            self.assertIsInstance(show, exp.Show)\n            self.assertIsInstance(show.args[\"like\"], exp.Literal)\n            self.assertEqual(show.text(\"like\"), \"%foo%\")\n\n            template = \"SHOW {} WHERE Column_name LIKE '%foo%'\"\n            show = self.validate_identity(template.format(key), template.format(write_key))\n            self.assertIsInstance(show, exp.Show)\n            self.assertIsInstance(show.args[\"where\"], exp.Where)\n            self.assertEqual(show.args[\"where\"].sql(), \"WHERE Column_name LIKE '%foo%'\")\n\n    def test_show_columns(self):\n        show = self.validate_identity(\"SHOW COLUMNS FROM tbl_name\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"COLUMNS\")\n        self.assertEqual(show.text(\"target\"), \"tbl_name\")\n        self.assertFalse(show.args[\"full\"])\n\n        show = self.validate_identity(\"SHOW FULL COLUMNS FROM tbl_name FROM db_name LIKE '%foo%'\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.text(\"target\"), \"tbl_name\")\n        self.assertTrue(show.args[\"full\"])\n        self.assertEqual(show.text(\"db\"), \"db_name\")\n        self.assertIsInstance(show.args[\"like\"], exp.Literal)\n        self.assertEqual(show.text(\"like\"), \"%foo%\")\n\n    def test_show_name(self):\n        for key in [\n            \"CREATE DATABASE\",\n            \"CREATE EVENT\",\n            \"CREATE FUNCTION\",\n            \"CREATE PROCEDURE\",\n            \"CREATE TABLE\",\n            \"CREATE TRIGGER\",\n            \"CREATE VIEW\",\n            \"FUNCTION CODE\",\n            \"PROCEDURE CODE\",\n        ]:\n            show = self.validate_identity(f\"SHOW {key} foo\")\n            self.assertIsInstance(show, exp.Show)\n            self.assertEqual(show.name, key)\n            self.assertEqual(show.text(\"target\"), \"foo\")\n\n    def test_show_grants(self):\n        show = self.validate_identity(\"SHOW GRANTS FOR foo\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"GRANTS\")\n        self.assertEqual(show.text(\"target\"), \"foo\")\n\n    def test_show_engine(self):\n        show = self.validate_identity(\"SHOW ENGINE foo STATUS\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"ENGINE\")\n        self.assertEqual(show.text(\"target\"), \"foo\")\n        self.assertFalse(show.args[\"mutex\"])\n\n        show = self.validate_identity(\"SHOW ENGINE foo MUTEX\")\n        self.assertEqual(show.name, \"ENGINE\")\n        self.assertEqual(show.text(\"target\"), \"foo\")\n        self.assertTrue(show.args[\"mutex\"])\n\n    def test_show_errors(self):\n        for key in [\"ERRORS\", \"WARNINGS\"]:\n            show = self.validate_identity(f\"SHOW {key}\")\n            self.assertIsInstance(show, exp.Show)\n            self.assertEqual(show.name, key)\n\n            show = self.validate_identity(f\"SHOW {key} LIMIT 2, 3\")\n            self.assertEqual(show.text(\"limit\"), \"3\")\n            self.assertEqual(show.text(\"offset\"), \"2\")\n\n    def test_show_index(self):\n        show = self.validate_identity(\"SHOW INDEX FROM foo\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"INDEX\")\n        self.assertEqual(show.text(\"target\"), \"foo\")\n\n        show = self.validate_identity(\"SHOW INDEX FROM foo FROM bar\")\n        self.assertEqual(show.text(\"db\"), \"bar\")\n\n        self.validate_all(\n            \"SHOW INDEX FROM bar.foo\", write={\"mysql\": \"SHOW INDEX FROM foo FROM bar\"}\n        )\n\n    def test_show_db_like_or_where_sql(self):\n        for key in [\n            \"OPEN TABLES\",\n            \"TABLE STATUS\",\n            \"TRIGGERS\",\n        ]:\n            show = self.validate_identity(f\"SHOW {key}\")\n            self.assertIsInstance(show, exp.Show)\n            self.assertEqual(show.name, key)\n\n            show = self.validate_identity(f\"SHOW {key} FROM db_name\")\n            self.assertEqual(show.name, key)\n            self.assertEqual(show.text(\"db\"), \"db_name\")\n\n            show = self.validate_identity(f\"SHOW {key} LIKE '%foo%'\")\n            self.assertEqual(show.name, key)\n            self.assertIsInstance(show.args[\"like\"], exp.Literal)\n            self.assertEqual(show.text(\"like\"), \"%foo%\")\n\n            show = self.validate_identity(f\"SHOW {key} WHERE Column_name LIKE '%foo%'\")\n            self.assertEqual(show.name, key)\n            self.assertIsInstance(show.args[\"where\"], exp.Where)\n            self.assertEqual(show.args[\"where\"].sql(), \"WHERE Column_name LIKE '%foo%'\")\n\n    def test_show_processlist(self):\n        show = self.validate_identity(\"SHOW PROCESSLIST\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"PROCESSLIST\")\n        self.assertFalse(show.args[\"full\"])\n\n        show = self.validate_identity(\"SHOW FULL PROCESSLIST\")\n        self.assertEqual(show.name, \"PROCESSLIST\")\n        self.assertTrue(show.args[\"full\"])\n\n    def test_show_profile(self):\n        show = self.validate_identity(\"SHOW PROFILE\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"PROFILE\")\n\n        show = self.validate_identity(\"SHOW PROFILE BLOCK IO\")\n        self.assertEqual(show.args[\"types\"][0].name, \"BLOCK IO\")\n\n        show = self.validate_identity(\n            \"SHOW PROFILE BLOCK IO, PAGE FAULTS FOR QUERY 1 OFFSET 2 LIMIT 3\"\n        )\n        self.assertEqual(show.args[\"types\"][0].name, \"BLOCK IO\")\n        self.assertEqual(show.args[\"types\"][1].name, \"PAGE FAULTS\")\n        self.assertEqual(show.text(\"query\"), \"1\")\n        self.assertEqual(show.text(\"offset\"), \"2\")\n        self.assertEqual(show.text(\"limit\"), \"3\")\n\n    def test_show_replica_status(self):\n        show = self.validate_identity(\"SHOW REPLICA STATUS\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"REPLICA STATUS\")\n\n        show = self.validate_identity(\"SHOW SLAVE STATUS\", \"SHOW REPLICA STATUS\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"REPLICA STATUS\")\n\n        show = self.validate_identity(\"SHOW REPLICA STATUS FOR CHANNEL channel_name\")\n        self.assertEqual(show.text(\"channel\"), \"channel_name\")\n\n    def test_show_tables(self):\n        show = self.validate_identity(\"SHOW TABLES\")\n        self.assertIsInstance(show, exp.Show)\n        self.assertEqual(show.name, \"TABLES\")\n\n        show = self.validate_identity(\"SHOW FULL TABLES FROM db_name LIKE '%foo%'\")\n        self.assertTrue(show.args[\"full\"])\n        self.assertEqual(show.text(\"db\"), \"db_name\")\n        self.assertIsInstance(show.args[\"like\"], exp.Literal)\n        self.assertEqual(show.text(\"like\"), \"%foo%\")\n\n    def test_set_variable(self):\n        cmd = self.parse_one(\"SET SESSION x = 1\")\n        item = cmd.expressions[0]\n        self.assertEqual(item.text(\"kind\"), \"SESSION\")\n        self.assertIsInstance(item.this, exp.EQ)\n        self.assertEqual(item.this.left.name, \"x\")\n        self.assertEqual(item.this.right.name, \"1\")\n\n        cmd = self.parse_one(\"SET @@GLOBAL.x = @@GLOBAL.y\")\n        item = cmd.expressions[0]\n        self.assertEqual(item.text(\"kind\"), \"\")\n        self.assertIsInstance(item.this, exp.EQ)\n        self.assertIsInstance(item.this.left, exp.SessionParameter)\n        self.assertIsInstance(item.this.right, exp.SessionParameter)\n\n        cmd = self.parse_one(\"SET NAMES 'charset_name' COLLATE 'collation_name'\")\n        item = cmd.expressions[0]\n        self.assertEqual(item.text(\"kind\"), \"NAMES\")\n        self.assertEqual(item.name, \"charset_name\")\n        self.assertEqual(item.text(\"collate\"), \"collation_name\")\n\n        cmd = self.parse_one(\"SET CHARSET DEFAULT\")\n        item = cmd.expressions[0]\n        self.assertEqual(item.text(\"kind\"), \"CHARACTER SET\")\n        self.assertEqual(item.this.name, \"DEFAULT\")\n\n        cmd = self.parse_one(\"SET x = 1, y = 2\")\n        self.assertEqual(len(cmd.expressions), 2)\n\n    def test_json_object(self):\n        self.validate_identity(\"SELECT JSON_OBJECT('id', 87, 'name', 'carrot')\")\n\n    def test_is_null(self):\n        self.validate_all(\n            \"SELECT ISNULL(x)\", write={\"\": \"SELECT (x IS NULL)\", \"mysql\": \"SELECT (x IS NULL)\"}\n        )\n\n    def test_monthname(self):\n        self.validate_all(\n            \"MONTHNAME(x)\",\n            write={\n                \"\": \"TIME_TO_STR(CAST(x AS DATE), '%B')\",\n                \"mysql\": \"DATE_FORMAT(x, '%M')\",\n            },\n        )\n\n    def test_safe_div(self):\n        self.validate_all(\n            \"a / b\",\n            write={\n                \"bigquery\": \"a / NULLIF(b, 0)\",\n                \"clickhouse\": \"a / b\",\n                \"databricks\": \"a / NULLIF(b, 0)\",\n                \"duckdb\": \"a / b\",\n                \"hive\": \"a / b\",\n                \"mysql\": \"a / b\",\n                \"oracle\": \"a / NULLIF(b, 0)\",\n                \"snowflake\": \"a / NULLIF(b, 0)\",\n                \"spark\": \"a / b\",\n                \"starrocks\": \"a / b\",\n                \"drill\": \"CAST(a AS DOUBLE) / NULLIF(b, 0)\",\n                \"postgres\": \"CAST(a AS DOUBLE PRECISION) / NULLIF(b, 0)\",\n                \"presto\": \"CAST(a AS DOUBLE) / NULLIF(b, 0)\",\n                \"redshift\": \"CAST(a AS DOUBLE PRECISION) / NULLIF(b, 0)\",\n                \"sqlite\": \"CAST(a AS REAL) / b\",\n                \"teradata\": \"CAST(a AS DOUBLE PRECISION) / NULLIF(b, 0)\",\n                \"trino\": \"CAST(a AS DOUBLE) / NULLIF(b, 0)\",\n                \"tsql\": \"CAST(a AS FLOAT) / NULLIF(b, 0)\",\n            },\n        )\n\n    def test_timestamp_trunc(self):\n        hive_dialects = (\"spark\", \"databricks\")\n        for dialect in (\"postgres\", \"snowflake\", *hive_dialects):\n            for unit in (\n                \"SECOND\",\n                \"DAY\",\n                \"MONTH\",\n                \"YEAR\",\n            ):\n                with self.subTest(f\"MySQL -> {dialect} Timestamp Trunc with unit {unit}: \"):\n                    cast = (\n                        \"TIMESTAMP('2001-02-16 20:38:40')\"\n                        if dialect in hive_dialects\n                        else \"CAST('2001-02-16 20:38:40' AS DATETIME)\"\n                    )\n                    self.validate_all(\n                        f\"DATE_ADD('0000-01-01 00:00:00', INTERVAL (TIMESTAMPDIFF({unit}, '0000-01-01 00:00:00', {cast})) {unit})\",\n                        read={\n                            dialect: f\"DATE_TRUNC({unit}, TIMESTAMP '2001-02-16 20:38:40')\",\n                        },\n                        write={\n                            \"mysql\": f\"DATE_ADD('0000-01-01 00:00:00', INTERVAL (TIMESTAMPDIFF({unit}, '0000-01-01 00:00:00', {cast})) {unit})\",\n                        },\n                    )\n\n    def test_at_time_zone(self):\n        with self.assertLogs() as cm:\n            # Check AT TIME ZONE doesnt discard the column name and also raises a warning\n            self.validate_identity(\n                \"SELECT foo AT TIME ZONE 'UTC'\",\n                write_sql=\"SELECT foo\",\n            )\n            assert \"AT TIME ZONE is not supported\" in cm.output[0]\n\n    def test_json_value(self):\n        json_doc = \"\"\"'{\"item\": \"shoes\", \"price\": \"49.95\"}'\"\"\"\n        self.validate_identity(f\"\"\"SELECT JSON_VALUE({json_doc}, '$.price')\"\"\")\n        self.validate_identity(\n            f\"\"\"SELECT JSON_VALUE({json_doc}, '$.price' RETURNING DECIMAL(4, 2))\"\"\"\n        )\n\n        for on_option in (\"NULL\", \"ERROR\", \"DEFAULT 1\"):\n            self.validate_identity(\n                f\"\"\"SELECT JSON_VALUE({json_doc}, '$.price' RETURNING DECIMAL(4, 2) {on_option} ON EMPTY {on_option} ON ERROR) AS price\"\"\"\n            )\n\n    def test_grant(self):\n        grant_cmds = [\n            \"GRANT 'role1', 'role2' TO 'user1'@'localhost', 'user2'@'localhost'\",\n            \"GRANT SELECT ON world.* TO 'role3'\",\n            \"GRANT SELECT ON db2.invoice TO 'jeffrey'@'localhost'\",\n            \"GRANT INSERT ON `d%`.* TO u\",\n            \"GRANT ALL ON test.* TO ''@'localhost'\",\n            \"GRANT SELECT (col1), INSERT (col1, col2) ON mydb.mytbl TO 'someuser'@'somehost'\",\n            \"GRANT SELECT, INSERT, UPDATE ON *.* TO u2\",\n        ]\n\n        for sql in grant_cmds:\n            with self.subTest(f\"Testing MySQL's GRANT command statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n    def test_revoke(self):\n        revoke_cmds = [\n            \"REVOKE 'role1', 'role2' FROM 'user1'@'localhost', 'user2'@'localhost'\",\n            \"REVOKE SELECT ON world.* FROM 'role3'\",\n            \"REVOKE SELECT ON db2.invoice FROM 'jeffrey'@'localhost'\",\n            \"REVOKE INSERT ON `d%`.* FROM u\",\n            \"REVOKE ALL ON test.* FROM ''@'localhost'\",\n            \"REVOKE SELECT (col1), INSERT (col1, col2) ON mydb.mytbl FROM 'someuser'@'somehost'\",\n            \"REVOKE SELECT, INSERT, UPDATE ON *.* FROM u2\",\n        ]\n\n        for sql in revoke_cmds:\n            with self.subTest(f\"Testing MySQL's REVOKE command statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n    def test_explain(self):\n        self.validate_identity(\n            \"EXPLAIN ANALYZE SELECT * FROM t\", \"DESCRIBE ANALYZE SELECT * FROM t\"\n        )\n\n        expression = self.parse_one(\"EXPLAIN ANALYZE SELECT * FROM t\")\n        self.assertIsInstance(expression, exp.Describe)\n        self.assertEqual(expression.text(\"style\"), \"ANALYZE\")\n\n        for format in (\"JSON\", \"TRADITIONAL\", \"TREE\"):\n            self.validate_identity(f\"DESCRIBE FORMAT={format} UPDATE test SET test_col = 'abc'\")\n\n    def test_number_format(self):\n        self.validate_all(\n            \"SELECT FORMAT(12332.123456, 4)\",\n            write={\n                \"duckdb\": \"SELECT FORMAT('{:,.4f}', 12332.123456)\",\n                \"mysql\": \"SELECT FORMAT(12332.123456, 4)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT(12332.1, 4)\",\n            write={\n                \"duckdb\": \"SELECT FORMAT('{:,.4f}', 12332.1)\",\n                \"mysql\": \"SELECT FORMAT(12332.1, 4)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT(12332.2, 0)\",\n            write={\n                \"duckdb\": \"SELECT FORMAT('{:,.0f}', 12332.2)\",\n                \"mysql\": \"SELECT FORMAT(12332.2, 0)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT(12332.2, 2, 'de_DE')\",\n            write={\n                \"duckdb\": UnsupportedError,\n                \"mysql\": \"SELECT FORMAT(12332.2, 2, 'de_DE')\",\n            },\n        )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE LOCAL TABLE tbl\")\n        self.validate_identity(\"ANALYZE NO_WRITE_TO_BINLOG TABLE tbl\")\n        self.validate_identity(\"ANALYZE tbl UPDATE HISTOGRAM ON col1\")\n        self.validate_identity(\"ANALYZE tbl UPDATE HISTOGRAM ON col1 USING DATA 'json_data'\")\n        self.validate_identity(\"ANALYZE tbl UPDATE HISTOGRAM ON col1 WITH 5 BUCKETS\")\n        self.validate_identity(\"ANALYZE tbl UPDATE HISTOGRAM ON col1 WITH 5 BUCKETS AUTO UPDATE\")\n        self.validate_identity(\"ANALYZE tbl UPDATE HISTOGRAM ON col1 WITH 5 BUCKETS MANUAL UPDATE\")\n        self.validate_identity(\"ANALYZE tbl DROP HISTOGRAM ON col1\")\n\n    def test_utc_time(self):\n        self.validate_identity(\"UTC_TIME()\").assert_is(exp.UtcTime)\n        self.validate_identity(\"UTC_TIME(6)\").assert_is(exp.UtcTime)\n        self.validate_identity(\"UTC_TIMESTAMP()\").assert_is(exp.UtcTimestamp)\n        self.validate_identity(\"UTC_TIMESTAMP(6)\").assert_is(exp.UtcTimestamp)\n\n    def test_mod(self):\n        self.validate_identity(\"x % y\").assert_is(exp.Mod)\n        self.validate_identity(\"x MOD y\", \"x % y\").assert_is(exp.Mod)\n        self.validate_identity(\"MOD(x, y)\", \"x % y\").assert_is(exp.Mod)\n\n    def test_numeric_trunc(self):\n        # MySQL uses TRUNCATE for numeric truncation\n        self.validate_identity(\"TRUNCATE(3.14159, 2)\").assert_is(exp.Trunc)\n        self.validate_identity(\"TRUNCATE(price, 0)\").assert_is(exp.Trunc)\n\n        # TRUNC alias normalizes to TRUNCATE in MySQL\n        self.validate_identity(\"TRUNC(3.14159, 2)\", \"TRUNCATE(3.14159, 2)\").assert_is(exp.Trunc)\n\n        # Cross-dialect numeric truncation transpilation\n        self.validate_all(\n            \"TRUNCATE(3.14159, 2)\",\n            write={\n                \"mysql\": \"TRUNCATE(3.14159, 2)\",\n                \"oracle\": \"TRUNC(3.14159, 2)\",\n                \"postgres\": \"TRUNC(3.14159, 2)\",\n                \"snowflake\": \"TRUNC(3.14159, 2)\",\n                \"tsql\": \"ROUND(3.14159, 2, 1)\",\n            },\n        )\n\n    def test_valid_interval_units(self):\n        for unit in (\n            \"SECOND_MICROSECOND\",\n            \"MINUTE_MICROSECOND\",\n            \"MINUTE_SECOND\",\n            \"HOUR_MICROSECOND\",\n            \"HOUR_SECOND\",\n            \"HOUR_MINUTE\",\n            \"DAY_MICROSECOND\",\n            \"DAY_SECOND\",\n            \"DAY_MINUTE\",\n            \"DAY_HOUR\",\n            \"YEAR_MONTH\",\n        ):\n            with self.subTest(f\"Testing INTERVAL unit: {unit}\"):\n                self.validate_identity(f\"DATE_ADD(base_date, INTERVAL day_interval {unit})\")\n\n    def test_create_trigger(self):\n        \"\"\"Test that MySQL CREATE TRIGGER statements fall back to Command parsing.\"\"\"\n        self.validate_identity(\n            \"CREATE TRIGGER check_age BEFORE INSERT ON users FOR EACH ROW BEGIN SET NEW.created_at = NOW() END\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"CREATE TRIGGER audit_update AFTER UPDATE ON accounts FOR EACH ROW BEGIN INSERT INTO audit_log (user_id, old_balance, new_balance, changed_at) VALUES (OLD.user_id, OLD.balance, NEW.balance, NOW()) END\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"CREATE TRIGGER track_deletes BEFORE DELETE ON orders FOR EACH ROW BEGIN UPDATE statistics SET delete_count = delete_count + 1 WHERE table_name = 'orders' END\",\n            check_command_warning=True,\n        )\n"
  },
  {
    "path": "tests/dialects/test_oracle.py",
    "content": "from sqlglot import exp, UnsupportedError, ParseError, parse, parse_one\nfrom tests.dialects.test_dialect import Validator\nfrom sqlglot.optimizer.qualify import qualify\n\n\nclass TestOracle(Validator):\n    dialect = \"oracle\"\n\n    def test_oracle(self):\n        self.validate_identity(\"1 /* /* */\", \"1 /* / * */\")\n        self.validate_all(\n            \"SELECT CONNECT_BY_ROOT x y\",\n            write={\n                \"\": \"SELECT CONNECT_BY_ROOT x AS y\",\n                \"oracle\": \"SELECT CONNECT_BY_ROOT x AS y\",\n            },\n        )\n        self.parse_one(\"ALTER TABLE tbl_name DROP FOREIGN KEY fk_symbol\").assert_is(exp.Alter)\n\n        self.validate_identity(\"XMLELEMENT(EVALNAME foo + bar)\")\n        self.validate_identity(\"SELECT BITMAP_BUCKET_NUMBER(32769)\")\n        self.validate_identity(\"SELECT BITMAP_CONSTRUCT_AGG(value)\")\n        self.validate_identity(\"DBMS_RANDOM.NORMAL\")\n        self.validate_identity(\"DBMS_RANDOM.VALUE(low, high)\").assert_is(exp.Rand)\n        self.validate_identity(\"DBMS_RANDOM.VALUE()\").assert_is(exp.Rand)\n        self.validate_identity(\"CAST(value AS NUMBER DEFAULT 0 ON CONVERSION ERROR)\")\n        self.validate_identity(\"SYSDATE\")\n        self.validate_identity(\"CREATE GLOBAL TEMPORARY TABLE t AS SELECT * FROM orders\")\n        self.validate_identity(\"CREATE PRIVATE TEMPORARY TABLE t AS SELECT * FROM orders\")\n        self.validate_identity(\"REGEXP_REPLACE('source', 'search')\")\n        self.validate_identity(\"TIMESTAMP(3) WITH TIME ZONE\")\n        self.validate_identity(\"SYSTIMESTAMP\").assert_is(exp.Systimestamp)\n        self.validate_identity(\"SELECT SYSTIMESTAMP AT TIME ZONE 'UTC'\")\n        self.validate_identity(\"CURRENT_TIMESTAMP(precision)\")\n        self.validate_identity(\"ALTER TABLE tbl_name DROP FOREIGN KEY fk_symbol\")\n        self.validate_identity(\"ALTER TABLE Payments ADD Stock NUMBER NOT NULL\")\n        self.validate_identity(\"SELECT x FROM t WHERE cond FOR UPDATE\")\n        self.validate_identity(\"SELECT JSON_OBJECT(k1: v1 FORMAT JSON, k2: v2 FORMAT JSON)\")\n        self.validate_identity(\"SELECT JSON_OBJECT('name': first_name || ' ' || last_name) FROM t\")\n        self.validate_identity(\"COALESCE(c1, c2, c3)\")\n        self.validate_identity(\"SELECT * FROM TABLE(foo)\")\n        self.validate_identity(\"SELECT a$x#b\")\n        self.validate_identity(\"SELECT :OBJECT\")\n        self.validate_identity(\"SELECT * FROM t FOR UPDATE\")\n        self.validate_identity(\"SELECT * FROM t FOR UPDATE WAIT 5\")\n        self.validate_identity(\"SELECT * FROM t FOR UPDATE NOWAIT\")\n        self.validate_identity(\"SELECT * FROM t FOR UPDATE SKIP LOCKED\")\n        self.validate_identity(\"SELECT * FROM t FOR UPDATE OF s.t.c, s.t.v\")\n        self.validate_identity(\"SELECT * FROM t FOR UPDATE OF s.t.c, s.t.v NOWAIT\")\n        self.validate_identity(\"SELECT * FROM t FOR UPDATE OF s.t.c, s.t.v SKIP LOCKED\")\n        self.validate_identity(\"SELECT STANDARD_HASH('hello')\")\n        self.validate_identity(\"SELECT STANDARD_HASH('hello', 'MD5')\")\n        self.validate_identity(\"SELECT * FROM table_name@dblink_name.database_link_domain\")\n        self.validate_identity(\"SELECT * FROM table_name SAMPLE (25) s\")\n        self.validate_identity(\"SELECT COUNT(*) * 10 FROM orders SAMPLE (10) SEED (1)\")\n        self.validate_identity(\"SELECT * FROM V$SESSION\")\n        self.validate_identity(\"SELECT TO_DATE('January 15, 1989, 11:00 A.M.')\")\n        self.validate_identity(\"SELECT INSTR(haystack, needle)\")\n        self.validate_identity(\n            \"SELECT (TIMESTAMP '2025-12-30 20:00:00' - TIMESTAMP '2025-12-29 14:30:00') DAY TO SECOND\",\n            \"SELECT (TO_TIMESTAMP('2025-12-30 20:00:00', 'YYYY-MM-DD HH24:MI:SS.FF6') - TO_TIMESTAMP('2025-12-29 14:30:00', 'YYYY-MM-DD HH24:MI:SS.FF6')) DAY TO SECOND\",\n        )\n        self.validate_identity(\"SELECT (SYSTIMESTAMP - order_date) DAY(9) TO SECOND FROM orders\")\n        self.validate_identity(\"SELECT (SYSTIMESTAMP - order_date) DAY(9) TO SECOND(3) FROM orders\")\n        self.validate_identity(\n            \"SELECT * FROM consumer LEFT JOIN groceries ON consumer.groceries_id = consumer.id PIVOT(MAX(type_id) FOR consumer_type IN (1, 2, 3, 4))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM test UNPIVOT INCLUDE NULLS (value FOR Description IN (col AS 'PREFIX ' || CHR(38) || ' SUFFIX'))\"\n        )\n        self.validate_identity(\n            \"SELECT last_name, employee_id, manager_id, LEVEL FROM employees START WITH employee_id = 100 CONNECT BY PRIOR employee_id = manager_id ORDER SIBLINGS BY last_name\"\n        )\n        self.validate_identity(\n            \"ALTER TABLE Payments ADD (Stock NUMBER NOT NULL, dropid VARCHAR2(500) NOT NULL)\"\n        )\n        self.validate_identity(\n            \"SELECT JSON_ARRAYAGG(JSON_OBJECT('RNK': RNK, 'RATING_CODE': RATING_CODE, 'DATE_VALUE': DATE_VALUE, 'AGENT_ID': AGENT_ID RETURNING CLOB) RETURNING CLOB) AS JSON_DATA FROM tablename\"\n        )\n        self.validate_identity(\n            \"SELECT JSON_ARRAY(FOO() FORMAT JSON, BAR() NULL ON NULL RETURNING CLOB STRICT)\"\n        )\n        self.validate_identity(\n            \"SELECT JSON_ARRAYAGG(FOO() FORMAT JSON ORDER BY bar NULL ON NULL RETURNING CLOB STRICT)\"\n        )\n        self.validate_identity(\n            \"SELECT COUNT(1) INTO V_Temp FROM TABLE(CAST(somelist AS data_list)) WHERE col LIKE '%contact'\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM t WHERE c LIKE (:v)\",\n        )\n        self.validate_identity(\n            \"SELECT department_id INTO v_department_id FROM departments FETCH FIRST 1 ROWS ONLY\"\n        )\n        self.validate_identity(\n            \"SELECT department_id BULK COLLECT INTO v_department_ids FROM departments\"\n        )\n        self.validate_identity(\n            \"SELECT department_id, department_name BULK COLLECT INTO v_department_ids, v_department_names FROM departments\"\n        )\n        self.validate_identity(\n            \"SELECT MIN(column_name) KEEP (DENSE_RANK FIRST ORDER BY column_name DESC) FROM table_name\"\n        )\n        self.validate_identity(\n            'XMLELEMENT(\"ImageID\", image.id)',\n            'XMLELEMENT(NAME \"ImageID\", image.id)',\n        )\n        self.validate_identity(\n            \"SELECT CAST('January 15, 1989, 11:00 A.M.' AS DATE DEFAULT NULL ON CONVERSION ERROR, 'Month dd, YYYY, HH:MI A.M.') FROM DUAL\",\n            \"SELECT TO_DATE('January 15, 1989, 11:00 A.M.', 'Month dd, YYYY, HH12:MI A.M.') FROM DUAL\",\n        )\n        self.validate_identity(\n            \"SELECT TRUNC(SYSDATE)\",\n            \"SELECT TRUNC(SYSDATE, 'DD')\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT JSON_OBJECT(KEY 'key1' IS emp.column1, KEY 'key2' IS emp.column1) \"emp_key\" FROM emp\"\"\",\n            \"\"\"SELECT JSON_OBJECT('key1': emp.column1, 'key2': emp.column1) AS \"emp_key\" FROM emp\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT JSON_OBJECTAGG(KEY department_name VALUE department_id) FROM dep WHERE id <= 30\",\n            \"SELECT JSON_OBJECTAGG(department_name: department_id) FROM dep WHERE id <= 30\",\n        )\n        self.validate_identity(\n            \"SELECT last_name, department_id, salary, MIN(salary) KEEP (DENSE_RANK FIRST ORDER BY commission_pct) \"\n            'OVER (PARTITION BY department_id) AS \"Worst\", MAX(salary) KEEP (DENSE_RANK LAST ORDER BY commission_pct) '\n            'OVER (PARTITION BY department_id) AS \"Best\" FROM employees ORDER BY department_id, salary, last_name'\n        )\n        self.validate_identity(\n            \"SELECT UNIQUE col1, col2 FROM table\",\n            \"SELECT DISTINCT col1, col2 FROM table\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM T ORDER BY I OFFSET NVL(:variable1, 10) ROWS FETCH NEXT NVL(:variable2, 10) ROWS ONLY\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t SAMPLE (.25)\",\n            \"SELECT * FROM t SAMPLE (0.25)\",\n        )\n        self.validate_identity(\"SELECT TO_CHAR(-100, 'L99', 'NL_CURRENCY = '' AusDollars '' ')\")\n        self.validate_identity(\n            \"SELECT * FROM t START WITH col CONNECT BY NOCYCLE PRIOR col1 = col2\"\n        )\n\n        self.validate_all(\n            \"SELECT DBMS_RANDOM.VALUE()\",\n            read={\n                \"oracle\": \"SELECT DBMS_RANDOM.VALUE\",\n                \"postgres\": \"SELECT RANDOM()\",\n            },\n            write={\n                \"oracle\": \"SELECT DBMS_RANDOM.VALUE()\",\n                \"postgres\": \"SELECT RANDOM()\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRIM('|' FROM '||Hello ||| world||')\",\n            write={\n                \"clickhouse\": \"SELECT TRIM(BOTH '|' FROM '||Hello ||| world||')\",\n                \"oracle\": \"SELECT TRIM('|' FROM '||Hello ||| world||')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT department_id, department_name INTO v_department_id, v_department_name FROM departments FETCH FIRST 1 ROWS ONLY\",\n            write={\n                \"oracle\": \"SELECT department_id, department_name INTO v_department_id, v_department_name FROM departments FETCH FIRST 1 ROWS ONLY\",\n                \"postgres\": UnsupportedError,\n                \"tsql\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM test WHERE MOD(col1, 4) = 3\",\n            read={\n                \"duckdb\": \"SELECT * FROM test WHERE col1 % 4 = 3\",\n            },\n            write={\n                \"duckdb\": \"SELECT * FROM test WHERE col1 % 4 = 3\",\n                \"oracle\": \"SELECT * FROM test WHERE MOD(col1, 4) = 3\",\n            },\n        )\n        self.validate_all(\n            \"CURRENT_TIMESTAMP BETWEEN TO_DATE(f.C_SDATE, 'YYYY/MM/DD') AND TO_DATE(f.C_EDATE, 'YYYY/MM/DD')\",\n            read={\n                \"postgres\": \"CURRENT_TIMESTAMP BETWEEN TO_DATE(f.C_SDATE, 'yyyy/mm/dd') AND TO_DATE(f.C_EDATE, 'yyyy/mm/dd')\",\n            },\n            write={\n                \"oracle\": \"CURRENT_TIMESTAMP BETWEEN TO_DATE(f.C_SDATE, 'YYYY/MM/DD') AND TO_DATE(f.C_EDATE, 'YYYY/MM/DD')\",\n                \"postgres\": \"CURRENT_TIMESTAMP BETWEEN TO_DATE(f.C_SDATE, 'YYYY/MM/DD') AND TO_DATE(f.C_EDATE, 'YYYY/MM/DD')\",\n            },\n        )\n        self.validate_all(\n            \"TO_CHAR(x)\",\n            write={\n                \"doris\": \"CAST(x AS STRING)\",\n                \"oracle\": \"TO_CHAR(x)\",\n            },\n        )\n        self.validate_all(\n            \"TO_NUMBER(expr, fmt, nlsparam)\",\n            read={\n                \"teradata\": \"TO_NUMBER(expr, fmt, nlsparam)\",\n            },\n            write={\n                \"oracle\": \"TO_NUMBER(expr, fmt, nlsparam)\",\n                \"teradata\": \"TO_NUMBER(expr, fmt, nlsparam)\",\n            },\n        )\n        self.validate_all(\n            \"TO_NUMBER(x)\",\n            write={\n                \"bigquery\": \"CAST(x AS FLOAT64)\",\n                \"doris\": \"CAST(x AS DOUBLE)\",\n                \"drill\": \"CAST(x AS DOUBLE)\",\n                \"duckdb\": \"CAST(x AS DOUBLE)\",\n                \"hive\": \"CAST(x AS DOUBLE)\",\n                \"mysql\": \"CAST(x AS DOUBLE)\",\n                \"oracle\": \"TO_NUMBER(x)\",\n                \"postgres\": \"CAST(x AS DOUBLE PRECISION)\",\n                \"presto\": \"CAST(x AS DOUBLE)\",\n                \"redshift\": \"CAST(x AS DOUBLE PRECISION)\",\n                \"snowflake\": \"TO_NUMBER(x)\",\n                \"spark\": \"CAST(x AS DOUBLE)\",\n                \"spark2\": \"CAST(x AS DOUBLE)\",\n                \"starrocks\": \"CAST(x AS DOUBLE)\",\n                \"tableau\": \"CAST(x AS DOUBLE)\",\n                \"teradata\": \"TO_NUMBER(x)\",\n            },\n        )\n        self.validate_all(\n            \"TO_NUMBER(x, fmt)\",\n            read={\n                \"databricks\": \"TO_NUMBER(x, fmt)\",\n                \"drill\": \"TO_NUMBER(x, fmt)\",\n                \"postgres\": \"TO_NUMBER(x, fmt)\",\n                \"snowflake\": \"TO_NUMBER(x, fmt)\",\n                \"spark\": \"TO_NUMBER(x, fmt)\",\n                \"redshift\": \"TO_NUMBER(x, fmt)\",\n                \"teradata\": \"TO_NUMBER(x, fmt)\",\n            },\n            write={\n                \"databricks\": \"TO_NUMBER(x, fmt)\",\n                \"drill\": \"TO_NUMBER(x, fmt)\",\n                \"oracle\": \"TO_NUMBER(x, fmt)\",\n                \"postgres\": \"TO_NUMBER(x, fmt)\",\n                \"snowflake\": \"TO_NUMBER(x, fmt)\",\n                \"spark\": \"TO_NUMBER(x, fmt)\",\n                \"redshift\": \"TO_NUMBER(x, fmt)\",\n                \"teradata\": \"TO_NUMBER(x, fmt)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(NULL AS VARCHAR2(2328 CHAR)) AS COL1\",\n            write={\n                \"oracle\": \"SELECT CAST(NULL AS VARCHAR2(2328 CHAR)) AS COL1\",\n                \"spark\": \"SELECT CAST(NULL AS VARCHAR(2328)) AS COL1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(NULL AS VARCHAR2(2328 BYTE)) AS COL1\",\n            write={\n                \"oracle\": \"SELECT CAST(NULL AS VARCHAR2(2328 BYTE)) AS COL1\",\n                \"spark\": \"SELECT CAST(NULL AS VARCHAR(2328)) AS COL1\",\n            },\n        )\n        self.validate_all(\n            \"DATE '2022-01-01'\",\n            write={\n                \"\": \"DATE_STR_TO_DATE('2022-01-01')\",\n                \"mysql\": \"CAST('2022-01-01' AS DATE)\",\n                \"oracle\": \"TO_DATE('2022-01-01', 'YYYY-MM-DD')\",\n                \"postgres\": \"CAST('2022-01-01' AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"x::binary_double\",\n            write={\n                \"oracle\": \"CAST(x AS DOUBLE PRECISION)\",\n                \"\": \"CAST(x AS DOUBLE)\",\n            },\n        )\n        self.validate_all(\n            \"x::binary_float\",\n            write={\n                \"oracle\": \"CAST(x AS FLOAT)\",\n                \"\": \"CAST(x AS FLOAT)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS sch.udt)\",\n            read={\n                \"postgres\": \"CAST(x AS sch.udt)\",\n            },\n            write={\n                \"oracle\": \"CAST(x AS sch.udt)\",\n                \"postgres\": \"CAST(x AS sch.udt)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP('2024-12-12 12:12:12.000000', 'YYYY-MM-DD HH24:MI:SS.FF6')\",\n            write={\n                \"oracle\": \"SELECT TO_TIMESTAMP('2024-12-12 12:12:12.000000', 'YYYY-MM-DD HH24:MI:SS.FF6')\",\n                \"duckdb\": \"SELECT STRPTIME('2024-12-12 12:12:12.000000', '%Y-%m-%d %H:%M:%S.%f')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_DATE('2024-12-12', 'YYYY-MM-DD')\",\n            write={\n                \"oracle\": \"SELECT TO_DATE('2024-12-12', 'YYYY-MM-DD')\",\n                \"duckdb\": \"SELECT CAST(STRPTIME('2024-12-12', '%Y-%m-%d') AS DATE)\",\n            },\n        )\n        self.validate_identity(\n            \"\"\"SELECT * FROM t ORDER BY a ASC NULLS LAST, b ASC NULLS FIRST, c DESC NULLS LAST, d DESC NULLS FIRST\"\"\",\n            \"\"\"SELECT * FROM t ORDER BY a ASC, b ASC NULLS FIRST, c DESC NULLS LAST, d DESC\"\"\",\n        )\n        self.validate_all(\n            \"NVL(NULL, 1)\",\n            write={\n                \"oracle\": \"NVL(NULL, 1)\",\n                \"\": \"COALESCE(NULL, 1)\",\n                \"clickhouse\": \"COALESCE(NULL, 1)\",\n            },\n        )\n        self.validate_all(\n            \"TRIM(BOTH 'h' FROM 'Hello World')\",\n            write={\n                \"oracle\": \"TRIM(BOTH 'h' FROM 'Hello World')\",\n                \"clickhouse\": \"TRIM(BOTH 'h' FROM 'Hello World')\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT /*+ ORDERED */* FROM tbl\", \"SELECT /*+ ORDERED */ * FROM tbl\"\n        )\n        self.validate_identity(\n            \"SELECT /* test */ /*+ ORDERED */* FROM tbl\",\n            \"/* test */ SELECT /*+ ORDERED */ * FROM tbl\",\n        )\n        self.validate_identity(\n            \"SELECT /*+ ORDERED */*/* test */ FROM tbl\",\n            \"SELECT /*+ ORDERED */ * /* test */ FROM tbl\",\n        )\n\n        self.validate_all(\n            \"SELECT * FROM t FETCH FIRST 10 ROWS ONLY\",\n            write={\n                \"oracle\": \"SELECT * FROM t FETCH FIRST 10 ROWS ONLY\",\n                \"tsql\": \"SELECT * FROM t ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY\",\n            },\n        )\n        self.validate_identity(\"CREATE OR REPLACE FORCE VIEW foo1.foo2\")\n        self.validate_identity(\"TO_TIMESTAMP('foo')\")\n        self.validate_identity(\n            \"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 AM', 'DD Mon YYYY HH12:MI AM')\"\n        )\n        self.validate_identity(\n            \"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 PM', 'DD Mon YYYY HH12:MI PM')\"\n        )\n        self.validate_identity(\n            \"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 A.M.', 'DD Mon YYYY HH12:MI A.M.')\"\n        )\n        self.validate_identity(\n            \"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 P.M.', 'DD Mon YYYY HH12:MI P.M.')\"\n        )\n        self.validate_identity(\n            \"SELECT CUME_DIST(15, 0.05) WITHIN GROUP (ORDER BY col1, col2) FROM t\"\n        )\n        self.validate_identity(\n            \"SELECT DENSE_RANK(15, 0.05) WITHIN GROUP (ORDER BY col1, col2) FROM t\"\n        )\n        self.validate_identity(\"SELECT RANK(15, 0.05) WITHIN GROUP (ORDER BY col1, col2) FROM t\")\n        self.validate_identity(\n            \"SELECT PERCENT_RANK(15, 0.05) WITHIN GROUP (ORDER BY col1, col2) FROM t\"\n        )\n        self.validate_identity(\"L2_DISTANCE(x, y)\")\n        self.validate_identity(\"BITMAP_OR_AGG(x)\")\n\n    def test_join_marker(self):\n        self.validate_identity(\"SELECT e1.x, e2.x FROM e e1, e e2 WHERE e1.y (+) = e2.y\")\n\n        self.validate_all(\n            \"SELECT e1.x, e2.x FROM e e1, e e2 WHERE e1.y = e2.y (+)\",\n            write={\"\": UnsupportedError},\n        )\n        self.validate_all(\n            \"SELECT e1.x, e2.x FROM e e1, e e2 WHERE e1.y = e2.y (+)\",\n            write={\n                \"\": \"SELECT e1.x, e2.x FROM e AS e1, e AS e2 WHERE e1.y = e2.y\",\n                \"oracle\": \"SELECT e1.x, e2.x FROM e e1, e e2 WHERE e1.y = e2.y (+)\",\n            },\n        )\n\n    def test_hints(self):\n        self.validate_identity(\"SELECT /*+ USE_NL(A B) */ A.COL_TEST FROM TABLE_A A, TABLE_B B\")\n        self.validate_identity(\n            \"SELECT /*+ INDEX(v.j jhist_employee_ix (employee_id start_date)) */ * FROM v\"\n        )\n        self.validate_identity(\n            \"SELECT /*+ USE_NL(A B C) */ A.COL_TEST FROM TABLE_A A, TABLE_B B, TABLE_C C\"\n        )\n        self.validate_identity(\n            \"SELECT /*+ NO_INDEX(employees emp_empid) */ employee_id FROM employees WHERE employee_id > 200\"\n        )\n        self.validate_identity(\n            \"SELECT /*+ NO_INDEX_FFS(items item_order_ix) */ order_id FROM order_items items\"\n        )\n        self.validate_identity(\n            \"SELECT /*+ LEADING(e j) */ * FROM employees e, departments d, job_history j WHERE e.department_id = d.department_id AND e.hire_date = j.start_date\"\n        )\n        self.validate_identity(\"INSERT /*+ APPEND */ INTO IAP_TBL (id, col1) VALUES (2, 'test2')\")\n        self.validate_identity(\"INSERT /*+ APPEND_VALUES */ INTO dest_table VALUES (i, 'Value')\")\n        self.validate_identity(\"INSERT /*+ APPEND(d) */ INTO dest d VALUES (i, 'Value')\")\n        self.validate_identity(\n            \"INSERT /*+ APPEND(d) */ INTO dest d (i, value) SELECT 1, 'value' FROM dual\"\n        )\n        self.validate_identity(\n            \"SELECT /*+ LEADING(departments employees) USE_NL(employees) */ * FROM employees JOIN departments ON employees.department_id = departments.department_id\",\n            \"\"\"SELECT /*+ LEADING(departments employees)\n  USE_NL(employees) */\n  *\nFROM employees\nJOIN departments\n  ON employees.department_id = departments.department_id\"\"\",\n            pretty=True,\n        )\n        self.validate_identity(\n            \"SELECT /*+ USE_NL(bbbbbbbbbbbbbbbbbbbbbbbb) LEADING(aaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccc dddddddddddddddddddddddd) INDEX(cccccccccccccccccccccccc) */ * FROM aaaaaaaaaaaaaaaaaaaaaaaa JOIN bbbbbbbbbbbbbbbbbbbbbbbb ON aaaaaaaaaaaaaaaaaaaaaaaa.id = bbbbbbbbbbbbbbbbbbbbbbbb.a_id JOIN cccccccccccccccccccccccc ON bbbbbbbbbbbbbbbbbbbbbbbb.id = cccccccccccccccccccccccc.b_id JOIN dddddddddddddddddddddddd ON cccccccccccccccccccccccc.id = dddddddddddddddddddddddd.c_id\",\n        )\n        self.validate_identity(\n            \"SELECT /*+ USE_NL(bbbbbbbbbbbbbbbbbbbbbbbb) LEADING(aaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbb cccccccccccccccccccccccc dddddddddddddddddddddddd) INDEX(cccccccccccccccccccccccc) */ * FROM aaaaaaaaaaaaaaaaaaaaaaaa JOIN bbbbbbbbbbbbbbbbbbbbbbbb ON aaaaaaaaaaaaaaaaaaaaaaaa.id = bbbbbbbbbbbbbbbbbbbbbbbb.a_id JOIN cccccccccccccccccccccccc ON bbbbbbbbbbbbbbbbbbbbbbbb.id = cccccccccccccccccccccccc.b_id JOIN dddddddddddddddddddddddd ON cccccccccccccccccccccccc.id = dddddddddddddddddddddddd.c_id\",\n            \"\"\"SELECT /*+ USE_NL(bbbbbbbbbbbbbbbbbbbbbbbb)\n  LEADING(\n    aaaaaaaaaaaaaaaaaaaaaaaa\n    bbbbbbbbbbbbbbbbbbbbbbbb\n    cccccccccccccccccccccccc\n    dddddddddddddddddddddddd\n  )\n  INDEX(cccccccccccccccccccccccc) */\n  *\nFROM aaaaaaaaaaaaaaaaaaaaaaaa\nJOIN bbbbbbbbbbbbbbbbbbbbbbbb\n  ON aaaaaaaaaaaaaaaaaaaaaaaa.id = bbbbbbbbbbbbbbbbbbbbbbbb.a_id\nJOIN cccccccccccccccccccccccc\n  ON bbbbbbbbbbbbbbbbbbbbbbbb.id = cccccccccccccccccccccccc.b_id\nJOIN dddddddddddddddddddddddd\n  ON cccccccccccccccccccccccc.id = dddddddddddddddddddddddd.c_id\"\"\",\n            pretty=True,\n        )\n        # Test that parsing error with keywords like select where etc falls back\n        self.validate_identity(\n            \"SELECT /*+ LEADING(departments employees) USE_NL(employees) select where group by is order by */ * FROM employees JOIN departments ON employees.department_id = departments.department_id\",\n            \"\"\"SELECT /*+ LEADING(departments employees) USE_NL(employees) select where group by is order by */\n  *\nFROM employees\nJOIN departments\n  ON employees.department_id = departments.department_id\"\"\",\n            pretty=True,\n        )\n        # Test that parsing error with , inside hint function falls back\n        self.validate_identity(\n            \"SELECT /*+ LEADING(departments, employees) */ * FROM employees JOIN departments ON employees.department_id = departments.department_id\"\n        )\n        # Test that parsing error with keyword inside hint function falls back\n        self.validate_identity(\n            \"SELECT /*+ LEADING(departments select) */ * FROM employees JOIN departments ON employees.department_id = departments.department_id\"\n        )\n\n    def test_xml_table(self):\n        self.validate_identity(\"XMLTABLE('x')\")\n        self.validate_identity(\"XMLTABLE('x' RETURNING SEQUENCE BY REF)\")\n        self.validate_identity(\"XMLTABLE('x' PASSING y)\")\n        self.validate_identity(\"XMLTABLE('x' PASSING y RETURNING SEQUENCE BY REF)\")\n        self.validate_identity(\n            \"XMLTABLE('x' RETURNING SEQUENCE BY REF COLUMNS a VARCHAR2, b FLOAT)\"\n        )\n        self.validate_identity(\n            \"SELECT x.* FROM example t, XMLTABLE(XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS \\\"ns1\\\"), '/root/data' PASSING t.xml COLUMNS id NUMBER PATH '@id', value VARCHAR2(100) PATH 'ns1:value/text()') x\"\n        )\n\n        self.validate_all(\n            \"\"\"SELECT warehouse_name warehouse,\n   warehouse2.\"Water\", warehouse2.\"Rail\"\n   FROM warehouses,\n   XMLTABLE('/Warehouse'\n      PASSING warehouses.warehouse_spec\n      COLUMNS\n         \"Water\" varchar2(6) PATH 'WaterAccess',\n         \"Rail\" varchar2(6) PATH 'RailAccess')\n      warehouse2\"\"\",\n            write={\n                \"oracle\": \"\"\"SELECT\n  warehouse_name AS warehouse,\n  warehouse2.\"Water\",\n  warehouse2.\"Rail\"\nFROM warehouses, XMLTABLE(\n  '/Warehouse'\n  PASSING\n    warehouses.warehouse_spec\n  COLUMNS\n    \"Water\" VARCHAR2(6) PATH 'WaterAccess',\n    \"Rail\" VARCHAR2(6) PATH 'RailAccess'\n) warehouse2\"\"\",\n            },\n            pretty=True,\n        )\n\n        self.validate_all(\n            \"\"\"SELECT table_name, column_name, data_default FROM xmltable('ROWSET/ROW'\n    passing dbms_xmlgen.getxmltype('SELECT table_name, column_name, data_default FROM user_tab_columns')\n    columns table_name      VARCHAR2(128)   PATH '*[1]'\n            , column_name   VARCHAR2(128)   PATH '*[2]'\n            , data_default  VARCHAR2(2000)  PATH '*[3]'\n            );\"\"\",\n            write={\n                \"oracle\": \"\"\"SELECT\n  table_name,\n  column_name,\n  data_default\nFROM XMLTABLE(\n  'ROWSET/ROW'\n  PASSING\n    dbms_xmlgen.getxmltype('SELECT table_name, column_name, data_default FROM user_tab_columns')\n  COLUMNS\n    table_name VARCHAR2(128) PATH '*[1]',\n    column_name VARCHAR2(128) PATH '*[2]',\n    data_default VARCHAR2(2000) PATH '*[3]'\n)\"\"\",\n            },\n            pretty=True,\n        )\n\n    def test_match_recognize(self):\n        self.validate_identity(\n            \"\"\"SELECT\n  *\nFROM sales_history\nMATCH_RECOGNIZE (\n  PARTITION BY product\n  ORDER BY\n    tstamp\n  MEASURES\n    STRT.tstamp AS start_tstamp,\n    LAST(UP.tstamp) AS peak_tstamp,\n    LAST(DOWN.tstamp) AS end_tstamp,\n    MATCH_NUMBER() AS mno\n  ONE ROW PER MATCH\n  AFTER MATCH SKIP TO LAST DOWN\n  PATTERN (STRT UP+ FLAT* DOWN+)\n  DEFINE\n    UP AS UP.units_sold > PREV(UP.units_sold),\n    FLAT AS FLAT.units_sold = PREV(FLAT.units_sold),\n    DOWN AS DOWN.units_sold < PREV(DOWN.units_sold)\n) MR\"\"\",\n            pretty=True,\n        )\n\n    def test_json_table(self):\n        self.validate_identity(\n            \"SELECT * FROM JSON_TABLE(foo FORMAT JSON, 'bla' ERROR ON ERROR NULL ON EMPTY COLUMNS(foo PATH 'bar'))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM JSON_TABLE(foo FORMAT JSON, 'bla' ERROR ON ERROR NULL ON EMPTY COLUMNS foo PATH 'bar')\",\n            \"SELECT * FROM JSON_TABLE(foo FORMAT JSON, 'bla' ERROR ON ERROR NULL ON EMPTY COLUMNS(foo PATH 'bar'))\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT\n  CASE WHEN DBMS_LOB.GETLENGTH(info) < 32000 THEN DBMS_LOB.SUBSTR(info) END AS info_txt,\n  info AS info_clob\nFROM schemaname.tablename ar\nINNER JOIN JSON_TABLE(:emps, '$[*]' COLUMNS(empno NUMBER PATH '$')) jt\n  ON ar.empno = jt.empno\"\"\",\n            pretty=True,\n        )\n        self.validate_identity(\n            \"\"\"SELECT\n  *\nFROM JSON_TABLE(res, '$.info[*]' COLUMNS(\n  tempid NUMBER PATH '$.tempid',\n  NESTED PATH '$.calid[*]' COLUMNS(last_dt PATH '$.last_dt ')\n)) src\"\"\",\n            pretty=True,\n        )\n        self.validate_identity(\"CONVERT('foo', 'dst')\")\n        self.validate_identity(\"CONVERT('foo', 'dst', 'src')\")\n\n    def test_connect_by(self):\n        start = \"START WITH last_name = 'King'\"\n        connect = \"CONNECT BY PRIOR employee_id = manager_id AND LEVEL <= 4\"\n        body = \"\"\"\n            SELECT last_name \"Employee\",\n            LEVEL, SYS_CONNECT_BY_PATH(last_name, '/') \"Path\"\n            FROM employees\n            WHERE level <= 3 AND department_id = 80\n        \"\"\"\n        pretty = \"\"\"SELECT\n  last_name AS \"Employee\",\n  LEVEL,\n  SYS_CONNECT_BY_PATH(last_name, '/') AS \"Path\"\nFROM employees\nWHERE\n  level <= 3 AND department_id = 80\nSTART WITH last_name = 'King'\nCONNECT BY PRIOR employee_id = manager_id AND LEVEL <= 4\"\"\"\n\n        for query in (f\"{body}{start}{connect}\", f\"{body}{connect}{start}\"):\n            self.validate_identity(query, pretty, pretty=True)\n\n    def test_query_restrictions(self):\n        for restriction in (\"READ ONLY\", \"CHECK OPTION\"):\n            for constraint_name in (\" CONSTRAINT name\", \"\"):\n                with self.subTest(f\"Restriction: {restriction}\"):\n                    self.validate_identity(f\"SELECT * FROM tbl WITH {restriction}{constraint_name}\")\n                    self.validate_identity(\n                        f\"CREATE VIEW view AS SELECT * FROM tbl WITH {restriction}{constraint_name}\"\n                    )\n\n    def test_multitable_inserts(self):\n        self.maxDiff = None\n        self.validate_identity(\n            \"INSERT ALL \"\n            \"INTO dest_tab1 (id, description) VALUES (id, description) \"\n            \"INTO dest_tab2 (id, description) VALUES (id, description) \"\n            \"INTO dest_tab3 (id, description) VALUES (id, description) \"\n            \"SELECT id, description FROM source_tab\"\n        )\n\n        self.validate_identity(\n            \"INSERT ALL \"\n            \"INTO pivot_dest (id, day, val) VALUES (id, 'mon', mon_val) \"\n            \"INTO pivot_dest (id, day, val) VALUES (id, 'tue', tue_val) \"\n            \"INTO pivot_dest (id, day, val) VALUES (id, 'wed', wed_val) \"\n            \"INTO pivot_dest (id, day, val) VALUES (id, 'thu', thu_val) \"\n            \"INTO pivot_dest (id, day, val) VALUES (id, 'fri', fri_val) \"\n            \"SELECT * \"\n            \"FROM pivot_source\"\n        )\n\n        self.validate_identity(\n            \"INSERT ALL \"\n            \"WHEN id <= 3 THEN \"\n            \"INTO dest_tab1 (id, description) VALUES (id, description) \"\n            \"WHEN id BETWEEN 4 AND 7 THEN \"\n            \"INTO dest_tab2 (id, description) VALUES (id, description) \"\n            \"WHEN id >= 8 THEN \"\n            \"INTO dest_tab3 (id, description) VALUES (id, description) \"\n            \"SELECT id, description \"\n            \"FROM source_tab\"\n        )\n\n        self.validate_identity(\n            \"INSERT ALL \"\n            \"WHEN id <= 3 THEN \"\n            \"INTO dest_tab1 (id, description) VALUES (id, description) \"\n            \"WHEN id BETWEEN 4 AND 7 THEN \"\n            \"INTO dest_tab2 (id, description) VALUES (id, description) \"\n            \"WHEN 1 = 1 THEN \"\n            \"INTO dest_tab3 (id, description) VALUES (id, description) \"\n            \"SELECT id, description \"\n            \"FROM source_tab\"\n        )\n\n        self.validate_identity(\n            \"INSERT FIRST \"\n            \"WHEN id <= 3 THEN \"\n            \"INTO dest_tab1 (id, description) VALUES (id, description) \"\n            \"WHEN id <= 5 THEN \"\n            \"INTO dest_tab2 (id, description) VALUES (id, description) \"\n            \"ELSE \"\n            \"INTO dest_tab3 (id, description) VALUES (id, description) \"\n            \"SELECT id, description \"\n            \"FROM source_tab\"\n        )\n\n        self.validate_identity(\n            \"INSERT FIRST \"\n            \"WHEN id <= 3 THEN \"\n            \"INTO dest_tab1 (id, description) VALUES (id, description) \"\n            \"ELSE \"\n            \"INTO dest_tab2 (id, description) VALUES (id, description) \"\n            \"INTO dest_tab3 (id, description) VALUES (id, description) \"\n            \"SELECT id, description \"\n            \"FROM source_tab\"\n        )\n\n        self.validate_identity(\n            \"/* COMMENT */ INSERT FIRST \"\n            \"WHEN salary > 4000 THEN INTO emp2 \"\n            \"WHEN salary > 5000 THEN INTO emp3 \"\n            \"WHEN salary > 6000 THEN INTO emp4 \"\n            \"SELECT salary FROM employees\"\n        )\n\n    def test_json_functions(self):\n        for format_json in (\"\", \" FORMAT JSON\"):\n            for on_cond in (\n                \"\",\n                \" TRUE ON ERROR\",\n                \" NULL ON EMPTY\",\n                \" DEFAULT 1 ON ERROR TRUE ON EMPTY\",\n            ):\n                for passing in (\"\", \" PASSING 'name1' AS \\\"var1\\\", 'name2' AS \\\"var2\\\"\"):\n                    with self.subTest(\"Testing JSON_EXISTS()\"):\n                        self.validate_identity(\n                            f\"SELECT * FROM t WHERE JSON_EXISTS(name{format_json}, '$[1].middle'{passing}{on_cond})\"\n                        )\n\n    def test_grant(self):\n        grant_cmds = [\n            \"GRANT purchases_reader_role TO george, maria\",\n            \"GRANT USAGE ON TYPE price TO finance_role\",\n            \"GRANT USAGE ON DERBY AGGREGATE types.maxPrice TO sales_role\",\n        ]\n\n        for sql in grant_cmds:\n            with self.subTest(f\"Testing Oracles's GRANT command statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n        self.validate_identity(\"GRANT SELECT ON TABLE t TO maria, harry\")\n        self.validate_identity(\"GRANT SELECT ON TABLE s.v TO PUBLIC\")\n        self.validate_identity(\"GRANT SELECT ON TABLE t TO purchases_reader_role\")\n        self.validate_identity(\"GRANT UPDATE, TRIGGER ON TABLE t TO anita, zhi\")\n        self.validate_identity(\"GRANT EXECUTE ON PROCEDURE p TO george\")\n        self.validate_identity(\"GRANT USAGE ON SEQUENCE order_id TO sales_role\")\n\n    def test_revoke(self):\n        revoke_cmds = [\n            \"REVOKE purchases_reader_role FROM george, maria\",\n            \"REVOKE USAGE ON TYPE price FROM finance_role\",\n            \"REVOKE USAGE ON DERBY AGGREGATE types.maxPrice FROM sales_role\",\n        ]\n\n        for sql in revoke_cmds:\n            with self.subTest(f\"Testing Oracle's REVOKE command statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n        self.validate_identity(\"REVOKE SELECT ON TABLE t FROM maria, harry\")\n        self.validate_identity(\"REVOKE SELECT ON TABLE s.v FROM PUBLIC\")\n        self.validate_identity(\"REVOKE SELECT ON TABLE t FROM purchases_reader_role\")\n        self.validate_identity(\"REVOKE UPDATE, TRIGGER ON TABLE t FROM anita, zhi\")\n        self.validate_identity(\"REVOKE EXECUTE ON PROCEDURE p FROM george\")\n        self.validate_identity(\"REVOKE USAGE ON SEQUENCE order_id FROM sales_role\")\n\n    def test_datetrunc(self):\n        self.validate_all(\n            \"TRUNC(SYSDATE, 'YEAR')\",\n            write={\n                \"clickhouse\": \"DATE_TRUNC('YEAR', CURRENT_TIMESTAMP())\",\n                \"oracle\": \"TRUNC(SYSDATE, 'YEAR')\",\n            },\n        )\n\n        # Make sure units are not normalized e.g 'Q' -> 'QUARTER' and 'W' -> 'WEEK'\n        # https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/ROUND-and-TRUNC-Date-Functions.html\n        for unit in (\n            \"'Q'\",\n            \"'W'\",\n        ):\n            self.validate_identity(f\"TRUNC(x, {unit})\")\n\n    def test_trunc_type_inference(self):\n        # Tests for build_trunc discrimination logic (shared across Oracle, Exasol, Snowflake)\n        # 5 cases: temporal+?, ?+string, numeric+?, ?+int, ?+?\n\n        # temporal + string: first arg typed as temporal\n        self.parse_one(\"TRUNC(CAST(x AS DATE), 'MONTH')\").assert_is(exp.DateTrunc)\n        self.parse_one(\"TRUNC(SYSDATE, 'MONTH')\").assert_is(exp.DateTrunc)\n\n        # ? + string: untyped first arg, string second arg infers DateTrunc\n        self.parse_one(\"TRUNC(col, 'MONTH')\").assert_is(exp.DateTrunc)\n\n        # numeric + int: first arg typed as numeric (literal infers type)\n        self.validate_identity(\"TRUNC(3.14159, 2)\").assert_is(exp.Trunc)\n\n        # ? + int: untyped first arg, int second arg infers Trunc\n        self.validate_identity(\"TRUNC(price, 0)\").assert_is(exp.Trunc)\n\n        # ? + ?: neither arg typed, fallback to Anonymous\n        self.validate_identity(\"TRUNC(foo, bar)\").assert_is(exp.Anonymous)\n\n    def test_trunc(self):\n        # Numeric truncation identity and transpilation\n        self.validate_identity(\"TRUNC(3.14159)\").assert_is(exp.Trunc)\n        self.validate_all(\n            \"TRUNC(3.14159)\",\n            write={\n                \"oracle\": \"TRUNC(3.14159)\",\n                \"postgres\": \"TRUNC(3.14159)\",\n                \"mysql\": \"TRUNCATE(3.14159)\",\n                \"tsql\": \"ROUND(3.14159, 0, 1)\",\n            },\n        )\n\n        # Cross-dialect numeric truncation transpilation\n        self.validate_all(\n            \"TRUNC(3.14159, 2)\",\n            read={\n                \"mysql\": \"TRUNCATE(3.14159, 2)\",\n                \"postgres\": \"TRUNC(3.14159, 2)\",\n                \"snowflake\": \"TRUNC(3.14159, 2)\",\n            },\n            write={\n                \"oracle\": \"TRUNC(3.14159, 2)\",\n                \"postgres\": \"TRUNC(3.14159, 2)\",\n                \"mysql\": \"TRUNCATE(3.14159, 2)\",\n                \"tsql\": \"ROUND(3.14159, 2, 1)\",\n                \"snowflake\": \"TRUNC(3.14159, 2)\",\n                \"bigquery\": \"TRUNC(3.14159, 2)\",\n                \"duckdb\": \"TRUNC(3.14159)\",\n                \"presto\": \"TRUNCATE(3.14159, 2)\",\n                \"clickhouse\": \"trunc(3.14159, 2)\",\n                \"spark\": \"CAST(3.14159 AS BIGINT)\",\n            },\n        )\n\n        # Date truncation with various units\n        for unit in (\"DAY\", \"WEEK\", \"MONTH\", \"QUARTER\", \"YEAR\"):\n            with self.subTest(f\"Date TRUNC with {unit}\"):\n                self.validate_all(\n                    f\"TRUNC(CAST(x AS DATE), '{unit}')\",\n                    write={\n                        \"oracle\": f\"TRUNC(CAST(x AS DATE), '{unit}')\",\n                        \"snowflake\": f\"DATE_TRUNC('{unit}', CAST(x AS DATE))\",\n                        \"postgres\": f\"DATE_TRUNC('{unit}', CAST(x AS DATE))\",\n                        \"bigquery\": f\"DATE_TRUNC(CAST(x AS DATE), {unit})\",\n                        \"duckdb\": f\"DATE_TRUNC('{unit}', CAST(x AS DATE))\",\n                        \"tsql\": f\"DATE_TRUNC('{unit}', CAST(x AS DATE))\",\n                        \"spark\": f\"TRUNC(CAST(x AS DATE), '{unit}')\",\n                    },\n                )\n\n        # Timestamp truncation with various units\n        for unit in (\"HOUR\", \"MINUTE\", \"SECOND\", \"DAY\", \"MONTH\", \"YEAR\"):\n            with self.subTest(f\"Timestamp TRUNC with {unit}\"):\n                self.validate_all(\n                    f\"TRUNC(CAST(x AS TIMESTAMP), '{unit}')\",\n                    write={\n                        \"oracle\": f\"TRUNC(CAST(x AS TIMESTAMP), '{unit}')\",\n                        \"snowflake\": f\"DATE_TRUNC('{unit}', CAST(x AS TIMESTAMP))\",\n                        \"postgres\": f\"DATE_TRUNC('{unit}', CAST(x AS TIMESTAMP))\",\n                        \"duckdb\": f\"DATE_TRUNC('{unit}', CAST(x AS TIMESTAMP))\",\n                        \"tsql\": f\"DATE_TRUNC('{unit}', CAST(x AS DATETIME2))\",\n                        \"spark\": f\"TRUNC(CAST(x AS TIMESTAMP), '{unit}')\",\n                    },\n                )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE TABLE tbl\")\n        self.validate_identity(\"ANALYZE INDEX ndx\")\n        self.validate_identity(\"ANALYZE TABLE db.tbl PARTITION(foo = 'foo', bar = 'bar')\")\n        self.validate_identity(\"ANALYZE TABLE db.tbl SUBPARTITION(foo = 'foo', bar = 'bar')\")\n        self.validate_identity(\"ANALYZE INDEX db.ndx PARTITION(foo = 'foo', bar = 'bar')\")\n        self.validate_identity(\"ANALYZE INDEX db.ndx PARTITION(part1)\")\n        self.validate_identity(\"ANALYZE CLUSTER db.cluster\")\n        self.validate_identity(\"ANALYZE TABLE tbl VALIDATE REF UPDATE\")\n        self.validate_identity(\"ANALYZE LIST CHAINED ROWS\")\n        self.validate_identity(\"ANALYZE LIST CHAINED ROWS INTO tbl\")\n        self.validate_identity(\"ANALYZE DELETE STATISTICS\")\n        self.validate_identity(\"ANALYZE DELETE SYSTEM STATISTICS\")\n        self.validate_identity(\"ANALYZE VALIDATE REF UPDATE\")\n        self.validate_identity(\"ANALYZE VALIDATE REF UPDATE SET DANGLING TO NULL\")\n        self.validate_identity(\"ANALYZE VALIDATE STRUCTURE\")\n        self.validate_identity(\"ANALYZE VALIDATE STRUCTURE CASCADE FAST\")\n        self.validate_identity(\n            \"ANALYZE TABLE tbl VALIDATE STRUCTURE CASCADE COMPLETE ONLINE INTO db.tbl\"\n        )\n        self.validate_identity(\n            \"ANALYZE TABLE tbl VALIDATE STRUCTURE CASCADE COMPLETE OFFLINE INTO db.tbl\"\n        )\n\n    def test_prior(self):\n        self.validate_identity(\n            \"SELECT id, PRIOR name AS parent_name, name FROM tree CONNECT BY NOCYCLE PRIOR id = parent_id\"\n        )\n\n        with self.assertRaises(ParseError):\n            parse_one(\"PRIOR as foo\", read=\"oracle\")\n\n    def test_utc_time(self):\n        self.validate_identity(\"UTC_TIME()\").assert_is(exp.UtcTime)\n        self.validate_identity(\"UTC_TIME(6)\").assert_is(exp.UtcTime)\n        self.validate_identity(\"UTC_TIMESTAMP()\").assert_is(exp.UtcTimestamp)\n        self.validate_identity(\"UTC_TIMESTAMP(6)\").assert_is(exp.UtcTimestamp)\n\n    def test_merge_builder_alias(self):\n        merge_stmt = exp.merge(\n            \"WHEN MATCHED THEN UPDATE SET my_table.col1 = source_table.col1\",\n            \"WHEN NOT MATCHED THEN INSERT (my_table.id, my_table.col1) VALUES (source_table.id, source_table.col1)\",\n            into=\"my_table\",\n            using=\"(SELECT * FROM something) source_table\",\n            on=\"my_table.id = source_table.id\",\n            dialect=\"oracle\",\n        )\n        self.assertEqual(\n            merge_stmt.sql(\"oracle\"),\n            \"MERGE INTO my_table USING (SELECT * FROM something) source_table ON my_table.id = source_table.id WHEN MATCHED THEN UPDATE SET my_table.col1 = source_table.col1 WHEN NOT MATCHED THEN INSERT (my_table.id, my_table.col1) VALUES (source_table.id, source_table.col1)\",\n        )\n\n    def test_pseudocolumns(self):\n        ast = self.validate_identity(\n            \"WITH t AS (SELECT 1 AS COL) SELECT col, ROWID FROM t WHERE ROWNUM = 1\"\n        )\n        self.assertIsNone(ast.find(exp.Pseudocolumn))\n\n        qualified = qualify(ast, dialect=\"oracle\")\n        self.assertIsNotNone(qualified.find(exp.Pseudocolumn))\n\n        self.assertEqual(\n            qualified.sql(dialect=\"oracle\"),\n            'WITH \"T\" AS (SELECT 1 AS \"COL\") SELECT \"T\".\"COL\" AS \"COL\", ROWID AS \"ROWID\" FROM \"T\" \"T\" WHERE ROWNUM = 1',\n        )\n\n    def test_chr(self):\n        self.validate_identity(\"SELECT CHR(187 USING NCHAR_CS)\")\n        self.validate_identity(\"SELECT CHR(187)\")\n\n    def test_full_procedure(self):\n        sql = \"\"\"\n        CREATE OR REPLACE PROCEDURE query_emp(\n            p_id     IN  VARCHAR2,\n            p_name   OUT VARCHAR2,\n            p_salary OUT NUMBER\n        ) AS\n        BEGIN\n            SELECT last_name, salary \n            INTO p_name, p_salary\n            FROM employees\n            WHERE employee_id = p_id;\n        END;\n        \"\"\"\n\n        expected_sqls = [\n            \"CREATE OR REPLACE PROCEDURE query_emp(p_id IN VARCHAR2, p_name OUT VARCHAR2, p_salary OUT NUMBER) AS BEGIN SELECT last_name, salary INTO p_name, p_salary FROM employees WHERE employee_id = p_id; END\",\n        ]\n\n        for expr, expected_sql in zip(parse(sql, read=\"oracle\"), expected_sqls):\n            self.assertEqual(expr.sql(dialect=\"oracle\"), expected_sql)\n\n        sql = \"\"\"\n        CREATE OR REPLACE PROCEDURE test_proc (\n            a NUMBER,\n            b IN NUMBER,\n            c IN OUT NUMBER,\n            d OUT NUMBER\n        ) AS\n        BEGIN\n            c := c + a + b;\n            d := 42 + c;\n        END;\n        \"\"\"\n\n        expected_sqls = [\n            \"CREATE OR REPLACE PROCEDURE test_proc(a NUMBER, b IN NUMBER, c IN OUT NUMBER, d OUT NUMBER) AS BEGIN c := c + a + b; d := 42 + c; END\",\n        ]\n\n        for expr, expected_sql in zip(parse(sql, read=\"oracle\"), expected_sqls):\n            self.assertEqual(expr.sql(dialect=\"oracle\"), expected_sql)\n\n    def test_create_trigger(self):\n        \"\"\"Test that Oracle CREATE TRIGGER statements fall back to Command parsing.\"\"\"\n        self.validate_identity(\n            \"CREATE TRIGGER check_salary BEFORE INSERT ON employees FOR EACH ROW BEGIN :NEW.status := 'PENDING' END\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"CREATE TRIGGER audit_trigger AFTER UPDATE ON accounts FOR EACH ROW BEGIN INSERT INTO audit_log (user_id, old_balance, new_balance, changed_at) VALUES (:OLD.id, :OLD.balance, :NEW.balance, SYSDATE) END\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"CREATE TRIGGER view_insert INSTEAD OF INSERT ON employee_view FOR EACH ROW BEGIN INSERT INTO employees (id, name, dept_id) VALUES (:NEW.id, :NEW.name, :NEW.dept_id) END\",\n            check_command_warning=True,\n        )\n"
  },
  {
    "path": "tests/dialects/test_pipe_syntax.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestPipeSyntax(Validator):\n    def test_select(self):\n        self.validate_identity(\"FROM x\", \"SELECT * FROM x\")\n        self.validate_identity(\n            \"FROM x |> SELECT x1, x2\", \"WITH __tmp1 AS (SELECT x1, x2 FROM x) SELECT * FROM __tmp1\"\n        )\n        self.validate_identity(\n            \"FROM x |> SELECT x.x1, x.x2\",\n            \"WITH __tmp1 AS (SELECT x.x1, x.x2 FROM x) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> SELECT x1 as c1, x2 as c2\",\n            \"WITH __tmp1 AS (SELECT x1 AS c1, x2 AS c2 FROM x) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> SELECT x1 + 1 as x1_a, x2 - 1 as x2_a |> WHERE x1_a > 1\",\n            \"WITH __tmp1 AS (SELECT x1 + 1 AS x1_a, x2 - 1 AS x2_a FROM x) SELECT * FROM __tmp1 WHERE x1_a > 1\",\n        )\n        self.validate_identity(\n            \"FROM x |> SELECT x1 + 1 as x1_a, x2 - 1 as x2_a |> WHERE x1_a > 1 |> SELECT x2_a\",\n            \"WITH __tmp1 AS (SELECT x1 + 1 AS x1_a, x2 - 1 AS x2_a FROM x), __tmp2 AS (SELECT x2_a FROM __tmp1 WHERE x1_a > 1) SELECT * FROM __tmp2\",\n        )\n        self.validate_identity(\n            \"FROM x |> WHERE x1 > 0 OR x2 > 0 |> WHERE x3 > 1 AND x4 > 1 |> SELECT x1, x4\",\n            \"WITH __tmp1 AS (SELECT x1, x4 FROM x WHERE (x1 > 0 OR x2 > 0) AND (x3 > 1 AND x4 > 1)) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> WHERE x1 > 1 |> WHERE x2 > 2 |> SELECT x1 as gt1, x2 as gt2\",\n            \"WITH __tmp1 AS (SELECT x1 AS gt1, x2 AS gt2 FROM x WHERE x1 > 1 AND x2 > 2) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> WHERE x1 > 1 AND x2 > 2 |> SELECT x1 as gt1, x2 as gt2 |> SELECT gt1 * 2 + gt2 * 2 AS gt2_2\",\n            \"WITH __tmp1 AS (SELECT x1 AS gt1, x2 AS gt2 FROM x WHERE x1 > 1 AND x2 > 2), __tmp2 AS (SELECT gt1 * 2 + gt2 * 2 AS gt2_2 FROM __tmp1) SELECT * FROM __tmp2\",\n        )\n        self.validate_identity(\n            \"SELECT 1 AS y, 2 AS x |> SELECT x, y\",\n            \"WITH __tmp1 AS (SELECT x, y FROM (SELECT 1 AS y, 2 AS x)) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"SELECT x1, x2, x3 FROM x |> AS a_x |> WHERE a_x.x1 > 0\",\n            \"WITH a_x AS (SELECT x1, x2, x3 FROM x) SELECT * FROM a_x WHERE a_x.x1 > 0\",\n        )\n        self.validate_identity(\n            \"SELECT x,y FROM (SELECT 1 as x, 2 as y) |> SELECT x, y\",\n            \"WITH __tmp1 AS (SELECT x, y FROM (SELECT 1 AS x, 2 AS y)) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"SELECT 'foo1' AS item1, 2 AS item2 UNION ALL SELECT 'foo2' AS item1, 5 AS item2 |> EXTEND SUM(item2) OVER() AS item2_sum\",\n            \"WITH __tmp1 AS (SELECT *, SUM(item2) OVER () AS item2_sum FROM (SELECT 'foo1' AS item1, 2 AS item2 UNION ALL SELECT 'foo2' AS item1, 5 AS item2)) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"SELECT x, x1 FROM (FROM (SELECT 1 as x, 2 as x1) |> AGGREGATE SUM(x1) as xx GROUP BY x,x1) |> SELECT x\",\n            \"WITH __tmp2 AS (SELECT x FROM (SELECT * FROM (WITH __tmp1 AS (SELECT SUM(x1) AS xx, x, x1 FROM (SELECT 1 AS x, 2 AS x1) GROUP BY x, x1) SELECT * FROM __tmp1))) SELECT * FROM __tmp2\",\n        )\n        self.validate_identity(\n            \"FROM (SELECT 1 as x1) AS x |> SELECT x.x1 |> UNION ALL (FROM (SELECT 1 AS c) |> SELECT c) |> SELECT x1\",\n            \"SELECT * FROM (WITH __tmp1 AS (SELECT x.x1 FROM (SELECT 1 AS x1) AS x), __tmp3 AS (SELECT * FROM __tmp1), __tmp4 AS (SELECT * FROM __tmp3 UNION ALL SELECT * FROM (WITH __tmp2 AS (SELECT c FROM (SELECT 1 AS c)) SELECT * FROM __tmp2)), __tmp5 AS (SELECT x1 FROM __tmp4) SELECT * FROM __tmp5)\",\n        )\n        self.validate_identity(\n            \"FROM (SELECT x1 FROM (SELECT 1 as x1) |> SELECT x1) |> SELECT x1\",\n            \"SELECT * FROM (WITH __tmp2 AS (SELECT x1 FROM ((WITH __tmp1 AS (SELECT x1 FROM (SELECT 1 AS x1)) SELECT * FROM __tmp1))) SELECT * FROM __tmp2)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM (FROM t2 |> SELECT id)\",\n            \"SELECT * FROM (WITH __tmp1 AS (SELECT id FROM t2) SELECT * FROM __tmp1)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1 LEFT JOIN (FROM t2 |> SELECT id) ON TRUE\",\n            \"SELECT * FROM t1 LEFT JOIN (WITH __tmp1 AS (SELECT id FROM t2) SELECT * FROM __tmp1) ON TRUE\",\n        )\n\n    def test_order_by(self):\n        self.validate_identity(\"FROM x |> ORDER BY x1\", \"SELECT * FROM x ORDER BY x1\")\n        self.validate_identity(\n            \"FROM x |> ORDER BY x1 |> ORDER BY x2\", \"SELECT * FROM x ORDER BY x2\"\n        )\n        self.validate_identity(\n            \"FROM x |> ORDER BY x1 |> WHERE x1 > 0 OR x1 != 1 |> ORDER BY x2 |> WHERE x2 > 0 AND x2 != 1 |> SELECT x1, x2\",\n            \"WITH __tmp1 AS (SELECT x1, x2 FROM x WHERE (x1 > 0 OR x1 <> 1) AND (x2 > 0 AND x2 <> 1) ORDER BY x2) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> ORDER BY x1 |> WHERE x1 > 0 |> SELECT x1\",\n            \"WITH __tmp1 AS (SELECT x1 FROM x WHERE x1 > 0 ORDER BY x1) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> WHERE x1 > 0 |> SELECT x1 |> ORDER BY x1\",\n            \"WITH __tmp1 AS (SELECT x1 FROM x WHERE x1 > 0) SELECT * FROM __tmp1 ORDER BY x1\",\n        )\n        self.validate_identity(\n            \"FROM x |> SELECT x1, x2, x3 |> ORDER BY x1 DESC NULLS FIRST, x2 ASC NULLS LAST, x3\",\n            \"WITH __tmp1 AS (SELECT x1, x2, x3 FROM x) SELECT * FROM __tmp1 ORDER BY x1 DESC NULLS FIRST, x2 ASC NULLS LAST, x3\",\n        )\n\n    def test_limit(self):\n        for option in (\"LIMIT 1\", \"LIMIT 1 OFFSET 2\"):\n            with self.subTest(f\"Testing pipe syntax LIMIT and OFFSET option: {option}\"):\n                self.validate_identity(f\"FROM x |> {option}\", f\"SELECT * FROM x {option}\")\n                self.validate_identity(f\"FROM x |> {option}\", f\"SELECT * FROM x {option}\")\n                self.validate_identity(\n                    f\"FROM x |> {option} |> SELECT x1, x2 |> WHERE x1 > 0 |> WHERE x2 > 0  |> ORDER BY x1, x2\",\n                    f\"WITH __tmp1 AS (SELECT x1, x2 FROM x {option}) SELECT * FROM __tmp1 WHERE x1 > 0 AND x2 > 0 ORDER BY x1, x2\",\n                )\n                self.validate_identity(\n                    f\"FROM x |> SELECT x1, x2 |> WHERE x1 > 0 |> WHERE x2 > 0  |> ORDER BY x1, x2 |> {option}\",\n                    f\"WITH __tmp1 AS (SELECT x1, x2 FROM x) SELECT * FROM __tmp1 WHERE x1 > 0 AND x2 > 0 ORDER BY x1, x2 {option}\",\n                )\n        self.validate_identity(\n            \"FROM x |> SELECT x1, x2 |> LIMIT 2 |> LIMIT 4\",\n            \"WITH __tmp1 AS (SELECT x1, x2 FROM x) SELECT * FROM __tmp1 LIMIT 2\",\n        )\n        self.validate_identity(\n            \"FROM x |> SELECT x1, x2 |> LIMIT 2 OFFSET 2 |> LIMIT 4 OFFSET 2\",\n            \"WITH __tmp1 AS (SELECT x1, x2 FROM x) SELECT * FROM __tmp1 LIMIT 2 OFFSET 4\",\n        )\n\n    def test_aggregate(self):\n        self.validate_identity(\n            \"FROM x |> AGGREGATE SUM(x1), MAX(x2), MIN(x3)\",\n            \"WITH __tmp1 AS (SELECT SUM(x1), MAX(x2), MIN(x3) FROM x) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> AGGREGATE SUM(x1) AS s_x1 |> SELECT s_x1\",\n            \"WITH __tmp1 AS (SELECT SUM(x1) AS s_x1 FROM x), __tmp2 AS (SELECT s_x1 FROM __tmp1) SELECT * FROM __tmp2\",\n        )\n        self.validate_identity(\n            \"FROM x |> AGGREGATE SUM(x1), MAX(x2), MIN(x3) GROUP BY x4, x5\",\n            \"WITH __tmp1 AS (SELECT SUM(x1), MAX(x2), MIN(x3), x4, x5 FROM x GROUP BY x4, x5) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> AGGREGATE SUM(x1), MAX(x2), MIN(x3) GROUP BY x4 AS a_x4, x5 AS a_x5\",\n            \"WITH __tmp1 AS (SELECT SUM(x1), MAX(x2), MIN(x3), x4 AS a_x4, x5 AS a_x5 FROM x GROUP BY a_x4, a_x5) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> AGGREGATE SUM(x1) as s_x1 GROUP BY x1 |> SELECT s_x1, x1 as ss_x1\",\n            \"WITH __tmp1 AS (SELECT SUM(x1) AS s_x1, x1 FROM x GROUP BY x1), __tmp2 AS (SELECT s_x1, x1 AS ss_x1 FROM __tmp1) SELECT * FROM __tmp2\",\n        )\n        self.validate_identity(\n            \"FROM x |> AGGREGATE SUM(x1) GROUP\",\n            \"WITH __tmp1 AS (SELECT SUM(x1) AS GROUP FROM x) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> AGGREGATE SUM(x1) as s_x1 GROUP BY x2 as g_x2 |> WHERE s_x1 > 0\",\n            \"WITH __tmp1 AS (SELECT SUM(x1) AS s_x1, x2 AS g_x2 FROM x GROUP BY g_x2) SELECT * FROM __tmp1 WHERE s_x1 > 0\",\n        )\n        for order_option in (\"ASC\", \"DESC\", \"ASC NULLS LAST\", \"DESC NULLS FIRST\"):\n            with self.subTest(f\"Testing pipe syntax AGGREGATE for order option: {order_option}\"):\n                self.validate_all(\n                    f\"WITH __tmp1 AS (SELECT SUM(x1) AS x_s FROM x ORDER BY x_s {order_option}) SELECT * FROM __tmp1\",\n                    read={\n                        \"bigquery\": f\"FROM x |> AGGREGATE SUM(x1) AS x_s {order_option}\",\n                    },\n                )\n                self.validate_all(\n                    f\"WITH __tmp1 AS (SELECT SUM(x1) AS x_s, x1 AS g_x1 FROM x GROUP BY g_x1 ORDER BY x_s {order_option}) SELECT * FROM __tmp1\",\n                    read={\n                        \"bigquery\": f\"FROM x |> AGGREGATE SUM(x1) AS x_s {order_option} GROUP BY x1 AS g_x1\",\n                    },\n                )\n            with self.subTest(\n                f\"Testing pipe syntax AGGREGATE with GROUP AND ORDER BY for order option: {order_option}\"\n            ):\n                self.validate_all(\n                    f\"WITH __tmp1 AS (SELECT SUM(x1) AS x_s, x1 AS g_x1 FROM x GROUP BY g_x1 ORDER BY g_x1 {order_option}), __tmp2 AS (SELECT g_x1, x_s FROM __tmp1) SELECT * FROM __tmp2\",\n                    read={\n                        \"bigquery\": f\"FROM x |> AGGREGATE SUM(x1) AS x_s GROUP AND ORDER BY x1 AS g_x1 {order_option} |> SELECT g_x1, x_s\",\n                    },\n                )\n\n    def test_set_operators(self):\n        self.validate_identity(\n            \"FROM x |> SELECT x.x1 |> UNION ALL (SELECT 1 AS c)\",\n            \"WITH __tmp1 AS (SELECT x.x1 FROM x), __tmp2 AS (SELECT * FROM __tmp1), __tmp3 AS (SELECT * FROM __tmp2 UNION ALL SELECT 1 AS c) SELECT * FROM __tmp3\",\n        )\n\n        for op_operator in (\n            \"UNION ALL\",\n            \"UNION DISTINCT\",\n            \"INTERSECT DISTINCT\",\n            \"EXCEPT DISTINCT\",\n        ):\n            with self.subTest(f\"Testing pipe syntax SET OPERATORS: {op_operator}\"):\n                self.validate_all(\n                    f\"FROM x|> {op_operator} (SELECT y1 FROM y), (SELECT z1 FROM z)\",\n                    write={\n                        \"bigquery\": f\"WITH __tmp1 AS (SELECT * FROM x), __tmp2 AS (SELECT * FROM __tmp1 {op_operator} SELECT y1 FROM y {op_operator} SELECT z1 FROM z) SELECT * FROM __tmp2\"\n                    },\n                )\n\n        for op_prefix in (\"LEFT OUTER\", \"FULL OUTER\"):\n            for op_operator in (\n                \"UNION ALL\",\n                \"UNION DISTINCT\",\n                \"INTERSECT DISTINCT\",\n                \"EXCEPT DISTINCT\",\n            ):\n                with self.subTest(f\"Testing pipe syntax SET OPERATORS: {op_prefix} {op_operator}\"):\n                    self.validate_all(\n                        f\"FROM x|> SELECT x1, x2 |> {op_prefix} {op_operator} BY NAME (SELECT y1, y2 FROM y), (SELECT z1, z2 FROM z)\",\n                        write={\n                            \"bigquery\": f\"WITH __tmp1 AS (SELECT x1, x2 FROM x), __tmp2 AS (SELECT * FROM __tmp1), __tmp3 AS (SELECT * FROM __tmp2 {op_prefix} {op_operator} BY NAME SELECT y1, y2 FROM y {op_prefix} {op_operator} BY NAME SELECT z1, z2 FROM z) SELECT * FROM __tmp3\",\n                        },\n                    )\n\n        self.validate_identity(\n            \"FROM d.x |> SELECT x.x1 |> UNION (SELECT 2 AS a1) |> SELECT x1 |> UNION (SELECT 3 as a2) |> SELECT x1 |> WHERE x1 > 100\",\n            \"\"\"WITH __tmp1 AS (\n  SELECT\n    x.x1\n  FROM d.x\n), __tmp2 AS (\n  SELECT\n    *\n  FROM __tmp1\n), __tmp3 AS (\n  SELECT\n    *\n  FROM __tmp2\n  UNION\n  SELECT\n    2 AS a1\n), __tmp4 AS (\n  SELECT\n    x1\n  FROM __tmp3\n), __tmp5 AS (\n  SELECT\n    *\n  FROM __tmp4\n), __tmp6 AS (\n  SELECT\n    *\n  FROM __tmp5\n  UNION\n  SELECT\n    3 AS a2\n), __tmp7 AS (\n  SELECT\n    x1\n  FROM __tmp6\n)\nSELECT\n  *\nFROM __tmp7\nWHERE\n  x1 > 100\"\"\",\n            pretty=True,\n        )\n        self.validate_identity(\n            \"FROM c.x |> UNION ALL (SELECT 2 AS a1, '2' as a2) |> AGGREGATE AVG(x1) as m_x1 |> SELECT * |> UNION ALL (SELECT y1 FROM c.y) |> SELECT m_x1\",\n            \"\"\"WITH __tmp1 AS (\n  SELECT\n    *\n  FROM c.x\n), __tmp2 AS (\n  SELECT\n    *\n  FROM __tmp1\n  UNION ALL\n  SELECT\n    2 AS a1,\n    '2' AS a2\n), __tmp3 AS (\n  SELECT\n    AVG(x1) AS m_x1\n  FROM __tmp2\n), __tmp4 AS (\n  SELECT\n    *\n  FROM __tmp3\n), __tmp5 AS (\n  SELECT\n    *\n  FROM __tmp4\n), __tmp6 AS (\n  SELECT\n    *\n  FROM __tmp5\n  UNION ALL\n  SELECT\n    y1\n  FROM c.y\n), __tmp7 AS (\n  SELECT\n    m_x1\n  FROM __tmp6\n)\nSELECT\n  *\nFROM __tmp7\"\"\",\n            pretty=True,\n        )\n\n        self.validate_identity(\n            \"FROM c.x |> UNION ALL (SELECT 2 AS a1, '2' as a2) |> UNION ALL (SELECT y1 FROM c.y) |> WHERE x > 200\",\n            \"\"\"WITH __tmp1 AS (\n  SELECT\n    *\n  FROM c.x\n), __tmp2 AS (\n  SELECT\n    *\n  FROM __tmp1\n  UNION ALL\n  SELECT\n    2 AS a1,\n    '2' AS a2\n), __tmp3 AS (\n  SELECT\n    *\n  FROM __tmp2\n), __tmp4 AS (\n  SELECT\n    *\n  FROM __tmp3\n  UNION ALL\n  SELECT\n    y1\n  FROM c.y\n)\nSELECT\n  *\nFROM __tmp4\nWHERE\n  x > 200\"\"\",\n            pretty=True,\n        )\n\n    def test_join(self):\n        self.validate_identity(\"FROM x |> CROSS JOIN y\", \"SELECT * FROM x CROSS JOIN y\")\n        for join_type in (\n            \"JOIN\",\n            \"INNER JOIN\",\n            \"FULL JOIN\",\n            \"FULL OUTER JOIN\",\n            \"LEFT JOIN\",\n            \"LEFT OUTER JOIN\",\n            \"RIGHT JOIN\",\n            \"RIGHT OUTER JOIN\",\n        ):\n            with self.subTest(f\"Testing pipe syntax no projecton with JOIN : {join_type}\"):\n                self.validate_identity(\n                    f\"FROM x |> {join_type} y ON x.id = y.id\",\n                    f\"SELECT * FROM x {join_type} y ON x.id = y.id\",\n                )\n            with self.subTest(f\"Testing pipe syntax projection with JOIN: {join_type}\"):\n                self.validate_identity(\n                    f\"FROM x |> SELECT id |> {join_type} y ON x.id = y.id\",\n                    f\"WITH __tmp1 AS (SELECT id FROM x) SELECT * FROM __tmp1 {join_type} y ON x.id = y.id\",\n                )\n            with self.subTest(f\"Testing pipe syntax complex queries with JOIN: {join_type}\"):\n                self.validate_identity(\n                    f\"FROM x |> {join_type} y ON x.id = y.id |> SELECT x1 as a_x1, x2 |> UNION ALL (SELECT 1, 2) |> WHERE a_x1 > 0\",\n                    f\"\"\"WITH __tmp1 AS (\n  SELECT\n    x1 AS a_x1,\n    x2\n  FROM x\n  {join_type} y\n    ON x.id = y.id\n), __tmp2 AS (\n  SELECT\n    *\n  FROM __tmp1\n), __tmp3 AS (\n  SELECT\n    *\n  FROM __tmp2\n  UNION ALL\n  SELECT\n    1,\n    2\n)\nSELECT\n  *\nFROM __tmp3\nWHERE\n  a_x1 > 0\"\"\",\n                    pretty=True,\n                )\n\n    def test_pivot_unpivot(self):\n        self.validate_identity(\n            \"FROM x |> PIVOT(SUM(x1) FOR quarter IN ('foo1', 'foo2'))\",\n            \"WITH __tmp1 AS (SELECT * FROM x PIVOT(SUM(x1) FOR quarter IN ('foo1', 'foo2'))) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> JOIN y on x.id = y.id |> PIVOT(SUM(x1) FOR quarter IN ('foo1', 'foo2'))\",\n            \"WITH __tmp1 AS (SELECT * FROM x PIVOT(SUM(x1) FOR quarter IN ('foo1', 'foo2')) JOIN y ON x.id = y.id) SELECT * FROM __tmp1\",\n        )\n\n        self.validate_identity(\n            \"FROM x |> UNPIVOT(col FOR item IN (foo1, foo2))\",\n            \"WITH __tmp1 AS (SELECT * FROM x UNPIVOT(col FOR item IN (foo1, foo2))) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> JOIN y on x.id = y.id |> UNPIVOT(col FOR item IN (foo1, foo2))\",\n            \"WITH __tmp1 AS (SELECT * FROM x UNPIVOT(col FOR item IN (foo1, foo2)) JOIN y ON x.id = y.id) SELECT * FROM __tmp1\",\n        )\n\n    def test_as(self):\n        self.validate_identity(\n            \"FROM x |> AS a_x |> WHERE a_x.x1 > 0\",\n            \"WITH a_x AS (SELECT * FROM x) SELECT * FROM a_x WHERE a_x.x1 > 0\",\n        )\n        self.validate_identity(\n            \"FROM x AS t |> AGGREGATE SUM(x1) AS s_x1 GROUP BY id, x2 |> AS t1 |> JOIN y AS t2 ON t1.id = t2.id |> SELECT t2.id, s_x1\",\n            \"WITH __tmp1 AS (SELECT SUM(x1) AS s_x1, id, x2 FROM x AS t GROUP BY id, x2), t1 AS (SELECT * FROM __tmp1), __tmp2 AS (SELECT t2.id, s_x1 FROM t1 JOIN y AS t2 ON t1.id = t2.id) SELECT * FROM __tmp2\",\n        )\n        self.validate_identity(\n            \"FROM x |> JOIN y ON x.x1 = y.y1 |> AS a |> WHERE a.x2 > 1\",\n            \"WITH a AS (SELECT * FROM x JOIN y ON x.x1 = y.y1) SELECT * FROM a WHERE a.x2 > 1\",\n        )\n\n    def test_extend(self):\n        self.validate_identity(\n            \"FROM x |> EXTEND id IN (1, 2) AS is_1_2, id + 1 as a_id\",\n            \"WITH __tmp1 AS (SELECT *, id IN (1, 2) AS is_1_2, id + 1 AS a_id FROM x) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> SELECT x.x1, x.x2 |> EXTEND x1 + 1 as x1_1, x2 + 1 as x2_1 |> WHERE x1_1 > 0 AND x2_1 > 0\",\n            \"WITH __tmp1 AS (SELECT x.x1, x.x2 FROM x), __tmp2 AS (SELECT *, x1 + 1 AS x1_1, x2 + 1 AS x2_1 FROM __tmp1) SELECT * FROM __tmp2 WHERE x1_1 > 0 AND x2_1 > 0\",\n        )\n        self.validate_identity(\n            \"FROM (SELECT 'foo1' AS item1, 2 AS item2 UNION ALL SELECT 'foo2' AS item1, 5 AS item2) |> EXTEND SUM(item2) OVER() AS item2_sum\",\n            \"SELECT * FROM (WITH __tmp1 AS (SELECT *, SUM(item2) OVER () AS item2_sum FROM (SELECT 'foo1' AS item1, 2 AS item2 UNION ALL SELECT 'foo2' AS item1, 5 AS item2)) SELECT * FROM __tmp1)\",\n        )\n\n    def test_tablesample(self):\n        self.validate_identity(\n            \"FROM x |> TABLESAMPLE SYSTEM (1 PERCENT)\",\n            \"SELECT * FROM x TABLESAMPLE SYSTEM (1 PERCENT)\",\n        )\n        self.validate_identity(\n            \"FROM x |> SELECT x.x1 |> TABLESAMPLE SYSTEM (1 PERCENT)\",\n            \"WITH __tmp1 AS (SELECT x.x1 FROM x TABLESAMPLE SYSTEM (1 PERCENT)) SELECT * FROM __tmp1\",\n        )\n        self.validate_identity(\n            \"FROM x |> TABLESAMPLE SYSTEM (1 PERCENT) |> WHERE x.x1 > 0 |> SELECT x1, x2\",\n            \"WITH __tmp1 AS (SELECT x1, x2 FROM x WHERE x.x1 > 0 TABLESAMPLE SYSTEM (1 PERCENT)) SELECT * FROM __tmp1\",\n        )\n"
  },
  {
    "path": "tests/dialects/test_postgres.py",
    "content": "from sqlglot import ParseError, UnsupportedError, exp, transpile\nfrom sqlglot.helper import logger as helper_logger\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestPostgres(Validator):\n    maxDiff = None\n    dialect = \"postgres\"\n\n    def test_postgres(self):\n        expr = self.parse_one(\"SELECT * FROM r CROSS JOIN LATERAL UNNEST(ARRAY[1]) AS s(location)\")\n        unnest = expr.args[\"joins\"][0].this.this\n        unnest.assert_is(exp.Unnest)\n\n        alter_table_only = \"\"\"ALTER TABLE ONLY \"Album\" ADD CONSTRAINT \"FK_AlbumArtistId\" FOREIGN KEY (\"ArtistId\") REFERENCES \"Artist\" (\"ArtistId\") ON DELETE NO ACTION ON UPDATE NO ACTION\"\"\"\n        expr = self.parse_one(alter_table_only)\n\n        self.assertIsInstance(expr, exp.Alter)\n        self.assertEqual(expr.sql(dialect=\"postgres\"), alter_table_only)\n\n        sql = \"ARRAY[x\" + \",x\" * 27 + \"]\"\n        expected_sql = \"ARRAY[\\n  x\" + (\",\\n  x\" * 27) + \"\\n]\"\n        self.validate_identity(sql, expected_sql, pretty=True)\n\n        self.validate_identity(\"SELECT GET_BIT(CAST(44 AS BIT(10)), 6)\")\n        self.validate_identity(\"SELECT * FROM t GROUP BY ROLLUP (a || '^' || b)\")\n        self.validate_identity(\"SELECT COSH(1.5)\")\n        self.validate_identity(\"SELECT EXP(1)\")\n        self.validate_identity(\n            \"SELECT MODE() WITHIN GROUP (ORDER BY status DESC) AS most_common FROM orders\"\n        )\n        self.validate_identity(\"SELECT ST_DISTANCE(gg1, gg2, FALSE) AS sphere_dist\")\n        self.validate_identity(\"SHA384(x)\")\n        self.validate_identity(\"1.x\", \"1. AS x\")\n        self.validate_identity(\"|/ x\", \"SQRT(x)\")\n        self.validate_identity(\"||/ x\", \"CBRT(x)\")\n        self.validate_identity(\"SELECT EXTRACT(QUARTER FROM CAST('2025-04-26' AS DATE))\")\n        self.validate_identity(\"SELECT DATE_TRUNC('QUARTER', CAST('2025-04-26' AS DATE))\")\n        self.validate_identity(\"STRING_TO_ARRAY('xx~^~yy~^~zz', '~^~', 'yy')\")\n        self.validate_identity(\"SELECT x FROM t WHERE CAST($1 AS TEXT) = 'ok'\")\n        self.validate_identity(\"SELECT * FROM t TABLESAMPLE SYSTEM (50) REPEATABLE (55)\")\n        self.validate_identity(\"x @@ y\")\n        self.validate_identity(\"CAST(x AS MONEY)\")\n        self.validate_identity(\"CAST(x AS INT4RANGE)\")\n        self.validate_identity(\"CAST(x AS INT4MULTIRANGE)\")\n        self.validate_identity(\"CAST(x AS INT8RANGE)\")\n        self.validate_identity(\"CAST(x AS INT8MULTIRANGE)\")\n        self.validate_identity(\"CAST(x AS NUMRANGE)\")\n        self.validate_identity(\"CAST(x AS NUMMULTIRANGE)\")\n        self.validate_identity(\"CAST(x AS TSRANGE)\")\n        self.validate_identity(\"CAST(x AS TSMULTIRANGE)\")\n        self.validate_identity(\"CAST(x AS TSTZRANGE)\")\n        self.validate_identity(\"CAST(x AS TSTZMULTIRANGE)\")\n        self.validate_identity(\"CAST(x AS DATERANGE)\")\n        self.validate_identity(\"CAST(x AS DATEMULTIRANGE)\")\n        self.validate_identity(\"x$\")\n        self.validate_identity(\"LENGTH(x)\")\n        self.validate_identity(\"LENGTH(x, utf8)\")\n        self.validate_identity(\"CHAR_LENGTH(x)\", \"LENGTH(x)\")\n        self.validate_identity(\"CHARACTER_LENGTH(x)\", \"LENGTH(x)\")\n        self.validate_identity(\"SELECT ARRAY[1, 2, 3]\")\n        self.validate_identity(\"SELECT ARRAY(SELECT 1)\")\n        self.validate_identity(\"STRING_AGG(x, y)\")\n        self.validate_identity(\"STRING_AGG(x, ',' ORDER BY y)\")\n        self.validate_identity(\"STRING_AGG(x, ',' ORDER BY y DESC)\")\n        self.validate_identity(\"STRING_AGG(DISTINCT x, ',' ORDER BY y DESC)\")\n        self.validate_identity(\"SELECT CASE WHEN SUBSTRING('abcdefg') IN ('ab') THEN 1 ELSE 0 END\")\n        self.validate_identity(\"COMMENT ON TABLE mytable IS 'this'\")\n        self.validate_identity(\"COMMENT ON MATERIALIZED VIEW my_view IS 'this'\")\n        self.validate_identity(\"SELECT e'\\\\xDEADBEEF'\")\n        self.validate_identity(\"SELECT CAST(e'\\\\176' AS BYTEA)\")\n        self.validate_identity(\"SELECT * FROM x WHERE SUBSTRING('Thomas' FROM '...$') IN ('mas')\")\n        self.validate_identity(\"SELECT TRIM(' X' FROM ' XXX ')\")\n        self.validate_identity(\"SELECT TRIM(LEADING 'bla' FROM ' XXX ' COLLATE utf8_bin)\")\n        self.validate_identity(\"\"\"SELECT * FROM JSON_TO_RECORDSET(z) AS y(\"rank\" INT)\"\"\")\n        self.validate_identity(\"SELECT ~x\")\n        self.validate_identity(\"x ~ 'y'\")\n        self.validate_identity(\"x ~* 'y'\")\n        self.validate_identity(\"SELECT * FROM r CROSS JOIN LATERAL UNNEST(ARRAY[1]) AS s(location)\")\n        self.validate_identity(\"CAST(1 AS DECIMAL) / CAST(2 AS DECIMAL) * -100\")\n        self.validate_identity(\"EXEC AS myfunc @id = 123\", check_command_warning=True)\n        self.validate_identity(\"SELECT CURRENT_SCHEMA\")\n        self.validate_identity(\"SELECT CURRENT_USER\")\n        self.validate_identity(\"SELECT CURRENT_ROLE\")\n        self.validate_identity(\"SELECT VERSION()\")\n        self.validate_identity(\"SELECT * FROM ONLY t1\")\n        self.validate_identity(\"SELECT INTERVAL '-1 MONTH'\")\n        self.validate_identity(\"SELECT INTERVAL '4.1 DAY'\")\n        self.validate_identity(\"SELECT INTERVAL '3.14159 HOUR'\")\n        self.validate_identity(\"SELECT INTERVAL '2.5 MONTH'\")\n        self.validate_identity(\"SELECT INTERVAL '-10.75 MINUTE'\")\n        self.validate_identity(\"SELECT INTERVAL '0.123456789 SECOND'\")\n        self.validate_identity(\n            \"SELECT SUM(x) OVER (PARTITION BY y ORDER BY interval ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - SUM(x) OVER (PARTITION BY y ORDER BY interval ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM test_data, LATERAL JSONB_ARRAY_ELEMENTS(data) WITH ORDINALITY AS elem(value, ordinality)\"\n        )\n        self.validate_identity(\n            \"SELECT id, name FROM xml_data AS t, XMLTABLE('/root/user' PASSING t.xml COLUMNS id INT PATH '@id', name TEXT PATH 'name/text()') AS x\"\n        )\n        self.validate_identity(\n            \"SELECT id, value FROM xml_content AS t, XMLTABLE(XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2), '/root/data' PASSING t.xml COLUMNS id INT PATH '@ns1:id', value TEXT PATH 'ns2:value/text()') AS x\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM t WHERE some_column >= CURRENT_DATE + INTERVAL '1 day 1 hour' AND some_another_column IS TRUE\"\n        )\n        self.validate_identity(\n            \"\"\"UPDATE \"x\" SET \"y\" = CAST('0 days 60.000000 seconds' AS INTERVAL) WHERE \"x\".\"id\" IN (2, 3)\"\"\"\n        )\n        self.validate_identity(\n            \"WITH t1 AS MATERIALIZED (SELECT 1), t2 AS NOT MATERIALIZED (SELECT 2) SELECT * FROM t1, t2\"\n        )\n        self.validate_identity(\n            \"\"\"LAST_VALUE(\"col1\") OVER (ORDER BY \"col2\" RANGE BETWEEN INTERVAL '1 DAY' PRECEDING AND '1 month' FOLLOWING)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"ALTER TABLE ONLY \"Album\" ADD CONSTRAINT \"FK_AlbumArtistId\" FOREIGN KEY (\"ArtistId\") REFERENCES \"Artist\" (\"ArtistId\") ON DELETE CASCADE\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"ALTER TABLE ONLY \"Album\" ADD CONSTRAINT \"FK_AlbumArtistId\" FOREIGN KEY (\"ArtistId\") REFERENCES \"Artist\" (\"ArtistId\") ON DELETE RESTRICT\"\"\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM JSON_ARRAY_ELEMENTS('[1,true, [2,false]]') WITH ORDINALITY\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM JSON_ARRAY_ELEMENTS('[1,true, [2,false]]') WITH ORDINALITY AS kv_json\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM JSON_ARRAY_ELEMENTS('[1,true, [2,false]]') WITH ORDINALITY AS kv_json(a, b)\"\n        )\n        self.validate_identity(\n            \"SELECT SUM(x) OVER a, SUM(y) OVER b FROM c WINDOW a AS (PARTITION BY d), b AS (PARTITION BY e)\"\n        )\n        self.validate_identity(\n            \"SELECT CASE WHEN SUBSTRING('abcdefg' FROM 1) IN ('ab') THEN 1 ELSE 0 END\"\n        )\n        self.validate_identity(\n            \"SELECT CASE WHEN SUBSTRING('abcdefg' FROM 1 FOR 2) IN ('ab') THEN 1 ELSE 0 END\"\n        )\n        self.validate_identity(\n            'SELECT * FROM \"x\" WHERE SUBSTRING(\"x\".\"foo\" FROM 1 FOR 2) IN (\\'mas\\')'\n        )\n        self.validate_identity(\n            \"SELECT * FROM x WHERE SUBSTRING('Thomas' FROM '%#\\\"o_a#\\\"_' FOR '#') IN ('mas')\"\n        )\n        self.validate_identity(\n            \"SELECT SUBSTRING('bla' + 'foo' || 'bar' FROM 3 - 1 + 5 FOR 4 + SOME_FUNC(arg1, arg2))\"\n        )\n        self.validate_identity(\n            \"SELECT TO_TIMESTAMP(1284352323.5), TO_TIMESTAMP('05 Dec 2000', 'DD Mon YYYY')\"\n        )\n        self.validate_identity(\n            \"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 AM', 'DD Mon YYYY HH:MI AM')\"\n        )\n        self.validate_identity(\n            \"SELECT TO_TIMESTAMP('05 Dec 2000 10:00 PM', 'DD Mon YYYY HH:MI PM')\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) AS ss\"\n        )\n        self.validate_identity(\n            \"SELECT c.oid, n.nspname, c.relname \"\n            \"FROM pg_catalog.pg_class AS c \"\n            \"LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace \"\n            \"WHERE c.relname OPERATOR(pg_catalog.~) '^(courses)$' COLLATE pg_catalog.default AND \"\n            \"pg_catalog.PG_TABLE_IS_VISIBLE(c.oid) \"\n            \"ORDER BY 2, 3\"\n        )\n        self.validate_identity(\n            \"SELECT e'foo \\\\' bar'\",\n            \"SELECT e'foo '' bar'\",\n        )\n        self.validate_identity(\"SELECT e'\\\\n'\")\n        self.validate_identity(\"SELECT e'\\\\t'\")\n        self.validate_identity(\n            \"SELECT e'update table_name set a = \\\\'foo\\\\' where 1 = 0' AS x FROM tab\",\n            \"SELECT e'update table_name set a = ''foo'' where 1 = 0' AS x FROM tab\",\n        )\n        self.validate_identity(\n            \"select count() OVER(partition by a order by a range offset preceding exclude current row)\",\n            \"SELECT COUNT() OVER (PARTITION BY a ORDER BY a range BETWEEN offset preceding AND CURRENT ROW EXCLUDE CURRENT ROW)\",\n        )\n        self.validate_identity(\n            \"x::JSON -> 'duration' ->> -1\",\n            \"JSON_EXTRACT_PATH_TEXT(CAST(x AS JSON) -> 'duration', -1)\",\n        ).assert_is(exp.JSONExtractScalar).this.assert_is(exp.JSONExtract)\n        self.validate_identity(\n            \"SELECT SUBSTRING('Thomas' FOR 3 FROM 2)\",\n            \"SELECT SUBSTRING('Thomas' FROM 2 FOR 3)\",\n        )\n        self.validate_identity(\n            \"SELECT ARRAY[1, 2, 3] <@ ARRAY[1, 2]\",\n            \"SELECT ARRAY[1, 2] @> ARRAY[1, 2, 3]\",\n        )\n        self.validate_identity(\n            \"SELECT DATE_PART('isodow'::varchar(6), current_date)\",\n            \"SELECT EXTRACT(CAST('isodow' AS VARCHAR(6)) FROM CURRENT_DATE)\",\n        )\n        self.validate_identity(\n            \"END WORK AND NO CHAIN\",\n            \"COMMIT AND NO CHAIN\",\n        )\n        self.validate_identity(\n            \"END AND CHAIN\",\n            \"COMMIT AND CHAIN\",\n        )\n        self.validate_identity(\n            \"\"\"x ? 'x'\"\"\",\n            \"x ? 'x'\",\n        )\n        self.validate_identity(\n            \"SELECT $$a$$\",\n            \"SELECT 'a'\",\n        )\n        self.validate_identity(\n            \"SELECT $$Dianne's horse$$\",\n            \"SELECT 'Dianne''s horse'\",\n        )\n        self.validate_identity(\n            \"SELECT $$The price is $9.95$$ AS msg\",\n            \"SELECT 'The price is $9.95' AS msg\",\n        )\n        self.validate_identity(\n            \"COMMENT ON TABLE mytable IS $$doc this$$\", \"COMMENT ON TABLE mytable IS 'doc this'\"\n        )\n        self.validate_identity(\n            \"UPDATE MYTABLE T1 SET T1.COL = 13\",\n            \"UPDATE MYTABLE AS T1 SET T1.COL = 13\",\n        )\n        self.validate_identity(\n            \"x !~ 'y'\",\n            \"NOT x ~ 'y'\",\n        )\n        self.validate_identity(\n            \"x !~* 'y'\",\n            \"NOT x ~* 'y'\",\n        )\n        self.validate_identity(\n            \"x ~~ 'y'\",\n            \"x LIKE 'y'\",\n        )\n        self.validate_identity(\n            \"x ~~* 'y'\",\n            \"x ILIKE 'y'\",\n        )\n        self.validate_identity(\n            \"x !~~ 'y'\",\n            \"NOT x LIKE 'y'\",\n        )\n        self.validate_identity(\n            \"x !~~* 'y'\",\n            \"NOT x ILIKE 'y'\",\n        )\n        self.validate_identity(\n            \"'45 days'::interval day\",\n            \"CAST('45 days' AS INTERVAL DAY)\",\n        )\n        self.validate_identity(\n            \"'x' 'y' 'z'\",\n            \"CONCAT('x', 'y', 'z')\",\n        )\n        self.validate_identity(\n            \"x::cstring\",\n            \"CAST(x AS CSTRING)\",\n        )\n        self.validate_identity(\n            \"x::oid\",\n            \"CAST(x AS OID)\",\n        )\n        self.validate_identity(\n            \"x::regclass\",\n            \"CAST(x AS REGCLASS)\",\n        )\n        self.validate_identity(\n            \"x::regcollation\",\n            \"CAST(x AS REGCOLLATION)\",\n        )\n        self.validate_identity(\n            \"x::regconfig\",\n            \"CAST(x AS REGCONFIG)\",\n        )\n        self.validate_identity(\n            \"x::regdictionary\",\n            \"CAST(x AS REGDICTIONARY)\",\n        )\n        self.validate_identity(\n            \"x::regnamespace\",\n            \"CAST(x AS REGNAMESPACE)\",\n        )\n        self.validate_identity(\n            \"x::regoper\",\n            \"CAST(x AS REGOPER)\",\n        )\n        self.validate_identity(\n            \"x::regoperator\",\n            \"CAST(x AS REGOPERATOR)\",\n        )\n        self.validate_identity(\n            \"x::regproc\",\n            \"CAST(x AS REGPROC)\",\n        )\n        self.validate_identity(\n            \"x::regprocedure\",\n            \"CAST(x AS REGPROCEDURE)\",\n        )\n        self.validate_identity(\n            \"x::regrole\",\n            \"CAST(x AS REGROLE)\",\n        )\n        self.validate_identity(\n            \"x::regtype\",\n            \"CAST(x AS REGTYPE)\",\n        )\n        self.validate_identity(\n            \"123::CHARACTER VARYING\",\n            \"CAST(123 AS VARCHAR)\",\n        )\n        self.validate_identity(\n            \"TO_TIMESTAMP(123::DOUBLE PRECISION)\",\n            \"TO_TIMESTAMP(CAST(123 AS DOUBLE PRECISION))\",\n        )\n        self.validate_identity(\n            \"SELECT to_timestamp(123)::time without time zone\",\n            \"SELECT CAST(TO_TIMESTAMP(123) AS TIME)\",\n        )\n        self.validate_identity(\n            \"SELECT SUM(x) OVER (PARTITION BY a ORDER BY d ROWS 1 PRECEDING)\",\n            \"SELECT SUM(x) OVER (PARTITION BY a ORDER BY d ROWS BETWEEN 1 PRECEDING AND CURRENT ROW)\",\n        )\n        self.validate_identity(\n            \"SELECT SUBSTRING(2022::CHAR(4) || LPAD(3::CHAR(2), 2, '0') FROM 3 FOR 4)\",\n            \"SELECT SUBSTRING(CAST(2022 AS CHAR(4)) || LPAD(CAST(3 AS CHAR(2)), 2, '0') FROM 3 FOR 4)\",\n        )\n        self.validate_identity(\n            \"SELECT m.name FROM manufacturers AS m LEFT JOIN LATERAL GET_PRODUCT_NAMES(m.id) pname ON TRUE WHERE pname IS NULL\",\n            \"SELECT m.name FROM manufacturers AS m LEFT JOIN LATERAL GET_PRODUCT_NAMES(m.id) AS pname ON TRUE WHERE pname IS NULL\",\n        )\n        self.validate_identity(\n            \"SELECT p1.id, p2.id, v1, v2 FROM polygons AS p1, polygons AS p2, LATERAL VERTICES(p1.poly) v1, LATERAL VERTICES(p2.poly) v2 WHERE (v1 <-> v2) < 10 AND p1.id <> p2.id\",\n            \"SELECT p1.id, p2.id, v1, v2 FROM polygons AS p1, polygons AS p2, LATERAL VERTICES(p1.poly) AS v1, LATERAL VERTICES(p2.poly) AS v2 WHERE (v1 <-> v2) < 10 AND p1.id <> p2.id\",\n        )\n        self.validate_identity(\n            \"SELECT id, email, CAST(deleted AS TEXT) FROM users WHERE deleted NOTNULL\",\n            \"SELECT id, email, CAST(deleted AS TEXT) FROM users WHERE NOT deleted IS NULL\",\n        )\n        self.validate_identity(\n            \"SELECT id, email, CAST(deleted AS TEXT) FROM users WHERE NOT deleted ISNULL\",\n            \"SELECT id, email, CAST(deleted AS TEXT) FROM users WHERE NOT deleted IS NULL\",\n        )\n        self.validate_identity(\n            \"\"\"'{\"x\": {\"y\": 1}}'::json->'x'->'y'\"\"\",\n            \"\"\"CAST('{\"x\": {\"y\": 1}}' AS JSON) -> 'x' -> 'y'\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"'[1,2,3]'::json->>2\"\"\",\n            \"CAST('[1,2,3]' AS JSON) ->> 2\",\n        )\n        self.validate_identity(\n            \"\"\"'{\"a\":1,\"b\":2}'::json->>'b'\"\"\",\n            \"\"\"CAST('{\"a\":1,\"b\":2}' AS JSON) ->> 'b'\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"'{\"a\":[1,2,3],\"b\":[4,5,6]}'::json#>'{a,2}'\"\"\",\n            \"\"\"CAST('{\"a\":[1,2,3],\"b\":[4,5,6]}' AS JSON) #> '{a,2}'\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"'{\"a\":[1,2,3],\"b\":[4,5,6]}'::json#>>'{a,2}'\"\"\",\n            \"\"\"CAST('{\"a\":[1,2,3],\"b\":[4,5,6]}' AS JSON) #>> '{a,2}'\"\"\",\n        )\n        self.validate_identity(\n            \"'[1,2,3]'::json->2\",\n            \"CAST('[1,2,3]' AS JSON) -> 2\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT JSON_ARRAY_ELEMENTS((foo->'sections')::JSON) AS sections\"\"\",\n            \"\"\"SELECT JSON_ARRAY_ELEMENTS(CAST((foo -> 'sections') AS JSON)) AS sections\"\"\",\n        )\n        self.validate_identity(\n            \"MERGE INTO x USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET x.a = y.b WHEN NOT MATCHED THEN INSERT (a, b) VALUES (y.a, y.b)\",\n            \"MERGE INTO x USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET a = y.b WHEN NOT MATCHED THEN INSERT (a, b) VALUES (y.a, y.b)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1*\",\n            \"SELECT * FROM t1\",\n        )\n        self.validate_identity(\n            \"SELECT SUBSTRING('afafa' for 1)\",\n            \"SELECT SUBSTRING('afafa' FROM 1 FOR 1)\",\n        )\n        self.validate_identity(\n            \"CAST(x AS INT8)\",\n            \"CAST(x AS BIGINT)\",\n        )\n        self.validate_identity(\n            \"\"\"\n            WITH\n              json_data AS (SELECT '{\"field_id\": [1, 2, 3]}'::JSON AS data),\n              field_ids AS (SELECT 'field_id' AS field_id)\n\n            SELECT\n                JSON_ARRAY_ELEMENTS(json_data.data -> field_ids.field_id) AS element\n            FROM json_data, field_ids\n            \"\"\",\n            \"\"\"WITH json_data AS (\n  SELECT\n    CAST('{\"field_id\": [1, 2, 3]}' AS JSON) AS data\n), field_ids AS (\n  SELECT\n    'field_id' AS field_id\n)\nSELECT\n  JSON_ARRAY_ELEMENTS(JSON_EXTRACT_PATH(json_data.data, field_ids.field_id)) AS element\nFROM json_data, field_ids\"\"\",\n            pretty=True,\n        )\n\n        self.validate_all(\n            \"x ? y\",\n            write={\n                \"\": \"JSONB_CONTAINS(x, y)\",\n                \"postgres\": \"x ? y\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CURRENT_TIMESTAMP + INTERVAL '-3 MONTH'\",\n            read={\n                \"mysql\": \"SELECT DATE_ADD(CURRENT_TIMESTAMP, INTERVAL -1 QUARTER)\",\n                \"postgres\": \"SELECT CURRENT_TIMESTAMP + INTERVAL '-3 MONTH'\",\n                \"tsql\": \"SELECT DATEADD(QUARTER, -1, GETDATE())\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY[]::INT[] AS foo\",\n            write={\n                \"postgres\": \"SELECT CAST(ARRAY[] AS INT[]) AS foo\",\n                \"duckdb\": \"SELECT CAST([] AS INT[]) AS foo\",\n            },\n        )\n        self.validate_all(\n            \"STRING_TO_ARRAY('xx~^~yy~^~zz', '~^~', 'yy')\",\n            read={\n                \"doris\": \"SPLIT_BY_STRING('xx~^~yy~^~zz', '~^~', 'yy')\",\n            },\n            write={\n                \"doris\": \"SPLIT_BY_STRING('xx~^~yy~^~zz', '~^~', 'yy')\",\n                \"postgres\": \"STRING_TO_ARRAY('xx~^~yy~^~zz', '~^~', 'yy')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]\",\n            read={\n                \"duckdb\": \"SELECT [1, 2, 3] @> [1, 2]\",\n            },\n            write={\n                \"duckdb\": \"SELECT [1, 2, 3] @> [1, 2]\",\n                \"mysql\": UnsupportedError,\n                \"postgres\": \"SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]\",\n            },\n        )\n        self.validate_all(\n            \"SELECT REGEXP_REPLACE('mr .', '[^a-zA-Z]', '', 'g')\",\n            write={\n                \"duckdb\": \"SELECT REGEXP_REPLACE('mr .', '[^a-zA-Z]', '', 'g')\",\n                \"postgres\": \"SELECT REGEXP_REPLACE('mr .', '[^a-zA-Z]', '', 'g')\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE t (c INT)\",\n            read={\n                \"mysql\": \"CREATE TABLE t (c INT COMMENT 'comment 1') COMMENT = 'comment 2'\",\n            },\n        )\n        self.validate_all(\n            'SELECT * FROM \"test_table\" ORDER BY RANDOM() LIMIT 5',\n            write={\n                \"bigquery\": \"SELECT * FROM `test_table` ORDER BY RAND() NULLS LAST LIMIT 5\",\n                \"duckdb\": 'SELECT * FROM \"test_table\" ORDER BY RANDOM() LIMIT 5',\n                \"postgres\": 'SELECT * FROM \"test_table\" ORDER BY RANDOM() LIMIT 5',\n                \"tsql\": \"SELECT TOP 5 * FROM [test_table] ORDER BY RAND()\",\n            },\n        )\n        self.validate_all(\n            \"SELECT (data -> 'en-US') AS acat FROM my_table\",\n            write={\n                \"duckdb\": \"\"\"SELECT (data -> '$.\"en-US\"') AS acat FROM my_table\"\"\",\n                \"postgres\": \"SELECT (data -> 'en-US') AS acat FROM my_table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT (data ->> 'en-US') AS acat FROM my_table\",\n            write={\n                \"duckdb\": \"\"\"SELECT (data ->> '$.\"en-US\"') AS acat FROM my_table\"\"\",\n                \"postgres\": \"SELECT (data ->> 'en-US') AS acat FROM my_table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_PATH_TEXT(x, k1, k2, k3) FROM t\",\n            read={\n                \"clickhouse\": \"SELECT JSONExtractString(x, k1, k2, k3) FROM t\",\n                \"redshift\": \"SELECT JSON_EXTRACT_PATH_TEXT(x, k1, k2, k3) FROM t\",\n            },\n            write={\n                \"clickhouse\": \"SELECT JSONExtractString(x, k1, k2, k3) FROM t\",\n                \"postgres\": \"SELECT JSON_EXTRACT_PATH_TEXT(x, k1, k2, k3) FROM t\",\n                \"redshift\": \"SELECT JSON_EXTRACT_PATH_TEXT(x, k1, k2, k3) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"x #> 'y'\",\n            read={\n                \"\": \"JSONB_EXTRACT(x, 'y')\",\n            },\n            write={\n                \"\": \"JSONB_EXTRACT(x, 'y')\",\n                \"postgres\": \"x #> 'y'\",\n            },\n        )\n        self.validate_all(\n            \"x #>> 'y'\",\n            read={\n                \"\": \"JSONB_EXTRACT_SCALAR(x, 'y')\",\n            },\n            write={\n                \"\": \"JSONB_EXTRACT_SCALAR(x, 'y')\",\n                \"postgres\": \"x #>> 'y'\",\n            },\n        )\n        self.validate_all(\n            \"x -> 'y' -> 0 -> 'z'\",\n            write={\n                \"\": \"JSON_EXTRACT(JSON_EXTRACT(JSON_EXTRACT(x, '$.y'), '$[0]'), '$.z')\",\n                \"postgres\": \"x -> 'y' -> 0 -> 'z'\",\n            },\n        )\n        self.validate_all(\n            \"\"\"JSON_EXTRACT_PATH('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}','f4')\"\"\",\n            write={\n                \"\": \"\"\"JSON_EXTRACT('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', '$.f4')\"\"\",\n                \"bigquery\": \"\"\"JSON_EXTRACT('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', '$.f4')\"\"\",\n                \"duckdb\": \"\"\"'{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}' -> '$.f4'\"\"\",\n                \"mysql\": \"\"\"JSON_EXTRACT('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', '$.f4')\"\"\",\n                \"postgres\": \"\"\"JSON_EXTRACT_PATH('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', 'f4')\"\"\",\n                \"presto\": \"\"\"JSON_EXTRACT('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', '$.f4')\"\"\",\n                \"redshift\": \"\"\"JSON_EXTRACT_PATH_TEXT('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', 'f4')\"\"\",\n                \"spark\": \"\"\"GET_JSON_OBJECT('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', '$.f4')\"\"\",\n                \"sqlite\": \"\"\"'{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}' -> '$.f4'\"\"\",\n                \"tsql\": \"\"\"ISNULL(JSON_QUERY('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', '$.f4'), JSON_VALUE('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"foo\"}}', '$.f4'))\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"JSON_EXTRACT_PATH_TEXT('{\"farm\": [\"a\", \"b\", \"c\"]}', 'farm', '0')\"\"\",\n            read={\n                \"duckdb\": \"\"\"'{\"farm\": [\"a\", \"b\", \"c\"]}' ->> '$.farm[0]'\"\"\",\n                \"redshift\": \"\"\"JSON_EXTRACT_PATH_TEXT('{\"farm\": [\"a\", \"b\", \"c\"]}', 'farm', '0')\"\"\",\n            },\n            write={\n                \"duckdb\": \"\"\"'{\"farm\": [\"a\", \"b\", \"c\"]}' ->> '$.farm[0]'\"\"\",\n                \"postgres\": \"\"\"JSON_EXTRACT_PATH_TEXT('{\"farm\": [\"a\", \"b\", \"c\"]}', 'farm', '0')\"\"\",\n                \"redshift\": \"\"\"JSON_EXTRACT_PATH_TEXT('{\"farm\": [\"a\", \"b\", \"c\"]}', 'farm', '0')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"JSON_EXTRACT_PATH(x, 'x', 'y', 'z')\",\n            read={\n                \"duckdb\": \"x -> '$.x.y.z'\",\n                \"postgres\": \"JSON_EXTRACT_PATH(x, 'x', 'y', 'z')\",\n            },\n            write={\n                \"duckdb\": \"x -> '$.x.y.z'\",\n                \"redshift\": \"JSON_EXTRACT_PATH_TEXT(x, 'x', 'y', 'z')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t TABLESAMPLE SYSTEM (50)\",\n            write={\n                \"postgres\": \"SELECT * FROM t TABLESAMPLE SYSTEM (50)\",\n                \"redshift\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount)\",\n            write={\n                \"databricks\": \"SELECT PERCENTILE_APPROX(amount, 0.5)\",\n                \"postgres\": \"SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount)\",\n                \"presto\": \"SELECT APPROX_PERCENTILE(amount, 0.5)\",\n                \"spark\": \"SELECT PERCENTILE_APPROX(amount, 0.5)\",\n                \"trino\": \"SELECT APPROX_PERCENTILE(amount, 0.5)\",\n            },\n        )\n        self.validate_all(\n            \"e'x'\",\n            write={\n                \"mysql\": \"x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART('minute', timestamp '2023-01-04 04:05:06.789')\",\n            write={\n                \"postgres\": \"SELECT EXTRACT(minute FROM CAST('2023-01-04 04:05:06.789' AS TIMESTAMP))\",\n                \"redshift\": \"SELECT EXTRACT(minute FROM CAST('2023-01-04 04:05:06.789' AS TIMESTAMP))\",\n                \"snowflake\": \"SELECT DATE_PART(minute, CAST('2023-01-04 04:05:06.789' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART('month', date '20220502')\",\n            write={\n                \"postgres\": \"SELECT EXTRACT(month FROM CAST('20220502' AS DATE))\",\n                \"redshift\": \"SELECT EXTRACT(month FROM CAST('20220502' AS DATE))\",\n                \"snowflake\": \"SELECT DATE_PART(month, CAST('20220502' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT (DATE '2016-01-10', DATE '2016-02-01') OVERLAPS (DATE '2016-01-20', DATE '2016-02-10')\",\n            write={\n                \"postgres\": \"SELECT (CAST('2016-01-10' AS DATE), CAST('2016-02-01' AS DATE)) OVERLAPS (CAST('2016-01-20' AS DATE), CAST('2016-02-10' AS DATE))\",\n                \"tsql\": \"SELECT (CAST('2016-01-10' AS DATE), CAST('2016-02-01' AS DATE)) OVERLAPS (CAST('2016-01-20' AS DATE), CAST('2016-02-10' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART('epoch', CAST('2023-01-04 04:05:06.789' AS TIMESTAMP))\",\n            read={\n                \"\": \"SELECT TIME_TO_UNIX(TIMESTAMP '2023-01-04 04:05:06.789')\",\n            },\n        )\n        self.validate_all(\n            \"x ^ y\",\n            write={\n                \"\": \"POWER(x, y)\",\n                \"postgres\": \"POWER(x, y)\",\n            },\n        )\n        self.validate_all(\n            \"x # y\",\n            write={\n                \"\": \"x ^ y\",\n                \"postgres\": \"x # y\",\n            },\n        )\n        self.validate_all(\n            \"SELECT GENERATE_SERIES(1, 5)\",\n            write={\n                \"bigquery\": UnsupportedError,\n                \"postgres\": \"SELECT GENERATE_SERIES(1, 5)\",\n            },\n        )\n        self.validate_all(\n            \"WITH dates AS (SELECT GENERATE_SERIES('2020-01-01'::DATE, '2024-01-01'::DATE, '1 day'::INTERVAL) AS date), date_table AS (SELECT DISTINCT DATE_TRUNC('MONTH', date) AS date FROM dates) SELECT * FROM date_table\",\n            write={\n                \"duckdb\": \"WITH dates AS (SELECT UNNEST(GENERATE_SERIES(CAST('2020-01-01' AS DATE), CAST('2024-01-01' AS DATE), CAST('1 day' AS INTERVAL))) AS date), date_table AS (SELECT DISTINCT DATE_TRUNC('MONTH', date) AS date FROM dates) SELECT * FROM date_table\",\n                \"postgres\": \"WITH dates AS (SELECT GENERATE_SERIES(CAST('2020-01-01' AS DATE), CAST('2024-01-01' AS DATE), CAST('1 day' AS INTERVAL)) AS date), date_table AS (SELECT DISTINCT DATE_TRUNC('MONTH', date) AS date FROM dates) SELECT * FROM date_table\",\n            },\n        )\n        self.validate_all(\n            \"GENERATE_SERIES(a, b, '  2   days  ')\",\n            write={\n                \"postgres\": \"GENERATE_SERIES(a, b, INTERVAL '2 DAYS')\",\n                \"presto\": \"UNNEST(SEQUENCE(a, b, INTERVAL '2' DAY))\",\n                \"trino\": \"UNNEST(SEQUENCE(a, b, INTERVAL '2' DAY))\",\n            },\n        )\n        self.validate_all(\n            \"GENERATE_SERIES('2019-01-01'::TIMESTAMP, NOW(), '1day')\",\n            write={\n                \"databricks\": \"EXPLODE(SEQUENCE(CAST('2019-01-01' AS TIMESTAMP), CAST(CURRENT_TIMESTAMP() AS TIMESTAMP), INTERVAL '1' DAY))\",\n                \"hive\": \"EXPLODE(SEQUENCE(CAST('2019-01-01' AS TIMESTAMP), CAST(CURRENT_TIMESTAMP() AS TIMESTAMP), INTERVAL '1' DAY))\",\n                \"postgres\": \"GENERATE_SERIES(CAST('2019-01-01' AS TIMESTAMP), CURRENT_TIMESTAMP, INTERVAL '1 DAY')\",\n                \"presto\": \"UNNEST(SEQUENCE(CAST('2019-01-01' AS TIMESTAMP), CAST(CURRENT_TIMESTAMP AS TIMESTAMP), INTERVAL '1' DAY))\",\n                \"spark\": \"EXPLODE(SEQUENCE(CAST('2019-01-01' AS TIMESTAMP), CAST(CURRENT_TIMESTAMP() AS TIMESTAMP), INTERVAL '1' DAY))\",\n                \"spark2\": \"EXPLODE(SEQUENCE(CAST('2019-01-01' AS TIMESTAMP), CAST(CURRENT_TIMESTAMP() AS TIMESTAMP), INTERVAL '1' DAY))\",\n                \"trino\": \"UNNEST(SEQUENCE(CAST('2019-01-01' AS TIMESTAMP), CAST(CURRENT_TIMESTAMP AS TIMESTAMP), INTERVAL '1' DAY))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM GENERATE_SERIES(a, b)\",\n            read={\n                \"tsql\": \"SELECT * FROM GENERATE_SERIES(a, b)\",\n            },\n            write={\n                \"databricks\": \"SELECT * FROM EXPLODE(SEQUENCE(a, b))\",\n                \"hive\": \"SELECT * FROM EXPLODE(SEQUENCE(a, b))\",\n                \"postgres\": \"SELECT * FROM GENERATE_SERIES(a, b)\",\n                \"presto\": \"SELECT * FROM UNNEST(SEQUENCE(a, b))\",\n                \"spark\": \"SELECT * FROM EXPLODE(SEQUENCE(a, b))\",\n                \"spark2\": \"SELECT * FROM EXPLODE(SEQUENCE(a, b))\",\n                \"trino\": \"SELECT * FROM UNNEST(SEQUENCE(a, b))\",\n                \"tsql\": \"SELECT * FROM GENERATE_SERIES(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t CROSS JOIN GENERATE_SERIES(2, 4)\",\n            write={\n                \"postgres\": \"SELECT * FROM t CROSS JOIN GENERATE_SERIES(2, 4)\",\n                \"presto\": \"SELECT * FROM t CROSS JOIN UNNEST(SEQUENCE(2, 4))\",\n                \"trino\": \"SELECT * FROM t CROSS JOIN UNNEST(SEQUENCE(2, 4))\",\n                \"tsql\": \"SELECT * FROM t CROSS JOIN GENERATE_SERIES(2, 4)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t CROSS JOIN GENERATE_SERIES(2, 4) AS s\",\n            write={\n                \"postgres\": \"SELECT * FROM t CROSS JOIN GENERATE_SERIES(2, 4) AS s\",\n                \"presto\": \"SELECT * FROM t CROSS JOIN UNNEST(SEQUENCE(2, 4)) AS _u(s)\",\n                \"trino\": \"SELECT * FROM t CROSS JOIN UNNEST(SEQUENCE(2, 4)) AS _u(s)\",\n                \"tsql\": \"SELECT * FROM t CROSS JOIN GENERATE_SERIES(2, 4) AS s\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x FETCH 1 ROW\",\n            write={\n                \"postgres\": \"SELECT * FROM x FETCH FIRST 1 ROWS ONLY\",\n                \"presto\": \"SELECT * FROM x FETCH FIRST 1 ROWS ONLY\",\n                \"hive\": \"SELECT * FROM x LIMIT 1\",\n                \"spark\": \"SELECT * FROM x LIMIT 1\",\n                \"sqlite\": \"SELECT * FROM x LIMIT 1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"postgres\": \"SELECT fname, lname, age FROM person ORDER BY age DESC, fname ASC, lname\",\n                \"presto\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname\",\n                \"hive\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname NULLS LAST\",\n                \"spark\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname NULLS LAST\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CASE WHEN SUBSTRING('abcdefg' FROM 1 FOR 2) IN ('ab') THEN 1 ELSE 0 END\",\n            write={\n                \"hive\": \"SELECT CASE WHEN SUBSTRING('abcdefg', 1, 2) IN ('ab') THEN 1 ELSE 0 END\",\n                \"spark\": \"SELECT CASE WHEN SUBSTRING('abcdefg', 1, 2) IN ('ab') THEN 1 ELSE 0 END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x WHERE SUBSTRING(col1 FROM 3 + LENGTH(col1) - 10 FOR 10) IN (col2)\",\n            write={\n                \"hive\": \"SELECT * FROM x WHERE SUBSTRING(col1, 3 + LENGTH(col1) - 10, 10) IN (col2)\",\n                \"spark\": \"SELECT * FROM x WHERE SUBSTRING(col1, 3 + LENGTH(col1) - 10, 10) IN (col2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRIM(BOTH ' XXX ')\",\n            write={\n                \"mysql\": \"SELECT TRIM(' XXX ')\",\n                \"postgres\": \"SELECT TRIM(' XXX ')\",\n                \"hive\": \"SELECT TRIM(' XXX ')\",\n            },\n        )\n        self.validate_all(\n            \"TRIM(LEADING FROM ' XXX ')\",\n            write={\n                \"mysql\": \"LTRIM(' XXX ')\",\n                \"postgres\": \"LTRIM(' XXX ')\",\n                \"hive\": \"LTRIM(' XXX ')\",\n                \"presto\": \"LTRIM(' XXX ')\",\n            },\n        )\n        self.validate_all(\n            \"TRIM(TRAILING FROM ' XXX ')\",\n            write={\n                \"mysql\": \"RTRIM(' XXX ')\",\n                \"postgres\": \"RTRIM(' XXX ')\",\n                \"hive\": \"RTRIM(' XXX ')\",\n                \"presto\": \"RTRIM(' XXX ')\",\n            },\n        )\n        self.validate_all(\n            \"TRIM(BOTH 'as' FROM 'as string as')\",\n            write={\n                \"postgres\": \"TRIM(BOTH 'as' FROM 'as string as')\",\n                \"spark\": \"TRIM(BOTH 'as' FROM 'as string as')\",\n            },\n        )\n        self.validate_identity(\n            \"\"\"SELECT TRIM(LEADING ' XXX ' COLLATE \"de_DE\")\"\"\",\n            \"\"\"SELECT LTRIM(' XXX ' COLLATE \"de_DE\")\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT TRIM(TRAILING ' XXX ' COLLATE \"de_DE\")\"\"\",\n            \"\"\"SELECT RTRIM(' XXX ' COLLATE \"de_DE\")\"\"\",\n        )\n        self.validate_identity(\"LEVENSHTEIN(col1, col2)\")\n        self.validate_identity(\"LEVENSHTEIN_LESS_EQUAL(col1, col2, 1)\")\n        self.validate_identity(\"LEVENSHTEIN(col1, col2, 1, 2, 3)\")\n        self.validate_identity(\"LEVENSHTEIN_LESS_EQUAL(col1, col2, 1, 2, 3, 4)\")\n\n        self.validate_all(\n            \"\"\"'{\"a\":1,\"b\":2}'::json->'b'\"\"\",\n            write={\n                \"postgres\": \"\"\"CAST('{\"a\":1,\"b\":2}' AS JSON) -> 'b'\"\"\",\n                \"redshift\": \"\"\"JSON_EXTRACT_PATH_TEXT('{\"a\":1,\"b\":2}', 'b')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"merge into x as x using (select id) as y on a = b WHEN matched then update set X.\"A\" = y.b\"\"\",\n            write={\n                \"postgres\": \"\"\"MERGE INTO x AS x USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET \"A\" = y.b\"\"\",\n                \"trino\": \"\"\"MERGE INTO x AS x USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET \"A\" = y.b\"\"\",\n                \"snowflake\": \"\"\"MERGE INTO x AS x USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET X.\"A\" = y.b\"\"\",\n            },\n        )\n        self.validate_all(\n            \"merge into x as z using (select id) as y on a = b WHEN matched then update set X.a = y.b\",\n            write={\n                \"postgres\": \"MERGE INTO x AS z USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET a = y.b\",\n                \"snowflake\": \"MERGE INTO x AS z USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET X.a = y.b\",\n            },\n        )\n        self.validate_all(\n            \"merge into x as z using (select id) as y on a = b WHEN matched then update set Z.a = y.b\",\n            write={\n                \"postgres\": \"MERGE INTO x AS z USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET a = y.b\",\n                \"snowflake\": \"MERGE INTO x AS z USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET Z.a = y.b\",\n            },\n        )\n        self.validate_all(\n            \"merge into x using (select id) as y on a = b WHEN matched then update set x.a = y.b\",\n            write={\n                \"postgres\": \"MERGE INTO x USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET a = y.b\",\n                \"snowflake\": \"MERGE INTO x USING (SELECT id) AS y ON a = b WHEN MATCHED THEN UPDATE SET x.a = y.b\",\n            },\n        )\n        self.validate_all(\n            \"x / y ^ z\",\n            write={\n                \"\": \"x / POWER(y, z)\",\n                \"postgres\": \"x / POWER(y, z)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS NAME)\",\n            read={\n                \"redshift\": \"CAST(x AS NAME)\",\n            },\n            write={\n                \"postgres\": \"CAST(x AS NAME)\",\n                \"redshift\": \"CAST(x AS NAME)\",\n            },\n        )\n        self.assertIsInstance(self.parse_one(\"id::UUID\"), exp.Cast)\n\n        self.validate_identity('1::\"int\"', \"CAST(1 AS INT)\").to.is_type(exp.DataType.Type.INT)\n        self.validate_identity(\n            '1::\"udt\"', 'CAST(1 AS \"udt\")'\n        ).to.this == exp.DataType.Type.USERDEFINED\n        self.validate_identity(\n            \"COPY tbl (col1, col2) FROM 'file' WITH (FORMAT format, HEADER MATCH, FREEZE TRUE)\"\n        )\n        self.validate_identity(\n            \"COPY tbl (col1, col2) TO 'file' WITH (FORMAT format, HEADER MATCH, FREEZE TRUE)\"\n        )\n        self.validate_identity(\n            \"COPY (SELECT * FROM t) TO 'file' WITH (FORMAT format, HEADER MATCH, FREEZE TRUE)\"\n        )\n        self.validate_identity(\"cast(a as FLOAT)\", \"CAST(a AS DOUBLE PRECISION)\")\n        self.validate_identity(\"cast(a as FLOAT8)\", \"CAST(a AS DOUBLE PRECISION)\")\n        self.validate_identity(\"cast(a as FLOAT4)\", \"CAST(a AS REAL)\")\n\n        self.validate_all(\n            \"1 / DIV(4, 2)\",\n            read={\n                \"postgres\": \"1 / DIV(4, 2)\",\n            },\n            write={\n                \"sqlite\": \"1 / CAST(CAST(CAST(4 AS REAL) / 2 AS INTEGER) AS REAL)\",\n                \"duckdb\": \"1 / CAST(4 // 2 AS DECIMAL)\",\n                \"bigquery\": \"1 / CAST(DIV(4, 2) AS NUMERIC)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(DIV(4, 2) AS DECIMAL(5, 3))\",\n            read={\n                \"duckdb\": \"CAST(4 // 2 AS DECIMAL(5, 3))\",\n            },\n            write={\n                \"duckdb\": \"CAST(CAST(4 // 2 AS DECIMAL) AS DECIMAL(5, 3))\",\n                \"postgres\": \"CAST(DIV(4, 2) AS DECIMAL(5, 3))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT TO_DATE('01/01/2000', 'MM/DD/YYYY')\",\n            write={\n                \"duckdb\": \"SELECT CAST(STRPTIME('01/01/2000', '%m/%d/%Y') AS DATE)\",\n                \"postgres\": \"SELECT TO_DATE('01/01/2000', 'MM/DD/YYYY')\",\n            },\n        )\n\n        self.validate_identity(\n            'SELECT js, js IS JSON AS \"json?\", js IS JSON VALUE AS \"scalar?\", js IS JSON SCALAR AS \"scalar?\", js IS JSON OBJECT AS \"object?\", js IS JSON ARRAY AS \"array?\" FROM t'\n        )\n        self.validate_identity(\n            'SELECT js, js IS JSON ARRAY WITH UNIQUE KEYS AS \"array w. UK?\", js IS JSON ARRAY WITHOUT UNIQUE KEYS AS \"array w/o UK?\", js IS JSON ARRAY UNIQUE KEYS AS \"array w UK 2?\" FROM t'\n        )\n        self.validate_identity(\n            \"MERGE INTO target_table USING source_table AS source ON target.id = source.id WHEN MATCHED THEN DO NOTHING WHEN NOT MATCHED THEN DO NOTHING RETURNING MERGE_ACTION(), *\"\n        )\n        self.validate_identity(\n            \"SELECT 1 FROM ((VALUES (1)) AS vals(id) LEFT OUTER JOIN tbl ON vals.id = tbl.id)\"\n        )\n        self.validate_identity(\"SELECT OVERLAY(a PLACING b FROM 1)\")\n        self.validate_identity(\"SELECT OVERLAY(a PLACING b FROM 1 FOR 1)\")\n        self.validate_identity(\"ARRAY[1, 2, 3] && ARRAY[1, 2]\").assert_is(exp.ArrayOverlaps)\n\n        self.validate_all(\n            \"\"\"SELECT JSONB_EXISTS('{\"a\": [1,2,3]}', 'a')\"\"\",\n            write={\n                \"postgres\": \"\"\"SELECT JSONB_EXISTS('{\"a\": [1,2,3]}', 'a')\"\"\",\n                \"duckdb\": \"\"\"SELECT JSON_EXISTS('{\"a\": [1,2,3]}', '$.a')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"WITH t AS (SELECT ARRAY[1, 2, 3] AS col) SELECT * FROM t WHERE 1 <= ANY(col) AND 2 = ANY(col)\",\n            write={\n                \"postgres\": \"WITH t AS (SELECT ARRAY[1, 2, 3] AS col) SELECT * FROM t WHERE 1 <= ANY(col) AND 2 = ANY(col)\",\n                \"hive\": \"WITH t AS (SELECT ARRAY(1, 2, 3) AS col) SELECT * FROM t WHERE EXISTS(col, x -> 1 <= x) AND EXISTS(col, x -> 2 = x)\",\n                \"spark2\": \"WITH t AS (SELECT ARRAY(1, 2, 3) AS col) SELECT * FROM t WHERE EXISTS(col, x -> 1 <= x) AND EXISTS(col, x -> 2 = x)\",\n                \"spark\": \"WITH t AS (SELECT ARRAY(1, 2, 3) AS col) SELECT * FROM t WHERE EXISTS(col, x -> 1 <= x) AND EXISTS(col, x -> 2 = x)\",\n                \"databricks\": \"WITH t AS (SELECT ARRAY(1, 2, 3) AS col) SELECT * FROM t WHERE EXISTS(col, x -> 1 <= x) AND EXISTS(col, x -> 2 = x)\",\n            },\n        )\n\n        self.validate_identity(\n            \"/*+ some comment*/ SELECT b.foo, b.bar FROM baz AS b\",\n            \"/* + some comment */ SELECT b.foo, b.bar FROM baz AS b\",\n        )\n\n        self.validate_identity(\n            \"SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY a) FILTER(WHERE CAST(b AS BOOLEAN)) AS mean_value FROM (VALUES (0, 't')) AS fake_data(a, b)\"\n        )\n\n        self.validate_all(\n            \"SELECT JSON_OBJECT_AGG(k, v) FROM t\",\n            write={\n                \"postgres\": \"SELECT JSON_OBJECT_AGG(k, v) FROM t\",\n                \"duckdb\": \"SELECT JSON_GROUP_OBJECT(k, v) FROM t\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT JSONB_OBJECT_AGG(k, v) FROM t\",\n            write={\n                \"postgres\": \"SELECT JSONB_OBJECT_AGG(k, v) FROM t\",\n                \"duckdb\": \"SELECT JSON_GROUP_OBJECT(k, v) FROM t\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT DATE_BIN('30 days', timestamp_col, (SELECT MIN(TIMESTAMP) from table)) FROM table\",\n            write={\n                \"postgres\": \"SELECT DATE_BIN('30 days', timestamp_col, (SELECT MIN(TIMESTAMP) FROM table)) FROM table\",\n                \"duckdb\": 'SELECT TIME_BUCKET(\\'30 days\\', timestamp_col, (SELECT MIN(TIMESTAMP) FROM \"table\")) FROM \"table\"',\n            },\n        )\n\n        # Postgres introduced ANY_VALUE in version 16\n        self.validate_all(\n            \"SELECT ANY_VALUE(1) AS col\",\n            write={\n                \"postgres\": \"SELECT ANY_VALUE(1) AS col\",\n                \"postgres, version=16\": \"SELECT ANY_VALUE(1) AS col\",\n                \"postgres, version=17.5\": \"SELECT ANY_VALUE(1) AS col\",\n                \"postgres, version=15\": \"SELECT MAX(1) AS col\",\n                \"postgres, version=13.9\": \"SELECT MAX(1) AS col\",\n            },\n        )\n\n        self.validate_identity(\"SELECT * FROM foo WHERE id = %s\")\n        self.validate_identity(\"SELECT * FROM foo WHERE id = %(id_param)s\")\n        self.validate_identity(\"SELECT * FROM foo WHERE id = ?\")\n\n        self.validate_identity(\"a ?| b\").assert_is(exp.JSONBContainsAnyTopKeys)\n        self.validate_identity(\n            \"\"\"SELECT '{\"a\":1, \"b\":2, \"c\":3}'::jsonb ?| array['b', 'c']\"\"\",\n            \"\"\"SELECT CAST('{\"a\":1, \"b\":2, \"c\":3}' AS JSONB) ?| ARRAY['b', 'c']\"\"\",\n        )\n\n        self.validate_identity(\"a ?& b\").assert_is(exp.JSONBContainsAllTopKeys)\n        self.validate_identity(\n            \"\"\"SELECT '[\"a\", \"b\"]'::jsonb ?& array['a', 'b']\"\"\",\n            \"\"\"SELECT CAST('[\"a\", \"b\"]' AS JSONB) ?& ARRAY['a', 'b']\"\"\",\n        )\n\n        self.validate_identity(\"a #- b\").assert_is(exp.JSONBDeleteAtPath)\n        self.validate_identity(\n            \"\"\"SELECT '[\"a\", {\"b\":1}]'::jsonb #- '{1,b}'\"\"\",\n            \"\"\"SELECT CAST('[\"a\", {\"b\":1}]' AS JSONB) #- '{1,b}'\"\"\",\n        )\n\n        self.validate_identity(\"SELECT JSON_AGG(DISTINCT name) FROM users\")\n        self.validate_identity(\n            \"SELECT JSON_AGG(c1 ORDER BY c1) FROM (VALUES ('c'), ('b'), ('a')) AS t(c1)\"\n        )\n        self.validate_identity(\n            \"SELECT JSON_AGG(DISTINCT c1 ORDER BY c1) FROM (VALUES ('c'), ('b'), ('a')) AS t(c1)\"\n        )\n        self.validate_all(\n            \"SELECT REGEXP_REPLACE('aaa', 'a', 'b')\",\n            read={\n                \"postgres\": \"SELECT REGEXP_REPLACE('aaa', 'a', 'b')\",\n                \"duckdb\": \"SELECT REGEXP_REPLACE('aaa', 'a', 'b')\",\n            },\n            write={\n                \"duckdb\": \"SELECT REGEXP_REPLACE('aaa', 'a', 'b')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT TO_CHAR(foo, bar)\",\n            read={\n                \"redshift\": \"SELECT TO_CHAR(foo, bar)\",\n            },\n            write={\n                \"postgres\": \"SELECT TO_CHAR(foo, bar)\",\n                \"redshift\": \"SELECT TO_CHAR(foo, bar)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE table1 (a INT, b INT, PRIMARY KEY (a))\",\n            read={\n                \"sqlite\": \"CREATE TABLE table1 (a INT, b INT, PRIMARY KEY (a))\",\n                \"postgres\": \"CREATE TABLE table1 (a INT, b INT, PRIMARY KEY (a))\",\n            },\n        )\n        self.validate_identity(\"SELECT NUMRANGE(1.1, 2.2) -|- NUMRANGE(2.2, 3.3)\")\n        self.validate_identity(\n            \"SELECT SLOPE(point '(4,4)', point '(0,0)')\",\n            \"SELECT SLOPE(CAST('(4,4)' AS POINT), CAST('(0,0)' AS POINT))\",\n        )\n\n        width_bucket = self.validate_identity(\"WIDTH_BUCKET(10, ARRAY[5, 15])\")\n        self.assertIsNotNone(width_bucket.args.get(\"threshold\"))\n\n        width_bucket = self.validate_identity(\"WIDTH_BUCKET(10, 5, 15, 25)\")\n        self.assertIsNone(width_bucket.args.get(\"threshold\"))\n\n        self.validate_all(\n            \"UPDATE foo SET a = bar.a, b = bar.b FROM bar WHERE foo.id = bar.id\",\n            write={\n                \"postgres\": \"UPDATE foo SET a = bar.a, b = bar.b FROM bar WHERE foo.id = bar.id\",\n                \"doris\": \"UPDATE foo SET a = bar.a, b = bar.b FROM bar WHERE foo.id = bar.id\",\n                \"starrocks\": \"UPDATE foo SET a = bar.a, b = bar.b FROM bar WHERE foo.id = bar.id\",\n                \"mysql\": \"UPDATE foo JOIN bar ON TRUE SET foo.a = bar.a, foo.b = bar.b WHERE foo.id = bar.id\",\n                \"singlestore\": \"UPDATE foo JOIN bar ON TRUE SET foo.a = bar.a, foo.b = bar.b WHERE foo.id = bar.id\",\n            },\n        )\n\n        self.validate_identity(\"SELECT MLEAST(VARIADIC ARRAY[10, -1, 5, 4.4])\")\n        self.validate_identity(\n            \"SELECT MLEAST(VARIADIC ARRAY[]::numeric[])\",\n            \"SELECT MLEAST(VARIADIC CAST(ARRAY[] AS DECIMAL[]))\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM schema_name.table_name st WHERE JSON_EXTRACT_PATH_TEXT((st.data)::json, variadic array['test'::text]) = 'test'::text\",\n            \"SELECT * FROM schema_name.table_name AS st WHERE JSON_EXTRACT_PATH_TEXT(CAST((st.data) AS JSON), VARIADIC ARRAY[CAST('test' AS TEXT)]) = CAST('test' AS TEXT)\",\n        )\n\n    def test_ddl(self):\n        # Checks that user-defined types are parsed into DataType instead of Identifier\n        self.parse_one(\"CREATE TABLE t (a udt)\").this.expressions[0].args[\"kind\"].assert_is(\n            exp.DataType\n        )\n\n        # Checks that OID is parsed into a DataType (ObjectIdentifier)\n        self.assertIsInstance(\n            self.parse_one(\"CREATE TABLE p.t (c oid)\").find(exp.DataType), exp.ObjectIdentifier\n        )\n\n        expr = self.parse_one(\"CREATE TABLE t (x INTERVAL day)\")\n        cdef = expr.find(exp.ColumnDef)\n        cdef.args[\"kind\"].assert_is(exp.DataType)\n        self.assertEqual(expr.sql(dialect=\"postgres\"), \"CREATE TABLE t (x INTERVAL DAY)\")\n\n        self.validate_identity('ALTER INDEX \"IX_Ratings_Column1\" RENAME TO \"IX_Ratings_Column2\"')\n        self.validate_identity('CREATE TABLE x (a TEXT COLLATE \"de_DE\")')\n        self.validate_identity('CREATE TABLE x (a TEXT COLLATE pg_catalog.\"default\")')\n        self.validate_identity(\"CREATE TABLE t (col INT[3][5])\")\n        self.validate_identity(\"CREATE TABLE t (col INT[3])\")\n        self.validate_identity(\"CREATE INDEX IF NOT EXISTS ON t(c)\")\n        self.validate_identity(\"CREATE INDEX et_vid_idx ON et(vid) INCLUDE (fid)\")\n        self.validate_identity(\"CREATE INDEX idx_x ON x USING BTREE(x, y) WHERE (NOT y IS NULL)\")\n        self.validate_identity(\"CREATE TABLE test (elems JSONB[])\")\n        self.validate_identity(\"CREATE TABLE public.y (x TSTZRANGE NOT NULL)\")\n        self.validate_identity(\"CREATE TABLE test (foo HSTORE)\")\n        self.validate_identity(\"CREATE TABLE test (foo JSONB)\")\n        self.validate_identity(\"CREATE TABLE test (foo VARCHAR(64)[])\")\n        self.validate_identity(\"CREATE TABLE test (foo INT) PARTITION BY HASH(foo)\")\n        self.validate_identity(\"INSERT INTO x VALUES (1, 'a', 2.0) RETURNING a\")\n        self.validate_identity(\"INSERT INTO x VALUES (1, 'a', 2.0) RETURNING a, b\")\n        self.validate_identity(\"INSERT INTO x VALUES (1, 'a', 2.0) RETURNING *\")\n        self.validate_identity(\"UPDATE tbl_name SET foo = 123 RETURNING a\")\n        self.validate_identity(\"CREATE TABLE cities_partdef PARTITION OF cities DEFAULT\")\n        self.validate_identity(\"CREATE TABLE t (c CHAR(2) UNIQUE NOT NULL) INHERITS (t1)\")\n        self.validate_identity(\"CREATE TABLE s.t (c CHAR(2) UNIQUE NOT NULL) INHERITS (s.t1, s.t2)\")\n        self.validate_identity(\"CREATE FUNCTION x(INT) RETURNS INT SET search_path = 'public'\")\n        self.validate_identity(\"TRUNCATE TABLE t1 CONTINUE IDENTITY\")\n        self.validate_identity(\"TRUNCATE TABLE t1 RESTART IDENTITY\")\n        self.validate_identity(\"TRUNCATE TABLE t1 CASCADE\")\n        self.validate_identity(\"TRUNCATE TABLE t1 RESTRICT\")\n        self.validate_identity(\"TRUNCATE TABLE t1 CONTINUE IDENTITY CASCADE\")\n        self.validate_identity(\"TRUNCATE TABLE t1 RESTART IDENTITY RESTRICT\")\n        self.validate_identity(\"ALTER TABLE t1 SET LOGGED\")\n        self.validate_identity(\"ALTER TABLE t1 SET UNLOGGED\")\n        self.validate_identity(\"ALTER TABLE t1 SET WITHOUT CLUSTER\")\n        self.validate_identity(\"ALTER TABLE t1 SET WITHOUT OIDS\")\n        self.validate_identity(\"ALTER TABLE t1 SET ACCESS METHOD method\")\n        self.validate_identity(\"ALTER TABLE t1 SET TABLESPACE tablespace\")\n        self.validate_identity(\"ALTER TABLE t1 SET (fillfactor = 5, autovacuum_enabled = TRUE)\")\n        self.validate_identity(\n            \"INSERT INTO book (isbn, title) VALUES ($1, $2) ON CONFLICT(isbn) WHERE deleted_at IS NULL DO UPDATE SET title = EXCLUDED.title RETURNING id, isbn\"\n        )\n        self.validate_identity(\n            \"INSERT INTO newtable AS t(a, b, c) VALUES (1, 2, 3) ON CONFLICT(c) DO UPDATE SET a = t.a + 1 WHERE t.a < 1\"\n        )\n        self.validate_identity(\n            \"ALTER TABLE tested_table ADD CONSTRAINT unique_example UNIQUE (column_name) NOT VALID\"\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION pymax(a INT, b INT) RETURNS INT LANGUAGE plpython3u AS $$\\n  if a > b:\\n    return a\\n  return b\\n$$\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (vid INT NOT NULL, CONSTRAINT ht_vid_nid_fid_idx EXCLUDE (INT4RANGE(vid, nid) WITH &&, INT4RANGE(fid, fid, '[]') WITH &&))\"\n        )\n        self.validate_identity(\"CREATE TABLE t (i INT, a TEXT, PRIMARY KEY (i) INCLUDE (a))\")\n        self.validate_identity(\n            \"CREATE TABLE t (i INT, PRIMARY KEY (i), EXCLUDE USING gist(col varchar_pattern_ops DESC NULLS LAST WITH &&) WITH (sp1=1, sp2=2))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (i INT, EXCLUDE USING btree(INT4RANGE(vid, nid, '[]') ASC NULLS FIRST WITH &&) INCLUDE (col1, col2))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (i INT, EXCLUDE USING gin(col1 WITH &&, col2 WITH ||) USING INDEX TABLESPACE tablespace WHERE (id > 5))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE A (LIKE B INCLUDING CONSTRAINT INCLUDING COMPRESSION EXCLUDING COMMENTS)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE cust_part3 PARTITION OF customers FOR VALUES WITH (MODULUS 3, REMAINDER 2)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE measurement_y2016m07 PARTITION OF measurement (unitsales DEFAULT 0) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01')\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE measurement_ym_older PARTITION OF measurement_year_month FOR VALUES FROM (MINVALUE, MINVALUE) TO (2016, 11)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE measurement_ym_y2016m11 PARTITION OF measurement_year_month FOR VALUES FROM (2016, 11) TO (2016, 12)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE cities_ab PARTITION OF cities (CONSTRAINT city_id_nonzero CHECK (city_id <> 0)) FOR VALUES IN ('a', 'b')\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE cities_ab PARTITION OF cities (CONSTRAINT city_id_nonzero CHECK (city_id <> 0)) FOR VALUES IN ('a', 'b') PARTITION BY RANGE(population)\"\n        )\n        self.validate_identity(\n            \"CREATE INDEX foo ON bar.baz USING btree(col1 varchar_pattern_ops ASC, col2)\"\n        )\n        self.validate_identity(\n            \"CREATE INDEX index_issues_on_title_trigram ON public.issues USING gin(title public.gin_trgm_ops)\"\n        )\n        self.validate_identity(\n            \"INSERT INTO x VALUES (1, 'a', 2.0) ON CONFLICT(id) DO NOTHING RETURNING *\"\n        )\n        self.validate_identity(\n            \"INSERT INTO x VALUES (1, 'a', 2.0) ON CONFLICT(id) DO UPDATE SET x.id = 1 RETURNING *\"\n        )\n        self.validate_identity(\n            \"INSERT INTO x VALUES (1, 'a', 2.0) ON CONFLICT(id) DO UPDATE SET x.id = excluded.id RETURNING *\"\n        )\n        self.validate_identity(\n            \"INSERT INTO x VALUES (1, 'a', 2.0) ON CONFLICT ON CONSTRAINT pkey DO NOTHING RETURNING *\"\n        )\n        self.validate_identity(\n            \"INSERT INTO x VALUES (1, 'a', 2.0) ON CONFLICT ON CONSTRAINT pkey DO UPDATE SET x.id = 1 RETURNING *\"\n        )\n        self.validate_identity(\n            \"DELETE FROM event USING sales AS s WHERE event.eventid = s.eventid RETURNING a\"\n        )\n        self.validate_identity(\n            \"WITH t(c) AS (SELECT 1) SELECT * INTO UNLOGGED foo FROM (SELECT c AS c FROM t) AS temp\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test (x TIMESTAMP WITHOUT TIME ZONE[][])\",\n            \"CREATE TABLE test (x TIMESTAMP[][])\",\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION add(integer, integer) RETURNS INT LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT AS 'select $1 + $2;'\",\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION add(integer, integer) RETURNS INT LANGUAGE SQL IMMUTABLE STRICT AS 'select $1 + $2;'\"\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION add(INT, INT) RETURNS INT SET search_path TO 'public' AS 'select $1 + $2;' LANGUAGE SQL IMMUTABLE\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION x(INT) RETURNS INT SET foo FROM CURRENT\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION add(integer, integer) RETURNS integer AS 'select $1 + $2;' LANGUAGE SQL IMMUTABLE CALLED ON NULL INPUT\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"CREATE CONSTRAINT TRIGGER my_trigger AFTER INSERT OR DELETE OR UPDATE OF col_a, col_b ON public.my_table DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION DO_STH()\"\n        )\n        self.validate_identity(\n            \"CREATE UNLOGGED TABLE foo AS WITH t(c) AS (SELECT 1) SELECT * FROM (SELECT c AS c FROM t) AS temp\"\n        )\n        self.validate_identity(\n            \"ALTER TABLE foo ADD COLUMN id BIGINT NOT NULL PRIMARY KEY DEFAULT 1, ADD CONSTRAINT fk_orders_user FOREIGN KEY (id) REFERENCES foo (id)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (col integer ARRAY[3])\",\n            \"CREATE TABLE t (col INT[3])\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (col integer ARRAY)\",\n            \"CREATE TABLE t (col INT[])\",\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION x(INT) RETURNS INT SET search_path TO 'public'\",\n            \"CREATE FUNCTION x(INT) RETURNS INT SET search_path = 'public'\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE test (x TIMESTAMP WITHOUT TIME ZONE[][])\",\n            \"CREATE TABLE test (x TIMESTAMP[][])\",\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE FUNCTION function_name (input_a character varying DEFAULT NULL::character varying)\",\n            \"CREATE OR REPLACE FUNCTION function_name(input_a VARCHAR DEFAULT CAST(NULL AS VARCHAR))\",\n        )\n\n        # Function parameter modes\n        self.validate_identity(\"CREATE FUNCTION foo(a INT)\")\n        self.validate_identity(\"CREATE FUNCTION foo(IN a INT)\")\n        self.validate_identity(\"CREATE FUNCTION foo(OUT a INT)\")\n        self.validate_identity(\"CREATE FUNCTION foo(INOUT a INT)\")\n        self.validate_identity(\"CREATE FUNCTION foo(VARIADIC a INT[])\")\n        self.validate_identity(\"CREATE FUNCTION foo(out INT)\")  # \"out\" as identifier\n        self.validate_identity(\"CREATE FUNCTION foo(inout VARCHAR)\")  # \"inout\" as identifier\n        self.validate_identity(\"CREATE FUNCTION foo(variadic INT[])\")  # \"variadic\" as identifier\n        self.validate_identity(\n            \"CREATE FUNCTION foo(a INT, OUT b INT, INOUT c VARCHAR, VARIADIC d INT[])\"\n        )\n        self.validate_identity(\"CREATE OR REPLACE FUNCTION foo(INOUT id UUID)\")\n        self.validate_identity(\n            \"CREATE OR REPLACE FUNCTION foo(id UUID, OUT created_at TIMESTAMPTZ)\"\n        )\n        self.validate_identity(\"CREATE FUNCTION foo(OUT x INT DEFAULT 5)\")\n        self.validate_identity(\"CREATE FUNCTION foo(INOUT y VARCHAR DEFAULT 'test')\")\n        self.validate_identity(\"CREATE FUNCTION foo(IN a INT DEFAULT 0, OUT b INT)\")\n        self.validate_all(\n            \"CREATE FUNCTION foo(VARIADIC args INT[] DEFAULT ARRAY[]::INT[])\",\n            write={\n                \"postgres\": \"CREATE FUNCTION foo(VARIADIC args INT[] DEFAULT CAST(ARRAY[] AS INT[]))\",\n            },\n        )\n        self.validate_identity(\"CREATE FUNCTION foo(OUT result INT, IN input INT DEFAULT 10)\")\n\n        self.validate_identity(\n            \"CREATE TABLE products (product_no INT UNIQUE, name TEXT, price DECIMAL)\",\n            \"CREATE TABLE products (product_no INT UNIQUE, name TEXT, price DECIMAL)\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE products (product_no INT CONSTRAINT must_be_different UNIQUE, name TEXT CONSTRAINT present NOT NULL, price DECIMAL)\",\n            \"CREATE TABLE products (product_no INT CONSTRAINT must_be_different UNIQUE, name TEXT CONSTRAINT present NOT NULL, price DECIMAL)\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE products (product_no INT, name TEXT, price DECIMAL, UNIQUE (product_no, name))\",\n            \"CREATE TABLE products (product_no INT, name TEXT, price DECIMAL, UNIQUE (product_no, name))\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE products (\"\n            \"product_no INT UNIQUE,\"\n            \" name TEXT,\"\n            \" price DECIMAL CHECK (price > 0),\"\n            \" discounted_price DECIMAL CONSTRAINT positive_discount CHECK (discounted_price > 0),\"\n            \" CHECK (product_no > 1),\"\n            \" CONSTRAINT valid_discount CHECK (price > discounted_price))\"\n        )\n        self.validate_identity(\n            \"\"\"\n            CREATE INDEX index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial\n            ON public.ci_builds\n            USING btree (commit_id, artifacts_expire_at, id)\n            WHERE (\n                ((type)::text = 'Ci::Build'::text)\n                AND ((retried = false) OR (retried IS NULL))\n                AND ((name)::text = ANY (ARRAY[\n                    ('sast'::character varying)::text,\n                    ('dependency_scanning'::character varying)::text,\n                    ('sast:container'::character varying)::text,\n                    ('container_scanning'::character varying)::text,\n                    ('dast'::character varying)::text\n                ]))\n            )\n            \"\"\",\n            \"CREATE INDEX index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial ON public.ci_builds USING btree(commit_id, artifacts_expire_at, id) WHERE ((CAST((type) AS TEXT) = CAST('Ci::Build' AS TEXT)) AND ((retried = FALSE) OR (retried IS NULL)) AND (CAST((name) AS TEXT) = ANY(ARRAY[CAST((CAST('sast' AS VARCHAR)) AS TEXT), CAST((CAST('dependency_scanning' AS VARCHAR)) AS TEXT), CAST((CAST('sast:container' AS VARCHAR)) AS TEXT), CAST((CAST('container_scanning' AS VARCHAR)) AS TEXT), CAST((CAST('dast' AS VARCHAR)) AS TEXT)])))\",\n        )\n        self.validate_identity(\n            \"CREATE INDEX index_ci_pipelines_on_project_idandrefandiddesc ON public.ci_pipelines USING btree(project_id, ref, id DESC)\"\n        )\n        self.validate_identity(\n            \"TRUNCATE TABLE ONLY t1, t2*, ONLY t3, t4, t5* RESTART IDENTITY CASCADE\",\n            \"TRUNCATE TABLE ONLY t1, t2, ONLY t3, t4, t5 RESTART IDENTITY CASCADE\",\n        )\n\n        self.validate_all(\n            \"CREATE TABLE x (a UUID, b BYTEA)\",\n            write={\n                \"duckdb\": \"CREATE TABLE x (a UUID, b BLOB)\",\n                \"presto\": \"CREATE TABLE x (a UUID, b VARBINARY)\",\n                \"hive\": \"CREATE TABLE x (a UUID, b BINARY)\",\n                \"spark\": \"CREATE TABLE x (a STRING, b BINARY)\",\n                \"tsql\": \"CREATE TABLE x (a UNIQUEIDENTIFIER, b VARBINARY)\",\n            },\n        )\n\n        self.validate_identity(\"CREATE TABLE tbl (col INT UNIQUE NULLS NOT DISTINCT DEFAULT 9.99)\")\n        self.validate_identity(\"CREATE TABLE tbl (col UUID UNIQUE DEFAULT GEN_RANDOM_UUID())\")\n        self.validate_identity(\"CREATE TABLE tbl (col UUID, UNIQUE NULLS NOT DISTINCT (col))\")\n        self.validate_identity(\"CREATE TABLE tbl (col_a INT GENERATED ALWAYS AS (1 + 2) STORED)\")\n        self.validate_identity(\n            \"CREATE TABLE tbl (col_a INTERVAL GENERATED ALWAYS AS (a - b) STORED)\"\n        )\n\n        self.validate_identity(\"CREATE INDEX CONCURRENTLY ix_table_id ON tbl USING btree(id)\")\n        self.validate_identity(\n            \"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_table_id ON tbl USING btree(id)\"\n        )\n        self.validate_identity(\"DROP INDEX ix_table_id\")\n        self.validate_identity(\"DROP INDEX IF EXISTS ix_table_id\")\n        self.validate_identity(\"DROP INDEX CONCURRENTLY ix_table_id\")\n        self.validate_identity(\"DROP INDEX CONCURRENTLY IF EXISTS ix_table_id\")\n\n        self.validate_identity(\n            \"\"\"\n        CREATE TABLE IF NOT EXISTS public.rental\n        (\n            inventory_id INT NOT NULL,\n            CONSTRAINT rental_customer_id_fkey FOREIGN KEY (customer_id)\n                REFERENCES public.customer (customer_id) MATCH FULL\n                ON UPDATE CASCADE\n                ON DELETE RESTRICT,\n            CONSTRAINT rental_inventory_id_fkey FOREIGN KEY (inventory_id)\n                REFERENCES public.inventory (inventory_id) MATCH PARTIAL\n                ON UPDATE CASCADE\n                ON DELETE RESTRICT,\n            CONSTRAINT rental_staff_id_fkey FOREIGN KEY (staff_id)\n                REFERENCES public.staff (staff_id) MATCH SIMPLE\n                ON UPDATE CASCADE\n                ON DELETE RESTRICT,\n            INITIALLY IMMEDIATE\n        )\n        \"\"\",\n            \"CREATE TABLE IF NOT EXISTS public.rental (inventory_id INT NOT NULL, CONSTRAINT rental_customer_id_fkey FOREIGN KEY (customer_id) REFERENCES public.customer (customer_id) MATCH FULL ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT rental_inventory_id_fkey FOREIGN KEY (inventory_id) REFERENCES public.inventory (inventory_id) MATCH PARTIAL ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT rental_staff_id_fkey FOREIGN KEY (staff_id) REFERENCES public.staff (staff_id) MATCH SIMPLE ON UPDATE CASCADE ON DELETE RESTRICT, INITIALLY IMMEDIATE)\",\n        )\n\n        for op in (\"=\", \">=\", \"<=\", \"<\", \">\", \"&&\", \"||\", \"@>\", \"<@\"):\n            with self.subTest(f\"Testing EXCLUDE with operator {op}\"):\n                self.validate_identity(\n                    f\"CREATE TABLE circles (c circle, EXCLUDE USING gist(c WITH {op}))\"\n                )\n\n        with self.assertRaises(ParseError):\n            transpile(\"CREATE TABLE products (price DECIMAL CHECK price > 0)\", read=\"postgres\")\n        with self.assertRaises(ParseError):\n            transpile(\n                \"CREATE TABLE products (price DECIMAL, CHECK price > 1)\",\n                read=\"postgres\",\n            )\n\n    def test_unnest(self):\n        self.validate_identity(\n            \"SELECT * FROM UNNEST(ARRAY[1, 2], ARRAY['foo', 'bar', 'baz']) AS x(a, b)\"\n        )\n\n        self.validate_all(\n            \"SELECT UNNEST(c) FROM t\",\n            write={\n                \"hive\": \"SELECT EXPLODE(c) FROM t\",\n                \"postgres\": \"SELECT UNNEST(c) FROM t\",\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col FROM t CROSS JOIN UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(c)))) AS _u(pos) CROSS JOIN UNNEST(c) WITH ORDINALITY AS _u_2(col, pos_2) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(c) AND _u_2.pos_2 = CARDINALITY(c))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UNNEST(ARRAY[1])\",\n            write={\n                \"hive\": \"SELECT EXPLODE(ARRAY(1))\",\n                \"postgres\": \"SELECT UNNEST(ARRAY[1])\",\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col FROM UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(ARRAY[1])))) AS _u(pos) CROSS JOIN UNNEST(ARRAY[1]) WITH ORDINALITY AS _u_2(col, pos_2) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(ARRAY[1]) AND _u_2.pos_2 = CARDINALITY(ARRAY[1]))\",\n            },\n        )\n\n    def test_array_offset(self):\n        with self.assertLogs(helper_logger) as cm:\n            self.validate_all(\n                \"SELECT col[1]\",\n                write={\n                    \"bigquery\": \"SELECT col[0]\",\n                    \"duckdb\": \"SELECT col[1]\",\n                    \"hive\": \"SELECT col[0]\",\n                    \"postgres\": \"SELECT col[1]\",\n                    \"presto\": \"SELECT col[1]\",\n                },\n            )\n\n            self.assertEqual(\n                cm.output,\n                [\n                    \"INFO:sqlglot:Applying array index offset (-1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                ],\n            )\n\n    def test_bool_or(self):\n        self.validate_identity(\n            \"SELECT a, LOGICAL_OR(b) FROM table GROUP BY a\",\n            \"SELECT a, BOOL_OR(b) FROM table GROUP BY a\",\n        )\n\n    def test_string_concat(self):\n        self.validate_identity(\"SELECT CONCAT('abcde', 2, NULL, 22)\")\n\n        self.validate_all(\n            \"CONCAT(a, b)\",\n            write={\n                \"\": \"CONCAT(COALESCE(a, ''), COALESCE(b, ''))\",\n                \"clickhouse\": \"CONCAT(COALESCE(a, ''), COALESCE(b, ''))\",\n                \"duckdb\": \"CONCAT(a, b)\",\n                \"postgres\": \"CONCAT(a, b)\",\n                \"presto\": \"CONCAT(COALESCE(CAST(a AS VARCHAR), ''), COALESCE(CAST(b AS VARCHAR), ''))\",\n            },\n        )\n        self.validate_all(\n            \"a || b\",\n            write={\n                \"\": \"a || b\",\n                \"clickhouse\": \"a || b\",\n                \"duckdb\": \"a || b\",\n                \"postgres\": \"a || b\",\n                \"presto\": \"CONCAT(CAST(a AS VARCHAR), CAST(b AS VARCHAR))\",\n            },\n        )\n\n    def test_variance(self):\n        self.validate_identity(\n            \"VAR_SAMP(x)\",\n            \"VAR_SAMP(x)\",\n        )\n        self.validate_identity(\n            \"VAR_POP(x)\",\n            \"VAR_POP(x)\",\n        )\n        self.validate_identity(\n            \"VARIANCE(x)\",\n            \"VAR_SAMP(x)\",\n        )\n\n        self.validate_all(\n            \"VAR_POP(x)\",\n            read={\n                \"\": \"VARIANCE_POP(x)\",\n            },\n            write={\n                \"postgres\": \"VAR_POP(x)\",\n            },\n        )\n\n    def test_corr(self):\n        self.validate_all(\n            \"SELECT CORR(a, b)\",\n            write={\n                \"duckdb\": \"SELECT CORR(a, b)\",\n                \"postgres\": \"SELECT CORR(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CORR(a, b) OVER (PARTITION BY c)\",\n            write={\n                \"duckdb\": \"SELECT CORR(a, b) OVER (PARTITION BY c)\",\n                \"postgres\": \"SELECT CORR(a, b) OVER (PARTITION BY c)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CORR(a, b) FILTER(WHERE c > 0)\",\n            write={\n                \"duckdb\": \"SELECT CORR(a, b) FILTER(WHERE c > 0)\",\n                \"postgres\": \"SELECT CORR(a, b) FILTER(WHERE c > 0)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CORR(a, b) FILTER(WHERE c > 0) OVER (PARTITION BY d)\",\n            write={\n                \"duckdb\": \"SELECT CORR(a, b) FILTER(WHERE c > 0) OVER (PARTITION BY d)\",\n                \"postgres\": \"SELECT CORR(a, b) FILTER(WHERE c > 0) OVER (PARTITION BY d)\",\n            },\n        )\n\n    def test_regexp_binary(self):\n        \"\"\"See https://github.com/tobymao/sqlglot/pull/2404 for details.\"\"\"\n        self.assertIsInstance(self.parse_one(\"'thomas' ~ '.*thomas.*'\"), exp.Binary)\n        self.assertIsInstance(self.parse_one(\"'thomas' ~* '.*thomas.*'\"), exp.Binary)\n\n    def test_unnest_json_array(self):\n        trino_input = \"\"\"\n            WITH t(boxcrate) AS (\n              SELECT JSON '[{\"boxes\": [{\"name\": \"f1\", \"type\": \"plant\", \"color\": \"red\"}]}]'\n            )\n            SELECT\n              JSON_EXTRACT_SCALAR(boxes,'$.name')  AS name,\n              JSON_EXTRACT_SCALAR(boxes,'$.type')  AS type,\n              JSON_EXTRACT_SCALAR(boxes,'$.color') AS color\n            FROM t\n            CROSS JOIN UNNEST(CAST(boxcrate AS array(json))) AS x(tbox)\n            CROSS JOIN UNNEST(CAST(json_extract(tbox, '$.boxes') AS array(json))) AS y(boxes)\n        \"\"\"\n\n        expected_postgres = \"\"\"WITH t(boxcrate) AS (\n  SELECT\n    CAST('[{\"boxes\": [{\"name\": \"f1\", \"type\": \"plant\", \"color\": \"red\"}]}]' AS JSON)\n)\nSELECT\n  JSON_EXTRACT_PATH_TEXT(boxes, 'name') AS name,\n  JSON_EXTRACT_PATH_TEXT(boxes, 'type') AS type,\n  JSON_EXTRACT_PATH_TEXT(boxes, 'color') AS color\nFROM t\nCROSS JOIN JSON_ARRAY_ELEMENTS(CAST(boxcrate AS JSON)) AS x(tbox)\nCROSS JOIN JSON_ARRAY_ELEMENTS(CAST(JSON_EXTRACT_PATH(tbox, 'boxes') AS JSON)) AS y(boxes)\"\"\"\n\n        self.validate_all(expected_postgres, read={\"trino\": trino_input}, pretty=True)\n\n    def test_rows_from(self):\n        self.validate_identity(\"\"\"SELECT * FROM ROWS FROM (FUNC1(col1, col2))\"\"\")\n        self.validate_identity(\n            \"\"\"SELECT * FROM ROWS FROM (FUNC1(col1) AS alias1(\"col1\" TEXT), FUNC2(col2) AS alias2(\"col2\" INT)) WITH ORDINALITY\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT * FROM table1, ROWS FROM (FUNC1(col1) AS alias1(\"col1\" TEXT)) WITH ORDINALITY AS alias3(\"col3\" INT, \"col4\" TEXT)\"\"\"\n        )\n\n    def test_array_length(self):\n        self.validate_identity(\"SELECT ARRAY_LENGTH(ARRAY[1, 2, 3], 1)\")\n\n        self.validate_all(\n            \"ARRAY_LENGTH(arr, 1)\",\n            read={\n                \"bigquery\": \"ARRAY_LENGTH(arr)\",\n                \"duckdb\": \"ARRAY_LENGTH(arr)\",\n                \"presto\": \"CARDINALITY(arr)\",\n                \"drill\": \"REPEATED_COUNT(arr)\",\n                \"teradata\": \"CARDINALITY(arr)\",\n                \"hive\": \"SIZE(arr)\",\n                \"spark2\": \"SIZE(arr)\",\n                \"spark\": \"SIZE(arr)\",\n                \"databricks\": \"SIZE(arr)\",\n            },\n            write={\n                \"duckdb\": \"ARRAY_LENGTH(arr, 1)\",\n                \"presto\": \"CARDINALITY(arr)\",\n                \"teradata\": \"CARDINALITY(arr)\",\n                \"bigquery\": \"ARRAY_LENGTH(arr)\",\n                \"drill\": \"REPEATED_COUNT(arr)\",\n                \"clickhouse\": \"LENGTH(arr)\",\n                \"hive\": \"SIZE(arr)\",\n                \"spark2\": \"SIZE(arr)\",\n                \"spark\": \"SIZE(arr)\",\n                \"databricks\": \"SIZE(arr)\",\n            },\n        )\n\n        self.validate_all(\n            \"ARRAY_LENGTH(arr, foo)\",\n            write={\n                \"duckdb\": \"ARRAY_LENGTH(arr, foo)\",\n                \"hive\": UnsupportedError,\n                \"spark2\": UnsupportedError,\n                \"spark\": UnsupportedError,\n                \"databricks\": UnsupportedError,\n                \"presto\": UnsupportedError,\n                \"teradata\": UnsupportedError,\n                \"bigquery\": UnsupportedError,\n                \"drill\": UnsupportedError,\n                \"clickhouse\": UnsupportedError,\n            },\n        )\n\n    def test_xmlelement(self):\n        self.validate_identity(\"SELECT XMLELEMENT(NAME foo)\")\n        self.validate_identity(\"SELECT XMLELEMENT(NAME foo, XMLATTRIBUTES('xyz' AS bar))\")\n        self.validate_identity(\"SELECT XMLELEMENT(NAME test, XMLATTRIBUTES(a, b)) FROM test\")\n        self.validate_identity(\n            \"SELECT XMLELEMENT(NAME foo, XMLATTRIBUTES(CURRENT_DATE AS bar), 'cont', 'ent')\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT XMLELEMENT(NAME \"foo$bar\", XMLATTRIBUTES('xyz' AS \"a&b\"))\"\"\"\n        )\n        self.validate_identity(\n            \"SELECT XMLELEMENT(NAME foo, XMLATTRIBUTES('xyz' AS bar), XMLELEMENT(NAME abc), XMLCOMMENT('test'), XMLELEMENT(NAME xyz))\"\n        )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE TBL\")\n        self.validate_identity(\"ANALYZE TBL(col1, col2)\")\n        self.validate_identity(\"ANALYZE VERBOSE SKIP_LOCKED TBL(col1, col2)\")\n        self.validate_identity(\"ANALYZE BUFFER_USAGE_LIMIT 1337 TBL\")\n\n    def test_recursive_cte(self):\n        for kind in (\"BREADTH\", \"DEPTH\"):\n            self.validate_identity(\n                f\"WITH RECURSIVE search_tree(id, link, data) AS (SELECT t.id, t.link, t.data FROM tree AS t UNION ALL SELECT t.id, t.link, t.data FROM tree AS t, search_tree AS st WHERE t.id = st.link) SEARCH {kind} FIRST BY id SET ordercol SELECT * FROM search_tree ORDER BY ordercol\"\n            )\n\n        self.validate_identity(\n            \"WITH RECURSIVE search_graph(id, link, data, depth) AS (SELECT g.id, g.link, g.data, 1 FROM graph AS g UNION ALL SELECT g.id, g.link, g.data, sg.depth + 1 FROM graph AS g, search_graph AS sg WHERE g.id = sg.link) CYCLE id SET is_cycle USING path SELECT * FROM search_graph\"\n        )\n\n    def test_json_extract(self):\n        for arrow_op in (\"->\", \"->>\"):\n            with self.subTest(f\"Ensure {arrow_op} operator roundtrips int values as subscripts\"):\n                self.validate_all(\n                    f\"SELECT foo {arrow_op} 1\",\n                    write={\n                        \"postgres\": f\"SELECT foo {arrow_op} 1\",\n                        \"duckdb\": f\"SELECT foo {arrow_op} '$[1]'\",\n                    },\n                )\n\n            with self.subTest(\n                f\"Ensure {arrow_op} operator roundtrips string values that represent integers as keys\"\n            ):\n                self.validate_all(\n                    f\"SELECT foo {arrow_op} '12'\",\n                    write={\n                        \"postgres\": f\"SELECT foo {arrow_op} '12'\",\n                        \"clickhouse\": \"SELECT JSONExtractString(foo, '12')\",\n                    },\n                )\n\n    def test_udt(self):\n        def _validate_udt(sql: str):\n            self.validate_identity(sql).to.assert_is(exp.DataType)\n\n        _validate_udt(\"CAST(5 AS MyType)\")\n        _validate_udt('CAST(5 AS \"MyType\")')\n        _validate_udt(\"CAST(5 AS MySchema.MyType)\")\n        _validate_udt('CAST(5 AS \"MySchema\".\"MyType\")')\n        _validate_udt('CAST(5 AS MySchema.\"MyType\")')\n        _validate_udt('CAST(5 AS \"MyCatalog\".\"MySchema\".\"MyType\")')\n\n    def test_round(self):\n        self.validate_identity(\"ROUND(x)\")\n        self.validate_identity(\"ROUND(x, y)\")\n        self.validate_identity(\"ROUND(CAST(x AS DOUBLE PRECISION))\")\n        self.validate_identity(\"ROUND(CAST(x AS DECIMAL), 4)\")\n        self.validate_identity(\"ROUND(CAST(x AS INT), 4)\")\n        self.validate_all(\n            \"ROUND(CAST(CAST(x AS DOUBLE PRECISION) AS DECIMAL), 4)\",\n            read={\n                \"postgres\": \"ROUND(x::DOUBLE, 4)\",\n                \"hive\": \"ROUND(x::DOUBLE, 4)\",\n                \"bigquery\": \"ROUND(x::DOUBLE, 4)\",\n            },\n        )\n        self.validate_all(\n            \"ROUND(CAST(x AS DECIMAL(18, 3)), 4)\", read={\"duckdb\": \"ROUND(x::DECIMAL, 4)\"}\n        )\n\n    def test_datatype(self):\n        self.assertEqual(exp.DataType.build(\"XML\", dialect=\"postgres\").sql(\"postgres\"), \"XML\")\n        self.validate_identity(\"CREATE TABLE foo (data XML)\")\n\n    def test_locks(self):\n        for key_type in (\"FOR SHARE\", \"FOR UPDATE\", \"FOR NO KEY UPDATE\", \"FOR KEY SHARE\"):\n            with self.subTest(f\"Test lock type {key_type}\"):\n                self.validate_identity(f\"SELECT 1 FROM foo AS x {key_type} OF x\")\n\n    def test_grant(self):\n        grant_cmds = [\n            \"GRANT SELECT ON TABLE users TO role1\",\n            \"GRANT INSERT, DELETE ON TABLE orders TO user1\",\n            \"GRANT SELECT ON employees TO manager WITH GRANT OPTION\",\n            \"GRANT USAGE ON SCHEMA finance TO user2\",\n            \"GRANT ALL PRIVILEGES ON DATABASE mydb TO PUBLIC\",\n            \"GRANT CREATE ON SCHEMA public TO developer\",\n            \"GRANT CONNECT ON DATABASE testdb TO readonly_user\",\n            \"GRANT TEMPORARY ON DATABASE testdb TO temp_user\",\n            \"GRANT TRIGGER ON orders TO audit_role\",\n            \"GRANT REFERENCES ON products TO foreign_key_user\",\n            \"GRANT TRUNCATE ON logs TO admin_role\",\n            \"GRANT UPDATE(salary) ON employees TO hr_manager\",\n            \"GRANT SELECT(id, name), UPDATE(email) ON customers TO customer_service\",\n        ]\n\n        for sql in grant_cmds:\n            with self.subTest(f\"Testing PostgreSQL's GRANT statement: {sql}\"):\n                self.validate_identity(sql)\n\n        self.validate_identity(\n            \"GRANT EXECUTE ON FUNCTION calculate_bonus(integer) TO analyst\",\n            \"GRANT EXECUTE ON FUNCTION CALCULATE_BONUS(integer) TO analyst\",\n        )\n\n        advanced_grants = [\n            \"GRANT INSERT, DELETE ON ALL TABLES IN SCHEMA myschema TO user1\",\n            \"GRANT developer_role TO john\",\n            \"GRANT admin_role TO mary WITH ADMIN OPTION\",\n        ]\n\n        for sql in advanced_grants:\n            with self.subTest(f\"Testing PostgreSQL's advanced GRANT statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n    def test_revoke(self):\n        revoke_cmds = [\n            \"REVOKE SELECT ON TABLE users FROM role1\",\n            \"REVOKE INSERT, DELETE ON TABLE orders FROM user1\",\n            \"REVOKE USAGE ON SCHEMA finance FROM user2\",\n            \"REVOKE ALL PRIVILEGES ON DATABASE mydb FROM PUBLIC\",\n            \"REVOKE CREATE ON SCHEMA public FROM developer\",\n            \"REVOKE CONNECT ON DATABASE testdb FROM readonly_user\",\n            \"REVOKE TEMPORARY ON DATABASE testdb FROM temp_user\",\n            \"REVOKE TRIGGER ON orders FROM audit_role\",\n            \"REVOKE REFERENCES ON products FROM foreign_key_user\",\n            \"REVOKE TRUNCATE ON logs FROM admin_role\",\n            \"REVOKE USAGE ON SCHEMA finance FROM user2 CASCADE\",\n            \"REVOKE SELECT ON TABLE orders FROM user1 RESTRICT\",\n            \"REVOKE GRANT OPTION FOR SELECT ON employees FROM manager\",\n            \"REVOKE GRANT OPTION FOR SELECT ON employees FROM manager RESTRICT\",\n            \"REVOKE UPDATE(salary) ON employees FROM hr_manager\",\n            \"REVOKE SELECT(id, name), UPDATE(email) ON customers FROM customer_service\",\n        ]\n\n        for sql in revoke_cmds:\n            with self.subTest(f\"Testing PostgreSQL's REVOKE statement: {sql}\"):\n                self.validate_identity(sql)\n\n        self.validate_identity(\n            \"REVOKE EXECUTE ON FUNCTION calculate_bonus(integer) FROM analyst\",\n            \"REVOKE EXECUTE ON FUNCTION CALCULATE_BONUS(integer) FROM analyst\",\n        )\n\n        advanced_revoke_cmds = [\n            \"REVOKE INSERT, DELETE ON ALL TABLES IN SCHEMA myschema FROM user1\",\n            \"REVOKE developer_role FROM john\",\n            \"REVOKE admin_role FROM mary\",\n        ]\n\n        for sql in advanced_revoke_cmds:\n            with self.subTest(f\"Testing PostgreSQL's advanced REVOKE statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n    def test_begin_transaction(self):\n        self.validate_all(\n            \"BEGIN\",\n            write={\n                \"postgres\": \"BEGIN\",\n                \"presto\": \"START TRANSACTION\",\n                \"trino\": \"START TRANSACTION\",\n            },\n        )\n\n        for keyword in (\"TRANSACTION\", \"WORK\"):\n            for level in (\n                \"ISOLATION LEVEL SERIALIZABLE\",\n                \"ISOLATION LEVEL READ COMMITTED\",\n                \"NOT DEFFERABLE\",\n                \"READ WRITE\",\n                \"DEFERRABLE\",\n            ):\n                with self.subTest(f\"Testing Postgres's BEGIN {keyword} {level}\"):\n                    self.validate_identity(\n                        f\"BEGIN {keyword} {level}, {level}\", f\"BEGIN {level}, {level}\"\n                    ).assert_is(exp.Transaction)\n\n    def test_interval_span(self):\n        for time_str in [\"1 01:\", \"1 01:00\", \"1.5 01:\", \"-0.25 01:\"]:\n            with self.subTest(f\"Postgres INTERVAL span, omitted DAY TO MINUTE unit: {time_str}\"):\n                self.validate_identity(f\"INTERVAL '{time_str}'\")\n\n        for time_str in [\n            \"1 01:01:\",\n            \"1 01:01:\",\n            \"1 01:01:01\",\n            \"1 01:01:01.01\",\n            \"1.5 01:01:\",\n            \"-0.25 01:01:\",\n        ]:\n            with self.subTest(f\"Postgres INTERVAL span, omitted DAY TO SECOND unit: {time_str}\"):\n                self.validate_identity(f\"INTERVAL '{time_str}'\")\n\n        # Ensure AND is not consumed as a unit following an omitted-span interval\n        with self.subTest(\"Postgres INTERVAL span, omitted unit with following AND\"):\n            day_time_str = \"a > INTERVAL '1 00:00' AND TRUE\"\n            self.validate_identity(day_time_str, \"a > INTERVAL '1 00:00' AND TRUE\")\n            self.assertIsInstance(self.parse_one(day_time_str), exp.And)\n\n    def test_postgres_create_trigger(self):\n        basic_triggers = [\n            \"CREATE TRIGGER check_update BEFORE UPDATE ON accounts FOR EACH ROW EXECUTE FUNCTION CHECK_ACCOUNT_UPDATE()\",\n            \"CREATE TRIGGER log_insert AFTER INSERT ON users FOR EACH ROW EXECUTE FUNCTION LOG_CHANGES()\",\n            \"CREATE TRIGGER audit_changes AFTER INSERT OR UPDATE OR DELETE ON products FOR EACH ROW EXECUTE FUNCTION AUDIT_LOG()\",\n            \"CREATE TRIGGER check_balance BEFORE UPDATE OF balance, status ON accounts FOR EACH ROW EXECUTE FUNCTION VALIDATE_BALANCE()\",\n            \"CREATE TRIGGER conditional_trigger BEFORE UPDATE ON users FOR EACH ROW WHEN (OLD.id <> NEW.id) EXECUTE FUNCTION CHECK_ID_CHANGE()\",\n            \"CREATE TRIGGER statement_trigger AFTER INSERT ON orders FOR EACH STATEMENT EXECUTE FUNCTION UPDATE_SUMMARY()\",\n            \"CREATE TRIGGER instead_trigger INSTEAD OF INSERT ON user_view FOR EACH ROW EXECUTE FUNCTION HANDLE_INSERT()\",\n            \"CREATE OR REPLACE TRIGGER replace_trigger BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION LOG_INSERT()\",\n            \"CREATE TRIGGER param_trigger BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION LOG_WITH_PARAMS('insert', 'users')\",\n            \"CREATE TRIGGER my_trigger BEFORE INSERT ON myschema.users FOR EACH ROW EXECUTE FUNCTION LOG_CHANGES()\",\n            \"CREATE TRIGGER truncate_trigger BEFORE TRUNCATE ON users FOR EACH STATEMENT EXECUTE FUNCTION LOG_TRUNCATE()\",\n            \"CREATE TRIGGER complex_when BEFORE UPDATE ON accounts FOR EACH ROW WHEN (OLD.balance IS DISTINCT FROM NEW.balance AND NEW.balance > 0) EXECUTE FUNCTION CHECK_BALANCE()\",\n            \"CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp FOR EACH ROW EXECUTE FUNCTION EMP_STAMP()\",\n            \"CREATE TRIGGER view_insert INSTEAD OF INSERT ON my_view FOR EACH ROW EXECUTE FUNCTION VIEW_INSERT_ROW()\",\n            \"CREATE TRIGGER check_update BEFORE UPDATE OF balance ON accounts FOR EACH ROW EXECUTE FUNCTION CHECK_ACCOUNT_UPDATE()\",\n            \"CREATE TRIGGER restock AFTER UPDATE ON products FOR EACH ROW WHEN (OLD.count <> NEW.count) EXECUTE FUNCTION RESTOCK_ITEM()\",\n            \"CREATE TRIGGER multi_col_update BEFORE UPDATE OF col1, col2, col3, col4 ON accounts FOR EACH ROW EXECUTE FUNCTION CHECK_COLUMNS()\",\n            \"CREATE TRIGGER all_events AFTER INSERT OR UPDATE OR DELETE OR TRUNCATE ON audit_table FOR EACH STATEMENT EXECUTE FUNCTION LOG_ALL_CHANGES()\",\n        ]\n\n        referencing_triggers = [\n            \"CREATE TRIGGER track_new_rows AFTER INSERT ON users REFERENCING NEW TABLE AS new_data FOR EACH STATEMENT EXECUTE FUNCTION PROCESS_NEW_USERS()\",\n            \"CREATE TRIGGER track_changes AFTER UPDATE ON accounts REFERENCING OLD TABLE AS old_data NEW TABLE AS new_data FOR EACH STATEMENT EXECUTE FUNCTION COMPARE_CHANGES()\",\n            \"CREATE TRIGGER statistics_update AFTER UPDATE ON sales REFERENCING OLD TABLE AS old_sales NEW TABLE AS new_sales FOR EACH STATEMENT EXECUTE FUNCTION UPDATE_STATISTICS()\",\n        ]\n\n        constraint_triggers = [\n            \"CREATE CONSTRAINT TRIGGER check_integrity AFTER INSERT ON users FOR EACH ROW EXECUTE FUNCTION VALIDATE_USER()\",\n            \"CREATE CONSTRAINT TRIGGER deferred_check AFTER INSERT ON orders DEFERRABLE FOR EACH ROW EXECUTE FUNCTION CHECK_ORDER()\",\n            \"CREATE CONSTRAINT TRIGGER deferred_check AFTER INSERT ON orders DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION CHECK_ORDER()\",\n            \"CREATE CONSTRAINT TRIGGER immediate_check AFTER INSERT ON orders NOT DEFERRABLE INITIALLY IMMEDIATE FOR EACH ROW EXECUTE FUNCTION CHECK_ORDER()\",\n            \"CREATE CONSTRAINT TRIGGER fk_check AFTER UPDATE ON orders FROM users FOR EACH ROW EXECUTE FUNCTION CHECK_FOREIGN_KEY()\",\n            \"CREATE CONSTRAINT TRIGGER fk_check AFTER UPDATE ON orders FROM public.users FOR EACH ROW EXECUTE FUNCTION CHECK_FOREIGN_KEY()\",\n            \"CREATE CONSTRAINT TRIGGER if_dist_exists AFTER INSERT OR UPDATE ON films DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION CHECK_FOREIGN_KEY('distributors', 'did')\",\n            \"CREATE CONSTRAINT TRIGGER check_fk AFTER INSERT ON orders FROM customers FOR EACH ROW EXECUTE FUNCTION CHECK_CUSTOMER_EXISTS()\",\n            \"CREATE CONSTRAINT TRIGGER complex_trigger AFTER UPDATE OF col1, col2 ON mytable FROM reftable DEFERRABLE INITIALLY DEFERRED REFERENCING OLD TABLE AS old_data NEW TABLE AS new_data FOR EACH STATEMENT WHEN (OLD.status <> NEW.status) EXECUTE FUNCTION COMPLEX_CHECK('param1', 'param2')\",\n        ]\n\n        for sql in basic_triggers + referencing_triggers + constraint_triggers:\n            with self.subTest(sql):\n                self.validate_identity(sql)\n\n        self.validate_identity(\n            \"CREATE TRIGGER proc_trigger BEFORE INSERT ON users FOR EACH ROW EXECUTE PROCEDURE LOG_CHANGES()\",\n            \"CREATE TRIGGER proc_trigger BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION LOG_CHANGES()\",\n        )\n        self.validate_identity(\n            'CREATE TRIGGER \"MyTrigger\" BEFORE INSERT ON \"MyTable\" FOR EACH ROW EXECUTE FUNCTION MYFUNCTION()'\n        )\n"
  },
  {
    "path": "tests/dialects/test_presto.py",
    "content": "from sqlglot import UnsupportedError, exp, parse_one\nfrom sqlglot.helper import logger as helper_logger\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestPresto(Validator):\n    dialect = \"presto\"\n\n    def test_cast(self):\n        self.validate_identity(\"DEALLOCATE PREPARE my_query\", check_command_warning=True)\n        self.validate_identity(\"DESCRIBE INPUT x\", check_command_warning=True)\n        self.validate_identity(\"DESCRIBE OUTPUT x\", check_command_warning=True)\n        self.validate_identity(\"SELECT * FROM x qualify\", \"SELECT * FROM x AS qualify\")\n        self.validate_identity(\"CAST(x AS IPADDRESS)\")\n        self.validate_identity(\"CAST(x AS IPPREFIX)\")\n        self.validate_identity(\"CAST(TDIGEST_AGG(1) AS TDIGEST)\")\n        self.validate_identity(\"CAST(x AS HYPERLOGLOG)\")\n        self.validate_identity(\n            \"RESET SESSION hive.optimized_reader_enabled\", check_command_warning=True\n        )\n        self.validate_identity(\n            \"TIMESTAMP '2025-06-20 11:22:29 Europe/Prague'\",\n            \"CAST('2025-06-20 11:22:29 Europe/Prague' AS TIMESTAMP WITH TIME ZONE)\",\n        )\n\n        self.validate_all(\n            \"CAST(x AS BOOLEAN)\",\n            read={\n                \"tsql\": \"CAST(x AS BIT)\",\n            },\n            write={\n                \"presto\": \"CAST(x AS BOOLEAN)\",\n                \"tsql\": \"CAST(x AS BIT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FROM_ISO8601_TIMESTAMP('2020-05-11T11:15:05')\",\n            write={\n                \"duckdb\": \"SELECT CAST('2020-05-11T11:15:05' AS TIMESTAMPTZ)\",\n                \"presto\": \"SELECT FROM_ISO8601_TIMESTAMP('2020-05-11T11:15:05')\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS INTERVAL YEAR TO MONTH)\",\n            write={\n                \"oracle\": \"CAST(x AS INTERVAL YEAR TO MONTH)\",\n                \"presto\": \"CAST(x AS INTERVAL YEAR TO MONTH)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS INTERVAL DAY TO SECOND)\",\n            write={\n                \"oracle\": \"CAST(x AS INTERVAL DAY TO SECOND)\",\n                \"presto\": \"CAST(x AS INTERVAL DAY TO SECOND)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('10C' AS INTEGER)\",\n            read={\n                \"postgres\": \"SELECT CAST('10C' AS INTEGER)\",\n                \"presto\": \"SELECT CAST('10C' AS INTEGER)\",\n                \"redshift\": \"SELECT CAST('10C' AS INTEGER)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('1970-01-01 00:00:00' AS TIMESTAMP)\",\n            read={\"postgres\": \"SELECT 'epoch'::TIMESTAMP\"},\n        )\n        self.validate_all(\n            \"FROM_BASE64(x)\",\n            read={\n                \"hive\": \"UNBASE64(x)\",\n            },\n            write={\n                \"hive\": \"UNBASE64(x)\",\n                \"presto\": \"FROM_BASE64(x)\",\n            },\n        )\n        self.validate_all(\n            \"TO_BASE64(x)\",\n            read={\n                \"hive\": \"BASE64(x)\",\n            },\n            write={\n                \"hive\": \"BASE64(x)\",\n                \"presto\": \"TO_BASE64(x)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS ARRAY(INT))\",\n            write={\n                \"bigquery\": \"CAST(a AS ARRAY<INT64>)\",\n                \"duckdb\": \"CAST(a AS INT[])\",\n                \"presto\": \"CAST(a AS ARRAY(INTEGER))\",\n                \"spark\": \"CAST(a AS ARRAY<INT>)\",\n                \"snowflake\": \"CAST(a AS ARRAY(INT))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(a AS VARCHAR)\",\n            write={\n                \"bigquery\": \"CAST(a AS STRING)\",\n                \"duckdb\": \"CAST(a AS TEXT)\",\n                \"presto\": \"CAST(a AS VARCHAR)\",\n                \"spark\": \"CAST(a AS STRING)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(ARRAY[1, 2] AS ARRAY(BIGINT))\",\n            write={\n                \"bigquery\": \"ARRAY<INT64>[1, 2]\",\n                \"duckdb\": \"CAST([1, 2] AS BIGINT[])\",\n                \"presto\": \"CAST(ARRAY[1, 2] AS ARRAY(BIGINT))\",\n                \"spark\": \"CAST(ARRAY(1, 2) AS ARRAY<BIGINT>)\",\n                \"snowflake\": \"CAST([1, 2] AS ARRAY(BIGINT))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(MAP(ARRAY['key'], ARRAY[1]) AS MAP(VARCHAR, INT))\",\n            write={\n                \"duckdb\": \"CAST(MAP(['key'], [1]) AS MAP(TEXT, INT))\",\n                \"presto\": \"CAST(MAP(ARRAY['key'], ARRAY[1]) AS MAP(VARCHAR, INTEGER))\",\n                \"hive\": \"CAST(MAP('key', 1) AS MAP<STRING, INT>)\",\n                \"snowflake\": \"CAST(OBJECT_CONSTRUCT('key', 1) AS MAP(VARCHAR, INT))\",\n                \"spark\": \"CAST(MAP_FROM_ARRAYS(ARRAY('key'), ARRAY(1)) AS MAP<STRING, INT>)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(MAP(ARRAY['a','b','c'], ARRAY[ARRAY[1], ARRAY[2], ARRAY[3]]) AS MAP(VARCHAR, ARRAY(INT)))\",\n            write={\n                \"bigquery\": \"CAST(MAP(['a', 'b', 'c'], [[1], [2], [3]]) AS MAP<STRING, ARRAY<INT64>>)\",\n                \"duckdb\": \"CAST(MAP(['a', 'b', 'c'], [[1], [2], [3]]) AS MAP(TEXT, INT[]))\",\n                \"presto\": \"CAST(MAP(ARRAY['a', 'b', 'c'], ARRAY[ARRAY[1], ARRAY[2], ARRAY[3]]) AS MAP(VARCHAR, ARRAY(INTEGER)))\",\n                \"hive\": \"CAST(MAP('a', ARRAY(1), 'b', ARRAY(2), 'c', ARRAY(3)) AS MAP<STRING, ARRAY<INT>>)\",\n                \"spark\": \"CAST(MAP_FROM_ARRAYS(ARRAY('a', 'b', 'c'), ARRAY(ARRAY(1), ARRAY(2), ARRAY(3))) AS MAP<STRING, ARRAY<INT>>)\",\n                \"snowflake\": \"CAST(OBJECT_CONSTRUCT('a', [1], 'b', [2], 'c', [3]) AS MAP(VARCHAR, ARRAY(INT)))\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS TIME(5) WITH TIME ZONE)\",\n            write={\n                \"duckdb\": \"CAST(x AS TIMETZ)\",\n                \"postgres\": \"CAST(x AS TIMETZ(5))\",\n                \"presto\": \"CAST(x AS TIME(5) WITH TIME ZONE)\",\n                \"redshift\": \"CAST(x AS TIME(5) WITH TIME ZONE)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS TIMESTAMP(9) WITH TIME ZONE)\",\n            write={\n                \"bigquery\": \"CAST(x AS TIMESTAMP)\",\n                \"duckdb\": \"CAST(x AS TIMESTAMPTZ)\",\n                \"presto\": \"CAST(x AS TIMESTAMP(9) WITH TIME ZONE)\",\n                \"hive\": \"CAST(x AS TIMESTAMP)\",\n                \"spark\": \"CAST(x AS TIMESTAMP)\",\n            },\n        )\n\n    def test_replace(self):\n        self.validate_all(\n            \"REPLACE(subject, pattern)\",\n            write={\n                \"bigquery\": \"REPLACE(subject, pattern, '')\",\n                \"duckdb\": \"REPLACE(subject, pattern, '')\",\n                \"hive\": \"REPLACE(subject, pattern, '')\",\n                \"snowflake\": \"REPLACE(subject, pattern, '')\",\n                \"spark\": \"REPLACE(subject, pattern, '')\",\n                \"presto\": \"REPLACE(subject, pattern, '')\",\n            },\n        )\n        self.validate_all(\n            \"REPLACE(subject, pattern, replacement)\",\n            read={\n                \"bigquery\": \"REPLACE(subject, pattern, replacement)\",\n                \"duckdb\": \"REPLACE(subject, pattern, replacement)\",\n                \"hive\": \"REPLACE(subject, pattern, replacement)\",\n                \"spark\": \"REPLACE(subject, pattern, replacement)\",\n                \"presto\": \"REPLACE(subject, pattern, replacement)\",\n            },\n            write={\n                \"bigquery\": \"REPLACE(subject, pattern, replacement)\",\n                \"duckdb\": \"REPLACE(subject, pattern, replacement)\",\n                \"hive\": \"REPLACE(subject, pattern, replacement)\",\n                \"snowflake\": \"REPLACE(subject, pattern, replacement)\",\n                \"spark\": \"REPLACE(subject, pattern, replacement)\",\n                \"presto\": \"REPLACE(subject, pattern, replacement)\",\n            },\n        )\n\n    def test_regex(self):\n        self.validate_all(\n            \"REGEXP_REPLACE('abcd', '[ab]')\",\n            write={\n                \"presto\": \"REGEXP_REPLACE('abcd', '[ab]', '')\",\n                \"spark\": \"REGEXP_REPLACE('abcd', '[ab]', '')\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_LIKE(a, 'x')\",\n            write={\n                \"duckdb\": \"REGEXP_MATCHES(a, 'x')\",\n                \"presto\": \"REGEXP_LIKE(a, 'x')\",\n                \"hive\": \"a RLIKE 'x'\",\n                \"spark\": \"a RLIKE 'x'\",\n            },\n        )\n        self.validate_all(\n            \"SPLIT(x, 'a.')\",\n            write={\n                \"duckdb\": \"STR_SPLIT(x, 'a.')\",\n                \"presto\": \"SPLIT(x, 'a.')\",\n                \"hive\": \"SPLIT(x, CONCAT('\\\\\\\\Q', 'a.', '\\\\\\\\E'))\",\n                \"spark\": \"SPLIT(x, CONCAT('\\\\\\\\Q', 'a.', '\\\\\\\\E'))\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SPLIT(x, 'a.')\",\n            write={\n                \"duckdb\": \"STR_SPLIT_REGEX(x, 'a.')\",\n                \"presto\": \"REGEXP_SPLIT(x, 'a.')\",\n                \"hive\": \"SPLIT(x, 'a.')\",\n                \"spark\": \"SPLIT(x, 'a.')\",\n            },\n        )\n        self.validate_all(\n            \"CARDINALITY(x)\",\n            write={\n                \"duckdb\": \"ARRAY_LENGTH(x)\",\n                \"presto\": \"CARDINALITY(x)\",\n                \"hive\": \"SIZE(x)\",\n                \"spark\": \"SIZE(x)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_JOIN(x, '-', 'a')\",\n            write={\n                \"hive\": \"CONCAT_WS('-', x)\",\n                \"spark\": \"ARRAY_JOIN(x, '-', 'a')\",\n            },\n        )\n        self.validate_all(\n            \"STRPOS(haystack, needle, occurrence)\",\n            write={\n                \"bigquery\": \"INSTR(haystack, needle, 1, occurrence)\",\n                \"oracle\": \"INSTR(haystack, needle, 1, occurrence)\",\n                \"presto\": \"STRPOS(haystack, needle, occurrence)\",\n                \"tableau\": \"FINDNTH(haystack, needle, occurrence)\",\n                \"trino\": \"STRPOS(haystack, needle, occurrence)\",\n                \"teradata\": \"INSTR(haystack, needle, 1, occurrence)\",\n            },\n        )\n\n    def test_interval_plural_to_singular(self):\n        # Microseconds, weeks and quarters are not supported in Presto/Trino INTERVAL literals\n        unit_to_expected = {\n            \"SeCoNds\": \"SECOND\",\n            \"minutes\": \"MINUTE\",\n            \"hours\": \"HOUR\",\n            \"days\": \"DAY\",\n            \"months\": \"MONTH\",\n            \"years\": \"YEAR\",\n        }\n\n        for unit, expected in unit_to_expected.items():\n            self.validate_all(\n                f\"SELECT INTERVAL '1' {unit}\",\n                write={\n                    \"bigquery\": f\"SELECT INTERVAL '1' {expected}\",\n                    \"presto\": f\"SELECT INTERVAL '1' {expected}\",\n                    \"trino\": f\"SELECT INTERVAL '1' {expected}\",\n                    \"mysql\": f\"SELECT INTERVAL '1' {expected}\",\n                    \"doris\": f\"SELECT INTERVAL '1' {expected}\",\n                },\n            )\n\n    def test_time(self):\n        expr = parse_one(\"TIME(7) WITH TIME ZONE\", into=exp.DataType, read=\"presto\")\n        self.assertEqual(expr.this, exp.DataType.Type.TIMETZ)\n\n        self.validate_identity(\"FROM_UNIXTIME(a, b)\")\n        self.validate_identity(\"FROM_UNIXTIME(a, b, c)\")\n        self.validate_identity(\"TRIM(a, b)\")\n        self.validate_identity(\"VAR_POP(a)\")\n\n        self.validate_all(\n            \"SELECT FROM_UNIXTIME(col) FROM tbl\",\n            write={\n                \"presto\": \"SELECT FROM_UNIXTIME(col) FROM tbl\",\n                \"spark\": \"SELECT CAST(FROM_UNIXTIME(col) AS TIMESTAMP) FROM tbl\",\n                \"trino\": \"SELECT FROM_UNIXTIME(col) FROM tbl\",\n            },\n        )\n        self.validate_all(\n            \"DATE_FORMAT(x, '%Y-%m-%d %H:%i:%S')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%F %T', x)\",\n                \"duckdb\": \"STRFTIME(x, '%Y-%m-%d %H:%M:%S')\",\n                \"presto\": \"DATE_FORMAT(x, '%Y-%m-%d %T')\",\n                \"hive\": \"DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss')\",\n                \"spark\": \"DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_PARSE(x, '%Y-%m-%d %H:%i:%S')\",\n            write={\n                \"duckdb\": \"STRPTIME(x, '%Y-%m-%d %H:%M:%S')\",\n                \"presto\": \"DATE_PARSE(x, '%Y-%m-%d %T')\",\n                \"hive\": \"CAST(x AS TIMESTAMP)\",\n                \"spark\": \"TO_TIMESTAMP(x, 'yyyy-MM-dd HH:mm:ss')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_PARSE(x, '%Y-%m-%d')\",\n            write={\n                \"duckdb\": \"STRPTIME(x, '%Y-%m-%d')\",\n                \"presto\": \"DATE_PARSE(x, '%Y-%m-%d')\",\n                \"hive\": \"CAST(x AS TIMESTAMP)\",\n                \"spark\": \"TO_TIMESTAMP(x, 'yyyy-MM-dd')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_FORMAT(x, '%T')\",\n            write={\n                \"hive\": \"DATE_FORMAT(x, 'HH:mm:ss')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_PARSE(SUBSTR(x, 1, 10), '%Y-%m-%d')\",\n            write={\n                \"duckdb\": \"STRPTIME(SUBSTRING(x, 1, 10), '%Y-%m-%d')\",\n                \"presto\": \"DATE_PARSE(SUBSTRING(x, 1, 10), '%Y-%m-%d')\",\n                \"hive\": \"CAST(SUBSTRING(x, 1, 10) AS TIMESTAMP)\",\n                \"spark\": \"TO_TIMESTAMP(SUBSTRING(x, 1, 10), 'yyyy-MM-dd')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_PARSE(SUBSTRING(x, 1, 10), '%Y-%m-%d')\",\n            write={\n                \"duckdb\": \"STRPTIME(SUBSTRING(x, 1, 10), '%Y-%m-%d')\",\n                \"presto\": \"DATE_PARSE(SUBSTRING(x, 1, 10), '%Y-%m-%d')\",\n                \"hive\": \"CAST(SUBSTRING(x, 1, 10) AS TIMESTAMP)\",\n                \"spark\": \"TO_TIMESTAMP(SUBSTRING(x, 1, 10), 'yyyy-MM-dd')\",\n            },\n        )\n        self.validate_all(\n            \"FROM_UNIXTIME(x)\",\n            write={\n                \"duckdb\": \"TO_TIMESTAMP(x)\",\n                \"presto\": \"FROM_UNIXTIME(x)\",\n                \"hive\": \"FROM_UNIXTIME(x)\",\n                \"spark\": \"CAST(FROM_UNIXTIME(x) AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"TO_UNIXTIME(x)\",\n            write={\n                \"duckdb\": \"EPOCH(x)\",\n                \"presto\": \"TO_UNIXTIME(x)\",\n                \"hive\": \"UNIX_TIMESTAMP(x)\",\n                \"spark\": \"UNIX_TIMESTAMP(x)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD('DAY', 1, x)\",\n            write={\n                \"duckdb\": \"x + INTERVAL 1 DAY\",\n                \"presto\": \"DATE_ADD('DAY', 1, x)\",\n                \"hive\": \"DATE_ADD(x, 1)\",\n                \"spark\": \"DATE_ADD(x, 1)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD('DAY', 1 * -1, x)\",\n            write={\n                \"presto\": \"DATE_ADD('DAY', 1 * -1, x)\",\n            },\n        )\n        self.validate_all(\n            \"NOW()\",\n            write={\n                \"presto\": \"CURRENT_TIMESTAMP\",\n                \"hive\": \"CURRENT_TIMESTAMP()\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_ADD('DAY', 1, CAST(CURRENT_DATE AS TIMESTAMP))\",\n            read={\n                \"redshift\": \"SELECT DATEADD(DAY, 1, CURRENT_DATE)\",\n            },\n        )\n        self.validate_all(\n            \"((DAY_OF_WEEK(CAST(CAST(TRY_CAST('2012-08-08 01:00:00' AS TIMESTAMP WITH TIME ZONE) AS TIMESTAMP) AS DATE)) % 7) + 1)\",\n            read={\n                \"spark\": \"DAYOFWEEK(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"DAY_OF_WEEK(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n            read={\n                \"duckdb\": \"ISODOW(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n            },\n            write={\n                \"spark\": \"((DAYOFWEEK(CAST('2012-08-08 01:00:00' AS TIMESTAMP)) % 7) + 1)\",\n                \"presto\": \"DAY_OF_WEEK(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n                \"duckdb\": \"ISODOW(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n            },\n        )\n\n        self.validate_all(\n            \"DAY_OF_MONTH(timestamp '2012-08-08 01:00:00')\",\n            write={\n                \"spark\": \"DAYOFMONTH(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n                \"presto\": \"DAY_OF_MONTH(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n                \"duckdb\": \"DAYOFMONTH(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n            },\n        )\n\n        self.validate_all(\n            \"DAY_OF_YEAR(timestamp '2012-08-08 01:00:00')\",\n            write={\n                \"spark\": \"DAYOFYEAR(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n                \"presto\": \"DAY_OF_YEAR(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n                \"duckdb\": \"DAYOFYEAR(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n            },\n        )\n\n        self.validate_all(\n            \"WEEK_OF_YEAR(timestamp '2012-08-08 01:00:00')\",\n            write={\n                \"spark\": \"WEEKOFYEAR(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n                \"presto\": \"WEEK_OF_YEAR(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n                \"duckdb\": \"WEEKOFYEAR(CAST('2012-08-08 01:00:00' AS TIMESTAMP))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT CAST('2012-10-31 00:00' AS TIMESTAMP) AT TIME ZONE 'America/Sao_Paulo'\",\n            write={\n                \"spark\": \"SELECT FROM_UTC_TIMESTAMP(CAST('2012-10-31 00:00' AS TIMESTAMP), 'America/Sao_Paulo')\",\n                \"presto\": \"SELECT AT_TIMEZONE(CAST('2012-10-31 00:00' AS TIMESTAMP), 'America/Sao_Paulo')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT AT_TIMEZONE(CAST('2012-10-31 00:00' AS TIMESTAMP WITH TIME ZONE), 'America/Sao_Paulo')\",\n            read={\n                \"spark\": \"SELECT FROM_UTC_TIMESTAMP(TIMESTAMP '2012-10-31 00:00', 'America/Sao_Paulo')\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS TIMESTAMP)\",\n            write={\"presto\": \"CAST(x AS TIMESTAMP)\"},\n            read={\"mysql\": \"CAST(x AS DATETIME)\", \"clickhouse\": \"CAST(x AS DATETIME64)\"},\n        )\n        self.validate_all(\n            \"CAST(x AS TIMESTAMP)\",\n            read={\"mysql\": \"TIMESTAMP(x)\"},\n        )\n        # this case isn't really correct, but it's a fall back for mysql's version\n        self.validate_all(\n            \"TIMESTAMP(x, '12:00:00')\",\n            write={\n                \"duckdb\": \"TIMESTAMP(x, '12:00:00')\",\n                \"presto\": \"TIMESTAMP(x, '12:00:00')\",\n            },\n        )\n        self.validate_all(\n            \"DATE_ADD('DAY', CAST(x AS BIGINT), y)\",\n            write={\n                \"presto\": \"DATE_ADD('DAY', CAST(x AS BIGINT), y)\",\n            },\n            read={\n                \"presto\": \"DATE_ADD('DAY', x, y)\",\n            },\n        )\n        self.validate_identity(\"DATE_ADD('DAY', 1, y)\")\n\n        self.validate_all(\n            \"SELECT DATE_ADD('MINUTE', 30, col)\",\n            write={\n                \"presto\": \"SELECT DATE_ADD('MINUTE', 30, col)\",\n                \"trino\": \"SELECT DATE_ADD('MINUTE', 30, col)\",\n            },\n        )\n\n        self.validate_identity(\"DATE_ADD('DAY', FLOOR(5), y)\")\n        self.validate_identity(\n            \"\"\"SELECT DATE_ADD('DAY', MOD(5, 2.5), y), DATE_ADD('DAY', CEIL(5.5), y)\"\"\",\n            \"\"\"SELECT DATE_ADD('DAY', CAST(5 % 2.5 AS BIGINT), y), DATE_ADD('DAY', CAST(CEIL(5.5) AS BIGINT), y)\"\"\",\n        )\n\n        self.validate_all(\n            \"DATE_ADD('MINUTE', CAST(FLOOR(CAST(EXTRACT(MINUTE FROM CURRENT_TIMESTAMP) AS DOUBLE) / NULLIF(30, 0)) * 30 AS BIGINT), col)\",\n            read={\n                \"spark\": \"TIMESTAMPADD(MINUTE, FLOOR(EXTRACT(MINUTE FROM CURRENT_TIMESTAMP)/30)*30, col)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT WEEK_OF_YEAR(y)\",\n            read={\n                \"presto\": \"SELECT WEEK(y)\",\n            },\n            write={\n                \"spark\": \"SELECT WEEKOFYEAR(y)\",\n                \"presto\": \"SELECT WEEK_OF_YEAR(y)\",\n                \"trino\": \"SELECT WEEK_OF_YEAR(y)\",\n            },\n        )\n\n    def test_ddl(self):\n        self.validate_all(\n            \"CREATE TABLE test WITH (FORMAT = 'PARQUET') AS SELECT 1\",\n            write={\n                \"duckdb\": \"CREATE TABLE test AS SELECT 1\",\n                \"presto\": \"CREATE TABLE test WITH (format='PARQUET') AS SELECT 1\",\n                \"hive\": \"CREATE TABLE test STORED AS PARQUET AS SELECT 1\",\n                \"spark\": \"CREATE TABLE test USING PARQUET AS SELECT 1\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE test STORED AS 'PARQUET' AS SELECT 1\",\n            write={\n                \"duckdb\": \"CREATE TABLE test AS SELECT 1\",\n                \"presto\": \"CREATE TABLE test WITH (format='PARQUET') AS SELECT 1\",\n                \"hive\": \"CREATE TABLE test STORED AS PARQUET AS SELECT 1\",\n                \"spark\": \"CREATE TABLE test STORED AS PARQUET AS SELECT 1\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE test WITH (FORMAT = 'PARQUET', X = '1', Z = '2') AS SELECT 1\",\n            write={\n                \"duckdb\": \"CREATE TABLE test AS SELECT 1\",\n                \"presto\": \"CREATE TABLE test WITH (format='PARQUET', X='1', Z='2') AS SELECT 1\",\n                \"hive\": \"CREATE TABLE test STORED AS PARQUET TBLPROPERTIES ('X'='1', 'Z'='2') AS SELECT 1\",\n                \"spark\": \"CREATE TABLE test USING PARQUET TBLPROPERTIES ('X'='1', 'Z'='2') AS SELECT 1\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE x (w VARCHAR, y INTEGER, z INTEGER) WITH (PARTITIONED_BY=ARRAY['y', 'z'])\",\n            write={\n                \"duckdb\": \"CREATE TABLE x (w TEXT, y INT, z INT)\",\n                \"presto\": \"CREATE TABLE x (w VARCHAR, y INTEGER, z INTEGER) WITH (PARTITIONED_BY=ARRAY['y', 'z'])\",\n                \"hive\": \"CREATE TABLE x (w STRING) PARTITIONED BY (y INT, z INT)\",\n                \"spark\": \"CREATE TABLE x (w STRING, y INT, z INT) PARTITIONED BY (y, z)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE x WITH (bucket_by = ARRAY['y'], bucket_count = 64) AS SELECT 1 AS y\",\n            write={\n                \"duckdb\": \"CREATE TABLE x AS SELECT 1 AS y\",\n                \"presto\": \"CREATE TABLE x WITH (bucket_by=ARRAY['y'], bucket_count=64) AS SELECT 1 AS y\",\n                \"hive\": \"CREATE TABLE x TBLPROPERTIES ('bucket_by'=ARRAY('y'), 'bucket_count'=64) AS SELECT 1 AS y\",\n                \"spark\": \"CREATE TABLE x TBLPROPERTIES ('bucket_by'=ARRAY('y'), 'bucket_count'=64) AS SELECT 1 AS y\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b VARCHAR))\",\n            write={\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a STRUCT(struct_col_a INT, struct_col_b TEXT))\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b VARCHAR))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRING>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRING>)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b ROW(nested_col_a VARCHAR, nested_col_b VARCHAR)))\",\n            write={\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a STRUCT(struct_col_a INT, struct_col_b STRUCT(nested_col_a TEXT, nested_col_b TEXT)))\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b ROW(nested_col_a VARCHAR, nested_col_b VARCHAR)))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRUCT<nested_col_a: STRING, nested_col_b: STRING>>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRUCT<nested_col_a: STRING, nested_col_b: STRING>>)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"presto\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname\",\n                \"spark\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname NULLS LAST\",\n            },\n        )\n\n        self.validate_all(\n            \"CREATE OR REPLACE VIEW x (cola) SELECT 1 as cola\",\n            write={\n                \"spark\": \"CREATE OR REPLACE VIEW x (cola) AS SELECT 1 AS cola\",\n                \"presto\": \"CREATE OR REPLACE VIEW x AS SELECT 1 AS cola\",\n            },\n        )\n\n        self.validate_all(\n            \"\"\"CREATE TABLE IF NOT EXISTS x (\"cola\" INTEGER, \"ds\" TEXT) COMMENT 'comment' WITH (PARTITIONED BY=(\"ds\"))\"\"\",\n            write={\n                \"spark\": \"CREATE TABLE IF NOT EXISTS x (`cola` INT, `ds` STRING) COMMENT 'comment' PARTITIONED BY (`ds`)\",\n                \"presto\": \"\"\"CREATE TABLE IF NOT EXISTS x (\"cola\" INTEGER, \"ds\" VARCHAR) COMMENT 'comment' WITH (PARTITIONED_BY=ARRAY['ds'])\"\"\",\n            },\n        )\n\n        self.validate_identity(\"\"\"CREATE OR REPLACE VIEW v SECURITY DEFINER AS SELECT id FROM t\"\"\")\n        self.validate_identity(\"\"\"CREATE OR REPLACE VIEW v SECURITY INVOKER AS SELECT id FROM t\"\"\")\n\n    def test_quotes(self):\n        self.validate_all(\n            \"''''\",\n            write={\n                \"duckdb\": \"''''\",\n                \"presto\": \"''''\",\n                \"hive\": \"'\\\\''\",\n                \"spark\": \"'\\\\''\",\n            },\n        )\n        self.validate_all(\n            \"'x'\",\n            write={\n                \"duckdb\": \"'x'\",\n                \"presto\": \"'x'\",\n                \"hive\": \"'x'\",\n                \"spark\": \"'x'\",\n            },\n        )\n        self.validate_all(\n            \"'''x'''\",\n            write={\n                \"duckdb\": \"'''x'''\",\n                \"presto\": \"'''x'''\",\n                \"hive\": \"'\\\\'x\\\\''\",\n                \"spark\": \"'\\\\'x\\\\''\",\n            },\n        )\n        self.validate_all(\n            \"'''x'\",\n            write={\n                \"duckdb\": \"'''x'\",\n                \"presto\": \"'''x'\",\n                \"hive\": \"'\\\\'x'\",\n                \"spark\": \"'\\\\'x'\",\n            },\n        )\n        self.validate_all(\n            \"x IN ('a', 'a''b')\",\n            write={\n                \"duckdb\": \"x IN ('a', 'a''b')\",\n                \"presto\": \"x IN ('a', 'a''b')\",\n                \"hive\": \"x IN ('a', 'a\\\\'b')\",\n                \"spark\": \"x IN ('a', 'a\\\\'b')\",\n            },\n        )\n\n    def test_unnest(self):\n        self.validate_all(\n            \"SELECT a FROM x CROSS JOIN UNNEST(ARRAY(y)) AS t (a)\",\n            write={\n                \"presto\": \"SELECT a FROM x CROSS JOIN UNNEST(ARRAY[y]) AS t(a)\",\n                \"hive\": \"SELECT a FROM x LATERAL VIEW EXPLODE(ARRAY(y)) t AS a\",\n                \"spark\": \"SELECT a FROM x LATERAL VIEW EXPLODE(ARRAY(y)) t AS a\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT a FROM x CROSS JOIN UNNEST(ARRAY(y)) AS t (a) CROSS JOIN b\",\n            write={\n                \"presto\": \"SELECT a FROM x CROSS JOIN UNNEST(ARRAY[y]) AS t(a) CROSS JOIN b\",\n                \"hive\": \"SELECT a FROM x CROSS JOIN b LATERAL VIEW EXPLODE(ARRAY(y)) t AS a\",\n            },\n        )\n\n    def test_unicode_string(self):\n        for prefix in (\"u&\", \"U&\"):\n            self.validate_all(\n                f\"{prefix}'Hello winter \\\\2603 !'\",\n                write={\n                    \"oracle\": \"U'Hello winter \\\\2603 !'\",\n                    \"presto\": \"U&'Hello winter \\\\2603 !'\",\n                    \"snowflake\": \"'Hello winter \\\\u2603 !'\",\n                    \"spark\": \"'Hello winter \\\\u2603 !'\",\n                },\n            )\n            self.validate_all(\n                f\"{prefix}'Hello winter #2603 !' UESCAPE '#'\",\n                write={\n                    \"oracle\": \"U'Hello winter \\\\2603 !'\",\n                    \"presto\": \"U&'Hello winter #2603 !' UESCAPE '#'\",\n                    \"snowflake\": \"'Hello winter \\\\u2603 !'\",\n                    \"spark\": \"'Hello winter \\\\u2603 !'\",\n                },\n            )\n\n    def test_presto(self):\n        self.assertEqual(\n            exp.func(\"md5\", exp.func(\"concat\", exp.cast(\"x\", \"text\"), exp.Literal.string(\"s\"))).sql(\n                dialect=\"presto\"\n            ),\n            \"LOWER(TO_HEX(MD5(TO_UTF8(CONCAT(CAST(x AS VARCHAR), CAST('s' AS VARCHAR))))))\",\n        )\n\n        with self.assertLogs(helper_logger):\n            self.validate_all(\n                \"SELECT COALESCE(ELEMENT_AT(MAP_FROM_ENTRIES(ARRAY[(51, '1')]), id), quantity) FROM my_table\",\n                write={\n                    \"postgres\": UnsupportedError,\n                    \"presto\": \"SELECT COALESCE(ELEMENT_AT(MAP_FROM_ENTRIES(ARRAY[(51, '1')]), id), quantity) FROM my_table\",\n                },\n            )\n            self.validate_all(\n                \"SELECT ELEMENT_AT(ARRAY[1, 2, 3], 4)\",\n                write={\n                    \"\": \"SELECT ARRAY(1, 2, 3)[3]\",\n                    \"bigquery\": \"SELECT [1, 2, 3][SAFE_ORDINAL(4)]\",\n                    \"postgres\": \"SELECT (ARRAY[1, 2, 3])[4]\",\n                    \"presto\": \"SELECT ELEMENT_AT(ARRAY[1, 2, 3], 4)\",\n                },\n            )\n\n        self.validate_identity(\"SELECT a FROM t GROUP BY a, ROLLUP (b), ROLLUP (c), ROLLUP (d)\")\n        self.validate_identity(\"SELECT a FROM test TABLESAMPLE BERNOULLI (50)\")\n        self.validate_identity(\"SELECT a FROM test TABLESAMPLE SYSTEM (75)\")\n        self.validate_identity(\"string_agg(x, ',')\", \"ARRAY_JOIN(ARRAY_AGG(x), ',')\")\n        self.validate_identity(\"SELECT * FROM x OFFSET 1 LIMIT 1\")\n        self.validate_identity(\"SELECT * FROM x OFFSET 1 FETCH FIRST 1 ROWS ONLY\")\n        self.validate_identity(\"SELECT BOOL_OR(a > 10) FROM asd AS T(a)\")\n\n        # Numeric TRUNCATE\n        self.validate_identity(\"TRUNCATE(3.14159, 2)\").assert_is(exp.Trunc)\n        self.validate_identity(\"TRUNCATE(3.14159)\").assert_is(exp.Trunc)\n        self.validate_all(\n            \"TRUNCATE(3.14159, 2)\",\n            read={\"postgres\": \"TRUNC(3.14159, 2)\"},\n        )\n        self.validate_identity(\"SELECT * FROM (VALUES (1))\")\n        self.validate_identity(\"START TRANSACTION READ WRITE, ISOLATION LEVEL SERIALIZABLE\")\n        self.validate_identity(\"START TRANSACTION ISOLATION LEVEL REPEATABLE READ\")\n        self.validate_identity(\"APPROX_PERCENTILE(a, b, c, d)\")\n        self.validate_identity(\n            \"SELECT SPLIT_TO_MAP('a:1;b:2;a:3', ';', ':', (k, v1, v2) -> CONCAT(v1, v2))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM example.testdb.customer_orders FOR VERSION AS OF 8954597067493422955\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM example.testdb.customer_orders FOR TIMESTAMP AS OF CAST('2022-03-23 09:59:29.803 Europe/Vienna' AS TIMESTAMP)\"\n        )\n        self.validate_identity(\n            \"SELECT origin_state, destination_state, origin_zip, SUM(package_weight) FROM shipping GROUP BY ALL CUBE (origin_state, destination_state), ROLLUP (origin_state, origin_zip)\"\n        )\n        self.validate_identity(\n            \"SELECT origin_state, destination_state, origin_zip, SUM(package_weight) FROM shipping GROUP BY DISTINCT CUBE (origin_state, destination_state), ROLLUP (origin_state, origin_zip)\"\n        )\n        self.validate_identity(\n            \"SELECT JSON_EXTRACT_SCALAR(CAST(extra AS JSON), '$.value_b'), COUNT(*) FROM table_a GROUP BY DISTINCT (JSON_EXTRACT_SCALAR(CAST(extra AS JSON), '$.value_b'))\"\n        )\n\n        self.validate_all(\n            \"SELECT LAST_DAY_OF_MONTH(CAST('2008-11-25' AS DATE))\",\n            read={\n                \"duckdb\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE))\",\n            },\n            write={\n                \"duckdb\": \"SELECT LAST_DAY(CAST('2008-11-25' AS DATE))\",\n                \"presto\": \"SELECT LAST_DAY_OF_MONTH(CAST('2008-11-25' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n            read={\n                \"bigquery\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n                \"clickhouse\": \"SELECT argMax(a.id, a.timestamp) FROM a\",\n                \"duckdb\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n                \"snowflake\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n                \"spark\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n                \"teradata\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n            },\n            write={\n                \"bigquery\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n                \"clickhouse\": \"SELECT argMax(a.id, a.timestamp) FROM a\",\n                \"duckdb\": \"SELECT ARG_MAX(a.id, a.timestamp) FROM a\",\n                \"presto\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n                \"snowflake\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n                \"spark\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n                \"teradata\": \"SELECT MAX_BY(a.id, a.timestamp) FROM a\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MIN_BY(a.id, a.timestamp, 3) FROM a\",\n            write={\n                \"clickhouse\": \"SELECT argMin(a.id, a.timestamp) FROM a\",\n                \"duckdb\": \"SELECT ARG_MIN(a.id, a.timestamp, 3) FROM a\",\n                \"presto\": \"SELECT MIN_BY(a.id, a.timestamp, 3) FROM a\",\n                \"snowflake\": \"SELECT MIN_BY(a.id, a.timestamp, 3) FROM a\",\n                \"spark\": \"SELECT MIN_BY(a.id, a.timestamp) FROM a\",\n                \"teradata\": \"SELECT MIN_BY(a.id, a.timestamp, 3) FROM a\",\n            },\n        )\n        self.validate_all(\n            \"\"\"JSON '\"foo\"'\"\"\",\n            write={\n                \"bigquery\": \"\"\"PARSE_JSON('\"foo\"')\"\"\",\n                \"postgres\": \"\"\"CAST('\"foo\"' AS JSON)\"\"\",\n                \"presto\": \"\"\"JSON_PARSE('\"foo\"')\"\"\",\n                \"snowflake\": \"\"\"PARSE_JSON('\"foo\"')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ROW(1, 2)\",\n            write={\n                \"presto\": \"SELECT ROW(1, 2)\",\n                \"spark\": \"SELECT STRUCT(1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"ARBITRARY(x)\",\n            read={\n                \"bigquery\": \"ANY_VALUE(x)\",\n                \"clickhouse\": \"any(x)\",\n                \"databricks\": \"ANY_VALUE(x)\",\n                \"doris\": \"ANY_VALUE(x)\",\n                \"drill\": \"ANY_VALUE(x)\",\n                \"hive\": \"FIRST(x)\",\n                \"mysql\": \"ANY_VALUE(x)\",\n                \"oracle\": \"ANY_VALUE(x)\",\n                \"redshift\": \"ANY_VALUE(x)\",\n                \"snowflake\": \"ANY_VALUE(x)\",\n                \"spark\": \"ANY_VALUE(x)\",\n                \"spark2\": \"FIRST(x)\",\n            },\n            write={\n                \"bigquery\": \"ANY_VALUE(x)\",\n                \"clickhouse\": \"any(x)\",\n                \"databricks\": \"ANY_VALUE(x)\",\n                \"doris\": \"ANY_VALUE(x)\",\n                \"drill\": \"ANY_VALUE(x)\",\n                \"duckdb\": \"ANY_VALUE(x)\",\n                \"hive\": \"FIRST(x)\",\n                \"mysql\": \"ANY_VALUE(x)\",\n                \"oracle\": \"ANY_VALUE(x)\",\n                \"postgres\": \"ANY_VALUE(x)\",\n                \"presto\": \"ARBITRARY(x)\",\n                \"redshift\": \"ANY_VALUE(x)\",\n                \"snowflake\": \"ANY_VALUE(x)\",\n                \"spark\": \"ANY_VALUE(x)\",\n                \"spark2\": \"FIRST(x)\",\n                \"sqlite\": \"MAX(x)\",\n                \"tsql\": \"MAX(x)\",\n            },\n        )\n        self.validate_all(\n            \"STARTS_WITH('abc', 'a')\",\n            read={\"spark\": \"STARTSWITH('abc', 'a')\"},\n            write={\n                \"presto\": \"STARTS_WITH('abc', 'a')\",\n                \"snowflake\": \"STARTSWITH('abc', 'a')\",\n                \"spark\": \"STARTSWITH('abc', 'a')\",\n            },\n        )\n        self.validate_all(\n            \"IS_NAN(x)\",\n            read={\n                \"spark\": \"ISNAN(x)\",\n            },\n            write={\n                \"presto\": \"IS_NAN(x)\",\n                \"spark\": \"ISNAN(x)\",\n                \"spark2\": \"ISNAN(x)\",\n            },\n        )\n        self.validate_all(\"VALUES 1, 2, 3\", write={\"presto\": \"VALUES (1), (2), (3)\"})\n        self.validate_all(\"INTERVAL '1 day'\", write={\"trino\": \"INTERVAL '1' DAY\"})\n        self.validate_all(\"(5 * INTERVAL '7' DAY)\", read={\"\": \"INTERVAL '5' WEEK\"})\n        self.validate_all(\"(5 * INTERVAL '7' DAY)\", read={\"\": \"INTERVAL '5' WEEKS\"})\n        self.validate_all(\n            \"SELECT SUBSTRING(a, 1, 3), SUBSTRING(a, LENGTH(a) - (3 - 1))\",\n            read={\n                \"redshift\": \"SELECT LEFT(a, 3), RIGHT(a, 3)\",\n            },\n        )\n        self.validate_all(\n            \"WITH RECURSIVE t(n) AS (SELECT 1 AS n UNION ALL SELECT n + 1 AS n FROM t WHERE n < 4) SELECT SUM(n) FROM t\",\n            read={\n                \"postgres\": \"WITH RECURSIVE t AS (SELECT 1 AS n UNION ALL SELECT n + 1 AS n FROM t WHERE n < 4) SELECT SUM(n) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"WITH RECURSIVE t(n, k) AS (SELECT 1 AS n, 2 AS k) SELECT SUM(n) FROM t\",\n            read={\n                \"postgres\": \"WITH RECURSIVE t AS (SELECT 1 AS n, 2 as k) SELECT SUM(n) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"WITH RECURSIVE t1(n) AS (SELECT 1 AS n), t2(n) AS (SELECT 2 AS n) SELECT SUM(t1.n), SUM(t2.n) FROM t1, t2\",\n            read={\n                \"postgres\": \"WITH RECURSIVE t1 AS (SELECT 1 AS n), t2 AS (SELECT 2 AS n) SELECT SUM(t1.n), SUM(t2.n) FROM t1, t2\",\n            },\n        )\n        self.validate_all(\n            \"WITH RECURSIVE t(n, _c_0) AS (SELECT 1 AS n, (1 + 2)) SELECT * FROM t\",\n            read={\n                \"postgres\": \"WITH RECURSIVE t AS (SELECT 1 AS n, (1 + 2)) SELECT * FROM t\",\n            },\n        )\n        self.validate_all(\n            'WITH RECURSIVE t(n, \"1\") AS (SELECT n, 1 FROM tbl) SELECT * FROM t',\n            read={\n                \"postgres\": \"WITH RECURSIVE t AS (SELECT n, 1 FROM tbl) SELECT * FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_OBJECT(KEY 'key1' VALUE 1, KEY 'key2' VALUE TRUE)\",\n            write={\n                \"presto\": \"SELECT JSON_OBJECT('key1': 1, 'key2': TRUE)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_AGG(x ORDER BY y DESC)\",\n            write={\n                \"hive\": \"COLLECT_LIST(x)\",\n                \"presto\": \"ARRAY_AGG(x ORDER BY y DESC)\",\n                \"spark\": \"COLLECT_LIST(x)\",\n                \"trino\": \"ARRAY_AGG(x ORDER BY y DESC)\",\n            },\n        )\n        self.validate_all(\n            'SELECT a.\"b\" FROM \"foo\"',\n            write={\n                \"duckdb\": 'SELECT a.\"b\" FROM \"foo\"',\n                \"presto\": 'SELECT a.\"b\" FROM \"foo\"',\n                \"spark\": \"SELECT a.`b` FROM `foo`\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY[1, 2]\",\n            write={\n                \"bigquery\": \"SELECT [1, 2]\",\n                \"duckdb\": \"SELECT [1, 2]\",\n                \"presto\": \"SELECT ARRAY[1, 2]\",\n                \"spark\": \"SELECT ARRAY(1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT APPROX_DISTINCT(a) FROM foo\",\n            write={\n                \"duckdb\": \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n                \"presto\": \"SELECT APPROX_DISTINCT(a) FROM foo\",\n                \"hive\": \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n                \"spark\": \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n            },\n        )\n        self.validate_all(\n            \"SELECT APPROX_DISTINCT(a, 0.1) FROM foo\",\n            write={\n                \"duckdb\": \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n                \"presto\": \"SELECT APPROX_DISTINCT(a, 0.1) FROM foo\",\n                \"hive\": \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n                \"spark\": \"SELECT APPROX_COUNT_DISTINCT(a, 0.1) FROM foo\",\n            },\n        )\n        self.validate_all(\n            \"SELECT APPROX_DISTINCT(a, 0.1) FROM foo\",\n            write={\n                \"presto\": \"SELECT APPROX_DISTINCT(a, 0.1) FROM foo\",\n                \"hive\": UnsupportedError,\n                \"spark\": \"SELECT APPROX_COUNT_DISTINCT(a, 0.1) FROM foo\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT(x, '$.name')\",\n            write={\n                \"presto\": \"SELECT JSON_EXTRACT(x, '$.name')\",\n                \"hive\": \"SELECT GET_JSON_OBJECT(x, '$.name')\",\n                \"spark\": \"SELECT GET_JSON_OBJECT(x, '$.name')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_SCALAR(x, '$.name')\",\n            write={\n                \"presto\": \"SELECT JSON_EXTRACT_SCALAR(x, '$.name')\",\n                \"hive\": \"SELECT GET_JSON_OBJECT(x, '$.name')\",\n                \"spark\": \"SELECT GET_JSON_OBJECT(x, '$.name')\",\n            },\n        )\n        self.validate_all(\n            \"'\\u6bdb'\",\n            write={\n                \"presto\": \"'\\u6bdb'\",\n                \"hive\": \"'\\u6bdb'\",\n                \"spark\": \"'\\u6bdb'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_SORT(x, (left, right) -> -1)\",\n            write={\n                \"duckdb\": \"SELECT ARRAY_SORT(x)\",\n                \"presto\": 'SELECT ARRAY_SORT(x, (\"left\", \"right\") -> -1)',\n                \"hive\": \"SELECT SORT_ARRAY(x)\",\n                \"spark\": \"SELECT ARRAY_SORT(x, (left, right) -> -1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_SORT(x)\",\n            write={\n                \"presto\": \"SELECT ARRAY_SORT(x)\",\n                \"hive\": \"SELECT SORT_ARRAY(x)\",\n                \"spark\": \"SELECT ARRAY_SORT(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_SORT(x, (left, right) -> -1)\",\n            write={\n                \"hive\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"MAP(a, b)\",\n            write={\n                \"hive\": UnsupportedError,\n                \"spark\": \"MAP_FROM_ARRAYS(a, b)\",\n                \"snowflake\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"MAP(ARRAY(a, b), ARRAY(c, d))\",\n            write={\n                \"hive\": \"MAP(a, c, b, d)\",\n                \"presto\": \"MAP(ARRAY[a, b], ARRAY[c, d])\",\n                \"spark\": \"MAP_FROM_ARRAYS(ARRAY(a, b), ARRAY(c, d))\",\n                \"snowflake\": \"OBJECT_CONSTRUCT(a, c, b, d)\",\n            },\n        )\n        self.validate_all(\n            \"MAP(ARRAY('a'), ARRAY('b'))\",\n            write={\n                \"hive\": \"MAP('a', 'b')\",\n                \"presto\": \"MAP(ARRAY['a'], ARRAY['b'])\",\n                \"spark\": \"MAP_FROM_ARRAYS(ARRAY('a'), ARRAY('b'))\",\n                \"snowflake\": \"OBJECT_CONSTRUCT('a', 'b')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST(ARRAY['7', '14']) AS x\",\n            write={\n                \"bigquery\": \"SELECT * FROM UNNEST(['7', '14'])\",\n                \"presto\": \"SELECT * FROM UNNEST(ARRAY['7', '14']) AS x\",\n                \"hive\": \"SELECT * FROM EXPLODE(ARRAY('7', '14')) AS x\",\n                \"spark\": \"SELECT * FROM EXPLODE(ARRAY('7', '14')) AS x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM UNNEST(ARRAY['7', '14']) AS x(y)\",\n            write={\n                \"bigquery\": \"SELECT * FROM UNNEST(['7', '14']) AS y\",\n                \"presto\": \"SELECT * FROM UNNEST(ARRAY['7', '14']) AS x(y)\",\n                \"hive\": \"SELECT * FROM EXPLODE(ARRAY('7', '14')) AS x(y)\",\n                \"spark\": \"SELECT * FROM EXPLODE(ARRAY('7', '14')) AS x(y)\",\n            },\n        )\n        self.validate_all(\n            \"WITH RECURSIVE t(n) AS (VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100 ) SELECT SUM(n) FROM t\",\n            write={\n                \"presto\": \"WITH RECURSIVE t(n) AS (VALUES (1) UNION ALL SELECT n + 1 FROM t WHERE n < 100) SELECT SUM(n) FROM t\",\n                \"spark\": \"WITH RECURSIVE t(n) AS (VALUES (1) UNION ALL SELECT n + 1 FROM t WHERE n < 100) SELECT SUM(n) FROM t\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT a, b, c, d, sum(y) FROM z GROUP BY CUBE(a) ROLLUP(a), GROUPING SETS((b, c)), d\",\n            write={\n                \"presto\": \"SELECT a, b, c, d, SUM(y) FROM z GROUP BY d, GROUPING SETS ((b, c)), CUBE (a), ROLLUP (a)\",\n                \"hive\": \"SELECT a, b, c, d, SUM(y) FROM z GROUP BY d, GROUPING SETS ((b, c)), CUBE (a), ROLLUP (a)\",\n            },\n        )\n        self.validate_all(\n            \"JSON_FORMAT(CAST(MAP_FROM_ENTRIES(ARRAY[('action_type', 'at')]) AS JSON))\",\n            write={\n                \"presto\": \"JSON_FORMAT(CAST(MAP_FROM_ENTRIES(ARRAY[('action_type', 'at')]) AS JSON))\",\n                \"spark\": \"TO_JSON(MAP_FROM_ENTRIES(ARRAY(('action_type', 'at'))))\",\n            },\n        )\n        self.validate_all(\n            \"JSON_FORMAT(x)\",\n            write={\n                \"bigquery\": \"TO_JSON_STRING(x)\",\n                \"duckdb\": \"CAST(TO_JSON(x) AS TEXT)\",\n                \"presto\": \"JSON_FORMAT(x)\",\n                \"spark\": \"TO_JSON(x)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"JSON_FORMAT(JSON '\"x\"')\"\"\",\n            write={\n                \"bigquery\": \"\"\"TO_JSON_STRING(PARSE_JSON('\"x\"'))\"\"\",\n                \"duckdb\": \"\"\"CAST(TO_JSON(JSON('\"x\"')) AS TEXT)\"\"\",\n                \"presto\": \"\"\"JSON_FORMAT(JSON_PARSE('\"x\"'))\"\"\",\n                \"spark\": \"\"\"REGEXP_EXTRACT(TO_JSON(FROM_JSON('[\"x\"]', SCHEMA_OF_JSON('[\"x\"]'))), '^.(.*).$', 1)\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT JSON_FORMAT(JSON '{\"a\": 1, \"b\": \"c\"}')\"\"\",\n            write={\n                \"spark\": \"\"\"SELECT REGEXP_EXTRACT(TO_JSON(FROM_JSON('[{\"a\": 1, \"b\": \"c\"}]', SCHEMA_OF_JSON('[{\"a\": 1, \"b\": \"c\"}]'))), '^.(.*).$', 1)\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT JSON_FORMAT(JSON '[1, 2, 3]')\"\"\",\n            write={\n                \"spark\": \"SELECT REGEXP_EXTRACT(TO_JSON(FROM_JSON('[[1, 2, 3]]', SCHEMA_OF_JSON('[[1, 2, 3]]'))), '^.(.*).$', 1)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n            read={\n                \"presto\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"trino\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"duckdb\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"snowflake\": \"REGEXP_SUBSTR('abc', '(a)(b)(c)')\",\n            },\n            write={\n                \"presto\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"trino\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"duckdb\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)')\",\n                \"snowflake\": \"REGEXP_SUBSTR('abc', '(a)(b)(c)')\",\n                \"hive\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)', 0)\",\n                \"spark2\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)', 0)\",\n                \"spark\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)', 0)\",\n                \"databricks\": \"REGEXP_EXTRACT('abc', '(a)(b)(c)', 0)\",\n            },\n        )\n        self.validate_all(\n            \"CURRENT_USER\",\n            read={\n                \"presto\": \"CURRENT_USER\",\n                \"trino\": \"CURRENT_USER\",\n                \"snowflake\": \"CURRENT_USER()\",  # Although the ANSI standard is CURRENT_USER\n            },\n            write={\n                \"trino\": \"CURRENT_USER\",\n                \"snowflake\": \"CURRENT_USER()\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT id, FIRST_VALUE(is_deleted) OVER (PARTITION BY id) AS first_is_deleted, NTH_VALUE(is_deleted, 2) OVER (PARTITION BY id) AS nth_is_deleted, LAST_VALUE(is_deleted) OVER (PARTITION BY id) AS last_is_deleted FROM my_table\"\n        )\n        self.validate_all(\n            \"SELECT NULLABLE FROM system.jdbc.types\",\n            read={\n                \"presto\": \"SELECT NULLABLE FROM system.jdbc.types\",\n                \"trino\": \"SELECT NULLABLE FROM system.jdbc.types\",\n            },\n        )\n\n        self.validate_identity(\n            \"SELECT * FROM foo FOR TIMESTAMP AS OF CAST('2020-01-01 00:00:00' AS TIMESTAMP) AS bar\"\n        )\n\n    def test_encode_decode(self):\n        self.validate_identity(\"FROM_UTF8(x, y)\")\n\n        self.validate_all(\n            \"TO_UTF8(x)\",\n            read={\n                \"duckdb\": \"ENCODE(x)\",\n                \"spark\": \"ENCODE(x, 'utf-8')\",\n            },\n            write={\n                \"duckdb\": \"ENCODE(x)\",\n                \"presto\": \"TO_UTF8(x)\",\n                \"spark\": \"ENCODE(x, 'utf-8')\",\n            },\n        )\n        self.validate_all(\n            \"FROM_UTF8(x)\",\n            read={\n                \"duckdb\": \"DECODE(x)\",\n                \"spark\": \"DECODE(x, 'utf-8')\",\n            },\n            write={\n                \"duckdb\": \"DECODE(x)\",\n                \"presto\": \"FROM_UTF8(x)\",\n                \"spark\": \"DECODE(x, 'utf-8')\",\n            },\n        )\n        self.validate_all(\n            \"ENCODE(x, 'invalid')\",\n            write={\n                \"presto\": UnsupportedError,\n                \"duckdb\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"DECODE(x, 'invalid')\",\n            write={\n                \"presto\": UnsupportedError,\n                \"duckdb\": UnsupportedError,\n            },\n        )\n\n    def test_hex_unhex(self):\n        self.validate_all(\n            \"TO_HEX(x)\",\n            write={\n                \"spark\": \"HEX(x)\",\n            },\n        )\n        self.validate_all(\n            \"FROM_HEX(x)\",\n            write={\n                \"spark\": \"UNHEX(x)\",\n            },\n        )\n        self.validate_all(\n            \"HEX(x)\",\n            write={\n                \"presto\": \"TO_HEX(x)\",\n            },\n        )\n        self.validate_all(\n            \"UNHEX(x)\",\n            write={\n                \"presto\": \"FROM_HEX(x)\",\n            },\n        )\n\n    def test_json(self):\n        with self.assertLogs(helper_logger):\n            self.validate_all(\n                \"\"\"SELECT JSON_EXTRACT_SCALAR(TRY(FILTER(CAST(JSON_EXTRACT('{\"k1\": [{\"k2\": \"{\\\\\"k3\\\\\": 1}\", \"k4\": \"v\"}]}', '$.k1') AS ARRAY(MAP(VARCHAR, VARCHAR))), x -> x['k4'] = 'v')[1]['k2']), '$.k3')\"\"\",\n                write={\n                    \"presto\": \"\"\"SELECT JSON_EXTRACT_SCALAR(TRY(FILTER(CAST(JSON_EXTRACT('{\"k1\": [{\"k2\": \"{\\\\\"k3\\\\\": 1}\", \"k4\": \"v\"}]}', '$.k1') AS ARRAY(MAP(VARCHAR, VARCHAR))), x -> x['k4'] = 'v')[1]['k2']), '$.k3')\"\"\",\n                    \"spark\": \"\"\"SELECT GET_JSON_OBJECT(FILTER(FROM_JSON(GET_JSON_OBJECT('{\"k1\": [{\"k2\": \"{\\\\\\\\\"k3\\\\\\\\\": 1}\", \"k4\": \"v\"}]}', '$.k1'), 'ARRAY<MAP<STRING, STRING>>'), x -> x['k4'] = 'v')[0]['k2'], '$.k3')\"\"\",\n                },\n            )\n\n        self.validate_all(\n            \"SELECT CAST(JSON '[1,23,456]' AS ARRAY(INTEGER))\",\n            write={\n                \"spark\": \"SELECT FROM_JSON('[1,23,456]', 'ARRAY<INT>')\",\n                \"presto\": \"SELECT CAST(JSON_PARSE('[1,23,456]') AS ARRAY(INTEGER))\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT CAST(JSON '{\"k1\":1,\"k2\":23,\"k3\":456}' AS MAP(VARCHAR, INTEGER))\"\"\",\n            write={\n                \"spark\": 'SELECT FROM_JSON(\\'{\"k1\":1,\"k2\":23,\"k3\":456}\\', \\'MAP<STRING, INT>\\')',\n                \"presto\": 'SELECT CAST(JSON_PARSE(\\'{\"k1\":1,\"k2\":23,\"k3\":456}\\') AS MAP(VARCHAR, INTEGER))',\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST(ARRAY [1, 23, 456] AS JSON)\",\n            write={\n                \"spark\": \"SELECT TO_JSON(ARRAY(1, 23, 456))\",\n                \"presto\": \"SELECT CAST(ARRAY[1, 23, 456] AS JSON)\",\n            },\n        )\n\n    def test_match_recognize(self):\n        self.validate_identity(\n            \"\"\"SELECT\n  *\nFROM orders\nMATCH_RECOGNIZE (\n  PARTITION BY custkey\n  ORDER BY\n    orderdate\n  MEASURES\n    A.totalprice AS starting_price,\n    LAST(B.totalprice) AS bottom_price,\n    LAST(C.totalprice) AS top_price\n  ONE ROW PER MATCH\n  AFTER MATCH SKIP PAST LAST ROW\n  PATTERN (A B+ C+ D+)\n  DEFINE\n    B AS totalprice < PREV(totalprice),\n    C AS totalprice > PREV(totalprice) AND totalprice <= A.totalprice,\n    D AS totalprice > PREV(totalprice),\n    E AS MAX(foo) >= NEXT(bar)\n)\"\"\",\n            pretty=True,\n        )\n\n    def test_to_char(self):\n        self.validate_all(\n            \"TO_CHAR(ts, 'dd')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%d', ts)\",\n                \"presto\": \"DATE_FORMAT(ts, '%d')\",\n            },\n        )\n        self.validate_all(\n            \"TO_CHAR(ts, 'hh')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%H', ts)\",\n                \"presto\": \"DATE_FORMAT(ts, '%H')\",\n            },\n        )\n        self.validate_all(\n            \"TO_CHAR(ts, 'hh24')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%H', ts)\",\n                \"presto\": \"DATE_FORMAT(ts, '%H')\",\n            },\n        )\n        self.validate_all(\n            \"TO_CHAR(ts, 'mi')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%M', ts)\",\n                \"presto\": \"DATE_FORMAT(ts, '%i')\",\n            },\n        )\n        self.validate_all(\n            \"TO_CHAR(ts, 'mm')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%m', ts)\",\n                \"presto\": \"DATE_FORMAT(ts, '%m')\",\n            },\n        )\n        self.validate_all(\n            \"TO_CHAR(ts, 'ss')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%S', ts)\",\n                \"presto\": \"DATE_FORMAT(ts, '%s')\",\n            },\n        )\n        self.validate_all(\n            \"TO_CHAR(ts, 'yyyy')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%Y', ts)\",\n                \"presto\": \"DATE_FORMAT(ts, '%Y')\",\n            },\n        )\n        self.validate_all(\n            \"TO_CHAR(ts, 'yy')\",\n            write={\n                \"bigquery\": \"FORMAT_DATE('%y', ts)\",\n                \"presto\": \"DATE_FORMAT(ts, '%y')\",\n            },\n        )\n\n    def test_signum(self):\n        self.validate_all(\n            \"SIGN(x)\",\n            read={\n                \"presto\": \"SIGN(x)\",\n                \"spark\": \"SIGNUM(x)\",\n                \"starrocks\": \"SIGN(x)\",\n            },\n            write={\n                \"presto\": \"SIGN(x)\",\n                \"spark\": \"SIGN(x)\",\n                \"starrocks\": \"SIGN(x)\",\n            },\n        )\n\n    def test_json_vs_row_extract(self):\n        for dialect in (\"trino\", \"presto\"):\n            s = parse_one('SELECT col:x:y.\"special string\"', read=\"snowflake\")\n\n            dialect_json_extract_setting = f\"{dialect}, variant_extract_is_json_extract=True\"\n            dialect_row_access_setting = f\"{dialect}, variant_extract_is_json_extract=False\"\n\n            # By default, Snowflake VARIANT will generate JSON_EXTRACT() in Presto/Trino\n            json_extract_result = \"\"\"SELECT JSON_EXTRACT(col, '$.x.y[\"special string\"]')\"\"\"\n            self.assertEqual(s.sql(dialect), json_extract_result)\n            self.assertEqual(s.sql(dialect_json_extract_setting), json_extract_result)\n\n            # If the setting is overriden to False, then generate ROW access (dot notation)\n            self.assertEqual(s.sql(dialect_row_access_setting), 'SELECT col.x.y.\"special string\"')\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE tbl\")\n        self.validate_identity(\"ANALYZE tbl WITH (prop1=val1, prop2=val2)\")\n\n    def test_bit_aggs(self):\n        self.validate_all(\n            \"BITWISE_AND_AGG(x)\",\n            read={\n                \"presto\": \"BITWISE_AND_AGG(x)\",\n                \"trino\": \"BITWISE_AND_AGG(x)\",\n                \"oracle\": \"BITWISE_AND_AGG(x)\",\n            },\n        )\n        self.validate_all(\n            \"BITWISE_OR_AGG(x)\",\n            read={\n                \"presto\": \"BITWISE_OR_AGG(x)\",\n                \"trino\": \"BITWISE_OR_AGG(x)\",\n                \"oracle\": \"BITWISE_OR_AGG(x)\",\n            },\n        )\n        self.validate_all(\n            \"BITWISE_XOR_AGG(x)\",\n            read={\n                \"presto\": \"BITWISE_XOR_AGG(x)\",\n                \"trino\": \"BITWISE_XOR_AGG(x)\",\n                \"oracle\": \"BITWISE_XOR_AGG(x)\",\n            },\n        )\n\n    def test_initcap(self):\n        self.validate_all(\n            \"INITCAP(col)\",\n            write={\n                \"presto\": \"REGEXP_REPLACE(col, '(\\\\w)(\\\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))\",\n            },\n        )\n"
  },
  {
    "path": "tests/dialects/test_prql.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestPRQL(Validator):\n    dialect = \"prql\"\n\n    def test_prql(self):\n        self.validate_all(\n            \"from x\",\n            write={\n                \"\": \"SELECT * FROM x\",\n            },\n        )\n        self.validate_all(\n            \"from x derive a + 1\",\n            write={\n                \"\": \"SELECT *, a + 1 FROM x\",\n            },\n        )\n        self.validate_all(\n            \"from x derive x = a + 1\",\n            write={\n                \"\": \"SELECT *, a + 1 AS x FROM x\",\n            },\n        )\n        self.validate_all(\n            \"from x derive {a + 1}\",\n            write={\n                \"\": \"SELECT *, a + 1 FROM x\",\n            },\n        )\n        self.validate_all(\n            \"from x derive {x = a + 1, b}\",\n            write={\n                \"\": \"SELECT *, a + 1 AS x, b FROM x\",\n            },\n        )\n        self.validate_all(\n            \"from x derive {x = a + 1, b} select {y = x, 2}\",\n            write={\"\": \"SELECT a + 1 AS y, 2 FROM x\"},\n        )\n        self.validate_all(\n            \"from x take 10\",\n            write={\n                \"\": \"SELECT * FROM x LIMIT 10\",\n            },\n        )\n        self.validate_all(\n            \"from x take 10 take 5\",\n            write={\n                \"\": \"SELECT * FROM x LIMIT 5\",\n            },\n        )\n        self.validate_all(\n            \"from x filter age > 25\",\n            write={\n                \"\": \"SELECT * FROM x WHERE age > 25\",\n            },\n        )\n        self.validate_all(\n            \"from x derive {x = a + 1, b} filter age > 25\",\n            write={\n                \"\": \"SELECT *, a + 1 AS x, b FROM x WHERE age > 25\",\n            },\n        )\n        self.validate_all(\n            \"from x filter dept != 'IT'\",\n            write={\n                \"\": \"SELECT * FROM x WHERE dept <> 'IT'\",\n            },\n        )\n        self.validate_all(\n            \"from x filter p == 'product' select { a, b }\",\n            write={\"\": \"SELECT a, b FROM x WHERE p = 'product'\"},\n        )\n        self.validate_all(\n            \"from x filter age > 25 filter age < 27\",\n            write={\"\": \"SELECT * FROM x WHERE age > 25 AND age < 27\"},\n        )\n        self.validate_all(\n            \"from x filter (age > 25 && age < 27)\",\n            write={\"\": \"SELECT * FROM x WHERE (age > 25 AND age < 27)\"},\n        )\n        self.validate_all(\n            \"from x filter (age > 25 || age < 27)\",\n            write={\"\": \"SELECT * FROM x WHERE (age > 25 OR age < 27)\"},\n        )\n        self.validate_all(\n            \"from x filter (age > 25 || age < 22) filter age > 26 filter age < 27\",\n            write={\n                \"\": \"SELECT * FROM x WHERE ((age > 25 OR age < 22) AND age > 26) AND age < 27\",\n            },\n        )\n        self.validate_all(\n            \"from x sort age\",\n            write={\n                \"\": \"SELECT * FROM x ORDER BY age\",\n            },\n        )\n        self.validate_all(\n            \"from x sort {-age}\",\n            write={\n                \"\": \"SELECT * FROM x ORDER BY age DESC\",\n            },\n        )\n        self.validate_all(\n            \"from x sort {age, name}\",\n            write={\n                \"\": \"SELECT * FROM x ORDER BY age, name\",\n            },\n        )\n        self.validate_all(\n            \"from x sort {-age, +name}\",\n            write={\n                \"\": \"SELECT * FROM x ORDER BY age DESC, name\",\n            },\n        )\n        self.validate_all(\n            \"from x append y\",\n            write={\n                \"\": \"SELECT * FROM x UNION ALL SELECT * FROM y\",\n            },\n        )\n        self.validate_all(\n            \"from x remove y\",\n            write={\n                \"\": \"SELECT * FROM x EXCEPT ALL SELECT * FROM y\",\n            },\n        )\n        self.validate_all(\n            \"from x intersect y\",\n            write={\"\": \"SELECT * FROM x INTERSECT ALL SELECT * FROM y\"},\n        )\n        self.validate_all(\n            \"from x filter a == null filter null != b\",\n            write={\n                \"\": \"SELECT * FROM x WHERE a IS NULL AND NOT b IS NULL\",\n            },\n        )\n        self.validate_all(\n            \"from x filter (a > 1 || null != b || c != null)\",\n            write={\n                \"\": \"SELECT * FROM x WHERE (a > 1 OR NOT b IS NULL OR NOT c IS NULL)\",\n            },\n        )\n        self.validate_all(\n            \"from a aggregate { average x }\",\n            write={\n                \"\": \"SELECT AVG(x) FROM a\",\n            },\n        )\n        self.validate_all(\n            \"from a aggregate { average x, min y, ct = sum z }\",\n            write={\n                \"\": \"SELECT AVG(x), MIN(y), COALESCE(SUM(z), 0) AS ct FROM a\",\n            },\n        )\n        self.validate_all(\n            \"from a aggregate { average x, min y, sum z }\",\n            write={\n                \"\": \"SELECT AVG(x), MIN(y), COALESCE(SUM(z), 0) FROM a\",\n            },\n        )\n        self.validate_all(\n            \"from a aggregate { min y, b = stddev x, max z }\",\n            write={\n                \"\": \"SELECT MIN(y), STDDEV(x) AS b, MAX(z) FROM a\",\n            },\n        )\n"
  },
  {
    "path": "tests/dialects/test_redshift.py",
    "content": "from sqlglot import exp, ParseError, parse_one, transpile\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestRedshift(Validator):\n    dialect = \"redshift\"\n\n    def test_redshift(self):\n        self.validate_identity(\"SELECT COSH(1.5)\")\n        self.validate_identity(\n            \"ROUND(CAST(a AS DOUBLE PRECISION) / CAST(b AS DOUBLE PRECISION), 2)\"\n        )\n        self.validate_all(\n            \"SELECT SPLIT_TO_ARRAY('12,345,6789')\",\n            write={\n                \"postgres\": \"SELECT STRING_TO_ARRAY('12,345,6789', ',')\",\n                \"redshift\": \"SELECT SPLIT_TO_ARRAY('12,345,6789', ',')\",\n            },\n        )\n        self.validate_all(\n            \"GETDATE()\",\n            read={\n                \"duckdb\": \"CURRENT_TIMESTAMP\",\n            },\n            write={\n                \"duckdb\": \"CURRENT_TIMESTAMP\",\n                \"redshift\": \"GETDATE()\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT JSON_EXTRACT_PATH_TEXT('{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}', 'farm', 'barn', 'color')\"\"\",\n            write={\n                \"bigquery\": \"\"\"SELECT JSON_EXTRACT_SCALAR('{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}', '$.farm.barn.color')\"\"\",\n                \"databricks\": \"\"\"SELECT '{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}':farm.barn.color\"\"\",\n                \"duckdb\": \"\"\"SELECT '{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}' ->> '$.farm.barn.color'\"\"\",\n                \"postgres\": \"\"\"SELECT JSON_EXTRACT_PATH_TEXT('{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}', 'farm', 'barn', 'color')\"\"\",\n                \"presto\": \"\"\"SELECT JSON_EXTRACT_SCALAR('{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}', '$.farm.barn.color')\"\"\",\n                \"redshift\": \"\"\"SELECT JSON_EXTRACT_PATH_TEXT('{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}', 'farm', 'barn', 'color')\"\"\",\n                \"spark\": \"\"\"SELECT GET_JSON_OBJECT('{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}', '$.farm.barn.color')\"\"\",\n                \"sqlite\": \"\"\"SELECT '{ \"farm\": {\"barn\": { \"color\": \"red\", \"feed stocked\": true }}}' ->> '$.farm.barn.color'\"\"\",\n            },\n        )\n        self.validate_all(\n            \"LISTAGG(sellerid, ', ')\",\n            read={\n                \"duckdb\": \"STRING_AGG(sellerid, ', ')\",\n                \"databricks\": \"STRING_AGG(sellerid, ', ')\",\n            },\n            write={\n                # GROUP_CONCAT, LISTAGG and STRING_AGG are aliases in DuckDB\n                \"duckdb\": \"LISTAGG(sellerid, ', ')\",\n                \"redshift\": \"LISTAGG(sellerid, ', ')\",\n                \"spark, version=3.0.0\": \"ARRAY_JOIN(COLLECT_LIST(sellerid), ', ')\",\n                \"spark, version=4.0.0\": \"LISTAGG(sellerid, ', ')\",\n                \"spark\": \"LISTAGG(sellerid, ', ')\",\n                \"databricks\": \"LISTAGG(sellerid, ', ')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT APPROXIMATE COUNT(DISTINCT y)\",\n            read={\n                \"spark\": \"SELECT APPROX_COUNT_DISTINCT(y)\",\n            },\n            write={\n                \"redshift\": \"SELECT APPROXIMATE COUNT(DISTINCT y)\",\n                \"spark\": \"SELECT APPROX_COUNT_DISTINCT(y)\",\n            },\n        )\n        self.validate_all(\n            \"x ~* 'pat'\",\n            write={\n                \"redshift\": \"x ~* 'pat'\",\n                \"snowflake\": \"REGEXP_LIKE(x, 'pat', 'i')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('01:03:05.124' AS TIME(2) WITH TIME ZONE)\",\n            read={\n                \"postgres\": \"SELECT CAST('01:03:05.124' AS TIMETZ(2))\",\n            },\n            write={\n                \"postgres\": \"SELECT CAST('01:03:05.124' AS TIMETZ(2))\",\n                \"redshift\": \"SELECT CAST('01:03:05.124' AS TIME(2) WITH TIME ZONE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST('2020-02-02 01:03:05.124' AS TIMESTAMP(2) WITH TIME ZONE)\",\n            read={\n                \"postgres\": \"SELECT CAST('2020-02-02 01:03:05.124' AS TIMESTAMPTZ(2))\",\n            },\n            write={\n                \"postgres\": \"SELECT CAST('2020-02-02 01:03:05.124' AS TIMESTAMPTZ(2))\",\n                \"redshift\": \"SELECT CAST('2020-02-02 01:03:05.124' AS TIMESTAMP(2) WITH TIME ZONE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT INTERVAL '5 DAYS'\",\n            read={\n                \"\": \"SELECT INTERVAL '5' days\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2008-03-31', 1)\",\n            write={\n                \"bigquery\": \"SELECT DATE_ADD(CAST('2008-03-31' AS DATETIME), INTERVAL 1 MONTH)\",\n                \"duckdb\": \"SELECT CAST('2008-03-31' AS TIMESTAMP) + INTERVAL 1 MONTH\",\n                \"redshift\": \"SELECT DATEADD(MONTH, 1, '2008-03-31')\",\n                \"trino\": \"SELECT DATE_ADD('MONTH', 1, CAST('2008-03-31' AS TIMESTAMP))\",\n                \"tsql\": \"SELECT DATEADD(MONTH, 1, CAST('2008-03-31' AS DATETIME2))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT STRTOL('abc', 16)\",\n            read={\n                \"trino\": \"SELECT FROM_BASE('abc', 16)\",\n            },\n            write={\n                \"redshift\": \"SELECT STRTOL('abc', 16)\",\n                \"trino\": \"SELECT FROM_BASE('abc', 16)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SNAPSHOT, type\",\n            write={\n                \"\": \"SELECT SNAPSHOT, type\",\n                \"redshift\": 'SELECT \"SNAPSHOT\", \"type\"',\n            },\n        )\n\n        self.validate_all(\n            \"x is true\",\n            write={\n                \"redshift\": \"x IS TRUE\",\n                \"presto\": \"x\",\n            },\n        )\n        self.validate_all(\n            \"x is false\",\n            write={\n                \"redshift\": \"x IS FALSE\",\n                \"presto\": \"NOT x\",\n            },\n        )\n        self.validate_all(\n            \"x is not false\",\n            write={\n                \"redshift\": \"NOT x IS FALSE\",\n                \"presto\": \"NOT NOT x\",\n            },\n        )\n        self.validate_all(\n            \"LEN(x)\",\n            write={\n                \"redshift\": \"LENGTH(x)\",\n                \"presto\": \"LENGTH(x)\",\n            },\n        )\n        self.validate_all(\n            \"x LIKE 'abc' || '%'\",\n            read={\n                \"duckdb\": \"STARTS_WITH(x, 'abc')\",\n            },\n            write={\n                \"redshift\": \"x LIKE 'abc' || '%'\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT SYSDATE\",\n            write={\n                \"\": \"SELECT CURRENT_TIMESTAMP()\",\n                \"postgres\": \"SELECT CURRENT_TIMESTAMP\",\n                \"redshift\": \"SELECT SYSDATE\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART(minute, timestamp '2023-01-04 04:05:06.789')\",\n            write={\n                \"postgres\": \"SELECT EXTRACT(minute FROM CAST('2023-01-04 04:05:06.789' AS TIMESTAMP))\",\n                \"redshift\": \"SELECT EXTRACT(minute FROM CAST('2023-01-04 04:05:06.789' AS TIMESTAMP))\",\n                \"snowflake\": \"SELECT DATE_PART(minute, CAST('2023-01-04 04:05:06.789' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART(month, date '20220502')\",\n            write={\n                \"postgres\": \"SELECT EXTRACT(month FROM CAST('20220502' AS DATE))\",\n                \"redshift\": \"SELECT EXTRACT(month FROM CAST('20220502' AS DATE))\",\n                \"snowflake\": \"SELECT DATE_PART(month, CAST('20220502' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            'create table \"group\" (\"col\" char(10))',\n            write={\n                \"redshift\": 'CREATE TABLE \"group\" (\"col\" CHAR(10))',\n                \"mysql\": \"CREATE TABLE `group` (`col` CHAR(10))\",\n            },\n        )\n        self.validate_all(\n            'create table if not exists city_slash_id(\"city/id\" integer not null, state char(2) not null)',\n            write={\n                \"redshift\": 'CREATE TABLE IF NOT EXISTS city_slash_id (\"city/id\" INTEGER NOT NULL, state CHAR(2) NOT NULL)',\n                \"presto\": 'CREATE TABLE IF NOT EXISTS city_slash_id (\"city/id\" INTEGER NOT NULL, state CHAR(2) NOT NULL)',\n            },\n        )\n        self.validate_all(\n            \"SELECT ST_AsEWKT(ST_GeomFromEWKT('SRID=4326;POINT(10 20)')::geography)\",\n            write={\n                \"redshift\": \"SELECT ST_ASEWKT(CAST(ST_GEOMFROMEWKT('SRID=4326;POINT(10 20)') AS GEOGRAPHY))\",\n                \"bigquery\": \"SELECT ST_AsEWKT(CAST(ST_GeomFromEWKT('SRID=4326;POINT(10 20)') AS GEOGRAPHY))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ST_AsEWKT(ST_GeogFromText('LINESTRING(110 40, 2 3, -10 80, -7 9)')::geometry)\",\n            write={\n                \"redshift\": \"SELECT ST_ASEWKT(CAST(ST_GEOGFROMTEXT('LINESTRING(110 40, 2 3, -10 80, -7 9)') AS GEOMETRY))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT 'abc'::BINARY\",\n            write={\n                \"redshift\": \"SELECT CAST('abc' AS VARBYTE)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE a (b BINARY VARYING(10))\",\n            write={\n                \"redshift\": \"CREATE TABLE a (b VARBYTE(10))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT 'abc'::CHARACTER\",\n            write={\n                \"redshift\": \"SELECT CAST('abc' AS CHAR)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DISTINCT ON (a) a, b FROM x ORDER BY c DESC\",\n            write={\n                \"bigquery\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"databricks\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"drill\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"hive\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"mysql\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY CASE WHEN c IS NULL THEN 1 ELSE 0 END DESC, c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"oracle\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x) _t WHERE _row_number = 1\",\n                \"presto\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"redshift\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"snowflake\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"spark\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"sqlite\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"starrocks\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY CASE WHEN c IS NULL THEN 1 ELSE 0 END DESC, c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"tableau\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"teradata\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"trino\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC NULLS FIRST) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n                \"tsql\": \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY CASE WHEN c IS NULL THEN 1 ELSE 0 END DESC, c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n            },\n        )\n        self.validate_all(\n            \"DECODE(x, a, b, c, d)\",\n            write={\n                \"\": \"DECODE(x, a, b, c, d)\",\n                \"duckdb\": \"CASE WHEN x = a OR (x IS NULL AND a IS NULL) THEN b WHEN x = c OR (x IS NULL AND c IS NULL) THEN d END\",\n                \"oracle\": \"DECODE(x, a, b, c, d)\",\n                \"redshift\": \"DECODE(x, a, b, c, d)\",\n                \"snowflake\": \"DECODE(x, a, b, c, d)\",\n                \"spark\": \"DECODE(x, a, b, c, d)\",\n            },\n        )\n        self.validate_all(\n            \"NVL(a, b, c, d)\",\n            write={\n                \"redshift\": \"COALESCE(a, b, c, d)\",\n                \"mysql\": \"COALESCE(a, b, c, d)\",\n                \"postgres\": \"COALESCE(a, b, c, d)\",\n            },\n        )\n\n        self.validate_identity(\n            \"DATEDIFF(days, a, b)\",\n            \"DATEDIFF(DAY, a, b)\",\n        )\n\n        self.validate_all(\n            \"DATEDIFF('day', a, b)\",\n            write={\n                \"bigquery\": \"DATE_DIFF(CAST(b AS DATETIME), CAST(a AS DATETIME), DAY)\",\n                \"duckdb\": \"DATE_DIFF('DAY', CAST(a AS TIMESTAMP), CAST(b AS TIMESTAMP))\",\n                \"hive\": \"DATEDIFF(b, a)\",\n                \"redshift\": \"DATEDIFF(DAY, a, b)\",\n                \"presto\": \"DATE_DIFF('DAY', CAST(a AS TIMESTAMP), CAST(b AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEADD(month, 18, '2008-02-28')\",\n            write={\n                \"bigquery\": \"SELECT DATE_ADD(CAST('2008-02-28' AS DATETIME), INTERVAL 18 MONTH)\",\n                \"duckdb\": \"SELECT CAST('2008-02-28' AS TIMESTAMP) + INTERVAL 18 MONTH\",\n                \"hive\": \"SELECT ADD_MONTHS('2008-02-28', 18)\",\n                \"mysql\": \"SELECT DATE_ADD('2008-02-28', INTERVAL 18 MONTH)\",\n                \"postgres\": \"SELECT CAST('2008-02-28' AS TIMESTAMP) + INTERVAL '18 MONTH'\",\n                \"presto\": \"SELECT DATE_ADD('MONTH', 18, CAST('2008-02-28' AS TIMESTAMP))\",\n                \"redshift\": \"SELECT DATEADD(MONTH, 18, '2008-02-28')\",\n                \"snowflake\": \"SELECT DATEADD(MONTH, 18, CAST('2008-02-28' AS TIMESTAMP))\",\n                \"tsql\": \"SELECT DATEADD(MONTH, 18, CAST('2008-02-28' AS DATETIME2))\",\n                \"spark\": \"SELECT DATE_ADD(MONTH, 18, '2008-02-28')\",\n                \"spark2\": \"SELECT ADD_MONTHS('2008-02-28', 18)\",\n                \"databricks\": \"SELECT DATE_ADD(MONTH, 18, '2008-02-28')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(week, '2009-01-01', '2009-12-31')\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF(CAST('2009-12-31' AS DATETIME), CAST('2009-01-01' AS DATETIME), WEEK)\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', CAST('2009-01-01' AS TIMESTAMP), CAST('2009-12-31' AS TIMESTAMP))\",\n                \"hive\": \"SELECT CAST(DATEDIFF('2009-12-31', '2009-01-01') / 7 AS INT)\",\n                \"postgres\": \"SELECT CAST(EXTRACT(days FROM (CAST('2009-12-31' AS TIMESTAMP) - CAST('2009-01-01' AS TIMESTAMP))) / 7 AS BIGINT)\",\n                \"presto\": \"SELECT DATE_DIFF('WEEK', CAST('2009-01-01' AS TIMESTAMP), CAST('2009-12-31' AS TIMESTAMP))\",\n                \"redshift\": \"SELECT DATEDIFF(WEEK, '2009-01-01', '2009-12-31')\",\n                \"snowflake\": \"SELECT DATEDIFF(WEEK, '2009-01-01', '2009-12-31')\",\n                \"tsql\": \"SELECT DATEDIFF(WEEK, '2009-01-01', '2009-12-31')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT EXTRACT(EPOCH FROM CURRENT_DATE)\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(EPOCH, CURRENT_DATE)\",\n                \"redshift\": \"SELECT EXTRACT(EPOCH FROM CURRENT_DATE)\",\n            },\n        )\n\n        self.validate_identity(\"SELECT VERSION()\")\n\n    def test_identity(self):\n        self.validate_identity(\"SELECT GETBIT(FROM_HEX('4d'), 2)\")\n        self.validate_identity(\"SELECT EXP(1)\")\n        self.validate_identity(\"ALTER TABLE table_name ALTER COLUMN bla TYPE VARCHAR\")\n        self.validate_identity(\"SELECT CAST(value AS FLOAT(8))\")\n        self.validate_identity(\"1 div\", \"1 AS div\")\n        self.validate_identity(\"LISTAGG(DISTINCT foo, ', ')\")\n        self.validate_identity(\"CREATE MATERIALIZED VIEW orders AUTO REFRESH YES AS SELECT 1\")\n        self.validate_identity(\"SELECT DATEADD(DAY, 1, 'today')\")\n        self.validate_identity(\"SELECT * FROM #x\")\n        self.validate_identity(\"SELECT INTERVAL '5 DAY'\")\n        self.validate_identity(\"foo$\")\n        self.validate_identity(\"CAST('bla' AS SUPER)\")\n        self.validate_identity(\"CREATE TABLE real1 (realcol REAL)\")\n        self.validate_identity(\"CAST('foo' AS HLLSKETCH)\")\n        self.validate_identity(\"'abc' SIMILAR TO '(b|c)%'\")\n        self.validate_identity(\"CREATE TABLE datetable (start_date DATE, end_date DATE)\")\n        self.validate_identity(\"SELECT APPROXIMATE AS y\")\n        self.validate_identity(\"CREATE TABLE t (c BIGINT IDENTITY(0, 1))\")\n        self.validate_identity(\n            \"COPY test_staging_tbl FROM 's3://your/bucket/prefix/here' IAM_ROLE default FORMAT AS AVRO 'auto'\"\n        )\n        self.validate_identity(\n            \"COPY test_staging_tbl FROM 's3://your/bucket/prefix/here' IAM_ROLE default FORMAT AS JSON 's3://jsonpaths_file'\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM venue WHERE (venuecity, venuestate) IN (('Miami', 'FL'), ('Tampa', 'FL')) ORDER BY venueid\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT tablename, \"column\" FROM pg_table_def WHERE \"column\" LIKE '%start\\\\\\\\_%' LIMIT 5\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT JSON_EXTRACT_PATH_TEXT('{\"f2\":{\"f3\":1},\"f4\":{\"f5\":99,\"f6\":\"star\"}', 'f4', 'f6', TRUE)\"\"\"\n        )\n        self.validate_identity(\n            'DATE_PART(year, \"somecol\")',\n            'EXTRACT(year FROM \"somecol\")',\n        ).this.assert_is(exp.Var)\n        self.validate_identity(\n            \"SELECT CONCAT('abc', 'def')\",\n            \"SELECT 'abc' || 'def'\",\n        )\n        self.validate_identity(\n            \"SELECT CONCAT_WS('DELIM', 'abc', 'def', 'ghi')\",\n            \"SELECT 'abc' || 'DELIM' || 'def' || 'DELIM' || 'ghi'\",\n        )\n        self.validate_identity(\n            \"SELECT TOP 1 x FROM y\",\n            \"SELECT x FROM y LIMIT 1\",\n        )\n        self.validate_identity(\n            \"SELECT DATE_DIFF('month', CAST('2020-02-29 00:00:00' AS TIMESTAMP), CAST('2020-03-02 00:00:00' AS TIMESTAMP))\",\n            \"SELECT DATEDIFF(MONTH, CAST('2020-02-29 00:00:00' AS TIMESTAMP), CAST('2020-03-02 00:00:00' AS TIMESTAMP))\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM x WHERE y = DATEADD('month', -1, DATE_TRUNC('month', (SELECT y FROM #temp_table)))\",\n            \"SELECT * FROM x WHERE y = DATEADD(MONTH, -1, DATE_TRUNC('MONTH', (SELECT y FROM #temp_table)))\",\n        )\n        self.validate_identity(\n            \"SELECT 'a''b'\",\n            \"SELECT 'a\\\\'b'\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE t (c BIGINT GENERATED BY DEFAULT AS IDENTITY (0, 1))\",\n            \"CREATE TABLE t (c BIGINT IDENTITY(0, 1))\",\n        )\n        self.validate_identity(\n            \"SELECT DATEADD(HOUR, 0, CAST('2020-02-02 01:03:05.124' AS TIMESTAMP))\"\n        )\n        self.validate_identity(\n            \"SELECT DATEDIFF(SECOND, '2020-02-02 00:00:00.000', '2020-02-02 01:03:05.124')\"\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE VIEW v1 AS SELECT id, AVG(average_metric1) AS m1, AVG(average_metric2) AS m2 FROM t GROUP BY id WITH NO SCHEMA BINDING\"\n        )\n        self.validate_identity(\n            \"SELECT caldate + INTERVAL '1 SECOND' AS dateplus FROM date WHERE caldate = '12-31-2008'\"\n        )\n        self.validate_identity(\n            \"SELECT COUNT(*) FROM event WHERE eventname LIKE '%Ring%' OR eventname LIKE '%Die%'\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE SOUP (LIKE other_table) DISTKEY(soup1) SORTKEY(soup2) DISTSTYLE ALL\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE sales (salesid INTEGER NOT NULL) DISTKEY(listid) COMPOUND SORTKEY(listid, sellerid) DISTSTYLE AUTO\"\n        )\n        self.validate_identity(\n            \"COPY customer FROM 's3://mybucket/customer' IAM_ROLE 'arn:aws:iam::0123456789012:role/MyRedshiftRole' REGION 'us-east-1' FORMAT orc\",\n        )\n        self.validate_identity(\n            \"COPY customer FROM 's3://mybucket/mydata' CREDENTIALS 'aws_iam_role=arn:aws:iam::<aws-account-id>:role/<role-name>;master_symmetric_key=<root-key>' emptyasnull blanksasnull timeformat 'YYYY-MM-DD HH:MI:SS'\"\n        )\n        self.validate_identity(\n            \"UNLOAD ('select * from venue') TO 's3://mybucket/unload/' IAM_ROLE 'arn:aws:iam::0123456789012:role/MyRedshiftRole'\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"CREATE TABLE SOUP (SOUP1 VARCHAR(50) NOT NULL ENCODE ZSTD, SOUP2 VARCHAR(70) NULL ENCODE DELTA)\"\n        )\n        self.validate_identity(\n            \"SELECT DATEADD('day', ndays, caldate)\",\n            \"SELECT DATEADD(DAY, ndays, caldate)\",\n        )\n        self.validate_identity(\n            \"CONVERT(INT, x)\",\n            \"CAST(x AS INTEGER)\",\n        )\n        self.validate_identity(\n            \"SELECT DATE_ADD('day', 1, DATE('2023-01-01'))\",\n            \"SELECT DATEADD(DAY, 1, DATE('2023-01-01'))\",\n        )\n\n        self.validate_identity(\n            \"\"\"SELECT\n  c_name,\n  orders.o_orderkey AS orderkey,\n  index AS orderkey_index\nFROM customer_orders_lineitem AS c, c.c_orders AS orders AT index\nORDER BY\n  orderkey_index\"\"\",\n            pretty=True,\n        )\n        self.validate_identity(\n            \"SELECT attr AS attr, JSON_TYPEOF(val) AS value_type FROM customer_orders_lineitem AS c, UNPIVOT c.c_orders[0] WHERE c_custkey = 9451\"\n        )\n        self.validate_identity(\n            \"SELECT attr AS attr, JSON_TYPEOF(val) AS value_type FROM customer_orders_lineitem AS c, UNPIVOT c.c_orders AS val AT attr WHERE c_custkey = 9451\"\n        )\n        self.validate_identity(\"SELECT JSON_PARSE('[]')\")\n\n        self.validate_identity(\"SELECT ARRAY(1, 2, 3)\")\n        self.validate_identity(\"SELECT ARRAY[1, 2, 3]\")\n\n        self.validate_identity(\n            \"\"\"SELECT CONVERT_TIMEZONE('America/New_York', '2024-08-06 09:10:00.000')\"\"\",\n            \"\"\"SELECT CONVERT_TIMEZONE('UTC', 'America/New_York', '2024-08-06 09:10:00.000')\"\"\",\n        )\n\n        self.validate_all(\n            \"SELECT *, 4 AS col4 EXCLUDE (col2, col3) FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3)\",\n            write={\n                \"redshift\": \"SELECT *, 4 AS col4 EXCLUDE (col2, col3) FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3)\",\n                \"duckdb\": \"SELECT * EXCLUDE (col2, col3) FROM (SELECT *, 4 AS col4 FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3))\",\n                \"snowflake\": \"SELECT * EXCLUDE (col2, col3) FROM (SELECT *, 4 AS col4 FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT *, 4 AS col4 EXCLUDE col2, col3 FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3)\",\n            write={\n                \"redshift\": \"SELECT *, 4 AS col4 EXCLUDE (col2, col3) FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3)\",\n                \"duckdb\": \"SELECT * EXCLUDE (col2, col3) FROM (SELECT *, 4 AS col4 FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3))\",\n                \"snowflake\": \"SELECT * EXCLUDE (col2, col3) FROM (SELECT *, 4 AS col4 FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT col1, *, col2 EXCLUDE(col3) FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3)\",\n            write={\n                \"redshift\": \"SELECT col1, *, col2 EXCLUDE (col3) FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3)\",\n                \"duckdb\": \"SELECT * EXCLUDE (col3) FROM (SELECT col1, *, col2 FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3))\",\n                \"snowflake\": \"SELECT * EXCLUDE (col3) FROM (SELECT col1, *, col2 FROM (SELECT 1 AS col1, 2 AS col2, 3 AS col3))\",\n            },\n        )\n\n        self.validate_identity(\"SELECT 1 EXCLUDE\", \"SELECT 1 AS EXCLUDE\")\n        self.validate_identity(\"SELECT 1 EXCLUDE FROM t\", \"SELECT 1 AS EXCLUDE FROM t\")\n        self.validate_identity(\"SELECT 1 AS EXCLUDE\")\n        self.validate_identity(\"SELECT * FROM (SELECT 1 AS EXCLUDE) AS t\")\n        self.validate_identity(\"SELECT 1 AS EXCLUDE, 2 AS foo\")\n\n    def test_values(self):\n        # Test crazy-sized VALUES clause to UNION ALL conversion to ensure we don't get RecursionError\n        values = [str(v) for v in range(0, 10000)]\n        values_query = f\"SELECT * FROM (VALUES {', '.join('(' + v + ')' for v in values)})\"\n        union_query = f\"SELECT * FROM ({' UNION ALL '.join('SELECT ' + v for v in values)})\"\n        self.assertEqual(transpile(values_query, write=\"redshift\")[0], union_query)\n\n        values_sql = transpile(\"SELECT * FROM (VALUES (1), (2))\", write=\"redshift\", pretty=True)[0]\n        self.assertEqual(\n            values_sql,\n            \"\"\"SELECT\n  *\nFROM (\n  SELECT\n    1\n  UNION ALL\n  SELECT\n    2\n)\"\"\",\n        )\n\n        self.validate_identity(\"INSERT INTO t (a) VALUES (1), (2), (3)\")\n        self.validate_identity(\"INSERT INTO t (a, b) VALUES (1, 2), (3, 4)\")\n\n        self.validate_all(\n            \"SELECT * FROM (SELECT 1, 2) AS t\",\n            read={\n                \"\": \"SELECT * FROM (VALUES (1, 2)) AS t\",\n            },\n            write={\n                \"mysql\": \"SELECT * FROM (SELECT 1, 2) AS t\",\n                \"presto\": \"SELECT * FROM (SELECT 1, 2) AS t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (SELECT 1 AS id) AS t1 CROSS JOIN (SELECT 1 AS id) AS t2\",\n            read={\n                \"\": \"SELECT * FROM (VALUES (1)) AS t1(id) CROSS JOIN (VALUES (1)) AS t2(id)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a, b FROM (SELECT 1 AS a, 2 AS b) AS t\",\n            read={\n                \"\": \"SELECT a, b FROM (VALUES (1, 2)) AS t (a, b)\",\n            },\n        )\n        self.validate_all(\n            'SELECT a, b FROM (SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4) AS \"t\"',\n            read={\n                \"\": 'SELECT a, b FROM (VALUES (1, 2), (3, 4)) AS \"t\" (a, b)',\n            },\n        )\n        self.validate_all(\n            \"SELECT a, b FROM (SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4 UNION ALL SELECT 5, 6 UNION ALL SELECT 7, 8) AS t\",\n            read={\n                \"\": \"SELECT a, b FROM (VALUES (1, 2), (3, 4), (5, 6), (7, 8)) AS t (a, b)\",\n            },\n        )\n        self.validate_all(\n            \"INSERT INTO t (a, b) SELECT a, b FROM (SELECT 1 AS a, 2 AS b UNION ALL SELECT 3, 4) AS t\",\n            read={\n                \"\": \"INSERT INTO t(a, b) SELECT a, b FROM (VALUES (1, 2), (3, 4)) AS t (a, b)\",\n            },\n        )\n        self.validate_identity(\"CREATE TABLE table_backup BACKUP NO AS SELECT * FROM event\")\n        self.validate_identity(\"CREATE TABLE table_backup BACKUP YES AS SELECT * FROM event\")\n        self.validate_identity(\"CREATE TABLE table_backup (i INTEGER, b VARCHAR) BACKUP NO\")\n        self.validate_identity(\"CREATE TABLE table_backup (i INTEGER, b VARCHAR) BACKUP YES\")\n        self.validate_identity(\n            \"select foo, bar from table_1 minus select foo, bar from table_2\",\n            \"SELECT foo, bar FROM table_1 EXCEPT SELECT foo, bar FROM table_2\",\n        )\n\n    def test_create_table_like(self):\n        self.validate_identity(\n            \"CREATE TABLE SOUP (LIKE other_table) DISTKEY(soup1) SORTKEY(soup2) DISTSTYLE ALL\"\n        )\n\n        self.validate_all(\n            \"CREATE TABLE t1 (LIKE t2)\",\n            write={\n                \"postgres\": \"CREATE TABLE t1 (LIKE t2)\",\n                \"presto\": \"CREATE TABLE t1 (LIKE t2)\",\n                \"redshift\": \"CREATE TABLE t1 (LIKE t2)\",\n                \"trino\": \"CREATE TABLE t1 (LIKE t2)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE t1 (col VARCHAR, LIKE t2)\",\n            write={\n                \"postgres\": \"CREATE TABLE t1 (col VARCHAR, LIKE t2)\",\n                \"presto\": \"CREATE TABLE t1 (col VARCHAR, LIKE t2)\",\n                \"redshift\": \"CREATE TABLE t1 (col VARCHAR, LIKE t2)\",\n                \"trino\": \"CREATE TABLE t1 (col VARCHAR, LIKE t2)\",\n            },\n        )\n\n    def test_alter_table(self):\n        self.validate_identity(\"ALTER TABLE s.t ALTER SORTKEY (c)\")\n        self.validate_identity(\"ALTER TABLE t ALTER SORTKEY AUTO\")\n        self.validate_identity(\"ALTER TABLE t ALTER SORTKEY NONE\")\n        self.validate_identity(\"ALTER TABLE t ALTER SORTKEY (c1, c2)\")\n        self.validate_identity(\"ALTER TABLE t ALTER SORTKEY (c1, c2)\")\n        self.validate_identity(\"ALTER TABLE t ALTER COMPOUND SORTKEY (c1, c2)\")\n        self.validate_identity(\"ALTER TABLE t ALTER DISTSTYLE ALL\")\n        self.validate_identity(\"ALTER TABLE t ALTER DISTSTYLE EVEN\")\n        self.validate_identity(\"ALTER TABLE t ALTER DISTSTYLE AUTO\")\n        self.validate_identity(\"ALTER TABLE t ALTER DISTSTYLE KEY DISTKEY c\")\n        self.validate_identity(\"ALTER TABLE t SET TABLE PROPERTIES ('a' = '5', 'b' = 'c')\")\n        self.validate_identity(\"ALTER TABLE t SET LOCATION 's3://bucket/folder/'\")\n        self.validate_identity(\"ALTER TABLE t SET FILE FORMAT AVRO\")\n        self.validate_identity(\n            \"ALTER TABLE t ALTER DISTKEY c\",\n            \"ALTER TABLE t ALTER DISTSTYLE KEY DISTKEY c\",\n        )\n\n        self.validate_all(\n            \"ALTER TABLE db.t1 RENAME TO db.t2\",\n            write={\n                \"spark\": \"ALTER TABLE db.t1 RENAME TO db.t2\",\n                \"redshift\": \"ALTER TABLE db.t1 RENAME TO t2\",\n            },\n        )\n\n    def test_varchar_max(self):\n        self.validate_all(\n            'CREATE TABLE \"TEST\" (\"cola\" VARCHAR(MAX))',\n            read={\n                \"redshift\": \"CREATE TABLE TEST (cola VARCHAR(max))\",\n                \"tsql\": \"CREATE TABLE TEST (cola VARCHAR(max))\",\n            },\n            write={\n                \"redshift\": 'CREATE TABLE \"TEST\" (\"cola\" VARCHAR(MAX))',\n            },\n            identify=True,\n        )\n\n    def test_no_schema_binding(self):\n        self.validate_all(\n            \"CREATE OR REPLACE VIEW v1 AS SELECT cola, colb FROM t1 WITH NO SCHEMA BINDING\",\n            write={\n                \"redshift\": \"CREATE OR REPLACE VIEW v1 AS SELECT cola, colb FROM t1 WITH NO SCHEMA BINDING\",\n            },\n        )\n\n    def test_column_unnesting(self):\n        self.validate_identity(\"SELECT c.*, o FROM bloo AS c, c.c_orders AS o\")\n        self.validate_identity(\n            \"SELECT c.*, o, l FROM bloo AS c, c.c_orders AS o, o.o_lineitems AS l\"\n        )\n\n        ast = parse_one(\"SELECT * FROM t.t JOIN t.c1 ON c1.c2 = t.c3\", read=\"redshift\")\n        ast.args[\"from_\"].this.assert_is(exp.Table)\n        ast.args[\"joins\"][0].this.assert_is(exp.Table)\n        self.assertEqual(ast.sql(\"redshift\"), \"SELECT * FROM t.t JOIN t.c1 ON c1.c2 = t.c3\")\n\n        ast = parse_one(\"SELECT * FROM t AS t CROSS JOIN t.c1\", read=\"redshift\")\n        ast.args[\"from_\"].this.assert_is(exp.Table)\n        ast.args[\"joins\"][0].this.assert_is(exp.Unnest)\n        self.assertEqual(ast.sql(\"redshift\"), \"SELECT * FROM t AS t CROSS JOIN t.c1\")\n\n        ast = parse_one(\n            \"SELECT * FROM x AS a, a.b AS c, c.d.e AS f, f.g.h.i.j.k AS l\", read=\"redshift\"\n        )\n        joins = ast.args[\"joins\"]\n        ast.args[\"from_\"].this.assert_is(exp.Table)\n        joins[0].this.assert_is(exp.Unnest)\n        joins[1].this.assert_is(exp.Unnest)\n        joins[2].this.assert_is(exp.Unnest).expressions[0].assert_is(exp.Dot)\n        self.assertEqual(\n            ast.sql(\"redshift\"), \"SELECT * FROM x AS a, a.b AS c, c.d.e AS f, f.g.h.i.j.k AS l\"\n        )\n\n    def test_join_markers(self):\n        self.validate_identity(\n            \"select a.foo, b.bar, a.baz from a, b where a.baz = b.baz (+)\",\n            \"SELECT a.foo, b.bar, a.baz FROM a, b WHERE a.baz = b.baz (+)\",\n        )\n\n    def test_time(self):\n        self.validate_all(\n            \"TIME_TO_STR(a, '%Y-%m-%d %H:%M:%S.%f')\",\n            write={\"redshift\": \"TO_CHAR(a, 'YYYY-MM-DD HH24:MI:SS.US')\"},\n        )\n\n    def test_grant(self):\n        grant_cmds = [\n            \"GRANT SELECT ON ALL TABLES IN SCHEMA qa_tickit TO fred\",\n            \"GRANT USAGE ON DATASHARE salesshare TO NAMESPACE '13b8833d-17c6-4f16-8fe4-1a018f5ed00d'\",\n            \"GRANT USAGE FOR SCHEMAS IN DATABASE Sales_db TO ROLE Sales\",\n            \"GRANT EXECUTE FOR FUNCTIONS IN SCHEMA Sales_schema TO bob\",\n            \"GRANT SELECT FOR TABLES IN DATABASE Sales_db TO alice WITH GRANT OPTION\",\n            \"GRANT ALL FOR TABLES IN SCHEMA ShareSchema DATABASE ShareDb TO ROLE Sales\",\n            \"GRANT ASSUMEROLE ON 'arn:aws:iam::123456789012:role/Redshift-Exfunc' TO reg_user1 FOR EXTERNAL FUNCTION\",\n            \"GRANT ROLE sample_role1 TO ROLE sample_role2\",\n        ]\n\n        for sql in grant_cmds:\n            with self.subTest(f\"Testing Redshift's GRANT command statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n        self.validate_identity(\"GRANT SELECT ON TABLE sales TO fred\")\n        self.validate_identity(\"GRANT ALL ON SCHEMA qa_tickit TO GROUP qa_users\")\n        self.validate_identity(\"GRANT ALL ON TABLE qa_tickit.sales TO GROUP qa_users\")\n        self.validate_identity(\n            \"GRANT ALL ON TABLE qa_tickit.sales TO GROUP qa_users, GROUP ro_users\"\n        )\n        self.validate_identity(\"GRANT ALL ON view_date TO view_user\")\n        self.validate_identity(\n            \"GRANT SELECT(cust_name, cust_phone), UPDATE(cust_contact_preference) ON cust_profile TO GROUP sales_group\"\n        )\n        self.validate_identity(\n            \"GRANT ALL(cust_name, cust_phone, cust_contact_preference) ON cust_profile TO GROUP sales_admin\"\n        )\n        self.validate_identity(\"GRANT USAGE ON DATABASE sales_db TO Bob\")\n        self.validate_identity(\"GRANT USAGE ON SCHEMA sales_schema TO ROLE Analyst_role\")\n        self.validate_identity(\"GRANT SELECT ON sales_db.sales_schema.tickit_sales_redshift TO Bob\")\n\n    def test_revoke(self):\n        revoke_cmds = [\n            \"REVOKE SELECT ON ALL TABLES IN SCHEMA qa_tickit FROM fred\",\n            \"REVOKE USAGE ON DATASHARE salesshare FROM NAMESPACE '13b8833d-17c6-4f16-8fe4-1a018f5ed00d'\",\n            \"REVOKE USAGE FOR SCHEMAS IN DATABASE Sales_db FROM ROLE Sales\",\n            \"REVOKE EXECUTE FOR FUNCTIONS IN SCHEMA Sales_schema FROM bob\",\n            \"REVOKE SELECT FOR TABLES IN DATABASE Sales_db FROM alice\",\n            \"REVOKE ROLE sample_role1 FROM ROLE sample_role2\",\n        ]\n\n        for sql in revoke_cmds:\n            with self.subTest(f\"Testing Redshift's REVOKE command statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n        self.validate_identity(\"REVOKE SELECT ON TABLE sales FROM fred\")\n        self.validate_identity(\"REVOKE ALL ON SCHEMA qa_tickit FROM GROUP qa_users\")\n        self.validate_identity(\"REVOKE USAGE ON DATABASE sales_db FROM Bob\")\n        self.validate_identity(\"REVOKE USAGE ON SCHEMA sales_schema FROM ROLE Analyst_role\")\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE TBL(col1, col2)\")\n        self.validate_identity(\"ANALYZE VERBOSE TBL\")\n        self.validate_identity(\"ANALYZE TBL PREDICATE COLUMNS\")\n        self.validate_identity(\"ANALYZE TBL ALL COLUMNS\")\n\n    def test_cast(self):\n        self.validate_identity('1::\"int\"', \"CAST(1 AS INTEGER)\").to.is_type(exp.DataType.Type.INT)\n\n        with self.assertRaises(ParseError):\n            parse_one('1::\"udt\"', read=\"redshift\")\n\n    def test_fetch_to_limit(self):\n        self.validate_all(\n            \"SELECT * FROM t FETCH FIRST 1 ROWS ONLY\",\n            write={\n                \"redshift\": \"SELECT * FROM t LIMIT 1\",\n                \"postgres\": \"SELECT * FROM t FETCH FIRST 1 ROWS ONLY\",\n            },\n        )\n\n    def test_to_timestamp(self):\n        # Redshift's TO_TIMESTAMP returns TIMESTAMPTZ\n        # https://docs.aws.amazon.com/redshift/latest/dg/r_TO_TIMESTAMP.html\n        expr = annotate_types(\n            parse_one(\"SELECT TO_TIMESTAMP('2023-01-01', 'YYYY-MM-DD')\", dialect=\"redshift\"),\n            dialect=\"redshift\",\n        )\n        self.assertEqual(expr.expressions[0].type.this, exp.DataType.Type.TIMESTAMPTZ)\n\n        self.validate_identity(\"SELECT LAG(x) IGNORE NULLS OVER (PARTITION BY y ORDER BY z)\")\n        self.validate_identity(\"SELECT LAG(x) RESPECT NULLS OVER (PARTITION BY y ORDER BY z)\")\n        self.validate_identity(\n            \"SELECT LAG(x IGNORE NULLS) OVER (PARTITION BY y ORDER BY z)\",\n            \"SELECT LAG(x) IGNORE NULLS OVER (PARTITION BY y ORDER BY z)\",\n        )\n        self.validate_identity(\n            \"SELECT LAG(x RESPECT NULLS) OVER (PARTITION BY y ORDER BY z)\",\n            \"SELECT LAG(x) RESPECT NULLS OVER (PARTITION BY y ORDER BY z)\",\n        )\n\n    def test_regexp_extract(self):\n        self.validate_all(\n            \"SELECT REGEXP_SUBSTR(abc, 'pattern(group)', 2) FROM table\",\n            write={\n                \"redshift\": '''SELECT REGEXP_SUBSTR(abc, 'pattern(group)', 2) FROM \"table\"''',\n                \"duckdb\": '''SELECT REGEXP_EXTRACT(SUBSTRING(abc, 2), 'pattern(group)') FROM \"table\"''',\n            },\n        )\n"
  },
  {
    "path": "tests/dialects/test_risingwave.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestRisingWave(Validator):\n    dialect = \"risingwave\"\n    maxDiff = None\n\n    def test_risingwave(self):\n        self.validate_all(\n            \"SELECT a FROM tbl\",\n            read={\n                \"\": \"SELECT a FROM tbl FOR UPDATE\",\n            },\n        )\n        self.validate_identity(\n            \"CREATE SOURCE from_kafka (*, gen_i32_field INT AS int32_field + 2, gen_i64_field INT AS int64_field + 2, WATERMARK FOR time_col AS time_col - INTERVAL '5 SECOND') INCLUDE header foo VARCHAR AS myheader INCLUDE key AS mykey WITH (connector='kafka', topic='my_topic') FORMAT PLAIN ENCODE PROTOBUF (A=1, B=2) KEY ENCODE PROTOBUF (A=3, B=4)\"\n        )\n        self.validate_identity(\n            \"CREATE SINK my_sink AS SELECT * FROM A WITH (connector='kafka', topic='my_topic') FORMAT PLAIN ENCODE PROTOBUF (A=1, B=2) KEY ENCODE PROTOBUF (A=3, B=4)\"\n        )\n        self.validate_identity(\n            \"WITH t1 AS MATERIALIZED (SELECT 1), t2 AS NOT MATERIALIZED (SELECT 2) SELECT * FROM t1, t2\"\n        )\n\n    def test_datatypes(self):\n        self.validate_identity(\"SELECT CAST(NULL AS MAP(VARCHAR, INT)) AS map_column\")\n\n        self.validate_identity(\n            \"SELECT NULL::MAP<VARCHAR, INT> AS map_column\",\n            \"SELECT CAST(NULL AS MAP(VARCHAR, INT)) AS map_column\",\n        )\n\n        self.validate_identity(\"CREATE TABLE t (map_col MAP(VARCHAR, INT))\")\n\n        self.validate_identity(\n            \"CREATE TABLE t (map_col MAP<VARCHAR, INT>)\",\n            \"CREATE TABLE t (map_col MAP(VARCHAR, INT))\",\n        )\n"
  },
  {
    "path": "tests/dialects/test_singlestore.py",
    "content": "from sqlglot import parse_one, exp\nfrom sqlglot.optimizer.qualify import qualify\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestSingleStore(Validator):\n    dialect = \"singlestore\"\n\n    def test_singlestore(self):\n        ast = parse_one(\n            \"SELECT id AS my_id FROM data WHERE my_id = 1 GROUP BY my_id HAVING my_id = 1\",\n            dialect=self.dialect,\n        )\n        ast = qualify(ast, dialect=self.dialect, schema={\"data\": {\"id\": \"INT\", \"my_id\": \"INT\"}})\n        self.assertEqual(\n            \"SELECT `data`.`id` AS `my_id` FROM `data` AS `data` WHERE `data`.`my_id` = 1 GROUP BY `data`.`my_id` HAVING `data`.`id` = 1\",\n            ast.sql(dialect=self.dialect),\n        )\n\n        self.validate_identity(\"SELECT 1\")\n        self.validate_identity(\"SELECT * FROM `users` ORDER BY ALL\")\n        self.validate_identity(\"SELECT ELT(2, 'foo', 'bar', 'baz')\")\n        self.validate_identity(\"SELECT CHARSET(CHAR(100 USING utf8))\")\n        self.validate_identity(\"SELECT TO_JSON(ROW(1, 2) :> RECORD(a INT, b INT))\")\n\n        self.validate_identity(\"JSON_KEYS(json_doc, 'a', 'b', 'c', 2)\")\n        self.validate_identity(\"SELECT VERSION()\")\n        self.validate_identity(\"SELECT CURTIME()\", \"SELECT CURRENT_TIME()\")\n\n    def test_byte_strings(self):\n        self.validate_identity(\"SELECT e'text'\")\n        self.validate_identity(\"SELECT E'text'\", \"SELECT e'text'\")\n\n    def test_national_strings(self):\n        self.validate_all(\n            \"SELECT 'text'\", read={\"\": \"SELECT N'text'\", \"singlestore\": \"SELECT 'text'\"}\n        )\n\n    def test_restricted_keywords(self):\n        self.validate_identity(\"SELECT * FROM abs\", \"SELECT * FROM `abs`\")\n        self.validate_identity(\"SELECT * FROM ABS\", \"SELECT * FROM `ABS`\")\n        self.validate_identity(\n            \"SELECT * FROM security_lists_intersect\", \"SELECT * FROM `security_lists_intersect`\"\n        )\n        self.validate_identity(\"SELECT * FROM vacuum\", \"SELECT * FROM `vacuum`\")\n\n    def test_time_formatting(self):\n        self.validate_identity(\"SELECT STR_TO_DATE('March 3rd, 2015', '%M %D, %Y')\")\n        self.validate_identity(\"SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %h:%i:%s')\")\n        self.validate_identity(\n            \"SELECT TO_DATE('03/01/2019', 'MM/DD/YYYY') AS `result`\",\n        )\n        self.validate_identity(\n            \"SELECT TO_TIMESTAMP('The date and time are 01/01/2018 2:30:15.123456', 'The date and time are MM/DD/YYYY HH12:MI:SS.FF6') AS `result`\",\n        )\n        self.validate_identity(\n            \"SELECT TO_CHAR('2018-03-01', 'MM/DD')\",\n        )\n        self.validate_identity(\n            \"SELECT TIME_FORMAT('12:05:47', '%s, %i, %h')\",\n            \"SELECT DATE_FORMAT('12:05:47' :> TIME(6), '%s, %i, %h')\",\n        )\n        self.validate_identity(\"SELECT DATE('2019-01-01 05:06')\")\n        self.validate_all(\n            \"SELECT DATE('2019-01-01 05:06')\",\n            read={\n                \"\": \"SELECT TS_OR_DS_TO_DATE('2019-01-01 05:06')\",\n                \"singlestore\": \"SELECT DATE('2019-01-01 05:06')\",\n            },\n        )\n\n    def test_cast(self):\n        self.validate_all(\n            \"SELECT 1 :> INT\",\n            read={\n                \"\": \"SELECT CAST(1 AS INT)\",\n            },\n            write={\n                \"singlestore\": \"SELECT 1 :> INT\",\n                \"\": \"SELECT CAST(1 AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT 1 !:> INT\",\n            read={\n                \"\": \"SELECT TRY_CAST(1 AS INT)\",\n            },\n            write={\n                \"singlestore\": \"SELECT 1 !:> INT\",\n                \"\": \"SELECT TRY_CAST(1 AS INT)\",\n            },\n        )\n        self.validate_identity(\"SELECT '{\\\"a\\\" : 1}' :> JSON\")\n        self.validate_identity(\"SELECT NOW() !:> TIMESTAMP(6)\")\n        self.validate_identity(\"SELECT x :> GEOGRAPHYPOINT\")\n        self.validate_all(\n            \"SELECT age :> TEXT FROM `users`\",\n            read={\n                \"\": \"SELECT CAST(age, 'TEXT') FROM users\",\n                \"singlestore\": \"SELECT age :> TEXT FROM `users`\",\n            },\n        )\n\n    def test_unix_functions(self):\n        self.validate_identity(\"SELECT FROM_UNIXTIME(1234567890)\")\n        self.validate_identity(\"SELECT FROM_UNIXTIME(1234567890, '%M %D, %Y')\")\n        self.validate_identity(\"SELECT UNIX_TIMESTAMP()\")\n        self.validate_identity(\"SELECT UNIX_TIMESTAMP('2009-02-13 23:31:30') AS funday\")\n\n        self.validate_all(\n            \"SELECT UNIX_TIMESTAMP('2009-02-13 23:31:30')\",\n            read={\"duckdb\": \"SELECT EPOCH('2009-02-13 23:31:30')\"},\n        )\n        self.validate_all(\n            \"SELECT UNIX_TIMESTAMP('2009-02-13 23:31:30')\",\n            read={\"duckdb\": \"SELECT TIME_STR_TO_UNIX('2009-02-13 23:31:30')\"},\n        )\n        self.validate_all(\n            \"SELECT UNIX_TIMESTAMP('2009-02-13 23:31:30')\",\n            read={\"\": \"SELECT TIME_STR_TO_UNIX('2009-02-13 23:31:30')\"},\n        )\n        self.validate_all(\n            \"SELECT UNIX_TIMESTAMP('2009-02-13 23:31:30')\",\n            read={\"\": \"SELECT UNIX_SECONDS('2009-02-13 23:31:30')\"},\n        )\n\n        self.validate_all(\n            \"SELECT FROM_UNIXTIME(1234567890, '%Y-%m-%d %T')\",\n            read={\"hive\": \"SELECT FROM_UNIXTIME(1234567890)\"},\n        )\n        self.validate_all(\n            \"SELECT FROM_UNIXTIME(1234567890) :> TEXT\",\n            read={\"\": \"SELECT UNIX_TO_TIME_STR(1234567890)\"},\n        )\n\n    def test_json_extract(self):\n        self.validate_identity(\"SELECT a::b FROM t\", \"SELECT JSON_EXTRACT_JSON(a, 'b') FROM t\")\n        self.validate_identity(\"SELECT a::b FROM t\", \"SELECT JSON_EXTRACT_JSON(a, 'b') FROM t\")\n        self.validate_identity(\"SELECT a::$b FROM t\", \"SELECT JSON_EXTRACT_STRING(a, 'b') FROM t\")\n        self.validate_identity(\"SELECT a::%b FROM t\", \"SELECT JSON_EXTRACT_DOUBLE(a, 'b') FROM t\")\n        self.validate_identity(\n            \"SELECT a::`b`::`2` FROM t\",\n            \"SELECT JSON_EXTRACT_JSON(JSON_EXTRACT_JSON(a, 'b'), '2') FROM t\",\n        )\n        self.validate_identity(\"SELECT a::2 FROM t\", \"SELECT JSON_EXTRACT_JSON(a, '2') FROM t\")\n\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_JSON(a, 'b') FROM t\",\n            read={\n                \"mysql\": \"SELECT JSON_EXTRACT(a, '$.b') FROM t\",\n                \"singlestore\": \"SELECT JSON_EXTRACT_JSON(a, 'b') FROM t\",\n            },\n            write={\"mysql\": \"SELECT JSON_EXTRACT(a, '$.b') FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_STRING(a, 'b') FROM t\",\n            write={\"\": \"SELECT JSON_EXTRACT_SCALAR(a, '$.b', STRING) FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_DOUBLE(a, 'b') FROM t\",\n            write={\"\": \"SELECT JSON_EXTRACT_SCALAR(a, '$.b', DOUBLE) FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_BIGINT(a, 'b') FROM t\",\n            write={\"\": \"SELECT JSON_EXTRACT_SCALAR(a, '$.b', BIGINT) FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_BIGINT(a, 'b') FROM t\",\n            write={\"\": \"SELECT JSON_EXTRACT_SCALAR(a, '$.b', BIGINT) FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_JSON(a, 'b', '2') FROM t\",\n            read={\n                \"mysql\": \"SELECT JSON_EXTRACT(a, '$.b[2]') FROM t\",\n                \"singlestore\": \"SELECT JSON_EXTRACT_JSON(a, 'b', '2') FROM t\",\n            },\n            write={\"mysql\": \"SELECT JSON_EXTRACT(a, '$.b[2]') FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT JSON_EXTRACT_STRING(a, 'b', 2) FROM t\",\n            write={\"\": \"SELECT JSON_EXTRACT_SCALAR(a, '$.b[2]', STRING) FROM t\"},\n        )\n\n        self.validate_all(\n            \"SELECT BSON_EXTRACT_BSON(a, 'b') FROM t\",\n            read={\n                \"mysql\": \"SELECT JSONB_EXTRACT(a, 'b') FROM t\",\n                \"singlestore\": \"SELECT BSON_EXTRACT_BSON(a, 'b') FROM t\",\n            },\n            write={\"mysql\": \"SELECT JSONB_EXTRACT(a, '$.b') FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT BSON_EXTRACT_STRING(a, 'b') FROM t\",\n            write={\"\": \"SELECT JSONB_EXTRACT_SCALAR(a, '$.b', STRING) FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT BSON_EXTRACT_DOUBLE(a, 'b') FROM t\",\n            write={\"\": \"SELECT JSONB_EXTRACT_SCALAR(a, '$.b', DOUBLE) FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT BSON_EXTRACT_BIGINT(a, 'b') FROM t\",\n            write={\"\": \"SELECT JSONB_EXTRACT_SCALAR(a, '$.b', BIGINT) FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT BSON_EXTRACT_BIGINT(a, 'b') FROM t\",\n            write={\"\": \"SELECT JSONB_EXTRACT_SCALAR(a, '$.b', BIGINT) FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT BSON_EXTRACT_BSON(a, 'b', 2) FROM t\",\n            write={\"\": \"SELECT JSONB_EXTRACT(a, '$.b[2]') FROM t\"},\n        )\n        self.validate_all(\n            \"SELECT BSON_EXTRACT_STRING(a, 'b', 2) FROM t\",\n            write={\"\": \"SELECT JSONB_EXTRACT_SCALAR(a, '$.b[2]', STRING) FROM t\"},\n        )\n        self.validate_all(\n            'SELECT JSON_EXTRACT_STRING(\\'{\"item\": \"shoes\", \"price\": \"49.95\"}\\', \\'price\\') :> DECIMAL(4, 2)',\n            read={\n                \"mysql\": 'SELECT JSON_VALUE(\\'{\"item\": \"shoes\", \"price\": \"49.95\"}\\', \\'$.price\\' RETURNING DECIMAL(4, 2))'\n            },\n        )\n\n    def test_json(self):\n        self.validate_identity(\"SELECT JSON_ARRAY_CONTAINS_STRING('[\\\"a\\\", \\\"b\\\"]', 'b')\")\n        self.validate_identity(\"SELECT JSON_ARRAY_CONTAINS_DOUBLE('[1, 2]', 1)\")\n        self.validate_identity('SELECT JSON_ARRAY_CONTAINS_JSON(\\'[\"{\"a\": 1}\"]\\', \\'{\"a\":   1}\\')')\n        self.validate_all(\n            \"SELECT JSON_ARRAY_CONTAINS_JSON('[\\\"a\\\"]', TO_JSON('a'))\",\n            read={\n                \"mysql\": \"SELECT 'a' MEMBER OF ('[\\\"a\\\"]')\",\n                \"singlestore\": \"SELECT JSON_ARRAY_CONTAINS_JSON('[\\\"a\\\"]', TO_JSON('a'))\",\n            },\n        )\n        self.validate_all(\n            'SELECT JSON_PRETTY(\\'[\"G\",\"alpha\",\"20\",10]\\')',\n            read={\n                \"singlestore\": 'SELECT JSON_PRETTY(\\'[\"G\",\"alpha\",\"20\",10]\\')',\n                \"\": 'SELECT JSON_FORMAT(\\'[\"G\",\"alpha\",\"20\",10]\\')',\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_AGG(name ORDER BY id ASC NULLS LAST, name DESC NULLS FIRST) FROM t\",\n            read={\n                \"singlestore\": \"SELECT JSON_AGG(name ORDER BY id ASC NULLS LAST, name DESC NULLS FIRST) FROM t\",\n                \"oracle\": \"SELECT JSON_ARRAYAGG(name ORDER BY id ASC, name DESC) FROM t\",\n            },\n        )\n        self.validate_identity(\"SELECT JSON_AGG(name) FROM t\")\n        self.validate_identity(\"SELECT JSON_AGG(t.*) FROM t\")\n        self.validate_all(\n            \"SELECT JSON_BUILD_ARRAY(id, name) FROM t\",\n            read={\n                \"singlestore\": \"SELECT JSON_BUILD_ARRAY(id, name) FROM t\",\n                \"oracle\": \"SELECT JSON_ARRAY(id, name) FROM t\",\n            },\n        )\n        self.validate_identity(\"JSON_BUILD_ARRAY(id, name)\").assert_is(exp.JSONArray)\n        self.validate_all(\n            \"SELECT BSON_MATCH_ANY_EXISTS('{\\\"x\\\":true}', 'x')\",\n            read={\n                \"singlestore\": \"SELECT BSON_MATCH_ANY_EXISTS('{\\\"x\\\":true}', 'x')\",\n                \"\": \"SELECT JSONB_EXISTS('{\\\"x\\\":true}', 'x')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_MATCH_ANY_EXISTS('{\\\"a\\\":1}', 'a')\",\n            read={\n                \"singlestore\": \"SELECT JSON_MATCH_ANY_EXISTS('{\\\"a\\\":1}', 'a')\",\n                \"oracle\": \"SELECT JSON_EXISTS('{\\\"a\\\":1}', '$.a')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_BUILD_OBJECT('name', name) FROM t\",\n            read={\n                \"singlestore\": \"SELECT JSON_BUILD_OBJECT('name', name) FROM t\",\n                \"\": \"SELECT JSON_OBJECT('name', name) FROM t\",\n            },\n        )\n        self.validate_identity(\"JSON_BUILD_OBJECT('name', name)\").assert_is(exp.JSONObject)\n\n    def test_date_parts_functions(self):\n        self.validate_identity(\n            \"SELECT DAYNAME('2014-04-18')\", \"SELECT DATE_FORMAT('2014-04-18', '%W')\"\n        )\n        self.validate_identity(\n            \"SELECT HOUR('2009-02-13 23:31:30')\",\n            \"SELECT DATE_FORMAT('2009-02-13 23:31:30' :> TIME(6), '%k') :> INT\",\n        )\n        self.validate_identity(\n            \"SELECT MICROSECOND('2009-02-13 23:31:30.123456')\",\n            \"SELECT DATE_FORMAT('2009-02-13 23:31:30.123456' :> TIME(6), '%f') :> INT\",\n        )\n        self.validate_identity(\n            \"SELECT SECOND('2009-02-13 23:31:30.123456')\",\n            \"SELECT DATE_FORMAT('2009-02-13 23:31:30.123456' :> TIME(6), '%s') :> INT\",\n        )\n        self.validate_identity(\n            \"SELECT MONTHNAME('2014-04-18')\", \"SELECT DATE_FORMAT('2014-04-18', '%M')\"\n        )\n        self.validate_identity(\n            \"SELECT WEEKDAY('2014-04-18')\", \"SELECT (DAYOFWEEK('2014-04-18') + 5) % 7\"\n        )\n        self.validate_identity(\n            \"SELECT MINUTE('2009-02-13 23:31:30.123456')\",\n            \"SELECT DATE_FORMAT('2009-02-13 23:31:30.123456' :> TIME(6), '%i') :> INT\",\n        )\n        self.validate_all(\n            \"SELECT ((DAYOFWEEK('2014-04-18') % 7) + 1)\",\n            read={\n                \"singlestore\": \"SELECT ((DAYOFWEEK('2014-04-18') % 7) + 1)\",\n                \"\": \"SELECT DAYOFWEEK_ISO('2014-04-18')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DAY('2014-04-18')\",\n            read={\n                \"singlestore\": \"SELECT DAY('2014-04-18')\",\n                \"\": \"SELECT DAY_OF_MONTH('2014-04-18')\",\n            },\n        )\n\n    def test_math_functions(self):\n        self.validate_all(\n            \"SELECT APPROX_COUNT_DISTINCT(asset_id) AS approx_distinct_asset_id FROM acd_assets\",\n            read={\n                \"singlestore\": \"SELECT APPROX_COUNT_DISTINCT(asset_id) AS approx_distinct_asset_id FROM acd_assets\",\n                \"\": \"SELECT HLL(asset_id) AS approx_distinct_asset_id FROM acd_assets\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT APPROX_COUNT_DISTINCT(asset_id1, asset_id2) AS approx_distinct_asset_id FROM acd_assets\"\n        )\n        self.validate_all(\n            \"SELECT APPROX_COUNT_DISTINCT(asset_id) AS approx_distinct_asset_id FROM acd_assets\",\n            read={\n                \"singlestore\": \"SELECT APPROX_COUNT_DISTINCT(asset_id) AS approx_distinct_asset_id FROM acd_assets\",\n                \"\": \"SELECT APPROX_DISTINCT(asset_id) AS approx_distinct_asset_id FROM acd_assets\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SUM(CASE WHEN age > 18 THEN 1 ELSE 0 END) FROM `users`\",\n            read={\n                \"singlestore\": \"SELECT SUM(CASE WHEN age > 18 THEN 1 ELSE 0 END) FROM `users`\",\n                \"\": \"SELECT COUNT_IF(age > 18) FROM users\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MAX(ABS(age > 18)) FROM `users`\",\n            read={\n                \"singlestore\": \"SELECT MAX(ABS(age > 18)) FROM `users`\",\n                \"\": \"SELECT LOGICAL_OR(age > 18) FROM users\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MIN(ABS(age > 18)) FROM `users`\",\n            read={\n                \"singlestore\": \"SELECT MIN(ABS(age > 18)) FROM `users`\",\n                \"\": \"SELECT LOGICAL_AND(age > 18) FROM users\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT `class`, student_id, test1, APPROX_PERCENTILE(test1, 0.3) OVER (PARTITION BY `class`) AS percentile FROM test_scores\"\n        )\n        self.validate_identity(\n            \"SELECT `class`, student_id, test1, APPROX_PERCENTILE(test1, 0.3, 0.4) OVER (PARTITION BY `class`) AS percentile FROM test_scores\"\n        )\n        self.validate_all(\n            \"SELECT APPROX_PERCENTILE(test1, 0.3) FROM test_scores\",\n            read={\n                \"singlestore\": \"SELECT APPROX_PERCENTILE(test1, 0.3) FROM test_scores\",\n                # accuracy parameter is not supported in SingleStore, so it is ignored\n                \"\": \"SELECT APPROX_QUANTILE(test1, 0.3, 0.4) FROM test_scores\",\n            },\n        )\n        self.validate_all(\n            \"SELECT VAR_SAMP(yearly_total) FROM player_scores\",\n            read={\n                \"singlestore\": \"SELECT VAR_SAMP(yearly_total) FROM player_scores\",\n                \"\": \"SELECT VARIANCE(yearly_total) FROM player_scores\",\n            },\n            write={\n                \"\": \"SELECT VARIANCE(yearly_total) FROM player_scores\",\n            },\n        )\n        self.validate_all(\n            \"SELECT VAR_POP(yearly_total) FROM player_scores\",\n            read={\n                \"singlestore\": \"SELECT VARIANCE(yearly_total) FROM player_scores\",\n                \"\": \"SELECT VARIANCE_POP(yearly_total) FROM player_scores\",\n            },\n            write={\n                \"\": \"SELECT VARIANCE_POP(yearly_total) FROM player_scores\",\n            },\n        )\n        self.validate_all(\n            \"SELECT POWER(id, 1 / 3) FROM orders\",\n            read={\n                \"\": \"SELECT CBRT(id) FROM orders\",\n                \"singlestore\": \"SELECT POWER(id, 1 / 3) FROM orders\",\n            },\n        )\n\n    def test_logical(self):\n        self.validate_all(\n            \"SELECT (TRUE AND (NOT FALSE)) OR ((NOT TRUE) AND FALSE)\",\n            read={\n                \"mysql\": \"SELECT TRUE XOR FALSE\",\n                \"singlestore\": \"SELECT (TRUE AND (NOT FALSE)) OR ((NOT TRUE) AND FALSE)\",\n            },\n        )\n\n    def test_string_functions(self):\n        self.validate_all(\n            \"SELECT 'a' RLIKE 'b'\",\n            read={\n                \"bigquery\": \"SELECT REGEXP_CONTAINS('a', 'b')\",\n                \"singlestore\": \"SELECT 'a' RLIKE 'b'\",\n            },\n        )\n        self.validate_identity(\"SELECT 'a' REGEXP 'b'\", \"SELECT 'a' RLIKE 'b'\")\n        self.validate_all(\n            \"SELECT LPAD('', LENGTH('a') * 3, 'a')\",\n            read={\n                \"\": \"SELECT REPEAT('a', 3)\",\n                \"singlestore\": \"SELECT LPAD('', LENGTH('a') * 3, 'a')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT REGEXP_SUBSTR('adog', 'O', 1, 1, 'c')\",\n            read={\n                # group parameter is not supported in SingleStore, so it is ignored\n                \"\": \"SELECT REGEXP_EXTRACT('adog', 'O', 1, 1, 'c', 'gr1')\",\n                \"singlestore\": \"SELECT REGEXP_SUBSTR('adog', 'O', 1, 1, 'c')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ('a' RLIKE '^[\\x00-\\x7f]*$')\",\n            read={\"singlestore\": \"SELECT ('a' RLIKE '^[\\x00-\\x7f]*$')\", \"\": \"SELECT IS_ASCII('a')\"},\n        )\n        self.validate_all(\n            \"SELECT UNHEX(MD5('data'))\",\n            read={\n                \"singlestore\": \"SELECT UNHEX(MD5('data'))\",\n                \"\": \"SELECT MD5_DIGEST('data')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CHAR(101)\", read={\"\": \"SELECT CHR(101)\", \"singlestore\": \"SELECT CHAR(101)\"}\n        )\n        self.validate_all(\n            \"SELECT INSTR('ohai', 'i')\",\n            read={\n                \"\": \"SELECT CONTAINS('ohai', 'i')\",\n                \"singlestore\": \"SELECT INSTR('ohai', 'i')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT REGEXP_MATCH('adog', 'O', 'c')\",\n            read={\n                # group, position, occurrence parameters are not supported in SingleStore, so they are ignored\n                \"\": \"SELECT REGEXP_EXTRACT_ALL('adog', 'O', 1, 'c', 1, 'gr1')\",\n                \"singlestore\": \"SELECT REGEXP_MATCH('adog', 'O', 'c')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT REGEXP_SUBSTR('adog', 'O', 1, 1, 'c')\",\n            read={\n                # group parameter is not supported in SingleStore, so it is ignored\n                \"\": \"SELECT REGEXP_EXTRACT('adog', 'O', 1, 1, 'c', 'gr1')\",\n                \"singlestore\": \"SELECT REGEXP_SUBSTR('adog', 'O', 1, 1, 'c')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT REGEXP_INSTR('abcd', CONCAT('^', 'ab'))\",\n            read={\n                \"\": \"SELECT STARTS_WITH('abcd', 'ab')\",\n                \"singlestore\": \"SELECT REGEXP_INSTR('abcd', CONCAT('^', 'ab'))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CONV('f', 16, 10)\",\n            read={\n                \"redshift\": \"SELECT STRTOL('f',16)\",\n                \"singlestore\": \"SELECT CONV('f', 16, 10)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LOWER('ABC') RLIKE LOWER('a.*')\",\n            read={\n                \"postgres\": \"SELECT 'ABC' ~* 'a.*'\",\n                \"singlestore\": \"SELECT LOWER('ABC') RLIKE LOWER('a.*')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CONCAT(SUBSTRING('abcdef', 1, 2 - 1), 'xyz', SUBSTRING('abcdef', 2 + 3))\",\n            read={\n                \"singlestore\": \"SELECT CONCAT(SUBSTRING('abcdef', 1, 2 - 1), 'xyz', SUBSTRING('abcdef', 2 + 3))\",\n                \"\": \"SELECT STUFF('abcdef', 2, 3, 'xyz')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SHA(email) FROM t\",\n            read={\n                \"singlestore\": \"SELECT SHA(email) FROM t\",\n                \"\": \"SELECT STANDARD_HASH(email) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SHA(email) FROM t\",\n            read={\n                \"singlestore\": \"SELECT SHA(email) FROM t\",\n                \"\": \"SELECT STANDARD_HASH(email, 'sha') FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MD5(email) FROM t\",\n            read={\n                \"singlestore\": \"SELECT MD5(email) FROM t\",\n                \"\": \"SELECT STANDARD_HASH(email, 'MD5') FROM t\",\n            },\n        )\n\n    def test_reduce_functions(self):\n        self.validate_all(\n            \"SELECT REDUCE(0, JSON_TO_ARRAY('[1,2,3,4]'), REDUCE_ACC() + REDUCE_VALUE()) AS `Result`\",\n            read={\n                # finish argument is not supported in SingleStore, so it is ignored\n                \"\": \"SELECT REDUCE(JSON_TO_ARRAY('[1,2,3,4]'), 0, REDUCE_ACC() + REDUCE_VALUE(), REDUCE_ACC() + REDUCE_VALUE()) AS Result\",\n                \"singlestore\": \"SELECT REDUCE(0, JSON_TO_ARRAY('[1,2,3,4]'), REDUCE_ACC() + REDUCE_VALUE()) AS `Result`\",\n            },\n        )\n\n    def test_time_functions(self):\n        self.validate_all(\n            \"SELECT TIME_BUCKET('1d', '2019-03-14 06:04:12', '2019-03-13 03:00:00')\",\n            read={\n                # unit and zone parameters are not supported in SingleStore, so they are ignored\n                \"\": \"SELECT DATE_BIN('1d', '2019-03-14 06:04:12', DAY, 'UTC', '2019-03-13 03:00:00')\",\n                \"singlestore\": \"SELECT TIME_BUCKET('1d', '2019-03-14 06:04:12', '2019-03-13 03:00:00')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '2019-03-14 06:04:12' :> DATE\",\n            read={\n                \"\": \"SELECT TIME_STR_TO_DATE('2019-03-14 06:04:12')\",\n                \"singlestore\": \"SELECT '2019-03-14 06:04:12' :> DATE\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CONVERT_TZ(NOW() :> TIMESTAMP, 'GMT', 'UTC')\",\n            read={\n                \"spark2\": \"SELECT TO_UTC_TIMESTAMP(NOW(), 'GMT')\",\n                \"singlestore\": \"SELECT CONVERT_TZ(NOW() :> TIMESTAMP, 'GMT', 'UTC')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT STR_TO_DATE(20190314, '%Y%m%d')\",\n            read={\n                \"\": \"SELECT DI_TO_DATE(20190314)\",\n                \"singlestore\": \"SELECT STR_TO_DATE(20190314, '%Y%m%d')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT (DATE_FORMAT('2019-03-14 06:04:12', '%Y%m%d') :> INT)\",\n            read={\n                \"singlestore\": \"SELECT (DATE_FORMAT('2019-03-14 06:04:12', '%Y%m%d') :> INT)\",\n                \"\": \"SELECT DATE_TO_DI('2019-03-14 06:04:12')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT (DATE_FORMAT('2019-03-14 06:04:12', '%Y%m%d') :> INT)\",\n            read={\n                \"singlestore\": \"SELECT (DATE_FORMAT('2019-03-14 06:04:12', '%Y%m%d') :> INT)\",\n                \"\": \"SELECT TS_OR_DI_TO_DI('2019-03-14 06:04:12')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '2019-03-14 06:04:12' :> TIME\",\n            read={\n                # zone parameter is not supported in SingleStore, so it is ignored\n                \"bigquery\": \"SELECT TIME('2019-03-14 06:04:12', 'GMT')\",\n                \"singlestore\": \"SELECT '2019-03-14 06:04:12' :> TIME\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_ADD(NOW(), INTERVAL '1' MONTH)\",\n            read={\n                \"bigquery\": \"SELECT DATETIME_ADD(NOW(), INTERVAL 1 MONTH)\",\n                \"singlestore\": \"SELECT DATE_ADD(NOW(), INTERVAL '1' MONTH)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_TRUNC('MINUTE', '2016-08-08 12:05:31')\",\n            read={\n                \"bigquery\": \"SELECT DATETIME_TRUNC('2016-08-08 12:05:31', MINUTE)\",\n                \"singlestore\": \"SELECT DATE_TRUNC('MINUTE', '2016-08-08 12:05:31')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_SUB('2010-04-02', INTERVAL '1' WEEK)\",\n            read={\n                \"bigquery\": \"SELECT DATETIME_SUB('2010-04-02', INTERVAL '1' WEEK)\",\n                \"singlestore\": \"SELECT DATE_SUB('2010-04-02', INTERVAL '1' WEEK)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMPDIFF(QUARTER, '2009-02-13', '2013-09-01')\",\n            read={\n                \"singlestore\": \"SELECT TIMESTAMPDIFF(QUARTER, '2009-02-13', '2013-09-01')\",\n                \"\": \"SELECT DATETIME_DIFF('2013-09-01', '2009-02-13', QUARTER)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMPDIFF(QUARTER, '2009-02-13', '2013-09-01')\",\n            read={\n                \"singlestore\": \"SELECT TIMESTAMPDIFF(QUARTER, '2009-02-13', '2013-09-01')\",\n                \"bigquery\": \"SELECT DATE_DIFF('2013-09-01', '2009-02-13', QUARTER)\",\n                \"duckdb\": \"SELECT DATE_DIFF('QUARTER', '2009-02-13', '2013-09-01')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(DATE('2013-09-01'), DATE('2009-02-13'))\",\n            read={\n                \"hive\": \"SELECT DATEDIFF('2013-09-01', '2009-02-13')\",\n                \"singlestore\": \"SELECT DATEDIFF(DATE('2013-09-01'), DATE('2009-02-13'))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_TRUNC('MINUTE', '2016-08-08 12:05:31')\",\n            read={\n                \"\": \"SELECT TIMESTAMP_TRUNC('2016-08-08 12:05:31', MINUTE)\",\n                \"singlestore\": \"SELECT DATE_TRUNC('MINUTE', '2016-08-08 12:05:31')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMPDIFF(WEEK, '2009-01-01', '2009-12-31') AS numweeks\",\n            read={\n                \"redshift\": \"SELECT datediff(week,'2009-01-01','2009-12-31') AS numweeks\",\n                \"singlestore\": \"SELECT TIMESTAMPDIFF(WEEK, '2009-01-01', '2009-12-31') AS numweeks\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF('2009-12-31', '2009-01-01') AS numweeks\",\n            read={\n                \"\": \"SELECT TS_OR_DS_DIFF('2009-12-31', '2009-01-01') AS numweeks\",\n                \"singlestore\": \"SELECT DATEDIFF('2009-12-31', '2009-01-01') AS numweeks\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CURRENT_DATE()\",\n            read={\n                \"\": \"SELECT CURRENT_DATE()\",\n                \"singlestore\": \"SELECT CURRENT_DATE\",\n            },\n        )\n        self.validate_all(\n            \"SELECT UTC_DATE()\",\n            read={\n                \"\": \"SELECT CURRENT_DATE('UTC')\",\n                \"singlestore\": \"SELECT UTC_DATE\",\n            },\n            write={\"\": \"SELECT CURRENT_DATE('UTC')\"},\n        )\n        self.validate_all(\n            \"SELECT CURRENT_TIME()\",\n            read={\n                \"\": \"SELECT CURRENT_TIME()\",\n                \"singlestore\": \"SELECT CURRENT_TIME\",\n            },\n        )\n        self.validate_identity(\"SELECT CURRENT_TIME(6)\")\n        self.validate_all(\n            \"SELECT UTC_TIME()\",\n            read={\n                \"\": \"SELECT CURRENT_TIME('UTC')\",\n                \"singlestore\": \"SELECT UTC_TIME\",\n            },\n            write={\"\": \"SELECT CURRENT_TIME('UTC')\"},\n        )\n        self.validate_all(\n            \"SELECT CURRENT_TIMESTAMP()\",\n            read={\n                \"\": \"SELECT CURRENT_TIMESTAMP()\",\n                \"singlestore\": \"SELECT CURRENT_TIMESTAMP\",\n            },\n        )\n        self.validate_identity(\"SELECT CURRENT_TIMESTAMP(6)\")\n        self.validate_all(\n            \"SELECT UTC_TIMESTAMP()\",\n            read={\n                \"\": \"SELECT CURRENT_TIMESTAMP('UTC')\",\n                \"singlestore\": \"SELECT UTC_TIMESTAMP\",\n            },\n            write={\"\": \"SELECT CURRENT_TIMESTAMP('UTC')\"},\n        )\n        self.validate_all(\n            \"SELECT CURRENT_TIMESTAMP(6) :> DATETIME(6)\",\n            read={\n                \"bigquery\": \"SELECT CURRENT_DATETIME()\",\n                \"singlestore\": \"SELECT CURRENT_TIMESTAMP(6) :> DATETIME(6)\",\n            },\n        )\n        self.validate_identity(\"SELECT UTC_TIMESTAMP(6)\")\n        self.validate_identity(\"SELECT UTC_TIME(6)\")\n\n    def test_types(self):\n        self.validate_all(\n            \"CREATE TABLE testTypes (a DECIMAL(10, 20))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a DECIMAL(10, 20))\",\n                \"bigquery\": \"CREATE TABLE testTypes (a BIGDECIMAL(10, 20))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a BOOLEAN)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a BOOLEAN)\",\n                \"tsql\": \"CREATE TABLE testTypes (a BIT)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a DATE)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a DATE)\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a DATE32)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a DATETIME)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a DATETIME)\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a DATETIME64)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a DECIMAL(9, 3))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a DECIMAL(9, 3))\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a DECIMAL32(3))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a DECIMAL(18, 3))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a DECIMAL(18, 3))\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a DECIMAL64(3))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a DECIMAL(38, 3))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a DECIMAL(38, 3))\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a DECIMAL128(3))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a DECIMAL(65, 3))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a DECIMAL(65, 3))\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a DECIMAL256(3))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a ENUM('a'))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a ENUM('a'))\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a ENUM8('a'))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a ENUM('a'))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a ENUM('a'))\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a ENUM16('a'))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a TEXT(2))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a TEXT(2))\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a FIXEDSTRING(2))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n                \"snowflake\": \"CREATE TABLE testTypes (a GEOMETRY)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a GEOGRAPHYPOINT)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a GEOGRAPHYPOINT)\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a POINT)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a RING)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a LINESTRING)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a POLYGON)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a GEOGRAPHY)\",\n                \"clickhouse\": \"CREATE TABLE testTypes (a MULTIPOLYGON)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a BSON)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a BSON)\",\n                \"postgres\": \"CREATE TABLE testTypes (a JSONB)\",\n            },\n        )\n        self.validate_identity(\"CREATE TABLE testTypes (a TIMESTAMP(6))\")\n        self.validate_all(\n            \"CREATE TABLE testTypes (a TIMESTAMP)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a TIMESTAMP)\",\n                \"duckdb\": \"CREATE TABLE testTypes (a TIMESTAMP_S)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a TIMESTAMP(6))\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a TIMESTAMP(6))\",\n                \"duckdb\": \"CREATE TABLE testTypes (a TIMESTAMP_MS)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE testTypes (a BLOB)\",\n            read={\n                \"singlestore\": \"CREATE TABLE testTypes (a BLOB)\",\n                \"\": \"CREATE TABLE testTypes (a VARBINARY)\",\n            },\n        )\n\n    def test_column_with_tablename(self):\n        self.validate_identity(\"SELECT `t0`.`name` FROM `t0`\")\n\n    def test_unicodestring_sql(self):\n        self.validate_all(\n            \"SELECT 'data'\",\n            read={\"presto\": \"SELECT U&'d\\\\0061t\\\\0061'\", \"singlestore\": \"SELECT 'data'\"},\n        )\n\n    def test_collate_sql(self):\n        self.validate_all(\n            \"SELECT name :> LONGTEXT COLLATE 'utf8mb4_bin' FROM `users`\",\n            read={\n                \"\": \"SELECT name COLLATE 'utf8mb4_bin' FROM users\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT name :> LONGTEXT COLLATE 'utf8mb4_bin' FROM `users`\",\n            \"SELECT name :> LONGTEXT :> LONGTEXT COLLATE 'utf8mb4_bin' FROM `users`\",\n        )\n\n    def test_match_against(self):\n        self.validate_identity(\n            \"SELECT MATCH(name) AGAINST('search term') FROM products\"\n        ).expressions[0].assert_is(exp.MatchAgainst)\n        self.validate_identity(\n            \"SELECT MATCH(name, name) AGAINST('book') FROM products\"\n        ).expressions[0].assert_is(exp.MatchAgainst)\n        self.validate_identity(\n            \"SELECT MATCH(TABLE products2) AGAINST('search term') FROM products2\"\n        ).expressions[0].assert_is(exp.MatchAgainst)\n\n    def test_show(self):\n        self.validate_identity(\"SHOW AGGREGATES FROM db1\")\n        self.validate_identity(\"SHOW AGGREGATES LIKE 'multiply%'\")\n        self.validate_identity(\"SHOW CDC EXTRACTOR POOL\")\n        self.validate_identity(\"SHOW CREATE AGGREGATE avg_udaf\")\n        self.validate_identity(\"SHOW CREATE PIPELINE mypipeline\")\n        self.validate_identity(\"SHOW CREATE PROJECTION lineitem_sort_shipdate FOR TABLE lineitem\")\n        self.validate_identity(\"SHOW DATABASE STATUS\")\n        self.validate_identity(\"SHOW DISTRIBUTED_PLANCACHE STATUS\")\n        self.validate_identity(\"SHOW FULLTEXT SERVICE STATUS\")\n        self.validate_identity(\"SHOW FULLTEXT SERVICE METRICS LOCAL\")\n        self.validate_identity(\"SHOW FULLTEXT SERVICE METRICS FOR NODE 1\")\n        self.validate_identity(\"SHOW FUNCTIONS FROM db LIKE 'a'\")\n        self.validate_identity(\"SHOW GROUPS\")\n        self.validate_identity(\"SHOW GROUPS FOR ROLE 'role_name_0'\")\n        self.validate_identity(\"SHOW GROUPS FOR USER 'root'\")\n        self.validate_identity(\"SHOW INDEXES FROM mytbl\", \"SHOW INDEX FROM mytbl\")\n        self.validate_identity(\"SHOW KEYS FROM mytbl\", \"SHOW INDEX FROM mytbl\")\n        self.validate_identity(\"SHOW LINKS ON Orderdb\")\n        self.validate_identity(\"SHOW LOAD ERRORS\")\n        self.validate_identity(\"SHOW LOAD WARNINGS\")\n        self.validate_identity(\"SHOW PARTITIONS ON memsql_demo\")\n        self.validate_identity(\"SHOW PIPELINES\")\n        self.validate_identity(\"SHOW PLAN JSON 25\")\n        self.validate_identity(\"SHOW PLAN 25\")\n        self.validate_identity(\"SHOW PLANCACHE\")\n        self.validate_identity(\"SHOW PROCEDURES FROM dbExample\")\n        self.validate_identity(\"SHOW PROCEDURES LIKE '%sp%'\")\n        self.validate_identity(\"SHOW PROJECTIONS ON TABLE t\")\n        self.validate_identity(\"SHOW PROJECTIONS\")\n        self.validate_identity(\"SHOW REPLICATION STATUS\")\n        self.validate_identity(\"SHOW REPRODUCTION\")\n        self.validate_identity(\"SHOW REPRODUCTION INTO OUTFILE 'a'\")\n        self.validate_identity(\"SHOW RESOURCE POOLS\")\n        self.validate_identity(\"SHOW ROLES LIKE 'xyz'\")\n        self.validate_identity(\"SHOW ROLES FOR GROUP 'group_0'\")\n        self.validate_identity(\"SHOW ROLES FOR USER 'root'\")\n        self.validate_identity(\"SHOW STATUS\")\n        self.validate_identity(\"SHOW USERS\")\n        self.validate_identity(\"SHOW USERS FOR GROUP 'group_name'\")\n        self.validate_identity(\"SHOW USERS FOR ROLE 'role_name'\")\n\n    def test_truncate(self):\n        self.validate_all(\n            \"TRUNCATE t1; TRUNCATE t2\",\n            read={\n                \"\": \"TRUNCATE TABLE t1, t2\",\n            },\n        )\n\n    def test_vector(self):\n        self.validate_all(\n            \"CREATE TABLE t (a VECTOR(10, I32))\",\n            read={\n                \"snowflake\": \"CREATE TABLE t (a VECTOR(INT, 10))\",\n                \"singlestore\": \"CREATE TABLE t (a VECTOR(10, I32))\",\n            },\n            write={\n                \"snowflake\": \"CREATE TABLE t (a VECTOR(INT, 10))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE t (a VECTOR(10))\",\n            read={\n                \"snowflake\": \"CREATE TABLE t (a VECTOR(10))\",\n                \"singlestore\": \"CREATE TABLE t (a VECTOR(10))\",\n            },\n            write={\n                \"snowflake\": \"CREATE TABLE t (a VECTOR(10))\",\n            },\n        )\n\n    def test_alter(self):\n        self.validate_identity(\"ALTER TABLE t CHANGE middle_initial middle_name\")\n        self.validate_identity(\"ALTER TABLE t MODIFY COLUMN name TEXT COLLATE 'binary'\")\n\n    def test_constraints(self):\n        self.validate_all(\n            \"CREATE TABLE ComputedColumnConstraint (points INT, score AS (points * 2) PERSISTED AUTO NOT NULL)\",\n            read={\n                \"\": \"CREATE TABLE ComputedColumnConstraint (points INT, score AS (points * 2) PERSISTED NOT NULL)\",\n                \"singlestore\": \"CREATE TABLE ComputedColumnConstraint (points INT, score AS (points * 2) AUTO NOT NULL)\",\n            },\n        )\n        self.validate_identity(\n            \"CREATE TABLE ComputedColumnConstraint (points INT, score AS (points * 2) PERSISTED BIGINT NOT NULL)\"\n        )\n\n    def test_dcolonqmark(self):\n        self.validate_identity(\"SELECT * FROM employee WHERE JSON_MATCH_ANY(payroll::?names)\")\n"
  },
  {
    "path": "tests/dialects/test_snowflake.py",
    "content": "from unittest import mock\n\nfrom sqlglot import ParseError, UnsupportedError, exp, parse_one\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom sqlglot.optimizer.normalize_identifiers import normalize_identifiers\nfrom sqlglot.optimizer.qualify_columns import quote_identifiers\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestSnowflake(Validator):\n    maxDiff = None\n    dialect = \"snowflake\"\n\n    def test_snowflake(self):\n        self.validate_identity(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"level1\": {\"level2\": {\"level3\": \"value\"}}}') AS data) SELECT data:     level1  : level2 : level3::VARIANT FROM t\"\"\",\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"level1\": {\"level2\": {\"level3\": \"value\"}}}') AS data) SELECT CAST(GET_PATH(data, 'level1.level2.level3') AS VARIANT) FROM t\"\"\",\n        )\n\n        self.validate_identity(\n            \"SELECT * FROM x ASOF JOIN y OFFSET MATCH_CONDITION (x.a > y.a)\",\n            \"SELECT * FROM x ASOF JOIN y AS OFFSET MATCH_CONDITION (x.a > y.a)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM x ASOF JOIN y LIMIT MATCH_CONDITION (x.a > y.a)\",\n            \"SELECT * FROM x ASOF JOIN y AS LIMIT MATCH_CONDITION (x.a > y.a)\",\n        )\n\n        self.validate_identity(\"SELECT session\")\n        self.validate_identity(\"x::nvarchar()\", \"CAST(x AS VARCHAR)\")\n\n        ast = self.parse_one(\"DATEADD(DAY, n, d)\")\n        ast.set(\"unit\", exp.Literal.string(\"MONTH\"))\n        self.assertEqual(ast.sql(\"snowflake\"), \"DATEADD(MONTH, n, d)\")\n\n        self.validate_identity(\"SELECT DATE_PART(EPOCH_MILLISECOND, CURRENT_TIMESTAMP()) AS a\")\n        self.validate_identity(\"SELECT GET(a, b)\")\n        self.validate_identity(\"SELECT HASH_AGG(a, b, c, d)\")\n        self.validate_identity(\"SELECT GREATEST(1, 2, 3, NULL)\")\n        self.validate_identity(\"SELECT GREATEST_IGNORE_NULLS(1, 2, 3, NULL)\")\n        self.validate_identity(\"SELECT LEAST(5, NULL, 7, 3)\")\n        self.validate_identity(\"SELECT LEAST_IGNORE_NULLS(5, NULL, 7, 3)\")\n        self.validate_identity(\"SELECT MAX(x)\")\n        self.validate_identity(\"SELECT COUNT(x)\")\n        self.validate_identity(\"SELECT MIN(amount)\")\n        self.validate_identity(\"SELECT MODE(x)\")\n        self.validate_identity(\"SELECT MODE(status) OVER (PARTITION BY region) FROM orders\")\n        self.validate_identity(\"SELECT TAN(x)\")\n        self.validate_identity(\"SELECT COS(x)\")\n        self.validate_identity(\"SELECT SINH(1.5)\")\n        self.validate_identity(\"SELECT MOD(x, y)\", \"SELECT x % y\")\n        self.validate_identity(\"SELECT ROUND(x)\")\n        self.validate_identity(\"SELECT ROUND(123.456, -1)\")\n        self.validate_identity(\"SELECT ROUND(123.456, 2, 'HALF_AWAY_FROM_ZERO')\")\n\n        self.validate_identity(\"SELECT FLOOR(x)\")\n        self.validate_identity(\"SELECT FLOOR(135.135, 1)\")\n        self.validate_identity(\"SELECT FLOOR(x, -1)\")\n        self.validate_identity(\n            \"SELECT PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY salary) FROM employees\"\n        )\n        self.assertEqual(\n            # Ensures we don't fail when generating ParseJSON with the `safe` arg set to `True`\n            self.validate_identity(\"\"\"SELECT TRY_PARSE_JSON('{\"x: 1}')\"\"\").sql(),\n            \"\"\"SELECT PARSE_JSON('{\"x: 1}')\"\"\",\n        )\n\n        self.validate_identity(\n            \"SELECT APPROX_TOP_K(col) FROM t\",\n            \"SELECT APPROX_TOP_K(col, 1) FROM t\",\n        )\n        self.validate_identity(\"SELECT APPROX_TOP_K(category, 3) FROM t\")\n        self.validate_identity(\"APPROX_TOP_K(C4, 3, 5)\").assert_is(exp.AggFunc)\n\n        self.validate_identity(\"SELECT MINHASH(5, col)\")\n        self.validate_identity(\"SELECT MINHASH(5, col1, col2)\")\n        self.validate_identity(\"SELECT MINHASH(5, *)\")\n        self.validate_identity(\"SELECT MINHASH_COMBINE(minhash_col)\")\n        self.validate_identity(\"SELECT APPROXIMATE_SIMILARITY(minhash_col)\")\n        self.validate_identity(\n            \"SELECT APPROXIMATE_JACCARD_INDEX(minhash_col)\",\n            \"SELECT APPROXIMATE_SIMILARITY(minhash_col)\",\n        )\n        self.validate_identity(\"SELECT APPROX_PERCENTILE_ACCUMULATE(col)\")\n        self.validate_identity(\"SELECT APPROX_PERCENTILE_ESTIMATE(state, 0.5)\")\n        self.validate_identity(\"SELECT APPROX_TOP_K_ACCUMULATE(col, 10)\")\n        self.validate_identity(\"SELECT APPROX_TOP_K_COMBINE(state, 2)\")\n        self.validate_identity(\"SELECT APPROX_TOP_K_COMBINE(state)\")\n        self.validate_identity(\"SELECT APPROX_TOP_K_ESTIMATE(state_column, 4)\")\n        self.validate_identity(\"SELECT APPROX_TOP_K_ESTIMATE(state_column)\")\n        self.validate_identity(\"SELECT APPROX_PERCENTILE_COMBINE(state_column)\")\n        self.validate_identity(\"SELECT EQUAL_NULL(1, 2)\")\n        self.validate_identity(\"SELECT EXP(1)\")\n        self.validate_identity(\"SELECT FACTORIAL(5)\")\n        self.validate_identity(\"SELECT BIT_LENGTH('abc')\")\n        self.validate_identity(\"SELECT BIT_LENGTH(x'A1B2')\")\n        self.validate_all(\n            \"SELECT BITMAP_BIT_POSITION(10)\",\n            write={\n                \"duckdb\": \"SELECT (CASE WHEN 10 > 0 THEN 10 - 1 ELSE ABS(10) END) % 32768\",\n                \"snowflake\": \"SELECT BITMAP_BIT_POSITION(10)\",\n            },\n        )\n        self.validate_identity(\"SELECT BITMAP_BUCKET_NUMBER(32769)\")\n        self.validate_identity(\"SELECT BITMAP_CONSTRUCT_AGG(value)\")\n        self.validate_all(\n            \"SELECT BITMAP_CONSTRUCT_AGG(v) FROM t\",\n            write={\n                \"snowflake\": \"SELECT BITMAP_CONSTRUCT_AGG(v) FROM t\",\n                \"duckdb\": \"SELECT (SELECT CASE WHEN l IS NULL OR LENGTH(l) = 0 THEN NULL WHEN LENGTH(l) <> LENGTH(LIST_FILTER(l, __v -> __v BETWEEN 0 AND 32767)) THEN NULL WHEN LENGTH(l) < 5 THEN UNHEX(PRINTF('%04X', LENGTH(l)) || h || REPEAT('00', GREATEST(0, 4 - LENGTH(l)) * 2)) ELSE UNHEX('08000000000000000000' || h) END FROM (SELECT l, COALESCE(LIST_REDUCE(LIST_TRANSFORM(l, __x -> PRINTF('%02X%02X', CAST(__x AS INT) & 255, (CAST(__x AS INT) >> 8) & 255)), (__a, __b) -> __a || __b, ''), '') AS h FROM (SELECT LIST_SORT(LIST_DISTINCT(LIST(v) FILTER(WHERE NOT v IS NULL))) AS l))) FROM t\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT BITMAP_COUNT(BITMAP_CONSTRUCT_AGG(value)) FROM TABLE(FLATTEN(INPUT => ARRAY_CONSTRUCT(1, 2, 3, 5)))\",\n            \"SELECT BITMAP_COUNT(BITMAP_CONSTRUCT_AGG(value)) FROM TABLE(FLATTEN(INPUT => [1, 2, 3, 5]))\",\n        )\n        self.validate_all(\n            \"SELECT ARRAY_MAX([1, 2, 3])\",\n            write={\n                \"duckdb\": \"SELECT LIST_MAX([1, 2, 3])\",\n                \"snowflake\": \"SELECT ARRAY_MAX([1, 2, 3])\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_MIN([1, 2, 3])\",\n            write={\n                \"duckdb\": \"SELECT LIST_MIN([1, 2, 3])\",\n                \"snowflake\": \"SELECT ARRAY_MIN([1, 2, 3])\",\n            },\n        )\n\n        self.validate_identity(\"SELECT BOOLAND(1, -2)\")\n        self.validate_identity(\"SELECT BOOLXOR(2, 0)\")\n        self.validate_identity(\"SELECT BOOLOR(1, 0)\")\n        self.validate_identity(\"SELECT TO_BOOLEAN('true')\")\n        self.validate_identity(\"SELECT TO_BOOLEAN(1)\")\n        self.validate_identity(\"SELECT TO_VARIANT(123)\")\n        self.validate_identity(\"SELECT IS_NULL_VALUE(GET_PATH(payload, 'field'))\")\n        self.validate_identity(\"SELECT RTRIMMED_LENGTH(' ABCD ')\")\n        self.validate_identity(\"SELECT HEX_DECODE_STRING('48656C6C6F')\")\n        self.validate_identity(\"SELECT HEX_ENCODE('Hello World')\")\n        self.validate_identity(\"SELECT HEX_ENCODE('Hello World', 1)\")\n        self.validate_identity(\"SELECT HEX_ENCODE('Hello World', 0)\")\n        self.validate_identity(\"SELECT IFNULL(col1, col2)\", \"SELECT COALESCE(col1, col2)\")\n        self.validate_identity(\"SELECT NEXT_DAY('2025-10-15', 'FRIDAY')\")\n        self.validate_identity(\"SELECT NVL2(col1, col2, col3)\")\n        self.validate_identity(\"SELECT NVL(col1, col2)\", \"SELECT COALESCE(col1, col2)\")\n        self.validate_identity(\"SELECT CHR(8364)\")\n        self.validate_identity('SELECT CHECK_JSON(\\'{\"key\": \"value\"}\\')')\n        self.validate_identity(\n            \"SELECT CHECK_XML('<root><key attribute=\\\"attr\\\">value</key></root>')\"\n        )\n        self.validate_identity(\n            \"SELECT CHECK_XML('<root><key attribute=\\\"attr\\\">value</key></root>', TRUE)\"\n        )\n        self.validate_identity(\"SELECT COMPRESS('Hello World', 'ZLIB')\")\n        self.validate_identity(\"SELECT DECOMPRESS_BINARY('compressed_data', 'SNAPPY')\")\n        self.validate_identity(\"SELECT DECOMPRESS_STRING('compressed_data', 'ZSTD')\")\n        self.validate_identity(\"SELECT LPAD('Hello', 10, '*')\")\n        self.validate_identity(\"SELECT LPAD(tbl.bin_col, 10)\")\n        self.validate_identity(\"SELECT RPAD('Hello', 10, '*')\")\n        self.validate_identity(\"SELECT RPAD(tbl.bin_col, 10)\")\n\n        self.validate_all(\n            \"SELECT RPAD('test', 10, 'ab')\",\n            write={\n                \"snowflake\": \"SELECT RPAD('test', 10, 'ab')\",\n                \"duckdb\": \"SELECT RPAD('test', 10, 'ab')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RPAD('data', 8)\",\n            write={\n                \"snowflake\": \"SELECT RPAD('data', 8)\",\n                \"duckdb\": \"SELECT RPAD('data', 8, ' ')\",\n                \"postgres\": \"SELECT RPAD('data', 8)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RPAD('exact', 5, '*')\",\n            write={\n                \"snowflake\": \"SELECT RPAD('exact', 5, '*')\",\n                \"duckdb\": \"SELECT RPAD('exact', 5, '*')\",\n            },\n        )\n\n        ast = self.validate_identity(\n            \"SELECT RPAD(TO_BINARY('Hi', 'UTF8'), 10, TO_BINARY('_', 'UTF8'))\"\n        )\n        annotated = annotate_types(ast, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"SELECT ENCODE('Hi') || REPEAT(ENCODE('_'), GREATEST(0, 10 - OCTET_LENGTH(ENCODE('Hi'))))\",\n        )\n\n        self.validate_identity(\"SELECT SOUNDEX(column_name)\")\n        self.validate_identity(\"SELECT SOUNDEX_P123(column_name)\")\n        self.validate_identity(\"SELECT ABS(x)\")\n        self.validate_identity(\"SELECT ASIN(0.5)\")\n        self.validate_identity(\"SELECT ASINH(0.5)\")\n        self.validate_identity(\"SELECT ATAN(0.5)\")\n        self.validate_identity(\"SELECT ATAN2(0.5, 0.3)\")\n        self.validate_identity(\"SELECT ATANH(0.5)\")\n        self.validate_identity(\"SELECT CBRT(27.0)\")\n        self.validate_identity(\"SELECT POW(2, 3)\", \"SELECT POWER(2, 3)\")\n        self.validate_identity(\"SELECT POW(2.5, 3.0)\", \"SELECT POWER(2.5, 3.0)\")\n        self.validate_identity(\"SELECT SQUARE(2.5)\", \"SELECT POWER(2.5, 2)\")\n        self.validate_identity(\"SELECT SIGN(x)\")\n        self.validate_identity(\"SELECT COSH(1.5)\")\n        self.validate_identity(\"SELECT TANH(0.5)\")\n        self.validate_all(\n            \"JAROWINKLER_SIMILARITY('hello', 'world')\",\n            write={\n                \"snowflake\": \"JAROWINKLER_SIMILARITY('hello', 'world')\",\n                \"duckdb\": \"JARO_WINKLER_SIMILARITY(UPPER('hello'), UPPER('world'))\",\n                \"clickhouse\": \"jaroWinklerSimilarity(UPPER('hello'), UPPER('world'))\",\n            },\n        )\n        self.validate_identity(\"SELECT TRANSLATE(column_name, 'abc', '123')\")\n        self.validate_identity(\"SELECT UNICODE(column_name)\")\n        self.validate_identity(\"SELECT WIDTH_BUCKET(col, 0, 100, 10)\")\n        self.validate_all(\n            \"SELECT SPLIT_PART('11.22.33', '.', 2)\",\n            write={\n                \"snowflake\": \"SELECT SPLIT_PART('11.22.33', '.', 2)\",\n                \"duckdb\": \"SELECT CASE WHEN '.' = '' THEN (CASE WHEN (CASE WHEN 2 = 0 THEN 1 ELSE 2 END) = 1 OR (CASE WHEN 2 = 0 THEN 1 ELSE 2 END) = -1 THEN '11.22.33' ELSE '' END) ELSE SPLIT_PART('11.22.33', '.', (CASE WHEN 2 = 0 THEN 1 ELSE 2 END)) END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SPLIT('127.0.0.1', '.')\",\n            write={\n                \"snowflake\": \"SELECT SPLIT('127.0.0.1', '.')\",\n                \"duckdb\": \"SELECT CASE WHEN '.' IS NULL THEN NULL WHEN '.' = '' THEN ['127.0.0.1'] ELSE STR_SPLIT('127.0.0.1', '.') END\",\n            },\n        )\n        self.validate_identity(\"SELECT PI()\")\n        self.validate_identity(\"SELECT DEGREES(PI() / 3)\")\n        self.validate_identity(\"SELECT DEGREES(1)\")\n        self.validate_identity(\"SELECT RADIANS(180)\")\n        self.validate_all(\n            \"SELECT REGR_VALX(y, x)\",\n            write={\n                \"snowflake\": \"SELECT REGR_VALX(y, x)\",\n                \"duckdb\": \"SELECT CASE WHEN y IS NULL THEN CAST(NULL AS DOUBLE) ELSE x END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT REGR_VALY(y, x)\",\n            write={\n                \"snowflake\": \"SELECT REGR_VALY(y, x)\",\n                \"duckdb\": \"SELECT CASE WHEN x IS NULL THEN CAST(NULL AS DOUBLE) ELSE y END\",\n            },\n        )\n        self.validate_identity(\"SELECT REGR_AVGX(y, x)\")\n        self.validate_identity(\"SELECT REGR_AVGY(y, x)\")\n        self.validate_identity(\"SELECT REGR_COUNT(y, x)\")\n        self.validate_identity(\"SELECT REGR_INTERCEPT(y, x)\")\n        self.validate_identity(\"SELECT REGR_R2(y, x)\")\n        self.validate_identity(\"SELECT REGR_SXX(y, x)\")\n        self.validate_identity(\"SELECT REGR_SXY(y, x)\")\n        self.validate_identity(\"SELECT REGR_SYY(y, x)\")\n        self.validate_identity(\"SELECT REGR_SLOPE(y, x)\")\n\n        self.validate_all(\n            \"SELECT IS_ARRAY(PARSE_JSON('[1,2,3]'))\",\n            write={\n                \"snowflake\": \"SELECT IS_ARRAY(PARSE_JSON('[1,2,3]'))\",\n                \"duckdb\": \"SELECT JSON_TYPE(JSON('[1,2,3]')) = 'ARRAY'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT IFF(x > 5, 10, 20)\",\n            write={\n                \"snowflake\": \"SELECT IFF(x > 5, 10, 20)\",\n                \"duckdb\": \"SELECT CASE WHEN x > 5 THEN 10 ELSE 20 END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT IFF(col IS NULL, 0, col)\",\n            write={\n                \"snowflake\": \"SELECT IFF(col IS NULL, 0, col)\",\n                \"duckdb\": \"SELECT CASE WHEN col IS NULL THEN 0 ELSE col END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT VAR_SAMP(x)\",\n            write={\n                \"snowflake\": \"SELECT VARIANCE(x)\",\n                \"duckdb\": \"SELECT VARIANCE(x)\",\n                \"postgres\": \"SELECT VAR_SAMP(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT GREATEST(1, 2)\",\n            write={\n                \"snowflake\": \"SELECT GREATEST(1, 2)\",\n                \"duckdb\": \"SELECT CASE WHEN 1 IS NULL OR 2 IS NULL THEN NULL ELSE GREATEST(1, 2) END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT GREATEST_IGNORE_NULLS(1, 2)\",\n            write={\n                \"snowflake\": \"SELECT GREATEST_IGNORE_NULLS(1, 2)\",\n                \"duckdb\": \"SELECT GREATEST(1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LEAST(1, 2)\",\n            write={\n                \"snowflake\": \"SELECT LEAST(1, 2)\",\n                \"duckdb\": \"SELECT CASE WHEN 1 IS NULL OR 2 IS NULL THEN NULL ELSE LEAST(1, 2) END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LEAST_IGNORE_NULLS(1, 2)\",\n            write={\n                \"snowflake\": \"SELECT LEAST_IGNORE_NULLS(1, 2)\",\n                \"duckdb\": \"SELECT LEAST(1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT VAR_POP(x)\",\n            write={\n                \"snowflake\": \"SELECT VARIANCE_POP(x)\",\n                \"duckdb\": \"SELECT VAR_POP(x)\",\n                \"postgres\": \"SELECT VAR_POP(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SKEW(a)\",\n            write={\n                \"snowflake\": \"SELECT SKEW(a)\",\n                \"duckdb\": \"SELECT SKEWNESS(a)\",\n                \"spark\": \"SELECT SKEWNESS(a)\",\n                \"trino\": \"SELECT SKEWNESS(a)\",\n            },\n            read={\n                \"duckdb\": \"SELECT SKEWNESS(a)\",\n                \"spark\": \"SELECT SKEWNESS(a)\",\n                \"trino\": \"SELECT SKEWNESS(a)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANDOM()\",\n            write={\n                \"snowflake\": \"SELECT RANDOM()\",\n                \"duckdb\": \"SELECT CAST(-9.223372036854776E+18 + RANDOM() * (9.223372036854776e+18 - -9.223372036854776E+18) AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANDOM(123)\",\n            write={\n                \"snowflake\": \"SELECT RANDOM(123)\",\n                \"duckdb\": \"SELECT CAST(-9.223372036854776E+18 + RANDOM() * (9.223372036854776e+18 - -9.223372036854776E+18) AS BIGINT)\",\n            },\n        )\n        self.validate_identity(\"SELECT RANDSTR(123, 456)\")\n        self.validate_identity(\"SELECT RANDSTR(123, RANDOM())\")\n        self.validate_identity(\"SELECT NORMAL(0, 1, RANDOM())\")\n\n        self.validate_all(\n            \"IS_NULL_VALUE(x)\",\n            write={\n                \"duckdb\": \"JSON_TYPE(x) = 'NULL'\",\n                \"snowflake\": \"IS_NULL_VALUE(x)\",\n            },\n        )\n        # Test RANDSTR transpilation to DuckDB\n        self.validate_all(\n            \"SELECT RANDSTR(10, 123)\",\n            write={\n                \"snowflake\": \"SELECT RANDSTR(10, 123)\",\n                \"duckdb\": \"SELECT (SELECT LISTAGG(SUBSTRING('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 1 + CAST(FLOOR(random_value * 62) AS INT), 1), '') FROM (SELECT (ABS(HASH(i + 123)) % 1000) / 1000.0 AS random_value FROM RANGE(10) AS t(i)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANDSTR(10, RANDOM(123))\",\n            write={\n                \"snowflake\": \"SELECT RANDSTR(10, RANDOM(123))\",\n                \"duckdb\": \"SELECT (SELECT LISTAGG(SUBSTRING('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 1 + CAST(FLOOR(random_value * 62) AS INT), 1), '') FROM (SELECT (ABS(HASH(i + 123)) % 1000) / 1000.0 AS random_value FROM RANGE(10) AS t(i)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RANDSTR(10, RANDOM())\",\n            write={\n                \"snowflake\": \"SELECT RANDSTR(10, RANDOM())\",\n                \"duckdb\": \"SELECT (SELECT LISTAGG(SUBSTRING('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 1 + CAST(FLOOR(random_value * 62) AS INT), 1), '') FROM (SELECT (ABS(HASH(i + CAST(-9.223372036854776E+18 + RANDOM() * (9.223372036854776e+18 - -9.223372036854776E+18) AS BIGINT))) % 1000) / 1000.0 AS random_value FROM RANGE(10) AS t(i)))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT BOOLNOT(0)\",\n            write={\n                \"snowflake\": \"SELECT BOOLNOT(0)\",\n                \"duckdb\": \"SELECT NOT (ROUND(0, 0))\",\n            },\n        )\n\n        expr = self.validate_identity(\"RIGHT('GAJGSKD', 2)\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"RIGHT('GAJGSKD', 2)\")\n\n        expr = self.validate_identity(\"RIGHT(TO_BINARY('SNOWIKOPN', 'utf-8'), ABS(-3))\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"), \"UNHEX(RIGHT(HEX(ENCODE('SNOWIKOPN')), ABS(-3) * 2))\"\n        )\n\n        self.validate_all(\n            \"SELECT ZIPF(1, 10, 1234)\",\n            write={\n                \"duckdb\": \"SELECT (WITH rand AS (SELECT (ABS(HASH(1234)) % 1000000) / 1000000.0 AS r), weights AS (SELECT i, 1.0 / POWER(i, 1) AS w FROM RANGE(1, 10 + 1) AS t(i)), cdf AS (SELECT i, SUM(w) OVER (ORDER BY i NULLS FIRST) / SUM(w) OVER () AS p FROM weights) SELECT MIN(i) FROM cdf WHERE p >= (SELECT r FROM rand))\",\n                \"snowflake\": \"SELECT ZIPF(1, 10, 1234)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ZIPF(2, 100, RANDOM())\",\n            write={\n                \"duckdb\": \"SELECT (WITH rand AS (SELECT RANDOM() AS r), weights AS (SELECT i, 1.0 / POWER(i, 2) AS w FROM RANGE(1, 100 + 1) AS t(i)), cdf AS (SELECT i, SUM(w) OVER (ORDER BY i NULLS FIRST) / SUM(w) OVER () AS p FROM weights) SELECT MIN(i) FROM cdf WHERE p >= (SELECT r FROM rand))\",\n                \"snowflake\": \"SELECT ZIPF(2, 100, RANDOM())\",\n            },\n        )\n\n        self.validate_identity(\"SELECT GROUPING_ID(a, b) AS g_id FROM x GROUP BY ROLLUP (a, b)\")\n        self.validate_identity(\"PARSE_URL('https://example.com/path')\")\n        self.validate_identity(\"PARSE_URL('https://example.com/path', 1)\")\n        self.validate_identity(\"SELECT XMLGET(object_col, 'level2')\")\n        self.validate_identity(\"SELECT XMLGET(object_col, 'level3', 1)\")\n        self.validate_identity(\"SELECT {*} FROM my_table\")\n        self.validate_identity(\"SELECT {my_table.*} FROM my_table\")\n        self.validate_identity(\"SELECT {* ILIKE 'col1%'} FROM my_table\")\n        self.validate_identity(\"SELECT {* EXCLUDE (col1)} FROM my_table\")\n        self.validate_identity(\"SELECT {* EXCLUDE (col1, col2)} FROM my_table\")\n        self.validate_identity(\"SELECT a, b, COUNT(*) FROM x GROUP BY ALL LIMIT 100\")\n        self.validate_identity(\"STRTOK_TO_ARRAY('a b c')\")\n        self.validate_identity(\"STRTOK_TO_ARRAY('a.b.c', '.')\")\n        self.validate_identity(\"GET(a, b)\")\n        self.validate_identity(\"INSERT INTO test VALUES (x'48FAF43B0AFCEF9B63EE3A93EE2AC2')\")\n        self.validate_identity(\"SELECT STAR(tbl, exclude := [foo])\")\n        self.validate_identity(\"SELECT CAST([1, 2, 3] AS VECTOR(FLOAT, 3))\")\n        self.validate_identity(\"SELECT VECTOR_COSINE_SIMILARITY(a, b)\")\n        self.validate_identity(\"SELECT VECTOR_INNER_PRODUCT(a, b)\")\n        self.validate_identity(\"SELECT VECTOR_L1_DISTANCE(a, b)\")\n        self.validate_identity(\"SELECT VECTOR_L2_DISTANCE(a, b)\")\n        self.validate_identity(\"SELECT CONNECT_BY_ROOT test AS test_column_alias\")\n        self.validate_identity(\"SELECT number\").selects[0].assert_is(exp.Column)\n        self.validate_identity(\"INTERVAL '4 years, 5 months, 3 hours'\")\n        self.validate_identity(\"ALTER TABLE table1 CLUSTER BY (name DESC)\")\n        self.validate_identity(\"SELECT rename, replace\")\n        self.validate_identity(\"SELECT TIMEADD(HOUR, 2, CAST('09:05:03' AS TIME))\")\n        self.validate_identity(\"SELECT CAST(OBJECT_CONSTRUCT('a', 1) AS MAP(VARCHAR, INT))\")\n        self.validate_identity(\n            \"SELECT MAP_CAT(CAST(col AS MAP(VARCHAR, VARCHAR)), CAST(col AS MAP(VARCHAR, VARCHAR)))\"\n        )\n        self.validate_all(\n            \"SELECT MAP_CAT(CAST(m1 AS MAP(VARCHAR, INT)), CAST(m2 AS MAP(VARCHAR, INT)))\",\n            write={\n                \"duckdb\": \"SELECT CASE WHEN CAST(m1 AS MAP(TEXT, INT)) IS NULL OR CAST(m2 AS MAP(TEXT, INT)) IS NULL THEN NULL ELSE MAP_FROM_ENTRIES(LIST_FILTER(LIST_TRANSFORM(LIST_DISTINCT(LIST_CONCAT(MAP_KEYS(CAST(m1 AS MAP(TEXT, INT))), MAP_KEYS(CAST(m2 AS MAP(TEXT, INT))))), __k -> STRUCT_PACK(key := __k, value := COALESCE(CAST(m2 AS MAP(TEXT, INT))[__k], CAST(m1 AS MAP(TEXT, INT))[__k]))), __x -> NOT __x.value IS NULL)) END\",\n                \"snowflake\": \"SELECT MAP_CAT(CAST(m1 AS MAP(VARCHAR, INT)), CAST(m2 AS MAP(VARCHAR, INT)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MAP_CAT(CAST(OBJECT_CONSTRUCT() AS MAP(VARCHAR, INT)), CAST(OBJECT_CONSTRUCT('a', 1) AS MAP(VARCHAR, INT)))\",\n            write={\n                \"duckdb\": \"SELECT CASE WHEN CAST(MAP() AS MAP(TEXT, INT)) IS NULL OR CAST({'a': 1} AS MAP(TEXT, INT)) IS NULL THEN NULL ELSE MAP_FROM_ENTRIES(LIST_FILTER(LIST_TRANSFORM(LIST_DISTINCT(LIST_CONCAT(MAP_KEYS(CAST(MAP() AS MAP(TEXT, INT))), MAP_KEYS(CAST({'a': 1} AS MAP(TEXT, INT))))), __k -> STRUCT_PACK(key := __k, value := COALESCE(CAST({'a': 1} AS MAP(TEXT, INT))[__k], CAST(MAP() AS MAP(TEXT, INT))[__k]))), __x -> NOT __x.value IS NULL)) END\",\n                \"snowflake\": \"SELECT MAP_CAT(CAST(OBJECT_CONSTRUCT() AS MAP(VARCHAR, INT)), CAST(OBJECT_CONSTRUCT('a', 1) AS MAP(VARCHAR, INT)))\",\n            },\n        )\n        self.validate_identity(\"SELECT MAP_CONTAINS_KEY('k1', CAST(col AS MAP(VARCHAR, VARCHAR)))\")\n        self.validate_identity(\"SELECT MAP_DELETE(CAST(col AS MAP(VARCHAR, VARCHAR)), 'k1')\")\n        self.validate_identity(\"SELECT MAP_INSERT(CAST(col AS MAP(VARCHAR, VARCHAR)), 'b', '2')\")\n        self.validate_identity(\"SELECT MAP_KEYS(CAST(col AS MAP(VARCHAR, VARCHAR)))\")\n        self.validate_identity(\"SELECT MAP_PICK(CAST(col AS MAP(VARCHAR, VARCHAR)), 'a', 'c')\")\n        self.validate_identity(\"SELECT MAP_SIZE(CAST(col AS MAP(VARCHAR, VARCHAR)))\")\n        self.validate_identity(\"SELECT CAST(OBJECT_CONSTRUCT('a', 1) AS OBJECT(a CHAR NOT NULL))\")\n        self.validate_identity(\"SELECT CAST([1, 2, 3] AS ARRAY(INT))\")\n        self.validate_identity(\"SELECT CAST(obj AS OBJECT(x CHAR) RENAME FIELDS)\")\n        self.validate_identity(\"SELECT CAST(obj AS OBJECT(x CHAR, y VARCHAR) ADD FIELDS)\")\n        self.validate_identity(\"SELECT TO_TIMESTAMP(123.4)\").selects[0].assert_is(exp.Anonymous)\n        self.validate_identity(\"SELECT TO_TIMESTAMP(x) FROM t\")\n        self.validate_identity(\"SELECT TO_TIMESTAMP_NTZ(x) FROM t\")\n        self.validate_identity(\"SELECT TO_TIMESTAMP_LTZ(x) FROM t\")\n        self.validate_identity(\"SELECT TO_TIMESTAMP_TZ(x) FROM t\")\n        self.validate_identity(\"TO_DECIMAL(expr)\", \"TO_NUMBER(expr)\")\n        self.validate_identity(\"TO_DECIMAL(expr, fmt)\", \"TO_NUMBER(expr, fmt)\")\n        self.validate_identity(\n            \"TO_DECIMAL(expr, fmt, precision, scale)\", \"TO_NUMBER(expr, fmt, precision, scale)\"\n        )\n        self.validate_identity(\"TO_NUMBER(expr)\")\n        self.validate_identity(\"TO_NUMBER(expr, fmt)\")\n        self.validate_identity(\"TO_NUMBER(expr, fmt, precision, scale)\")\n        self.validate_identity(\"TO_DECFLOAT('123.456')\")\n        self.validate_identity(\"TO_DECFLOAT('1,234.56', '999,999.99')\")\n        self.validate_identity(\"TRY_TO_DECFLOAT('123.456')\")\n        self.validate_identity(\"TRY_TO_DECFLOAT('1,234.56', '999,999.99')\")\n        self.validate_all(\n            \"TRY_TO_BOOLEAN('true')\",\n            write={\n                \"snowflake\": \"TRY_TO_BOOLEAN('true')\",\n                \"duckdb\": \"CASE WHEN UPPER(CAST('true' AS TEXT)) = 'ON' THEN TRUE WHEN UPPER(CAST('true' AS TEXT)) = 'OFF' THEN FALSE ELSE TRY_CAST('true' AS BOOLEAN) END\",\n            },\n        )\n\n        self.validate_identity(\"TRY_TO_DECIMAL('123.45')\", \"TRY_TO_NUMBER('123.45')\")\n        self.validate_identity(\n            \"TRY_TO_DECIMAL('123.45', '999.99')\", \"TRY_TO_NUMBER('123.45', '999.99')\"\n        )\n        self.validate_identity(\n            \"TRY_TO_DECIMAL('123.45', '999.99', 10, 2)\", \"TRY_TO_NUMBER('123.45', '999.99', 10, 2)\"\n        )\n        self.validate_all(\n            \"TRY_TO_DOUBLE('123.456')\",\n            write={\n                \"snowflake\": \"TRY_TO_DOUBLE('123.456')\",\n                \"duckdb\": \"TRY_CAST('123.456' AS DOUBLE)\",\n            },\n        )\n        self.validate_identity(\"TRY_TO_DOUBLE('123.456', '999.99')\")\n        self.validate_all(\n            \"TRY_TO_DOUBLE('-4.56E-03', 'S9.99EEEE')\",\n            write={\n                \"snowflake\": \"TRY_TO_DOUBLE('-4.56E-03', 'S9.99EEEE')\",\n                \"duckdb\": UnsupportedError,\n            },\n        )\n        self.validate_identity(\"TO_FILE(object_col)\")\n        self.validate_identity(\"TO_FILE('file.csv')\")\n        self.validate_identity(\"TO_FILE('file.csv', 'relativepath/')\")\n        self.validate_identity(\"TRY_TO_FILE(object_col)\")\n        self.validate_identity(\"TRY_TO_FILE('file.csv')\")\n        self.validate_identity(\"TRY_TO_FILE('file.csv', 'relativepath/')\")\n        self.validate_identity(\"TRY_TO_NUMBER('123.45')\")\n        self.validate_identity(\"TRY_TO_NUMBER('123.45', '999.99')\")\n        self.validate_identity(\"TRY_TO_NUMBER('123.45', '999.99', 10, 2)\")\n        self.validate_identity(\"TO_NUMERIC('123.45')\", \"TO_NUMBER('123.45')\")\n        self.validate_identity(\"TO_NUMERIC('123.45', '999.99')\", \"TO_NUMBER('123.45', '999.99')\")\n        self.validate_identity(\n            \"TO_NUMERIC('123.45', '999.99', 10, 2)\", \"TO_NUMBER('123.45', '999.99', 10, 2)\"\n        )\n        self.validate_identity(\"TRY_TO_NUMERIC('123.45')\", \"TRY_TO_NUMBER('123.45')\")\n        self.validate_identity(\n            \"TRY_TO_NUMERIC('123.45', '999.99')\", \"TRY_TO_NUMBER('123.45', '999.99')\"\n        )\n        self.validate_identity(\n            \"TRY_TO_NUMERIC('123.45', '999.99', 10, 2)\", \"TRY_TO_NUMBER('123.45', '999.99', 10, 2)\"\n        )\n        self.validate_all(\n            \"TRY_TO_TIME('12:30:00')\",\n            write={\n                \"snowflake\": \"TRY_CAST('12:30:00' AS TIME)\",\n                \"duckdb\": \"TRY_CAST('12:30:00' AS TIME)\",\n            },\n        )\n        self.validate_identity(\"TRY_TO_TIME('12:30:00', 'AUTO')\")\n        self.validate_all(\n            \"TRY_TO_TIMESTAMP('2024-01-15 12:30:00')\",\n            write={\n                \"snowflake\": \"TRY_CAST('2024-01-15 12:30:00' AS TIMESTAMP)\",\n                \"duckdb\": \"TRY_CAST('2024-01-15 12:30:00' AS TIMESTAMP)\",\n            },\n        )\n        self.validate_identity(\"TRY_TO_TIMESTAMP('2024-01-15 12:30:00', 'AUTO')\")\n        self.validate_identity(\"ALTER TABLE authors ADD CONSTRAINT c1 UNIQUE (id, email)\")\n        self.validate_identity(\"RM @parquet_stage\", check_command_warning=True)\n        self.validate_identity(\"REMOVE @parquet_stage\", check_command_warning=True)\n        self.validate_identity(\"SELECT TIMESTAMP_FROM_PARTS(2024, 5, 9, 14, 30, 45)\")\n        self.validate_identity(\"SELECT TIMESTAMP_FROM_PARTS(2024, 5, 9, 14, 30, 45, 123)\")\n        self.validate_identity(\"SELECT TIMESTAMP_LTZ_FROM_PARTS(2013, 4, 5, 12, 00, 00)\")\n        self.validate_identity(\"SELECT TIMESTAMP_TZ_FROM_PARTS(2013, 4, 5, 12, 00, 00)\")\n        self.validate_identity(\n            \"SELECT TIMESTAMP_TZ_FROM_PARTS(2013, 4, 5, 12, 00, 00, 0, 'America/Los_Angeles')\"\n        )\n        self.validate_identity(\n            \"SELECT TIMESTAMP_FROM_PARTS(CAST('2024-05-09' AS DATE), CAST('14:30:45' AS TIME))\"\n        )\n        self.validate_identity(\n            \"SELECT TIMESTAMP_NTZ_FROM_PARTS(TO_DATE('2013-04-05'), TO_TIME('12:00:00'))\",\n            \"SELECT TIMESTAMP_FROM_PARTS(CAST('2013-04-05' AS DATE), CAST('12:00:00' AS TIME))\",\n        )\n        self.validate_identity(\n            \"SELECT TIMESTAMP_NTZ_FROM_PARTS(2013, 4, 5, 12, 00, 00, 987654321)\",\n            \"SELECT TIMESTAMP_FROM_PARTS(2013, 4, 5, 12, 00, 00, 987654321)\",\n        )\n\n        self.validate_identity(\"SELECT DATE_FROM_PARTS(1977, 8, 7)\")\n        self.validate_identity(\"SELECT GET_PATH(v, 'attr[0].name') FROM vartab\")\n        self.validate_identity(\"SELECT TO_ARRAY(CAST(x AS ARRAY))\")\n        self.validate_identity(\"SELECT TO_ARRAY(CAST(['test'] AS VARIANT))\")\n        self.validate_identity(\"SELECT ARRAY_UNIQUE_AGG(x)\")\n        self.validate_identity(\"SELECT ARRAY_APPEND([1, 2, 3], 4)\")\n        self.validate_identity(\"SELECT ARRAY_CAT([1, 2], [3, 4])\")\n        self.validate_identity(\"SELECT ARRAY_PREPEND([2, 3, 4], 1)\")\n        self.validate_identity(\"SELECT ARRAY_REMOVE([1, 2, 3], 2)\")\n        self.validate_identity(\"SELECT ARRAYS_ZIP([1, 2, 3])\")\n        self.validate_identity(\"SELECT ARRAYS_ZIP([1, 2, 3], ['a', 'b', 'c'], [10, 20, 30])\")\n        self.validate_identity(\"SELECT AI_AGG(review, 'Summarize the reviews')\")\n        self.validate_identity(\"SELECT AI_SUMMARIZE_AGG(review)\")\n        self.validate_identity(\"SELECT AI_CLASSIFY('text', ['travel', 'cooking'])\")\n        self.validate_identity(\"SELECT OBJECT_CONSTRUCT()\")\n        self.validate_identity(\"SELECT CURRENT_ACCOUNT()\")\n        self.validate_identity(\"SELECT CURRENT_ACCOUNT_NAME()\")\n        self.validate_identity(\"SELECT CURRENT_AVAILABLE_ROLES()\")\n        self.validate_identity(\"SELECT CURRENT_CLIENT()\")\n        self.validate_identity(\"SELECT CURRENT_IP_ADDRESS()\")\n        self.validate_identity(\"SELECT CURRENT_DATABASE()\")\n        self.validate_identity(\"SELECT CURRENT_SCHEMAS()\")\n        self.validate_identity(\"SELECT CURRENT_SECONDARY_ROLES()\")\n        self.validate_identity(\"SELECT CURRENT_SESSION()\")\n        self.validate_identity(\"SELECT CURRENT_STATEMENT()\")\n        self.validate_identity(\"SELECT CURRENT_VERSION()\")\n        self.validate_identity(\"SELECT CURRENT_TRANSACTION()\")\n        self.validate_identity(\"SELECT CURRENT_WAREHOUSE()\")\n        self.validate_identity(\"SELECT CURRENT_ORGANIZATION_USER()\")\n        self.validate_identity(\"SELECT CURRENT_REGION()\")\n        self.validate_identity(\"SELECT CURRENT_ROLE()\")\n        self.validate_identity(\"SELECT CURRENT_ROLE_TYPE()\")\n        self.validate_identity(\"SELECT DAY(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"SELECT DAYOFMONTH(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"SELECT DAYOFYEAR(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"SELECT MONTH(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"SELECT QUARTER(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"SELECT WEEK(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"SELECT WEEKISO(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"WEEKOFYEAR(tstamp)\", \"WEEK(tstamp)\")\n        self.validate_identity(\"SELECT YEAR(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"SELECT YEAROFWEEK(CURRENT_TIMESTAMP())\")\n        self.validate_identity(\"SELECT YEAROFWEEKISO(CURRENT_TIMESTAMP())\")\n        self.validate_all(\n            \"SELECT DAYOFWEEKISO('2024-01-15'::DATE)\",\n            write={\n                \"snowflake\": \"SELECT DAYOFWEEKISO(CAST('2024-01-15' AS DATE))\",\n                \"duckdb\": \"SELECT ISODOW(CAST('2024-01-15' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT YEAROFWEEK('2024-12-31'::DATE)\",\n            write={\n                \"snowflake\": \"SELECT YEAROFWEEK(CAST('2024-12-31' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(ISOYEAR FROM CAST('2024-12-31' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT YEAROFWEEKISO('2024-12-31'::DATE)\",\n            write={\n                \"snowflake\": \"SELECT YEAROFWEEKISO(CAST('2024-12-31' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(ISOYEAR FROM CAST('2024-12-31' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT WEEKISO('2024-01-15'::DATE)\",\n            write={\n                \"snowflake\": \"SELECT WEEKISO(CAST('2024-01-15' AS DATE))\",\n                \"duckdb\": \"SELECT WEEKOFYEAR(CAST('2024-01-15' AS DATE))\",\n            },\n        )\n        self.validate_identity(\"SELECT SUM(amount) FROM mytable GROUP BY ALL\")\n        self.validate_identity(\"SELECT STDDEV(x)\")\n        self.validate_identity(\"SELECT STDDEV(x) OVER (PARTITION BY 1)\")\n        self.validate_identity(\"SELECT STDDEV_POP(x)\")\n        self.validate_identity(\"SELECT STDDEV_POP(x) OVER (PARTITION BY 1)\")\n        self.validate_identity(\"SELECT STDDEV_SAMP(x)\", \"SELECT STDDEV(x)\")\n        self.validate_identity(\n            \"SELECT STDDEV_SAMP(x) OVER (PARTITION BY 1)\", \"SELECT STDDEV(x) OVER (PARTITION BY 1)\"\n        )\n        self.validate_identity(\"SELECT KURTOSIS(x)\")\n        self.validate_identity(\"SELECT KURTOSIS(x) OVER (PARTITION BY 1)\")\n        self.validate_identity(\"WITH x AS (SELECT 1 AS foo) SELECT foo FROM IDENTIFIER('x')\")\n        self.validate_identity(\"WITH x AS (SELECT 1 AS foo) SELECT IDENTIFIER('foo') FROM x\")\n        self.validate_identity(\"INITCAP('iqamqinterestedqinqthisqtopic', 'q')\")\n        self.validate_identity(\"OBJECT_CONSTRUCT(*)\")\n        self.validate_identity(\"SELECT CAST('2021-01-01' AS DATE) + INTERVAL '1 DAY'\")\n        self.validate_identity(\"SELECT HLL(*)\")\n        self.validate_identity(\"SELECT HLL(a)\")\n        self.validate_identity(\"SELECT HLL(DISTINCT t.a)\")\n        self.validate_identity(\"SELECT HLL(a, b, c)\")\n        self.validate_identity(\"SELECT HLL(DISTINCT a, b, c)\")\n        self.validate_identity(\"$x\")  # parameter\n        self.validate_identity(\"a$b\")  # valid snowflake identifier\n        self.validate_identity(\"SELECT REGEXP_LIKE(a, b, c)\")\n        self.validate_identity(\"CREATE TABLE foo (bar DOUBLE AUTOINCREMENT START 0 INCREMENT 1)\")\n        self.validate_identity(\"COMMENT IF EXISTS ON TABLE foo IS 'bar'\")\n        self.validate_identity(\"SELECT CONVERT_TIMEZONE('UTC', 'America/Los_Angeles', col)\")\n        self.validate_identity(\"SELECT CURRENT_ORGANIZATION_NAME()\")\n        self.validate_identity(\"ALTER TABLE a SWAP WITH b\")\n        self.validate_identity(\"SELECT MATCH_CONDITION\")\n        self.validate_identity(\"SELECT OBJECT_AGG(key, value) FROM tbl\")\n        self.validate_identity(\"1 /* /* */\", \"1 /* / * */\")\n        self.validate_identity(\"TO_TIMESTAMP(col, fmt)\")\n        self.validate_identity(\"SELECT TO_CHAR(CAST('12:05:05' AS TIME))\")\n        self.validate_identity(\"SELECT TRIM(COALESCE(TO_CHAR(CAST(c AS TIME)), '')) FROM t\")\n        self.validate_identity(\"SELECT GET_PATH(PARSE_JSON(foo), 'bar')\")\n        self.validate_identity(\"SELECT PARSE_IP('192.168.1.1', 'INET')\")\n        self.validate_identity(\"SELECT PARSE_IP('192.168.1.1', 'INET', 0)\")\n        self.validate_identity(\"SELECT GET_PATH(foo, 'bar')\")\n        self.validate_identity(\"SELECT a, exclude, b FROM xxx\")\n        self.validate_identity(\"SELECT ARRAY_SORT(x, TRUE, FALSE)\")\n        self.validate_all(\n            \"SELECT ARRAY_SORT(x)\",\n            read={\"snowflake\": \"SELECT ARRAY_SORT(x)\"},\n            write={\n                \"duckdb\": \"SELECT LIST_SORT(x)\",\n                \"snowflake\": \"SELECT ARRAY_SORT(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_SORT(x, FALSE)\",\n            read={\"snowflake\": \"SELECT ARRAY_SORT(x, FALSE)\"},\n            write={\n                \"duckdb\": \"SELECT LIST_SORT(x, 'DESC', 'NULLS FIRST')\",\n                \"snowflake\": \"SELECT ARRAY_SORT(x, FALSE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_SORT(x, foo, TRUE)\",\n            read={\"snowflake\": \"SELECT ARRAY_SORT(x, foo, TRUE)\"},\n            write={\n                \"duckdb\": \"SELECT LIST_SORT(x, foo, 'NULLS FIRST')\",\n                \"snowflake\": \"SELECT ARRAY_SORT(x, foo, TRUE)\",\n            },\n        )\n        self.validate_identity(\"SELECT BOOLXOR_AGG(col) FROM tbl\")\n        self.validate_identity(\n            \"SELECT PERCENTILE_DISC(0.9) WITHIN GROUP (ORDER BY col) OVER (PARTITION BY category)\"\n        )\n        self.validate_identity(\n            \"SELECT DATEADD(DAY, -7, DATEADD(t.m, 1, CAST('2023-01-03' AS DATE))) FROM (SELECT 'month' AS m) AS t\"\n        ).selects[0].this.unit.assert_is(exp.Column)\n\n        self.validate_all(\n            \"SELECT STRTOK('a$b$c', SUBSTRING('.$^', 1, 2), 2)\",\n            write={\n                \"snowflake\": \"SELECT STRTOK('a$b$c', SUBSTRING('.$^', 1, 2), 2)\",\n                \"duckdb\": r\"\"\"SELECT CASE WHEN SUBSTRING('.$^', 1, 2) = '' AND 'a$b$c' = '' THEN NULL WHEN SUBSTRING('.$^', 1, 2) = '' AND 2 = 1 THEN 'a$b$c' WHEN SUBSTRING('.$^', 1, 2) = '' THEN NULL WHEN 2 < 0 THEN NULL WHEN 'a$b$c' IS NULL OR SUBSTRING('.$^', 1, 2) IS NULL OR 2 IS NULL THEN NULL ELSE LIST_FILTER(REGEXP_SPLIT_TO_ARRAY('a$b$c', CASE WHEN SUBSTRING('.$^', 1, 2) = '' THEN '' ELSE '[' || REGEXP_REPLACE(SUBSTRING('.$^', 1, 2), '([\\[\\]^.\\-*+?(){}|$\\\\])', '\\\\\\1', 'g') || ']' END), x -> NOT x = '')[2] END\"\"\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT STRTOK('a$b/cg', '$/.')\",\n            write={\n                \"snowflake\": \"SELECT STRTOK('a$b/cg', '$/.', 1)\",\n                \"duckdb\": r\"\"\"SELECT CASE WHEN '$/.' = '' AND 'a$b/cg' = '' THEN NULL WHEN '$/.' = '' AND 1 = 1 THEN 'a$b/cg' WHEN '$/.' = '' THEN NULL WHEN 1 < 0 THEN NULL WHEN 'a$b/cg' IS NULL OR '$/.' IS NULL OR 1 IS NULL THEN NULL ELSE LIST_FILTER(REGEXP_SPLIT_TO_ARRAY('a$b/cg', CASE WHEN '$/.' = '' THEN '' ELSE '[' || REGEXP_REPLACE('$/.', '([\\[\\]^.\\-*+?(){}|$\\\\])', '\\\\\\1', 'g') || ']' END), x -> NOT x = '')[1] END\"\"\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT STRTOK('ab')\",\n            write={\n                \"snowflake\": \"SELECT STRTOK('ab', ' ', 1)\",\n                \"duckdb\": r\"\"\"SELECT CASE WHEN ' ' = '' AND 'ab' = '' THEN NULL WHEN ' ' = '' AND 1 = 1 THEN 'ab' WHEN ' ' = '' THEN NULL WHEN 1 < 0 THEN NULL WHEN 'ab' IS NULL OR ' ' IS NULL OR 1 IS NULL THEN NULL ELSE LIST_FILTER(REGEXP_SPLIT_TO_ARRAY('ab', CASE WHEN ' ' = '' THEN '' ELSE '[' || REGEXP_REPLACE(' ', '([\\[\\]^.\\-*+?(){}|$\\\\])', '\\\\\\1', 'g') || ']' END), x -> NOT x = '')[1] END\"\"\",\n            },\n        )\n\n        self.validate_identity(\"SELECT FILE_URL FROM DIRECTORY(@mystage) WHERE SIZE > 100000\").args[\n            \"from_\"\n        ].this.this.assert_is(exp.DirectoryStage).this.assert_is(exp.Var)\n        self.validate_identity(\n            \"SELECT AI_CLASSIFY('text', ['travel', 'cooking'], OBJECT_CONSTRUCT('output_mode', 'multi'))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM table AT (TIMESTAMP => '2024-07-24') UNPIVOT(a FOR b IN (c)) AS pivot_table\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN ('2023_Q1', '2023_Q2', '2023_Q3', '2023_Q4', '2024_Q1') DEFAULT ON NULL (0)) ORDER BY empid\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (SELECT DISTINCT quarter FROM ad_campaign_types_by_quarter WHERE television = TRUE ORDER BY quarter)) ORDER BY empid\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY ORDER BY quarter)) ORDER BY empid\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY)) ORDER BY empid\"\n        )\n        self.validate_identity(\n            \"MERGE INTO my_db AS ids USING (SELECT new_id FROM my_model WHERE NOT col IS NULL) AS new_ids ON ids.type = new_ids.type AND ids.source = new_ids.source WHEN NOT MATCHED THEN INSERT VALUES (new_ids.new_id)\"\n        )\n        self.validate_identity(\n            \"INSERT OVERWRITE TABLE t SELECT 1\", \"INSERT OVERWRITE INTO t SELECT 1\"\n        )\n        self.validate_identity(\n            'DESCRIBE TABLE \"SNOWFLAKE_SAMPLE_DATA\".\"TPCDS_SF100TCL\".\"WEB_SITE\" type=stage'\n        )\n        self.validate_identity(\n            \"SELECT * FROM DATA AS DATA_L ASOF JOIN DATA AS DATA_R MATCH_CONDITION (DATA_L.VAL > DATA_R.VAL) ON DATA_L.ID = DATA_R.ID\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT TO_TIMESTAMP('2025-01-16T14:45:30.123+0500', 'yyyy-mm-DDThh24:mi:ss.ff9tzhtzm')\"\"\"\n        )\n        self.validate_identity(\n            \"SELECT * REPLACE (CAST(col AS TEXT) AS scol) FROM t\",\n            \"SELECT * REPLACE (CAST(col AS VARCHAR) AS scol) FROM t\",\n        )\n        self.validate_identity(\n            \"GET(value, 'foo')::VARCHAR\",\n            \"CAST(GET(value, 'foo') AS VARCHAR)\",\n        )\n        self.validate_identity(\n            \"SELECT 1 put\",\n            \"SELECT 1 AS put\",\n        )\n        self.validate_identity(\n            \"SELECT 1 get\",\n            \"SELECT 1 AS get\",\n        )\n        self.validate_identity(\n            \"WITH t (SELECT 1 AS c) SELECT c FROM t\",\n            \"WITH t AS (SELECT 1 AS c) SELECT c FROM t\",\n        )\n        self.validate_identity(\n            \"GET_PATH(json_data, '$id')\",\n            \"\"\"GET_PATH(json_data, '[\"$id\"]')\"\"\",\n        )\n        self.validate_identity(\n            \"CAST(x AS GEOGRAPHY)\",\n            \"TO_GEOGRAPHY(x)\",\n        )\n        self.validate_identity(\n            \"CAST(x AS GEOMETRY)\",\n            \"TO_GEOMETRY(x)\",\n        )\n        self.validate_identity(\"TO_GEOGRAPHY(x)\")\n        self.validate_identity(\"TO_GEOMETRY(x)\")\n        self.validate_identity(\"TO_GEOGRAPHY(x, y)\")\n        self.validate_identity(\"TO_GEOMETRY(x, y)\")\n        self.validate_identity(\n            \"transform(x, a int -> a + a + 1)\",\n            \"TRANSFORM(x, a -> CAST(a AS INT) + CAST(a AS INT) + 1)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM s WHERE c NOT IN (1, 2, 3)\",\n            \"SELECT * FROM s WHERE NOT c IN (1, 2, 3)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM s WHERE c NOT IN (SELECT * FROM t)\",\n            \"SELECT * FROM s WHERE c <> ALL (SELECT * FROM t)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1 INNER JOIN t2 USING (t1.col)\",\n            \"SELECT * FROM t1 INNER JOIN t2 USING (col)\",\n        )\n        self.validate_identity(\n            \"CURRENT_TIMESTAMP - INTERVAL '1 w' AND (1 = 1)\",\n            \"CURRENT_TIMESTAMP() - INTERVAL '1 WEEK' AND (1 = 1)\",\n        )\n        self.validate_identity(\n            \"REGEXP_REPLACE('target', 'pattern', '\\n')\",\n            \"REGEXP_REPLACE('target', 'pattern', '\\\\n')\",\n        )\n        self.validate_identity(\n            \"SELECT a:from::STRING, a:from || ' test' \",\n            \"SELECT CAST(GET_PATH(a, 'from') AS VARCHAR), GET_PATH(a, 'from') || ' test'\",\n        )\n        self.validate_identity(\n            \"SELECT a:select\",\n            \"SELECT GET_PATH(a, 'select')\",\n        )\n        self.validate_identity(\"x:from\", \"GET_PATH(x, 'from')\")\n        self.validate_identity(\n            \"value:values::string::int\",\n            \"CAST(CAST(GET_PATH(value, 'values') AS VARCHAR) AS INT)\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT GET_PATH(PARSE_JSON('{\"y\": [{\"z\": 1}]}'), 'y[0]:z')\"\"\",\n            \"\"\"SELECT GET_PATH(PARSE_JSON('{\"y\": [{\"z\": 1}]}'), 'y[0].z')\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT p FROM t WHERE p:val NOT IN ('2')\",\n            \"SELECT p FROM t WHERE NOT GET_PATH(p, 'val') IN ('2')\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT PARSE_JSON('{\"x\": \"hello\"}'):x LIKE 'hello'\"\"\",\n            \"\"\"SELECT GET_PATH(PARSE_JSON('{\"x\": \"hello\"}'), 'x') LIKE 'hello'\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT data:x LIKE 'hello' FROM some_table\"\"\",\n            \"\"\"SELECT GET_PATH(data, 'x') LIKE 'hello' FROM some_table\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT SUM({ fn CONVERT(123, SQL_DOUBLE) })\",\n            \"SELECT SUM(CAST(123 AS DOUBLE))\",\n        )\n        self.validate_identity(\n            \"SELECT SUM({ fn CONVERT(123, SQL_VARCHAR) })\",\n            \"SELECT SUM(CAST(123 AS VARCHAR))\",\n        )\n        self.validate_identity(\n            \"SELECT TIMESTAMPFROMPARTS(d, t)\",\n            \"SELECT TIMESTAMP_FROM_PARTS(d, t)\",\n        )\n        self.validate_identity(\n            \"SELECT v:attr[0].name FROM vartab\",\n            \"SELECT GET_PATH(v, 'attr[0].name') FROM vartab\",\n        )\n        self.validate_identity(\n            'SELECT v:\"fruit\" FROM vartab',\n            \"\"\"SELECT GET_PATH(v, 'fruit') FROM vartab\"\"\",\n        )\n        self.validate_identity(\n            \"v:attr[0]:name\",\n            \"GET_PATH(v, 'attr[0].name')\",\n        )\n        self.validate_identity(\n            \"a.x:from.b:c.d::int\",\n            \"CAST(GET_PATH(a.x, 'from.b.c.d') AS INT)\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT PARSE_JSON('{\"food\":{\"fruit\":\"banana\"}}'):food.fruit::VARCHAR\"\"\",\n            \"\"\"SELECT CAST(GET_PATH(PARSE_JSON('{\"food\":{\"fruit\":\"banana\"}}'), 'food.fruit') AS VARCHAR)\"\"\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t, UNNEST(x) WITH ORDINALITY\",\n            \"SELECT * FROM t, TABLE(FLATTEN(INPUT => x)) AS _t0(seq, key, path, index, value, this)\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE foo (ID INT COMMENT $$some comment$$)\",\n            \"CREATE TABLE foo (ID INT COMMENT 'some comment')\",\n        )\n        self.validate_identity(\n            \"SELECT state, city, SUM(retail_price * quantity) AS gross_revenue FROM sales GROUP BY ALL\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM foo window\",\n            \"SELECT * FROM foo AS window\",\n        )\n        self.validate_identity(\n            r\"SELECT RLIKE(a, $$regular expression with \\ characters: \\d{2}-\\d{3}-\\d{4}$$, 'i') FROM log_source\",\n            r\"SELECT REGEXP_LIKE(a, 'regular expression with \\\\ characters: \\\\d{2}-\\\\d{3}-\\\\d{4}', 'i') FROM log_source\",\n        )\n        self.validate_identity(\n            r\"SELECT $$a ' \\ \\t \\x21 z $ $$\",\n            r\"SELECT 'a \\' \\\\ \\\\t \\\\x21 z $ '\",\n        )\n        self.validate_identity(\n            \"SELECT {'test': 'best'}::VARIANT\",\n            \"SELECT CAST(OBJECT_CONSTRUCT('test', 'best') AS VARIANT)\",\n        )\n        self.validate_identity(\n            \"SELECT {fn DAYNAME('2022-5-13')}\",\n            \"SELECT DAYNAME('2022-5-13')\",\n        )\n        self.validate_identity(\n            \"SELECT {fn LOG(5)}\",\n            \"SELECT LN(5)\",\n        )\n        self.validate_identity(\n            \"SELECT {fn CEILING(5.3)}\",\n            \"SELECT CEIL(5.3)\",\n        )\n        self.validate_identity(\n            \"SELECT CEIL(3.14)\",\n        )\n        self.validate_identity(\n            \"SELECT CEIL(3.14, 1)\",\n        )\n        self.validate_identity(\n            \"CAST(x AS BYTEINT)\",\n            \"CAST(x AS INT)\",\n        )\n        self.validate_identity(\n            \"CAST(x AS CHAR VARYING)\",\n            \"CAST(x AS VARCHAR)\",\n        )\n        self.validate_identity(\n            \"CAST(x AS CHARACTER VARYING)\",\n            \"CAST(x AS VARCHAR)\",\n        )\n        self.validate_identity(\n            \"CAST(x AS NCHAR VARYING)\",\n            \"CAST(x AS VARCHAR)\",\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE TEMPORARY TABLE x (y NUMBER IDENTITY(0, 1))\",\n            \"CREATE OR REPLACE TEMPORARY TABLE x (y DECIMAL(38, 0) AUTOINCREMENT START 0 INCREMENT 1)\",\n        )\n        self.validate_identity(\n            \"CREATE TEMPORARY TABLE x (y NUMBER AUTOINCREMENT(0, 1))\",\n            \"CREATE TEMPORARY TABLE x (y DECIMAL(38, 0) AUTOINCREMENT START 0 INCREMENT 1)\",\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE TABLE x (y NUMBER(38, 0) NOT NULL AUTOINCREMENT START 1 INCREMENT 1 ORDER)\",\n            \"CREATE OR REPLACE TABLE x (y DECIMAL(38, 0) NOT NULL AUTOINCREMENT START 1 INCREMENT 1 ORDER)\",\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE TABLE x (y NUMBER(38, 0) NOT NULL AUTOINCREMENT START 1 INCREMENT 1 NOORDER)\",\n            \"CREATE OR REPLACE TABLE x (y DECIMAL(38, 0) NOT NULL AUTOINCREMENT START 1 INCREMENT 1 NOORDER)\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE x (y NUMBER IDENTITY START 0 INCREMENT 1)\",\n            \"CREATE TABLE x (y DECIMAL(38, 0) AUTOINCREMENT START 0 INCREMENT 1)\",\n        )\n        self.validate_identity(\n            \"ALTER TABLE foo ADD COLUMN id INT identity(1, 1)\",\n            \"ALTER TABLE foo ADD id INT AUTOINCREMENT START 1 INCREMENT 1\",\n        )\n        self.validate_identity(\n            \"SELECT DAYOFWEEK('2016-01-02T23:39:20.123-07:00'::TIMESTAMP)\",\n            \"SELECT DAYOFWEEK(CAST('2016-01-02T23:39:20.123-07:00' AS TIMESTAMP))\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM xxx WHERE col ilike '%Don''t%'\",\n            \"SELECT * FROM xxx WHERE col ILIKE '%Don\\\\'t%'\",\n        )\n        self.validate_identity(\n            \"SELECT * EXCLUDE a, b FROM xxx\",\n            \"SELECT * EXCLUDE (a), b FROM xxx\",\n        )\n        self.validate_identity(\n            \"SELECT * RENAME a AS b, c AS d FROM xxx\",\n            \"SELECT * RENAME (a AS b), c AS d FROM xxx\",\n        )\n\n        # Support for optional trailing commas after tables in from clause\n        self.validate_identity(\n            \"SELECT * FROM xxx, yyy, zzz,\",\n            \"SELECT * FROM xxx, yyy, zzz\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM xxx, yyy, zzz, WHERE foo = bar\",\n            \"SELECT * FROM xxx, yyy, zzz WHERE foo = bar\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM xxx, yyy, zzz\",\n            \"SELECT * FROM xxx, yyy, zzz\",\n        )\n\n        self.validate_all(\n            \"SELECT LTRIM(RTRIM(col)) FROM t1\",\n            write={\n                \"duckdb\": \"SELECT LTRIM(RTRIM(col)) FROM t1\",\n                \"snowflake\": \"SELECT LTRIM(RTRIM(col)) FROM t1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT value['x'] AS x FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 'x')])) AS _t0(seq, key, path, index, value, this)\",\n            read={\n                \"bigquery\": \"SELECT x FROM UNNEST([STRUCT('x' AS x)])\",\n                \"snowflake\": \"SELECT value['x'] AS x FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 'x')])) AS _t0(seq, key, path, index, value, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT value['x'] AS x, value['y'] AS y, value['z'] AS z FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 1, 'y', 2, 'z', 3)])) AS _t0(seq, key, path, index, value, this)\",\n            read={\n                \"bigquery\": \"SELECT x, y, z FROM UNNEST([STRUCT(1 AS x, 2 AS y, 3 AS z)])\",\n                \"snowflake\": \"SELECT value['x'] AS x, value['y'] AS y, value['z'] AS z FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 1, 'y', 2, 'z', 3)])) AS _t0(seq, key, path, index, value, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT u1['x'] AS x, u2['y'] AS y FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 1)])) AS _t0(seq, key, path, index, u1, this) CROSS JOIN TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('y', 2)])) AS _t1(seq, key, path, index, u2, this)\",\n            read={\n                \"bigquery\": \"SELECT u1.x, u2.y FROM UNNEST([STRUCT(1 AS x)]) AS u1, UNNEST([STRUCT(2 AS y)]) AS u2\",\n                \"snowflake\": \"SELECT u1['x'] AS x, u2['y'] AS y FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 1)])) AS _t0(seq, key, path, index, u1, this) CROSS JOIN TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('y', 2)])) AS _t1(seq, key, path, index, u2, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT t.id, value['name'] AS name, value['age'] AS age FROM t CROSS JOIN TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('name', 'John', 'age', 30)])) AS _t0(seq, key, path, index, value, this)\",\n            read={\n                \"bigquery\": \"SELECT t.id, name, age FROM t, UNNEST([STRUCT('John' AS name, 30 AS age)])\",\n                \"snowflake\": \"SELECT t.id, value['name'] AS name, value['age'] AS age FROM t CROSS JOIN TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('name', 'John', 'age', 30)])) AS _t0(seq, key, path, index, value, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT value FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 1)])) AS _t0(seq, key, path, index, value, this)\",\n            read={\n                \"bigquery\": \"SELECT value FROM UNNEST([STRUCT(1 AS x)]) AS value\",\n                \"snowflake\": \"SELECT value FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 1)])) AS _t0(seq, key, path, index, value, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT t.col1, value['field1'] AS field1, other_col, value['field2'] AS field2 FROM t CROSS JOIN TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('field1', 'a', 'field2', 'b')])) AS _t0(seq, key, path, index, value, this)\",\n            read={\n                \"bigquery\": \"SELECT t.col1, field1, other_col, field2 FROM t, UNNEST([STRUCT('a' AS field1, 'b' AS field2)])\",\n                \"snowflake\": \"SELECT t.col1, value['field1'] AS field1, other_col, value['field2'] AS field2 FROM t CROSS JOIN TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('field1', 'a', 'field2', 'b')])) AS _t0(seq, key, path, index, value, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (SELECT value['x'] AS x FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 'value')])) AS _t0(seq, key, path, index, value, this))\",\n            read={\n                \"bigquery\": \"SELECT * FROM (SELECT x FROM UNNEST([STRUCT('value' AS x)]))\",\n                \"snowflake\": \"SELECT * FROM (SELECT value['x'] AS x FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 'value')])) AS _t0(seq, key, path, index, value, this))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT value FROM TABLE(FLATTEN(INPUT => [1, 2, 3])) AS _t0(seq, key, path, index, value, this)\",\n            read={\n                \"bigquery\": \"SELECT value FROM UNNEST([1, 2, 3]) AS value\",\n                \"snowflake\": \"SELECT value FROM TABLE(FLATTEN(INPUT => [1, 2, 3])) AS _t0(seq, key, path, index, value, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t1 AS t1 CROSS JOIN t2 AS t2 LEFT JOIN t3 AS t3 ON t1.a = t3.i\",\n            read={\n                \"bigquery\": \"SELECT * FROM t1 AS t1, t2 AS t2 LEFT JOIN t3 AS t3 ON t1.a = t3.i\",\n                \"snowflake\": \"SELECT * FROM t1 AS t1 CROSS JOIN t2 AS t2 LEFT JOIN t3 AS t3 ON t1.a = t3.i\",\n            },\n        )\n        self.validate_all(\n            \"SELECT value['x'] AS x, yval, zval FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 'x', 'y', ['y1', 'y2', 'y3'], 'z', ['z1', 'z2', 'z3'])])) AS _t0(seq, key, path, index, value, this) CROSS JOIN TABLE(FLATTEN(INPUT => value['y'])) AS _t1(seq, key, path, index, yval, this) CROSS JOIN TABLE(FLATTEN(INPUT => value['z'])) AS _t2(seq, key, path, index, zval, this)\",\n            read={\n                \"bigquery\": \"SELECT x, yval, zval FROM UNNEST([STRUCT('x' AS x, ['y1', 'y2', 'y3'] AS y, ['z1', 'z2', 'z3'] AS z)]), UNNEST(y) AS yval, UNNEST(z) AS zval\",\n                \"snowflake\": \"SELECT value['x'] AS x, yval, zval FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('x', 'x', 'y', ['y1', 'y2', 'y3'], 'z', ['z1', 'z2', 'z3'])])) AS _t0(seq, key, path, index, value, this) CROSS JOIN TABLE(FLATTEN(INPUT => value['y'])) AS _t1(seq, key, path, index, yval, this) CROSS JOIN TABLE(FLATTEN(INPUT => value['z'])) AS _t2(seq, key, path, index, zval, this)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT _u['foo'] AS foo, bar, baz FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('foo', 'x', 'bars', ['y', 'z'], 'bazs', ['w'])])) AS _t0(seq, key, path, index, _u, this) CROSS JOIN TABLE(FLATTEN(INPUT => _u['bars'])) AS _t1(seq, key, path, index, bar, this) CROSS JOIN TABLE(FLATTEN(INPUT => _u['bazs'])) AS _t2(seq, key, path, index, baz, this)\",\n            read={\n                \"bigquery\": \"SELECT _u.foo, bar, baz FROM UNNEST([struct('x' AS foo, ['y', 'z'] AS bars, ['w'] AS bazs)]) AS _u, UNNEST(_u.bars) AS bar, UNNEST(_u.bazs) AS baz\",\n            },\n        )\n        self.validate_all(\n            \"SELECT _u, _u['foo'] AS foo, _u['bar'] AS bar FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('foo', 'x', 'bar', 'y')])) AS _t0(seq, key, path, index, _u, this)\",\n            read={\n                \"bigquery\": \"select _u, _u.foo, _u.bar from unnest([struct('x' as foo, 'y' AS bar)]) as _u\",\n            },\n        )\n        self.validate_all(\n            \"SELECT _u['foo'][0].bar FROM TABLE(FLATTEN(INPUT => [OBJECT_CONSTRUCT('foo', [OBJECT_CONSTRUCT('bar', 1)])])) AS _t0(seq, key, path, index, _u, this)\",\n            read={\n                \"bigquery\": \"select _u.foo[0].bar from unnest([struct([struct(1 as bar)] as foo)]) as _u\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAYS_OVERLAP(col1, col2)\",\n            read={\n                \"snowflake\": \"SELECT ARRAYS_OVERLAP(col1, col2)\",\n            },\n            write={\n                \"snowflake\": \"SELECT ARRAYS_OVERLAP(col1, col2)\",\n                \"duckdb\": \"SELECT (col1 && col2) OR (ARRAY_LENGTH(col1) <> LIST_COUNT(col1) AND ARRAY_LENGTH(col2) <> LIST_COUNT(col2))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_INTERSECTION([1, 2], [2, 3])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_INTERSECTION([1, 2], [2, 3])\",\n                \"starrocks\": \"SELECT ARRAY_INTERSECT([1, 2], [2, 3])\",\n                \"duckdb\": \"SELECT CASE WHEN [1, 2] IS NULL OR [2, 3] IS NULL THEN NULL ELSE LIST_TRANSFORM(LIST_FILTER(LIST_ZIP([1, 2], GENERATE_SERIES(1, LENGTH([1, 2]))), pair -> (LENGTH(LIST_FILTER([1, 2][1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1])) <= LENGTH(LIST_FILTER([2, 3], e -> e IS NOT DISTINCT FROM pair[1])))), pair -> pair[1]) END\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE test_table (id NUMERIC NOT NULL AUTOINCREMENT)\",\n            write={\n                \"duckdb\": \"CREATE TABLE test_table (id DECIMAL(38, 0) NOT NULL)\",\n                \"snowflake\": \"CREATE TABLE test_table (id DECIMAL(38, 0) NOT NULL AUTOINCREMENT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP('2025-01-16 14:45:30.123', 'yyyy-mm-DD hh24:mi:ss.ff6')\",\n            write={\n                \"\": \"SELECT STR_TO_TIME('2025-01-16 14:45:30.123', '%Y-%m-%d %H:%M:%S.%f')\",\n                \"snowflake\": \"SELECT TO_TIMESTAMP('2025-01-16 14:45:30.123', 'yyyy-mm-DD hh24:mi:ss.ff6')\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_CONSTRUCT_COMPACT(1, null, 2)\",\n            write={\n                \"spark\": \"ARRAY_COMPACT(ARRAY(1, NULL, 2))\",\n                \"snowflake\": \"ARRAY_CONSTRUCT_COMPACT(1, NULL, 2)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_COMPACT(arr)\",\n            read={\n                \"spark\": \"ARRAY_COMPACT(arr)\",\n                \"databricks\": \"ARRAY_COMPACT(arr)\",\n                \"snowflake\": \"ARRAY_COMPACT(arr)\",\n            },\n            write={\n                \"spark\": \"ARRAY_COMPACT(arr)\",\n                \"databricks\": \"ARRAY_COMPACT(arr)\",\n            },\n        )\n        self.validate_all(\n            \"OBJECT_CONSTRUCT_KEEP_NULL('key_1', 'one', 'key_2', NULL)\",\n            read={\n                \"bigquery\": \"JSON_OBJECT(['key_1', 'key_2'], ['one', NULL])\",\n                \"duckdb\": \"JSON_OBJECT('key_1', 'one', 'key_2', NULL)\",\n            },\n            write={\n                \"bigquery\": \"JSON_OBJECT('key_1', 'one', 'key_2', NULL)\",\n                \"duckdb\": \"JSON_OBJECT('key_1', 'one', 'key_2', NULL)\",\n                \"snowflake\": \"OBJECT_CONSTRUCT_KEEP_NULL('key_1', 'one', 'key_2', NULL)\",\n            },\n        )\n        # Test simple case - uses MAKE_TIME (values within normal ranges)\n        self.validate_all(\n            \"SELECT TIME_FROM_PARTS(12, 34, 56)\",\n            write={\n                \"duckdb\": \"SELECT MAKE_TIME(12, 34, 56)\",\n                \"snowflake\": \"SELECT TIME_FROM_PARTS(12, 34, 56)\",\n            },\n        )\n        # Test with nanoseconds - uses INTERVAL arithmetic\n        self.validate_all(\n            \"SELECT TIME_FROM_PARTS(12, 34, 56, 987654321)\",\n            write={\n                \"duckdb\": \"SELECT CAST('00:00:00' AS TIME) + INTERVAL ((12 * 3600) + (34 * 60) + 56 + (987654321 / 1000000000.0)) SECOND\",\n                \"snowflake\": \"SELECT TIME_FROM_PARTS(12, 34, 56, 987654321)\",\n            },\n        )\n        # Test overflow normalization - documented Snowflake feature with INTERVAL arithmetic\n        self.validate_all(\n            \"SELECT TIME_FROM_PARTS(0, 100, 0)\",\n            write={\n                \"duckdb\": \"SELECT CAST('00:00:00' AS TIME) + INTERVAL ((0 * 3600) + (100 * 60) + 0) SECOND\",\n                \"snowflake\": \"SELECT TIME_FROM_PARTS(0, 100, 0)\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT TIMESTAMPNTZFROMPARTS(2013, 4, 5, 12, 00, 00)\",\n            \"SELECT TIMESTAMP_FROM_PARTS(2013, 4, 5, 12, 00, 00)\",\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMP_FROM_PARTS(2013, 4, 5, 12, 00, 00)\",\n            read={\n                \"duckdb\": \"SELECT MAKE_TIMESTAMP(2013, 4, 5, 12, 00, 00)\",\n                \"snowflake\": \"SELECT TIMESTAMP_NTZ_FROM_PARTS(2013, 4, 5, 12, 00, 00)\",\n            },\n            write={\n                \"duckdb\": \"SELECT MAKE_TIMESTAMP(2013, 4, 5, 12, 00, 00)\",\n                \"snowflake\": \"SELECT TIMESTAMP_FROM_PARTS(2013, 4, 5, 12, 00, 00)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMP_FROM_PARTS(TO_DATE('2023-06-15'), TO_TIME('14:30:45'))\",\n            write={\n                \"duckdb\": \"SELECT CAST('2023-06-15' AS DATE) + CAST('14:30:45' AS TIME)\",\n                \"snowflake\": \"SELECT TIMESTAMP_FROM_PARTS(CAST('2023-06-15' AS DATE), CAST('14:30:45' AS TIME))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMP_NTZ_FROM_PARTS(TO_DATE('2023-06-15'), TO_TIME('14:30:45'))\",\n            write={\n                \"duckdb\": \"SELECT CAST('2023-06-15' AS DATE) + CAST('14:30:45' AS TIME)\",\n                \"snowflake\": \"SELECT TIMESTAMP_FROM_PARTS(CAST('2023-06-15' AS DATE), CAST('14:30:45' AS TIME))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMP_LTZ_FROM_PARTS(2023, 6, 15, 14, 30, 45)\",\n            write={\n                \"duckdb\": \"SELECT CAST(MAKE_TIMESTAMP(2023, 6, 15, 14, 30, 45) AS TIMESTAMPTZ)\",\n                \"snowflake\": \"SELECT TIMESTAMP_LTZ_FROM_PARTS(2023, 6, 15, 14, 30, 45)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMESTAMP_TZ_FROM_PARTS(2023, 6, 15, 14, 30, 45, 0, 'America/Los_Angeles')\",\n            write={\n                \"duckdb\": \"SELECT MAKE_TIMESTAMP(2023, 6, 15, 14, 30, 45) AT TIME ZONE 'America/Los_Angeles'\",\n                \"snowflake\": \"SELECT TIMESTAMP_TZ_FROM_PARTS(2023, 6, 15, 14, 30, 45, 0, 'America/Los_Angeles')\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH vartab(v) AS (select parse_json('[{\"attr\": [{\"name\": \"banana\"}]}]')) SELECT GET_PATH(v, '[0].attr[0].name') FROM vartab\"\"\",\n            write={\n                \"bigquery\": \"\"\"WITH vartab AS (SELECT PARSE_JSON('[{\"attr\": [{\"name\": \"banana\"}]}]') AS v) SELECT JSON_EXTRACT(v, '$[0].attr[0].name') FROM vartab\"\"\",\n                \"duckdb\": \"\"\"WITH vartab(v) AS (SELECT JSON('[{\"attr\": [{\"name\": \"banana\"}]}]')) SELECT v -> '$[0].attr[0].name' FROM vartab\"\"\",\n                \"mysql\": \"\"\"WITH vartab(v) AS (SELECT '[{\"attr\": [{\"name\": \"banana\"}]}]') SELECT JSON_EXTRACT(v, '$[0].attr[0].name') FROM vartab\"\"\",\n                \"presto\": \"\"\"WITH vartab(v) AS (SELECT JSON_PARSE('[{\"attr\": [{\"name\": \"banana\"}]}]')) SELECT JSON_EXTRACT(v, '$[0].attr[0].name') FROM vartab\"\"\",\n                \"snowflake\": \"\"\"WITH vartab(v) AS (SELECT PARSE_JSON('[{\"attr\": [{\"name\": \"banana\"}]}]')) SELECT GET_PATH(v, '[0].attr[0].name') FROM vartab\"\"\",\n                \"tsql\": \"\"\"WITH vartab(v) AS (SELECT '[{\"attr\": [{\"name\": \"banana\"}]}]') SELECT ISNULL(JSON_QUERY(v, '$[0].attr[0].name'), JSON_VALUE(v, '$[0].attr[0].name')) FROM vartab\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH vartab(v) AS (select parse_json('{\"attr\": [{\"name\": \"banana\"}]}')) SELECT GET_PATH(v, 'attr[0].name') FROM vartab\"\"\",\n            write={\n                \"bigquery\": \"\"\"WITH vartab AS (SELECT PARSE_JSON('{\"attr\": [{\"name\": \"banana\"}]}') AS v) SELECT JSON_EXTRACT(v, '$.attr[0].name') FROM vartab\"\"\",\n                \"duckdb\": \"\"\"WITH vartab(v) AS (SELECT JSON('{\"attr\": [{\"name\": \"banana\"}]}')) SELECT v -> '$.attr[0].name' FROM vartab\"\"\",\n                \"mysql\": \"\"\"WITH vartab(v) AS (SELECT '{\"attr\": [{\"name\": \"banana\"}]}') SELECT JSON_EXTRACT(v, '$.attr[0].name') FROM vartab\"\"\",\n                \"presto\": \"\"\"WITH vartab(v) AS (SELECT JSON_PARSE('{\"attr\": [{\"name\": \"banana\"}]}')) SELECT JSON_EXTRACT(v, '$.attr[0].name') FROM vartab\"\"\",\n                \"snowflake\": \"\"\"WITH vartab(v) AS (SELECT PARSE_JSON('{\"attr\": [{\"name\": \"banana\"}]}')) SELECT GET_PATH(v, 'attr[0].name') FROM vartab\"\"\",\n                \"tsql\": \"\"\"WITH vartab(v) AS (SELECT '{\"attr\": [{\"name\": \"banana\"}]}') SELECT ISNULL(JSON_QUERY(v, '$.attr[0].name'), JSON_VALUE(v, '$.attr[0].name')) FROM vartab\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit\"\"\",\n            write={\n                \"bigquery\": \"\"\"SELECT JSON_EXTRACT(PARSE_JSON('{\"fruit\":\"banana\"}'), '$.fruit')\"\"\",\n                \"databricks\": \"\"\"SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit\"\"\",\n                \"duckdb\": \"\"\"SELECT JSON('{\"fruit\":\"banana\"}') -> '$.fruit'\"\"\",\n                \"mysql\": \"\"\"SELECT JSON_EXTRACT('{\"fruit\":\"banana\"}', '$.fruit')\"\"\",\n                \"presto\": \"\"\"SELECT JSON_EXTRACT(JSON_PARSE('{\"fruit\":\"banana\"}'), '$.fruit')\"\"\",\n                \"snowflake\": \"\"\"SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')\"\"\",\n                \"spark\": \"\"\"SELECT GET_JSON_OBJECT('{\"fruit\":\"banana\"}', '$.fruit')\"\"\",\n                \"tsql\": \"\"\"SELECT ISNULL(JSON_QUERY('{\"fruit\":\"banana\"}', '$.fruit'), JSON_VALUE('{\"fruit\":\"banana\"}', '$.fruit'))\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_ARRAY(['test'])\",\n            write={\n                \"snowflake\": \"SELECT TO_ARRAY(['test'])\",\n                \"spark\": \"SELECT ARRAY('test')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_ARRAY(['test'])\",\n            write={\n                \"snowflake\": \"SELECT TO_ARRAY(['test'])\",\n                \"spark\": \"SELECT ARRAY('test')\",\n            },\n        )\n        self.validate_all(\n            # We need to qualify the columns in this query because \"value\" would be ambiguous\n            'WITH t(x, \"value\") AS (SELECT [1, 2, 3], 1) SELECT IFF(_u.pos = _u_2.pos_2, _u_2.\"value\", NULL) AS \"value\" FROM t CROSS JOIN TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(t.x)) - 1) + 1))) AS _u(seq, key, path, index, pos, this) CROSS JOIN TABLE(FLATTEN(INPUT => t.x)) AS _u_2(seq, key, path, pos_2, \"value\", this) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > (ARRAY_SIZE(t.x) - 1) AND _u_2.pos_2 = (ARRAY_SIZE(t.x) - 1))',\n            read={\n                \"duckdb\": 'WITH t(x, \"value\") AS (SELECT [1,2,3], 1) SELECT UNNEST(t.x) AS \"value\" FROM t',\n            },\n        )\n        self.validate_all(\n            \"SELECT { 'Manitoba': 'Winnipeg', 'foo': 'bar' } AS province_capital\",\n            write={\n                \"duckdb\": \"SELECT {'Manitoba': 'Winnipeg', 'foo': 'bar'} AS province_capital\",\n                \"snowflake\": \"SELECT OBJECT_CONSTRUCT('Manitoba', 'Winnipeg', 'foo', 'bar') AS province_capital\",\n                \"spark\": \"SELECT STRUCT('Winnipeg' AS Manitoba, 'bar' AS foo) AS province_capital\",\n            },\n        )\n        self.validate_all(\n            \"SELECT COLLATE('B', 'und:ci')\",\n            write={\n                \"bigquery\": \"SELECT COLLATE('B', 'und:ci')\",\n                \"snowflake\": \"SELECT COLLATE('B', 'und:ci')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT To_BOOLEAN('T')\",\n            write={\n                \"duckdb\": \"SELECT CASE WHEN UPPER(CAST('T' AS TEXT)) = 'ON' THEN TRUE WHEN UPPER(CAST('T' AS TEXT)) = 'OFF' THEN FALSE WHEN ISNAN(TRY_CAST('T' AS REAL)) OR ISINF(TRY_CAST('T' AS REAL)) THEN ERROR('TO_BOOLEAN: Non-numeric values NaN and INF are not supported') ELSE CAST('T' AS BOOLEAN) END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM x START WITH a = b CONNECT BY c = PRIOR d\",\n            read={\n                \"oracle\": \"SELECT * FROM x START WITH a = b CONNECT BY c = PRIOR d\",\n            },\n            write={\n                \"oracle\": \"SELECT * FROM x START WITH a = b CONNECT BY c = PRIOR d\",\n                \"snowflake\": \"SELECT * FROM x START WITH a = b CONNECT BY c = PRIOR d\",\n            },\n        )\n        self.validate_all(\n            \"SELECT INSERT(a, 0, 0, 'b')\",\n            read={\n                \"mysql\": \"SELECT INSERT(a, 0, 0, 'b')\",\n                \"snowflake\": \"SELECT INSERT(a, 0, 0, 'b')\",\n                \"tsql\": \"SELECT STUFF(a, 0, 0, 'b')\",\n            },\n            write={\n                \"mysql\": \"SELECT INSERT(a, 0, 0, 'b')\",\n                \"snowflake\": \"SELECT INSERT(a, 0, 0, 'b')\",\n                \"tsql\": \"SELECT STUFF(a, 0, 0, 'b')\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_GENERATE_RANGE(0, 3)\",\n            write={\n                \"bigquery\": \"GENERATE_ARRAY(0, 3 - 1)\",\n                \"duckdb\": \"RANGE(0, 3)\",\n                \"postgres\": \"GENERATE_SERIES(0, 3 - 1)\",\n                \"presto\": \"SEQUENCE(0, 2)\",\n                \"snowflake\": \"ARRAY_GENERATE_RANGE(0, 3)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_GENERATE_RANGE(0, 3 + 1)\",\n            read={\n                \"bigquery\": \"GENERATE_ARRAY(0, 3)\",\n                \"postgres\": \"GENERATE_SERIES(0, 3)\",\n                \"presto\": \"SEQUENCE(0, 3)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ARRAY_GENERATE_RANGE(-5, -25, -10)\",\n            write={\n                \"duckdb\": \"SELECT RANGE(-5, -25, -10)\",\n                \"snowflake\": \"SELECT ARRAY_GENERATE_RANGE(-5, -25, -10)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_GENERATE_RANGE(5, 1, -1)\",\n            write={\n                \"duckdb\": \"SELECT RANGE(5, 1, -1)\",\n                \"snowflake\": \"SELECT ARRAY_GENERATE_RANGE(5, 1, -1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART('year', TIMESTAMP '2020-01-01')\",\n            write={\n                \"hive\": \"SELECT EXTRACT(year FROM CAST('2020-01-01' AS TIMESTAMP))\",\n                \"snowflake\": \"SELECT DATE_PART('year', CAST('2020-01-01' AS TIMESTAMP))\",\n                \"spark\": \"SELECT EXTRACT(year FROM CAST('2020-01-01' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (VALUES (0) foo(bar))\",\n            write={\"snowflake\": \"SELECT * FROM (VALUES (0)) AS foo(bar)\"},\n        )\n        self.validate_all(\n            \"OBJECT_CONSTRUCT('a', b, 'c', d)\",\n            read={\n                \"\": \"STRUCT(b as a, d as c)\",\n            },\n            write={\n                \"duckdb\": \"{'a': b, 'c': d}\",\n                \"snowflake\": \"OBJECT_CONSTRUCT('a', b, 'c', d)\",\n                \"\": \"STRUCT(b AS a, d AS c)\",\n            },\n        )\n        self.validate_identity(\"OBJECT_CONSTRUCT(a, b, c, d)\")\n\n        self.validate_all(\n            \"SELECT i, p, o FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) = 1\",\n            write={\n                \"\": \"SELECT i, p, o FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o NULLS LAST) = 1\",\n                \"databricks\": \"SELECT i, p, o FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o NULLS LAST) = 1\",\n                \"hive\": \"SELECT i, p, o FROM (SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o NULLS LAST) AS _w FROM qt) AS _t WHERE _w = 1\",\n                \"presto\": \"SELECT i, p, o FROM (SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS _w FROM qt) AS _t WHERE _w = 1\",\n                \"snowflake\": \"SELECT i, p, o FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) = 1\",\n                \"spark\": \"SELECT i, p, o FROM (SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o NULLS LAST) AS _w FROM qt) AS _t WHERE _w = 1\",\n                \"sqlite\": \"SELECT i, p, o FROM (SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o NULLS LAST) AS _w FROM qt) AS _t WHERE _w = 1\",\n                \"trino\": \"SELECT i, p, o FROM (SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS _w FROM qt) AS _t WHERE _w = 1\",\n            },\n        )\n\n        # NTH_VALUE FROM FIRST not supported in DuckDB\n        self.validate_all(\n            \"SELECT NTH_VALUE(is_deleted, 2) FROM FIRST IGNORE NULLS OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n            write={\n                \"snowflake\": \"SELECT NTH_VALUE(is_deleted, 2) FROM FIRST IGNORE NULLS OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n                \"duckdb\": \"SELECT NTH_VALUE(is_deleted, 2 IGNORE NULLS) OVER (PARTITION BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS nth_is_deleted FROM my_table\",\n            },\n        )\n\n        # NTH_VALUE FROM LAST not supported in DuckDB\n        self.validate_all(\n            \"SELECT NTH_VALUE(is_deleted, 2) FROM LAST RESPECT NULLS OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n            write={\n                \"snowflake\": \"SELECT NTH_VALUE(is_deleted, 2) FROM LAST RESPECT NULLS OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n                \"duckdb\": \"SELECT NTH_VALUE(is_deleted, 2 RESPECT NULLS) OVER (PARTITION BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS nth_is_deleted FROM my_table\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT NTH_VALUE(is_deleted, 2) OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n            write={\n                \"snowflake\": \"SELECT NTH_VALUE(is_deleted, 2) OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n                \"duckdb\": \"SELECT NTH_VALUE(is_deleted, 2) OVER (PARTITION BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS nth_is_deleted FROM my_table\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT NTH_VALUE(is_deleted, 2) OVER (PARTITION BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS nth_is_deleted FROM my_table\",\n            write={\n                \"snowflake\": \"SELECT NTH_VALUE(is_deleted, 2) OVER (PARTITION BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS nth_is_deleted FROM my_table\",\n                \"duckdb\": \"SELECT NTH_VALUE(is_deleted, 2) OVER (PARTITION BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS nth_is_deleted FROM my_table\",\n            },\n        )\n\n        for func in (\n            \"FIRST_VALUE\",\n            \"LAST_VALUE\",\n        ):\n            for options in (\n                \" IGNORE NULLS\",\n                \" RESPECT NULLS\",\n                \"\",\n            ):\n                self.validate_all(\n                    f\"SELECT {func}(is_deleted){options} OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n                    write={\n                        \"snowflake\": f\"SELECT {func}(is_deleted){options} OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n                        \"duckdb\": f\"SELECT {func}(is_deleted{options}) OVER (PARTITION BY id ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS nth_is_deleted FROM my_table\",\n                    },\n                )\n                self.validate_all(\n                    f\"SELECT {func}(is_deleted){options} OVER (PARTITION BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS nth_is_deleted FROM my_table\",\n                    write={\n                        \"snowflake\": f\"SELECT {func}(is_deleted){options} OVER (PARTITION BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS nth_is_deleted FROM my_table\",\n                        \"duckdb\": f\"SELECT {func}(is_deleted{options}) OVER (PARTITION BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS nth_is_deleted FROM my_table\",\n                    },\n                )\n\n        self.validate_all(\n            \"SELECT LEAD(is_deleted, 2, -10) RESPECT NULLS OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n            write={\n                \"snowflake\": \"SELECT LEAD(is_deleted, 2, -10) RESPECT NULLS OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n                \"duckdb\": \"SELECT LEAD(is_deleted, 2, -10 RESPECT NULLS) OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT LEAD(is_deleted, 2) OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n            write={\n                \"snowflake\": \"SELECT LEAD(is_deleted, 2) OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n                \"duckdb\": \"SELECT LEAD(is_deleted, 2) OVER (PARTITION BY id) AS nth_is_deleted FROM my_table\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT LAG(amount) OVER (ORDER BY seq) AS basic_lag\",\n            write={\n                \"snowflake\": \"SELECT LAG(amount) OVER (ORDER BY seq) AS basic_lag\",\n                \"duckdb\": \"SELECT LAG(amount) OVER (ORDER BY seq) AS basic_lag\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT LAG(amount, 2) IGNORE NULLS OVER (PARTITION BY category ORDER BY seq) AS lag_offset_ignore_nulls\",\n            write={\n                \"snowflake\": \"SELECT LAG(amount, 2) IGNORE NULLS OVER (PARTITION BY category ORDER BY seq) AS lag_offset_ignore_nulls\",\n                \"duckdb\": \"SELECT LAG(amount, 2 IGNORE NULLS) OVER (PARTITION BY category ORDER BY seq) AS lag_offset_ignore_nulls\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT LAG(amount, 2, -777) RESPECT NULLS OVER (PARTITION BY category ORDER BY seq ASC) AS lag_full_ignore_nulls\",\n            write={\n                \"snowflake\": \"SELECT LAG(amount, 2, -777) RESPECT NULLS OVER (PARTITION BY category ORDER BY seq ASC) AS lag_full_ignore_nulls\",\n                \"duckdb\": \"SELECT LAG(amount, 2, -777 RESPECT NULLS) OVER (PARTITION BY category ORDER BY seq ASC) AS lag_full_ignore_nulls\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT BOOLOR_AGG(c1), BOOLOR_AGG(c2) FROM test\",\n            write={\n                \"\": \"SELECT LOGICAL_OR(c1), LOGICAL_OR(c2) FROM test\",\n                \"duckdb\": \"SELECT BOOL_OR(CAST(c1 AS BOOLEAN)), BOOL_OR(CAST(c2 AS BOOLEAN)) FROM test\",\n                \"oracle\": \"SELECT MAX(c1), MAX(c2) FROM test\",\n                \"postgres\": \"SELECT BOOL_OR(c1), BOOL_OR(c2) FROM test\",\n                \"snowflake\": \"SELECT BOOLOR_AGG(c1), BOOLOR_AGG(c2) FROM test\",\n                \"spark\": \"SELECT BOOL_OR(c1), BOOL_OR(c2) FROM test\",\n                \"sqlite\": \"SELECT MAX(c1), MAX(c2) FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BOOLAND_AGG(c1), BOOLAND_AGG(c2) FROM test\",\n            write={\n                \"\": \"SELECT LOGICAL_AND(c1), LOGICAL_AND(c2) FROM test\",\n                \"duckdb\": \"SELECT BOOL_AND(CAST(c1 AS BOOLEAN)), BOOL_AND(CAST(c2 AS BOOLEAN)) FROM test\",\n                \"oracle\": \"SELECT MIN(c1), MIN(c2) FROM test\",\n                \"postgres\": \"SELECT BOOL_AND(c1), BOOL_AND(c2) FROM test\",\n                \"snowflake\": \"SELECT BOOLAND_AGG(c1), BOOLAND_AGG(c2) FROM test\",\n                \"spark\": \"SELECT BOOL_AND(c1), BOOL_AND(c2) FROM test\",\n                \"sqlite\": \"SELECT MIN(c1), MIN(c2) FROM test\",\n                \"mysql\": \"SELECT MIN(c1), MIN(c2) FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BOOLXOR_AGG(c1) FROM test\",\n            write={\n                \"duckdb\": \"SELECT COUNT_IF(CAST(c1 AS BOOLEAN)) = 1 FROM test\",\n                \"snowflake\": \"SELECT BOOLXOR_AGG(c1) FROM test\",\n            },\n        )\n        for suffix in (\n            \"\",\n            \" OVER ()\",\n        ):\n            self.validate_all(\n                f\"SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x){suffix}\",\n                read={\n                    \"postgres\": f\"SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x){suffix}\",\n                },\n                write={\n                    \"\": f\"SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x NULLS LAST){suffix}\",\n                    \"duckdb\": f\"SELECT QUANTILE_CONT(x, 0.5 ORDER BY x){suffix}\",\n                    \"postgres\": f\"SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x){suffix}\",\n                    \"snowflake\": f\"SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x){suffix}\",\n                },\n            )\n            for func in (\n                \"COVAR_POP\",\n                \"COVAR_SAMP\",\n            ):\n                self.validate_all(\n                    f\"SELECT {func}(y, x){suffix}\",\n                    write={\n                        \"\": f\"SELECT {func}(y, x){suffix}\",\n                        \"duckdb\": f\"SELECT {func}(y, x){suffix}\",\n                        \"postgres\": f\"SELECT {func}(y, x){suffix}\",\n                        \"snowflake\": f\"SELECT {func}(y, x){suffix}\",\n                    },\n                )\n        self.validate_all(\n            \"TO_CHAR(x, y)\",\n            read={\n                \"\": \"TO_CHAR(x, y)\",\n                \"snowflake\": \"TO_VARCHAR(x, y)\",\n            },\n            write={\n                \"\": \"CAST(x AS TEXT)\",\n                \"databricks\": \"TO_CHAR(x, y)\",\n                \"drill\": \"TO_CHAR(x, y)\",\n                \"oracle\": \"TO_CHAR(x, y)\",\n                \"postgres\": \"TO_CHAR(x, y)\",\n                \"snowflake\": \"TO_CHAR(x, y)\",\n                \"teradata\": \"TO_CHAR(x, y)\",\n            },\n        )\n        for to_func in (\"TO_CHAR\", \"TO_VARCHAR\"):\n            with self.subTest(f\"Testing transpilation of {to_func}\"):\n                self.validate_identity(\n                    f\"{to_func}(foo::DATE, 'yyyy')\",\n                    \"TO_CHAR(CAST(foo AS DATE), 'yyyy')\",\n                )\n                self.validate_all(\n                    f\"{to_func}(foo::TIMESTAMP, 'YYYY-MM')\",\n                    write={\n                        \"snowflake\": \"TO_CHAR(CAST(foo AS TIMESTAMP), 'yyyy-mm')\",\n                        \"duckdb\": \"STRFTIME(CAST(foo AS TIMESTAMP), '%Y-%m')\",\n                    },\n                )\n        self.validate_all(\n            \"SQUARE(x)\",\n            write={\n                \"bigquery\": \"POWER(x, 2)\",\n                \"clickhouse\": \"POWER(x, 2)\",\n                \"databricks\": \"POWER(x, 2)\",\n                \"drill\": \"POW(x, 2)\",\n                \"duckdb\": \"POWER(x, 2)\",\n                \"hive\": \"POWER(x, 2)\",\n                \"mysql\": \"POWER(x, 2)\",\n                \"oracle\": \"POWER(x, 2)\",\n                \"postgres\": \"POWER(x, 2)\",\n                \"presto\": \"POWER(x, 2)\",\n                \"redshift\": \"POWER(x, 2)\",\n                \"snowflake\": \"POWER(x, 2)\",\n                \"spark\": \"POWER(x, 2)\",\n                \"sqlite\": \"POWER(x, 2)\",\n                \"starrocks\": \"POWER(x, 2)\",\n                \"teradata\": \"x ** 2\",\n                \"trino\": \"POWER(x, 2)\",\n                \"tsql\": \"POWER(x, 2)\",\n            },\n        )\n        self.validate_all(\n            \"POWER(x, 2)\",\n            read={\n                \"oracle\": \"SQUARE(x)\",\n                \"snowflake\": \"SQUARE(x)\",\n                \"tsql\": \"SQUARE(x)\",\n            },\n        )\n        self.validate_all(\n            \"DIV0(foo, bar)\",\n            write={\n                \"snowflake\": \"IFF(bar = 0 AND NOT foo IS NULL, 0, foo / bar)\",\n                \"sqlite\": \"IIF(bar = 0 AND NOT foo IS NULL, 0, CAST(foo AS REAL) / bar)\",\n                \"presto\": \"IF(bar = 0 AND NOT foo IS NULL, 0, CAST(foo AS DOUBLE) / bar)\",\n                \"spark\": \"IF(bar = 0 AND NOT foo IS NULL, 0, foo / bar)\",\n                \"hive\": \"IF(bar = 0 AND NOT foo IS NULL, 0, foo / bar)\",\n                \"duckdb\": \"CASE WHEN bar = 0 AND NOT foo IS NULL THEN 0 ELSE foo / bar END\",\n            },\n        )\n        self.validate_all(\n            \"DIV0(a - b, c - d)\",\n            write={\n                \"snowflake\": \"IFF((c - d) = 0 AND NOT (a - b) IS NULL, 0, (a - b) / (c - d))\",\n                \"sqlite\": \"IIF((c - d) = 0 AND NOT (a - b) IS NULL, 0, CAST((a - b) AS REAL) / (c - d))\",\n                \"presto\": \"IF((c - d) = 0 AND NOT (a - b) IS NULL, 0, CAST((a - b) AS DOUBLE) / (c - d))\",\n                \"spark\": \"IF((c - d) = 0 AND NOT (a - b) IS NULL, 0, (a - b) / (c - d))\",\n                \"hive\": \"IF((c - d) = 0 AND NOT (a - b) IS NULL, 0, (a - b) / (c - d))\",\n                \"duckdb\": \"CASE WHEN (c - d) = 0 AND NOT (a - b) IS NULL THEN 0 ELSE (a - b) / (c - d) END\",\n            },\n        )\n        self.validate_all(\n            \"DIV0NULL(foo, bar)\",\n            write={\n                \"snowflake\": \"IFF(bar = 0 OR bar IS NULL, 0, foo / bar)\",\n                \"sqlite\": \"IIF(bar = 0 OR bar IS NULL, 0, CAST(foo AS REAL) / bar)\",\n                \"presto\": \"IF(bar = 0 OR bar IS NULL, 0, CAST(foo AS DOUBLE) / bar)\",\n                \"spark\": \"IF(bar = 0 OR bar IS NULL, 0, foo / bar)\",\n                \"hive\": \"IF(bar = 0 OR bar IS NULL, 0, foo / bar)\",\n                \"duckdb\": \"CASE WHEN bar = 0 OR bar IS NULL THEN 0 ELSE foo / bar END\",\n            },\n        )\n        self.validate_all(\n            \"DIV0NULL(a - b, c - d)\",\n            write={\n                \"snowflake\": \"IFF((c - d) = 0 OR (c - d) IS NULL, 0, (a - b) / (c - d))\",\n                \"sqlite\": \"IIF((c - d) = 0 OR (c - d) IS NULL, 0, CAST((a - b) AS REAL) / (c - d))\",\n                \"presto\": \"IF((c - d) = 0 OR (c - d) IS NULL, 0, CAST((a - b) AS DOUBLE) / (c - d))\",\n                \"spark\": \"IF((c - d) = 0 OR (c - d) IS NULL, 0, (a - b) / (c - d))\",\n                \"hive\": \"IF((c - d) = 0 OR (c - d) IS NULL, 0, (a - b) / (c - d))\",\n                \"duckdb\": \"CASE WHEN (c - d) = 0 OR (c - d) IS NULL THEN 0 ELSE (a - b) / (c - d) END\",\n            },\n        )\n        self.validate_all(\n            \"ZEROIFNULL(foo)\",\n            write={\n                \"snowflake\": \"IFF(foo IS NULL, 0, foo)\",\n                \"sqlite\": \"IIF(foo IS NULL, 0, foo)\",\n                \"presto\": \"IF(foo IS NULL, 0, foo)\",\n                \"spark\": \"IF(foo IS NULL, 0, foo)\",\n                \"hive\": \"IF(foo IS NULL, 0, foo)\",\n                \"duckdb\": \"CASE WHEN foo IS NULL THEN 0 ELSE foo END\",\n            },\n        )\n        self.validate_all(\n            \"NULLIFZERO(foo)\",\n            write={\n                \"snowflake\": \"IFF(foo = 0, NULL, foo)\",\n                \"sqlite\": \"IIF(foo = 0, NULL, foo)\",\n                \"presto\": \"IF(foo = 0, NULL, foo)\",\n                \"spark\": \"IF(foo = 0, NULL, foo)\",\n                \"hive\": \"IF(foo = 0, NULL, foo)\",\n                \"duckdb\": \"CASE WHEN foo = 0 THEN NULL ELSE foo END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * EXCLUDE (a, b) REPLACE (c AS d, E AS F) FROM xxx\",\n            read={\n                \"duckdb\": \"SELECT * EXCLUDE (a, b) REPLACE (c AS d, E AS F) FROM xxx\",\n            },\n            write={\n                \"snowflake\": \"SELECT * EXCLUDE (a, b) REPLACE (c AS d, E AS F) FROM xxx\",\n                \"duckdb\": \"SELECT * EXCLUDE (a, b) REPLACE (c AS d, E AS F) FROM xxx\",\n            },\n        )\n        self.validate_all(\n            '''SELECT PARSE_JSON('{\"a\": {\"b c\": \"foo\"}}'):a:\"b c\"''',\n            write={\n                \"duckdb\": \"\"\"SELECT JSON('{\"a\": {\"b c\": \"foo\"}}') -> '$.a.\"b c\"'\"\"\",\n                \"mysql\": \"\"\"SELECT JSON_EXTRACT('{\"a\": {\"b c\": \"foo\"}}', '$.a.\"b c\"')\"\"\",\n                \"snowflake\": \"\"\"SELECT GET_PATH(PARSE_JSON('{\"a\": {\"b c\": \"foo\"}}'), 'a[\"b c\"]')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM test WHERE a = 1 GROUP BY a HAVING a = 2 QUALIFY z ORDER BY a LIMIT 10\",\n            write={\n                \"bigquery\": \"SELECT a FROM test WHERE a = 1 GROUP BY a HAVING a = 2 QUALIFY z ORDER BY a NULLS LAST LIMIT 10\",\n                \"snowflake\": \"SELECT a FROM test WHERE a = 1 GROUP BY a HAVING a = 2 QUALIFY z ORDER BY a LIMIT 10\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM test AS t QUALIFY ROW_NUMBER() OVER (PARTITION BY a ORDER BY Z) = 1\",\n            write={\n                \"bigquery\": \"SELECT a FROM test AS t QUALIFY ROW_NUMBER() OVER (PARTITION BY a ORDER BY Z NULLS LAST) = 1\",\n                \"snowflake\": \"SELECT a FROM test AS t QUALIFY ROW_NUMBER() OVER (PARTITION BY a ORDER BY Z) = 1\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP(col, 'DD-MM-YYYY HH12:MI:SS') FROM t\",\n            write={\n                \"bigquery\": \"SELECT PARSE_TIMESTAMP('%d-%m-%Y %I:%M:%S', col) FROM t\",\n                \"duckdb\": \"SELECT STRPTIME(col, '%d-%m-%Y %I:%M:%S') FROM t\",\n                \"snowflake\": \"SELECT TO_TIMESTAMP(col, 'DD-mm-yyyy hh12:mi:ss') FROM t\",\n                \"spark\": \"SELECT TO_TIMESTAMP(col, 'dd-MM-yyyy hh:mm:ss') FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP(1659981729)\",\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_SECONDS(1659981729)\",\n                \"snowflake\": \"SELECT TO_TIMESTAMP(1659981729)\",\n                \"spark\": \"SELECT CAST(FROM_UNIXTIME(1659981729) AS TIMESTAMP)\",\n                \"redshift\": \"SELECT (TIMESTAMP 'epoch' + 1659981729 * INTERVAL '1 SECOND')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP(1659981729000, 3)\",\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_MILLIS(1659981729000)\",\n                \"snowflake\": \"SELECT TO_TIMESTAMP(1659981729000, 3)\",\n                \"spark\": \"SELECT TIMESTAMP_MILLIS(1659981729000)\",\n                \"redshift\": \"SELECT (TIMESTAMP 'epoch' + (1659981729000 / POWER(10, 3)) * INTERVAL '1 SECOND')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP(16599817290000, 4)\",\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_SECONDS(CAST(16599817290000 / POWER(10, 4) AS INT64))\",\n                \"snowflake\": \"SELECT TO_TIMESTAMP(16599817290000, 4)\",\n                \"spark\": \"SELECT TIMESTAMP_SECONDS(16599817290000 / POWER(10, 4))\",\n                \"redshift\": \"SELECT (TIMESTAMP 'epoch' + (16599817290000 / POWER(10, 4)) * INTERVAL '1 SECOND')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP('1659981729')\",\n            write={\n                \"snowflake\": \"SELECT TO_TIMESTAMP('1659981729')\",\n                \"spark\": \"SELECT CAST(FROM_UNIXTIME('1659981729') AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP(1659981729000000000, 9)\",\n            write={\n                \"bigquery\": \"SELECT TIMESTAMP_SECONDS(CAST(1659981729000000000 / POWER(10, 9) AS INT64))\",\n                \"duckdb\": \"SELECT TO_TIMESTAMP(1659981729000000000 / POWER(10, 9)) AT TIME ZONE 'UTC'\",\n                \"presto\": \"SELECT FROM_UNIXTIME(CAST(1659981729000000000 AS DOUBLE) / POW(10, 9))\",\n                \"snowflake\": \"SELECT TO_TIMESTAMP(1659981729000000000, 9)\",\n                \"spark\": \"SELECT TIMESTAMP_SECONDS(1659981729000000000 / POWER(10, 9))\",\n                \"redshift\": \"SELECT (TIMESTAMP 'epoch' + (1659981729000000000 / POWER(10, 9)) * INTERVAL '1 SECOND')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP('2013-04-05 01:02:03')\",\n            write={\n                \"bigquery\": \"SELECT CAST('2013-04-05 01:02:03' AS DATETIME)\",\n                \"snowflake\": \"SELECT CAST('2013-04-05 01:02:03' AS TIMESTAMP)\",\n                \"spark\": \"SELECT CAST('2013-04-05 01:02:03' AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP('04/05/2013 01:02:03', 'mm/DD/yyyy hh24:mi:ss')\",\n            read={\n                \"bigquery\": \"SELECT PARSE_TIMESTAMP('%m/%d/%Y %H:%M:%S', '04/05/2013 01:02:03')\",\n                \"duckdb\": \"SELECT STRPTIME('04/05/2013 01:02:03', '%m/%d/%Y %H:%M:%S')\",\n            },\n            write={\n                \"bigquery\": \"SELECT PARSE_TIMESTAMP('%m/%d/%Y %T', '04/05/2013 01:02:03')\",\n                \"snowflake\": \"SELECT TO_TIMESTAMP('04/05/2013 01:02:03', 'mm/DD/yyyy hh24:mi:ss')\",\n                \"spark\": \"SELECT TO_TIMESTAMP('04/05/2013 01:02:03', 'MM/dd/yyyy HH:mm:ss')\",\n            },\n        )\n        self.validate_all(\n            \"TO_TIMESTAMP('2024-01-15 3:00 AM', 'YYYY-MM-DD HH12:MI PM')\",\n            write={\n                \"duckdb\": \"STRPTIME('2024-01-15 3:00 AM', '%Y-%m-%d %I:%M %p')\",\n                \"snowflake\": \"TO_TIMESTAMP('2024-01-15 3:00 AM', 'yyyy-mm-DD hh12:mi pm')\",\n            },\n        )\n        self.validate_all(\n            \"TO_TIMESTAMP('2024-01-15 3:00 PM', 'YYYY-MM-DD HH12:MI AM')\",\n            write={\n                \"duckdb\": \"STRPTIME('2024-01-15 3:00 PM', '%Y-%m-%d %I:%M %p')\",\n                \"snowflake\": \"TO_TIMESTAMP('2024-01-15 3:00 PM', 'yyyy-mm-DD hh12:mi pm')\",\n            },\n        )\n        self.validate_all(\n            \"TO_TIMESTAMP('2024-01-15 3:00 PM', 'YYYY-MM-DD HH12:MI PM')\",\n            write={\n                \"duckdb\": \"STRPTIME('2024-01-15 3:00 PM', '%Y-%m-%d %I:%M %p')\",\n                \"snowflake\": \"TO_TIMESTAMP('2024-01-15 3:00 PM', 'yyyy-mm-DD hh12:mi pm')\",\n            },\n        )\n        self.validate_all(\n            \"TO_TIMESTAMP('2024-01-15 3:00 AM', 'YYYY-MM-DD HH12:MI AM')\",\n            write={\n                \"duckdb\": \"STRPTIME('2024-01-15 3:00 AM', '%Y-%m-%d %I:%M %p')\",\n                \"snowflake\": \"TO_TIMESTAMP('2024-01-15 3:00 AM', 'yyyy-mm-DD hh12:mi pm')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT IFF(TRUE, 'true', 'false')\",\n            write={\n                \"snowflake\": \"SELECT IFF(TRUE, 'true', 'false')\",\n                \"spark\": \"SELECT IF(TRUE, 'true', 'false')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"duckdb\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname\",\n                \"postgres\": \"SELECT fname, lname, age FROM person ORDER BY age DESC, fname ASC, lname\",\n                \"presto\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname\",\n                \"hive\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname NULLS LAST\",\n                \"spark\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname NULLS LAST\",\n                \"snowflake\": \"SELECT fname, lname, age FROM person ORDER BY age DESC, fname ASC, lname\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_AGG(DISTINCT a)\",\n            write={\n                \"spark\": \"SELECT COLLECT_LIST(DISTINCT a)\",\n                \"snowflake\": \"SELECT ARRAY_AGG(DISTINCT a)\",\n                \"duckdb\": \"SELECT ARRAY_AGG(DISTINCT a) FILTER(WHERE a IS NOT NULL)\",\n                \"presto\": \"SELECT ARRAY_AGG(DISTINCT a) FILTER(WHERE a IS NOT NULL)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_AGG(col) WITHIN GROUP (ORDER BY sort_col)\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_AGG(col) WITHIN GROUP (ORDER BY sort_col)\",\n                \"duckdb\": \"SELECT ARRAY_AGG(col ORDER BY sort_col) FILTER(WHERE col IS NOT NULL)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_AGG(DISTINCT col) WITHIN GROUP (ORDER BY col DESC)\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_AGG(DISTINCT col) WITHIN GROUP (ORDER BY col DESC)\",\n                \"duckdb\": \"SELECT ARRAY_AGG(DISTINCT col ORDER BY col DESC NULLS FIRST) FILTER(WHERE col IS NOT NULL)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_TO_STRING(x, '')\",\n            write={\n                \"spark\": \"SELECT ARRAY_JOIN(x, '')\",\n                \"snowflake\": \"SELECT ARRAY_TO_STRING(x, '')\",\n                \"duckdb\": \"SELECT CASE WHEN '' IS NULL THEN NULL ELSE ARRAY_TO_STRING(LIST_TRANSFORM(x, x -> COALESCE(CAST(x AS TEXT), '')), '') END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_TO_STRING(x, NULL)\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_TO_STRING(x, NULL)\",\n                \"duckdb\": \"SELECT CASE WHEN NULL IS NULL THEN NULL ELSE ARRAY_TO_STRING(LIST_TRANSFORM(x, x -> COALESCE(CAST(x AS TEXT), '')), NULL) END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_TO_STRING([], ',')\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_TO_STRING([], ',')\",\n                \"duckdb\": \"SELECT CASE WHEN ',' IS NULL THEN NULL ELSE ARRAY_TO_STRING(LIST_TRANSFORM([], x -> COALESCE(CAST(x AS TEXT), '')), ',') END\",\n            },\n        )\n        self.validate_all(\n            \"TO_ARRAY(x)\",\n            write={\n                \"spark\": \"IF(x IS NULL, NULL, ARRAY(x))\",\n                \"snowflake\": \"TO_ARRAY(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a INTERSECT ALL SELECT * FROM b\",\n            write={\n                \"snowflake\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM a EXCEPT ALL SELECT * FROM b\",\n            write={\n                \"snowflake\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_UNION_AGG(a)\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_UNION_AGG(a)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT $$a$$\",\n            write={\n                \"snowflake\": \"SELECT 'a'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RLIKE(a, b)\",\n            write={\n                \"duckdb\": \"SELECT REGEXP_MATCHES(a, '^(' || (b) || ')$')\",\n                \"hive\": \"SELECT a RLIKE b\",\n                \"snowflake\": \"SELECT REGEXP_LIKE(a, b)\",\n                \"spark\": \"SELECT a RLIKE b\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RLIKE(a, b, 'i')\",\n            write={\n                \"duckdb\": \"SELECT REGEXP_MATCHES(a, '^(' || (b) || ')$', 'i')\",\n                \"snowflake\": \"SELECT REGEXP_LIKE(a, b, 'i')\",\n            },\n        )\n        self.validate_all(\n            \"'foo' REGEXP 'bar'\",\n            write={\n                \"snowflake\": \"REGEXP_LIKE('foo', 'bar')\",\n                \"postgres\": \"'foo' ~ 'bar'\",\n                \"mysql\": \"REGEXP_LIKE('foo', 'bar')\",\n                \"bigquery\": \"REGEXP_CONTAINS('foo', 'bar')\",\n            },\n        )\n        self.validate_all(\n            \"'foo' NOT REGEXP 'bar'\",\n            write={\n                \"snowflake\": \"NOT REGEXP_LIKE('foo', 'bar')\",\n                \"postgres\": \"NOT 'foo' ~ 'bar'\",\n                \"mysql\": \"NOT REGEXP_LIKE('foo', 'bar')\",\n                \"bigquery\": \"NOT REGEXP_CONTAINS('foo', 'bar')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM test pivot\",\n            write={\n                \"snowflake\": \"SELECT a FROM test AS pivot\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a FROM test unpivot\",\n            write={\n                \"snowflake\": \"SELECT a FROM test AS unpivot\",\n            },\n        )\n        self.validate_all(\n            \"trim(date_column, 'UTC')\",\n            write={\n                \"bigquery\": \"TRIM(date_column, 'UTC')\",\n                \"snowflake\": \"TRIM(date_column, 'UTC')\",\n                \"postgres\": \"TRIM('UTC' FROM date_column)\",\n            },\n        )\n        self.validate_all(\n            \"trim(date_column)\",\n            write={\n                \"snowflake\": \"TRIM(date_column)\",\n                \"bigquery\": \"TRIM(date_column)\",\n            },\n        )\n        self.validate_all(\n            \"DECODE(x, a, b, c, d, e)\",\n            write={\n                \"duckdb\": \"CASE WHEN x = a OR (x IS NULL AND a IS NULL) THEN b WHEN x = c OR (x IS NULL AND c IS NULL) THEN d ELSE e END\",\n                \"snowflake\": \"DECODE(x, a, b, c, d, e)\",\n            },\n        )\n        self.validate_all(\n            \"DECODE(TRUE, a.b = 'value', 'value')\",\n            write={\n                \"duckdb\": \"CASE WHEN TRUE = (a.b = 'value') OR (TRUE IS NULL AND (a.b = 'value') IS NULL) THEN 'value' END\",\n                \"snowflake\": \"DECODE(TRUE, a.b = 'value', 'value')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BOOLAND(1, -2)\",\n            read={\n                \"snowflake\": \"SELECT BOOLAND(1, -2)\",\n            },\n            write={\n                \"snowflake\": \"SELECT BOOLAND(1, -2)\",\n                \"duckdb\": \"SELECT ((ROUND(1, 0)) AND (ROUND(-2, 0)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BOOLOR(1, 0)\",\n            write={\n                \"snowflake\": \"SELECT BOOLOR(1, 0)\",\n                \"duckdb\": \"SELECT ((ROUND(1, 0)) OR (ROUND(0, 0)))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BOOLXOR(2, 0.3)\",\n            read={\n                \"snowflake\": \"SELECT BOOLXOR(2, 0.3)\",\n            },\n            write={\n                \"snowflake\": \"SELECT BOOLXOR(2, 0.3)\",\n                \"duckdb\": \"SELECT (ROUND(2, 0) AND (NOT ROUND(0.3, 0))) OR ((NOT ROUND(2, 0)) AND ROUND(0.3, 0))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT APPROX_PERCENTILE(a, 0.5) FROM t\",\n            read={\n                \"trino\": \"SELECT APPROX_PERCENTILE(a, 1, 0.5, 0.001) FROM t\",\n                \"presto\": \"SELECT APPROX_PERCENTILE(a, 1, 0.5, 0.001) FROM t\",\n            },\n            write={\n                \"trino\": \"SELECT APPROX_PERCENTILE(a, 0.5) FROM t\",\n                \"presto\": \"SELECT APPROX_PERCENTILE(a, 0.5) FROM t\",\n                \"snowflake\": \"SELECT APPROX_PERCENTILE(a, 0.5) FROM t\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT OBJECT_INSERT(OBJECT_INSERT(OBJECT_INSERT(OBJECT_CONSTRUCT('key5', 'value5'), 'key1', 5), 'key2', 2.2), 'key3', 'value3')\",\n            write={\n                \"snowflake\": \"SELECT OBJECT_INSERT(OBJECT_INSERT(OBJECT_INSERT(OBJECT_CONSTRUCT('key5', 'value5'), 'key1', 5), 'key2', 2.2), 'key3', 'value3')\",\n                \"duckdb\": \"SELECT STRUCT_INSERT(STRUCT_INSERT(STRUCT_INSERT({'key5': 'value5'}, key1 := 5), key2 := 2.2), key3 := 'value3')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT OBJECT_INSERT(OBJECT_INSERT(OBJECT_INSERT(OBJECT_CONSTRUCT(), 'key1', 5), 'key2', 2.2), 'key3', 'value3')\",\n            write={\n                \"snowflake\": \"SELECT OBJECT_INSERT(OBJECT_INSERT(OBJECT_INSERT(OBJECT_CONSTRUCT(), 'key1', 5), 'key2', 2.2), 'key3', 'value3')\",\n                \"duckdb\": \"SELECT STRUCT_INSERT(STRUCT_INSERT(STRUCT_PACK(key1 := 5), key2 := 2.2), key3 := 'value3')\",\n            },\n        )\n\n        self.validate_identity(\n            \"\"\"SELECT ARRAY_CONSTRUCT('foo')::VARIANT[0]\"\"\",\n            \"\"\"SELECT CAST(['foo'] AS VARIANT)[0]\"\"\",\n        )\n\n        self.validate_all(\n            \"SELECT CONVERT_TIMEZONE('America/New_York', '2024-08-06 09:10:00.000')\",\n            write={\n                \"snowflake\": \"SELECT CONVERT_TIMEZONE('America/New_York', '2024-08-06 09:10:00.000')\",\n                \"spark\": \"SELECT CONVERT_TIMEZONE('America/New_York', '2024-08-06 09:10:00.000')\",\n                \"databricks\": \"SELECT CONVERT_TIMEZONE('America/New_York', '2024-08-06 09:10:00.000')\",\n                \"redshift\": \"SELECT CONVERT_TIMEZONE('America/New_York', '2024-08-06 09:10:00.000')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT CONVERT_TIMEZONE('America/Los_Angeles', 'America/New_York', '2024-08-06 09:10:00.000')\",\n            write={\n                \"snowflake\": \"SELECT CONVERT_TIMEZONE('America/Los_Angeles', 'America/New_York', '2024-08-06 09:10:00.000')\",\n                \"spark\": \"SELECT CONVERT_TIMEZONE('America/Los_Angeles', 'America/New_York', '2024-08-06 09:10:00.000')\",\n                \"databricks\": \"SELECT CONVERT_TIMEZONE('America/Los_Angeles', 'America/New_York', '2024-08-06 09:10:00.000')\",\n                \"redshift\": \"SELECT CONVERT_TIMEZONE('America/Los_Angeles', 'America/New_York', '2024-08-06 09:10:00.000')\",\n                \"mysql\": \"SELECT CONVERT_TZ('2024-08-06 09:10:00.000', 'America/Los_Angeles', 'America/New_York')\",\n                \"duckdb\": \"SELECT CAST('2024-08-06 09:10:00.000' AS TIMESTAMP) AT TIME ZONE 'America/Los_Angeles' AT TIME ZONE 'America/New_York'\",\n            },\n        )\n\n        self.validate_identity(\n            \"SELECT UUID_STRING(), UUID_STRING('fe971b24-9572-4005-b22f-351e9c09274d', 'foo')\"\n        )\n\n        self.validate_all(\n            \"UUID_STRING('fe971b24-9572-4005-b22f-351e9c09274d', 'foo')\",\n            read={\n                \"snowflake\": \"UUID_STRING('fe971b24-9572-4005-b22f-351e9c09274d', 'foo')\",\n            },\n            write={\n                \"hive\": \"UUID()\",\n                \"spark2\": \"UUID()\",\n                \"spark\": \"UUID()\",\n                \"databricks\": \"UUID()\",\n                \"duckdb\": \"UUID()\",\n                \"presto\": \"UUID()\",\n                \"trino\": \"UUID()\",\n                \"postgres\": \"GEN_RANDOM_UUID()\",\n                \"bigquery\": \"GENERATE_UUID()\",\n            },\n        )\n        self.validate_identity(\"TRY_TO_TIMESTAMP(foo)\").assert_is(exp.Anonymous)\n        self.validate_identity(\"TRY_TO_TIMESTAMP('12345')\").assert_is(exp.Anonymous)\n        self.validate_all(\n            \"SELECT TRY_TO_TIMESTAMP('2024-01-15 12:30:00.000')\",\n            write={\n                \"snowflake\": \"SELECT TRY_CAST('2024-01-15 12:30:00.000' AS TIMESTAMP)\",\n                \"duckdb\": \"SELECT TRY_CAST('2024-01-15 12:30:00.000' AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRY_TO_TIMESTAMP('invalid')\",\n            write={\n                \"snowflake\": \"SELECT TRY_CAST('invalid' AS TIMESTAMP)\",\n                \"duckdb\": \"SELECT TRY_CAST('invalid' AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRY_TO_TIMESTAMP('04/05/2013 01:02:03', 'mm/DD/yyyy hh24:mi:ss')\",\n            write={\n                \"snowflake\": \"SELECT TRY_TO_TIMESTAMP('04/05/2013 01:02:03', 'mm/DD/yyyy hh24:mi:ss')\",\n                \"duckdb\": \"SELECT CAST(TRY_STRPTIME('04/05/2013 01:02:03', '%m/%d/%Y %H:%M:%S') AS TIMESTAMP)\",\n            },\n        )\n\n        self.validate_all(\n            \"EDITDISTANCE(col1, col2)\",\n            write={\n                \"duckdb\": \"LEVENSHTEIN(col1, col2)\",\n                \"snowflake\": \"EDITDISTANCE(col1, col2)\",\n            },\n        )\n        self.validate_all(\n            \"EDITDISTANCE(col1, col2, 3)\",\n            write={\n                \"bigquery\": \"EDIT_DISTANCE(col1, col2, max_distance => 3)\",\n                \"duckdb\": \"CASE WHEN LEVENSHTEIN(col1, col2) IS NULL OR 3 IS NULL THEN NULL ELSE LEAST(LEVENSHTEIN(col1, col2), 3) END\",\n                \"postgres\": \"LEVENSHTEIN_LESS_EQUAL(col1, col2, 3)\",\n                \"snowflake\": \"EDITDISTANCE(col1, col2, 3)\",\n            },\n        )\n\n        self.validate_identity(\"MINHASH(100, col1)\")\n        self.validate_identity(\"MINHASH(100, col1, col2)\")\n        self.validate_all(\n            \"MINHASH(4, col1)\",\n            write={\n                \"duckdb\": \"(SELECT JSON_OBJECT('state', LIST(min_h ORDER BY seed NULLS FIRST), 'type', 'minhash', 'version', 1) FROM (SELECT seed, LIST_MIN(LIST_TRANSFORM(vals, __v -> HASH(CAST(__v AS TEXT) || CAST(seed AS TEXT)))) AS min_h FROM (SELECT LIST(col1) AS vals), RANGE(0, 4) AS t(seed)))\",\n                \"snowflake\": \"MINHASH(4, col1)\",\n            },\n        )\n\n        self.validate_identity(\"MINHASH_COMBINE(sig_col)\")\n        self.validate_all(\n            \"MINHASH_COMBINE(sig_col)\",\n            write={\n                \"duckdb\": \"(SELECT JSON_OBJECT('state', LIST(min_h ORDER BY idx NULLS FIRST), 'type', 'minhash', 'version', 1) FROM (SELECT pos AS idx, MIN(val) AS min_h FROM UNNEST(LIST(sig_col)) AS _(sig) JOIN UNNEST(CAST(sig -> '$.state' AS UBIGINT[])) WITH ORDINALITY AS t(val, pos) ON TRUE GROUP BY pos))\",\n                \"snowflake\": \"MINHASH_COMBINE(sig_col)\",\n            },\n        )\n\n        self.validate_identity(\"APPROXIMATE_SIMILARITY(sig_col)\")\n        self.validate_all(\n            \"APPROXIMATE_SIMILARITY(sig_col)\",\n            write={\n                \"duckdb\": \"(SELECT CAST(SUM(CASE WHEN num_distinct = 1 THEN 1 ELSE 0 END) AS DOUBLE) / COUNT(*) FROM (SELECT pos, COUNT(DISTINCT h) AS num_distinct FROM (SELECT h, pos FROM UNNEST(LIST(sig_col)) AS _(sig) JOIN UNNEST(CAST(sig -> '$.state' AS UBIGINT[])) WITH ORDINALITY AS s(h, pos) ON TRUE) GROUP BY pos))\",\n                \"snowflake\": \"APPROXIMATE_SIMILARITY(sig_col)\",\n            },\n        )\n\n        self.validate_identity(\"SELECT BITNOT(a)\")\n        self.validate_identity(\"SELECT BIT_NOT(a)\", \"SELECT BITNOT(a)\")\n        self.validate_all(\n            \"SELECT BITNOT(-1)\",\n            write={\n                \"duckdb\": \"SELECT ~(-1)\",\n                \"snowflake\": \"SELECT BITNOT(-1)\",\n            },\n        )\n        self.validate_identity(\"SELECT BITAND(a, b)\")\n        self.validate_identity(\"SELECT BITAND(a, b, 'LEFT')\")\n        self.validate_identity(\"SELECT BIT_AND(a, b)\", \"SELECT BITAND(a, b)\")\n        self.validate_identity(\"SELECT BIT_AND(a, b, 'LEFT')\", \"SELECT BITAND(a, b, 'LEFT')\")\n        self.validate_identity(\"SELECT BITOR(a, b)\")\n        self.validate_identity(\"SELECT BITOR(a, b, 'LEFT')\")\n        self.validate_identity(\"SELECT BIT_OR(a, b)\", \"SELECT BITOR(a, b)\")\n        self.validate_identity(\"SELECT BIT_OR(a, b, 'RIGHT')\", \"SELECT BITOR(a, b, 'RIGHT')\")\n        self.validate_identity(\"SELECT BITXOR(a, b)\")\n        self.validate_identity(\"SELECT BITXOR(a, b, 'LEFT')\")\n        self.validate_identity(\"SELECT BIT_XOR(a, b)\", \"SELECT BITXOR(a, b)\")\n        self.validate_identity(\"SELECT BIT_XOR(a, b, 'LEFT')\", \"SELECT BITXOR(a, b, 'LEFT')\")\n\n        # duckdb has an order of operations precedence issue with bitshift and bitwise operators\n        self.validate_all(\n            \"SELECT BITOR(BITSHIFTLEFT(5, 16), BITSHIFTLEFT(3, 8))\",\n            write={\"duckdb\": \"SELECT (CAST(5 AS INT128) << 16) | (CAST(3 AS INT128) << 8)\"},\n        )\n        self.validate_all(\n            \"SELECT BITAND(BITSHIFTLEFT(255, 4), BITSHIFTLEFT(15, 2))\",\n            write={\n                \"snowflake\": \"SELECT BITAND(BITSHIFTLEFT(255, 4), BITSHIFTLEFT(15, 2))\",\n                \"duckdb\": \"SELECT (CAST(255 AS INT128) << 4) & (CAST(15 AS INT128) << 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BITSHIFTLEFT(255, 4)\",\n            write={\n                \"snowflake\": \"SELECT BITSHIFTLEFT(255, 4)\",\n                \"duckdb\": \"SELECT CAST(255 AS INT128) << 4\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BITSHIFTRIGHT(255, 4)\",\n            write={\n                \"snowflake\": \"SELECT BITSHIFTRIGHT(255, 4)\",\n                \"duckdb\": \"SELECT CAST(255 AS INT128) >> 4\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BITSHIFTLEFT(X'002A'::BINARY, 1)\",\n            write={\n                \"snowflake\": \"SELECT BITSHIFTLEFT(CAST(x'002A' AS BINARY), 1)\",\n                \"duckdb\": \"SELECT CAST(CAST(CAST(UNHEX('002A') AS BLOB) AS BIT) << 1 AS BLOB)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BITSHIFTRIGHT(X'002A'::BINARY, 1)\",\n            write={\n                \"snowflake\": \"SELECT BITSHIFTRIGHT(CAST(x'002A' AS BINARY), 1)\",\n                \"duckdb\": \"SELECT CAST(CAST(CAST(UNHEX('002A') AS BLOB) AS BIT) >> 1 AS BLOB)\",\n            },\n        )\n\n        self.validate_all(\n            \"OCTET_LENGTH('A')\",\n            read={\n                \"bigquery\": \"BYTE_LENGTH('A')\",\n                \"snowflake\": \"OCTET_LENGTH('A')\",\n            },\n        )\n\n        self.validate_identity(\"CREATE TABLE t (id INT PRIMARY KEY AUTOINCREMENT)\")\n\n        self.validate_all(\n            \"SELECT HEX_DECODE_BINARY('65')\",\n            write={\n                \"bigquery\": \"SELECT FROM_HEX('65')\",\n                \"duckdb\": \"SELECT UNHEX('65')\",\n                \"snowflake\": \"SELECT HEX_DECODE_BINARY('65')\",\n            },\n        )\n\n        self.validate_all(\n            \"DAYOFWEEKISO(foo)\",\n            read={\n                \"snowflake\": \"DAYOFWEEKISO(foo)\",\n                \"presto\": \"DAY_OF_WEEK(foo)\",\n                \"trino\": \"DAY_OF_WEEK(foo)\",\n            },\n            write={\n                \"duckdb\": \"ISODOW(foo)\",\n            },\n        )\n\n        self.validate_all(\n            \"DAYOFWEEKISO(foo)\",\n            read={\n                \"presto\": \"DOW(foo)\",\n                \"trino\": \"DOW(foo)\",\n            },\n        )\n\n        self.validate_all(\n            \"DAYOFYEAR(foo)\",\n            read={\n                \"presto\": \"DOY(foo)\",\n                \"trino\": \"DOY(foo)\",\n            },\n            write={\n                \"snowflake\": \"DAYOFYEAR(foo)\",\n            },\n        )\n\n        self.validate_identity(\"TO_JSON(OBJECT_CONSTRUCT('name', 'Alice'))\")\n\n        with self.assertRaises(ParseError):\n            parse_one(\n                \"SELECT id, PRIOR name AS parent_name, name FROM tree CONNECT BY NOCYCLE PRIOR id = parent_id\",\n                dialect=\"snowflake\",\n            )\n\n        self.validate_all(\n            \"SELECT CAST(1 AS DOUBLE), CAST(1 AS DOUBLE)\",\n            read={\n                \"bigquery\": \"SELECT CAST(1 AS BIGDECIMAL), CAST(1 AS BIGNUMERIC)\",\n            },\n            write={\n                \"snowflake\": \"SELECT CAST(1 AS DOUBLE), CAST(1 AS DOUBLE)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT DATE_PART(WEEKISO, CAST('2013-12-25' AS DATE))\",\n            read={\n                \"bigquery\": \"SELECT EXTRACT(ISOWEEK FROM CAST('2013-12-25' AS DATE))\",\n                \"snowflake\": \"SELECT DATE_PART(WEEKISO, CAST('2013-12-25' AS DATE))\",\n            },\n            write={\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2013-12-25' AS DATE), '%V') AS INT)\",\n            },\n        )\n        # DATE_PART/EXTRACT with specifiers not supported in DuckDB\n        self.validate_all(\n            \"SELECT DATE_PART(YEAROFWEEK, CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(YEAROFWEEK, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2026-01-06' AS DATE), '%G') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART(YEAROFWEEKISO, CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(YEAROFWEEKISO, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2026-01-06' AS DATE), '%G') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART(NANOSECOND, CAST('2026-01-06 11:45:00.123456789' AS TIMESTAMPNTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(NANOSECOND, CAST('2026-01-06 11:45:00.123456789' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST(CAST('2026-01-06 11:45:00.123456789' AS TIMESTAMP) AS TIMESTAMP_NS), '%n') AS BIGINT)\",\n            },\n        )\n        # TIMESTAMP_NTZ tests - using NTZ for consistent behavior across timezones\n        self.validate_all(\n            \"SELECT EXTRACT(YEAR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(YEAR, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(YEAR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(QUARTER FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(QUARTER, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(QUARTER FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(MONTH FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(MONTH, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(MONTH FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(WEEK FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(WEEK, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(WEEK FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(WEEKISO FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(WEEKISO, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2026-01-06 11:45:00' AS TIMESTAMP), '%V') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAY FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAY, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(DAY FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAYOFMONTH FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAY, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(DAY FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAYOFWEEK FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAYOFWEEK, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(DAYOFWEEK FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAYOFWEEKISO FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAYOFWEEKISO, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(ISODOW FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAYOFYEAR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAYOFYEAR, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(DAYOFYEAR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(YEAROFWEEK FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(YEAROFWEEK, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2026-01-06 11:45:00' AS TIMESTAMP), '%G') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(YEAROFWEEKISO FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(YEAROFWEEKISO, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2026-01-06 11:45:00' AS TIMESTAMP), '%G') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(HOUR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(HOUR, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(HOUR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(MINUTE FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(MINUTE, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(MINUTE FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(SECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(SECOND, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EXTRACT(SECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(NANOSECOND FROM CAST('2026-01-06 11:45:00.123456789' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(NANOSECOND, CAST('2026-01-06 11:45:00.123456789' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST(CAST('2026-01-06 11:45:00.123456789' AS TIMESTAMP) AS TIMESTAMP_NS), '%n') AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(EPOCH_SECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(EPOCH_SECOND, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT CAST(EPOCH(CAST('2026-01-06 11:45:00' AS TIMESTAMP)) AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(EPOCH_MILLISECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(EPOCH_MILLISECOND, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EPOCH_MS(CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(EPOCH_MICROSECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(EPOCH_MICROSECOND, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EPOCH_US(CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(EPOCH_NANOSECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(EPOCH_NANOSECOND, CAST('2026-01-06 11:45:00' AS TIMESTAMPNTZ))\",\n                \"duckdb\": \"SELECT EPOCH_NS(CAST('2026-01-06 11:45:00' AS TIMESTAMP))\",\n            },\n        )\n        # EXTRACT from DATE - exhaustive tests\n        self.validate_all(\n            \"SELECT EXTRACT(YEAR FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(YEAR, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(YEAR FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(QUARTER FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(QUARTER, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(QUARTER FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(MONTH FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(MONTH, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(MONTH FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(WEEK FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(WEEK, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(WEEK FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(WEEKISO FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(WEEKISO, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2026-01-06' AS DATE), '%V') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAY FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAY, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(DAY FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAYOFMONTH FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAY, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(DAY FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAYOFWEEK FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAYOFWEEK, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(DAYOFWEEK FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAYOFWEEKISO FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAYOFWEEKISO, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(ISODOW FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(DAYOFYEAR FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAYOFYEAR, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT EXTRACT(DAYOFYEAR FROM CAST('2026-01-06' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(YEAROFWEEK FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(YEAROFWEEK, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2026-01-06' AS DATE), '%G') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(YEAROFWEEKISO FROM CAST('2026-01-06' AS DATE))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(YEAROFWEEKISO, CAST('2026-01-06' AS DATE))\",\n                \"duckdb\": \"SELECT CAST(STRFTIME(CAST('2026-01-06' AS DATE), '%G') AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(HOUR FROM CAST('11:45:00.123456789' AS TIME))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(HOUR, CAST('11:45:00.123456789' AS TIME))\",\n                \"duckdb\": \"SELECT EXTRACT(HOUR FROM CAST('11:45:00.123456789' AS TIME))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(MINUTE FROM CAST('11:45:00.123456789' AS TIME))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(MINUTE, CAST('11:45:00.123456789' AS TIME))\",\n                \"duckdb\": \"SELECT EXTRACT(MINUTE FROM CAST('11:45:00.123456789' AS TIME))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT(SECOND FROM CAST('11:45:00.123456789' AS TIME))\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(SECOND, CAST('11:45:00.123456789' AS TIME))\",\n                \"duckdb\": \"SELECT EXTRACT(SECOND FROM CAST('11:45:00.123456789' AS TIME))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ST_MAKEPOINT(10, 20)\",\n            write={\n                \"snowflake\": \"SELECT ST_MAKEPOINT(10, 20)\",\n                \"starrocks\": \"SELECT ST_POINT(10, 20)\",\n            },\n        )\n\n        self.validate_all(\n            \"LAST_DAY(CAST('2023-04-15' AS DATE))\",\n            write={\n                \"snowflake\": \"LAST_DAY(CAST('2023-04-15' AS DATE))\",\n                \"duckdb\": \"LAST_DAY(CAST('2023-04-15' AS DATE))\",\n            },\n        )\n\n        self.validate_all(\n            \"LAST_DAY(CAST('2023-04-15' AS DATE), MONTH)\",\n            write={\n                \"snowflake\": \"LAST_DAY(CAST('2023-04-15' AS DATE), MONTH)\",\n                \"duckdb\": \"LAST_DAY(CAST('2023-04-15' AS DATE))\",\n            },\n        )\n\n        self.validate_all(\n            \"LAST_DAY(CAST('2024-06-15' AS DATE), YEAR)\",\n            write={\n                \"snowflake\": \"LAST_DAY(CAST('2024-06-15' AS DATE), YEAR)\",\n                \"duckdb\": \"MAKE_DATE(EXTRACT(YEAR FROM CAST('2024-06-15' AS DATE)), 12, 31)\",\n            },\n        )\n\n        self.validate_all(\n            \"LAST_DAY(CAST('2024-01-15' AS DATE), QUARTER)\",\n            write={\n                \"snowflake\": \"LAST_DAY(CAST('2024-01-15' AS DATE), QUARTER)\",\n                \"duckdb\": \"LAST_DAY(MAKE_DATE(EXTRACT(YEAR FROM CAST('2024-01-15' AS DATE)), EXTRACT(QUARTER FROM CAST('2024-01-15' AS DATE)) * 3, 1))\",\n            },\n        )\n\n        self.validate_all(\n            \"LAST_DAY(CAST('2025-12-15' AS DATE), WEEK)\",\n            write={\n                \"snowflake\": \"LAST_DAY(CAST('2025-12-15' AS DATE), WEEK)\",\n                \"duckdb\": \"CAST(CAST('2025-12-15' AS DATE) + INTERVAL ((7 - EXTRACT(DAYOFWEEK FROM CAST('2025-12-15' AS DATE))) % 7) DAY AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ST_DISTANCE(a, b)\",\n            write={\n                \"snowflake\": \"SELECT ST_DISTANCE(a, b)\",\n                \"starrocks\": \"SELECT ST_DISTANCE_SPHERE(ST_X(a), ST_Y(a), ST_X(b), ST_Y(b))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT DATE_PART(DAYOFWEEKISO, foo)\",\n            read={\n                \"snowflake\": \"SELECT DATE_PART(WEEKDAY_ISO, foo)\",\n            },\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAYOFWEEKISO, foo)\",\n                \"duckdb\": \"SELECT EXTRACT(ISODOW FROM foo)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT DATE_PART(DAYOFWEEK_ISO, foo)\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(DAYOFWEEKISO, foo)\",\n                \"duckdb\": \"SELECT EXTRACT(ISODOW FROM foo)\",\n            },\n        )\n        self.validate_identity(\"ALTER TABLE foo ADD col1 VARCHAR(512), col2 VARCHAR(512)\")\n        self.validate_identity(\n            \"ALTER TABLE foo ADD col1 VARCHAR NOT NULL TAG (key1='value_1'), col2 VARCHAR NOT NULL TAG (key2='value_2')\"\n        )\n        self.validate_identity(\"ALTER TABLE foo ADD IF NOT EXISTS col1 INT, col2 INT\")\n        self.validate_identity(\"ALTER TABLE foo ADD IF NOT EXISTS col1 INT, IF NOT EXISTS col2 INT\")\n        self.validate_identity(\"ALTER TABLE foo ADD col1 INT, IF NOT EXISTS col2 INT\")\n        self.validate_identity(\"ALTER TABLE IF EXISTS foo ADD IF NOT EXISTS col1 INT\")\n        # ADD_MONTHS - Basic integer months with type preservation\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2023-01-31', 1)\",\n            write={\n                \"duckdb\": \"SELECT CASE WHEN LAST_DAY(CAST('2023-01-31' AS TIMESTAMP)) = CAST('2023-01-31' AS TIMESTAMP) THEN LAST_DAY(CAST('2023-01-31' AS TIMESTAMP) + INTERVAL 1 MONTH) ELSE CAST('2023-01-31' AS TIMESTAMP) + INTERVAL 1 MONTH END\",\n                \"snowflake\": \"SELECT ADD_MONTHS('2023-01-31', 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2023-01-31'::date, 1)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2023-01-31' AS DATE)) = CAST('2023-01-31' AS DATE) THEN LAST_DAY(CAST('2023-01-31' AS DATE) + INTERVAL 1 MONTH) ELSE CAST('2023-01-31' AS DATE) + INTERVAL 1 MONTH END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2023-01-31' AS DATE), 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2023-01-31'::timestamptz, 1)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2023-01-31' AS TIMESTAMPTZ)) = CAST('2023-01-31' AS TIMESTAMPTZ) THEN LAST_DAY(CAST('2023-01-31' AS TIMESTAMPTZ) + INTERVAL 1 MONTH) ELSE CAST('2023-01-31' AS TIMESTAMPTZ) + INTERVAL 1 MONTH END AS TIMESTAMPTZ)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2023-01-31' AS TIMESTAMPTZ), 1)\",\n            },\n        )\n\n        # ADD_MONTHS - Float month values (rounded to integer)\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-05-15'::DATE, 2.7)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-05-15' AS DATE)) = CAST('2016-05-15' AS DATE) THEN LAST_DAY(CAST('2016-05-15' AS DATE) + TO_MONTHS(CAST(ROUND(2.7) AS INT))) ELSE CAST('2016-05-15' AS DATE) + TO_MONTHS(CAST(ROUND(2.7) AS INT)) END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-05-15' AS DATE), 2.7)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-05-15'::DATE, -2.3)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-05-15' AS DATE)) = CAST('2016-05-15' AS DATE) THEN LAST_DAY(CAST('2016-05-15' AS DATE) + TO_MONTHS(CAST(ROUND(-2.3) AS INT))) ELSE CAST('2016-05-15' AS DATE) + TO_MONTHS(CAST(ROUND(-2.3) AS INT)) END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-05-15' AS DATE), -2.3)\",\n            },\n        )\n\n        # ADD_MONTHS - Decimal month values (rounded to integer)\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-05-15'::DATE, 3.2::DECIMAL(10,2))\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-05-15' AS DATE)) = CAST('2016-05-15' AS DATE) THEN LAST_DAY(CAST('2016-05-15' AS DATE) + TO_MONTHS(CAST(ROUND(CAST(3.2 AS DECIMAL(10, 2))) AS INT))) ELSE CAST('2016-05-15' AS DATE) + TO_MONTHS(CAST(ROUND(CAST(3.2 AS DECIMAL(10, 2))) AS INT)) END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-05-15' AS DATE), CAST(3.2 AS DECIMAL(10, 2)))\",\n            },\n        )\n\n        # ADD_MONTHS - End-of-month preservation (Snowflake semantic)\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-02-29'::DATE, 1)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-02-29' AS DATE)) = CAST('2016-02-29' AS DATE) THEN LAST_DAY(CAST('2016-02-29' AS DATE) + INTERVAL 1 MONTH) ELSE CAST('2016-02-29' AS DATE) + INTERVAL 1 MONTH END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-02-29' AS DATE), 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-05-31'::DATE, 1)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-05-31' AS DATE)) = CAST('2016-05-31' AS DATE) THEN LAST_DAY(CAST('2016-05-31' AS DATE) + INTERVAL 1 MONTH) ELSE CAST('2016-05-31' AS DATE) + INTERVAL 1 MONTH END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-05-31' AS DATE), 1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-05-31'::DATE, -1)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-05-31' AS DATE)) = CAST('2016-05-31' AS DATE) THEN LAST_DAY(CAST('2016-05-31' AS DATE) + INTERVAL (-1) MONTH) ELSE CAST('2016-05-31' AS DATE) + INTERVAL (-1) MONTH END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-05-31' AS DATE), -1)\",\n            },\n        )\n\n        # ADD_MONTHS - Mid-month dates (end-of-month logic should not trigger)\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-05-15'::DATE, 1)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-05-15' AS DATE)) = CAST('2016-05-15' AS DATE) THEN LAST_DAY(CAST('2016-05-15' AS DATE) + INTERVAL 1 MONTH) ELSE CAST('2016-05-15' AS DATE) + INTERVAL 1 MONTH END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-05-15' AS DATE), 1)\",\n            },\n        )\n\n        # ADD_MONTHS - NULL handling\n        self.validate_all(\n            \"SELECT ADD_MONTHS(NULL::DATE, 2)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST(NULL AS DATE)) = CAST(NULL AS DATE) THEN LAST_DAY(CAST(NULL AS DATE) + INTERVAL 2 MONTH) ELSE CAST(NULL AS DATE) + INTERVAL 2 MONTH END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST(NULL AS DATE), 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-05-15'::DATE, NULL)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-05-15' AS DATE)) = CAST('2016-05-15' AS DATE) THEN LAST_DAY(CAST('2016-05-15' AS DATE) + INTERVAL (NULL) MONTH) ELSE CAST('2016-05-15' AS DATE) + INTERVAL (NULL) MONTH END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-05-15' AS DATE), NULL)\",\n            },\n        )\n\n        # ADD_MONTHS - Zero months\n        self.validate_all(\n            \"SELECT ADD_MONTHS('2016-05-15'::DATE, 0)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CASE WHEN LAST_DAY(CAST('2016-05-15' AS DATE)) = CAST('2016-05-15' AS DATE) THEN LAST_DAY(CAST('2016-05-15' AS DATE) + INTERVAL 0 MONTH) ELSE CAST('2016-05-15' AS DATE) + INTERVAL 0 MONTH END AS DATE)\",\n                \"snowflake\": \"SELECT ADD_MONTHS(CAST('2016-05-15' AS DATE), 0)\",\n            },\n        )\n\n        self.validate_identity(\"SELECT HOUR(CAST('08:50:57' AS TIME))\")\n        self.validate_identity(\"SELECT MINUTE(CAST('08:50:57' AS TIME))\")\n        self.validate_identity(\"SELECT SECOND(CAST('08:50:57' AS TIME))\")\n        self.validate_identity(\"SELECT HOUR(CAST('2024-05-09 08:50:57' AS TIMESTAMP))\")\n        self.validate_identity(\"SELECT MONTHNAME(CAST('2024-05-09' AS DATE))\")\n        self.validate_all(\n            \"SELECT DAYNAME(TO_DATE('2025-01-15'))\",\n            write={\n                \"duckdb\": \"SELECT STRFTIME(CAST('2025-01-15' AS DATE), '%a')\",\n                \"snowflake\": \"SELECT DAYNAME(CAST('2025-01-15' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DAYNAME(TO_TIMESTAMP('2025-02-28 10:30:45'))\",\n            write={\n                \"duckdb\": \"SELECT STRFTIME(CAST('2025-02-28 10:30:45' AS TIMESTAMP), '%a')\",\n                \"snowflake\": \"SELECT DAYNAME(CAST('2025-02-28 10:30:45' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MONTHNAME(TO_DATE('2025-01-15'))\",\n            write={\n                \"duckdb\": \"SELECT STRFTIME(CAST('2025-01-15' AS DATE), '%b')\",\n                \"snowflake\": \"SELECT MONTHNAME(CAST('2025-01-15' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MONTHNAME(TO_TIMESTAMP('2025-02-28 10:30:45'))\",\n            write={\n                \"duckdb\": \"SELECT STRFTIME(CAST('2025-02-28 10:30:45' AS TIMESTAMP), '%b')\",\n                \"snowflake\": \"SELECT MONTHNAME(CAST('2025-02-28 10:30:45' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_identity(\"SELECT PREVIOUS_DAY(CAST('2024-05-09' AS DATE), 'MONDAY')\")\n        self.validate_identity(\"SELECT TIME_FROM_PARTS(14, 30, 45)\")\n        self.validate_identity(\"SELECT TIME_FROM_PARTS(14, 30, 45, 123)\")\n\n        self.validate_identity(\n            \"SELECT MONTHS_BETWEEN(CAST('2019-03-15' AS DATE), CAST('2019-02-15' AS DATE))\"\n        )\n        self.validate_identity(\n            \"SELECT MONTHS_BETWEEN(CAST('2019-03-01 02:00:00' AS TIMESTAMP), CAST('2019-02-15 01:00:00' AS TIMESTAMP))\"\n        )\n\n        self.validate_identity(\n            \"SELECT TIME_SLICE(CAST('2024-05-09 08:50:57.891' AS TIMESTAMP), 15, 'MINUTE')\"\n        )\n        self.validate_identity(\"SELECT TIME_SLICE(CAST('2024-05-09' AS DATE), 1, 'DAY')\")\n        self.validate_identity(\n            \"SELECT TIME_SLICE(CAST('2024-05-09 08:50:57.891' AS TIMESTAMP), 1, 'HOUR', 'start')\"\n        )\n\n        # TIME_SLICE transpilation to DuckDB\n        self.validate_all(\n            \"SELECT TIME_SLICE(TIMESTAMP '2024-03-15 14:37:42', 1, 'HOUR')\",\n            write={\n                \"snowflake\": \"SELECT TIME_SLICE(CAST('2024-03-15 14:37:42' AS TIMESTAMP), 1, 'HOUR')\",\n                \"duckdb\": \"SELECT TIME_BUCKET(INTERVAL 1 HOUR, CAST('2024-03-15 14:37:42' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_SLICE(TIMESTAMP '2024-03-15 14:37:42', 1, 'HOUR', 'END')\",\n            write={\n                \"snowflake\": \"SELECT TIME_SLICE(CAST('2024-03-15 14:37:42' AS TIMESTAMP), 1, 'HOUR', 'END')\",\n                \"duckdb\": \"SELECT TIME_BUCKET(INTERVAL 1 HOUR, CAST('2024-03-15 14:37:42' AS TIMESTAMP)) + INTERVAL 1 HOUR\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_SLICE(DATE '2024-03-15', 1, 'DAY')\",\n            write={\n                \"snowflake\": \"SELECT TIME_SLICE(CAST('2024-03-15' AS DATE), 1, 'DAY')\",\n                \"duckdb\": \"SELECT TIME_BUCKET(INTERVAL 1 DAY, CAST('2024-03-15' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_SLICE(DATE '2024-03-15', 1, 'DAY', 'END')\",\n            write={\n                \"snowflake\": \"SELECT TIME_SLICE(CAST('2024-03-15' AS DATE), 1, 'DAY', 'END')\",\n                \"duckdb\": \"SELECT CAST(TIME_BUCKET(INTERVAL 1 DAY, CAST('2024-03-15' AS DATE)) + INTERVAL 1 DAY AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_SLICE(TIMESTAMP '2024-03-15 14:37:42', 15, 'MINUTE')\",\n            write={\n                \"snowflake\": \"SELECT TIME_SLICE(CAST('2024-03-15 14:37:42' AS TIMESTAMP), 15, 'MINUTE')\",\n                \"duckdb\": \"SELECT TIME_BUCKET(INTERVAL 15 MINUTE, CAST('2024-03-15 14:37:42' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_SLICE(TIMESTAMP '2024-03-15 14:37:42', 1, 'QUARTER')\",\n            write={\n                \"snowflake\": \"SELECT TIME_SLICE(CAST('2024-03-15 14:37:42' AS TIMESTAMP), 1, 'QUARTER')\",\n                \"duckdb\": \"SELECT TIME_BUCKET(INTERVAL 1 QUARTER, CAST('2024-03-15 14:37:42' AS TIMESTAMP))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIME_SLICE(DATE '2024-03-15', 1, 'WEEK', 'END')\",\n            write={\n                \"snowflake\": \"SELECT TIME_SLICE(CAST('2024-03-15' AS DATE), 1, 'WEEK', 'END')\",\n                \"duckdb\": \"SELECT CAST(TIME_BUCKET(INTERVAL 1 WEEK, CAST('2024-03-15' AS DATE)) + INTERVAL 1 WEEK AS DATE)\",\n            },\n        )\n\n        for join in (\"FULL OUTER\", \"LEFT\", \"RIGHT\", \"LEFT OUTER\", \"RIGHT OUTER\", \"INNER\"):\n            with self.subTest(f\"Testing transpilation of {join} from Snowflake to DuckDB\"):\n                self.validate_all(\n                    f\"SELECT * FROM t1 {join} JOIN t2\",\n                    read={\n                        \"snowflake\": f\"SELECT * FROM t1 {join} JOIN t2\",\n                    },\n                    write={\n                        \"duckdb\": \"SELECT * FROM t1, t2\",\n                    },\n                )\n\n        self.validate_identity(\n            \"SELECT * EXCLUDE foo RENAME bar AS baz FROM tbl\",\n            \"SELECT * EXCLUDE (foo) RENAME (bar AS baz) FROM tbl\",\n        )\n\n        self.validate_all(\n            \"WITH foo AS (SELECT [1] AS arr_1) SELECT (SELECT unnested_arr FROM TABLE(FLATTEN(INPUT => arr_1)) AS _t0(seq, key, path, index, unnested_arr, this)) AS f FROM foo\",\n            read={\n                \"bigquery\": \"WITH foo AS (SELECT [1] AS arr_1) SELECT (SELECT unnested_arr FROM UNNEST(arr_1) AS unnested_arr) AS f FROM foo\",\n            },\n        )\n\n        self.validate_identity(\"SELECT LIKE(col, 'pattern')\", \"SELECT col LIKE 'pattern'\")\n        self.validate_identity(\"SELECT ILIKE(col, 'pattern')\", \"SELECT col ILIKE 'pattern'\")\n        self.validate_identity(\n            \"SELECT LIKE(col, 'pattern', '\\\\\\\\')\", \"SELECT col LIKE 'pattern' ESCAPE '\\\\\\\\'\"\n        )\n        self.validate_identity(\n            \"SELECT ILIKE(col, 'pattern', '\\\\\\\\')\", \"SELECT col ILIKE 'pattern' ESCAPE '\\\\\\\\'\"\n        )\n        self.validate_identity(\n            \"SELECT LIKE(col, 'pattern', '!')\", \"SELECT col LIKE 'pattern' ESCAPE '!'\"\n        )\n        self.validate_identity(\n            \"SELECT ILIKE(col, 'pattern', '!')\", \"SELECT col ILIKE 'pattern' ESCAPE '!'\"\n        )\n\n        expr = self.validate_identity(\"SELECT BASE64_ENCODE('Hello World')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"SELECT TO_BASE64(ENCODE('Hello World'))\")\n        self.validate_all(\n            \"SELECT BASE64_ENCODE(x)\",\n            write={\n                \"duckdb\": \"SELECT TO_BASE64(x)\",\n                \"snowflake\": \"SELECT BASE64_ENCODE(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BASE64_ENCODE(x, 76)\",\n            write={\n                \"duckdb\": \"SELECT RTRIM(REGEXP_REPLACE(TO_BASE64(x), '(.{76})', '\\\\1' || CHR(10), 'g'), CHR(10))\",\n                \"snowflake\": \"SELECT BASE64_ENCODE(x, 76)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BASE64_ENCODE(x, 76, '+/=')\",\n            write={\n                \"duckdb\": \"SELECT RTRIM(REGEXP_REPLACE(TO_BASE64(x), '(.{76})', '\\\\1' || CHR(10), 'g'), CHR(10))\",\n                \"snowflake\": \"SELECT BASE64_ENCODE(x, 76, '+/=')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT BASE64_DECODE_STRING('U25vd2ZsYWtl')\",\n            write={\n                \"snowflake\": \"SELECT BASE64_DECODE_STRING('U25vd2ZsYWtl')\",\n                \"duckdb\": \"SELECT DECODE(FROM_BASE64('U25vd2ZsYWtl'))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BASE64_DECODE_STRING('U25vd2ZsYWtl', '-_+')\",\n            write={\n                \"snowflake\": \"SELECT BASE64_DECODE_STRING('U25vd2ZsYWtl', '-_+')\",\n                \"duckdb\": \"SELECT DECODE(FROM_BASE64(REPLACE(REPLACE(REPLACE('U25vd2ZsYWtl', '-', '+'), '_', '/'), '+', '=')))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BASE64_DECODE_BINARY(x)\",\n            write={\n                \"snowflake\": \"SELECT BASE64_DECODE_BINARY(x)\",\n                \"duckdb\": \"SELECT FROM_BASE64(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT BASE64_DECODE_BINARY(x, '-_+')\",\n            write={\n                \"snowflake\": \"SELECT BASE64_DECODE_BINARY(x, '-_+')\",\n                \"duckdb\": \"SELECT FROM_BASE64(REPLACE(REPLACE(REPLACE(x, '-', '+'), '_', '/'), '+', '='))\",\n            },\n        )\n\n        self.validate_identity(\"SELECT TRY_HEX_DECODE_BINARY('48656C6C6F')\")\n\n        self.validate_identity(\"SELECT TRY_HEX_DECODE_STRING('48656C6C6F')\")\n\n        self.validate_all(\n            \"SELECT ARRAY_CONTAINS(CAST('1' AS VARIANT), ['1'])\",\n            read={\n                \"presto\": \"SELECT CONTAINS(ARRAY['1'], '1')\",\n                \"snowflake\": \"SELECT ARRAY_CONTAINS(CAST('1' AS VARIANT), ['1'])\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_CONTAINS(CAST(CAST('2020-10-10' AS DATE) AS VARIANT), [CAST('2020-10-10' AS DATE)])\",\n            read={\n                \"presto\": \"SELECT CONTAINS(ARRAY[DATE '2020-10-10'], DATE '2020-10-10')\",\n                \"snowflake\": \"SELECT ARRAY_CONTAINS(CAST(CAST('2020-10-10' AS DATE) AS VARIANT), [CAST('2020-10-10' AS DATE)])\",\n            },\n        )\n        self.validate_identity(\"SELECT ARRAY_CONTAINS(1, [1])\")\n\n        self.validate_all(\n            \"SELECT ARRAY_CONTAINS(x, [1, NULL, 3])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_CONTAINS(x, [1, NULL, 3])\",\n                \"duckdb\": \"SELECT CASE WHEN x IS NULL THEN NULLIF(ARRAY_LENGTH([1, NULL, 3]) <> LIST_COUNT([1, NULL, 3]), FALSE) ELSE ARRAY_CONTAINS([1, NULL, 3], x) END\",\n            },\n        )\n\n        self.validate_identity(\"SELECT ARRAY_DISTINCT(['A', 'B', 'A'])\")\n\n        self.validate_all(\n            \"SELECT ARRAY_DISTINCT(['A', NULL, 'B', NULL])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_DISTINCT(['A', NULL, 'B', NULL])\",\n                \"duckdb\": \"SELECT CASE WHEN ARRAY_LENGTH(['A', NULL, 'B', NULL]) <> LIST_COUNT(['A', NULL, 'B', NULL]) THEN LIST_APPEND(LIST_DISTINCT(LIST_FILTER(['A', NULL, 'B', NULL], _u -> NOT _u IS NULL)), NULL) ELSE LIST_DISTINCT(['A', NULL, 'B', NULL]) END\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])\",\n                \"duckdb\": \"SELECT CASE WHEN ARRAY_LENGTH([1, 2, 2, 3, 1]) <> LIST_COUNT([1, 2, 2, 3, 1]) THEN LIST_APPEND(LIST_DISTINCT(LIST_FILTER([1, 2, 2, 3, 1], _u -> NOT _u IS NULL)), NULL) ELSE LIST_DISTINCT([1, 2, 2, 3, 1]) END\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT x'ABCD'\",\n            write={\n                \"snowflake\": \"SELECT x'ABCD'\",\n                \"duckdb\": \"SELECT UNHEX('ABCD')\",\n            },\n        )\n\n        self.validate_all(\n            \"SET a = 1\",\n            write={\n                \"snowflake\": \"SET a = 1\",\n                \"bigquery\": \"SET a = 1\",\n                \"duckdb\": \"SET VARIABLE a = 1\",\n            },\n        )\n        self.validate_all(\n            \"CAST(6.43 AS FLOAT)\",\n            write={\n                \"snowflake\": \"CAST(6.43 AS DOUBLE)\",\n                \"duckdb\": \"CAST(6.43 AS DOUBLE)\",\n            },\n        )\n        self.validate_all(\n            \"UNIFORM(1, 10, RANDOM(5))\",\n            write={\n                \"snowflake\": \"UNIFORM(1, 10, RANDOM(5))\",\n                \"databricks\": \"UNIFORM(1, 10, 5)\",\n                \"duckdb\": \"CAST(FLOOR(1 + RANDOM() * (10 - 1 + 1)) AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"UNIFORM(1, 10, RANDOM())\",\n            write={\n                \"snowflake\": \"UNIFORM(1, 10, RANDOM())\",\n                \"databricks\": \"UNIFORM(1, 10)\",\n                \"duckdb\": \"CAST(FLOOR(1 + RANDOM() * (10 - 1 + 1)) AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"UNIFORM(1, 10, 5)\",\n            write={\n                \"snowflake\": \"UNIFORM(1, 10, 5)\",\n                \"databricks\": \"UNIFORM(1, 10, 5)\",\n                \"duckdb\": \"CAST(FLOOR(1 + (ABS(HASH(5)) % 1000000) / 1000000.0 * (10 - 1 + 1)) AS BIGINT)\",\n            },\n        )\n        self.validate_all(\n            \"NORMAL(0, 1, 42)\",\n            write={\n                \"snowflake\": \"NORMAL(0, 1, 42)\",\n                \"duckdb\": \"0 + (1 * SQRT(-2 * LN(GREATEST((ABS(HASH(42)) % 1000000) / 1000000.0, 1e-10))) * COS(2 * PI() * (ABS(HASH(42 + 1)) % 1000000) / 1000000.0))\",\n            },\n        )\n        self.validate_all(\n            \"NORMAL(10.5, 2.5, RANDOM())\",\n            write={\n                \"snowflake\": \"NORMAL(10.5, 2.5, RANDOM())\",\n                \"duckdb\": \"10.5 + (2.5 * SQRT(-2 * LN(GREATEST(RANDOM(), 1e-10))) * COS(2 * PI() * RANDOM()))\",\n            },\n        )\n        self.validate_all(\n            \"NORMAL(10.5, 2.5, RANDOM(5))\",\n            write={\n                \"snowflake\": \"NORMAL(10.5, 2.5, RANDOM(5))\",\n                \"duckdb\": \"10.5 + (2.5 * SQRT(-2 * LN(GREATEST((ABS(HASH(5)) % 1000000) / 1000000.0, 1e-10))) * COS(2 * PI() * (ABS(HASH(5 + 1)) % 1000000) / 1000000.0))\",\n            },\n        )\n        self.validate_all(\n            \"SYSDATE()\",\n            write={\n                \"snowflake\": \"SYSDATE()\",\n                \"duckdb\": \"CURRENT_TIMESTAMP AT TIME ZONE 'UTC'\",\n            },\n        )\n        self.validate_identity(\"SYSTIMESTAMP()\", \"CURRENT_TIMESTAMP()\")\n        self.validate_identity(\"GETDATE()\", \"CURRENT_TIMESTAMP()\")\n        self.validate_identity(\"LOCALTIMESTAMP\", \"CURRENT_TIMESTAMP\")\n        self.validate_identity(\"LOCALTIMESTAMP()\", \"CURRENT_TIMESTAMP()\")\n        self.validate_identity(\"LOCALTIMESTAMP(3)\", \"CURRENT_TIMESTAMP(3)\")\n\n        self.validate_all(\n            \"SELECT CURRENT_TIME(4)\",\n            write={\n                \"snowflake\": \"SELECT CURRENT_TIME(4)\",\n                \"duckdb\": \"SELECT LOCALTIME\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT CURRENT_TIME\",\n            write={\n                \"snowflake\": \"SELECT CURRENT_TIME\",\n                \"duckdb\": \"SELECT LOCALTIME\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FROM_PARTS(2026, 1, 100)\",\n            write={\n                \"snowflake\": \"SELECT DATE_FROM_PARTS(2026, 1, 100)\",\n                \"duckdb\": \"SELECT CAST(MAKE_DATE(2026, 1, 1) + INTERVAL (1 - 1) MONTH + INTERVAL (100 - 1) DAY AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FROM_PARTS(2026, 14, 32)\",\n            write={\n                \"snowflake\": \"SELECT DATE_FROM_PARTS(2026, 14, 32)\",\n                \"duckdb\": \"SELECT CAST(MAKE_DATE(2026, 1, 1) + INTERVAL (14 - 1) MONTH + INTERVAL (32 - 1) DAY AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FROM_PARTS(2026, 0, 0)\",\n            write={\n                \"snowflake\": \"SELECT DATE_FROM_PARTS(2026, 0, 0)\",\n                \"duckdb\": \"SELECT CAST(MAKE_DATE(2026, 1, 1) + INTERVAL (0 - 1) MONTH + INTERVAL (0 - 1) DAY AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FROM_PARTS(2026, -14, -32)\",\n            write={\n                \"snowflake\": \"SELECT DATE_FROM_PARTS(2026, -14, -32)\",\n                \"duckdb\": \"SELECT CAST(MAKE_DATE(2026, 1, 1) + INTERVAL (-14 - 1) MONTH + INTERVAL (-32 - 1) DAY AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FROM_PARTS(2024, 1, 60)\",\n            write={\n                \"snowflake\": \"SELECT DATE_FROM_PARTS(2024, 1, 60)\",\n                \"duckdb\": \"SELECT CAST(MAKE_DATE(2024, 1, 1) + INTERVAL (1 - 1) MONTH + INTERVAL (60 - 1) DAY AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FROM_PARTS(2026, NULL, 100)\",\n            write={\n                \"snowflake\": \"SELECT DATE_FROM_PARTS(2026, NULL, 100)\",\n                \"duckdb\": \"SELECT CAST(MAKE_DATE(2026, 1, 1) + INTERVAL (NULL - 1) MONTH + INTERVAL (100 - 1) DAY AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FROM_PARTS(2024 + 2, 1 + 2, 2 + 3)\",\n            write={\n                \"snowflake\": \"SELECT DATE_FROM_PARTS(2024 + 2, 1 + 2, 2 + 3)\",\n                \"duckdb\": \"SELECT CAST(MAKE_DATE(2024 + 2, 1, 1) + INTERVAL ((1 + 2) - 1) MONTH + INTERVAL ((2 + 3) - 1) DAY AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT DATE_FROM_PARTS(year, month, date)\",\n            write={\n                \"snowflake\": \"SELECT DATE_FROM_PARTS(year, month, date)\",\n                \"duckdb\": \"SELECT CAST(MAKE_DATE(year, 1, 1) + INTERVAL (month - 1) MONTH + INTERVAL (date - 1) DAY AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"EQUAL_NULL(a, b)\",\n            write={\n                \"snowflake\": \"EQUAL_NULL(a, b)\",\n                \"duckdb\": \"a IS NOT DISTINCT FROM b\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT CURRENT_VERSION()\",\n            write={\n                \"snowflake\": \"SELECT CURRENT_VERSION()\",\n                \"databricks\": \"SELECT CURRENT_VERSION()\",\n                \"spark\": \"SELECT VERSION()\",\n                \"mysql\": \"SELECT VERSION()\",\n                \"singlestore\": \"SELECT VERSION()\",\n                \"starrocks\": \"SELECT CURRENT_VERSION()\",\n                \"postgres\": \"SELECT VERSION()\",\n                \"redshift\": \"SELECT VERSION()\",\n                \"clickhouse\": \"SELECT VERSION()\",\n                \"trino\": \"SELECT VERSION()\",\n                \"duckdb\": \"SELECT VERSION()\",\n            },\n        )\n\n        self.validate_identity(\"SELECT CURRENT_DATABASE()\")\n        self.validate_identity(\"SELECT CURRENT_SCHEMA()\")\n\n        self.validate_all(\n            \"SELECT 1 WHERE 'abc' ILIKE ANY('%a%')\",\n            write={\n                \"snowflake\": \"SELECT 1 WHERE 'abc' ILIKE ANY('%a%')\",\n                \"duckdb\": \"SELECT 1 WHERE 'abc' ILIKE '%a%'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT 1 WHERE 'abc' LIKE ALL ('%a%')\",\n            write={\n                \"snowflake\": \"SELECT 1 WHERE 'abc' LIKE ALL ('%a%')\",\n                \"duckdb\": \"SELECT 1 WHERE 'abc' LIKE '%a%'\",\n            },\n        )\n\n    def test_null_treatment(self):\n        self.validate_all(\n            r\"SELECT FIRST_VALUE(TABLE1.COLUMN1) OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS MY_ALIAS FROM TABLE1\",\n            write={\n                \"snowflake\": r\"SELECT FIRST_VALUE(TABLE1.COLUMN1) OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2) AS MY_ALIAS FROM TABLE1\"\n            },\n        )\n        self.validate_all(\n            r\"SELECT FIRST_VALUE(TABLE1.COLUMN1 RESPECT NULLS) OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS MY_ALIAS FROM TABLE1\",\n            write={\n                \"snowflake\": r\"SELECT FIRST_VALUE(TABLE1.COLUMN1) RESPECT NULLS OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2) AS MY_ALIAS FROM TABLE1\"\n            },\n        )\n        self.validate_all(\n            r\"SELECT FIRST_VALUE(TABLE1.COLUMN1) RESPECT NULLS OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS MY_ALIAS FROM TABLE1\",\n            write={\n                \"snowflake\": r\"SELECT FIRST_VALUE(TABLE1.COLUMN1) RESPECT NULLS OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2) AS MY_ALIAS FROM TABLE1\"\n            },\n        )\n        self.validate_all(\n            r\"SELECT FIRST_VALUE(TABLE1.COLUMN1 IGNORE NULLS) OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS MY_ALIAS FROM TABLE1\",\n            write={\n                \"snowflake\": r\"SELECT FIRST_VALUE(TABLE1.COLUMN1) IGNORE NULLS OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2) AS MY_ALIAS FROM TABLE1\"\n            },\n        )\n        self.validate_all(\n            r\"SELECT FIRST_VALUE(TABLE1.COLUMN1) IGNORE NULLS OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS MY_ALIAS FROM TABLE1\",\n            write={\n                \"snowflake\": r\"SELECT FIRST_VALUE(TABLE1.COLUMN1) IGNORE NULLS OVER (PARTITION BY RANDOM_COLUMN1, RANDOM_COLUMN2) AS MY_ALIAS FROM TABLE1\"\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM foo WHERE 'str' IN (SELECT value FROM TABLE(FLATTEN(INPUT => vals)) AS _u(seq, key, path, index, value, this))\",\n            read={\n                \"bigquery\": \"SELECT * FROM foo WHERE 'str' IN UNNEST(vals)\",\n            },\n            write={\n                \"snowflake\": \"SELECT * FROM foo WHERE 'str' IN (SELECT value FROM TABLE(FLATTEN(INPUT => vals)) AS _u(seq, key, path, index, value, this))\",\n            },\n        )\n\n    def test_staged_files(self):\n        # Ensure we don't treat staged file paths as identifiers (i.e. they're not normalized)\n        staged_file = parse_one(\"SELECT * FROM @foo\", read=\"snowflake\")\n        self.assertEqual(\n            normalize_identifiers(staged_file, dialect=\"snowflake\").sql(dialect=\"snowflake\"),\n            staged_file.sql(dialect=\"snowflake\"),\n        )\n\n        self.validate_identity('SELECT * FROM @\"mystage\"')\n        self.validate_identity('SELECT * FROM @\"myschema\".\"mystage\"/file.gz')\n        self.validate_identity('SELECT * FROM @\"my_DB\".\"schEMA1\".mystage/file.gz')\n        self.validate_identity(\"SELECT metadata$filename FROM @s1/\")\n        self.validate_identity(\"SELECT * FROM @~\")\n        self.validate_identity(\"SELECT * FROM @~/some/path/to/file.csv\")\n        self.validate_identity(\"SELECT * FROM @mystage\")\n        self.validate_identity(\"SELECT * FROM '@mystage'\")\n        self.validate_identity(\"SELECT * FROM @namespace.mystage/path/to/file.json.gz\")\n        self.validate_identity(\"SELECT * FROM @namespace.%table_name/path/to/file.json.gz\")\n        self.validate_identity(\"SELECT * FROM '@external/location' (FILE_FORMAT => 'path.to.csv')\")\n        self.validate_identity(\"PUT file:///dir/tmp.csv @%table\", check_command_warning=True)\n        self.validate_identity(\"SELECT * FROM (SELECT a FROM @foo)\")\n        self.validate_identity(\n            \"SELECT * FROM (SELECT * FROM '@external/location' (FILE_FORMAT => 'path.to.csv'))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM @foo/bar (FILE_FORMAT => ds_sandbox.test.my_csv_format, PATTERN => 'test') AS bla\"\n        )\n        self.validate_identity(\n            \"SELECT t.$1, t.$2 FROM @mystage1 (FILE_FORMAT => 'myformat', PATTERN => '.*data.*[.]csv.gz') AS t\"\n        )\n        self.validate_identity(\n            \"SELECT parse_json($1):a.b FROM @mystage2/data1.json.gz\",\n            \"SELECT GET_PATH(PARSE_JSON($1), 'a.b') FROM @mystage2/data1.json.gz\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM @mystage t (c1)\",\n            \"SELECT * FROM @mystage AS t(c1)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM @foo/bar (PATTERN => 'test', FILE_FORMAT => ds_sandbox.test.my_csv_format) AS bla\",\n            \"SELECT * FROM @foo/bar (FILE_FORMAT => ds_sandbox.test.my_csv_format, PATTERN => 'test') AS bla\",\n        )\n\n        self.validate_identity(\n            \"SELECT * FROM @test.public.thing/location/somefile.csv( FILE_FORMAT => 'fmt' )\",\n            \"SELECT * FROM @test.public.thing/location/somefile.csv (FILE_FORMAT => 'fmt')\",\n        )\n\n    def test_sample(self):\n        self.validate_identity(\"SELECT * FROM testtable TABLESAMPLE BERNOULLI (20.3)\")\n        self.validate_identity(\"SELECT * FROM testtable TABLESAMPLE SYSTEM (3) SEED (82)\")\n        self.validate_identity(\n            \"SELECT a FROM test PIVOT(SUM(x) FOR y IN ('z', 'q')) AS x TABLESAMPLE BERNOULLI (0.1)\"\n        )\n        self.validate_identity(\n            \"SELECT i, j FROM table1 AS t1 INNER JOIN table2 AS t2 TABLESAMPLE BERNOULLI (50) WHERE t2.j = t1.i\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM (SELECT * FROM t1 JOIN t2 ON t1.a = t2.c) TABLESAMPLE BERNOULLI (1)\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM testtable TABLESAMPLE (10 ROWS)\",\n            \"SELECT * FROM testtable TABLESAMPLE BERNOULLI (10 ROWS)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM testtable TABLESAMPLE (100)\",\n            \"SELECT * FROM testtable TABLESAMPLE BERNOULLI (100)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM testtable SAMPLE (10)\",\n            \"SELECT * FROM testtable TABLESAMPLE BERNOULLI (10)\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM testtable SAMPLE ROW (0)\",\n            \"SELECT * FROM testtable TABLESAMPLE ROW (0)\",\n        )\n        self.validate_identity(\n            \"SELECT a FROM test SAMPLE BLOCK (0.5) SEED (42)\",\n            \"SELECT a FROM test TABLESAMPLE BLOCK (0.5) SEED (42)\",\n        )\n        self.validate_identity(\n            \"SELECT user_id, value FROM table_name SAMPLE BERNOULLI ($s) SEED (0)\",\n            \"SELECT user_id, value FROM table_name TABLESAMPLE BERNOULLI ($s) SEED (0)\",\n        )\n\n        self.validate_all(\n            \"SELECT * FROM example TABLESAMPLE BERNOULLI (3) SEED (82)\",\n            read={\n                \"duckdb\": \"SELECT * FROM example TABLESAMPLE BERNOULLI (3 PERCENT) REPEATABLE (82)\",\n            },\n            write={\n                \"databricks\": \"SELECT * FROM example TABLESAMPLE (3 PERCENT) REPEATABLE (82)\",\n                \"duckdb\": \"SELECT * FROM example TABLESAMPLE BERNOULLI (3 PERCENT) REPEATABLE (82)\",\n                \"snowflake\": \"SELECT * FROM example TABLESAMPLE BERNOULLI (3) SEED (82)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM test AS _tmp TABLESAMPLE (5)\",\n            write={\n                \"postgres\": \"SELECT * FROM test AS _tmp TABLESAMPLE BERNOULLI (5)\",\n                \"snowflake\": \"SELECT * FROM test AS _tmp TABLESAMPLE BERNOULLI (5)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\n            SELECT i, j\n                FROM\n                     table1 AS t1 SAMPLE (25)     -- 25% of rows in table1\n                         INNER JOIN\n                     table2 AS t2 SAMPLE (50)     -- 50% of rows in table2\n                WHERE t2.j = t1.i\"\"\",\n            write={\n                \"snowflake\": \"SELECT i, j FROM table1 AS t1 TABLESAMPLE BERNOULLI (25) /* 25% of rows in table1 */ INNER JOIN table2 AS t2 TABLESAMPLE BERNOULLI (50) /* 50% of rows in table2 */ WHERE t2.j = t1.i\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM testtable SAMPLE BLOCK (0.012) REPEATABLE (99992)\",\n            write={\n                \"snowflake\": \"SELECT * FROM testtable TABLESAMPLE BLOCK (0.012) SEED (99992)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (SELECT * FROM t1 join t2 on t1.a = t2.c) SAMPLE (1)\",\n            write={\n                \"snowflake\": \"SELECT * FROM (SELECT * FROM t1 JOIN t2 ON t1.a = t2.c) TABLESAMPLE BERNOULLI (1)\",\n                \"spark\": \"SELECT * FROM (SELECT * FROM t1 JOIN t2 ON t1.a = t2.c) TABLESAMPLE (1 PERCENT)\",\n            },\n        )\n        self.validate_all(\n            \"TO_DOUBLE(expr)\",\n            write={\n                \"snowflake\": \"TO_DOUBLE(expr)\",\n                \"duckdb\": \"CAST(expr AS DOUBLE)\",\n            },\n        )\n        self.validate_all(\n            \"TO_DOUBLE(expr, fmt)\",\n            write={\n                \"snowflake\": \"TO_DOUBLE(expr, fmt)\",\n                \"duckdb\": UnsupportedError,\n            },\n        )\n\n    def test_timestamps(self):\n        self.validate_identity(\"SELECT CAST('12:00:00' AS TIME)\")\n        self.validate_identity(\"SELECT DATE_PART(month, a)\")\n\n        self.validate_identity(\n            \"SELECT DATE_PART(year FROM CAST('2024-04-08' AS DATE))\",\n            \"SELECT DATE_PART(year, CAST('2024-04-08' AS DATE))\",\n        ).expressions[0].assert_is(exp.Extract)\n        self.validate_identity(\n            \"SELECT DATE_PART('month' FROM CAST('2024-04-08' AS DATE))\",\n            \"SELECT DATE_PART('month', CAST('2024-04-08' AS DATE))\",\n        ).expressions[0].assert_is(exp.Extract)\n        self.validate_identity(\n            \"SELECT DATE_PART(day FROM a)\", \"SELECT DATE_PART(day, a)\"\n        ).expressions[0].assert_is(exp.Extract)\n\n        for data_type in (\n            \"TIMESTAMP\",\n            \"TIMESTAMPLTZ\",\n            \"TIMESTAMPNTZ\",\n        ):\n            self.validate_identity(f\"CAST(a AS {data_type})\")\n\n        self.validate_identity(\"CAST(a AS TIMESTAMP_NTZ)\", \"CAST(a AS TIMESTAMPNTZ)\")\n        self.validate_identity(\"CAST(a AS TIMESTAMP_LTZ)\", \"CAST(a AS TIMESTAMPLTZ)\")\n\n        self.validate_all(\n            \"SELECT a::TIMESTAMP_LTZ(9)\",\n            write={\n                \"snowflake\": \"SELECT CAST(a AS TIMESTAMPLTZ(9))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a::TIMESTAMPLTZ\",\n            write={\n                \"snowflake\": \"SELECT CAST(a AS TIMESTAMPLTZ)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a::TIMESTAMP WITH LOCAL TIME ZONE\",\n            write={\n                \"snowflake\": \"SELECT CAST(a AS TIMESTAMPLTZ)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXTRACT('month', a)\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART('month', a)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART('month', a)\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART('month', a)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART(month, a::DATETIME)\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(month, CAST(a AS DATETIME))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART(epoch_second, foo) as ddate from table_name\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(EPOCH_SECOND, foo) AS ddate FROM table_name\",\n                \"duckdb\": \"SELECT CAST(EPOCH(foo) AS BIGINT) AS ddate FROM table_name\",\n                \"presto\": \"SELECT TO_UNIXTIME(CAST(foo AS TIMESTAMP)) AS ddate FROM table_name\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_PART(epoch_milliseconds, foo) as ddate from table_name\",\n            write={\n                \"snowflake\": \"SELECT DATE_PART(EPOCH_MILLISECOND, foo) AS ddate FROM table_name\",\n                \"duckdb\": \"SELECT EPOCH_MS(foo) AS ddate FROM table_name\",\n                \"presto\": \"SELECT TO_UNIXTIME(CAST(foo AS TIMESTAMP)) * 1000 AS ddate FROM table_name\",\n            },\n        )\n        self.validate_all(\n            \"DATEADD(DAY, 5, CAST('2008-12-25' AS DATE))\",\n            read={\n                \"snowflake\": \"TIMESTAMPADD(DAY, 5, CAST('2008-12-25' AS DATE))\",\n            },\n            write={\n                \"bigquery\": \"DATE_ADD(CAST('2008-12-25' AS DATE), INTERVAL 5 DAY)\",\n                \"snowflake\": \"DATEADD(DAY, 5, CAST('2008-12-25' AS DATE))\",\n            },\n        )\n        self.validate_identity(\n            \"DATEDIFF(DAY, CAST('2007-12-25' AS DATE), CAST('2008-12-25' AS DATE))\"\n        )\n        self.validate_identity(\n            \"TIMEDIFF(DAY, CAST('2007-12-25' AS DATE), CAST('2008-12-25' AS DATE))\",\n            \"DATEDIFF(DAY, CAST('2007-12-25' AS DATE), CAST('2008-12-25' AS DATE))\",\n        )\n        self.validate_identity(\n            \"TIMESTAMPDIFF(DAY, CAST('2007-12-25' AS DATE), CAST('2008-12-25' AS DATE))\",\n            \"DATEDIFF(DAY, CAST('2007-12-25' AS DATE), CAST('2008-12-25' AS DATE))\",\n        )\n\n        # Test DATEDIFF with WEEK unit - week boundary crossing\n        self.validate_all(\n            \"DATEDIFF(WEEK, '2024-12-13', '2024-12-17')\",\n            write={\n                \"duckdb\": \"DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-12-13' AS DATE)), DATE_TRUNC('WEEK', CAST('2024-12-17' AS DATE)))\",\n                \"snowflake\": \"DATEDIFF(WEEK, '2024-12-13', '2024-12-17')\",\n            },\n        )\n        self.validate_all(\n            \"DATEDIFF(WEEK, '2024-12-15', '2024-12-16')\",\n            write={\n                \"duckdb\": \"DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST('2024-12-15' AS DATE)), DATE_TRUNC('WEEK', CAST('2024-12-16' AS DATE)))\",\n                \"snowflake\": \"DATEDIFF(WEEK, '2024-12-15', '2024-12-16')\",\n            },\n        )\n\n        # Test DATEDIFF with other date parts - should not use DATE_TRUNC\n        self.validate_all(\n            \"DATEDIFF(YEAR, '2020-01-15', '2023-06-20')\",\n            write={\n                \"duckdb\": \"DATE_DIFF('YEAR', CAST('2020-01-15' AS DATE), CAST('2023-06-20' AS DATE))\",\n                \"snowflake\": \"DATEDIFF(YEAR, '2020-01-15', '2023-06-20')\",\n            },\n        )\n        self.validate_all(\n            \"DATEDIFF(MONTH, '2020-01-15', '2023-06-20')\",\n            write={\n                \"duckdb\": \"DATE_DIFF('MONTH', CAST('2020-01-15' AS DATE), CAST('2023-06-20' AS DATE))\",\n                \"snowflake\": \"DATEDIFF(MONTH, '2020-01-15', '2023-06-20')\",\n            },\n        )\n        self.validate_all(\n            \"DATEDIFF(QUARTER, '2020-01-15', '2023-06-20')\",\n            write={\n                \"duckdb\": \"DATE_DIFF('QUARTER', CAST('2020-01-15' AS DATE), CAST('2023-06-20' AS DATE))\",\n                \"snowflake\": \"DATEDIFF(QUARTER, '2020-01-15', '2023-06-20')\",\n            },\n        )\n\n        # Test DATEDIFF with NANOSECOND - DuckDB uses EPOCH_NS since DATE_DIFF doesn't support NANOSECOND\n        self.validate_all(\n            \"DATEDIFF(NANOSECOND, '2023-01-01 10:00:00.000000000', '2023-01-01 10:00:00.123456789')\",\n            write={\n                \"duckdb\": \"EPOCH_NS(CAST('2023-01-01 10:00:00.123456789' AS TIMESTAMP_NS)) - EPOCH_NS(CAST('2023-01-01 10:00:00.000000000' AS TIMESTAMP_NS))\",\n                \"snowflake\": \"DATEDIFF(NANOSECOND, '2023-01-01 10:00:00.000000000', '2023-01-01 10:00:00.123456789')\",\n            },\n        )\n\n        # Test DATEDIFF with NANOSECOND on columns\n        self.validate_all(\n            \"DATEDIFF(NANOSECOND, start_time, end_time)\",\n            write={\n                \"duckdb\": \"EPOCH_NS(CAST(end_time AS TIMESTAMP_NS)) - EPOCH_NS(CAST(start_time AS TIMESTAMP_NS))\",\n                \"snowflake\": \"DATEDIFF(NANOSECOND, start_time, end_time)\",\n            },\n        )\n\n        # Test DATEADD with NANOSECOND - DuckDB uses MAKE_TIMESTAMP_NS since INTERVAL doesn't support NANOSECOND\n        self.validate_all(\n            \"DATEADD(NANOSECOND, 123456789, '2023-01-01 10:00:00.000000000')\",\n            write={\n                \"duckdb\": \"MAKE_TIMESTAMP_NS(EPOCH_NS(CAST('2023-01-01 10:00:00.000000000' AS TIMESTAMP_NS)) + 123456789)\",\n                \"snowflake\": \"DATEADD(NANOSECOND, 123456789, '2023-01-01 10:00:00.000000000')\",\n            },\n        )\n\n        # Test DATEADD with NANOSECOND on columns\n        self.validate_all(\n            \"DATEADD(NANOSECOND, nano_offset, timestamp_col)\",\n            write={\n                \"duckdb\": \"MAKE_TIMESTAMP_NS(EPOCH_NS(CAST(timestamp_col AS TIMESTAMP_NS)) + nano_offset)\",\n                \"snowflake\": \"DATEADD(NANOSECOND, nano_offset, timestamp_col)\",\n            },\n        )\n\n        # Test negative NANOSECOND values (subtraction)\n        self.validate_all(\n            \"DATEADD(NANOSECOND, -123456789, '2023-01-01 10:00:00.500000000')\",\n            write={\n                \"duckdb\": \"MAKE_TIMESTAMP_NS(EPOCH_NS(CAST('2023-01-01 10:00:00.500000000' AS TIMESTAMP_NS)) + -123456789)\",\n                \"snowflake\": \"DATEADD(NANOSECOND, -123456789, '2023-01-01 10:00:00.500000000')\",\n            },\n        )\n\n        # Test TIMESTAMPDIFF with NANOSECOND - Snowflake parser converts to DATEDIFF\n        self.validate_all(\n            \"TIMESTAMPDIFF(NANOSECOND, '2023-01-01 10:00:00.000000000', '2023-01-01 10:00:00.123456789')\",\n            write={\n                \"duckdb\": \"EPOCH_NS(CAST('2023-01-01 10:00:00.123456789' AS TIMESTAMP_NS)) - EPOCH_NS(CAST('2023-01-01 10:00:00.000000000' AS TIMESTAMP_NS))\",\n                \"snowflake\": \"DATEDIFF(NANOSECOND, '2023-01-01 10:00:00.000000000', '2023-01-01 10:00:00.123456789')\",\n            },\n        )\n\n        # Test TIMESTAMPADD with NANOSECOND - Snowflake parser converts to DATEADD\n        self.validate_all(\n            \"TIMESTAMPADD(NANOSECOND, 123456789, '2023-01-01 10:00:00.000000000')\",\n            write={\n                \"duckdb\": \"MAKE_TIMESTAMP_NS(EPOCH_NS(CAST('2023-01-01 10:00:00.000000000' AS TIMESTAMP_NS)) + 123456789)\",\n                \"snowflake\": \"DATEADD(NANOSECOND, 123456789, '2023-01-01 10:00:00.000000000')\",\n            },\n        )\n\n        self.validate_identity(\"DATEADD(y, 5, x)\", \"DATEADD(YEAR, 5, x)\")\n        self.validate_identity(\"DATEADD(y, 5, x)\", \"DATEADD(YEAR, 5, x)\")\n        self.validate_identity(\"DATE_PART(yyy, x)\", \"DATE_PART(YEAR, x)\")\n        self.validate_identity(\"DATE_TRUNC(yr, x)\", \"DATE_TRUNC('YEAR', x)\")\n        self.validate_all(\n            \"DATE_TRUNC('YEAR', CAST('2024-06-15' AS DATE))\",\n            write={\n                \"snowflake\": \"DATE_TRUNC('YEAR', CAST('2024-06-15' AS DATE))\",\n                \"duckdb\": \"DATE_TRUNC('YEAR', CAST('2024-06-15' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('HOUR', CAST('2026-01-01 00:00:00' AS TIMESTAMP))\",\n            write={\n                \"snowflake\": \"DATE_TRUNC('HOUR', CAST('2026-01-01 00:00:00' AS TIMESTAMP))\",\n                \"duckdb\": \"DATE_TRUNC('HOUR', CAST('2026-01-01 00:00:00' AS TIMESTAMP))\",\n            },\n        )\n        # Snowflake's DATE_TRUNC return type matches type of the expresison\n        # DuckDB's DATE_TRUNC return type matches type of granularity part.\n        # In Snowflake --> DuckDB, DATE_TRUNC(date_part, timestamp) should be cast to timestamp to preserve Snowflake behavior.\n        self.validate_all(\n            \"DATE_TRUNC(YEAR, TIMESTAMP '2026-01-01 00:00:00')\",\n            write={\n                \"snowflake\": \"DATE_TRUNC('YEAR', CAST('2026-01-01 00:00:00' AS TIMESTAMP))\",\n                \"duckdb\": \"CAST(DATE_TRUNC('YEAR', CAST('2026-01-01 00:00:00' AS TIMESTAMP)) AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC(MONTH, CAST('2024-06-15 14:23:45' AS TIMESTAMPTZ))\",\n            write={\n                \"snowflake\": \"DATE_TRUNC('MONTH', CAST('2024-06-15 14:23:45' AS TIMESTAMPTZ))\",\n                \"duckdb\": \"CAST(DATE_TRUNC('MONTH', CAST('2024-06-15 14:23:45' AS TIMESTAMPTZ)) AS TIMESTAMPTZ)\",\n            },\n        )\n        self.validate_all(\n            \"DATE_TRUNC('WEEK', CURRENT_DATE)\",\n            write={\n                \"snowflake\": \"DATE_TRUNC('WEEK', CURRENT_DATE)\",\n                \"duckdb\": \"DATE_TRUNC('WEEK', CURRENT_DATE)\",\n            },\n        )\n\n        # In Snowflake --> DuckDB, DATE_TRUNC(time_part, date) should be cast to date to preserve Snowflake behavior.\n        self.validate_all(\n            \"DATE_TRUNC('HOUR', CAST('2026-01-01' AS DATE))\",\n            write={\n                \"snowflake\": \"DATE_TRUNC('HOUR', CAST('2026-01-01' AS DATE))\",\n                \"duckdb\": \"CAST(DATE_TRUNC('HOUR', CAST('2026-01-01' AS DATE)) AS DATE)\",\n            },\n        )\n\n        # DuckDB does not support DATE_TRUNC(time_part, time), so we add a dummy date to generate DATE_TRUNC(time_part, date) --> DATE in DuckDB\n        # Then it is casted to a time (HH:MM:SS) to match Snowflake.\n        self.validate_all(\n            \"DATE_TRUNC('HOUR', CAST('14:23:45.123456' AS TIME))\",\n            write={\n                \"snowflake\": \"DATE_TRUNC('HOUR', CAST('14:23:45.123456' AS TIME))\",\n                \"duckdb\": \"CAST(DATE_TRUNC('HOUR', CAST('1970-01-01' AS DATE) + CAST('14:23:45.123456' AS TIME)) AS TIME)\",\n            },\n        )\n\n        self.validate_all(\n            \"DATE(x)\",\n            write={\n                \"duckdb\": \"CAST(x AS DATE)\",\n                \"snowflake\": \"TO_DATE(x)\",\n            },\n        )\n\n        self.validate_all(\n            \"DATE('01-01-2000', 'MM-DD-YYYY')\",\n            write={\n                \"snowflake\": \"TO_DATE('01-01-2000', 'mm-DD-yyyy')\",\n                \"duckdb\": \"CAST(STRPTIME('01-01-2000', '%m-%d-%Y') AS DATE)\",\n            },\n        )\n\n        self.validate_identity(\"SELECT TO_TIME(x) FROM t\")\n        self.validate_all(\n            \"SELECT TO_TIME('12:05:00')\",\n            write={\n                \"bigquery\": \"SELECT CAST('12:05:00' AS TIME)\",\n                \"snowflake\": \"SELECT CAST('12:05:00' AS TIME)\",\n                \"duckdb\": \"SELECT CAST('12:05:00' AS TIME)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIME('2024-01-15 14:30:00'::TIMESTAMP)\",\n            write={\n                \"bigquery\": \"SELECT TIME(CAST('2024-01-15 14:30:00' AS DATETIME))\",\n                \"snowflake\": \"SELECT TO_TIME(CAST('2024-01-15 14:30:00' AS TIMESTAMP))\",\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-15 14:30:00' AS TIMESTAMP) AS TIME)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIME(CONVERT_TIMEZONE('UTC', 'US/Pacific', '2024-08-06 09:10:00.000')) AS pst_time\",\n            write={\n                \"snowflake\": \"SELECT TO_TIME(CONVERT_TIMEZONE('UTC', 'US/Pacific', '2024-08-06 09:10:00.000')) AS pst_time\",\n                \"duckdb\": \"SELECT CAST(CAST('2024-08-06 09:10:00.000' AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE 'US/Pacific' AS TIME) AS pst_time\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIME('11.15.00', 'hh24.mi.ss')\",\n            write={\n                \"snowflake\": \"SELECT TO_TIME('11.15.00', 'hh24.mi.ss')\",\n                \"duckdb\": \"SELECT CAST(STRPTIME('11.15.00', '%H.%M.%S') AS TIME)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIME('093000', 'HH24MISS')\",\n            write={\n                \"duckdb\": \"SELECT CAST(STRPTIME('093000', '%H%M%S') AS TIME)\",\n                \"snowflake\": \"SELECT TO_TIME('093000', 'hh24miss')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRY_TO_TIME('093000', 'HH24MISS')\",\n            write={\n                \"snowflake\": \"SELECT TRY_TO_TIME('093000', 'hh24miss')\",\n                \"duckdb\": \"SELECT TRY_CAST(TRY_STRPTIME('093000', '%H%M%S') AS TIME)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRY_TO_TIME('11.15.00')\",\n            write={\n                \"snowflake\": \"SELECT TRY_CAST('11.15.00' AS TIME)\",\n                \"duckdb\": \"SELECT TRY_CAST('11.15.00' AS TIME)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRY_TO_TIME('11.15.00', 'hh24.mi.ss')\",\n            write={\n                \"snowflake\": \"SELECT TRY_TO_TIME('11.15.00', 'hh24.mi.ss')\",\n                \"duckdb\": \"SELECT TRY_CAST(TRY_STRPTIME('11.15.00', '%H.%M.%S') AS TIME)\",\n            },\n        )\n\n    def test_to_date(self):\n        self.validate_identity(\"TO_DATE('12345')\").assert_is(exp.Anonymous)\n\n        self.validate_identity(\"TO_DATE(x)\").assert_is(exp.TsOrDsToDate)\n\n        self.validate_all(\n            \"TO_DATE('01-01-2000', 'MM-DD-YYYY')\",\n            write={\n                \"snowflake\": \"TO_DATE('01-01-2000', 'mm-DD-yyyy')\",\n                \"duckdb\": \"CAST(STRPTIME('01-01-2000', '%m-%d-%Y') AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"TO_DATE(x, 'MM-DD-YYYY')\",\n            write={\n                \"snowflake\": \"TO_DATE(x, 'mm-DD-yyyy')\",\n                \"duckdb\": \"CAST(STRPTIME(x, '%m-%d-%Y') AS DATE)\",\n            },\n        )\n\n        self.validate_identity(\n            \"SELECT TO_DATE('2019-02-28') + INTERVAL '1 day, 1 year'\",\n            \"SELECT CAST('2019-02-28' AS DATE) + INTERVAL '1 day, 1 year'\",\n        )\n\n        self.validate_identity(\"TRY_TO_DATE(x)\").assert_is(exp.TsOrDsToDate)\n\n        self.validate_all(\n            \"TRY_TO_DATE('2024-01-31')\",\n            write={\n                \"snowflake\": \"TRY_CAST('2024-01-31' AS DATE)\",\n                \"duckdb\": \"TRY_CAST('2024-01-31' AS DATE)\",\n            },\n        )\n        self.validate_identity(\"TRY_TO_DATE('2024-01-31', 'AUTO')\")\n\n        self.validate_all(\n            \"TRY_TO_DATE('01-01-2000', 'MM-DD-YYYY')\",\n            write={\n                \"snowflake\": \"TRY_TO_DATE('01-01-2000', 'mm-DD-yyyy')\",\n                \"duckdb\": \"CAST(CAST(TRY_STRPTIME('01-01-2000', '%m-%d-%Y') AS TIMESTAMP) AS DATE)\",\n            },\n        )\n\n        for i in range(1, 10):\n            fractional_format = \"ff\" + str(i)\n            duck_db_format = \"%n\"\n            if i == 3:\n                duck_db_format = \"%g\"\n            elif i == 6:\n                duck_db_format = \"%f\"\n            with self.subTest(f\"Testing snowflake {fractional_format} format\"):\n                self.validate_all(\n                    f\"TRY_TO_DATE('2013-04-28T20:57:01', 'yyyy-mm-DDThh24:mi:ss.{fractional_format}')\",\n                    write={\n                        \"snowflake\": f\"TRY_TO_DATE('2013-04-28T20:57:01', 'yyyy-mm-DDThh24:mi:ss.{fractional_format}')\",\n                        \"duckdb\": f\"CAST(CAST(TRY_STRPTIME('2013-04-28T20:57:01', '%Y-%m-%dT%H:%M:%S.{duck_db_format}') AS TIMESTAMP) AS DATE)\",\n                    },\n                )\n\n        self.validate_all(\n            \"TRY_TO_DATE('2013-04-28T20:57:01.888', 'yyyy-mm-DDThh24:mi:ss.ff')\",\n            write={\n                \"snowflake\": \"TRY_TO_DATE('2013-04-28T20:57:01.888', 'yyyy-mm-DDThh24:mi:ss.ff9')\",\n                \"duckdb\": \"CAST(CAST(TRY_STRPTIME('2013-04-28T20:57:01.888', '%Y-%m-%dT%H:%M:%S.%n') AS TIMESTAMP) AS DATE)\",\n            },\n        )\n\n        tz_to_format = {\n            \"tzh:tzm\": \"+07:00\",\n            \"tzhtzm\": \"+0700\",\n            \"tzh\": \"+07\",\n        }\n\n        for tz_format, tz in tz_to_format.items():\n            with self.subTest(f\"Testing snowflake {tz_format} timezone format\"):\n                self.validate_all(\n                    f\"TRY_TO_DATE('2013-04-28 20:57 {tz}', 'YYYY-MM-DD HH24:MI {tz_format}')\",\n                    write={\n                        \"snowflake\": f\"TRY_TO_DATE('2013-04-28 20:57 {tz}', 'yyyy-mm-DD hh24:mi {tz_format}')\",\n                        \"duckdb\": f\"CAST(CAST(TRY_STRPTIME('2013-04-28 20:57 {tz}', '%Y-%m-%d %H:%M %z') AS TIMESTAMP) AS DATE)\",\n                    },\n                )\n\n        self.validate_all(\n            \"\"\"TRY_TO_DATE('2013-04-28T20:57', 'YYYY-MM-DD\"T\"HH24:MI:SS')\"\"\",\n            write={\n                \"snowflake\": \"TRY_TO_DATE('2013-04-28T20:57', 'yyyy-mm-DDThh24:mi:ss')\",\n                \"duckdb\": \"CAST(CAST(TRY_STRPTIME('2013-04-28T20:57', '%Y-%m-%dT%H:%M:%S') AS TIMESTAMP) AS DATE)\",\n            },\n        )\n\n    def test_trunc(self):\n        # Numeric truncation identity\n        self.validate_identity(\"TRUNC(3.14159, 2)\").assert_is(exp.Trunc)\n        self.validate_identity(\"TRUNC(price, 0)\").assert_is(exp.Trunc)\n        self.validate_identity(\"TRUNC(3.14159)\").assert_is(exp.Trunc)\n\n        # Single-arg TRUNC is always numeric in Snowflake (date trunc requires unit)\n        self.validate_identity(\"TRUNC(col)\").assert_is(exp.Trunc)\n\n        # Date truncation with typed column and unit\n        # (parse_one because DateTrunc generates as DATE_TRUNC, not TRUNC)\n        self.parse_one(\"TRUNC(CAST(x AS DATE), 'MONTH')\").assert_is(exp.DateTrunc)\n        self.parse_one(\"TRUNC(CAST(x AS TIMESTAMP), 'MONTH')\").assert_is(exp.DateTrunc)\n        self.parse_one(\"TRUNC(CAST(x AS DATETIME), 'MONTH')\").assert_is(exp.DateTrunc)\n\n        # Fallback to Anonymous when type cannot be determined\n        self.validate_identity(\"TRUNC(foo, bar)\").assert_is(exp.Anonymous)\n\n        # Cross-dialect numeric truncation transpilation\n        self.validate_all(\n            \"TRUNC(3.14159, 2)\",\n            write={\n                \"snowflake\": \"TRUNC(3.14159, 2)\",\n                \"oracle\": \"TRUNC(3.14159, 2)\",\n                \"postgres\": \"TRUNC(3.14159, 2)\",\n                \"mysql\": \"TRUNCATE(3.14159, 2)\",\n                \"tsql\": \"ROUND(3.14159, 2, 1)\",\n                \"bigquery\": \"TRUNC(3.14159, 2)\",\n                \"duckdb\": \"TRUNC(3.14159)\",\n                \"presto\": \"TRUNCATE(3.14159, 2)\",\n                \"clickhouse\": \"trunc(3.14159, 2)\",\n                \"spark\": \"CAST(3.14159 AS BIGINT)\",\n            },\n        )\n\n        # Single-argument numeric TRUNC transpilation\n        self.validate_all(\n            \"TRUNC(3.14159)\",\n            write={\n                \"snowflake\": \"TRUNC(3.14159)\",\n                \"oracle\": \"TRUNC(3.14159)\",\n                \"postgres\": \"TRUNC(3.14159)\",\n                \"mysql\": \"TRUNCATE(3.14159)\",\n                \"tsql\": \"ROUND(3.14159, 0, 1)\",\n            },\n        )\n\n        # Read numeric TRUNC from other dialects\n        self.validate_all(\n            \"TRUNC(price, 2)\",\n            read={\n                \"mysql\": \"TRUNCATE(price, 2)\",\n                \"oracle\": \"TRUNC(price, 2)\",\n                \"postgres\": \"TRUNC(price, 2)\",\n            },\n            write={\n                \"snowflake\": \"TRUNC(price, 2)\",\n            },\n        )\n\n    def test_semi_structured_types(self):\n        self.validate_identity(\"SELECT CAST(a AS VARIANT)\")\n        self.validate_identity(\"SELECT CAST(a AS ARRAY)\")\n\n        self.validate_all(\n            \"SELECT a::VARIANT\",\n            write={\n                \"snowflake\": \"SELECT CAST(a AS VARIANT)\",\n                \"tsql\": \"SELECT CAST(a AS SQL_VARIANT)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY_CONSTRUCT(0, 1, 2)\",\n            write={\n                \"snowflake\": \"[0, 1, 2]\",\n                \"bigquery\": \"[0, 1, 2]\",\n                \"duckdb\": \"[0, 1, 2]\",\n                \"presto\": \"ARRAY[0, 1, 2]\",\n                \"spark\": \"ARRAY(0, 1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAYS_ZIP([1, 2], [3, 4], [4, 5])\",\n            write={\n                \"snowflake\": \"ARRAYS_ZIP([1, 2], [3, 4], [4, 5])\",\n                \"duckdb\": \"CASE WHEN [1, 2] IS NULL OR [3, 4] IS NULL OR [4, 5] IS NULL THEN NULL WHEN LENGTH([1, 2]) = 0 AND LENGTH([3, 4]) = 0 AND LENGTH([4, 5]) = 0 THEN [{'$1': NULL, '$2': NULL, '$3': NULL}] ELSE LIST_TRANSFORM(RANGE(0, CASE WHEN LENGTH([1, 2]) IS NULL OR LENGTH([3, 4]) IS NULL OR LENGTH([4, 5]) IS NULL THEN NULL ELSE GREATEST(LENGTH([1, 2]), LENGTH([3, 4]), LENGTH([4, 5])) END), __i -> {'$1': COALESCE([1, 2], [])[__i + 1], '$2': COALESCE([3, 4], [])[__i + 1], '$3': COALESCE([4, 5], [])[__i + 1]}) END\",\n            },\n        )\n        self.validate_all(\n            \"ARRAYS_ZIP([1, 2, 3])\",\n            write={\n                \"snowflake\": \"ARRAYS_ZIP([1, 2, 3])\",\n                \"duckdb\": \"CASE WHEN [1, 2, 3] IS NULL THEN NULL WHEN LENGTH([1, 2, 3]) = 0 THEN [{'$1': NULL}] ELSE LIST_TRANSFORM(RANGE(0, LENGTH([1, 2, 3])), __i -> {'$1': COALESCE([1, 2, 3], [])[__i + 1]}) END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT a::OBJECT\",\n            write={\n                \"snowflake\": \"SELECT CAST(a AS OBJECT)\",\n            },\n        )\n\n    def test_next_day(self):\n        self.validate_all(\n            \"SELECT NEXT_DAY(CAST('2024-01-01' AS DATE), 'Monday')\",\n            write={\n                \"snowflake\": \"SELECT NEXT_DAY(CAST('2024-01-01' AS DATE), 'Monday')\",\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-01' AS DATE) + INTERVAL ((((1 - ISODOW(CAST('2024-01-01' AS DATE))) + 6) % 7) + 1) DAY AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT NEXT_DAY(CAST('2024-01-05' AS DATE), 'Friday')\",\n            write={\n                \"snowflake\": \"SELECT NEXT_DAY(CAST('2024-01-05' AS DATE), 'Friday')\",\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-05' AS DATE) + INTERVAL ((((5 - ISODOW(CAST('2024-01-05' AS DATE))) + 6) % 7) + 1) DAY AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT NEXT_DAY(CAST('2024-01-05' AS DATE), 'WE')\",\n            write={\n                \"snowflake\": \"SELECT NEXT_DAY(CAST('2024-01-05' AS DATE), 'WE')\",\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-05' AS DATE) + INTERVAL ((((3 - ISODOW(CAST('2024-01-05' AS DATE))) + 6) % 7) + 1) DAY AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT NEXT_DAY(CAST('2024-01-01 10:30:45' AS TIMESTAMP), 'Friday')\",\n            write={\n                \"snowflake\": \"SELECT NEXT_DAY(CAST('2024-01-01 10:30:45' AS TIMESTAMP), 'Friday')\",\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-01 10:30:45' AS TIMESTAMP) + INTERVAL ((((5 - ISODOW(CAST('2024-01-01 10:30:45' AS TIMESTAMP))) + 6) % 7) + 1) DAY AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT NEXT_DAY(CAST('2024-01-01' AS DATE), day_column)\",\n            write={\n                \"snowflake\": \"SELECT NEXT_DAY(CAST('2024-01-01' AS DATE), day_column)\",\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-01' AS DATE) + INTERVAL ((((CASE WHEN STARTS_WITH(UPPER(day_column), 'MO') THEN 1 WHEN STARTS_WITH(UPPER(day_column), 'TU') THEN 2 WHEN STARTS_WITH(UPPER(day_column), 'WE') THEN 3 WHEN STARTS_WITH(UPPER(day_column), 'TH') THEN 4 WHEN STARTS_WITH(UPPER(day_column), 'FR') THEN 5 WHEN STARTS_WITH(UPPER(day_column), 'SA') THEN 6 WHEN STARTS_WITH(UPPER(day_column), 'SU') THEN 7 END - ISODOW(CAST('2024-01-01' AS DATE))) + 6) % 7) + 1) DAY AS DATE)\",\n            },\n        )\n\n    def test_previous_day(self):\n        self.validate_all(\n            \"SELECT PREVIOUS_DAY(DATE '2024-01-15', 'Monday')\",\n            write={\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-15' AS DATE) - INTERVAL ((((ISODOW(CAST('2024-01-15' AS DATE)) - 1) + 6) % 7) + 1) DAY AS DATE)\",\n                \"snowflake\": \"SELECT PREVIOUS_DAY(CAST('2024-01-15' AS DATE), 'Monday')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT PREVIOUS_DAY(DATE '2024-01-15', 'Fr')\",\n            write={\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-15' AS DATE) - INTERVAL ((((ISODOW(CAST('2024-01-15' AS DATE)) - 5) + 6) % 7) + 1) DAY AS DATE)\",\n                \"snowflake\": \"SELECT PREVIOUS_DAY(CAST('2024-01-15' AS DATE), 'Fr')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT PREVIOUS_DAY(TIMESTAMP '2024-01-15 10:30:45', 'Monday')\",\n            write={\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-15 10:30:45' AS TIMESTAMP) - INTERVAL ((((ISODOW(CAST('2024-01-15 10:30:45' AS TIMESTAMP)) - 1) + 6) % 7) + 1) DAY AS DATE)\",\n                \"snowflake\": \"SELECT PREVIOUS_DAY(CAST('2024-01-15 10:30:45' AS TIMESTAMP), 'Monday')\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT PREVIOUS_DAY(DATE '2024-01-15', day_column)\",\n            write={\n                \"duckdb\": \"SELECT CAST(CAST('2024-01-15' AS DATE) - INTERVAL ((((ISODOW(CAST('2024-01-15' AS DATE)) - CASE WHEN STARTS_WITH(UPPER(day_column), 'MO') THEN 1 WHEN STARTS_WITH(UPPER(day_column), 'TU') THEN 2 WHEN STARTS_WITH(UPPER(day_column), 'WE') THEN 3 WHEN STARTS_WITH(UPPER(day_column), 'TH') THEN 4 WHEN STARTS_WITH(UPPER(day_column), 'FR') THEN 5 WHEN STARTS_WITH(UPPER(day_column), 'SA') THEN 6 WHEN STARTS_WITH(UPPER(day_column), 'SU') THEN 7 END) + 6) % 7) + 1) DAY AS DATE)\",\n                \"snowflake\": \"SELECT PREVIOUS_DAY(CAST('2024-01-15' AS DATE), day_column)\",\n            },\n        )\n\n    def test_historical_data(self):\n        self.validate_identity(\"SELECT * FROM my_table AT (STATEMENT => $query_id_var)\")\n        self.validate_identity(\"SELECT * FROM my_table AT (OFFSET => -60 * 5)\")\n        self.validate_identity(\"SELECT * FROM my_table BEFORE (STATEMENT => $query_id_var)\")\n        self.validate_identity(\"SELECT * FROM my_table BEFORE (OFFSET => -60 * 5)\")\n        self.validate_identity(\"CREATE SCHEMA restored_schema CLONE my_schema AT (OFFSET => -3600)\")\n        self.validate_identity(\n            \"CREATE TABLE restored_table CLONE my_table AT (TIMESTAMP => CAST('Sat, 09 May 2015 01:01:00 +0300' AS TIMESTAMPTZ))\",\n        )\n        self.validate_identity(\n            \"CREATE DATABASE restored_db CLONE my_db BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726')\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM my_table AT (TIMESTAMP => TO_TIMESTAMP(1432669154242, 3))\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM my_table AT (OFFSET => -60 * 5) AS T WHERE T.flag = 'valid'\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM my_table AT (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726')\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM my_table BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726')\"\n        )\n        self.validate_identity(\n            \"SELECT * FROM my_table AT (TIMESTAMP => 'Fri, 01 May 2015 16:20:00 -0700'::timestamp)\",\n            \"SELECT * FROM my_table AT (TIMESTAMP => CAST('Fri, 01 May 2015 16:20:00 -0700' AS TIMESTAMP))\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM my_table AT(TIMESTAMP => 'Fri, 01 May 2015 16:20:00 -0700'::timestamp_tz)\",\n            \"SELECT * FROM my_table AT (TIMESTAMP => CAST('Fri, 01 May 2015 16:20:00 -0700' AS TIMESTAMPTZ))\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM my_table BEFORE (TIMESTAMP => 'Fri, 01 May 2015 16:20:00 -0700'::timestamp_tz);\",\n            \"SELECT * FROM my_table BEFORE (TIMESTAMP => CAST('Fri, 01 May 2015 16:20:00 -0700' AS TIMESTAMPTZ))\",\n        )\n        self.validate_identity(\n            \"\"\"\n            SELECT oldt.* , newt.*\n            FROM my_table BEFORE(STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS oldt\n            FULL OUTER JOIN my_table AT(STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS newt\n            ON oldt.id = newt.id\n            WHERE oldt.id IS NULL OR newt.id IS NULL;\n            \"\"\",\n            \"SELECT oldt.*, newt.* FROM my_table BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS oldt FULL OUTER JOIN my_table AT (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS newt ON oldt.id = newt.id WHERE oldt.id IS NULL OR newt.id IS NULL\",\n        )\n\n        # Make sure that the historical data keywords can still be used as aliases\n        for historical_data_prefix in (\"AT\", \"BEFORE\", \"END\", \"CHANGES\"):\n            for schema_suffix in (\"\", \"(col)\"):\n                with self.subTest(\n                    f\"Testing historical data prefix alias: {historical_data_prefix}{schema_suffix}\"\n                ):\n                    self.validate_identity(\n                        f\"SELECT * FROM foo {historical_data_prefix}{schema_suffix}\",\n                        f\"SELECT * FROM foo AS {historical_data_prefix}{schema_suffix}\",\n                    )\n\n    def test_ddl(self):\n        for constraint_prefix in (\"WITH \", \"\"):\n            with self.subTest(f\"Constraint prefix: {constraint_prefix}\"):\n                self.validate_identity(\n                    f\"CREATE TABLE t (id INT {constraint_prefix}MASKING POLICY p.q.r)\",\n                    \"CREATE TABLE t (id INT MASKING POLICY p.q.r)\",\n                )\n                self.validate_identity(\n                    f\"CREATE TABLE t (id INT {constraint_prefix}MASKING POLICY p USING (c1, c2, c3))\",\n                    \"CREATE TABLE t (id INT MASKING POLICY p USING (c1, c2, c3))\",\n                )\n                self.validate_identity(\n                    f\"CREATE TABLE t (id INT {constraint_prefix}PROJECTION POLICY p.q.r)\",\n                    \"CREATE TABLE t (id INT PROJECTION POLICY p.q.r)\",\n                )\n                self.validate_identity(\n                    f\"CREATE TABLE t (id INT {constraint_prefix}TAG (key1='value_1', key2='value_2'))\",\n                    \"CREATE TABLE t (id INT TAG (key1='value_1', key2='value_2'))\",\n                )\n\n        self.validate_identity(\"CREATE OR REPLACE TABLE foo COPY GRANTS USING TEMPLATE (SELECT 1)\")\n        self.validate_identity(\"USE SECONDARY ROLES ALL\")\n        self.validate_identity(\"USE SECONDARY ROLES NONE\")\n        self.validate_identity(\"USE SECONDARY ROLES a, b, c\")\n        self.validate_identity(\"CREATE SECURE VIEW table1 AS (SELECT a FROM table2)\")\n        self.validate_identity(\"CREATE OR REPLACE VIEW foo (uid) COPY GRANTS AS (SELECT 1)\")\n        self.validate_identity(\"CREATE TABLE geospatial_table (id INT, g GEOGRAPHY)\")\n        self.validate_identity(\"CREATE MATERIALIZED VIEW a COMMENT='...' AS SELECT 1 FROM x\")\n        self.validate_identity(\"CREATE DATABASE mytestdb_clone CLONE mytestdb\")\n        self.validate_identity(\"CREATE SCHEMA mytestschema_clone CLONE testschema\")\n        self.validate_identity(\"CREATE TABLE IDENTIFIER('foo') (COLUMN1 VARCHAR, COLUMN2 VARCHAR)\")\n        self.validate_identity(\"CREATE TABLE IDENTIFIER($foo) (col1 VARCHAR, col2 VARCHAR)\")\n        self.validate_identity(\"CREATE TAG cost_center ALLOWED_VALUES 'a', 'b'\")\n        self.validate_identity(\"CREATE WAREHOUSE x\").this.assert_is(exp.Identifier)\n        self.validate_identity(\"CREATE STREAMLIT x\").this.assert_is(exp.Identifier)\n        self.validate_identity(\n            \"CREATE TEMPORARY STAGE stage1 FILE_FORMAT=(TYPE=PARQUET)\"\n        ).this.assert_is(exp.Table)\n        self.validate_identity(\n            \"CREATE STAGE stage1 FILE_FORMAT='format1'\",\n            \"CREATE STAGE stage1 FILE_FORMAT=(FORMAT_NAME='format1')\",\n        )\n        self.validate_identity(\"CREATE STAGE stage1 FILE_FORMAT=(FORMAT_NAME=stage1.format1)\")\n        self.validate_identity(\"CREATE STAGE stage1 FILE_FORMAT=(FORMAT_NAME='stage1.format1')\")\n        self.validate_identity(\n            \"CREATE STAGE stage1 FILE_FORMAT=schema1.format1\",\n            \"CREATE STAGE stage1 FILE_FORMAT=(FORMAT_NAME=schema1.format1)\",\n        )\n        with self.assertRaises(ParseError):\n            self.parse_one(\"CREATE STAGE stage1 FILE_FORMAT=123\", dialect=\"snowflake\")\n        self.validate_identity(\n            \"CREATE STAGE s1 URL='s3://bucket-123' FILE_FORMAT=(TYPE='JSON') CREDENTIALS=(aws_key_id='test' aws_secret_key='test')\"\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE TAG IF NOT EXISTS cost_center COMMENT='cost_center tag'\"\n        ).this.assert_is(exp.Identifier)\n        self.validate_identity(\n            \"CREATE TEMPORARY FILE FORMAT fileformat1 TYPE=PARQUET COMPRESSION=auto\"\n        ).this.assert_is(exp.Table)\n        self.validate_identity(\n            \"CREATE DYNAMIC TABLE product (pre_tax_profit, taxes, after_tax_profit) TARGET_LAG='20 minutes' WAREHOUSE=mywh AS SELECT revenue - cost, (revenue - cost) * tax_rate, (revenue - cost) * (1.0 - tax_rate) FROM staging_table\"\n        )\n        self.validate_identity(\n            \"ALTER TABLE db_name.schmaName.tblName ADD COLUMN_1 VARCHAR NOT NULL TAG (key1='value_1')\"\n        )\n        self.validate_identity(\n            \"DROP FUNCTION my_udf (OBJECT(city VARCHAR, zipcode DECIMAL(38, 0), val ARRAY(BOOLEAN)))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE orders_clone_restore CLONE orders AT (TIMESTAMP => TO_TIMESTAMP_TZ('04/05/2013 01:02:03', 'mm/dd/yyyy hh24:mi:ss'))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE orders_clone_restore CLONE orders BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726')\"\n        )\n        self.validate_identity(\n            \"CREATE SCHEMA mytestschema_clone_restore CLONE testschema BEFORE (TIMESTAMP => TO_TIMESTAMP(40 * 365 * 86400))\"\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE TABLE EXAMPLE_DB.DEMO.USERS (ID DECIMAL(38, 0) NOT NULL, PRIMARY KEY (ID), FOREIGN KEY (CITY_CODE) REFERENCES EXAMPLE_DB.DEMO.CITIES (CITY_CODE))\"\n        )\n        self.validate_identity(\n            \"CREATE ICEBERG TABLE my_iceberg_table (amount ARRAY(INT)) CATALOG='SNOWFLAKE' EXTERNAL_VOLUME='my_external_volume' BASE_LOCATION='my/relative/path/from/extvol'\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE OR REPLACE FUNCTION ibis_udfs.public.object_values(\"obj\" OBJECT) RETURNS ARRAY LANGUAGE JAVASCRIPT RETURNS NULL ON NULL INPUT AS ' return Object.values(obj) '\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE OR REPLACE FUNCTION ibis_udfs.public.object_values(\"obj\" OBJECT) RETURNS ARRAY LANGUAGE JAVASCRIPT STRICT AS ' return Object.values(obj) '\"\"\"\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE TABLE TEST (SOME_REF DECIMAL(38, 0) NOT NULL FOREIGN KEY REFERENCES SOME_OTHER_TABLE (ID))\"\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE FUNCTION my_udf(location OBJECT(city VARCHAR, zipcode DECIMAL(38, 0), val ARRAY(BOOLEAN))) RETURNS VARCHAR AS $$ SELECT 'foo' $$\",\n            \"CREATE OR REPLACE FUNCTION my_udf(location OBJECT(city VARCHAR, zipcode DECIMAL(38, 0), val ARRAY(BOOLEAN))) RETURNS VARCHAR AS ' SELECT \\\\'foo\\\\' '\",\n        )\n        self.validate_identity(\n            \"CREATE OR REPLACE FUNCTION my_udtf(foo BOOLEAN) RETURNS TABLE(col1 ARRAY(INT)) AS $$ WITH t AS (SELECT CAST([1, 2, 3] AS ARRAY(INT)) AS c) SELECT c FROM t $$\",\n            \"CREATE OR REPLACE FUNCTION my_udtf(foo BOOLEAN) RETURNS TABLE (col1 ARRAY(INT)) AS ' WITH t AS (SELECT CAST([1, 2, 3] AS ARRAY(INT)) AS c) SELECT c FROM t '\",\n        )\n        self.validate_identity(\n            \"CREATE SEQUENCE seq1 WITH START=1, INCREMENT=1 ORDER\",\n            \"CREATE SEQUENCE seq1 START WITH 1 INCREMENT BY 1 ORDER\",\n        )\n        self.validate_identity(\n            \"CREATE SEQUENCE seq1 WITH START=1 INCREMENT=1 ORDER\",\n            \"CREATE SEQUENCE seq1 START WITH 1 INCREMENT BY 1 ORDER\",\n        )\n        self.validate_identity(\n            \"\"\"create external table et2(\n  col1 date as (parse_json(metadata$external_table_partition):COL1::date),\n  col2 varchar as (parse_json(metadata$external_table_partition):COL2::varchar),\n  col3 number as (parse_json(metadata$external_table_partition):COL3::number))\n  partition by (col1,col2,col3)\n  location=@s2/logs/\n  partition_type = user_specified\n  file_format = (type = parquet compression = gzip binary_as_text = false)\"\"\",\n            \"CREATE EXTERNAL TABLE et2 (col1 DATE AS (CAST(GET_PATH(PARSE_JSON(metadata$external_table_partition), 'COL1') AS DATE)), col2 VARCHAR AS (CAST(GET_PATH(PARSE_JSON(metadata$external_table_partition), 'COL2') AS VARCHAR)), col3 DECIMAL(38, 0) AS (CAST(GET_PATH(PARSE_JSON(metadata$external_table_partition), 'COL3') AS DECIMAL(38, 0)))) PARTITION BY (col1, col2, col3) LOCATION=@s2/logs/ partition_type=user_specified FILE_FORMAT=(type=parquet compression=gzip binary_as_text=FALSE)\",\n        )\n\n        self.validate_all(\n            \"CREATE TABLE orders_clone CLONE orders\",\n            read={\n                \"bigquery\": \"CREATE TABLE orders_clone CLONE orders\",\n            },\n            write={\n                \"bigquery\": \"CREATE TABLE orders_clone CLONE orders\",\n                \"snowflake\": \"CREATE TABLE orders_clone CLONE orders\",\n            },\n        )\n        self.validate_all(\n            \"CREATE OR REPLACE TRANSIENT TABLE a (id INT)\",\n            read={\n                \"postgres\": \"CREATE OR REPLACE TRANSIENT TABLE a (id INT)\",\n                \"snowflake\": \"CREATE OR REPLACE TRANSIENT TABLE a (id INT)\",\n            },\n            write={\n                \"postgres\": \"CREATE OR REPLACE TABLE a (id INT)\",\n                \"mysql\": \"CREATE OR REPLACE TABLE a (id INT)\",\n                \"snowflake\": \"CREATE OR REPLACE TRANSIENT TABLE a (id INT)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE a (b INT)\",\n            read={\"teradata\": \"CREATE MULTISET TABLE a (b INT)\"},\n            write={\"snowflake\": \"CREATE TABLE a (b INT)\"},\n        )\n\n        self.validate_identity(\"CREATE TABLE a TAG (key1='value_1', key2='value_2')\")\n        self.validate_all(\n            \"CREATE TABLE a TAG (key1='value_1')\",\n            read={\n                \"snowflake\": \"CREATE TABLE a WITH TAG (key1='value_1')\",\n            },\n        )\n\n        for action in (\"SET\", \"DROP\"):\n            with self.subTest(f\"ALTER COLUMN {action} NOT NULL\"):\n                self.validate_all(\n                    f\"ALTER TABLE a ALTER COLUMN my_column {action} NOT NULL\",\n                    read={\n                        \"snowflake\": f\"ALTER TABLE a MODIFY COLUMN my_column {action} NOT NULL\",\n                    },\n                    write={\n                        \"snowflake\": f\"ALTER TABLE a ALTER COLUMN my_column {action} NOT NULL\",\n                        \"duckdb\": f\"ALTER TABLE a ALTER COLUMN my_column {action} NOT NULL\",\n                        \"postgres\": f\"ALTER TABLE a ALTER COLUMN my_column {action} NOT NULL\",\n                    },\n                )\n\n    def test_user_defined_functions(self):\n        self.validate_all(\n            \"CREATE FUNCTION a(x DATE, y BIGINT) RETURNS ARRAY LANGUAGE JAVASCRIPT AS $$ SELECT 1 $$\",\n            write={\n                \"snowflake\": \"CREATE FUNCTION a(x DATE, y BIGINT) RETURNS ARRAY LANGUAGE JAVASCRIPT AS ' SELECT 1 '\",\n            },\n        )\n        self.validate_all(\n            \"CREATE FUNCTION a() RETURNS TABLE (b INT) AS 'SELECT 1'\",\n            write={\n                \"snowflake\": \"CREATE FUNCTION a() RETURNS TABLE (b INT) AS 'SELECT 1'\",\n                \"bigquery\": \"CREATE TABLE FUNCTION a() RETURNS TABLE <b INT64> AS SELECT 1\",\n            },\n        )\n        self.validate_all(\n            \"CREATE FUNCTION a() RETURNS INT IMMUTABLE AS 'SELECT 1'\",\n            write={\n                \"snowflake\": \"CREATE FUNCTION a() RETURNS INT IMMUTABLE AS 'SELECT 1'\",\n            },\n        )\n\n    def test_stored_procedures(self):\n        self.validate_identity(\"CALL a.b.c(x, y)\", check_command_warning=True)\n        self.validate_identity(\n            \"CREATE PROCEDURE a.b.c(x INT, y VARIANT) RETURNS OBJECT EXECUTE AS CALLER AS 'BEGIN SELECT 1; END;'\"\n        )\n\n    def test_table_function(self):\n        self.validate_identity(\"SELECT * FROM TABLE('MYTABLE')\")\n        self.validate_identity(\"SELECT * FROM TABLE($MYVAR)\")\n        self.validate_identity(\"SELECT * FROM TABLE(?)\")\n        self.validate_identity(\"SELECT * FROM TABLE(:BINDING)\")\n        self.validate_identity(\"SELECT * FROM TABLE($MYVAR) WHERE COL1 = 10\")\n        self.validate_identity(\"SELECT * FROM TABLE('t1') AS f\")\n        self.validate_identity(\"SELECT * FROM (TABLE('t1') CROSS JOIN TABLE('t2'))\")\n        self.validate_identity(\"SELECT * FROM TABLE('t1'), LATERAL (SELECT * FROM t2)\")\n        self.validate_identity(\"SELECT * FROM TABLE('t1') UNION ALL SELECT * FROM TABLE('t2')\")\n        self.validate_identity(\"SELECT * FROM TABLE('t1') TABLESAMPLE BERNOULLI (20.3)\")\n        self.validate_identity(\"\"\"SELECT * FROM TABLE('MYDB.\"MYSCHEMA\".\"MYTABLE\"')\"\"\")\n        self.validate_identity(\n            'SELECT * FROM TABLE($$MYDB. \"MYSCHEMA\".\"MYTABLE\"$$)',\n            \"\"\"SELECT * FROM TABLE('MYDB. \"MYSCHEMA\".\"MYTABLE\"')\"\"\",\n        )\n\n    def test_flatten(self):\n        self.assertEqual(\n            exp.select(exp.Explode(this=exp.column(\"x\")).as_(\"y\", quoted=True)).sql(\n                \"snowflake\", pretty=True\n            ),\n            \"\"\"SELECT\n  IFF(_u.pos = _u_2.pos_2, _u_2.\"y\", NULL) AS \"y\"\nFROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, (\n  GREATEST(ARRAY_SIZE(x)) - 1\n) + 1))) AS _u(seq, key, path, index, pos, this)\nCROSS JOIN TABLE(FLATTEN(INPUT => x)) AS _u_2(seq, key, path, pos_2, \"y\", this)\nWHERE\n  _u.pos = _u_2.pos_2\n  OR (\n    _u.pos > (\n      ARRAY_SIZE(x) - 1\n    ) AND _u_2.pos_2 = (\n      ARRAY_SIZE(x) - 1\n    )\n  )\"\"\",\n        )\n\n        self.validate_all(\n            \"\"\"\n            select\n              dag_report.acct_id,\n              dag_report.report_date,\n              dag_report.report_uuid,\n              dag_report.airflow_name,\n              dag_report.dag_id,\n              f.value::varchar as operator\n            from cs.telescope.dag_report,\n            table(flatten(input=>split(operators, ','))) f\n            \"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT\n  dag_report.acct_id,\n  dag_report.report_date,\n  dag_report.report_uuid,\n  dag_report.airflow_name,\n  dag_report.dag_id,\n  CAST(f.value AS VARCHAR) AS operator\nFROM cs.telescope.dag_report, TABLE(FLATTEN(input => SPLIT(operators, ','))) AS f\"\"\"\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"\n            SELECT\n              uc.user_id,\n              uc.start_ts AS ts,\n              CASE\n                WHEN uc.start_ts::DATE >= '2023-01-01' AND uc.country_code IN ('US') AND uc.user_id NOT IN (\n                  SELECT DISTINCT\n                    _id\n                  FROM\n                    users,\n                    LATERAL FLATTEN(INPUT => PARSE_JSON(flags)) datasource\n                  WHERE datasource.value:name = 'something'\n                )\n                  THEN 'Sample1'\n                  ELSE 'Sample2'\n              END AS entity\n            FROM user_countries AS uc\n            LEFT JOIN (\n              SELECT user_id, MAX(IFF(service_entity IS NULL,1,0)) AS le_null\n              FROM accepted_user_agreements\n              GROUP BY 1\n            ) AS aua\n              ON uc.user_id = aua.user_id\n            \"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT\n  uc.user_id,\n  uc.start_ts AS ts,\n  CASE\n    WHEN CAST(uc.start_ts AS DATE) >= '2023-01-01'\n    AND uc.country_code IN ('US')\n    AND uc.user_id <> ALL (\n      SELECT DISTINCT\n        _id\n      FROM users, LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)\n      WHERE\n        GET_PATH(datasource.value, 'name') = 'something'\n    )\n    THEN 'Sample1'\n    ELSE 'Sample2'\n  END AS entity\nFROM user_countries AS uc\nLEFT JOIN (\n  SELECT\n    user_id,\n    MAX(IFF(service_entity IS NULL, 1, 0)) AS le_null\n  FROM accepted_user_agreements\n  GROUP BY\n    1\n) AS aua\n  ON uc.user_id = aua.user_id\nCROSS JOIN TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, (\n  GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1\n) + 1))) AS _u(seq, key, path, index, pos, this)\nCROSS JOIN TABLE(FLATTEN(INPUT => PARSE_JSON(flags))) AS _u_2(seq, key, path, pos_2, entity, this)\nWHERE\n  _u.pos = _u_2.pos_2\n  OR (\n    _u.pos > (\n      ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1\n    )\n    AND _u_2.pos_2 = (\n      ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1\n    )\n  )\"\"\",\n            },\n            pretty=True,\n        )\n\n        # All examples from https://docs.snowflake.com/en/sql-reference/functions/flatten.html#syntax\n        self.validate_all(\n            \"SELECT * FROM TABLE(FLATTEN(input => parse_json('[1, ,77]'))) f\",\n            write={\n                \"snowflake\": \"SELECT * FROM TABLE(FLATTEN(input => PARSE_JSON('[1, ,77]'))) AS f\"\n            },\n        )\n\n        self.validate_all(\n            \"\"\"SELECT * FROM TABLE(FLATTEN(input => parse_json('{\"a\":1, \"b\":[77,88]}'), outer => true)) f\"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT * FROM TABLE(FLATTEN(input => PARSE_JSON('{\"a\":1, \"b\":[77,88]}'), outer => TRUE)) AS f\"\"\"\n            },\n        )\n\n        self.validate_all(\n            \"\"\"SELECT * FROM TABLE(FLATTEN(input => parse_json('{\"a\":1, \"b\":[77,88]}'), path => 'b')) f\"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT * FROM TABLE(FLATTEN(input => PARSE_JSON('{\"a\":1, \"b\":[77,88]}'), path => 'b')) AS f\"\"\"\n            },\n        )\n\n        self.validate_all(\n            \"\"\"SELECT * FROM TABLE(FLATTEN(input => parse_json('[]'))) f\"\"\",\n            write={\"snowflake\": \"\"\"SELECT * FROM TABLE(FLATTEN(input => PARSE_JSON('[]'))) AS f\"\"\"},\n        )\n\n        self.validate_all(\n            \"\"\"SELECT * FROM TABLE(FLATTEN(input => parse_json('[]'), outer => true)) f\"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT * FROM TABLE(FLATTEN(input => PARSE_JSON('[]'), outer => TRUE)) AS f\"\"\"\n            },\n        )\n\n        self.validate_all(\n            \"\"\"SELECT * FROM TABLE(FLATTEN(input => parse_json('{\"a\":1, \"b\":[77,88], \"c\": {\"d\":\"X\"}}'))) f\"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT * FROM TABLE(FLATTEN(input => PARSE_JSON('{\"a\":1, \"b\":[77,88], \"c\": {\"d\":\"X\"}}'))) AS f\"\"\"\n            },\n        )\n\n        self.validate_all(\n            \"\"\"SELECT * FROM TABLE(FLATTEN(input => parse_json('{\"a\":1, \"b\":[77,88], \"c\": {\"d\":\"X\"}}'), recursive => true)) f\"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT * FROM TABLE(FLATTEN(input => PARSE_JSON('{\"a\":1, \"b\":[77,88], \"c\": {\"d\":\"X\"}}'), recursive => TRUE)) AS f\"\"\"\n            },\n        )\n\n        self.validate_all(\n            \"\"\"SELECT * FROM TABLE(FLATTEN(input => parse_json('{\"a\":1, \"b\":[77,88], \"c\": {\"d\":\"X\"}}'), recursive => true, mode => 'object')) f\"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT * FROM TABLE(FLATTEN(input => PARSE_JSON('{\"a\":1, \"b\":[77,88], \"c\": {\"d\":\"X\"}}'), recursive => TRUE, mode => 'object')) AS f\"\"\"\n            },\n        )\n\n        self.validate_all(\n            \"\"\"\n            SELECT id as \"ID\",\n              f.value AS \"Contact\",\n              f1.value:type AS \"Type\",\n              f1.value:content AS \"Details\"\n            FROM persons p,\n              lateral flatten(input => p.c, path => 'contact') f,\n              lateral flatten(input => f.value:business) f1\n            \"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT\n  id AS \"ID\",\n  f.value AS \"Contact\",\n  GET_PATH(f1.value, 'type') AS \"Type\",\n  GET_PATH(f1.value, 'content') AS \"Details\"\nFROM persons AS p, LATERAL FLATTEN(input => p.c, path => 'contact') AS f(SEQ, KEY, PATH, INDEX, VALUE, THIS), LATERAL FLATTEN(input => GET_PATH(f.value, 'business')) AS f1(SEQ, KEY, PATH, INDEX, VALUE, THIS)\"\"\",\n            },\n            pretty=True,\n        )\n\n        self.validate_all(\n            \"\"\"\n            SELECT id as \"ID\",\n              value AS \"Contact\"\n            FROM persons p,\n              lateral flatten(input => p.c, path => 'contact')\n            \"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT\n  id AS \"ID\",\n  value AS \"Contact\"\nFROM persons AS p, LATERAL FLATTEN(input => p.c, path => 'contact') AS _flattened(SEQ, KEY, PATH, INDEX, VALUE, THIS)\"\"\",\n            },\n            pretty=True,\n        )\n\n    def test_minus(self):\n        self.validate_all(\n            \"SELECT 1 EXCEPT SELECT 1\",\n            read={\n                \"oracle\": \"SELECT 1 MINUS SELECT 1\",\n                \"snowflake\": \"SELECT 1 MINUS SELECT 1\",\n            },\n        )\n\n    def test_values(self):\n        select = exp.select(\"*\").from_(\"values (map(['a'], [1]))\")\n        self.assertEqual(select.sql(\"snowflake\"), \"SELECT * FROM (SELECT OBJECT_CONSTRUCT('a', 1))\")\n\n        self.validate_all(\n            'SELECT \"c0\", \"c1\" FROM (VALUES (1, 2), (3, 4)) AS \"t0\"(\"c0\", \"c1\")',\n            read={\n                \"spark\": \"SELECT `c0`, `c1` FROM (VALUES (1, 2), (3, 4)) AS `t0`(`c0`, `c1`)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"SELECT $1 AS \"_1\" FROM VALUES ('a'), ('b')\"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT $1 AS \"_1\" FROM (VALUES ('a'), ('b'))\"\"\",\n                \"spark\": \"\"\"SELECT ${1} AS `_1` FROM VALUES ('a'), ('b')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (SELECT OBJECT_CONSTRUCT('a', 1) AS x) AS t\",\n            read={\n                \"duckdb\": \"SELECT * FROM (VALUES ({'a': 1})) AS t(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM (SELECT OBJECT_CONSTRUCT('a', 1) AS x UNION ALL SELECT OBJECT_CONSTRUCT('a', 2)) AS t\",\n            read={\n                \"duckdb\": \"SELECT * FROM (VALUES ({'a': 1}), ({'a': 2})) AS t(x)\",\n            },\n        )\n\n    def test_describe(self):\n        self.validate_identity(\"DESCRIBE SEMANTIC VIEW TPCDS_SEMANTIC_VIEW_SM\")\n        self.validate_identity(\n            \"DESC SEMANTIC VIEW TPCDS_SEMANTIC_VIEW_SM\",\n            \"DESCRIBE SEMANTIC VIEW TPCDS_SEMANTIC_VIEW_SM\",\n        )\n\n        self.validate_all(\n            \"DESCRIBE TABLE db.table\",\n            write={\n                \"snowflake\": \"DESCRIBE TABLE db.table\",\n                \"spark\": \"DESCRIBE db.table\",\n            },\n        )\n        self.validate_all(\n            \"DESCRIBE db.table\",\n            write={\n                \"snowflake\": \"DESCRIBE TABLE db.table\",\n                \"spark\": \"DESCRIBE db.table\",\n            },\n        )\n        self.validate_all(\n            \"DESC TABLE db.table\",\n            write={\n                \"snowflake\": \"DESCRIBE TABLE db.table\",\n                \"spark\": \"DESCRIBE db.table\",\n            },\n        )\n        self.validate_all(\n            \"DESC VIEW db.table\",\n            write={\n                \"snowflake\": \"DESCRIBE VIEW db.table\",\n                \"spark\": \"DESCRIBE db.table\",\n            },\n        )\n\n        for kind, object_name, prop_type in (\n            (\"DYNAMIC TABLE\", \"db.schema.t1\", exp.DynamicProperty),\n            (\"MATERIALIZED VIEW\", \"my_view\", exp.MaterializedProperty),\n            (\"EXTERNAL VOLUME\", \"vol1\", exp.ExternalProperty),\n            (\"COMPUTE POOL\", \"pool1\", exp.ComputeProperty),\n            (\"MASKING POLICY\", \"db.schema.pol1\", exp.MaskingProperty),\n            (\"ROW ACCESS POLICY\", \"pol1\", exp.RowAccessProperty),\n            (\"API INTEGRATION\", \"int1\", exp.ApiProperty),\n            (\"APPLICATION PACKAGE\", \"pkg1\", exp.ApplicationProperty),\n            (\"SECURITY INTEGRATION\", \"int1\", exp.SecurityIntegrationProperty),\n            (\"NETWORK RULE\", \"rule1\", exp.NetworkProperty),\n            (\"ICEBERG TABLE\", \"db.schema.t1\", exp.IcebergProperty),\n            (\"HYBRID TABLE\", \"t1\", exp.HybridProperty),\n            (\"CATALOG INTEGRATION\", \"int1\", exp.CatalogProperty),\n            (\"DATABASE ROLE\", \"role1\", exp.DatabaseProperty),\n        ):\n            with self.subTest(kind=kind):\n                ast = self.validate_identity(f\"DESCRIBE {kind} {object_name}\")\n                self.assertEqual(ast.args[\"kind\"], kind.split()[-1])\n                self.assertIsInstance(ast.find(prop_type), prop_type)\n                self.validate_identity(\n                    f\"DESC {kind} {object_name}\", f\"DESCRIBE {kind} {object_name}\"\n                )\n\n        # Verify keyword tokens used by DESCRIBE work as identifiers and aliases\n        from sqlglot import parser\n        from sqlglot.parsers.snowflake import SnowflakeParser\n\n        tokens = {t.name.lower() for t in SnowflakeParser.CREATABLES - parser.Parser.CREATABLES}\n        tokens |= {k.lower() for k in SnowflakeParser.DESCRIBE_QUALIFIER_PARSERS}\n        tokens -= {\"row\"}  # ROW is not a valid identifier in Snowflake\n\n        for token in sorted(tokens):\n            with self.subTest(token=token):\n                self.validate_identity(f\"SELECT {token} FROM t\")\n                self.validate_identity(f\"SELECT 1 AS {token}\")\n\n        cols = \", \".join(f\"{t} VARCHAR\" for t in sorted(tokens))\n        self.validate_identity(f\"CREATE TABLE t ({cols})\")\n\n        self.validate_all(\n            \"ENDSWITH('abc', 'c')\",\n            read={\n                \"bigquery\": \"ENDS_WITH('abc', 'c')\",\n                \"clickhouse\": \"endsWith('abc', 'c')\",\n                \"databricks\": \"ENDSWITH('abc', 'c')\",\n                \"duckdb\": \"ENDS_WITH('abc', 'c')\",\n                \"presto\": \"ENDS_WITH('abc', 'c')\",\n                \"spark\": \"ENDSWITH('abc', 'c')\",\n            },\n            write={\n                \"bigquery\": \"ENDS_WITH('abc', 'c')\",\n                \"clickhouse\": \"endsWith('abc', 'c')\",\n                \"databricks\": \"ENDSWITH('abc', 'c')\",\n                \"duckdb\": \"ENDS_WITH('abc', 'c')\",\n                \"presto\": \"ENDS_WITH('abc', 'c')\",\n                \"snowflake\": \"ENDSWITH('abc', 'c')\",\n                \"spark\": \"ENDSWITH('abc', 'c')\",\n            },\n        )\n\n    def test_parse_like_any(self):\n        for keyword in (\"LIKE\", \"ILIKE\"):\n            ast = self.validate_identity(f\"a {keyword} ANY FUN('foo')\")\n\n            ast.sql()  # check that this doesn't raise\n\n    @mock.patch(\"sqlglot.generator.logger\")\n    def test_regexp_functions(self, logger):\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern, pos, occ, params, group)\",\n            write={\n                \"bigquery\": \"REGEXP_EXTRACT(subject, pattern, pos, occ)\",\n                \"hive\": \"REGEXP_EXTRACT(subject, pattern, group)\",\n                \"presto\": 'REGEXP_EXTRACT(subject, pattern, \"group\")',\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern, pos, occ, params, group)\",\n                \"spark\": \"REGEXP_EXTRACT(subject, pattern, group)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern)\",\n            read={\n                \"bigquery\": \"REGEXP_EXTRACT(subject, pattern)\",\n            },\n            write={\n                \"bigquery\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern, 1, 1, 'c', 1)\",\n            read={\n                \"hive\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"spark2\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"spark\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"databricks\": \"REGEXP_EXTRACT(subject, pattern)\",\n            },\n            write={\n                \"hive\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"spark2\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"spark\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"databricks\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern, 1, 1, 'c', 1)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern, 1, 1, 'c', group)\",\n            read={\n                \"duckdb\": \"REGEXP_EXTRACT(subject, pattern, group)\",\n                \"hive\": \"REGEXP_EXTRACT(subject, pattern, group)\",\n                \"presto\": \"REGEXP_EXTRACT(subject, pattern, group)\",\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern, 1, 1, 'c', group)\",\n                \"spark\": \"REGEXP_EXTRACT(subject, pattern, group)\",\n            },\n        )\n\n        self.validate_identity(\"REGEXP_SUBSTR_ALL(subject, pattern, pos, occ, param, group)\")\n\n        # DuckDB transpilation tests for REGEXP_SUBSTR\n        # DuckDB's default (no group) is semantically equivalent to group=0\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern)\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern, 3)\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT(NULLIF(SUBSTRING(subject, 3), ''), pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern, 3)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern, 1, 2)\",\n            write={\n                \"duckdb\": \"ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(subject, pattern), 2)\",\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern, 1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern, 1, 1, 'e')\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern, 1, 1, 'e')\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR(subject, pattern, 1, 1, 'e', 0)\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT(subject, pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR(subject, pattern, 1, 1, 'e')\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR_ALL(subject, pattern)\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT_ALL(subject, pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR_ALL(subject, pattern)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR_ALL(subject, pattern, 3)\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT_ALL(SUBSTRING(subject, 3), pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR_ALL(subject, pattern, 3)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR_ALL(subject, pattern, 1, 2)\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT_ALL(subject, pattern)[2:]\",\n                \"snowflake\": \"REGEXP_SUBSTR_ALL(subject, pattern, 1, 2)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR_ALL(subject, pattern, 1, 1, 'e')\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT_ALL(subject, pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR_ALL(subject, pattern, 1, 1, 'e')\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_SUBSTR_ALL(subject, pattern, 1, 1, 'e', 0)\",\n            write={\n                \"duckdb\": \"REGEXP_EXTRACT_ALL(subject, pattern)\",\n                \"snowflake\": \"REGEXP_SUBSTR_ALL(subject, pattern, 1, 1, 'e')\",\n            },\n        )\n\n        self.validate_identity(\"SELECT SEARCH((play, line), 'dream')\")\n        self.validate_identity(\"SELECT SEARCH(line, 'king', ANALYZER => 'UNICODE_ANALYZER')\")\n        self.validate_identity(\"SELECT SEARCH(character, 'king queen', SEARCH_MODE => 'AND')\")\n        self.validate_identity(\n            \"SELECT SEARCH(line, 'king', ANALYZER => 'UNICODE_ANALYZER', SEARCH_MODE => 'OR')\"\n        )\n\n        # AST validation tests - verify argument mapping\n        ast = self.validate_identity(\"SELECT SEARCH(line, 'king')\")\n        search_ast = ast.find(exp.Search)\n        self.assertEqual(list(search_ast.args), [\"this\", \"expression\"])\n        self.assertIsNone(search_ast.args.get(\"analyzer\"))\n        self.assertIsNone(search_ast.args.get(\"search_mode\"))\n\n        ast = self.validate_identity(\"SELECT SEARCH(line, 'king', ANALYZER => 'UNICODE_ANALYZER')\")\n        search_ast = ast.find(exp.Search)\n        self.assertIsNotNone(search_ast.args.get(\"analyzer\"))\n        self.assertIsNone(search_ast.args.get(\"search_mode\"))\n\n        ast = self.validate_identity(\"SELECT SEARCH(character, 'king queen', SEARCH_MODE => 'AND')\")\n        search_ast = ast.find(exp.Search)\n        self.assertIsNone(search_ast.args.get(\"analyzer\"))\n        self.assertIsNotNone(search_ast.args.get(\"search_mode\"))\n\n        # Test with arguments in different order (search_mode first, then analyzer)\n        ast = self.validate_identity(\n            \"SELECT SEARCH(line, 'king', SEARCH_MODE => 'AND', ANALYZER => 'PATTERN_ANALYZER')\",\n            \"SELECT SEARCH(line, 'king', ANALYZER => 'PATTERN_ANALYZER', SEARCH_MODE => 'AND')\",\n        )\n        search_ast = ast.find(exp.Search)\n        self.assertEqual(list(search_ast.args), [\"this\", \"expression\", \"search_mode\", \"analyzer\"])\n        analyzer = search_ast.args.get(\"analyzer\")\n        self.assertIsNotNone(analyzer)\n        search_mode = search_ast.args.get(\"search_mode\")\n        self.assertIsNotNone(search_mode)\n\n        self.validate_identity(\"SELECT SEARCH_IP(col, '192.168.0.0')\").selects[0].assert_is(\n            exp.SearchIp\n        )\n\n        self.validate_identity(\"SELECT REGEXP_COUNT('hello world', 'l ')\")\n        self.validate_identity(\"SELECT REGEXP_COUNT('hello world', 'l', 1)\")\n        self.validate_identity(\"SELECT REGEXP_COUNT('hello world', 'l', 1, 'i')\")\n\n        self.validate_all(\n            \"SELECT REGEXP_COUNT('hello', 'l')\",\n            write={\n                \"snowflake\": \"SELECT REGEXP_COUNT('hello', 'l')\",\n                \"duckdb\": \"SELECT CASE WHEN 'l' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL('hello', 'l')) END\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT REGEXP_COUNT('hello world', 'l', 7)\",\n            write={\n                \"snowflake\": \"SELECT REGEXP_COUNT('hello world', 'l', 7)\",\n                \"duckdb\": \"SELECT CASE WHEN 'l' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('hello world', 7), 'l')) END\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')\",\n            write={\n                \"snowflake\": \"SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')\",\n                \"duckdb\": \"SELECT CASE WHEN '(?im)' || 'L' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('Hello World', 1), '(?im)' || 'L')) END\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT REGEXP_COUNT(subject, pattern)\",\n            write={\n                \"snowflake\": \"SELECT REGEXP_COUNT(subject, pattern)\",\n                \"duckdb\": \"SELECT CASE WHEN pattern = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(subject, pattern)) END\",\n            },\n        )\n\n        self.validate_identity(\"SELECT REGEXP_INSTR('abc', 'a')\")\n        self.validate_identity(\"SELECT REGEXP_INSTR('abc', 'a', 1, 1, 0, 'i')\")\n\n        # Basic transpilation\n        self.validate_all(\n            \"SELECT REGEXP_INSTR(subject, pattern)\",\n            write={\n                \"snowflake\": \"SELECT REGEXP_INSTR(subject, pattern)\",\n                \"duckdb\": \"SELECT CASE WHEN subject IS NULL OR pattern IS NULL THEN NULL WHEN pattern = '' THEN 0 WHEN LENGTH(REGEXP_EXTRACT_ALL(subject, pattern)) < 1 THEN 0 ELSE 1 + COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(subject, pattern)[1:1], x -> LENGTH(x))), 0) + COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(subject, pattern)[1:1 - 1], x -> LENGTH(x))), 0) + 0 END\",\n            },\n        )\n\n        # With position offset\n        self.validate_all(\n            \"SELECT REGEXP_INSTR(subject, pattern, 5)\",\n            write={\n                \"snowflake\": \"SELECT REGEXP_INSTR(subject, pattern, 5)\",\n                \"duckdb\": \"SELECT CASE WHEN subject IS NULL OR pattern IS NULL OR 5 IS NULL THEN NULL WHEN pattern = '' THEN 0 WHEN LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(subject, 5), pattern)) < 1 THEN 0 ELSE 1 + COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(SUBSTRING(subject, 5), pattern)[1:1], x -> LENGTH(x))), 0) + COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(SUBSTRING(subject, 5), pattern)[1:1 - 1], x -> LENGTH(x))), 0) + 5 - 1 END\",\n            },\n        )\n\n        # With occurrence\n        self.validate_all(\n            \"SELECT REGEXP_INSTR(subject, pattern, 1, 2)\",\n            write={\n                \"snowflake\": \"SELECT REGEXP_INSTR(subject, pattern, 1, 2)\",\n                \"duckdb\": \"SELECT CASE WHEN subject IS NULL OR pattern IS NULL OR 1 IS NULL OR 2 IS NULL THEN NULL WHEN pattern = '' THEN 0 WHEN LENGTH(REGEXP_EXTRACT_ALL(subject, pattern)) < 2 THEN 0 ELSE 1 + COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(subject, pattern)[1:2], x -> LENGTH(x))), 0) + COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(subject, pattern)[1:2 - 1], x -> LENGTH(x))), 0) + 0 END\",\n            },\n        )\n\n        # With flags\n        self.validate_all(\n            \"SELECT REGEXP_INSTR(subject, pattern, 1, 1, 0, 'im')\",\n            write={\n                \"snowflake\": \"SELECT REGEXP_INSTR(subject, pattern, 1, 1, 0, 'im')\",\n                \"duckdb\": \"SELECT CASE WHEN subject IS NULL OR pattern IS NULL OR 1 IS NULL OR 1 IS NULL OR 0 IS NULL OR 'im' IS NULL THEN NULL WHEN '(?im)' || pattern = '' THEN 0 WHEN LENGTH(REGEXP_EXTRACT_ALL(subject, '(?im)' || pattern)) < 1 THEN 0 ELSE 1 + COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(subject, '(?im)' || pattern)[1:1], x -> LENGTH(x))), 0) + COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(subject, '(?im)' || pattern)[1:1 - 1], x -> LENGTH(x))), 0) + 0 END\",\n            },\n        )\n\n    @mock.patch(\"sqlglot.generator.logger\")\n    def test_regexp_replace(self, logger):\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern)\",\n            write={\n                \"bigquery\": \"REGEXP_REPLACE(subject, pattern, '')\",\n                \"duckdb\": \"REGEXP_REPLACE(subject, pattern, '', 'g')\",\n                \"hive\": \"REGEXP_REPLACE(subject, pattern, '')\",\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, '')\",\n                \"spark\": \"REGEXP_REPLACE(subject, pattern, '')\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern, replacement)\",\n            read={\n                \"bigquery\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"duckdb\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"hive\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"spark\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n            },\n            write={\n                \"bigquery\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"duckdb\": \"REGEXP_REPLACE(subject, pattern, replacement, 'g')\",\n                \"postgres\": \"REGEXP_REPLACE(subject, pattern, replacement, 'g')\",\n                \"hive\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"spark\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern, replacement, position)\",\n            read={\n                \"spark\": \"REGEXP_REPLACE(subject, pattern, replacement, position)\",\n            },\n            write={\n                \"bigquery\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"duckdb\": \"REGEXP_REPLACE(subject, pattern, replacement, 'g')\",\n                \"postgres\": \"REGEXP_REPLACE(subject, pattern, replacement, position, 'g')\",\n                \"hive\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, position)\",\n                \"spark\": \"REGEXP_REPLACE(subject, pattern, replacement, position)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence, 'c')\",\n            write={\n                \"bigquery\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"duckdb\": \"REGEXP_REPLACE(subject, pattern, replacement, 'c')\",\n                \"postgres\": \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence, 'c')\",\n                \"hive\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, position, occurrence, 'c')\",\n                \"spark\": \"REGEXP_REPLACE(subject, pattern, replacement, position)\",\n            },\n        )\n\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern, replacement, 1, 0, 'c')\",\n            write={\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, 1, 0, 'c')\",\n                \"duckdb\": \"REGEXP_REPLACE(subject, pattern, replacement, 'cg')\",\n                \"postgres\": \"REGEXP_REPLACE(subject, pattern, replacement, 1, 0, 'cg')\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern, replacement, 1, 1)\",\n            write={\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, 1, 1)\",\n                \"duckdb\": \"REGEXP_REPLACE(subject, pattern, replacement)\",\n                \"postgres\": \"REGEXP_REPLACE(subject, pattern, replacement, 1, 1)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern, replacement, 3, 0)\",\n            write={\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, 3, 0)\",\n                \"duckdb\": \"SUBSTRING(subject, 1, 2) || REGEXP_REPLACE(SUBSTRING(subject, 3), pattern, replacement, 'g')\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern, replacement, 3, 1)\",\n            write={\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, 3, 1)\",\n                \"duckdb\": \"SUBSTRING(subject, 1, 2) || REGEXP_REPLACE(SUBSTRING(subject, 3), pattern, replacement)\",\n            },\n        )\n        self.validate_all(\n            \"REGEXP_REPLACE(subject, pattern, replacement, 1, 0, 'i')\",\n            write={\n                \"snowflake\": \"REGEXP_REPLACE(subject, pattern, replacement, 1, 0, 'i')\",\n                \"duckdb\": \"REGEXP_REPLACE(subject, pattern, replacement, 'ig')\",\n            },\n        )\n\n    def test_replace(self):\n        self.validate_all(\n            \"REPLACE(subject, pattern)\",\n            write={\n                \"bigquery\": \"REPLACE(subject, pattern, '')\",\n                \"duckdb\": \"REPLACE(subject, pattern, '')\",\n                \"hive\": \"REPLACE(subject, pattern, '')\",\n                \"snowflake\": \"REPLACE(subject, pattern, '')\",\n                \"spark\": \"REPLACE(subject, pattern, '')\",\n            },\n        )\n        self.validate_all(\n            \"REPLACE(subject, pattern, replacement)\",\n            read={\n                \"bigquery\": \"REPLACE(subject, pattern, replacement)\",\n                \"duckdb\": \"REPLACE(subject, pattern, replacement)\",\n                \"hive\": \"REPLACE(subject, pattern, replacement)\",\n                \"spark\": \"REPLACE(subject, pattern, replacement)\",\n            },\n            write={\n                \"bigquery\": \"REPLACE(subject, pattern, replacement)\",\n                \"duckdb\": \"REPLACE(subject, pattern, replacement)\",\n                \"hive\": \"REPLACE(subject, pattern, replacement)\",\n                \"snowflake\": \"REPLACE(subject, pattern, replacement)\",\n                \"spark\": \"REPLACE(subject, pattern, replacement)\",\n            },\n        )\n\n    def test_match_recognize(self):\n        for window_frame in (\"\", \"FINAL \", \"RUNNING \"):\n            for row in (\n                \"ONE ROW PER MATCH\",\n                \"ALL ROWS PER MATCH\",\n                \"ALL ROWS PER MATCH SHOW EMPTY MATCHES\",\n                \"ALL ROWS PER MATCH OMIT EMPTY MATCHES\",\n                \"ALL ROWS PER MATCH WITH UNMATCHED ROWS\",\n            ):\n                for after in (\n                    \"AFTER MATCH SKIP\",\n                    \"AFTER MATCH SKIP PAST LAST ROW\",\n                    \"AFTER MATCH SKIP TO NEXT ROW\",\n                    \"AFTER MATCH SKIP TO FIRST x\",\n                    \"AFTER MATCH SKIP TO LAST x\",\n                ):\n                    with self.subTest(\n                        f\"MATCH_RECOGNIZE with window frame {window_frame}, rows {row}, after {after}: \"\n                    ):\n                        self.validate_identity(\n                            f\"\"\"SELECT\n  *\nFROM x\nMATCH_RECOGNIZE (\n  PARTITION BY a, b\n  ORDER BY\n    x DESC\n  MEASURES\n    {window_frame}y AS b\n  {row}\n  {after}\n  PATTERN (^ S1 S2*? ( {{- S3 -}} S4 )+ | PERMUTE(S1, S2){{1,2}} $)\n  DEFINE\n    x AS y\n)\"\"\",\n                            pretty=True,\n                        )\n\n    def test_show_users(self):\n        self.validate_identity(\"SHOW USERS\")\n        self.validate_identity(\"SHOW TERSE USERS\")\n        self.validate_identity(\"SHOW USERS LIKE '_foo%' STARTS WITH 'bar' LIMIT 5 FROM 'baz'\")\n\n    def test_show_databases(self):\n        self.validate_identity(\"SHOW TERSE DATABASES\")\n        self.validate_identity(\n            \"SHOW TERSE DATABASES HISTORY LIKE 'foo' STARTS WITH 'bla' LIMIT 5 FROM 'bob' WITH PRIVILEGES USAGE, MODIFY\"\n        )\n\n        ast = parse_one(\"SHOW DATABASES IN ACCOUNT\", read=\"snowflake\")\n        self.assertEqual(ast.this, \"DATABASES\")\n        self.assertEqual(ast.args.get(\"scope_kind\"), \"ACCOUNT\")\n\n    def test_show_file_formats(self):\n        self.validate_identity(\"SHOW FILE FORMATS\")\n        self.validate_identity(\"SHOW FILE FORMATS LIKE 'foo' IN DATABASE db1\")\n        self.validate_identity(\"SHOW FILE FORMATS LIKE 'foo' IN SCHEMA db1.schema1\")\n\n        ast = parse_one(\"SHOW FILE FORMATS IN ACCOUNT\", read=\"snowflake\")\n        self.assertEqual(ast.this, \"FILE FORMATS\")\n        self.assertEqual(ast.args.get(\"scope_kind\"), \"ACCOUNT\")\n\n    def test_show_functions(self):\n        self.validate_identity(\"SHOW FUNCTIONS\")\n        self.validate_identity(\"SHOW FUNCTIONS LIKE 'foo' IN CLASS bla\")\n\n        ast = parse_one(\"SHOW FUNCTIONS IN ACCOUNT\", read=\"snowflake\")\n        self.assertEqual(ast.this, \"FUNCTIONS\")\n        self.assertEqual(ast.args.get(\"scope_kind\"), \"ACCOUNT\")\n\n    def test_show_procedures(self):\n        self.validate_identity(\"SHOW PROCEDURES\")\n        self.validate_identity(\"SHOW PROCEDURES LIKE 'foo' IN APPLICATION app\")\n        self.validate_identity(\"SHOW PROCEDURES LIKE 'foo' IN APPLICATION PACKAGE pkg\")\n\n        ast = parse_one(\"SHOW PROCEDURES IN ACCOUNT\", read=\"snowflake\")\n        self.assertEqual(ast.this, \"PROCEDURES\")\n        self.assertEqual(ast.args.get(\"scope_kind\"), \"ACCOUNT\")\n\n    def test_show_stages(self):\n        self.validate_identity(\"SHOW STAGES\")\n        self.validate_identity(\"SHOW STAGES LIKE 'foo' IN DATABASE db1\")\n        self.validate_identity(\"SHOW STAGES LIKE 'foo' IN SCHEMA db1.schema1\")\n\n        ast = parse_one(\"SHOW STAGES IN ACCOUNT\", read=\"snowflake\")\n        self.assertEqual(ast.this, \"STAGES\")\n        self.assertEqual(ast.args.get(\"scope_kind\"), \"ACCOUNT\")\n\n    def test_show_warehouses(self):\n        self.validate_identity(\"SHOW WAREHOUSES\")\n        self.validate_identity(\"SHOW WAREHOUSES LIKE 'foo' WITH PRIVILEGES USAGE, MODIFY\")\n\n        ast = parse_one(\"SHOW WAREHOUSES\", read=\"snowflake\")\n        self.assertEqual(ast.this, \"WAREHOUSES\")\n\n    def test_show_schemas(self):\n        self.validate_identity(\n            \"show terse schemas in database db1 starts with 'a' limit 10 from 'b'\",\n            \"SHOW TERSE SCHEMAS IN DATABASE db1 STARTS WITH 'a' LIMIT 10 FROM 'b'\",\n        )\n\n        ast = parse_one(\"SHOW SCHEMAS IN DATABASE db1\", read=\"snowflake\")\n        self.assertEqual(ast.args.get(\"scope_kind\"), \"DATABASE\")\n        self.assertEqual(ast.find(exp.Table).sql(dialect=\"snowflake\"), \"db1\")\n\n    def test_show_objects(self):\n        self.validate_identity(\n            \"show terse objects in schema db1.schema1 starts with 'a' limit 10 from 'b'\",\n            \"SHOW TERSE OBJECTS IN SCHEMA db1.schema1 STARTS WITH 'a' LIMIT 10 FROM 'b'\",\n        )\n        self.validate_identity(\n            \"show terse objects in db1.schema1 starts with 'a' limit 10 from 'b'\",\n            \"SHOW TERSE OBJECTS IN SCHEMA db1.schema1 STARTS WITH 'a' LIMIT 10 FROM 'b'\",\n        )\n\n        ast = parse_one(\"SHOW OBJECTS IN db1.schema1\", read=\"snowflake\")\n        self.assertEqual(ast.args.get(\"scope_kind\"), \"SCHEMA\")\n        self.assertEqual(ast.find(exp.Table).sql(dialect=\"snowflake\"), \"db1.schema1\")\n\n    def test_show_columns(self):\n        self.validate_identity(\"SHOW COLUMNS\")\n        self.validate_identity(\"SHOW COLUMNS IN TABLE dt_test\")\n        self.validate_identity(\"SHOW COLUMNS LIKE '_foo%' IN TABLE dt_test\")\n        self.validate_identity(\"SHOW COLUMNS IN VIEW\")\n        self.validate_identity(\"SHOW COLUMNS LIKE '_foo%' IN VIEW dt_test\")\n\n        ast = parse_one(\"SHOW COLUMNS LIKE '_testing%' IN dt_test\", read=\"snowflake\")\n        self.assertEqual(ast.find(exp.Table).sql(dialect=\"snowflake\"), \"dt_test\")\n        self.assertEqual(ast.find(exp.Literal).sql(dialect=\"snowflake\"), \"'_testing%'\")\n\n    def test_show_tables(self):\n        self.validate_identity(\n            \"SHOW TABLES LIKE 'line%' IN tpch.public\",\n            \"SHOW TABLES LIKE 'line%' IN SCHEMA tpch.public\",\n        )\n        self.validate_identity(\n            \"SHOW TABLES HISTORY IN tpch.public\",\n            \"SHOW TABLES HISTORY IN SCHEMA tpch.public\",\n        )\n        self.validate_identity(\n            \"show terse tables in schema db1.schema1 starts with 'a' limit 10 from 'b'\",\n            \"SHOW TERSE TABLES IN SCHEMA db1.schema1 STARTS WITH 'a' LIMIT 10 FROM 'b'\",\n        )\n        self.validate_identity(\n            \"show terse tables in db1.schema1 starts with 'a' limit 10 from 'b'\",\n            \"SHOW TERSE TABLES IN SCHEMA db1.schema1 STARTS WITH 'a' LIMIT 10 FROM 'b'\",\n        )\n\n        ast = parse_one(\"SHOW TABLES IN db1.schema1\", read=\"snowflake\")\n        self.assertEqual(ast.find(exp.Table).sql(dialect=\"snowflake\"), \"db1.schema1\")\n\n    def test_show_primary_keys(self):\n        self.validate_identity(\"SHOW PRIMARY KEYS\")\n        self.validate_identity(\"SHOW PRIMARY KEYS IN ACCOUNT\")\n        self.validate_identity(\"SHOW PRIMARY KEYS IN DATABASE\")\n        self.validate_identity(\"SHOW PRIMARY KEYS IN DATABASE foo\")\n        self.validate_identity(\"SHOW PRIMARY KEYS IN TABLE\")\n        self.validate_identity(\"SHOW PRIMARY KEYS IN TABLE foo\")\n        self.validate_identity(\n            'SHOW PRIMARY KEYS IN \"TEST\".\"PUBLIC\".\"foo\"',\n            'SHOW PRIMARY KEYS IN TABLE \"TEST\".\"PUBLIC\".\"foo\"',\n        )\n        self.validate_identity(\n            'SHOW TERSE PRIMARY KEYS IN \"TEST\".\"PUBLIC\".\"foo\"',\n            'SHOW PRIMARY KEYS IN TABLE \"TEST\".\"PUBLIC\".\"foo\"',\n        )\n\n        ast = parse_one('SHOW PRIMARY KEYS IN \"TEST\".\"PUBLIC\".\"foo\"', read=\"snowflake\")\n        self.assertEqual(ast.find(exp.Table).sql(dialect=\"snowflake\"), '\"TEST\".\"PUBLIC\".\"foo\"')\n\n    def test_show_views(self):\n        self.validate_identity(\"SHOW TERSE VIEWS\")\n        self.validate_identity(\"SHOW VIEWS\")\n        self.validate_identity(\"SHOW VIEWS LIKE 'foo%'\")\n        self.validate_identity(\"SHOW VIEWS IN ACCOUNT\")\n        self.validate_identity(\"SHOW VIEWS IN DATABASE\")\n        self.validate_identity(\"SHOW VIEWS IN DATABASE foo\")\n        self.validate_identity(\"SHOW VIEWS IN SCHEMA foo\")\n        self.validate_identity(\n            \"SHOW VIEWS IN foo\",\n            \"SHOW VIEWS IN SCHEMA foo\",\n        )\n\n        ast = parse_one(\"SHOW VIEWS IN db1.schema1\", read=\"snowflake\")\n        self.assertEqual(ast.find(exp.Table).sql(dialect=\"snowflake\"), \"db1.schema1\")\n\n    def test_show_unique_keys(self):\n        self.validate_identity(\"SHOW UNIQUE KEYS\")\n        self.validate_identity(\"SHOW UNIQUE KEYS IN ACCOUNT\")\n        self.validate_identity(\"SHOW UNIQUE KEYS IN DATABASE\")\n        self.validate_identity(\"SHOW UNIQUE KEYS IN DATABASE foo\")\n        self.validate_identity(\"SHOW UNIQUE KEYS IN TABLE\")\n        self.validate_identity(\"SHOW UNIQUE KEYS IN TABLE foo\")\n        self.validate_identity(\n            'SHOW UNIQUE KEYS IN \"TEST\".\"PUBLIC\".\"foo\"',\n            'SHOW UNIQUE KEYS IN SCHEMA \"TEST\".\"PUBLIC\".\"foo\"',\n        )\n        self.validate_identity(\n            'SHOW TERSE UNIQUE KEYS IN \"TEST\".\"PUBLIC\".\"foo\"',\n            'SHOW UNIQUE KEYS IN SCHEMA \"TEST\".\"PUBLIC\".\"foo\"',\n        )\n\n        ast = parse_one('SHOW UNIQUE KEYS IN \"TEST\".\"PUBLIC\".\"foo\"', read=\"snowflake\")\n        self.assertEqual(ast.find(exp.Table).sql(dialect=\"snowflake\"), '\"TEST\".\"PUBLIC\".\"foo\"')\n\n    def test_show_imported_keys(self):\n        self.validate_identity(\"SHOW IMPORTED KEYS\")\n        self.validate_identity(\"SHOW IMPORTED KEYS IN ACCOUNT\")\n        self.validate_identity(\"SHOW IMPORTED KEYS IN DATABASE\")\n        self.validate_identity(\"SHOW IMPORTED KEYS IN DATABASE foo\")\n        self.validate_identity(\"SHOW IMPORTED KEYS IN TABLE\")\n        self.validate_identity(\"SHOW IMPORTED KEYS IN TABLE foo\")\n        self.validate_identity(\n            'SHOW IMPORTED KEYS IN \"TEST\".\"PUBLIC\".\"foo\"',\n            'SHOW IMPORTED KEYS IN SCHEMA \"TEST\".\"PUBLIC\".\"foo\"',\n        )\n        self.validate_identity(\n            'SHOW TERSE IMPORTED KEYS IN \"TEST\".\"PUBLIC\".\"foo\"',\n            'SHOW IMPORTED KEYS IN SCHEMA \"TEST\".\"PUBLIC\".\"foo\"',\n        )\n\n        ast = parse_one('SHOW IMPORTED KEYS IN \"TEST\".\"PUBLIC\".\"foo\"', read=\"snowflake\")\n        self.assertEqual(ast.find(exp.Table).sql(dialect=\"snowflake\"), '\"TEST\".\"PUBLIC\".\"foo\"')\n\n    def test_show_sequences(self):\n        self.validate_identity(\"SHOW TERSE SEQUENCES\")\n        self.validate_identity(\"SHOW SEQUENCES\")\n        self.validate_identity(\"SHOW SEQUENCES LIKE '_foo%' IN ACCOUNT\")\n        self.validate_identity(\"SHOW SEQUENCES LIKE '_foo%' IN DATABASE\")\n        self.validate_identity(\"SHOW SEQUENCES LIKE '_foo%' IN DATABASE foo\")\n        self.validate_identity(\"SHOW SEQUENCES LIKE '_foo%' IN SCHEMA\")\n        self.validate_identity(\"SHOW SEQUENCES LIKE '_foo%' IN SCHEMA foo\")\n        self.validate_identity(\n            \"SHOW SEQUENCES LIKE '_foo%' IN foo\",\n            \"SHOW SEQUENCES LIKE '_foo%' IN SCHEMA foo\",\n        )\n\n        ast = parse_one(\"SHOW SEQUENCES IN dt_test\", read=\"snowflake\")\n        self.assertEqual(ast.args.get(\"scope_kind\"), \"SCHEMA\")\n\n    def test_storage_integration(self):\n        self.validate_identity(\n            \"\"\"CREATE STORAGE INTEGRATION s3_int\nTYPE=EXTERNAL_STAGE\nSTORAGE_PROVIDER='S3'\nSTORAGE_AWS_ROLE_ARN='arn:aws:iam::001234567890:role/myrole'\nENABLED=TRUE\nSTORAGE_ALLOWED_LOCATIONS=('s3://mybucket1/path1/', 's3://mybucket2/path2/')\"\"\",\n            pretty=True,\n        ).this.assert_is(exp.Identifier)\n\n    def test_swap(self):\n        ast = parse_one(\"ALTER TABLE a SWAP WITH b\", read=\"snowflake\")\n        assert isinstance(ast, exp.Alter)\n        assert isinstance(ast.args[\"actions\"][0], exp.SwapTable)\n\n    def test_try_cast(self):\n        self.validate_all(\"TRY_CAST('foo' AS VARCHAR)\", read={\"hive\": \"CAST('foo' AS STRING)\"})\n        self.validate_all(\"CAST(5 + 5 AS VARCHAR)\", read={\"hive\": \"CAST(5 + 5 AS STRING)\"})\n        self.validate_all(\n            \"CAST(TRY_CAST('2020-01-01' AS DATE) AS VARCHAR)\",\n            read={\n                \"hive\": \"CAST(CAST('2020-01-01' AS DATE) AS STRING)\",\n                \"snowflake\": \"CAST(TRY_CAST('2020-01-01' AS DATE) AS VARCHAR)\",\n            },\n        )\n        self.validate_all(\n            \"TRY_CAST('val' AS VARCHAR)\",\n            read={\n                \"hive\": \"CAST('val' AS STRING)\",\n                \"snowflake\": \"TRY_CAST('val' AS VARCHAR)\",\n            },\n        )\n        self.validate_identity(\"SELECT TRY_CAST(x AS DOUBLE)\")\n        self.validate_identity(\n            \"SELECT TRY_CAST(FOO() AS TEXT)\", \"SELECT TRY_CAST(FOO() AS VARCHAR)\"\n        )\n\n        expression = parse_one(\"SELECT CAST(t.x AS STRING) FROM t\", read=\"hive\")\n\n        for value_type in (\"string\", \"int\"):\n            with self.subTest(\n                f\"Testing Hive -> Snowflake CAST/TRY_CAST conversion for {value_type}\"\n            ):\n                func = \"TRY_CAST\" if value_type == \"string\" else \"CAST\"\n\n                expression = annotate_types(expression, schema={\"t\": {\"x\": value_type}})\n                self.assertEqual(\n                    expression.sql(dialect=\"snowflake\"), f\"SELECT {func}(t.x AS VARCHAR) FROM t\"\n                )\n\n    def test_decfloat(self):\n        self.validate_all(\n            \"SELECT CAST(1.5 AS DECFLOAT)\",\n            write={\n                \"snowflake\": \"SELECT CAST(1.5 AS DECFLOAT)\",\n                \"duckdb\": \"SELECT CAST(1.5 AS DECIMAL(38, 5))\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE t (x DECFLOAT)\",\n            write={\n                \"snowflake\": \"CREATE TABLE t (x DECFLOAT)\",\n                \"duckdb\": \"CREATE TABLE t (x DECIMAL(38, 5))\",\n            },\n        )\n\n    def test_copy(self):\n        self.validate_identity(\"COPY INTO test (c1) FROM (SELECT $1.c1 FROM @mystage)\")\n        self.validate_identity(\n            \"\"\"COPY INTO temp FROM @random_stage/path/ FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|' NULL_IF=('str1', 'str2') FIELD_OPTIONALLY_ENCLOSED_BY='\"' TIMESTAMP_FORMAT='TZHTZM YYYY-MM-DD HH24:MI:SS.FF9' DATE_FORMAT='TZHTZM YYYY-MM-DD HH24:MI:SS.FF9' BINARY_FORMAT=BASE64) VALIDATION_MODE = 'RETURN_3_ROWS'\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"COPY INTO load1 FROM @%load1/data1/ CREDENTIALS = (AWS_KEY_ID='id' AWS_SECRET_KEY='key' AWS_TOKEN='token') FILES = ('test1.csv', 'test2.csv') FORCE = TRUE\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"COPY INTO mytable FROM 'azure://myaccount.blob.core.windows.net/mycontainer/data/files' CREDENTIALS = (AZURE_SAS_TOKEN='token') ENCRYPTION = (TYPE='AZURE_CSE' MASTER_KEY='kPx...') FILE_FORMAT = (FORMAT_NAME=my_csv_format)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"COPY INTO mytable (col1, col2) FROM 's3://mybucket/data/files' STORAGE_INTEGRATION = \"storage\" ENCRYPTION = (TYPE='NONE' MASTER_KEY='key') FILES = ('file1', 'file2') PATTERN = 'pattern' FILE_FORMAT = (FORMAT_NAME=my_csv_format NULL_IF=('')) PARSE_HEADER = TRUE\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"COPY INTO @my_stage/result/data FROM (SELECT * FROM orderstiny) FILE_FORMAT = (TYPE='csv')\"\"\"\n        )\n        self.validate_identity(\"COPY INTO mytable FILE_FORMAT = (TYPE='csv')\")\n        self.validate_identity(\n            \"\"\"COPY INTO MY_DATABASE.MY_SCHEMA.MY_TABLE FROM @MY_DATABASE.MY_SCHEMA.MY_STAGE/my_path FILE_FORMAT = (FORMAT_NAME=MY_DATABASE.MY_SCHEMA.MY_FILE_FORMAT)\"\"\"\n        )\n        self.validate_all(\n            \"\"\"COPY INTO 's3://example/data.csv'\n    FROM EXTRA.EXAMPLE.TABLE\n    CREDENTIALS = ()\n    FILE_FORMAT = (TYPE = CSV COMPRESSION = NONE NULL_IF = ('') FIELD_OPTIONALLY_ENCLOSED_BY = '\"')\n    HEADER = TRUE\n    OVERWRITE = TRUE\n    SINGLE = TRUE\n            \"\"\",\n            write={\n                \"\": \"\"\"COPY INTO 's3://example/data.csv'\nFROM EXTRA.EXAMPLE.TABLE\nCREDENTIALS = () WITH (\n  FILE_FORMAT = (TYPE=CSV COMPRESSION=NONE NULL_IF=(\n    ''\n  ) FIELD_OPTIONALLY_ENCLOSED_BY='\"'),\n  HEADER TRUE,\n  OVERWRITE TRUE,\n  SINGLE TRUE\n)\"\"\",\n                \"snowflake\": \"\"\"COPY INTO 's3://example/data.csv'\nFROM EXTRA.EXAMPLE.TABLE\nCREDENTIALS = ()\nFILE_FORMAT = (TYPE=CSV COMPRESSION=NONE NULL_IF=(\n  ''\n) FIELD_OPTIONALLY_ENCLOSED_BY='\"')\nHEADER = TRUE\nOVERWRITE = TRUE\nSINGLE = TRUE\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"\"\"COPY INTO 's3://example/data.csv'\n    FROM EXTRA.EXAMPLE.TABLE\n    STORAGE_INTEGRATION = S3_INTEGRATION\n    FILE_FORMAT = (TYPE=CSV COMPRESSION=NONE NULL_IF=('') FIELD_OPTIONALLY_ENCLOSED_BY='\"')\n    HEADER = TRUE\n    OVERWRITE = TRUE\n    SINGLE = TRUE\n            \"\"\",\n            write={\n                \"\": \"\"\"COPY INTO 's3://example/data.csv' FROM EXTRA.EXAMPLE.TABLE STORAGE_INTEGRATION = S3_INTEGRATION WITH (FILE_FORMAT = (TYPE=CSV COMPRESSION=NONE NULL_IF=('') FIELD_OPTIONALLY_ENCLOSED_BY='\"'), HEADER TRUE, OVERWRITE TRUE, SINGLE TRUE)\"\"\",\n                \"snowflake\": \"\"\"COPY INTO 's3://example/data.csv' FROM EXTRA.EXAMPLE.TABLE STORAGE_INTEGRATION = S3_INTEGRATION FILE_FORMAT = (TYPE=CSV COMPRESSION=NONE NULL_IF=('') FIELD_OPTIONALLY_ENCLOSED_BY='\"') HEADER = TRUE OVERWRITE = TRUE SINGLE = TRUE\"\"\",\n            },\n        )\n\n        copy_ast = parse_one(\n            \"\"\"COPY INTO 's3://example/contacts.csv' FROM db.tbl STORAGE_INTEGRATION = PROD_S3_SIDETRADE_INTEGRATION FILE_FORMAT = (FORMAT_NAME=my_csv_format TYPE=CSV COMPRESSION=NONE NULL_IF=('') FIELD_OPTIONALLY_ENCLOSED_BY='\"') MATCH_BY_COLUMN_NAME = CASE_SENSITIVE OVERWRITE = TRUE SINGLE = TRUE INCLUDE_METADATA = (col1 = METADATA$START_SCAN_TIME)\"\"\",\n            read=\"snowflake\",\n        )\n        self.assertEqual(\n            quote_identifiers(copy_ast, dialect=\"snowflake\").sql(dialect=\"snowflake\"),\n            \"\"\"COPY INTO 's3://example/contacts.csv' FROM \"db\".\"tbl\" STORAGE_INTEGRATION = \"PROD_S3_SIDETRADE_INTEGRATION\" FILE_FORMAT = (FORMAT_NAME=\"my_csv_format\" TYPE=CSV COMPRESSION=NONE NULL_IF=('') FIELD_OPTIONALLY_ENCLOSED_BY='\"') MATCH_BY_COLUMN_NAME = CASE_SENSITIVE OVERWRITE = TRUE SINGLE = TRUE INCLUDE_METADATA = (\"col1\" = \"METADATA$START_SCAN_TIME\")\"\"\",\n        )\n\n    def test_put_to_stage(self):\n        self.validate_identity('PUT \\'file:///dir/tmp.csv\\' @\"my_DB\".\"schEMA1\".\"MYstage\"')\n\n        # PUT with file path and stage ref containing spaces (wrapped in single quotes)\n        ast = parse_one(\"PUT 'file://my file.txt' '@s1/my folder'\", read=\"snowflake\")\n        self.assertIsInstance(ast, exp.Put)\n        self.assertEqual(ast.this, exp.Literal(this=\"file://my file.txt\", is_string=True))\n        self.assertEqual(ast.args[\"target\"], exp.Var(this=\"'@s1/my folder'\"))\n        self.assertEqual(ast.sql(\"snowflake\"), \"PUT 'file://my file.txt' '@s1/my folder'\")\n\n        # expression with additional properties\n        ast = parse_one(\n            \"PUT 'file:///tmp/my.txt' @stage1/folder PARALLEL = 1 AUTO_COMPRESS=false source_compression=gzip OVERWRITE=TRUE\",\n            read=\"snowflake\",\n        )\n        self.assertIsInstance(ast, exp.Put)\n        self.assertEqual(ast.this, exp.Literal(this=\"file:///tmp/my.txt\", is_string=True))\n        self.assertEqual(ast.args[\"target\"], exp.Var(this=\"@stage1/folder\"))\n        properties = ast.args.get(\"properties\")\n        props_dict = {prop.this.this: prop.args[\"value\"].this for prop in properties.expressions}\n        self.assertEqual(\n            props_dict,\n            {\n                \"PARALLEL\": \"1\",\n                \"AUTO_COMPRESS\": False,\n                \"source_compression\": \"gzip\",\n                \"OVERWRITE\": True,\n            },\n        )\n\n        # validate identity for different args and properties\n        self.validate_identity(\"PUT 'file:///dir/tmp.csv' @s1/test\")\n\n        # the unquoted URI variant is not fully supported yet\n        self.validate_identity(\"PUT file:///dir/tmp.csv @%table\", check_command_warning=True)\n        self.validate_identity(\n            \"PUT file:///dir/tmp.csv @s1/test PARALLEL=1 AUTO_COMPRESS=FALSE source_compression=gzip OVERWRITE=TRUE\",\n            check_command_warning=True,\n        )\n\n    def test_get_from_stage(self):\n        self.validate_identity('GET @\"my_DB\".\"schEMA1\".\"MYstage\" \\'file:///dir/tmp.csv\\'')\n        self.validate_identity(\"GET @s1/test 'file:///dir/tmp.csv'\").assert_is(exp.Get)\n\n        # GET with file path and stage ref containing spaces (wrapped in single quotes)\n        ast = parse_one(\"GET '@s1/my folder' 'file://my file.txt'\", read=\"snowflake\")\n        self.assertIsInstance(ast, exp.Get)\n        self.assertEqual(ast.args[\"target\"], exp.Var(this=\"'@s1/my folder'\"))\n        self.assertEqual(ast.this, exp.Literal(this=\"file://my file.txt\", is_string=True))\n        self.assertEqual(ast.sql(\"snowflake\"), \"GET '@s1/my folder' 'file://my file.txt'\")\n\n        # expression with additional properties\n        ast = parse_one(\"GET @stage1/folder 'file:///tmp/my.txt' PARALLEL = 1\", read=\"snowflake\")\n        self.assertIsInstance(ast, exp.Get)\n        self.assertEqual(ast.args[\"target\"], exp.Var(this=\"@stage1/folder\"))\n        self.assertEqual(ast.this, exp.Literal(this=\"file:///tmp/my.txt\", is_string=True))\n        properties = ast.args.get(\"properties\")\n        props_dict = {prop.this.this: prop.args[\"value\"].this for prop in properties.expressions}\n        self.assertEqual(props_dict, {\"PARALLEL\": \"1\"})\n\n        # the unquoted URI variant is not fully supported yet\n        self.validate_identity(\"GET @%table file:///dir/tmp.csv\", check_command_warning=True)\n        self.validate_identity(\n            \"GET @s1/test file:///dir/tmp.csv PARALLEL=1\",\n            check_command_warning=True,\n        )\n\n    def test_querying_semi_structured_data(self):\n        self.validate_identity(\"SELECT $1\")\n        self.validate_identity(\"SELECT $1.elem\")\n\n        self.validate_identity(\"SELECT $1:a.b\", \"SELECT GET_PATH($1, 'a.b')\")\n        self.validate_identity(\"SELECT t.$23:a.b\", \"SELECT GET_PATH(t.$23, 'a.b')\")\n        self.validate_identity(\"SELECT t.$17:a[0].b[0].c\", \"SELECT GET_PATH(t.$17, 'a[0].b[0].c')\")\n\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"a\": [1, 2]}') AS v), s AS (SELECT 1 AS x) SELECT t.v:a[s.x] FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"a\": [1, 2]}') AS v), s AS (SELECT 1 AS x) SELECT GET_PATH(t.v, 'a')[s.x] FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"a\": [1, 2]}') AS v), s AS (SELECT 1 AS x) SELECT (t.v -> '$.a')[s.x] FROM t, s\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": 1}]}') AS v), s AS (SELECT 0 AS x) SELECT t.v:c[s.x]:r FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": 1}]}') AS v), s AS (SELECT 0 AS x) SELECT GET_PATH(GET_PATH(t.v, 'c')[s.x], 'r') FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"c\": [{\"r\": 1}]}') AS v), s AS (SELECT 0 AS x) SELECT (t.v -> '$.c')[s.x] -> '$.r' FROM t, s\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": {\"d\": 1}}]}') AS v), s AS (SELECT 0 AS x) SELECT t.v:c[s.x]:r:d::varchar FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": {\"d\": 1}}]}') AS v), s AS (SELECT 0 AS x) SELECT CAST(GET_PATH(GET_PATH(t.v, 'c')[s.x], 'r.d') AS VARCHAR) FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"c\": [{\"r\": {\"d\": 1}}]}') AS v), s AS (SELECT 0 AS x) SELECT CAST((t.v -> '$.c')[s.x] -> '$.r.d' AS TEXT) FROM t, s\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"a\": {\"b\": [1, 2]}}') AS v), s AS (SELECT 1 AS x) SELECT t.v:a:b[s.x] FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"a\": {\"b\": [1, 2]}}') AS v), s AS (SELECT 1 AS x) SELECT GET_PATH(t.v, 'a.b')[s.x] FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"a\": {\"b\": [1, 2]}}') AS v), s AS (SELECT 1 AS x) SELECT (t.v -> '$.a.b')[s.x] FROM t, s\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": 1}]}') AS v), s AS (SELECT 0 AS x) SELECT t.v:c[s.x].r FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": 1}]}') AS v), s AS (SELECT 0 AS x) SELECT GET_PATH(GET_PATH(t.v, 'c')[s.x], 'r') FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"c\": [{\"r\": 1}]}') AS v), s AS (SELECT 0 AS x) SELECT (t.v -> '$.c')[s.x] -> '$.r' FROM t, s\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": {\"d\": 1}}]}') AS v), s AS (SELECT 0 AS x) SELECT t.v:c[s.x].r.d FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": {\"d\": 1}}]}') AS v), s AS (SELECT 0 AS x) SELECT GET_PATH(GET_PATH(t.v, 'c')[s.x], 'r.d') FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"c\": [{\"r\": {\"d\": 1}}]}') AS v), s AS (SELECT 0 AS x) SELECT (t.v -> '$.c')[s.x] -> '$.r.d' FROM t, s\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": {\"d\": {\"e\": 1}}}]}') AS v), s AS (SELECT 0 AS x) SELECT t.v:c[s.x].r.d.e FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"c\": [{\"r\": {\"d\": {\"e\": 1}}}]}') AS v), s AS (SELECT 0 AS x) SELECT GET_PATH(GET_PATH(t.v, 'c')[s.x], 'r.d.e') FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"c\": [{\"r\": {\"d\": {\"e\": 1}}}]}') AS v), s AS (SELECT 0 AS x) SELECT (t.v -> '$.c')[s.x] -> '$.r.d.e' FROM t, s\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"a\": {\"b\": [{\"r\": {\"d\": 1}}]}}') AS v), s AS (SELECT 0 AS x) SELECT t.v:a.b[s.x].r.d FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"a\": {\"b\": [{\"r\": {\"d\": 1}}]}}') AS v), s AS (SELECT 0 AS x) SELECT GET_PATH(GET_PATH(t.v, 'a.b')[s.x], 'r.d') FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"a\": {\"b\": [{\"r\": {\"d\": 1}}]}}') AS v), s AS (SELECT 0 AS x) SELECT (t.v -> '$.a.b')[s.x] -> '$.r.d' FROM t, s\"\"\",\n            },\n        )\n        self.validate_all(\n            \"\"\"WITH t AS (SELECT PARSE_JSON('{\"a\": {\"b\": [{\"r\": {\"d\": [10, 20, 30]}}]}}') AS v), s AS (SELECT 0 AS x, 2 AS y) SELECT t.v:a.b[s.x].r.d[s.y] FROM t, s\"\"\",\n            write={\n                \"snowflake\": \"\"\"WITH t AS (SELECT PARSE_JSON('{\"a\": {\"b\": [{\"r\": {\"d\": [10, 20, 30]}}]}}') AS v), s AS (SELECT 0 AS x, 2 AS y) SELECT GET_PATH(GET_PATH(t.v, 'a.b')[s.x], 'r.d')[s.y] FROM t, s\"\"\",\n                \"duckdb\": \"\"\"WITH t AS (SELECT JSON('{\"a\": {\"b\": [{\"r\": {\"d\": [10, 20, 30]}}]}}') AS v), s AS (SELECT 0 AS x, 2 AS y) SELECT ((t.v -> '$.a.b')[s.x] -> '$.r.d')[s.y] FROM t, s\"\"\",\n            },\n        )\n\n        self.validate_all(\n            \"\"\"\n            SELECT col:\"customer's department\"\n            \"\"\",\n            write={\n                \"snowflake\": \"\"\"SELECT GET_PATH(col, '[\"customer\\\\'s department\"]')\"\"\",\n                \"postgres\": \"SELECT JSON_EXTRACT_PATH(col, 'customer''s department')\",\n            },\n        )\n\n    def test_alter_set_unset(self):\n        self.validate_identity(\"ALTER TABLE tbl SET DATA_RETENTION_TIME_IN_DAYS=1\")\n        self.validate_identity(\"ALTER TABLE tbl SET DEFAULT_DDL_COLLATION='test'\")\n        self.validate_identity(\"ALTER TABLE foo SET COMMENT='bar'\")\n        self.validate_identity(\"ALTER TABLE foo SET CHANGE_TRACKING=FALSE\")\n        self.validate_identity(\"ALTER TABLE table1 SET TAG foo.bar = 'baz'\")\n        self.validate_identity(\"ALTER TABLE IF EXISTS foo SET TAG a = 'a', b = 'b', c = 'c'\")\n        self.validate_identity(\n            \"\"\"ALTER TABLE tbl SET STAGE_FILE_FORMAT = (TYPE=CSV FIELD_DELIMITER='|' NULL_IF=('') FIELD_OPTIONALLY_ENCLOSED_BY='\"' TIMESTAMP_FORMAT='TZHTZM YYYY-MM-DD HH24:MI:SS.FF9' DATE_FORMAT='TZHTZM YYYY-MM-DD HH24:MI:SS.FF9' BINARY_FORMAT=BASE64)\"\"\",\n        )\n        self.validate_identity(\n            \"\"\"ALTER TABLE tbl SET STAGE_COPY_OPTIONS = (ON_ERROR=SKIP_FILE SIZE_LIMIT=5 PURGE=TRUE MATCH_BY_COLUMN_NAME=CASE_SENSITIVE)\"\"\"\n        )\n\n        self.validate_identity(\"ALTER TABLE foo UNSET TAG a, b, c\")\n        self.validate_identity(\"ALTER TABLE foo UNSET DATA_RETENTION_TIME_IN_DAYS, CHANGE_TRACKING\")\n\n    def test_alter_session(self):\n        expr = self.validate_identity(\n            \"ALTER SESSION SET autocommit = FALSE, QUERY_TAG = 'qtag', JSON_INDENT = 1\"\n        )\n        self.assertEqual(\n            expr.find(exp.AlterSession),\n            exp.AlterSession(\n                expressions=[\n                    exp.SetItem(\n                        this=exp.EQ(\n                            this=exp.Column(this=exp.Identifier(this=\"autocommit\", quoted=False)),\n                            expression=exp.Boolean(this=False),\n                        ),\n                    ),\n                    exp.SetItem(\n                        this=exp.EQ(\n                            this=exp.Column(this=exp.Identifier(this=\"QUERY_TAG\", quoted=False)),\n                            expression=exp.Literal(this=\"qtag\", is_string=True),\n                        ),\n                    ),\n                    exp.SetItem(\n                        this=exp.EQ(\n                            this=exp.Column(this=exp.Identifier(this=\"JSON_INDENT\", quoted=False)),\n                            expression=exp.Literal(this=\"1\", is_string=False),\n                        ),\n                    ),\n                ],\n                unset=False,\n            ),\n        )\n\n        expr = self.validate_identity(\"ALTER SESSION UNSET autocommit, QUERY_TAG\")\n        self.assertEqual(\n            expr.find(exp.AlterSession),\n            exp.AlterSession(\n                expressions=[\n                    exp.SetItem(this=exp.Identifier(this=\"autocommit\", quoted=False)),\n                    exp.SetItem(this=exp.Identifier(this=\"QUERY_TAG\", quoted=False)),\n                ],\n                unset=True,\n            ),\n        )\n\n    def test_from_changes(self):\n        self.validate_identity(\n            \"\"\"SELECT C1 FROM t1 CHANGES (INFORMATION => APPEND_ONLY) AT (STREAM => 's1') END (TIMESTAMP => $ts2)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT C1 FROM t1 CHANGES (INFORMATION => APPEND_ONLY) BEFORE (STATEMENT => 'STMT_ID') END (TIMESTAMP => $ts2)\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"SELECT 1 FROM some_table CHANGES (INFORMATION => APPEND_ONLY) AT (TIMESTAMP => TO_TIMESTAMP_TZ('2024-07-01 00:00:00+00:00')) END (TIMESTAMP => TO_TIMESTAMP_TZ('2024-07-01 14:28:59.999999+00:00'))\"\"\",\n            \"\"\"SELECT 1 FROM some_table CHANGES (INFORMATION => APPEND_ONLY) AT (TIMESTAMP => CAST('2024-07-01 00:00:00+00:00' AS TIMESTAMPTZ)) END (TIMESTAMP => CAST('2024-07-01 14:28:59.999999+00:00' AS TIMESTAMPTZ))\"\"\",\n        )\n\n    def test_grant(self):\n        grant_cmds = [\n            \"GRANT SELECT ON FUTURE TABLES IN DATABASE d1 TO ROLE r1\",\n            \"GRANT INSERT, DELETE ON FUTURE TABLES IN SCHEMA d1.s1 TO ROLE r2\",\n            \"GRANT SELECT ON ALL TABLES IN SCHEMA mydb.myschema to ROLE analyst\",\n            \"GRANT SELECT, INSERT ON FUTURE TABLES IN SCHEMA mydb.myschema TO ROLE role1\",\n            \"GRANT CREATE MATERIALIZED VIEW ON SCHEMA mydb.myschema TO DATABASE ROLE mydb.dr1\",\n        ]\n\n        for sql in grant_cmds:\n            with self.subTest(f\"Testing Snowflake's GRANT command statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n        self.validate_identity(\n            \"GRANT ALL PRIVILEGES ON FUNCTION mydb.myschema.ADD5(number) TO ROLE analyst\"\n        )\n\n    def test_revoke(self):\n        revoke_cmds = [\n            \"REVOKE SELECT ON FUTURE TABLES IN DATABASE d1 FROM ROLE r1\",\n            \"REVOKE INSERT, DELETE ON FUTURE TABLES IN SCHEMA d1.s1 FROM ROLE r2\",\n            \"REVOKE SELECT ON ALL TABLES IN SCHEMA mydb.myschema FROM ROLE analyst\",\n            \"REVOKE SELECT, INSERT ON FUTURE TABLES IN SCHEMA mydb.myschema FROM ROLE role1\",\n            \"REVOKE CREATE MATERIALIZED VIEW ON SCHEMA mydb.myschema FROM DATABASE ROLE mydb.dr1\",\n        ]\n\n        for sql in revoke_cmds:\n            with self.subTest(f\"Testing Snowflake's REVOKE command statement: {sql}\"):\n                self.validate_identity(sql, check_command_warning=True)\n\n        self.validate_identity(\n            \"REVOKE ALL PRIVILEGES ON FUNCTION mydb.myschema.ADD5(number) FROM ROLE analyst\"\n        )\n\n    def test_window_function_arg(self):\n        query = \"SELECT * FROM TABLE(db.schema.FUNC(a) OVER ())\"\n\n        ast = self.parse_one(query)\n        window = ast.find(exp.Window)\n\n        self.assertEqual(ast.sql(\"snowflake\"), query)\n        self.assertEqual(len(list(ast.find_all(exp.Column))), 1)\n        self.assertEqual(window.this.sql(\"snowflake\"), \"db.schema.FUNC(a)\")\n\n    def test_offset_without_limit(self):\n        self.validate_all(\n            \"SELECT 1 ORDER BY 1 LIMIT NULL OFFSET 0\",\n            read={\n                \"trino\": \"SELECT 1 ORDER BY 1 OFFSET 0\",\n            },\n        )\n\n    def test_listagg(self):\n        self.validate_identity(\"LISTAGG(data['some_field'], ',')\")\n\n        for distinct in (\"\", \"DISTINCT \"):\n            self.validate_all(\n                f\"SELECT LISTAGG({distinct}col, '|SEPARATOR|') WITHIN GROUP (ORDER BY col2) FROM t\",\n                read={\n                    \"trino\": f\"SELECT LISTAGG({distinct}col, '|SEPARATOR|') WITHIN GROUP (ORDER BY col2) FROM t\",\n                    \"duckdb\": f\"SELECT LISTAGG({distinct}col, '|SEPARATOR|' ORDER BY col2) FROM t\",\n                },\n                write={\n                    \"snowflake\": f\"SELECT LISTAGG({distinct}col, '|SEPARATOR|') WITHIN GROUP (ORDER BY col2) FROM t\",\n                    \"trino\": f\"SELECT LISTAGG({distinct}col, '|SEPARATOR|') WITHIN GROUP (ORDER BY col2) FROM t\",\n                    \"duckdb\": f\"SELECT LISTAGG({distinct}col, '|SEPARATOR|' ORDER BY col2) FROM t\",\n                },\n            )\n\n    def test_rely_options(self):\n        for option in (\"NORELY\", \"RELY\"):\n            self.validate_identity(\n                f\"CREATE TABLE t (col1 INT PRIMARY KEY {option}, col2 INT UNIQUE {option}, col3 INT NOT NULL FOREIGN KEY REFERENCES other_t (id) {option})\"\n            )\n            self.validate_identity(\n                f\"CREATE TABLE t (col1 INT, col2 INT, col3 INT, PRIMARY KEY (col1) {option}, UNIQUE (col1, col2) {option}, FOREIGN KEY (col3) REFERENCES other_t (id) {option})\"\n            )\n\n    def test_parameter(self):\n        expr = self.validate_identity(\"SELECT :1\")\n        self.assertEqual(expr.find(exp.Placeholder), exp.Placeholder(this=\"1\"))\n        self.validate_identity(\"SELECT :1, :2\")\n        self.validate_identity(\"SELECT :1 + :2\")\n\n    def test_max_by_min_by(self):\n        max_by = self.validate_identity(\"MAX_BY(DISTINCT selected_col, filtered_col)\")\n        min_by = self.validate_identity(\"MIN_BY(DISTINCT selected_col, filtered_col)\")\n\n        for node in (max_by, min_by):\n            self.assertEqual(len(node.this.expressions), 1)\n            self.assertIsInstance(node.expression, exp.Column)\n\n        # Test 3-argument case (returns array)\n        max_by_3 = self.validate_identity(\"MAX_BY(selected_col, filtered_col, 5)\")\n        min_by_3 = self.validate_identity(\"MIN_BY(selected_col, filtered_col, 3)\")\n\n        for node in (max_by_3, min_by_3):\n            with self.subTest(f\"Checking  count arg of {node.sql('snowflake')}\"):\n                self.assertIsNotNone(node.args.get(\"count\"))\n\n        self.validate_all(\n            \"SELECT MAX_BY(a, b) FROM t\",\n            write={\n                \"snowflake\": \"SELECT MAX_BY(a, b) FROM t\",\n                \"duckdb\": \"SELECT ARG_MAX(a, b) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MIN_BY(a, b) FROM t\",\n            write={\n                \"snowflake\": \"SELECT MIN_BY(a, b) FROM t\",\n                \"duckdb\": \"SELECT ARG_MIN(a, b) FROM t\",\n            },\n        )\n\n    def test_create_view_copy_grants(self):\n        # for normal views, 'COPY GRANTS' goes *after* the column list. ref: https://docs.snowflake.com/en/sql-reference/sql/create-view#syntax\n        self.validate_identity(\n            \"CREATE OR REPLACE VIEW FOO (A, B) COPY GRANTS AS SELECT A, B FROM TBL\"\n        )\n\n        # for materialized views, 'COPY GRANTS' must go *before* the column list or an error will be thrown. ref: https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view#syntax\n        self.validate_identity(\n            \"CREATE OR REPLACE MATERIALIZED VIEW FOO COPY GRANTS (A, B) AS SELECT A, B FROM TBL\"\n        )\n\n        # check that only 'COPY GRANTS' goes before the column list and other properties still go after\n        self.validate_identity(\n            \"CREATE OR REPLACE MATERIALIZED VIEW FOO COPY GRANTS (A, B) COMMENT='foo' TAG (a='b') AS SELECT A, B FROM TBL\"\n        )\n\n        # no COPY GRANTS\n        self.validate_identity(\"CREATE OR REPLACE VIEW FOO (A, B) AS SELECT A, B FROM TBL\")\n        self.validate_identity(\n            \"CREATE OR REPLACE MATERIALIZED VIEW FOO (A, B) AS SELECT A, B FROM TBL\"\n        )\n\n    def test_semantic_view(self):\n        for dimensions, metrics, where, facts in [\n            (None, None, None, None),\n            (None, None, None, \"a.a\"),\n            (\"DATE_PART('year', a.b)\", None, None, None),\n            (None, \"a.b, a.c\", None, None),\n            (None, None, None, \"a.d, a.e\"),\n            (\"a.b, a.c\", \"a.b, a.c\", None, None),\n            (\"a.b\", \"a.b, a.c\", \"a.c > 5\", None),\n            (\"a.b\", None, \"a.c > 5\", \"a.d\"),\n        ]:\n            with self.subTest(\n                f\"Testing Snowflake's SEMANTIC_VIEW command statement: {dimensions}, {metrics}, {facts} {where}\"\n            ):\n                dimensions_str = f\" DIMENSIONS {dimensions}\" if dimensions else \"\"\n                metrics_str = f\" METRICS {metrics}\" if metrics else \"\"\n                fact_str = f\" FACTS {facts}\" if facts else \"\"\n                where_str = f\" WHERE {where}\" if where else \"\"\n\n                self.validate_identity(\n                    f\"SELECT * FROM SEMANTIC_VIEW(tbl{metrics_str}{dimensions_str}{fact_str}{where_str}) ORDER BY foo\"\n                )\n                self.validate_identity(\n                    f\"SELECT * FROM SEMANTIC_VIEW(tbl{dimensions_str}{fact_str}{metrics_str}{where_str})\",\n                    f\"SELECT * FROM SEMANTIC_VIEW(tbl{metrics_str}{dimensions_str}{fact_str}{where_str})\",\n                )\n\n        self.validate_identity(\n            \"SELECT * FROM SEMANTIC_VIEW(foo METRICS a.b, a.c DIMENSIONS a.b, a.c WHERE a.b > '1995-01-01')\",\n            \"\"\"SELECT\n  *\nFROM SEMANTIC_VIEW(\n  foo\n  METRICS a.b, a.c\n  DIMENSIONS a.b, a.c\n  WHERE a.b > '1995-01-01'\n)\"\"\",\n            pretty=True,\n        )\n\n        self.validate_identity(\n            \"SELECT col1, col2, metric1 FROM SEMANTIC_VIEW(mydb.myschema.my_semantic_view METRICS metric1 DIMENSIONS col1, DATE_TRUNC('MONTH', timestamp_col) AS col2) ORDER BY col1, col2 DESC\"\n        )\n\n    def test_get_extract(self):\n        self.validate_all(\n            \"SELECT GET([4, 5, 6], 1)\",\n            write={\n                \"snowflake\": \"SELECT GET([4, 5, 6], 1)\",\n                \"duckdb\": \"SELECT [4, 5, 6][2]\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT GET(col::MAP(INTEGER, VARCHAR), 1)\",\n            write={\n                \"snowflake\": \"SELECT GET(CAST(col AS MAP(INT, VARCHAR)), 1)\",\n                \"duckdb\": \"SELECT CAST(col AS MAP(INT, TEXT))[1]\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT GET(v, 'field')\",\n            write={\n                \"snowflake\": \"SELECT GET(v, 'field')\",\n                \"duckdb\": \"SELECT v -> '$.field'\",\n            },\n        )\n\n        self.validate_identity(\"GET(foo, bar)\").assert_is(exp.GetExtract)\n\n    def test_create_sequence(self):\n        self.validate_identity(\n            \"CREATE SEQUENCE seq  START=5 comment = 'foo' INCREMENT=10\",\n            \"CREATE SEQUENCE seq COMMENT='foo' START WITH 5 INCREMENT BY 10\",\n        )\n        self.validate_all(\n            \"CREATE SEQUENCE seq WITH START=1 INCREMENT=1\",\n            write={\n                \"snowflake\": \"CREATE SEQUENCE seq START WITH 1 INCREMENT BY 1\",\n                \"duckdb\": \"CREATE SEQUENCE seq START WITH 1 INCREMENT BY 1\",\n            },\n        )\n\n    def test_bit_aggs(self):\n        bit_and_funcs = [\"BITANDAGG\", \"BITAND_AGG\", \"BIT_AND_AGG\", \"BIT_ANDAGG\"]\n        bit_or_funcs = [\"BITORAGG\", \"BITOR_AGG\", \"BIT_OR_AGG\", \"BIT_ORAGG\"]\n        bit_xor_funcs = [\"BITXORAGG\", \"BITXOR_AGG\", \"BIT_XOR_AGG\", \"BIT_XORAGG\"]\n        for bit_func in (bit_and_funcs, bit_or_funcs, bit_xor_funcs):\n            for name in bit_func:\n                with self.subTest(f\"Testing Snowflakes {name}\"):\n                    self.validate_identity(f\"{name}(x)\", f\"{bit_func[0]}(x)\")\n\n    def test_bitmap_or_agg(self):\n        self.validate_identity(\"BITMAP_OR_AGG(x)\")\n\n    def test_md5_functions(self):\n        self.validate_identity(\"MD5_HEX(col)\", \"MD5(col)\")\n        self.validate_identity(\"MD5(col)\")\n        self.validate_identity(\"MD5_BINARY(col)\")\n        self.validate_identity(\"MD5_NUMBER_LOWER64(col)\")\n        self.validate_identity(\"MD5_NUMBER_UPPER64(col)\")\n\n    def test_sha1(self):\n        self.validate_all(\n            \"SHA1(x)\",\n            write={\n                \"snowflake\": \"SHA1(x)\",\n                \"duckdb\": \"SHA1(x)\",\n            },\n        )\n\n        expr = self.validate_identity(\"SHA1('text')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"SHA1('text')\")\n        self.assertEqual(annotated.sql(\"snowflake\"), \"SHA1('text')\")\n\n        self.validate_all(\n            \"SHA1(X'002A'::BINARY)\",\n            write={\n                \"snowflake\": \"SHA1(CAST(x'002A' AS BINARY))\",\n                \"duckdb\": \"SHA1(CAST(UNHEX('002A') AS BLOB))\",\n            },\n        )\n        expr = self.validate_identity(\"SHA1(123)\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"snowflake\"), \"SHA1(123)\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"SHA1(CAST(123 AS TEXT))\")\n\n        expr = self.validate_identity(\"SHA1(DATE '2024-01-15')\", \"SHA1(CAST('2024-01-15' AS DATE))\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"snowflake\"), \"SHA1(CAST('2024-01-15' AS DATE))\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"SHA1(CAST(CAST('2024-01-15' AS DATE) AS TEXT))\")\n\n    def test_model_attribute(self):\n        self.validate_identity(\"SELECT model!mladmin\")\n        self.validate_identity(\"SELECT model!PREDICT(1)\")\n        self.validate_identity(\"SELECT m!PREDICT(INPUT_DATA => {*}) AS p FROM tbl\")\n        self.validate_identity(\"SELECT m!PREDICT(INPUT_DATA => {tbl.*}) AS p FROM tbl\")\n        self.validate_identity(\"x.y.z!PREDICT(foo, bar, baz, bla)\")\n        self.validate_identity(\n            \"SELECT * FROM TABLE(model_trained_with_labeled_data!DETECT_ANOMALIES(INPUT_DATA => TABLE(view_with_data_to_analyze), TIMESTAMP_COLNAME => 'date', TARGET_COLNAME => 'sales', CONFIG_OBJECT => OBJECT_CONSTRUCT('prediction_interval', 0.99)))\"\n        )\n\n    def test_set_item_kind_attribute(self):\n        expr = parse_one(\"ALTER SESSION SET autocommit = FALSE\", read=\"snowflake\")\n        set_item = expr.find(exp.SetItem)\n        self.assertIsNotNone(set_item)\n        self.assertIsNone(set_item.args.get(\"kind\"))\n\n        expr = parse_one(\"SET a = 1\", read=\"snowflake\")\n        set_item = expr.find(exp.SetItem)\n        self.assertIsNotNone(set_item)\n        self.assertEqual(set_item.args.get(\"kind\"), \"VARIABLE\")\n\n    def test_round(self):\n        self.validate_all(\n            \"SELECT ROUND(2.25) AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25) AS value\",\n                \"duckdb\": \"SELECT ROUND(2.25) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(2.25, 1) AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25, 1) AS value\",\n                \"duckdb\": \"SELECT ROUND(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(EXPR => 2.25, SCALE => 1) AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25, 1) AS value\",\n                \"duckdb\": \"SELECT ROUND(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(SCALE => 1, EXPR => 2.25) AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25, 1) AS value\",\n                \"duckdb\": \"SELECT ROUND(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(2.25, 1, 'HALF_AWAY_FROM_ZERO') AS value\",\n            write={\n                \"snowflake\": \"\"\"SELECT ROUND(2.25, 1, 'HALF_AWAY_FROM_ZERO') AS value\"\"\",\n                \"duckdb\": \"SELECT ROUND(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(EXPR => 2.25, SCALE => 1, ROUNDING_MODE => 'HALF_AWAY_FROM_ZERO') AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25, 1, 'HALF_AWAY_FROM_ZERO') AS value\",\n                \"duckdb\": \"SELECT ROUND(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(2.25, 1, 'HALF_TO_EVEN') AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25, 1, 'HALF_TO_EVEN') AS value\",\n                \"duckdb\": \"SELECT ROUND_EVEN(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(ROUNDING_MODE => 'HALF_TO_EVEN', EXPR => 2.25, SCALE => 1) AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25, 1, 'HALF_TO_EVEN') AS value\",\n                \"duckdb\": \"SELECT ROUND_EVEN(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(SCALE => 1, EXPR => 2.25, , ROUNDING_MODE => 'HALF_TO_EVEN') AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25, 1, 'HALF_TO_EVEN') AS value\",\n                \"duckdb\": \"SELECT ROUND_EVEN(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(EXPR => 2.25, SCALE => 1, ROUNDING_MODE => 'HALF_TO_EVEN') AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.25, 1, 'HALF_TO_EVEN') AS value\",\n                \"duckdb\": \"SELECT ROUND_EVEN(2.25, 1) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(2.256, 1.8) AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.256, 1.8) AS value\",\n                \"duckdb\": \"SELECT ROUND(2.256, CAST(1.8 AS INT)) AS value\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ROUND(2.256, CAST(1.8 AS DECIMAL(38, 0))) AS value\",\n            write={\n                \"snowflake\": \"SELECT ROUND(2.256, CAST(1.8 AS DECIMAL(38, 0))) AS value\",\n                \"duckdb\": \"SELECT ROUND(2.256, CAST(CAST(1.8 AS DECIMAL(38, 0)) AS INT)) AS value\",\n            },\n        )\n\n    def test_get_bit(self):\n        self.validate_all(\n            \"SELECT GETBIT(11, 1)\",\n            write={\n                \"snowflake\": \"SELECT GETBIT(11, 1)\",\n                \"databricks\": \"SELECT GETBIT(11, 1)\",\n                \"redshift\": \"SELECT GETBIT(11, 1)\",\n            },\n        )\n        expr = self.validate_identity(\"GETBIT(11, 1)\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n\n        self.assertEqual(annotated.sql(\"duckdb\"), \"(11 >> 1) & 1\")\n        self.assertEqual(annotated.sql(\"postgres\"), \"11 >> 1 & 1\")\n\n    def test_to_binary(self):\n        expr = self.validate_identity(\"TO_BINARY('48454C50', 'HEX')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"UNHEX('48454C50')\")\n\n        expr = self.validate_identity(\"TO_BINARY('48454C50')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"UNHEX('48454C50')\")\n\n        expr = self.validate_identity(\"TO_BINARY('TEST', 'UTF-8')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"ENCODE('TEST')\")\n\n        expr = self.validate_identity(\"TO_BINARY('SEVMUA==', 'BASE64')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"FROM_BASE64('SEVMUA==')\")\n\n        expr = self.validate_identity(\"TRY_TO_BINARY('48454C50', 'HEX')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"TRY(UNHEX('48454C50'))\")\n\n        expr = self.validate_identity(\"TRY_TO_BINARY('48454C50')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"TRY(UNHEX('48454C50'))\")\n\n        expr = self.validate_identity(\"TRY_TO_BINARY('Hello', 'UTF-8')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"TRY(ENCODE('Hello'))\")\n\n        expr = self.validate_identity(\"TRY_TO_BINARY('SGVsbG8=', 'BASE64')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"TRY(FROM_BASE64('SGVsbG8='))\")\n\n        expr = self.validate_identity(\"TRY_TO_BINARY('Hello', 'UTF-16')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"NULL\")\n\n    def test_reverse(self):\n        # Test REVERSE with TO_BINARY (BLOB type) - UTF-8 format\n        expr = self.validate_identity(\"REVERSE(TO_BINARY('ABC', 'UTF-8'))\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"), \"CAST(REVERSE(CAST(ENCODE('ABC') AS TEXT)) AS BLOB)\"\n        )\n\n        # Test REVERSE with TO_BINARY - HEX format\n        expr = self.validate_identity(\"REVERSE(TO_BINARY('414243', 'HEX'))\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"CAST(REVERSE(CAST(UNHEX('414243') AS TEXT)) AS BLOB)\",\n        )\n\n        # Test REVERSE with HEX_DECODE_BINARY\n        expr = self.validate_identity(\"REVERSE(HEX_DECODE_BINARY('414243'))\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"CAST(REVERSE(CAST(UNHEX('414243') AS TEXT)) AS BLOB)\",\n        )\n\n        # Test REVERSE with VARCHAR (should not add casts)\n        expr = self.validate_identity(\"REVERSE('ABC')\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"REVERSE('ABC')\")\n\n    def test_float_interval(self):\n        # Test TIMEADD with float interval value - DuckDB INTERVAL requires integers\n        expr = self.validate_identity(\"TIMEADD(HOUR, 2.5, CAST('10:30:00' AS TIME))\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"CAST('10:30:00' AS TIME) + INTERVAL (CAST(ROUND(2.5) AS INT)) HOUR\",\n        )\n\n        # Test DATEADD with decimal interval value\n        expr = self.validate_identity(\n            \"DATEADD(HOUR, CAST(3.8 AS DECIMAL(10, 2)), CAST('2024-01-01 10:00:00' AS TIMESTAMP))\"\n        )\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"CAST('2024-01-01 10:00:00' AS TIMESTAMP) + INTERVAL (CAST(ROUND(CAST(3.8 AS DECIMAL(10, 2))) AS INT)) HOUR\",\n        )\n\n        # Test TIMESTAMPADD with float interval value - Snowflake parser converts to DATEADD\n        expr = self.parse_one(\n            \"TIMESTAMPADD(MINUTE, 30.9, CAST('2024-01-01 10:00:00' AS TIMESTAMP))\",\n            dialect=\"snowflake\",\n        )\n        self.assertEqual(\n            expr.sql(\"snowflake\"), \"DATEADD(MINUTE, 30.9, CAST('2024-01-01 10:00:00' AS TIMESTAMP))\"\n        )\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"CAST('2024-01-01 10:00:00' AS TIMESTAMP) + INTERVAL (CAST(ROUND(30.9) AS INT)) MINUTE\",\n        )\n\n    def test_transpile_bitwise_ops(self):\n        # Binary bitwise operations\n        expr = self.parse_one(\"SELECT BITOR(x'FF', x'0F')\", dialect=\"snowflake\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"SELECT CAST(CAST(UNHEX('FF') AS BIT) | CAST(UNHEX('0F') AS BIT) AS BLOB)\",\n        )\n        self.assertEqual(annotated.sql(\"snowflake\"), \"SELECT BITOR(x'FF', x'0F')\")\n\n        expr = self.parse_one(\"SELECT BITAND(x'FF', x'0F')\", dialect=\"snowflake\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"SELECT CAST(CAST(UNHEX('FF') AS BIT) & CAST(UNHEX('0F') AS BIT) AS BLOB)\",\n        )\n        self.assertEqual(annotated.sql(\"snowflake\"), \"SELECT BITAND(x'FF', x'0F')\")\n\n        expr = self.parse_one(\"SELECT BITXOR(x'FF', x'0F')\", dialect=\"snowflake\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(\n            annotated.sql(\"duckdb\"),\n            \"SELECT CAST(XOR(CAST(UNHEX('FF') AS BIT), CAST(UNHEX('0F') AS BIT)) AS BLOB)\",\n        )\n        self.assertEqual(annotated.sql(\"snowflake\"), \"SELECT BITXOR(x'FF', x'0F')\")\n\n        expr = self.parse_one(\"SELECT BITNOT(x'FF')\", dialect=\"snowflake\")\n        annotated = annotate_types(expr, dialect=\"snowflake\")\n        self.assertEqual(annotated.sql(\"duckdb\"), \"SELECT CAST(~CAST(UNHEX('FF') AS BIT) AS BLOB)\")\n        self.assertEqual(annotated.sql(\"snowflake\"), \"SELECT BITNOT(x'FF')\")\n\n    def test_quoting(self):\n        self.assertEqual(\n            parse_one(\"select a, B from DUAL\", dialect=\"snowflake\").sql(\n                dialect=\"snowflake\", identify=\"safe\"\n            ),\n            'SELECT a, \"B\" FROM DUAL',\n        )\n\n    def test_floor(self):\n        self.validate_all(\n            \"SELECT FLOOR(1.753, 2)\",\n            write={\"duckdb\": \"SELECT ROUND(FLOOR(1.753 * POWER(10, 2)) / POWER(10, 2), 2)\"},\n        )\n        self.validate_all(\n            \"SELECT FLOOR(123.45, -1)\",\n            write={\"duckdb\": \"SELECT ROUND(FLOOR(123.45 * POWER(10, -1)) / POWER(10, -1), -1)\"},\n        )\n        self.validate_all(\n            \"SELECT FLOOR(a + b, 2)\",\n            write={\"duckdb\": \"SELECT ROUND(FLOOR((a + b) * POWER(10, 2)) / POWER(10, 2), 2)\"},\n        )\n        self.validate_all(\n            \"SELECT FLOOR(1.234, 1.5)\",\n            write={\n                \"duckdb\": \"SELECT ROUND(FLOOR(1.234 * POWER(10, CAST(1.5 AS INT))) / POWER(10, CAST(1.5 AS INT)), CAST(1.5 AS INT))\"\n            },\n        )\n\n    def test_seq_functions(self):\n        # SEQ1 - 1-byte sequences\n        self.validate_all(\n            \"SELECT SEQ1() FROM test\",\n            write={\n                \"duckdb\": \"SELECT (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 256 FROM test\",\n                \"snowflake\": \"SELECT SEQ1() FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SEQ1(0) FROM test\",\n            write={\n                \"duckdb\": \"SELECT (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 256 FROM test\",\n                \"snowflake\": \"SELECT SEQ1(0) FROM test\",\n            },\n        )\n        # 1 means it's signed parameter, which affects wrap-around behavior\n        self.validate_all(\n            \"SELECT SEQ1(1) FROM test\",\n            write={\n                \"duckdb\": \"SELECT (CASE WHEN (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 256 >= 128 THEN (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 256 - 256 ELSE (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 256 END) FROM test\",\n                \"snowflake\": \"SELECT SEQ1(1) FROM test\",\n            },\n        )\n\n        # SEQ2 - 2-byte sequences\n        self.validate_all(\n            \"SELECT SEQ2() FROM test\",\n            write={\n                \"duckdb\": \"SELECT (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 65536 FROM test\",\n                \"snowflake\": \"SELECT SEQ2() FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SEQ2(0) FROM test\",\n            write={\n                \"duckdb\": \"SELECT (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 65536 FROM test\",\n                \"snowflake\": \"SELECT SEQ2(0) FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SEQ2(1) FROM test\",\n            write={\n                \"duckdb\": \"SELECT (CASE WHEN (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 65536 >= 32768 THEN (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 65536 - 65536 ELSE (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 65536 END) FROM test\",\n                \"snowflake\": \"SELECT SEQ2(1) FROM test\",\n            },\n        )\n\n        # SEQ4 - 4-byte sequences\n        self.validate_all(\n            \"SELECT SEQ4() FROM test\",\n            write={\n                \"duckdb\": \"SELECT (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 4294967296 FROM test\",\n                \"snowflake\": \"SELECT SEQ4() FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SEQ4(0) FROM test\",\n            write={\n                \"duckdb\": \"SELECT (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 4294967296 FROM test\",\n                \"snowflake\": \"SELECT SEQ4(0) FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SEQ4(1) FROM test\",\n            write={\n                \"duckdb\": \"SELECT (CASE WHEN (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 4294967296 >= 2147483648 THEN (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 4294967296 - 4294967296 ELSE (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 4294967296 END) FROM test\",\n                \"snowflake\": \"SELECT SEQ4(1) FROM test\",\n            },\n        )\n\n        # SEQ8 - 8-byte sequences\n        self.validate_all(\n            \"SELECT SEQ8() FROM test\",\n            write={\n                \"duckdb\": \"SELECT (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 18446744073709551616 FROM test\",\n                \"snowflake\": \"SELECT SEQ8() FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SEQ8(0) FROM test\",\n            write={\n                \"duckdb\": \"SELECT (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 18446744073709551616 FROM test\",\n                \"snowflake\": \"SELECT SEQ8(0) FROM test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SEQ8(1) FROM test\",\n            write={\n                \"duckdb\": \"SELECT (CASE WHEN (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 18446744073709551616 >= 9223372036854775808 THEN (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 18446744073709551616 - 18446744073709551616 ELSE (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % 18446744073709551616 END) FROM test\",\n                \"snowflake\": \"SELECT SEQ8(1) FROM test\",\n            },\n        )\n\n    def test_generator(self):\n        self.validate_identity(\"SELECT 1 FROM TABLE(GENERATOR(ROWCOUNT => 10))\")\n        self.validate_identity(\"SELECT 1 FROM TABLE(GENERATOR(TIMELIMIT => 5))\")\n        self.validate_identity(\"SELECT 1 FROM TABLE(GENERATOR(ROWCOUNT => 10, TIMELIMIT => 5))\")\n\n        # Positional args are mapped to ROWCOUNT, TIMELIMIT in order\n        self.validate_identity(\n            \"SELECT 1 FROM TABLE(GENERATOR(10))\",\n            \"SELECT 1 FROM TABLE(GENERATOR(ROWCOUNT => 10))\",\n        )\n        self.validate_identity(\n            \"SELECT 1 FROM TABLE(GENERATOR(10, 5))\",\n            \"SELECT 1 FROM TABLE(GENERATOR(ROWCOUNT => 10, TIMELIMIT => 5))\",\n        )\n\n        # Basic ROWCOUNT transpilation\n        self.validate_all(\n            \"SELECT 1 FROM TABLE(GENERATOR(ROWCOUNT => 5))\",\n            write={\n                \"duckdb\": \"SELECT 1 FROM RANGE(5)\",\n                \"snowflake\": \"SELECT 1 FROM TABLE(GENERATOR(ROWCOUNT => 5))\",\n            },\n        )\n\n        # GENERATOR with SEQ functions - the common use case\n        # SEQ is replaced with `range` column reference to avoid nested window function issues\n        self.validate_all(\n            \"SELECT SEQ8() FROM TABLE(GENERATOR(ROWCOUNT => 5))\",\n            write={\n                \"duckdb\": \"SELECT range % 18446744073709551616 FROM RANGE(5)\",\n                \"snowflake\": \"SELECT SEQ8() FROM TABLE(GENERATOR(ROWCOUNT => 5))\",\n            },\n        )\n\n        # GENERATOR with JOIN in parenthesized construct - preserves joins\n        self.validate_all(\n            \"SELECT * FROM (TABLE(GENERATOR(ROWCOUNT => 5)) JOIN other ON 1 = 1)\",\n            write={\n                \"duckdb\": \"SELECT * FROM (RANGE(5) JOIN other ON 1 = 1)\",\n                \"snowflake\": \"SELECT * FROM (TABLE(GENERATOR(ROWCOUNT => 5)) JOIN other ON 1 = 1)\",\n            },\n        )\n\n    def test_ceil(self):\n        self.validate_all(\n            \"SELECT CEIL(1.753, 2)\",\n            write={\"duckdb\": \"SELECT ROUND(CEIL(1.753 * POWER(10, 2)) / POWER(10, 2), 2)\"},\n        )\n        self.validate_all(\n            \"SELECT CEIL(123.45, -1)\",\n            write={\"duckdb\": \"SELECT ROUND(CEIL(123.45 * POWER(10, -1)) / POWER(10, -1), -1)\"},\n        )\n        self.validate_all(\n            \"SELECT CEIL(a + b, 2)\",\n            write={\"duckdb\": \"SELECT ROUND(CEIL((a + b) * POWER(10, 2)) / POWER(10, 2), 2)\"},\n        )\n        self.validate_all(\n            \"SELECT CEIL(1.234, 1.5)\",\n            write={\n                \"duckdb\": \"SELECT ROUND(CEIL(1.234 * POWER(10, CAST(1.5 AS INT))) / POWER(10, CAST(1.5 AS INT)), CAST(1.5 AS INT))\"\n            },\n        )\n\n    def test_corr(self):\n        self.validate_all(\n            \"SELECT CORR(a, b)\",\n            read={\n                \"snowflake\": \"SELECT CORR(a, b)\",\n                \"postgres\": \"SELECT CORR(a, b)\",\n            },\n            write={\n                \"snowflake\": \"SELECT CORR(a, b)\",\n                \"postgres\": \"SELECT CORR(a, b)\",\n                \"duckdb\": \"SELECT CASE WHEN ISNAN(CORR(a, b)) THEN NULL ELSE CORR(a, b) END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CORR(a, b) OVER (PARTITION BY c)\",\n            read={\n                \"snowflake\": \"SELECT CORR(a, b) OVER (PARTITION BY c)\",\n                \"postgres\": \"SELECT CORR(a, b) OVER (PARTITION BY c)\",\n            },\n            write={\n                \"snowflake\": \"SELECT CORR(a, b) OVER (PARTITION BY c)\",\n                \"postgres\": \"SELECT CORR(a, b) OVER (PARTITION BY c)\",\n                \"duckdb\": \"SELECT CASE WHEN ISNAN(CORR(a, b) OVER (PARTITION BY c)) THEN NULL ELSE CORR(a, b) OVER (PARTITION BY c) END\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT CORR(a, b) FILTER(WHERE c > 0)\",\n            write={\n                \"duckdb\": \"SELECT CASE WHEN ISNAN(CORR(a, b) FILTER(WHERE c > 0)) THEN NULL ELSE CORR(a, b) FILTER(WHERE c > 0) END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CORR(a, b) FILTER(WHERE c > 0) OVER (PARTITION BY d)\",\n            write={\n                \"duckdb\": \"SELECT CASE WHEN ISNAN(CORR(a, b) FILTER(WHERE c > 0) OVER (PARTITION BY d)) THEN NULL ELSE CORR(a, b) FILTER(WHERE c > 0) OVER (PARTITION BY d) END\",\n            },\n        )\n\n    def test_encryption_functions(self):\n        # ENCRYPT\n        self.validate_identity(\"ENCRYPT(value, 'passphrase')\")\n        self.validate_identity(\"ENCRYPT(value, 'passphrase', 'aad')\")\n        self.validate_identity(\"ENCRYPT(value, 'passphrase', 'aad', 'AES-GCM')\")\n\n        # ENCRYPT_RAW\n        self.validate_identity(\"ENCRYPT_RAW(value, key, iv)\")\n        self.validate_identity(\"ENCRYPT_RAW(value, key, iv, aad)\")\n        self.validate_identity(\"ENCRYPT_RAW(value, key, iv, aad, 'AES-GCM')\")\n\n        # DECRYPT\n        self.validate_identity(\"DECRYPT(encrypted, 'passphrase')\")\n        self.validate_identity(\"DECRYPT(encrypted, 'passphrase', 'aad')\")\n        self.validate_identity(\"DECRYPT(encrypted, 'passphrase', 'aad', 'AES-GCM')\")\n\n        # DECRYPT_RAW\n        self.validate_identity(\"DECRYPT_RAW(encrypted, key, iv)\")\n        self.validate_identity(\"DECRYPT_RAW(encrypted, key, iv, aad)\")\n        self.validate_identity(\"DECRYPT_RAW(encrypted, key, iv, aad, 'AES-GCM')\")\n        self.validate_identity(\"DECRYPT_RAW(encrypted, key, iv, aad, 'AES-GCM', aead)\")\n\n        # TRY_DECRYPT (parses as Decrypt with safe=True)\n        self.validate_identity(\"TRY_DECRYPT(encrypted, 'passphrase')\")\n        self.validate_identity(\"TRY_DECRYPT(encrypted, 'passphrase', 'aad')\")\n        self.validate_identity(\"TRY_DECRYPT(encrypted, 'passphrase', 'aad', 'AES-GCM')\")\n\n        # TRY_DECRYPT_RAW (parses as DecryptRaw with safe=True)\n        self.validate_identity(\"TRY_DECRYPT_RAW(encrypted, key, iv)\")\n        self.validate_identity(\"TRY_DECRYPT_RAW(encrypted, key, iv, aad)\")\n        self.validate_identity(\"TRY_DECRYPT_RAW(encrypted, key, iv, aad, 'AES-GCM')\")\n        self.validate_identity(\"TRY_DECRYPT_RAW(encrypted, key, iv, aad, 'AES-GCM', aead)\")\n\n    def test_update_statement(self):\n        self.validate_identity(\"UPDATE test SET t = 1 FROM t1\")\n        self.validate_identity(\"UPDATE test SET t = 1 FROM t2 JOIN t3 ON t2.id = t3.id\")\n        self.validate_identity(\n            \"UPDATE test SET t = 1 FROM (SELECT id FROM test2) AS t2 JOIN test3 AS t3 ON t2.id = t3.id\"\n        )\n\n        self.validate_identity(\n            \"UPDATE sometesttable u FROM (SELECT 5195 AS new_count, '01bee1e5-0000-d31e-0000-e80ef02b9f27' query_id ) b SET qry_hash_count = new_count WHERE u.sample_query_id  = b.query_id\",\n            \"UPDATE sometesttable AS u SET qry_hash_count = new_count FROM (SELECT 5195 AS new_count, '01bee1e5-0000-d31e-0000-e80ef02b9f27' AS query_id) AS b WHERE u.sample_query_id = b.query_id\",\n        )\n\n    def test_type_sensitive_bitshift_transpilation(self):\n        ast = annotate_types(self.parse_one(\"SELECT BITSHIFTLEFT(X'FF', 4)\"), dialect=\"snowflake\")\n        self.assertEqual(ast.sql(\"duckdb\"), \"SELECT CAST(CAST(UNHEX('FF') AS BIT) << 4 AS BLOB)\")\n\n        ast = annotate_types(self.parse_one(\"SELECT BITSHIFTRIGHT(X'FF', 4)\"), dialect=\"snowflake\")\n        self.assertEqual(ast.sql(\"duckdb\"), \"SELECT CAST(CAST(UNHEX('FF') AS BIT) >> 4 AS BLOB)\")\n\n    def test_array_flatten(self):\n        # String array flattening\n        self.validate_all(\n            \"SELECT ARRAY_FLATTEN([['a', 'b'], ['c', 'd', 'e']])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_FLATTEN([['a', 'b'], ['c', 'd', 'e']])\",\n                \"duckdb\": \"SELECT FLATTEN([['a', 'b'], ['c', 'd', 'e']])\",\n                \"starrocks\": \"SELECT ARRAY_FLATTEN([['a', 'b'], ['c', 'd', 'e']])\",\n            },\n        )\n\n        # Nested arrays (single level flattening)\n        self.validate_all(\n            \"SELECT ARRAY_FLATTEN([[[1, 2], [3]], [[4], [5]]])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_FLATTEN([[[1, 2], [3]], [[4], [5]]])\",\n                \"duckdb\": \"SELECT FLATTEN([[[1, 2], [3]], [[4], [5]]])\",\n            },\n        )\n\n        # Array with NULL elements\n        self.validate_all(\n            \"SELECT ARRAY_FLATTEN([[1, NULL, 3], [4]])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_FLATTEN([[1, NULL, 3], [4]])\",\n                \"duckdb\": \"SELECT FLATTEN([[1, NULL, 3], [4]])\",\n            },\n        )\n\n        # Empty arrays\n        self.validate_all(\n            \"SELECT ARRAY_FLATTEN([[]])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_FLATTEN([[]])\",\n                \"duckdb\": \"SELECT FLATTEN([[]])\",\n            },\n        )\n\n    def test_array_except(self):\n        self.validate_all(\n            \"SELECT ARRAY_EXCEPT([1, 2, 3], [2])\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_EXCEPT([1, 2, 3], [2])\",\n                \"duckdb\": \"SELECT CASE WHEN [1, 2, 3] IS NULL OR [2] IS NULL THEN NULL ELSE LIST_TRANSFORM(LIST_FILTER(LIST_ZIP([1, 2, 3], GENERATE_SERIES(1, LENGTH([1, 2, 3]))), pair -> (LENGTH(LIST_FILTER([1, 2, 3][1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1])) > LENGTH(LIST_FILTER([2], e -> e IS NOT DISTINCT FROM pair[1])))), pair -> pair[1]) END\",\n            },\n        )\n\n    def test_array_position(self):\n        self.validate_all(\n            \"SELECT ARRAY_POSITION(2, ARRAY_CONSTRUCT(1, 2, 3))\",\n            write={\n                \"snowflake\": \"SELECT ARRAY_POSITION(2, [1, 2, 3])\",\n                \"duckdb\": \"SELECT ARRAY_POSITION([1, 2, 3], 2) - 1\",\n            },\n        )\n\n    def test_array_slice(self):\n        self.validate_all(\n            \"ARRAY_SLICE(arr, s, e)\",\n            write={\n                \"snowflake\": \"ARRAY_SLICE(arr, s, e)\",\n                \"duckdb\": \"ARRAY_SLICE(arr, CASE WHEN s >= 0 THEN s + 1 ELSE s END, CASE WHEN e < 0 THEN e - 1 ELSE e END)\",\n            },\n        )\n\n    def test_space(self):\n        # Integer literal\n        self.validate_all(\n            \"SELECT SPACE(5)\",\n            write={\n                \"snowflake\": \"SELECT REPEAT(' ', 5)\",\n                \"duckdb\": \"SELECT REPEAT(' ', CAST(5 AS BIGINT))\",\n            },\n        )\n\n        # Float literal (tests rounding behavior)\n        self.validate_all(\n            \"SELECT SPACE(3.7)\",\n            write={\n                \"snowflake\": \"SELECT REPEAT(' ', 3.7)\",\n                \"duckdb\": \"SELECT REPEAT(' ', CAST(3.7 AS BIGINT))\",\n            },\n        )\n\n        # NULL value\n        self.validate_all(\n            \"SELECT SPACE(NULL)\",\n            write={\n                \"snowflake\": \"SELECT REPEAT(' ', NULL)\",\n                \"duckdb\": \"SELECT REPEAT(' ', CAST(NULL AS BIGINT))\",\n            },\n        )\n\n    def test_charindex(self):\n        self.validate_all(\n            \"SELECT CHARINDEX('sub', 'testsubstring', -1)\",\n            write={\n                \"snowflake\": \"SELECT CHARINDEX('sub', 'testsubstring', -1)\",\n                \"duckdb\": \"SELECT CASE WHEN STRPOS(SUBSTRING('testsubstring', CASE WHEN -1 <= 0 THEN 1 ELSE -1 END), 'sub') = 0 THEN 0 ELSE STRPOS(SUBSTRING('testsubstring', CASE WHEN -1 <= 0 THEN 1 ELSE -1 END), 'sub') + CASE WHEN -1 <= 0 THEN 1 ELSE -1 END - 1 END\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CHARINDEX('sub', 'testsubstring', p)\",\n            write={\n                \"snowflake\": \"SELECT CHARINDEX('sub', 'testsubstring', p)\",\n                \"duckdb\": \"SELECT CASE WHEN STRPOS(SUBSTRING('testsubstring', CASE WHEN p <= 0 THEN 1 ELSE p END), 'sub') = 0 THEN 0 ELSE STRPOS(SUBSTRING('testsubstring', CASE WHEN p <= 0 THEN 1 ELSE p END), 'sub') + CASE WHEN p <= 0 THEN 1 ELSE p END - 1 END\",\n            },\n        )\n\n    def test_directed_joins(self):\n        self.validate_identity(\"SELECT * FROM a CROSS DIRECTED JOIN b USING (id)\")\n        self.validate_identity(\"SELECT * FROM a INNER DIRECTED JOIN b USING (id)\")\n        self.validate_identity(\"SELECT * FROM a NATURAL INNER DIRECTED JOIN b USING (id)\")\n\n        for join_side in (\"LEFT\", \"RIGHT\", \"FULL\"):\n            for outer in (\"\", \" OUTER\"):\n                for natural in (\"\", \"NATURAL \"):\n                    prefix = natural + join_side + outer + \" DIRECTED\"\n                    with self.subTest(f\"Testing {prefix} JOIN\"):\n                        self.validate_identity(f\"SELECT * FROM a {prefix} JOIN b USING (id)\")\n"
  },
  {
    "path": "tests/dialects/test_solr.py",
    "content": "from sqlglot import exp\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestSolr(Validator):\n    dialect = \"solr\"\n\n    def test_solr(self):\n        self.validate_identity(\"SELECT `default`.column FROM t\")\n        self.failureException('SELECT \"column\" FROM t')\n        self.validate_identity(\"SELECT column FROM t WHERE column = 'val'\")\n        self.validate_identity(\"a || b\", \"a OR b\").assert_is(exp.Or)\n"
  },
  {
    "path": "tests/dialects/test_spark.py",
    "content": "from unittest import mock\n\nfrom sqlglot import exp, parse_one\nfrom sqlglot.dialects.dialect import Dialects\nfrom sqlglot.errors import UnsupportedError\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestSpark(Validator):\n    dialect = \"spark\"\n\n    def test_ddl(self):\n        self.validate_identity(\"DAYOFWEEK(TO_DATE(x))\")\n        self.validate_identity(\"DAYOFMONTH(TO_DATE(x))\")\n        self.validate_identity(\"DAYOFYEAR(TO_DATE(x))\")\n        self.validate_identity(\"WEEKOFYEAR(TO_DATE(x))\")\n        self.validate_identity(\"SELECT MODE(category)\")\n        self.validate_identity(\"SELECT MODE(price, TRUE) AS deterministic_mode FROM products\")\n        self.validate_identity(\"SELECT MODE() WITHIN GROUP (ORDER BY status) FROM orders\")\n        self.validate_identity(\"DROP NAMESPACE my_catalog.my_namespace\")\n        self.validate_identity(\"CREATE NAMESPACE my_catalog.my_namespace\")\n        self.validate_identity(\"INSERT OVERWRITE TABLE db1.tb1 TABLE db2.tb2\")\n        self.validate_identity(\"CREATE TABLE foo AS WITH t AS (SELECT 1 AS col) SELECT col FROM t\")\n        self.validate_identity(\"CREATE TEMPORARY VIEW test AS SELECT 1\")\n        self.validate_identity(\"CREATE TABLE foo (col VARCHAR(50))\")\n        self.validate_identity(\"CREATE TABLE foo (col STRUCT<struct_col_a: VARCHAR((50))>)\")\n        self.validate_identity(\"CREATE TABLE foo (col STRING) CLUSTERED BY (col) INTO 10 BUCKETS\")\n        self.validate_identity(\n            \"CREATE TABLE foo (col STRING) CLUSTERED BY (col) SORTED BY (col) INTO 10 BUCKETS\"\n        )\n        self.validate_identity(\"TRUNCATE TABLE t1 PARTITION(age = 10, name = 'test', address)\")\n\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a struct<struct_col_a:int, struct_col_b:string>)\",\n            write={\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a STRUCT(struct_col_a INT, struct_col_b TEXT))\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b VARCHAR))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRING>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRING>)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a struct<struct_col_a:int, struct_col_b:struct<nested_col_a:string, nested_col_b:string>>)\",\n            write={\n                \"bigquery\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a INT64, struct_col_b STRUCT<nested_col_a STRING, nested_col_b STRING>>)\",\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a STRUCT(struct_col_a INT, struct_col_b STRUCT(nested_col_a TEXT, nested_col_b TEXT)))\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ROW(struct_col_a INTEGER, struct_col_b ROW(nested_col_a VARCHAR, nested_col_b VARCHAR)))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRUCT<nested_col_a: STRING, nested_col_b: STRING>>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a STRUCT<struct_col_a: INT, struct_col_b: STRUCT<nested_col_a: STRING, nested_col_b: STRING>>)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE db.example_table (col_a array<int>, col_b array<array<int>>)\",\n            write={\n                \"bigquery\": \"CREATE TABLE db.example_table (col_a ARRAY<INT64>, col_b ARRAY<ARRAY<INT64>>)\",\n                \"duckdb\": \"CREATE TABLE db.example_table (col_a INT[], col_b INT[][])\",\n                \"presto\": \"CREATE TABLE db.example_table (col_a ARRAY(INTEGER), col_b ARRAY(ARRAY(INTEGER)))\",\n                \"hive\": \"CREATE TABLE db.example_table (col_a ARRAY<INT>, col_b ARRAY<ARRAY<INT>>)\",\n                \"spark\": \"CREATE TABLE db.example_table (col_a ARRAY<INT>, col_b ARRAY<ARRAY<INT>>)\",\n                \"snowflake\": \"CREATE TABLE db.example_table (col_a ARRAY, col_b ARRAY)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE x USING ICEBERG PARTITIONED BY (MONTHS(y)) LOCATION 's3://z'\",\n            write={\n                \"duckdb\": \"CREATE TABLE x\",\n                \"presto\": \"CREATE TABLE x WITH (format='ICEBERG', PARTITIONED_BY=ARRAY['MONTHS(y)'])\",\n                \"hive\": \"CREATE TABLE x STORED AS ICEBERG PARTITIONED BY (MONTHS(y)) LOCATION 's3://z'\",\n                \"spark\": \"CREATE TABLE x USING ICEBERG PARTITIONED BY (MONTHS(y)) LOCATION 's3://z'\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE test STORED AS PARQUET AS SELECT 1\",\n            write={\n                \"duckdb\": \"CREATE TABLE test AS SELECT 1\",\n                \"presto\": \"CREATE TABLE test WITH (format='PARQUET') AS SELECT 1\",\n                \"trino\": \"CREATE TABLE test WITH (format='PARQUET') AS SELECT 1\",\n                \"athena\": \"CREATE TABLE test WITH (format='PARQUET') AS SELECT 1\",  # note: lowercase format property is important for Athena\n                \"hive\": \"CREATE TABLE test STORED AS PARQUET AS SELECT 1\",\n                \"spark\": \"CREATE TABLE test STORED AS PARQUET AS SELECT 1\",\n            },\n        )\n        self.validate_all(\n            \"\"\"CREATE TABLE blah (col_a INT) COMMENT \"Test comment: blah\" PARTITIONED BY (date STRING) USING ICEBERG TBLPROPERTIES('x' = '1')\"\"\",\n            write={\n                \"duckdb\": \"\"\"CREATE TABLE blah (\n  col_a INT\n)\"\"\",  # Partition columns should exist in table\n                \"presto\": \"\"\"CREATE TABLE blah (\n  col_a INTEGER,\n  date VARCHAR\n)\nCOMMENT 'Test comment: blah'\nWITH (\n  PARTITIONED_BY=ARRAY['date'],\n  format='ICEBERG',\n  x='1'\n)\"\"\",\n                \"hive\": \"\"\"CREATE TABLE blah (\n  col_a INT\n)\nCOMMENT 'Test comment: blah'\nPARTITIONED BY (\n  date STRING\n)\nSTORED AS ICEBERG\nTBLPROPERTIES (\n  'x'='1'\n)\"\"\",\n                \"spark\": \"\"\"CREATE TABLE blah (\n  col_a INT,\n  date STRING\n)\nCOMMENT 'Test comment: blah'\nPARTITIONED BY (\n  date\n)\nUSING ICEBERG\nTBLPROPERTIES (\n  'x'='1'\n)\"\"\",\n            },\n            pretty=True,\n        )\n\n        self.validate_all(\n            \"CACHE TABLE testCache OPTIONS ('storageLevel' 'DISK_ONLY') SELECT * FROM testData\",\n            write={\n                \"spark\": \"CACHE TABLE testCache OPTIONS('storageLevel' = 'DISK_ONLY') AS SELECT * FROM testData\"\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE StudentInfo ADD COLUMNS (LastName STRING, DOB TIMESTAMP)\",\n            write={\n                \"spark\": \"ALTER TABLE StudentInfo ADD COLUMNS (LastName STRING, DOB TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE db.example ALTER COLUMN col_a TYPE BIGINT\",\n            write={\n                \"spark\": \"ALTER TABLE db.example ALTER COLUMN col_a TYPE BIGINT\",\n                \"hive\": \"ALTER TABLE db.example CHANGE COLUMN col_a col_a BIGINT\",\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE db.example CHANGE COLUMN col_a col_a BIGINT\",\n            write={\n                \"spark\": \"ALTER TABLE db.example ALTER COLUMN col_a TYPE BIGINT\",\n                \"hive\": \"ALTER TABLE db.example CHANGE COLUMN col_a col_a BIGINT\",\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE db.example RENAME COLUMN col_a TO col_b\",\n            write={\n                \"spark\": \"ALTER TABLE db.example RENAME COLUMN col_a TO col_b\",\n                \"hive\": UnsupportedError,\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE StudentInfo DROP COLUMNS (LastName, DOB)\",\n            write={\n                \"spark\": \"ALTER TABLE StudentInfo DROP COLUMNS (LastName, DOB)\",\n            },\n        )\n        self.validate_identity(\"ALTER VIEW StudentInfoView AS SELECT * FROM StudentInfo\")\n        self.validate_identity(\"ALTER VIEW StudentInfoView AS SELECT LastName FROM StudentInfo\")\n        self.validate_identity(\"ALTER VIEW StudentInfoView RENAME TO StudentInfoViewRenamed\")\n        self.validate_identity(\n            \"ALTER VIEW StudentInfoView SET TBLPROPERTIES ('key1'='val1', 'key2'='val2')\"\n        )\n        self.validate_identity(\n            \"ALTER VIEW StudentInfoView UNSET TBLPROPERTIES ('key1', 'key2')\",\n            check_command_warning=True,\n        )\n\n    def test_to_date(self):\n        self.validate_all(\n            \"TO_DATE(x, 'yyyy-MM-dd')\",\n            write={\n                \"duckdb\": \"TRY_CAST(x AS DATE)\",\n                \"hive\": \"TO_DATE(x)\",\n                \"presto\": \"CAST(CAST(x AS TIMESTAMP) AS DATE)\",\n                \"spark\": \"TO_DATE(x)\",\n                \"snowflake\": \"TRY_TO_DATE(x, 'yyyy-mm-DD')\",\n                \"databricks\": \"TO_DATE(x)\",\n            },\n        )\n        self.validate_all(\n            \"TO_DATE(x, 'yyyy')\",\n            write={\n                \"duckdb\": \"CAST(CAST(TRY_STRPTIME(x, '%Y') AS TIMESTAMP) AS DATE)\",\n                \"hive\": \"TO_DATE(x, 'yyyy')\",\n                \"presto\": \"CAST(DATE_PARSE(x, '%Y') AS DATE)\",\n                \"spark\": \"TO_DATE(x, 'yyyy')\",\n                \"snowflake\": \"TRY_TO_DATE(x, 'yyyy')\",\n                \"databricks\": \"TO_DATE(x, 'yyyy')\",\n            },\n        )\n\n    @mock.patch(\"sqlglot.generator.logger\")\n    def test_hint(self, logger):\n        self.validate_all(\n            \"SELECT /*+ COALESCE(3) */ * FROM x\",\n            write={\n                \"spark\": \"SELECT /*+ COALESCE(3) */ * FROM x\",\n                \"bigquery\": \"SELECT * FROM x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ COALESCE(3), REPARTITION(1) */ * FROM x\",\n            write={\n                \"spark\": \"SELECT /*+ COALESCE(3), REPARTITION(1) */ * FROM x\",\n                \"bigquery\": \"SELECT * FROM x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ BROADCAST(table) */ cola FROM table\",\n            write={\n                \"spark\": \"SELECT /*+ BROADCAST(table) */ cola FROM table\",\n                \"bigquery\": \"SELECT cola FROM table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ BROADCASTJOIN(table) */ cola FROM table\",\n            write={\n                \"spark\": \"SELECT /*+ BROADCASTJOIN(table) */ cola FROM table\",\n                \"bigquery\": \"SELECT cola FROM table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ MAPJOIN(table) */ cola FROM table\",\n            write={\n                \"spark\": \"SELECT /*+ MAPJOIN(table) */ cola FROM table\",\n                \"bigquery\": \"SELECT cola FROM table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ MERGE(table) */ cola FROM table\",\n            write={\n                \"spark\": \"SELECT /*+ MERGE(table) */ cola FROM table\",\n                \"bigquery\": \"SELECT cola FROM table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ SHUFFLEMERGE(table) */ cola FROM table\",\n            write={\n                \"spark\": \"SELECT /*+ SHUFFLEMERGE(table) */ cola FROM table\",\n                \"bigquery\": \"SELECT cola FROM table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ MERGEJOIN(table) */ cola FROM table\",\n            write={\n                \"spark\": \"SELECT /*+ MERGEJOIN(table) */ cola FROM table\",\n                \"bigquery\": \"SELECT cola FROM table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ SHUFFLE_HASH(table) */ cola FROM table\",\n            write={\n                \"spark\": \"SELECT /*+ SHUFFLE_HASH(table) */ cola FROM table\",\n                \"bigquery\": \"SELECT cola FROM table\",\n            },\n        )\n        self.validate_all(\n            \"SELECT /*+ SHUFFLE_REPLICATE_NL(table) */ cola FROM table\",\n            write={\n                \"spark\": \"SELECT /*+ SHUFFLE_REPLICATE_NL(table) */ cola FROM table\",\n                \"bigquery\": \"SELECT cola FROM table\",\n            },\n        )\n\n    def test_spark(self):\n        self.assertEqual(\n            parse_one(\"REFRESH TABLE t\", read=\"spark\").assert_is(exp.Refresh).sql(dialect=\"spark\"),\n            \"REFRESH TABLE t\",\n        )\n\n        # Spark TRUNC is date-only, should parse to DateTrunc (not numeric Trunc)\n        self.validate_identity(\"TRUNC(date_col, 'MM')\").assert_is(exp.DateTrunc)\n\n        # Numeric TRUNC from other dialects - Spark has no native support, uses CAST to BIGINT\n        self.validate_all(\n            \"CAST(3.14159 AS BIGINT)\",\n            read={\"postgres\": \"TRUNC(3.14159, 2)\"},\n        )\n\n        self.validate_identity(\"SELECT APPROX_TOP_K_ACCUMULATE(col, 10)\")\n        self.validate_identity(\"SELECT APPROX_TOP_K_ACCUMULATE(col)\")\n        self.validate_identity(\"SELECT BITMAP_BIT_POSITION(10)\")\n        self.validate_identity(\"SELECT BITMAP_CONSTRUCT_AGG(value)\")\n        self.validate_identity(\"ALTER TABLE foo ADD PARTITION(event = 'click')\")\n        self.validate_identity(\"ALTER TABLE foo ADD IF NOT EXISTS PARTITION(event = 'click')\")\n        self.validate_identity(\"IF(cond, foo AS bar, bla AS baz)\")\n        self.validate_identity(\"any_value(col, true)\", \"ANY_VALUE(col) IGNORE NULLS\")\n        self.validate_identity(\"first(col, true)\", \"FIRST(col) IGNORE NULLS\")\n        self.validate_identity(\"first_value(col, true)\", \"FIRST_VALUE(col) IGNORE NULLS\")\n        self.validate_identity(\"last(col, true)\", \"LAST(col) IGNORE NULLS\")\n        self.validate_identity(\"last_value(col, true)\", \"LAST_VALUE(col) IGNORE NULLS\")\n        self.validate_identity(\"DESCRIBE EXTENDED db.tbl\")\n        self.validate_identity(\"SELECT * FROM test TABLESAMPLE (50 PERCENT)\")\n        self.validate_identity(\"SELECT * FROM test TABLESAMPLE (5 ROWS)\")\n        self.validate_identity(\"SELECT * FROM test TABLESAMPLE (BUCKET 4 OUT OF 10)\")\n        self.validate_identity(\"REFRESH 'hdfs://path/to/table'\")\n        self.validate_identity(\"REFRESH TABLE tempDB.view1\")\n        self.validate_identity(\"SELECT CASE WHEN a = NULL THEN 1 ELSE 2 END\")\n        self.validate_identity(\"SELECT * FROM t1 SEMI JOIN t2 ON t1.x = t2.x\")\n        self.validate_identity(\"SELECT TRANSFORM(ARRAY(1, 2, 3), x -> x + 1)\")\n        self.validate_identity(\"SELECT TRANSFORM(ARRAY(1, 2, 3), (x, i) -> x + i)\")\n        self.validate_identity(\"REFRESH TABLE a.b.c\")\n        self.validate_identity(\"INTERVAL '-86' DAYS\")\n        self.validate_identity(\"TRIM('    SparkSQL   ')\")\n        self.validate_identity(\"TRIM(BOTH 'SL' FROM 'SSparkSQLS')\")\n        self.validate_identity(\"TRIM(LEADING 'SL' FROM 'SSparkSQLS')\")\n        self.validate_identity(\"TRIM(TRAILING 'SL' FROM 'SSparkSQLS')\")\n        self.validate_identity(\"SPLIT(str, pattern, lim)\")\n        self.validate_identity(\n            \"SELECT * FROM t1, t2\",\n            \"SELECT * FROM t1 CROSS JOIN t2\",\n        )\n        self.validate_identity(\n            \"SELECT 1 limit\",\n            \"SELECT 1 AS limit\",\n        )\n        self.validate_identity(\n            \"SELECT 1 offset\",\n            \"SELECT 1 AS offset\",\n        )\n        self.validate_identity(\n            \"SELECT UNIX_TIMESTAMP()\",\n            \"SELECT UNIX_TIMESTAMP(CURRENT_TIMESTAMP())\",\n        )\n        self.validate_identity(\n            \"SELECT CAST('2023-01-01' AS TIMESTAMP) + INTERVAL 23 HOUR + 59 MINUTE + 59 SECONDS\",\n            \"SELECT CAST('2023-01-01' AS TIMESTAMP) + INTERVAL '23' HOUR + INTERVAL '59' MINUTE + INTERVAL '59' SECONDS\",\n        )\n        self.validate_identity(\n            \"SELECT CAST('2023-01-01' AS TIMESTAMP) + INTERVAL '23' HOUR + '59' MINUTE + '59' SECONDS\",\n            \"SELECT CAST('2023-01-01' AS TIMESTAMP) + INTERVAL '23' HOUR + INTERVAL '59' MINUTE + INTERVAL '59' SECONDS\",\n        )\n        self.validate_identity(\n            \"SELECT INTERVAL '5' HOURS '30' MINUTES '5' SECONDS '6' MILLISECONDS '7' MICROSECONDS\",\n            \"SELECT INTERVAL '5' HOURS + INTERVAL '30' MINUTES + INTERVAL '5' SECONDS + INTERVAL '6' MILLISECONDS + INTERVAL '7' MICROSECONDS\",\n        )\n        self.validate_identity(\n            \"SELECT INTERVAL 5 HOURS 30 MINUTES 5 SECONDS 6 MILLISECONDS 7 MICROSECONDS\",\n            \"SELECT INTERVAL '5' HOURS + INTERVAL '30' MINUTES + INTERVAL '5' SECONDS + INTERVAL '6' MILLISECONDS + INTERVAL '7' MICROSECONDS\",\n        )\n        self.validate_identity(\n            \"SELECT REGEXP_REPLACE('100-200', r'([^0-9])', '')\",\n            \"SELECT REGEXP_REPLACE('100-200', '([^0-9])', '')\",\n        )\n        self.validate_identity(\n            \"SELECT REGEXP_REPLACE('100-200', R'([^0-9])', '')\",\n            \"SELECT REGEXP_REPLACE('100-200', '([^0-9])', '')\",\n        )\n        self.validate_identity(\n            \"SELECT STR_TO_MAP('a:1,b:2,c:3')\",\n            \"SELECT STR_TO_MAP('a:1,b:2,c:3', ',', ':')\",\n        )\n\n        self.validate_all(\n            \"SELECT * FROM parquet.`name.parquet`\",\n            read={\n                \"duckdb\": \"SELECT * FROM READ_PARQUET('name.parquet')\",\n                \"spark\": \"SELECT * FROM parquet.`name.parquet`\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_JSON(STRUCT('blah' AS x)) AS y\",\n            write={\n                \"presto\": \"SELECT JSON_FORMAT(CAST(CAST(ROW('blah') AS ROW(x VARCHAR)) AS JSON)) AS y\",\n                \"spark\": \"SELECT TO_JSON(STRUCT('blah' AS x)) AS y\",\n                \"trino\": \"SELECT JSON_FORMAT(CAST(CAST(ROW('blah') AS ROW(x VARCHAR)) AS JSON)) AS y\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRY_ELEMENT_AT(ARRAY(1, 2, 3), 2)\",\n            read={\n                \"databricks\": \"SELECT TRY_ELEMENT_AT(ARRAY(1, 2, 3), 2)\",\n                \"presto\": \"SELECT ELEMENT_AT(ARRAY[1, 2, 3], 2)\",\n            },\n            write={\n                \"databricks\": \"SELECT TRY_ELEMENT_AT(ARRAY(1, 2, 3), 2)\",\n                \"spark\": \"SELECT TRY_ELEMENT_AT(ARRAY(1, 2, 3), 2)\",\n                \"duckdb\": \"SELECT [1, 2, 3][2]\",\n                \"duckdb, version=1.1.0\": \"SELECT ([1, 2, 3])[2]\",\n                \"presto\": \"SELECT ELEMENT_AT(ARRAY[1, 2, 3], 2)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT ELEMENT_AT(ARRAY(1, 2, 3), 1)\",\n            read={\n                \"spark2\": \"SELECT ELEMENT_AT(ARRAY(1, 2, 3), 1)\",\n                \"spark\": \"SELECT ELEMENT_AT(ARRAY(1, 2, 3), 1)\",\n                \"databricks\": \"SELECT ELEMENT_AT(ARRAY(1, 2, 3), 1)\",\n            },\n            write={\n                \"spark2\": \"SELECT ELEMENT_AT(ARRAY(1, 2, 3), 1)\",\n                \"databricks\": \"SELECT ELEMENT_AT(ARRAY(1, 2, 3), 1)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT h.id, amount FROM hourlycostagg h LATERAL VIEW inline(h.costs) c\",\n            write={\n                \"duckdb\": \"SELECT h.id, amount FROM hourlycostagg AS h CROSS JOIN LATERAL (SELECT UNNEST(h.costs, max_depth => 2)) AS c\",\n            },\n        )\n        self.validate_all(\n            \"SELECT h.id, amount FROM hourlycostagg h LATERAL VIEW inline(h.adjustments) as type, val, curr\",\n            write={\n                \"duckdb\": \"SELECT h.id, amount FROM hourlycostagg AS h CROSS JOIN LATERAL (SELECT UNNEST(h.adjustments, max_depth => 2)) AS _u_0(type, val, curr)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\n            WITH hourlycostagg AS (\n                SELECT\n                    101 AS id,\n                    ARRAY(\n                        STRUCT(10.0 AS amount, 'USD' AS currency),\n                        STRUCT(20.0 AS amount, 'EUR' AS currency)\n                    ) AS costs,\n                    ARRAY(\n                        STRUCT('tax' AS type, 0.15 AS val, 'EUR' AS currency),\n                        STRUCT('fee' AS type, 5.00 AS val, 'EUR' AS currency)\n                    ) AS adjustments,\n                    ARRAY(\n                        STRUCT(\n                            12.0 AS length,\n                            STRUCT('A' AS tag, 98.5 AS score) AS details\n                        ),\n                        STRUCT(\n                            23.0 AS length,\n                            STRUCT('B' AS tag, 99.5 AS score) AS details\n                        )\n                    ) AS info\n            )\n            SELECT\n                h.id,\n                amount,\n                currency,\n                type,\n                val,\n                leng\n            FROM hourlycostagg h\n            LATERAL VIEW inline(h.costs) c\n            LATERAL VIEW inline(h.adjustments) as type, val, curr\n            LATERAL VIEW inline(h.info) exploded as leng, det\n            \"\"\",\n            write={\n                \"duckdb\": \"WITH hourlycostagg AS (SELECT 101 AS id, [{'amount': 10.0, 'currency': 'USD'}, {'amount': 20.0, 'currency': 'EUR'}] AS costs, [{'type': 'tax', 'val': 0.15, 'currency': 'EUR'}, {'type': 'fee', 'val': 5.00, 'currency': 'EUR'}] AS adjustments, [{'length': 12.0, 'details': {'tag': 'A', 'score': 98.5}}, {'length': 23.0, 'details': {'tag': 'B', 'score': 99.5}}] AS info) SELECT h.id, amount, currency, type, val, leng FROM hourlycostagg AS h CROSS JOIN LATERAL (SELECT UNNEST(h.costs, max_depth => 2)) AS c CROSS JOIN LATERAL (SELECT UNNEST(h.adjustments, max_depth => 2)) AS _u_1(type, val, curr) CROSS JOIN LATERAL (SELECT UNNEST(h.info, max_depth => 2)) AS exploded(leng, det)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT id_column, name, age FROM test_table LATERAL VIEW INLINE(struc_column) explode_view AS name, age\",\n            write={\n                \"presto\": \"SELECT id_column, name, age FROM test_table CROSS JOIN UNNEST(struc_column) AS explode_view(name, age)\",\n                \"spark\": \"SELECT id_column, name, age FROM test_table LATERAL VIEW INLINE(struc_column) explode_view AS name, age\",\n                \"duckdb\": \"SELECT id_column, name, age FROM test_table CROSS JOIN LATERAL (SELECT UNNEST(struc_column, max_depth => 2)) AS explode_view(name, age)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_AGG(x) FILTER (WHERE x = 5) FROM (SELECT 1 UNION ALL SELECT NULL) AS t(x)\",\n            write={\n                \"duckdb\": \"SELECT ARRAY_AGG(x) FILTER(WHERE x = 5 AND NOT x IS NULL) FROM (SELECT 1 UNION ALL SELECT NULL) AS t(x)\",\n                \"spark\": \"SELECT COLLECT_LIST(x) FILTER(WHERE x = 5) FROM (SELECT 1 UNION ALL SELECT NULL) AS t(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_AGG(1)\",\n            write={\n                \"duckdb\": \"SELECT ARRAY_AGG(1)\",\n                \"spark\": \"SELECT COLLECT_LIST(1)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_AGG(DISTINCT STRUCT('a'))\",\n            write={\n                \"duckdb\": \"SELECT ARRAY_AGG(DISTINCT {'col1': 'a'})\",\n                \"spark\": \"SELECT COLLECT_LIST(DISTINCT STRUCT('a' AS col1))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_FORMAT(DATE '2020-01-01', 'EEEE') AS weekday\",\n            write={\n                \"presto\": \"SELECT DATE_FORMAT(CAST(CAST('2020-01-01' AS DATE) AS TIMESTAMP), '%W') AS weekday\",\n                \"spark\": \"SELECT DATE_FORMAT(CAST('2020-01-01' AS DATE), 'EEEE') AS weekday\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRY_ELEMENT_AT(MAP(1, 'a', 2, 'b'), 2)\",\n            read={\n                \"databricks\": \"SELECT TRY_ELEMENT_AT(MAP(1, 'a', 2, 'b'), 2)\",\n            },\n            write={\n                \"databricks\": \"SELECT TRY_ELEMENT_AT(MAP(1, 'a', 2, 'b'), 2)\",\n                \"duckdb\": \"SELECT MAP([1, 2], ['a', 'b'])[2]\",\n                \"duckdb, version=1.1.0\": \"SELECT (MAP([1, 2], ['a', 'b'])[2])[1]\",\n                \"spark\": \"SELECT TRY_ELEMENT_AT(MAP(1, 'a', 2, 'b'), 2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT SPLIT('123|789', '\\\\\\\\|')\",\n            read={\n                \"duckdb\": \"SELECT STR_SPLIT_REGEX('123|789', '\\\\|')\",\n                \"presto\": \"SELECT REGEXP_SPLIT('123|789', '\\\\|')\",\n            },\n            write={\n                \"duckdb\": \"SELECT STR_SPLIT_REGEX('123|789', '\\\\|')\",\n                \"presto\": \"SELECT REGEXP_SPLIT('123|789', '\\\\|')\",\n                \"spark\": \"SELECT SPLIT('123|789', '\\\\\\\\|')\",\n            },\n        )\n        self.validate_all(\n            \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT id, name) AS cnt FROM tbl\",\n            write={\n                \"clickhouse\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT id, name) AS cnt FROM tbl\",\n                \"databricks\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT id, name) AS cnt FROM tbl\",\n                \"doris\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS `name` UNION ALL SELECT NULL AS id, 'jake' AS `name`) SELECT COUNT(DISTINCT id, `name`) AS cnt FROM tbl\",\n                \"duckdb\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT CASE WHEN id IS NULL THEN NULL WHEN name IS NULL THEN NULL ELSE (id, name) END) AS cnt FROM tbl\",\n                \"hive\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT id, name) AS cnt FROM tbl\",\n                \"mysql\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT id, name) AS cnt FROM tbl\",\n                \"postgres\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT CASE WHEN id IS NULL THEN NULL WHEN name IS NULL THEN NULL ELSE (id, name) END) AS cnt FROM tbl\",\n                \"presto\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT CASE WHEN id IS NULL THEN NULL WHEN name IS NULL THEN NULL ELSE (id, name) END) AS cnt FROM tbl\",\n                \"snowflake\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT id, name) AS cnt FROM tbl\",\n                \"spark\": \"WITH tbl AS (SELECT 1 AS id, 'eggy' AS name UNION ALL SELECT NULL AS id, 'jake' AS name) SELECT COUNT(DISTINCT id, name) AS cnt FROM tbl\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_UTC_TIMESTAMP('2016-08-31', 'Asia/Seoul')\",\n            write={\n                \"bigquery\": \"SELECT DATETIME(TIMESTAMP(CAST('2016-08-31' AS DATETIME), 'Asia/Seoul'), 'UTC')\",\n                \"duckdb\": \"SELECT CAST('2016-08-31' AS TIMESTAMP) AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC'\",\n                \"postgres\": \"SELECT CAST('2016-08-31' AS TIMESTAMP) AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC'\",\n                \"presto\": \"SELECT WITH_TIMEZONE(CAST('2016-08-31' AS TIMESTAMP), 'Asia/Seoul') AT TIME ZONE 'UTC'\",\n                \"redshift\": \"SELECT CAST('2016-08-31' AS TIMESTAMP) AT TIME ZONE 'Asia/Seoul' AT TIME ZONE 'UTC'\",\n                \"snowflake\": \"SELECT CONVERT_TIMEZONE('Asia/Seoul', 'UTC', CAST('2016-08-31' AS TIMESTAMP))\",\n                \"spark\": \"SELECT TO_UTC_TIMESTAMP(CAST('2016-08-31' AS TIMESTAMP), 'Asia/Seoul')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FROM_UTC_TIMESTAMP('2016-08-31', 'Asia/Seoul')\",\n            write={\n                \"presto\": \"SELECT AT_TIMEZONE(CAST('2016-08-31' AS TIMESTAMP), 'Asia/Seoul')\",\n                \"spark\": \"SELECT FROM_UTC_TIMESTAMP(CAST('2016-08-31' AS TIMESTAMP), 'Asia/Seoul')\",\n            },\n        )\n        self.validate_all(\n            \"foo.bar\",\n            read={\n                \"\": \"STRUCT_EXTRACT(foo, bar)\",\n            },\n        )\n        self.validate_all(\n            \"MAP(1, 2, 3, 4)\",\n            write={\n                \"spark\": \"MAP(1, 2, 3, 4)\",\n                \"trino\": \"MAP(ARRAY[1, 3], ARRAY[2, 4])\",\n            },\n        )\n        self.validate_all(\n            \"MAP()\",\n            read={\n                \"spark\": \"MAP()\",\n                \"trino\": \"MAP()\",\n            },\n            write={\n                \"trino\": \"MAP(ARRAY[], ARRAY[])\",\n            },\n        )\n        self.validate_all(\n            \"SELECT STR_TO_MAP('a:1,b:2,c:3', ',', ':')\",\n            read={\n                \"presto\": \"SELECT SPLIT_TO_MAP('a:1,b:2,c:3', ',', ':')\",\n                \"spark\": \"SELECT STR_TO_MAP('a:1,b:2,c:3', ',', ':')\",\n            },\n            write={\n                \"presto\": \"SELECT SPLIT_TO_MAP('a:1,b:2,c:3', ',', ':')\",\n                \"spark\": \"SELECT STR_TO_MAP('a:1,b:2,c:3', ',', ':')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(MONTH, CAST('1996-10-30' AS TIMESTAMP), CAST('1997-02-28 10:30:00' AS TIMESTAMP))\",\n            read={\n                \"duckdb\": \"SELECT DATEDIFF('month', CAST('1996-10-30' AS TIMESTAMPTZ), CAST('1997-02-28 10:30:00' AS TIMESTAMPTZ))\",\n            },\n            write={\n                \"spark\": \"SELECT DATEDIFF(MONTH, CAST('1996-10-30' AS TIMESTAMP), CAST('1997-02-28 10:30:00' AS TIMESTAMP))\",\n                \"spark2\": \"SELECT CAST(MONTHS_BETWEEN(CAST('1997-02-28 10:30:00' AS TIMESTAMP), CAST('1996-10-30' AS TIMESTAMP)) AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(week, '2020-01-01', '2020-12-31')\",\n            write={\n                \"bigquery\": \"SELECT DATE_DIFF(CAST('2020-12-31' AS DATE), CAST('2020-01-01' AS DATE), WEEK)\",\n                \"duckdb\": \"SELECT DATE_DIFF('WEEK', CAST('2020-01-01' AS DATE), CAST('2020-12-31' AS DATE))\",\n                \"hive\": \"SELECT CAST(DATEDIFF('2020-12-31', '2020-01-01') / 7 AS INT)\",\n                \"postgres\": \"SELECT CAST(EXTRACT(days FROM (CAST(CAST('2020-12-31' AS DATE) AS TIMESTAMP) - CAST(CAST('2020-01-01' AS DATE) AS TIMESTAMP))) / 7 AS BIGINT)\",\n                \"redshift\": \"SELECT DATEDIFF(WEEK, CAST('2020-01-01' AS DATE), CAST('2020-12-31' AS DATE))\",\n                \"snowflake\": \"SELECT DATEDIFF(WEEK, TO_DATE('2020-01-01'), TO_DATE('2020-12-31'))\",\n                \"spark\": \"SELECT DATEDIFF(WEEK, '2020-01-01', '2020-12-31')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MONTHS_BETWEEN('1997-02-28 10:30:00', '1996-10-30')\",\n            write={\n                \"duckdb\": \"SELECT DATE_DIFF('MONTH', CAST('1996-10-30' AS DATE), CAST('1997-02-28 10:30:00' AS DATE)) + CASE WHEN DAY(CAST('1997-02-28 10:30:00' AS DATE)) = DAY(LAST_DAY(CAST('1997-02-28 10:30:00' AS DATE))) AND DAY(CAST('1996-10-30' AS DATE)) = DAY(LAST_DAY(CAST('1996-10-30' AS DATE))) THEN 0 ELSE (DAY(CAST('1997-02-28 10:30:00' AS DATE)) - DAY(CAST('1996-10-30' AS DATE))) / 31.0 END\",\n                \"hive\": \"SELECT MONTHS_BETWEEN('1997-02-28 10:30:00', '1996-10-30')\",\n                \"spark\": \"SELECT MONTHS_BETWEEN('1997-02-28 10:30:00', '1996-10-30')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MONTHS_BETWEEN('1997-02-28 10:30:00', '1996-10-30', FALSE)\",\n            write={\n                \"duckdb\": \"SELECT DATE_DIFF('MONTH', CAST('1996-10-30' AS DATE), CAST('1997-02-28 10:30:00' AS DATE)) + CASE WHEN DAY(CAST('1997-02-28 10:30:00' AS DATE)) = DAY(LAST_DAY(CAST('1997-02-28 10:30:00' AS DATE))) AND DAY(CAST('1996-10-30' AS DATE)) = DAY(LAST_DAY(CAST('1996-10-30' AS DATE))) THEN 0 ELSE (DAY(CAST('1997-02-28 10:30:00' AS DATE)) - DAY(CAST('1996-10-30' AS DATE))) / 31.0 END\",\n                \"hive\": \"SELECT MONTHS_BETWEEN('1997-02-28 10:30:00', '1996-10-30')\",\n                \"spark\": \"SELECT MONTHS_BETWEEN('1997-02-28 10:30:00', '1996-10-30', FALSE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP('2016-12-31 00:12:00')\",\n            write={\n                \"\": \"SELECT CAST('2016-12-31 00:12:00' AS TIMESTAMP)\",\n                \"duckdb\": \"SELECT CAST('2016-12-31 00:12:00' AS TIMESTAMP)\",\n                \"spark\": \"SELECT CAST('2016-12-31 00:12:00' AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP(x, 'zZ')\",\n            write={\n                \"\": \"SELECT STR_TO_TIME(x, '%Z%z')\",\n                \"duckdb\": \"SELECT STRPTIME(x, '%Z%z')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TO_TIMESTAMP('2016-12-31', 'yyyy-MM-dd')\",\n            read={\n                \"duckdb\": \"SELECT STRPTIME('2016-12-31', '%Y-%m-%d')\",\n            },\n            write={\n                \"\": \"SELECT STR_TO_TIME('2016-12-31', '%Y-%m-%d')\",\n                \"duckdb\": \"SELECT STRPTIME('2016-12-31', '%Y-%m-%d')\",\n                \"spark\": \"SELECT TO_TIMESTAMP('2016-12-31', 'yyyy-MM-dd')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT RLIKE('John Doe', 'John.*')\",\n            write={\n                \"bigquery\": \"SELECT REGEXP_CONTAINS('John Doe', 'John.*')\",\n                \"hive\": \"SELECT 'John Doe' RLIKE 'John.*'\",\n                \"postgres\": \"SELECT 'John Doe' ~ 'John.*'\",\n                \"snowflake\": \"SELECT REGEXP_LIKE('John Doe', 'John.*')\",\n                \"spark\": \"SELECT 'John Doe' RLIKE 'John.*'\",\n            },\n        )\n        self.validate_all(\n            \"UNHEX(MD5(x))\",\n            write={\n                \"bigquery\": \"FROM_HEX(TO_HEX(MD5(x)))\",\n                \"spark\": \"UNHEX(MD5(x))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM ((VALUES 1))\", write={\"spark\": \"SELECT * FROM (VALUES (1))\"}\n        )\n        self.validate_all(\n            \"SELECT CAST(STRUCT('fooo') AS STRUCT<a: VARCHAR(2)>)\",\n            write={\"spark\": \"SELECT CAST(STRUCT('fooo' AS col1) AS STRUCT<a: STRING>)\"},\n        )\n        self.validate_all(\n            \"SELECT CAST(123456 AS VARCHAR(3))\",\n            write={\n                \"\": \"SELECT TRY_CAST(123456 AS TEXT)\",\n                \"databricks\": \"SELECT TRY_CAST(123456 AS STRING)\",\n                \"spark\": \"SELECT CAST(123456 AS STRING)\",\n                \"spark2\": \"SELECT CAST(123456 AS STRING)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRY_CAST('a' AS INT)\",\n            write={\n                \"\": \"SELECT TRY_CAST('a' AS INT)\",\n                \"databricks\": \"SELECT TRY_CAST('a' AS INT)\",\n                \"spark\": \"SELECT TRY_CAST('a' AS INT)\",\n                \"spark2\": \"SELECT CAST('a' AS INT)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT piv.Q1 FROM (SELECT * FROM produce PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2'))) AS piv\",\n            read={\n                \"snowflake\": \"SELECT piv.Q1 FROM produce PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2')) piv\",\n            },\n        )\n        self.validate_all(\n            \"SELECT piv.Q1 FROM (SELECT * FROM (SELECT * FROM produce) PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2'))) AS piv\",\n            read={\n                \"snowflake\": \"SELECT piv.Q1 FROM (SELECT * FROM produce) PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2')) piv\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM produce PIVOT(SUM(produce.sales) FOR quarter IN ('Q1', 'Q2'))\",\n            read={\n                \"snowflake\": \"SELECT * FROM produce PIVOT (SUM(produce.sales) FOR produce.quarter IN ('Q1', 'Q2'))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM produce AS p PIVOT(SUM(p.sales) AS sales FOR quarter IN ('Q1' AS Q1, 'Q2' AS Q1))\",\n            read={\n                \"bigquery\": \"SELECT * FROM produce AS p PIVOT(SUM(p.sales) AS sales FOR p.quarter IN ('Q1' AS Q1, 'Q2' AS Q1))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEDIFF(MONTH, '2020-01-01', '2020-03-05')\",\n            write={\n                \"databricks\": \"SELECT DATEDIFF(MONTH, '2020-01-01', '2020-03-05')\",\n                \"hive\": \"SELECT CAST(MONTHS_BETWEEN('2020-03-05', '2020-01-01') AS INT)\",\n                \"presto\": \"SELECT DATE_DIFF('MONTH', CAST(CAST('2020-01-01' AS TIMESTAMP) AS DATE), CAST(CAST('2020-03-05' AS TIMESTAMP) AS DATE))\",\n                \"spark\": \"SELECT DATEDIFF(MONTH, '2020-01-01', '2020-03-05')\",\n                \"spark2\": \"SELECT CAST(MONTHS_BETWEEN('2020-03-05', '2020-01-01') AS INT)\",\n                \"trino\": \"SELECT DATE_DIFF('MONTH', CAST(CAST('2020-01-01' AS TIMESTAMP) AS DATE), CAST(CAST('2020-03-05' AS TIMESTAMP) AS DATE))\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM quarterly_sales PIVOT(SUM(amount) AS amount, 'dummy' AS bar FOR quarter IN ('2023_Q1'))\",\n            read={\n                \"spark\": \"SELECT * FROM quarterly_sales PIVOT(SUM(amount) amount, 'dummy' bar FOR quarter IN ('2023_Q1'))\",\n                \"databricks\": \"SELECT * FROM quarterly_sales PIVOT(SUM(amount) amount, 'dummy' bar FOR quarter IN ('2023_Q1'))\",\n            },\n            write={\n                \"databricks\": \"SELECT * FROM quarterly_sales PIVOT(SUM(amount) AS amount, 'dummy' AS bar FOR quarter IN ('2023_Q1'))\",\n            },\n        )\n\n        for data_type in (\n            \"BOOLEAN\",\n            \"DATE\",\n            \"DOUBLE\",\n            \"FLOAT\",\n            \"INT\",\n            \"TIMESTAMP\",\n        ):\n            self.validate_all(\n                f\"{data_type}(x)\",\n                write={\n                    \"\": f\"CAST(x AS {data_type})\",\n                    \"spark\": f\"CAST(x AS {data_type})\",\n                },\n            )\n\n        for ts_suffix in (\"NTZ\", \"LTZ\"):\n            self.validate_all(\n                f\"TIMESTAMP_{ts_suffix}(x)\",\n                write={\n                    \"\": f\"CAST(x AS TIMESTAMP{ts_suffix})\",\n                    \"spark\": f\"CAST(x AS TIMESTAMP_{ts_suffix})\",\n                },\n            )\n\n        self.validate_all(\n            \"STRING(x)\",\n            write={\n                \"\": \"CAST(x AS TEXT)\",\n                \"spark\": \"CAST(x AS STRING)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x AS TIMESTAMP)\",\n            read={\n                \"trino\": \"CAST(x AS TIMESTAMP(6) WITH TIME ZONE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_ADD(my_date_column, 1)\",\n            write={\n                \"spark\": \"SELECT DATE_ADD(my_date_column, 1)\",\n                \"spark2\": \"SELECT DATE_ADD(my_date_column, 1)\",\n                \"bigquery\": \"SELECT DATE_ADD(CAST(CAST(my_date_column AS DATETIME) AS DATE), INTERVAL 1 DAY)\",\n            },\n        )\n        self.validate_all(\n            \"AGGREGATE(my_arr, 0, (acc, x) -> acc + x, s -> s * 2)\",\n            write={\n                \"trino\": \"REDUCE(my_arr, 0, (acc, x) -> acc + x, s -> s * 2)\",\n                \"duckdb\": \"REDUCE(my_arr, 0, (acc, x) -> acc + x, s -> s * 2)\",\n                \"hive\": \"REDUCE(my_arr, 0, (acc, x) -> acc + x, s -> s * 2)\",\n                \"presto\": \"REDUCE(my_arr, 0, (acc, x) -> acc + x, s -> s * 2)\",\n                \"spark\": \"AGGREGATE(my_arr, 0, (acc, x) -> acc + x, s -> s * 2)\",\n            },\n        )\n        self.validate_all(\n            \"TRIM('SL', 'SSparkSQLS')\", write={\"spark\": \"TRIM('SL' FROM 'SSparkSQLS')\"}\n        )\n        self.validate_all(\n            \"ARRAY_SORT(x, (left, right) -> -1)\",\n            write={\n                \"duckdb\": \"ARRAY_SORT(x)\",\n                \"presto\": 'ARRAY_SORT(x, (\"left\", \"right\") -> -1)',\n                \"hive\": \"SORT_ARRAY(x)\",\n                \"spark\": \"ARRAY_SORT(x, (left, right) -> -1)\",\n            },\n        )\n        self.validate_all(\n            \"ARRAY(0, 1, 2)\",\n            write={\n                \"bigquery\": \"[0, 1, 2]\",\n                \"duckdb\": \"[0, 1, 2]\",\n                \"presto\": \"ARRAY[0, 1, 2]\",\n                \"hive\": \"ARRAY(0, 1, 2)\",\n                \"spark\": \"ARRAY(0, 1, 2)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"clickhouse\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname NULLS FIRST\",\n                \"duckdb\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname NULLS FIRST\",\n                \"postgres\": \"SELECT fname, lname, age FROM person ORDER BY age DESC, fname ASC, lname NULLS FIRST\",\n                \"presto\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC, lname NULLS FIRST\",\n                \"hive\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n                \"spark\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n                \"snowflake\": \"SELECT fname, lname, age FROM person ORDER BY age DESC, fname ASC, lname NULLS FIRST\",\n            },\n        )\n        self.validate_all(\n            \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n            write={\n                \"duckdb\": \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n                \"presto\": \"SELECT APPROX_DISTINCT(a) FROM foo\",\n                \"hive\": \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n                \"spark\": \"SELECT APPROX_COUNT_DISTINCT(a) FROM foo\",\n            },\n        )\n        self.validate_all(\n            \"MONTH('2021-03-01')\",\n            write={\n                \"duckdb\": \"MONTH(CAST('2021-03-01' AS DATE))\",\n                \"presto\": \"MONTH(CAST(CAST('2021-03-01' AS TIMESTAMP) AS DATE))\",\n                \"hive\": \"MONTH('2021-03-01')\",\n                \"spark\": \"MONTH('2021-03-01')\",\n            },\n        )\n        self.validate_all(\n            \"YEAR('2021-03-01')\",\n            write={\n                \"duckdb\": \"YEAR(CAST('2021-03-01' AS DATE))\",\n                \"presto\": \"YEAR(CAST(CAST('2021-03-01' AS TIMESTAMP) AS DATE))\",\n                \"hive\": \"YEAR('2021-03-01')\",\n                \"spark\": \"YEAR('2021-03-01')\",\n            },\n        )\n        self.validate_all(\n            \"'\\u6bdb'\",\n            write={\n                \"duckdb\": \"'毛'\",\n                \"presto\": \"'毛'\",\n                \"hive\": \"'毛'\",\n                \"spark\": \"'毛'\",\n            },\n        )\n        self.validate_all(\n            \"SELECT LEFT(x, 2), RIGHT(x, 2)\",\n            write={\n                \"duckdb\": \"SELECT LEFT(x, 2), RIGHT(x, 2)\",\n                \"presto\": \"SELECT SUBSTRING(x, 1, 2), SUBSTRING(x, LENGTH(x) - (2 - 1))\",\n                \"hive\": \"SELECT SUBSTRING(x, 1, 2), SUBSTRING(x, LENGTH(x) - (2 - 1))\",\n                \"spark\": \"SELECT LEFT(x, 2), RIGHT(x, 2)\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT SUBSTR('Spark' FROM 5 FOR 1)\", \"SELECT SUBSTRING('Spark', 5, 1)\"\n        )\n        self.validate_identity(\"SELECT SUBSTR('Spark SQL', 5)\", \"SELECT SUBSTRING('Spark SQL', 5)\")\n        self.validate_identity(\n            \"SELECT SUBSTR(ENCODE('Spark SQL', 'utf-8'), 5)\",\n            \"SELECT SUBSTRING(ENCODE('Spark SQL', 'utf-8'), 5)\",\n        )\n        self.validate_all(\n            \"MAP_FROM_ARRAYS(ARRAY(1), c)\",\n            write={\n                \"duckdb\": \"MAP([1], c)\",\n                \"presto\": \"MAP(ARRAY[1], c)\",\n                \"hive\": \"MAP(ARRAY(1), c)\",\n                \"spark\": \"MAP_FROM_ARRAYS(ARRAY(1), c)\",\n                \"snowflake\": \"OBJECT_CONSTRUCT([1], c)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT ARRAY_SORT(x)\",\n            write={\n                \"duckdb\": \"SELECT ARRAY_SORT(x)\",\n                \"presto\": \"SELECT ARRAY_SORT(x)\",\n                \"hive\": \"SELECT SORT_ARRAY(x)\",\n                \"spark\": \"SELECT ARRAY_SORT(x)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE_ADD(MONTH, 20, col)\",\n            read={\n                \"spark\": \"SELECT TIMESTAMPADD(MONTH, 20, col)\",\n            },\n            write={\n                \"spark\": \"SELECT DATE_ADD(MONTH, 20, col)\",\n                \"databricks\": \"SELECT DATE_ADD(MONTH, 20, col)\",\n                \"presto\": \"SELECT DATE_ADD('MONTH', 20, col)\",\n                \"trino\": \"SELECT DATE_ADD('MONTH', 20, col)\",\n            },\n        )\n        self.validate_identity(\"DESCRIBE schema.test PARTITION(ds = '2024-01-01')\")\n\n        self.validate_all(\n            \"SELECT ANY_VALUE(col, true), FIRST(col, true), FIRST_VALUE(col, true) OVER ()\",\n            write={\n                \"duckdb\": \"SELECT ANY_VALUE(col), ANY_VALUE(col), FIRST_VALUE(col IGNORE NULLS) OVER ()\"\n            },\n        )\n\n        self.validate_all(\n            \"SELECT STRUCT(1, 2)\",\n            write={\n                \"spark\": \"SELECT STRUCT(1 AS col1, 2 AS col2)\",\n                \"presto\": \"SELECT CAST(ROW(1, 2) AS ROW(col1 INTEGER, col2 INTEGER))\",\n                \"duckdb\": \"SELECT {'col1': 1, 'col2': 2}\",\n            },\n        )\n        self.validate_all(\n            \"SELECT STRUCT(x, 1, y AS col3, STRUCT(5)) FROM t\",\n            write={\n                \"spark\": \"SELECT STRUCT(x AS x, 1 AS col2, y AS col3, STRUCT(5 AS col1) AS col4) FROM t\",\n                \"duckdb\": \"SELECT {'x': x, 'col2': 1, 'col3': y, 'col4': {'col1': 5}} FROM t\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT TIMESTAMPDIFF(MONTH, foo, bar)\",\n            read={\n                \"databricks\": \"SELECT TIMESTAMPDIFF(MONTH, foo, bar)\",\n            },\n            write={\n                \"spark\": \"SELECT TIMESTAMPDIFF(MONTH, foo, bar)\",\n                \"databricks\": \"SELECT TIMESTAMPDIFF(MONTH, foo, bar)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT CAST(col AS TIMESTAMP)\",\n            write={\n                \"spark2\": \"SELECT CAST(col AS TIMESTAMP)\",\n                \"spark\": \"SELECT CAST(col AS TIMESTAMP)\",\n                \"databricks\": \"SELECT TRY_CAST(col AS TIMESTAMP)\",\n                \"duckdb\": \"SELECT TRY_CAST(col AS TIMESTAMPTZ)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM {df}\",\n            read={\n                \"databricks\": \"SELECT * FROM {df}\",\n            },\n            write={\n                \"spark\": \"SELECT * FROM {df}\",\n                \"databricks\": \"SELECT * FROM {df}\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM {df} WHERE id > :foo\",\n            read={\n                \"databricks\": \"SELECT * FROM {df} WHERE id > :foo\",\n            },\n            write={\n                \"spark\": \"SELECT * FROM {df} WHERE id > :foo\",\n                \"databricks\": \"SELECT * FROM {df} WHERE id > :foo\",\n            },\n        )\n        self.validate_all(\n            \"STRING_AGG(x, ', ')\",\n            write={\n                \"spark, version=3.0.0\": \"ARRAY_JOIN(COLLECT_LIST(x), ', ')\",\n                \"spark, version=4.0.0\": \"LISTAGG(x, ', ')\",\n                \"spark\": \"LISTAGG(x, ', ')\",\n            },\n        )\n        self.validate_all(\n            \"LISTAGG(x, ', ')\",\n            write={\n                \"spark, version=3.0.0\": \"ARRAY_JOIN(COLLECT_LIST(x), ', ')\",\n                \"spark, version=4.0.0\": \"LISTAGG(x, ', ')\",\n                \"spark\": \"LISTAGG(x, ', ')\",\n            },\n        )\n        self.validate_all(\n            \"LIKE(foo, 'pattern')\",\n            write={\n                \"spark\": \"foo LIKE 'pattern'\",\n                \"databricks\": \"foo LIKE 'pattern'\",\n            },\n        )\n        self.validate_all(\n            \"LIKE(foo, 'pattern', '!')\",\n            write={\n                \"spark\": \"foo LIKE 'pattern' ESCAPE '!'\",\n                \"databricks\": \"foo LIKE 'pattern' ESCAPE '!'\",\n            },\n        )\n        self.validate_all(\n            \"ILIKE(foo, 'pattern')\",\n            write={\n                \"spark\": \"foo ILIKE 'pattern'\",\n                \"databricks\": \"foo ILIKE 'pattern'\",\n            },\n        )\n        self.validate_all(\n            \"ILIKE(foo, 'pattern', '!')\",\n            write={\n                \"spark\": \"foo ILIKE 'pattern' ESCAPE '!'\",\n                \"databricks\": \"foo ILIKE 'pattern' ESCAPE '!'\",\n            },\n        )\n        self.validate_identity(\"BIT_GET(11, 0)\", \"GETBIT(11, 0)\")\n        self.validate_identity(\"BITMAP_OR_AGG(x)\")\n        self.validate_identity(\"SELECT ELT(2, 'foo', 'bar', 'baz') AS Result\")\n        self.validate_identity(\"SELECT MAKE_INTERVAL(100, 11, 12, 13, 14, 14, 15)\")\n        self.validate_identity(\"SELECT name, GROUPING_ID() FROM customer GROUP BY ROLLUP (name)\")\n        self.validate_identity(\"SELECT MAKE_TIMESTAMP(2014, 12, 28, 6, 30, 45.887)\")\n        self.validate_identity(\"SELECT CURDATE()\", \"SELECT CURRENT_DATE\")\n\n        self.validate_all(\n            \"SELECT BIT_COUNT(0)\",\n            write={\n                \"spark\": \"SELECT BIT_COUNT(0)\",\n                \"databricks\": \"SELECT BIT_COUNT(0)\",\n                \"duckdb\": \"SELECT BIT_COUNT(0)\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT * FROM foo TIMESTAMP AS OF '2020-01-01 00:00:00' AS bar\",\n            read={\n                \"spark\": \"SELECT * FROM foo TIMESTAMP AS OF '2020-01-01 00:00:00' AS bar\",\n                \"databricks\": \"SELECT * FROM foo TIMESTAMP AS OF '2020-01-01 00:00:00' AS bar\",\n            },\n            write={\n                \"databricks\": \"SELECT * FROM foo TIMESTAMP AS OF '2020-01-01 00:00:00' AS bar\",\n            },\n        )\n\n        self.validate_all(\n            \"WITH RECURSIVE t(n) AS (SELECT * FROM VALUES (1) AS _values) SELECT n FROM t\",\n            read={\n                \"spark\": \"WITH RECURSIVE t(n) AS (SELECT * FROM VALUES (1) AS _values) SELECT n FROM t\",\n                \"databricks\": \"WITH RECURSIVE t(n) AS (SELECT * FROM VALUES (1) AS _values) SELECT n FROM t\",\n            },\n            write={\n                \"databricks\": \"WITH RECURSIVE t(n) AS (SELECT * FROM VALUES (1) AS _values) SELECT n FROM t\",\n            },\n        )\n\n    def test_bool_or(self):\n        self.validate_all(\n            \"SELECT a, LOGICAL_OR(b) FROM table GROUP BY a\",\n            write={\"spark\": \"SELECT a, BOOL_OR(b) FROM table GROUP BY a\"},\n        )\n\n    def test_current_user(self):\n        self.validate_all(\n            \"CURRENT_USER\",\n            write={\"spark\": \"CURRENT_USER()\"},\n        )\n        self.validate_all(\n            \"CURRENT_USER()\",\n            write={\"spark\": \"CURRENT_USER()\"},\n        )\n\n    def test_transform_query(self):\n        self.validate_identity(\"SELECT TRANSFORM(x) USING 'x' AS (x INT) FROM t\")\n        self.validate_identity(\n            \"SELECT TRANSFORM(zip_code, name, age) USING 'cat' AS (a, b, c) FROM person WHERE zip_code > 94511\"\n        )\n        self.validate_identity(\n            \"SELECT TRANSFORM(zip_code, name, age) USING 'cat' AS (a STRING, b STRING, c STRING) FROM person WHERE zip_code > 94511\"\n        )\n        self.validate_identity(\n            \"SELECT TRANSFORM(name, age) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\\\\n' NULL DEFINED AS 'NULL' USING 'cat' AS (name_age STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY '@' LINES TERMINATED BY '\\\\n' NULL DEFINED AS 'NULL' FROM person\"\n        )\n        self.validate_identity(\n            \"SELECT TRANSFORM(zip_code, name, age) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe' WITH SERDEPROPERTIES ('field.delim'='\\\\t') USING 'cat' AS (a STRING, b STRING, c STRING) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe' WITH SERDEPROPERTIES ('field.delim'='\\\\t') FROM person WHERE zip_code > 94511\"\n        )\n        self.validate_identity(\n            \"SELECT TRANSFORM(zip_code, name, age) USING 'cat' FROM person WHERE zip_code > 94500\"\n        )\n\n    def test_insert_cte(self):\n        self.validate_all(\n            \"INSERT OVERWRITE TABLE table WITH cte AS (SELECT cola FROM other_table) SELECT cola FROM cte\",\n            write={\n                \"databricks\": \"WITH cte AS (SELECT cola FROM other_table) INSERT OVERWRITE TABLE table SELECT cola FROM cte\",\n                \"hive\": \"WITH cte AS (SELECT cola FROM other_table) INSERT OVERWRITE TABLE table SELECT cola FROM cte\",\n                \"spark\": \"WITH cte AS (SELECT cola FROM other_table) INSERT OVERWRITE TABLE table SELECT cola FROM cte\",\n                \"spark2\": \"WITH cte AS (SELECT cola FROM other_table) INSERT OVERWRITE TABLE table SELECT cola FROM cte\",\n            },\n        )\n\n    def test_explode_projection_to_unnest(self):\n        self.validate_all(\n            \"SELECT EXPLODE(x) FROM tbl\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, col, NULL) AS col FROM tbl CROSS JOIN UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(x)) - 1)) AS pos CROSS JOIN UNNEST(x) AS col WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH(x) - 1) AND pos_2 = (ARRAY_LENGTH(x) - 1))\",\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col FROM tbl CROSS JOIN UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(x)))) AS _u(pos) CROSS JOIN UNNEST(x) WITH ORDINALITY AS _u_2(col, pos_2) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(x) AND _u_2.pos_2 = CARDINALITY(x))\",\n                \"spark\": \"SELECT EXPLODE(x) FROM tbl\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXPLODE(col) FROM _u\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, col_2, NULL) AS col_2 FROM _u CROSS JOIN UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(col)) - 1)) AS pos CROSS JOIN UNNEST(col) AS col_2 WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH(col) - 1) AND pos_2 = (ARRAY_LENGTH(col) - 1))\",\n                \"presto\": \"SELECT IF(_u_2.pos = _u_3.pos_2, _u_3.col_2) AS col_2 FROM _u CROSS JOIN UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(col)))) AS _u_2(pos) CROSS JOIN UNNEST(col) WITH ORDINALITY AS _u_3(col_2, pos_2) WHERE _u_2.pos = _u_3.pos_2 OR (_u_2.pos > CARDINALITY(col) AND _u_3.pos_2 = CARDINALITY(col))\",\n                \"spark\": \"SELECT EXPLODE(col) FROM _u\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXPLODE(col) AS exploded FROM schema.tbl\",\n            write={\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.exploded) AS exploded FROM schema.tbl CROSS JOIN UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(col)))) AS _u(pos) CROSS JOIN UNNEST(col) WITH ORDINALITY AS _u_2(exploded, pos_2) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(col) AND _u_2.pos_2 = CARDINALITY(col))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT EXPLODE(ARRAY(1, 2))\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, col, NULL) AS col FROM UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH([1, 2])) - 1)) AS pos CROSS JOIN UNNEST([1, 2]) AS col WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH([1, 2]) - 1) AND pos_2 = (ARRAY_LENGTH([1, 2]) - 1))\",\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col FROM UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(ARRAY[1, 2])))) AS _u(pos) CROSS JOIN UNNEST(ARRAY[1, 2]) WITH ORDINALITY AS _u_2(col, pos_2) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(ARRAY[1, 2]) AND _u_2.pos_2 = CARDINALITY(ARRAY[1, 2]))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT POSEXPLODE(ARRAY(2, 3)) AS x\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, x, NULL) AS x, IF(pos = pos_2, pos_2, NULL) AS pos_2 FROM UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH([2, 3])) - 1)) AS pos CROSS JOIN UNNEST([2, 3]) AS x WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH([2, 3]) - 1) AND pos_2 = (ARRAY_LENGTH([2, 3]) - 1))\",\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.x) AS x, IF(_u.pos = _u_2.pos_2, _u_2.pos_2) AS pos_2 FROM UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(ARRAY[2, 3])))) AS _u(pos) CROSS JOIN UNNEST(ARRAY[2, 3]) WITH ORDINALITY AS _u_2(x, pos_2) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(ARRAY[2, 3]) AND _u_2.pos_2 = CARDINALITY(ARRAY[2, 3]))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT POSEXPLODE(ARRAY('a'))\",\n            write={\n                \"duckdb\": \"SELECT GENERATE_SUBSCRIPTS(['a'], 1) - 1 AS pos, UNNEST(['a']) AS col\",\n                \"spark\": \"SELECT POSEXPLODE(ARRAY('a'))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT POSEXPLODE(x) AS (a, b)\",\n            write={\n                \"presto\": \"SELECT IF(_u.pos = _u_2.a, _u_2.b) AS b, IF(_u.pos = _u_2.a, _u_2.a) AS a FROM UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(x)))) AS _u(pos) CROSS JOIN UNNEST(x) WITH ORDINALITY AS _u_2(b, a) WHERE _u.pos = _u_2.a OR (_u.pos > CARDINALITY(x) AND _u_2.a = CARDINALITY(x))\",\n                \"duckdb\": \"SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS a, UNNEST(x) AS b\",\n                \"spark\": \"SELECT POSEXPLODE(x) AS (a, b)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM POSEXPLODE(ARRAY('a'))\",\n            write={\n                \"duckdb\": \"SELECT * FROM (SELECT GENERATE_SUBSCRIPTS(['a'], 1) - 1 AS pos, UNNEST(['a']) AS col)\",\n                \"spark\": \"SELECT * FROM POSEXPLODE(ARRAY('a'))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM POSEXPLODE(ARRAY('a')) AS (a, b)\",\n            write={\n                \"duckdb\": \"SELECT * FROM (SELECT GENERATE_SUBSCRIPTS(['a'], 1) - 1 AS a, UNNEST(['a']) AS b)\",\n                \"spark\": \"SELECT * FROM POSEXPLODE(ARRAY('a')) AS _t0(a, b)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT POSEXPLODE(ARRAY(2, 3)), EXPLODE(ARRAY(4, 5, 6)) FROM tbl\",\n            write={\n                \"bigquery\": \"SELECT IF(pos = pos_2, col, NULL) AS col, IF(pos = pos_2, pos_2, NULL) AS pos_2, IF(pos = pos_3, col_2, NULL) AS col_2 FROM tbl CROSS JOIN UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH([2, 3]), ARRAY_LENGTH([4, 5, 6])) - 1)) AS pos CROSS JOIN UNNEST([2, 3]) AS col WITH OFFSET AS pos_2 CROSS JOIN UNNEST([4, 5, 6]) AS col_2 WITH OFFSET AS pos_3 WHERE (pos = pos_2 OR (pos > (ARRAY_LENGTH([2, 3]) - 1) AND pos_2 = (ARRAY_LENGTH([2, 3]) - 1))) AND (pos = pos_3 OR (pos > (ARRAY_LENGTH([4, 5, 6]) - 1) AND pos_3 = (ARRAY_LENGTH([4, 5, 6]) - 1)))\",\n                \"presto\": \"SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col, IF(_u.pos = _u_2.pos_2, _u_2.pos_2) AS pos_2, IF(_u.pos = _u_3.pos_3, _u_3.col_2) AS col_2 FROM tbl CROSS JOIN UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(ARRAY[2, 3]), CARDINALITY(ARRAY[4, 5, 6])))) AS _u(pos) CROSS JOIN UNNEST(ARRAY[2, 3]) WITH ORDINALITY AS _u_2(col, pos_2) CROSS JOIN UNNEST(ARRAY[4, 5, 6]) WITH ORDINALITY AS _u_3(col_2, pos_3) WHERE (_u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(ARRAY[2, 3]) AND _u_2.pos_2 = CARDINALITY(ARRAY[2, 3]))) AND (_u.pos = _u_3.pos_3 OR (_u.pos > CARDINALITY(ARRAY[4, 5, 6]) AND _u_3.pos_3 = CARDINALITY(ARRAY[4, 5, 6])))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT col, pos, POSEXPLODE(ARRAY(2, 3)) FROM _u\",\n            write={\n                \"presto\": \"SELECT col, pos, IF(_u_2.pos_2 = _u_3.pos_3, _u_3.col_2) AS col_2, IF(_u_2.pos_2 = _u_3.pos_3, _u_3.pos_3) AS pos_3 FROM _u CROSS JOIN UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(ARRAY[2, 3])))) AS _u_2(pos_2) CROSS JOIN UNNEST(ARRAY[2, 3]) WITH ORDINALITY AS _u_3(col_2, pos_3) WHERE _u_2.pos_2 = _u_3.pos_3 OR (_u_2.pos_2 > CARDINALITY(ARRAY[2, 3]) AND _u_3.pos_3 = CARDINALITY(ARRAY[2, 3]))\",\n            },\n        )\n\n    def test_strip_modifiers(self):\n        without_modifiers = \"SELECT * FROM t\"\n        with_modifiers = f\"{without_modifiers} CLUSTER BY y DISTRIBUTE BY x SORT BY z\"\n        query = self.parse_one(with_modifiers)\n\n        for dialect in Dialects:\n            with self.subTest(f\"Transpiling query with CLUSTER/DISTRIBUTE/SORT BY to {dialect}\"):\n                name = dialect.value\n                if name in (\"\", \"databricks\", \"hive\", \"spark\", \"spark2\"):\n                    self.assertEqual(query.sql(name), with_modifiers)\n                else:\n                    self.assertEqual(query.sql(name), without_modifiers)\n\n    def test_schema_binding_options(self):\n        for schema_binding in (\n            \"BINDING\",\n            \"COMPENSATION\",\n            \"TYPE EVOLUTION\",\n            \"EVOLUTION\",\n        ):\n            with self.subTest(f\"Test roundtrip of VIEW schema binding {schema_binding}\"):\n                self.validate_identity(\n                    f\"CREATE VIEW emp_v WITH SCHEMA {schema_binding} AS SELECT * FROM emp\"\n                )\n\n    def test_minus(self):\n        self.validate_all(\n            \"SELECT * FROM db.table1 MINUS SELECT * FROM db.table2\",\n            write={\n                \"spark\": \"SELECT * FROM db.table1 EXCEPT SELECT * FROM db.table2\",\n                \"databricks\": \"SELECT * FROM db.table1 EXCEPT SELECT * FROM db.table2\",\n            },\n        )\n\n    def test_string(self):\n        for dialect in (\"hive\", \"spark2\", \"spark\", \"databricks\"):\n            with self.subTest(f\"Testing STRING() for {dialect}\"):\n                query = parse_one(\"STRING(a)\", dialect=dialect)\n                self.assertEqual(query.sql(dialect), \"CAST(a AS STRING)\")\n\n    def test_binary_string(self):\n        for dialect in (\"spark2\", \"spark\", \"databricks\"):\n            with self.subTest(f\"Testing HEX strings for {dialect}\"):\n                query = parse_one(\"X'ab'\", dialect=dialect)\n                self.assertEqual(query.sql(dialect), \"X'ab'\")\n\n            with self.subTest(f\"Testing empty HEX strings for {dialect}\"):\n                query = parse_one(\"X''\", dialect=dialect)\n                self.assertEqual(query.sql(dialect), \"X''\")\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE TABLE tbl COMPUTE STATISTICS NOSCAN\")\n        self.validate_identity(\"ANALYZE TABLE tbl COMPUTE STATISTICS FOR ALL COLUMNS\")\n        self.validate_identity(\"ANALYZE TABLE tbl COMPUTE STATISTICS FOR COLUMNS foo, bar\")\n        self.validate_identity(\"ANALYZE TABLE ctlg.db.tbl COMPUTE STATISTICS NOSCAN\")\n        self.validate_identity(\"ANALYZE TABLES COMPUTE STATISTICS NOSCAN\")\n        self.validate_identity(\"ANALYZE TABLES FROM db COMPUTE STATISTICS\")\n        self.validate_identity(\"ANALYZE TABLES IN db COMPUTE STATISTICS\")\n        self.validate_identity(\n            \"ANALYZE TABLE ctlg.db.tbl PARTITION(foo = 'foo', bar = 'bar') COMPUTE STATISTICS NOSCAN\"\n        )\n\n    def test_transpile_annotated_exploded_column(self):\n        from sqlglot.optimizer.annotate_types import annotate_types\n        from sqlglot.optimizer.qualify import qualify\n\n        for db_prefix in (\"\", \"explode_view.\"):\n            with self.subTest(f\"Annotated exploded column with prefix: {db_prefix}.\"):\n                sql = f\"\"\"\n                    WITH test_table AS (\n                      SELECT\n                        12345 AS id_column,\n                        ARRAY(\n                          STRUCT('John' AS name, 30 AS age),\n                          STRUCT('Mary' AS name, 20 AS age),\n                          STRUCT('Mike' AS name, 80 AS age),\n                          STRUCT('Dan' AS name, 50 AS age)\n                        ) AS struct_column\n                    )\n\n                    SELECT\n                        id_column,\n                        {db_prefix}new_column.name,\n                        {db_prefix}new_column.age\n                    FROM test_table\n                    LATERAL VIEW EXPLODE(struct_column) explode_view AS new_column\n                \"\"\"\n\n                expr = self.parse_one(sql)\n                qualified = qualify(expr, dialect=\"spark\")\n                annotated = annotate_types(qualified, dialect=\"spark\")\n\n                self.assertEqual(\n                    annotated.sql(\"spark\"),\n                    \"WITH `test_table` AS (SELECT 12345 AS `id_column`, ARRAY(STRUCT('John' AS `name`, 30 AS `age`), STRUCT('Mary' AS `name`, 20 AS `age`), STRUCT('Mike' AS `name`, 80 AS `age`), STRUCT('Dan' AS `name`, 50 AS `age`)) AS `struct_column`) SELECT `test_table`.`id_column` AS `id_column`, `explode_view`.`new_column`.`name` AS `name`, `explode_view`.`new_column`.`age` AS `age` FROM `test_table` AS `test_table` LATERAL VIEW EXPLODE(`test_table`.`struct_column`) explode_view AS `new_column`\",\n                )\n                self.assertEqual(\n                    annotated.sql(\"presto\"),\n                    \"\"\"WITH \"test_table\" AS (SELECT 12345 AS \"id_column\", ARRAY[CAST(ROW('John', 30) AS ROW(\"name\" VARCHAR, \"age\" INTEGER)), CAST(ROW('Mary', 20) AS ROW(\"name\" VARCHAR, \"age\" INTEGER)), CAST(ROW('Mike', 80) AS ROW(\"name\" VARCHAR, \"age\" INTEGER)), CAST(ROW('Dan', 50) AS ROW(\"name\" VARCHAR, \"age\" INTEGER))] AS \"struct_column\") SELECT \"test_table\".\"id_column\" AS \"id_column\", \"explode_view\".\"name\" AS \"name\", \"explode_view\".\"age\" AS \"age\" FROM \"test_table\" AS \"test_table\" CROSS JOIN UNNEST(\"test_table\".\"struct_column\") AS \"explode_view\"(\"name\", \"age\")\"\"\",\n                )\n\n    def test_approx_percentile(self):\n        self.validate_all(\n            \"PERCENTILE_APPROX(DISTINCT col, 0.3)\",\n            read={\n                \"spark\": \"APPROX_PERCENTILE(DISTINCT col, 0.3)\",\n                \"databricks\": \"APPROX_PERCENTILE(DISTINCT col, 0.3)\",\n            },\n        )\n        self.validate_all(\n            \"PERCENTILE_APPROX(DISTINCT col, 0.3, 200)\",\n            read={\n                \"spark\": \"APPROX_PERCENTILE(DISTINCT col, 0.3, 200)\",\n                \"databricks\": \"APPROX_PERCENTILE(DISTINCT col, 0.3, 200)\",\n            },\n        )\n\n        approx_quantile_expr = self.validate_identity(\"PERCENTILE_APPROX(DISTINCT col, 0.3)\")\n        approx_quantile_expr.assert_is(exp.ApproxQuantile)\n        approx_quantile_expr.this.assert_is(exp.Distinct)\n        approx_quantile_expr.args.get(\"quantile\").assert_is(exp.Literal)\n\n        approx_quantile_expr = self.validate_identity(\"PERCENTILE_APPROX(DISTINCT col, 0.3, 200)\")\n        approx_quantile_expr.assert_is(exp.ApproxQuantile)\n        approx_quantile_expr.this.assert_is(exp.Distinct)\n        approx_quantile_expr.args.get(\"quantile\").assert_is(exp.Literal)\n        approx_quantile_expr.args.get(\"accuracy\").assert_is(exp.Literal)\n\n    def test_array_insert(self):\n        self.validate_all(\n            \"SELECT ARRAY_INSERT(ARRAY('a', 'b', 'c'), 1, 'z')\",\n            read={\n                \"databricks\": \"SELECT ARRAY_INSERT(ARRAY('a', 'b', 'c'), 1, 'z')\",\n            },\n            write={\n                \"databricks\": \"SELECT ARRAY_INSERT(ARRAY('a', 'b', 'c'), 1, 'z')\",\n                \"spark\": \"SELECT ARRAY_INSERT(ARRAY('a', 'b', 'c'), 1, 'z')\",\n            },\n        )\n\n    def test_declare(self):\n        self.validate_identity(\"DECLARE VAR x INT\", \"DECLARE x INT\")\n        self.validate_identity(\"DECLARE x INT\")\n        self.validate_identity(\"DECLARE VARIABLE myvar INT DEFAULT 5\", \"DECLARE myvar INT = 5\")\n        self.validate_identity(\"DECLARE x, y, z INT DEFAULT 1\", \"DECLARE x, y, z INT = 1\")\n        self.validate_identity(\"DECLARE x INT = 5\")\n        self.validate_identity(\"DECLARE five = 5\")\n        self.validate_identity(\"DECLARE OR REPLACE five = 55\")\n        self.validate_identity(\"DECLARE VARIABLE size DEFAULT 6\", \"DECLARE size = 6\")\n        self.validate_identity(\"DECLARE some_var STRING\")\n\n    def test_set_variable(self):\n        self.validate_all(\n            \"SET VAR v = 5\",\n            write={\n                \"spark\": \"SET VARIABLE v = 5\",\n                \"databricks\": \"SET VARIABLE v = 5\",\n            },\n        )\n        self.validate_all(\n            \"SET VARIABLE v = 5\",\n            write={\n                \"spark\": \"SET VARIABLE v = 5\",\n                \"databricks\": \"SET VARIABLE v = 5\",\n            },\n        )\n\n        self.validate_all(\n            \"SET VARIABLE v = (SELECT MAX(c1) FROM VALUES (1), (2) AS T(c1))\",\n            write={\n                \"spark\": \"SET VARIABLE v = (SELECT MAX(c1) FROM VALUES (1), (2) AS T(c1))\",\n                \"databricks\": \"SET VARIABLE v = (SELECT MAX(c1) FROM VALUES (1), (2) AS T(c1))\",\n            },\n        )\n\n        self.validate_all(\n            \"SET VARIABLE v = DEFAULT\",\n            write={\n                \"spark\": \"SET VARIABLE v = DEFAULT\",\n                \"databricks\": \"SET VARIABLE v = DEFAULT\",\n            },\n        )\n\n        self.validate_all(\n            \"SET VARIABLE v1 = 1, v2 = '2'\",\n            write={\n                \"spark\": \"SET VARIABLE v1 = 1, v2 = '2'\",\n                \"databricks\": \"SET VARIABLE v1 = 1, v2 = '2'\",\n            },\n        )\n\n        self.validate_all(\n            \"SET VARIABLE (v1, v2) = (SELECT 1, 2)\",\n            write={\n                \"spark\": \"SET VARIABLE (v1, v2) = (SELECT 1, 2)\",\n                \"databricks\": \"SET VARIABLE (v1, v2) = (SELECT 1, 2)\",\n            },\n        )\n"
  },
  {
    "path": "tests/dialects/test_sqlite.py",
    "content": "from tests.dialects.test_dialect import Validator\n\nfrom sqlglot import exp\nfrom sqlglot.helper import logger as helper_logger\n\n\nclass TestSQLite(Validator):\n    dialect = \"sqlite\"\n\n    def test_sqlite(self):\n        self.validate_identity(\"SELECT * FROM t AS t INDEXED BY s.i\")\n        self.validate_identity(\"SELECT * FROM t INDEXED BY s.i\")\n        self.validate_identity(\"SELECT * FROM t INDEXED BY i\")\n        self.validate_identity(\"SELECT * FROM t NOT INDEXED\")\n        self.validate_identity(\"SELECT match FROM t\")\n        self.validate_identity(\"SELECT rowid FROM t1 WHERE t1 MATCH 'lorem'\")\n        self.validate_identity(\"SELECT RANK() OVER (RANGE CURRENT ROW) FROM tbl\")\n        self.validate_identity(\"UNHEX(a, b)\")\n        self.validate_identity(\"SELECT DATE()\")\n        self.validate_identity(\"SELECT DATE('now', 'start of month', '+1 month', '-1 day')\")\n        self.validate_identity(\"SELECT DATETIME(1092941466, 'unixepoch')\")\n        self.validate_identity(\"SELECT DATETIME(1092941466, 'auto')\")\n        self.validate_identity(\"SELECT DATETIME(1092941466, 'unixepoch', 'localtime')\")\n        self.validate_identity(\"SELECT UNIXEPOCH()\")\n        self.validate_identity(\"SELECT JULIANDAY('now') - JULIANDAY('1776-07-04')\")\n        self.validate_identity(\"SELECT UNIXEPOCH() - UNIXEPOCH('2004-01-01 02:34:56')\")\n        self.validate_identity(\"SELECT DATE('now', 'start of year', '+9 months', 'weekday 2')\")\n        self.validate_identity(\"SELECT (JULIANDAY('now') - 2440587.5) * 86400.0\")\n        self.validate_identity(\"SELECT UNIXEPOCH('now', 'subsec')\")\n        self.validate_identity(\"SELECT TIMEDIFF('now', '1809-02-12')\")\n        self.validate_identity(\"SELECT * FROM GENERATE_SERIES(1, 5)\")\n        self.validate_identity(\"SELECT INSTR(haystack, needle)\")\n        self.validate_identity(\n            \"SELECT a, SUM(b) OVER (ORDER BY a ROWS BETWEEN -1 PRECEDING AND 1 FOLLOWING) FROM t1 ORDER BY 1\"\n        )\n        self.validate_identity(\n            \"SELECT JSON_EXTRACT('[10, 20, [30, 40]]', '$[2]', '$[0]', '$[1]')\",\n        )\n        self.validate_identity(\n            \"\"\"SELECT item AS \"item\", some AS \"some\" FROM data WHERE (item = 'value_1' COLLATE NOCASE) AND (some = 't' COLLATE NOCASE) ORDER BY item ASC LIMIT 1 OFFSET 0\"\"\"\n        )\n        self.validate_identity(\n            \"SELECT a FROM t1 WHERE a NOT NULL AND a NOT NULL ORDER BY a\",\n            \"SELECT a FROM t1 WHERE NOT a IS NULL AND NOT a IS NULL ORDER BY a\",\n        )\n        self.validate_identity(\n            \"SELECT a, b FROM t1 WHERE b + a NOT NULL ORDER BY 1\",\n            \"SELECT a, b FROM t1 WHERE NOT b + a IS NULL ORDER BY 1\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM t1, t2\",\n            \"SELECT * FROM t1 CROSS JOIN t2\",\n        )\n        self.validate_identity(\n            \"ALTER TABLE t RENAME a TO b\",\n            \"ALTER TABLE t RENAME COLUMN a TO b\",\n        )\n\n        self.validate_all(\"SELECT LIKE(y, x)\", write={\"sqlite\": \"SELECT x LIKE y\"})\n        self.validate_all(\"SELECT GLOB('*y*', 'xyz')\", write={\"sqlite\": \"SELECT 'xyz' GLOB '*y*'\"})\n        self.validate_all(\n            \"SELECT LIKE('%y%', 'xyz', '')\", write={\"sqlite\": \"SELECT 'xyz' LIKE '%y%' ESCAPE ''\"}\n        )\n        self.validate_all(\n            \"SELECT MIN(a, b) FROM t\",\n            read={\n                \"postgres\": \"SELECT LEAST(a, b) FROM t\",\n                \"sqlite\": \"SELECT MIN(a, b) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT MAX(a, b) FROM t\",\n            read={\n                \"postgres\": \"SELECT GREATEST(a, b) FROM t\",\n                \"sqlite\": \"SELECT MAX(a, b) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_GROUP_ARRAY(name) FROM t\",\n            read={\n                \"postgres\": \"SELECT JSON_AGG(name) FROM t\",\n                \"sqlite\": \"SELECT JSON_GROUP_ARRAY(name) FROM t\",\n            },\n            write={\n                \"postgres\": \"SELECT JSON_AGG(name) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"SELECT JSON_GROUP_OBJECT(name, value) FROM t\",\n            read={\n                \"postgres\": \"SELECT JSON_OBJECT_AGG(name, value) FROM t\",\n                \"sqlite\": \"SELECT JSON_GROUP_OBJECT(name, value) FROM t\",\n            },\n            write={\n                \"postgres\": \"SELECT JSON_OBJECT_AGG(name, value) FROM t\",\n            },\n        )\n        self.validate_all(\n            \"CURRENT_DATE\",\n            read={\n                \"\": \"CURRENT_DATE\",\n                \"snowflake\": \"CURRENT_DATE()\",\n            },\n        )\n        self.validate_all(\n            \"CURRENT_TIME\",\n            read={\n                \"\": \"CURRENT_TIME\",\n            },\n        )\n        self.validate_all(\n            \"CURRENT_TIMESTAMP\",\n            read={\n                \"\": \"CURRENT_TIMESTAMP\",\n                \"snowflake\": \"CURRENT_TIMESTAMP()\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATE('2020-01-01 16:03:05')\",\n            read={\n                \"snowflake\": \"SELECT CAST('2020-01-01 16:03:05' AS DATE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST([a].[b] AS SMALLINT) FROM foo\",\n            write={\n                \"sqlite\": 'SELECT CAST(\"a\".\"b\" AS INTEGER) FROM foo',\n                \"spark\": \"SELECT CAST(`a`.`b` AS SMALLINT) FROM foo\",\n            },\n        )\n        self.validate_all(\n            \"EDITDIST3(col1, col2)\",\n            read={\n                \"sqlite\": \"EDITDIST3(col1, col2)\",\n                \"spark\": \"LEVENSHTEIN(col1, col2)\",\n            },\n            write={\n                \"sqlite\": \"EDITDIST3(col1, col2)\",\n                \"spark\": \"LEVENSHTEIN(col1, col2)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            write={\n                \"spark\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n                \"sqlite\": \"SELECT fname, lname, age FROM person ORDER BY age DESC NULLS FIRST, fname ASC NULLS LAST, lname\",\n            },\n        )\n        self.validate_all(\"x\", read={\"snowflake\": \"LEAST(x)\"})\n        self.validate_all(\"x\", read={\"postgres\": \"GREATEST(x)\"})\n        self.validate_all(\"MIN(x)\", read={\"snowflake\": \"MIN(x)\"}, write={\"snowflake\": \"MIN(x)\"})\n        self.validate_all(\n            \"MIN(x, y, z)\",\n            read={\"snowflake\": \"LEAST(x, y, z)\"},\n            write={\"snowflake\": \"LEAST(x, y, z)\"},\n        )\n        self.validate_all(\n            \"UNICODE(x)\",\n            write={\n                \"\": \"UNICODE(x)\",\n                \"mysql\": \"ORD(CONVERT(x USING utf32))\",\n                \"oracle\": \"ASCII(UNISTR(x))\",\n                \"postgres\": \"ASCII(x)\",\n                \"redshift\": \"ASCII(x)\",\n                \"spark\": \"ASCII(x)\",\n            },\n        )\n        self.validate_identity(\n            \"SELECT * FROM station WHERE city IS NOT ''\",\n            \"SELECT * FROM station WHERE NOT city IS ''\",\n        )\n        self.validate_identity(\"SELECT JSON_OBJECT('col1', 1, 'col2', '1')\")\n        self.validate_identity(\n            'CREATE TABLE \"foo t\" (\"foo t id\" TEXT NOT NULL, PRIMARY KEY (\"foo t id\"))',\n            'CREATE TABLE \"foo t\" (\"foo t id\" TEXT NOT NULL PRIMARY KEY)',\n        )\n        self.validate_identity(\"REPLACE INTO foo (x, y) VALUES (1, 2)\", check_command_warning=True)\n        self.validate_identity(\n            \"ATTACH DATABASE 'foo' AS schema_name\", \"ATTACH 'foo' AS schema_name\"\n        )\n        self.validate_identity(\n            \"ATTACH DATABASE NOT EXISTS(SELECT 1) AS schema_name\",\n            \"ATTACH NOT EXISTS(SELECT 1) AS schema_name\",\n        )\n        self.validate_identity(\n            \"ATTACH DATABASE IIF(NOT EXISTS(SELECT 1), 'foo1', 'foo2') AS schema_name\",\n            \"ATTACH IIF(NOT EXISTS(SELECT 1), 'foo1', 'foo2') AS schema_name\",\n        )\n        self.validate_identity(\n            \"ATTACH DATABASE 'foo' || '.foo2' AS schema_name\",\n            \"ATTACH 'foo' || '.foo2' AS schema_name\",\n        )\n        self.validate_identity(\"DETACH DATABASE schema_name\", \"DETACH schema_name\")\n        self.validate_identity(\"SELECT * FROM t WHERE NULL IS y\")\n        self.validate_identity(\n            \"SELECT * FROM t WHERE NULL IS NOT y\", \"SELECT * FROM t WHERE NOT NULL IS y\"\n        )\n        self.validate_identity(\"SELECT SQLITE_VERSION()\")\n\n    def test_strftime(self):\n        self.validate_identity(\"SELECT STRFTIME('%Y/%m/%d', 'now')\")\n        self.validate_identity(\"SELECT STRFTIME('%Y-%m-%d', '2016-10-16', 'start of month')\")\n        self.validate_identity(\n            \"SELECT STRFTIME('%s')\",\n            \"SELECT STRFTIME('%s', CURRENT_TIMESTAMP)\",\n        )\n\n        self.validate_all(\n            \"SELECT STRFTIME('%Y-%m-%d', '2020-01-01 12:05:03')\",\n            write={\n                \"duckdb\": \"SELECT STRFTIME(CAST('2020-01-01 12:05:03' AS TIMESTAMP), '%Y-%m-%d')\",\n                \"sqlite\": \"SELECT STRFTIME('%Y-%m-%d', '2020-01-01 12:05:03')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT STRFTIME('%Y-%m-%d', CURRENT_TIMESTAMP)\",\n            write={\n                \"duckdb\": \"SELECT STRFTIME(CAST(CURRENT_TIMESTAMP AS TIMESTAMP), '%Y-%m-%d')\",\n                \"sqlite\": \"SELECT STRFTIME('%Y-%m-%d', CURRENT_TIMESTAMP)\",\n            },\n        )\n\n    def test_datediff(self):\n        self.validate_all(\n            \"DATEDIFF(a, b, 'day')\",\n            write={\"sqlite\": \"CAST((JULIANDAY(a) - JULIANDAY(b)) AS INTEGER)\"},\n        )\n        self.validate_all(\n            \"DATEDIFF(a, b, 'hour')\",\n            write={\"sqlite\": \"CAST((JULIANDAY(a) - JULIANDAY(b)) * 24.0 AS INTEGER)\"},\n        )\n        self.validate_all(\n            \"DATEDIFF(a, b, 'year')\",\n            write={\"sqlite\": \"CAST((JULIANDAY(a) - JULIANDAY(b)) / 365.0 AS INTEGER)\"},\n        )\n\n    def test_hexadecimal_literal(self):\n        self.validate_all(\n            \"SELECT 0XCC\",\n            write={\n                \"sqlite\": \"SELECT x'CC'\",\n                \"mysql\": \"SELECT x'CC'\",\n            },\n        )\n\n    def test_window_null_treatment(self):\n        self.validate_all(\n            \"SELECT FIRST_VALUE(Name) OVER (PARTITION BY AlbumId ORDER BY Bytes DESC) AS LargestTrack FROM tracks\",\n            write={\n                \"sqlite\": \"SELECT FIRST_VALUE(Name) OVER (PARTITION BY AlbumId ORDER BY Bytes DESC) AS LargestTrack FROM tracks\"\n            },\n        )\n\n    def test_longvarchar_dtype(self):\n        self.validate_all(\n            \"CREATE TABLE foo (bar LONGVARCHAR)\",\n            write={\"sqlite\": \"CREATE TABLE foo (bar TEXT)\"},\n        )\n\n    def test_warnings(self):\n        with self.assertLogs(helper_logger) as cm:\n            self.validate_identity(\n                \"SELECT * FROM t AS t(c1, c2)\",\n                \"SELECT * FROM t AS t\",\n            )\n\n            self.assertIn(\"Named columns are not supported in table alias.\", cm.output[0])\n\n    def test_trunc(self):\n        # SQLite TRUNC only accepts one argument\n        self.validate_identity(\"TRUNC(3.14)\").assert_is(exp.Trunc)\n\n        # Decimals arg is dropped with warning (best-effort transpilation)\n        with self.assertLogs(helper_logger) as cm:\n            self.validate_identity(\"TRUNC(3.14, 2)\", \"TRUNC(3.14)\").assert_is(exp.Trunc)\n            self.assertIn(\"'decimals' is not supported\", cm.output[0])\n\n    def test_ddl(self):\n        for conflict_action in (\"ABORT\", \"FAIL\", \"IGNORE\", \"REPLACE\", \"ROLLBACK\"):\n            with self.subTest(f\"ON CONFLICT {conflict_action}\"):\n                self.validate_identity(\"CREATE TABLE a (b, c, UNIQUE (b, c) ON CONFLICT IGNORE)\")\n\n        self.validate_identity(\"CREATE TABLE over (x, y)\")\n        self.validate_identity(\"INSERT OR ABORT INTO foo (x, y) VALUES (1, 2)\")\n        self.validate_identity(\"INSERT OR FAIL INTO foo (x, y) VALUES (1, 2)\")\n        self.validate_identity(\"INSERT OR IGNORE INTO foo (x, y) VALUES (1, 2)\")\n        self.validate_identity(\"INSERT OR REPLACE INTO foo (x, y) VALUES (1, 2)\")\n        self.validate_identity(\"INSERT OR ROLLBACK INTO foo (x, y) VALUES (1, 2)\")\n        self.validate_identity(\"CREATE TABLE foo (id INTEGER PRIMARY KEY ASC)\")\n        self.validate_identity(\"CREATE TEMPORARY TABLE foo (id INTEGER)\")\n\n        self.validate_all(\n            \"\"\"\n            CREATE TABLE \"Track\"\n            (\n                CONSTRAINT \"PK_Track\" FOREIGN KEY (\"TrackId\"),\n                FOREIGN KEY (\"AlbumId\") REFERENCES \"Album\" (\n                    \"AlbumId\"\n                ) ON DELETE NO ACTION ON UPDATE NO ACTION,\n                FOREIGN KEY (\"AlbumId\") ON DELETE CASCADE ON UPDATE RESTRICT,\n                FOREIGN KEY (\"AlbumId\") ON DELETE SET NULL ON UPDATE SET DEFAULT\n            )\n            \"\"\",\n            write={\n                \"sqlite\": \"\"\"CREATE TABLE \"Track\" (\n  CONSTRAINT \"PK_Track\" FOREIGN KEY (\"TrackId\"),\n  FOREIGN KEY (\"AlbumId\") REFERENCES \"Album\" (\n    \"AlbumId\"\n  ) ON DELETE NO ACTION ON UPDATE NO ACTION,\n  FOREIGN KEY (\"AlbumId\") ON DELETE CASCADE ON UPDATE RESTRICT,\n  FOREIGN KEY (\"AlbumId\") ON DELETE SET NULL ON UPDATE SET DEFAULT\n)\"\"\",\n            },\n            pretty=True,\n        )\n        self.validate_all(\n            \"CREATE TABLE z (a INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT)\",\n            read={\n                \"mysql\": \"CREATE TABLE z (a INT UNIQUE PRIMARY KEY AUTO_INCREMENT)\",\n                \"postgres\": \"CREATE TABLE z (a INT GENERATED BY DEFAULT AS IDENTITY NOT NULL UNIQUE PRIMARY KEY)\",\n            },\n            write={\n                \"sqlite\": \"CREATE TABLE z (a INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT)\",\n                \"mysql\": \"CREATE TABLE z (a INT UNIQUE PRIMARY KEY AUTO_INCREMENT)\",\n                \"postgres\": \"CREATE TABLE z (a INT GENERATED BY DEFAULT AS IDENTITY NOT NULL UNIQUE PRIMARY KEY)\",\n            },\n        )\n        self.validate_all(\n            \"\"\"CREATE TABLE \"x\" (\"Name\" NVARCHAR(200) NOT NULL)\"\"\",\n            write={\n                \"sqlite\": \"\"\"CREATE TABLE \"x\" (\"Name\" TEXT(200) NOT NULL)\"\"\",\n                \"mysql\": \"CREATE TABLE `x` (`Name` VARCHAR(200) NOT NULL)\",\n            },\n        )\n\n        self.validate_identity(\n            \"CREATE TABLE store (store_id INTEGER PRIMARY KEY AUTOINCREMENT, mgr_id INTEGER NOT NULL UNIQUE REFERENCES staff ON UPDATE CASCADE)\"\n        )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE tbl\")\n        self.validate_identity(\"ANALYZE schma.tbl\")\n\n    def test_create_trigger(self):\n        \"\"\"Test that SQLite CREATE TRIGGER statements fall back to Command parsing.\"\"\"\n        self.validate_identity(\n            \"CREATE TRIGGER log_insert AFTER INSERT ON users BEGIN INSERT INTO audit_log (user_id, action, created_at) VALUES (NEW.id, 'INSERT', datetime('now')) END\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"CREATE TRIGGER check_balance BEFORE UPDATE OF balance ON accounts WHEN NEW.balance < 0 BEGIN UPDATE accounts SET balance = 0 WHERE id = NEW.id END\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"CREATE TRIGGER view_insert INSTEAD OF INSERT ON employee_view BEGIN INSERT INTO employees (id, name, department) VALUES (NEW.id, NEW.name, NEW.department) END\",\n            check_command_warning=True,\n        )\n"
  },
  {
    "path": "tests/dialects/test_starrocks.py",
    "content": "from sqlglot import exp\nfrom sqlglot.errors import UnsupportedError\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestStarrocks(Validator):\n    dialect = \"starrocks\"\n\n    def test_starrocks(self):\n        self.assertEqual(self.validate_identity(\"arr[1]\").expressions[0], exp.Literal.number(0))\n\n        self.validate_identity(\"SELECT ARRAY_JOIN([1, 3, 5, NULL], '_', 'NULL')\")\n        self.validate_identity(\"SELECT ARRAY_JOIN([1, 3, 5, NULL], '_')\")\n        self.validate_identity(\"ALTER TABLE a SWAP WITH b\")\n        self.validate_identity(\"SELECT ARRAY_AGG(a) FROM x\")\n        self.validate_identity(\"SELECT ST_POINT(10, 20)\")\n        self.validate_identity(\"SELECT ST_DISTANCE_SPHERE(10.1, 20.2, 30.3, 40.4)\")\n        self.validate_identity(\"ARRAY_FLATTEN(arr)\").assert_is(exp.Flatten)\n\n        self.validate_all(\n            \"SELECT * FROM t WHERE cond\",\n            read={\n                \"\": \"SELECT * FROM t WHERE cond IS TRUE\",\n                \"starrocks\": \"SELECT * FROM t WHERE cond\",\n            },\n        )\n\n        self.validate_identity(\"CURRENT_VERSION()\")\n\n    def test_ddl(self):\n        self.validate_identity(\"INSERT OVERWRITE my_table SELECT * FROM other_table\")\n        self.validate_identity(\"CREATE TABLE t (c INT) COMMENT 'c'\")\n\n        ddl_sqls = [\n            \"PARTITION BY (col1, col2)\",\n            \"PARTITION BY DATE_TRUNC('DAY', col2), col1\",\n            \"PARTITION BY FROM_UNIXTIME(col2)\",\n            \"DISTRIBUTED BY HASH (col1) BUCKETS 1\",\n            \"DISTRIBUTED BY HASH (col1)\",\n            \"DISTRIBUTED BY RANDOM BUCKETS 1\",\n            \"DISTRIBUTED BY RANDOM\",\n            \"DISTRIBUTED BY HASH (col1) ORDER BY (col1)\",\n            \"DISTRIBUTED BY HASH (col1) PROPERTIES ('replication_num'='1')\",\n            \"PRIMARY KEY (col1) DISTRIBUTED BY HASH (col1)\",\n            \"DUPLICATE KEY (col1, col2) DISTRIBUTED BY HASH (col1)\",\n            \"UNIQUE KEY (col1, col2) PARTITION BY RANGE (col1) (START ('2024-01-01') END ('2024-01-31') EVERY (INTERVAL 1 DAY)) DISTRIBUTED BY HASH (col1)\",\n            \"UNIQUE KEY (col1, col2) PARTITION BY RANGE (col1, col2) (START ('1') END ('10') EVERY (1), START ('10') END ('100') EVERY (10)) DISTRIBUTED BY HASH (col1)\",\n            \"ORDER BY (col1, col2)\",\n            \"DISTRIBUTED BY HASH (col1) ROLLUP (r1(event_day, siteid), r2(event_day, citycode), r3(event_day))\",\n            \"DISTRIBUTED BY HASH (col1) ROLLUP (r1(col2))\",\n            \"DISTRIBUTED BY HASH (col1) ROLLUP (`r1`(`col2`))\",\n            \"DISTRIBUTED BY HASH (col1) ROLLUP (r1(col2) FROM base_index)\",\n            \"DISTRIBUTED BY HASH (col1) ROLLUP (r1(col2) PROPERTIES ('storage_type'='column'))\",\n            \"DISTRIBUTED BY HASH (col1) ROLLUP (r1(col2) FROM base_index PROPERTIES ('k'='v'))\",\n            \"DISTRIBUTED BY HASH (col1) ROLLUP (r1(col2) PROPERTIES ('k1'='v1', 'k2'='v2'))\",\n        ]\n\n        for properties in ddl_sqls:\n            with self.subTest(f\"Testing create scheme: {properties}\"):\n                self.validate_identity(f\"CREATE TABLE foo (col1 BIGINT, col2 BIGINT) {properties}\")\n                self.validate_identity(\n                    f\"CREATE TABLE foo (col1 BIGINT, col2 BIGINT) ENGINE=OLAP {properties}\"\n                )\n\n        # Test the different wider DECIMAL types\n        self.validate_identity(\n            \"CREATE TABLE foo (col0 DECIMAL(9, 1), col1 DECIMAL32(9, 1), col2 DECIMAL64(18, 10), col3 DECIMAL128(38, 10)) DISTRIBUTED BY HASH (col1) BUCKETS 1\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE foo (col1 LARGEINT) DISTRIBUTED BY HASH (col1) BUCKETS 1\"\n        )\n        self.validate_identity(\n            \"CREATE VIEW foo (foo_col1) SECURITY NONE AS SELECT bar_col1 FROM bar\"\n        )\n\n        # Test ROLLUP property\n        self.validate_all(\n            \"CREATE TABLE foo (col1 BIGINT, col2 BIGINT) ROLLUP (r1(col1, col2), r2(col1))\",\n            write={\n                \"starrocks\": \"CREATE TABLE foo (col1 BIGINT, col2 BIGINT) ROLLUP (r1(col1, col2), r2(col1))\",\n                \"spark\": \"CREATE TABLE foo (col1 BIGINT, col2 BIGINT)\",\n                \"duckdb\": \"CREATE TABLE foo (col1 BIGINT, col2 BIGINT)\",\n                \"postgres\": \"CREATE TABLE foo (col1 BIGINT, col2 BIGINT)\",\n            },\n        )\n\n        multi_column_cluster = exp.Cluster(\n            expressions=[\n                exp.column(\"c\"),\n                exp.column(\"d\"),\n            ]\n        )\n        self.assertEqual(multi_column_cluster.sql(dialect=\"starrocks\"), \"ORDER BY (c, d)\")\n\n        single_column_cluster = exp.Cluster(expressions=[exp.column(\"c\")])\n        self.assertEqual(single_column_cluster.sql(dialect=\"starrocks\"), \"ORDER BY (c)\")\n\n        mv_properties = [\n            # partitioning in MV\n            \"PARTITION BY (DATE_FUNC(ts), region) REFRESH ASYNC\",\n            \"PARTITION BY (DATE_TRUNC('DAY', ts)) REFRESH ASYNC\",\n            \"PARTITION BY (col1, col2) REFRESH ASYNC\",\n            # MV: Refresh trigger property\n            \"REFRESH ASYNC\",\n            \"REFRESH IMMEDIATE\",\n            \"REFRESH DEFERRED\",\n            \"REFRESH DEFERRED ASYNC\",\n            \"REFRESH IMMEDIATE ASYNC\",\n            \"REFRESH DEFERRED MANUAL\",\n            \"REFRESH IMMEDIATE MANUAL\",\n            \"REFRESH IMMEDIATE START ('2025-01-01 00:00:00') EVERY (INTERVAL 5 MINUTE)\",\n            \"REFRESH IMMEDIATE ASYNC EVERY (INTERVAL 5 MINUTE)\",\n            \"REFRESH DEFERRED START ('2025-01-01 00:00:00') EVERY (INTERVAL 5 MINUTE)\",\n            \"REFRESH DEFERRED ASYNC EVERY (INTERVAL 5 MINUTE)\",\n            \"REFRESH ASYNC START ('2025-01-01 00:00:00') EVERY (INTERVAL 5 MINUTE)\",\n            \"REFRESH ASYNC EVERY (INTERVAL 5 MINUTE)\",\n        ]\n        for properties in mv_properties:\n            with self.subTest(f\"Testing refresh clause: {properties}\"):\n                self.validate_identity(f\"CREATE MATERIALIZED VIEW mv {properties} AS SELECT 1\")\n\n        # RENAME table without TO keyword\n        self.validate_identity(\"ALTER TABLE t1 RENAME t2\")\n\n    def test_identity(self):\n        self.validate_identity(\"SELECT CAST(`a`.`b` AS INT) FROM foo\")\n        self.validate_identity(\"SELECT APPROX_COUNT_DISTINCT(a) FROM x\")\n        self.validate_identity(\"SELECT [1, 2, 3]\")\n        self.validate_identity(\n            \"\"\"SELECT CAST(PARSE_JSON(fieldvalue) -> '00000000-0000-0000-0000-00000000' AS VARCHAR) AS `code` FROM (SELECT '{\"00000000-0000-0000-0000-00000000\":\"code01\"}') AS t(fieldvalue)\"\"\"\n        )\n        self.validate_identity(\n            \"SELECT text FROM example_table\", write_sql=\"SELECT `text` FROM example_table\"\n        )\n\n    def test_time(self):\n        self.validate_identity(\"TIMESTAMP('2022-01-01')\")\n        self.validate_identity(\n            \"SELECT DATE_DIFF('SECOND', '2010-11-30 23:59:59', '2010-11-30 20:58:59')\"\n        )\n        self.validate_identity(\n            \"SELECT DATE_DIFF('MINUTE', '2010-11-30 23:59:59', '2010-11-30 20:58:59')\"\n        )\n\n    def test_regex(self):\n        self.validate_all(\n            \"SELECT REGEXP(abc, '%foo%')\",\n            read={\n                \"mysql\": \"SELECT REGEXP_LIKE(abc, '%foo%')\",\n                \"starrocks\": \"SELECT REGEXP(abc, '%foo%')\",\n            },\n            write={\n                \"mysql\": \"SELECT REGEXP_LIKE(abc, '%foo%')\",\n            },\n        )\n\n    def test_unnest(self):\n        self.validate_identity(\n            \"SELECT student, score, t.unnest FROM tests CROSS JOIN LATERAL UNNEST(scores) AS t\",\n            \"SELECT student, score, t.unnest FROM tests CROSS JOIN LATERAL UNNEST(scores) AS t(unnest)\",\n        )\n        self.validate_all(\n            \"SELECT student, score, unnest FROM tests CROSS JOIN LATERAL UNNEST(scores)\",\n            write={\n                \"spark\": \"SELECT student, score, unnest FROM tests LATERAL VIEW EXPLODE(scores) unnest AS unnest\",\n                \"starrocks\": \"SELECT student, score, unnest FROM tests CROSS JOIN LATERAL UNNEST(scores) AS unnest(unnest)\",\n            },\n        )\n        self.validate_all(\n            r\"\"\"SELECT * FROM UNNEST(array['John','Jane','Jim','Jamie'], array[24,25,26,27]) AS t(name, age)\"\"\",\n            write={\n                \"postgres\": \"SELECT * FROM UNNEST(ARRAY['John', 'Jane', 'Jim', 'Jamie'], ARRAY[24, 25, 26, 27]) AS t(name, age)\",\n                \"spark\": \"SELECT * FROM INLINE(ARRAYS_ZIP(ARRAY('John', 'Jane', 'Jim', 'Jamie'), ARRAY(24, 25, 26, 27))) AS t(name, age)\",\n                \"starrocks\": \"SELECT * FROM UNNEST(['John', 'Jane', 'Jim', 'Jamie'], [24, 25, 26, 27]) AS t(name, age)\",\n            },\n        )\n\n        # Use UNNEST to convert into multiple columns\n        # see: https://docs.starrocks.io/docs/sql-reference/sql-functions/array-functions/unnest/\n        self.validate_all(\n            r\"\"\"SELECT id, t.type, t.scores FROM example_table, unnest(split(type, \";\"), scores) AS t(type,scores)\"\"\",\n            write={\n                \"postgres\": \"SELECT id, t.type, t.scores FROM example_table, UNNEST(SPLIT(type, ';'), scores) AS t(type, scores)\",\n                \"spark\": r\"\"\"SELECT id, t.type, t.scores FROM example_table LATERAL VIEW INLINE(ARRAYS_ZIP(SPLIT(type, CONCAT('\\\\Q', ';', '\\\\E')), scores)) t AS type, scores\"\"\",\n                \"databricks\": r\"\"\"SELECT id, t.type, t.scores FROM example_table LATERAL VIEW INLINE(ARRAYS_ZIP(SPLIT(type, CONCAT('\\\\Q', ';', '\\\\E')), scores)) t AS type, scores\"\"\",\n                \"starrocks\": r\"\"\"SELECT id, t.type, t.scores FROM example_table, UNNEST(SPLIT(type, ';'), scores) AS t(type, scores)\"\"\",\n                \"hive\": UnsupportedError,\n            },\n        )\n\n        self.validate_all(\n            r\"\"\"SELECT id, t.type, t.scores FROM example_table_2 CROSS JOIN LATERAL unnest(split(type, \";\"), scores) AS t(type,scores)\"\"\",\n            write={\n                \"spark\": r\"\"\"SELECT id, t.type, t.scores FROM example_table_2 LATERAL VIEW INLINE(ARRAYS_ZIP(SPLIT(type, CONCAT('\\\\Q', ';', '\\\\E')), scores)) t AS type, scores\"\"\",\n                \"starrocks\": r\"\"\"SELECT id, t.type, t.scores FROM example_table_2 CROSS JOIN LATERAL UNNEST(SPLIT(type, ';'), scores) AS t(type, scores)\"\"\",\n                \"hive\": UnsupportedError,\n            },\n        )\n\n        lateral_explode_sqls = [\n            \"SELECT id, t.col FROM tbl, UNNEST(scores) AS t(col)\",\n            \"SELECT id, t.col FROM tbl CROSS JOIN LATERAL UNNEST(scores) AS t(col)\",\n        ]\n\n        for sql in lateral_explode_sqls:\n            with self.subTest(f\"Testing Starrocks roundtrip & transpilation of: {sql}\"):\n                self.validate_all(\n                    sql,\n                    write={\n                        \"starrocks\": sql,\n                        \"spark\": \"SELECT id, t.col FROM tbl LATERAL VIEW EXPLODE(scores) t AS col\",\n                    },\n                )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE TABLE TBL(c1, c2) PROPERTIES ('prop1'=val1)\")\n        self.validate_identity(\"ANALYZE FULL TABLE TBL(c1, c2) PROPERTIES ('prop1'=val1)\")\n        self.validate_identity(\"ANALYZE SAMPLE TABLE TBL(c1, c2) PROPERTIES ('prop1'=val1)\")\n        self.validate_identity(\"ANALYZE TABLE TBL(c1, c2) WITH SYNC MODE PROPERTIES ('prop1'=val1)\")\n        self.validate_identity(\n            \"ANALYZE TABLE TBL(c1, c2) WITH ASYNC MODE PROPERTIES ('prop1'=val1)\"\n        )\n        self.validate_identity(\n            \"ANALYZE TABLE TBL UPDATE HISTOGRAM ON c1, c2 PROPERTIES ('prop1'=val1)\"\n        )\n        self.validate_identity(\n            \"ANALYZE TABLE TBL UPDATE HISTOGRAM ON c1, c2 WITH 5 BUCKETS PROPERTIES ('prop1'=val1)\"\n        )\n        self.validate_identity(\n            \"ANALYZE TABLE TBL UPDATE HISTOGRAM ON c1, c2 WITH SYNC MODE WITH 5 BUCKETS PROPERTIES ('prop1'=val1)\"\n        )\n        self.validate_identity(\n            \"ANALYZE TABLE TBL UPDATE HISTOGRAM ON c1, c2 WITH ASYNC MODE WITH 5 BUCKETS PROPERTIES ('prop1'=val1)\"\n        )\n\n    def test_between(self):\n        self.validate_all(\n            \"SELECT * FROM t WHERE a BETWEEN 1 AND 5\",\n            write={\n                \"starrocks\": \"SELECT * FROM t WHERE a BETWEEN 1 AND 5\",\n                \"mysql\": \"SELECT * FROM t WHERE a BETWEEN 1 AND 5\",\n            },\n        )\n        self.validate_identity(\"SELECT a BETWEEN 1 AND 5 FROM t\")\n        self.validate_identity(\n            \"DELETE FROM t WHERE a BETWEEN b AND c\",\n            \"DELETE FROM t WHERE a >= b AND a <= c\",\n        )\n        self.validate_identity(\n            \"DELETE FROM t WHERE a BETWEEN 1 AND 10 AND b BETWEEN 20 AND 30 OR c BETWEEN 'x' AND 'z'\",\n            \"DELETE FROM t WHERE a >= 1 AND a <= 10 AND b >= 20 AND b <= 30 OR c >= 'x' AND c <= 'z'\",\n        )\n\n    def test_partition(self):\n        # Column-based partitioning\n        for cols in \"col1\", \"col1, col2\":\n            with self.subTest(f\"Testing PARTITION BY with {cols}\"):\n                self.validate_identity(\n                    f\"CREATE TABLE test_table (col1 INT, col2 DATE) PARTITION BY ({cols})\"\n                )\n                self.validate_identity(\n                    f\"CREATE TABLE test_table (col1 INT, col2 DATE) PARTITION BY {cols}\",\n                    f\"CREATE TABLE test_table (col1 INT, col2 DATE) PARTITION BY ({cols})\",\n                )\n\n        # Expr-based partitioning\n        self.validate_identity(\n            \"CREATE TABLE test_table (col2 DATE) PARTITION BY DATE_TRUNC('DAY', col2)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (col2 BIGINT) PARTITION BY FROM_UNIXTIME(col2, '%Y%m%d')\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 STRING, col2 BIGINT) PARTITION BY FROM_UNIXTIME(col2, '%Y%m%d'), col1\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 BIGINT, col2 DATE) PARTITION BY FROM_UNIXTIME(col2, '%Y%m%d'), DATE_TRUNC('DAY', col1)\"\n        )\n\n        # LIST partitioning\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 STRING) PARTITION BY LIST (col1) (PARTITION pLos_Angeles VALUES IN ('Los Angeles'), PARTITION pSan_Francisco VALUES IN ('San Francisco'))\"\n        )\n\n        # Multi-column LIST partitioning\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 DATE, col2 STRING) PARTITION BY LIST (col1, col2) (PARTITION p1 VALUES IN (('2022-04-01', 'LA'), ('2022-04-01', 'SF')))\"\n        )\n\n        # RANGE partitioning with explicit values\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 DATE) PARTITION BY RANGE (col1) (PARTITION p1 VALUES LESS THAN ('2020-01-31'), PARTITION p2 VALUES LESS THAN ('2020-02-29'), PARTITION p3 VALUES LESS THAN ('2020-03-31'))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 STRING) PARTITION BY RANGE (STR2DATE(col1, '%Y-%m-%d')) (PARTITION p1 VALUES LESS THAN ('2021-01-01'), PARTITION p2 VALUES LESS THAN ('2021-01-02'), PARTITION p3 VALUES LESS THAN ('2021-01-03'))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 DATE) PARTITION BY RANGE (col1) (PARTITION p1 VALUES LESS THAN ('2020-01-31'), PARTITION p_max VALUES LESS THAN (MAXVALUE))\"\n        )\n\n        # RANGE partitioning with START/END/EVERY\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 BIGINT) PARTITION BY RANGE (col1) (START ('1') END ('10') EVERY (1), START ('10') END ('100') EVERY (10))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE test_table (col1 DATE) PARTITION BY RANGE (col1) (START ('2019-01-01') END ('2021-01-01') EVERY (INTERVAL 1 YEAR), START ('2021-01-01') END ('2021-05-01') EVERY (INTERVAL 1 MONTH), START ('2021-05-01') END ('2021-05-04') EVERY (INTERVAL 1 DAY))\"\n        )\n"
  },
  {
    "path": "tests/dialects/test_tableau.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestTableau(Validator):\n    dialect = \"tableau\"\n\n    def test_tableau(self):\n        self.validate_all(\n            \"[x]\",\n            write={\n                \"hive\": \"`x`\",\n                \"tableau\": \"[x]\",\n            },\n        )\n        self.validate_all(\n            '\"x\"',\n            write={\n                \"hive\": \"'x'\",\n                \"tableau\": \"'x'\",\n            },\n        )\n\n        self.validate_all(\n            \"IF x = 'a' THEN y ELSE NULL END\",\n            read={\n                \"presto\": \"IF(x = 'a', y, NULL)\",\n            },\n            write={\n                \"presto\": \"IF(x = 'a', y, NULL)\",\n                \"hive\": \"IF(x = 'a', y, NULL)\",\n                \"tableau\": \"IF x = 'a' THEN y ELSE NULL END\",\n            },\n        )\n        self.validate_all(\n            \"IFNULL(a, 0)\",\n            read={\n                \"presto\": \"COALESCE(a, 0)\",\n            },\n            write={\n                \"presto\": \"COALESCE(a, 0)\",\n                \"hive\": \"COALESCE(a, 0)\",\n                \"tableau\": \"IFNULL(a, 0)\",\n            },\n        )\n        self.validate_all(\n            \"COUNTD(a)\",\n            read={\n                \"presto\": \"COUNT(DISTINCT a)\",\n            },\n            write={\n                \"presto\": \"COUNT(DISTINCT a)\",\n                \"hive\": \"COUNT(DISTINCT a)\",\n                \"tableau\": \"COUNTD(a)\",\n            },\n        )\n        self.validate_all(\n            \"COUNTD((a))\",\n            read={\n                \"presto\": \"COUNT(DISTINCT(a))\",\n            },\n            write={\n                \"presto\": \"COUNT(DISTINCT (a))\",\n                \"hive\": \"COUNT(DISTINCT (a))\",\n                \"tableau\": \"COUNTD((a))\",\n            },\n        )\n        self.validate_all(\n            \"COUNT(a)\",\n            read={\n                \"presto\": \"COUNT(a)\",\n            },\n            write={\n                \"presto\": \"COUNT(a)\",\n                \"hive\": \"COUNT(a)\",\n                \"tableau\": \"COUNT(a)\",\n            },\n        )\n"
  },
  {
    "path": "tests/dialects/test_teradata.py",
    "content": "from sqlglot import exp\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestTeradata(Validator):\n    dialect = \"teradata\"\n\n    def test_teradata(self):\n        self.validate_all(\n            \"RANDOM(l, u)\",\n            write={\n                \"\": \"(u - l) * RAND() + l\",\n                \"teradata\": \"RANDOM(l, u)\",\n            },\n        )\n        self.validate_identity(\"TO_NUMBER(expr, fmt, nlsparam)\")\n        self.validate_identity(\"SELECT TOP 10 * FROM tbl\")\n        self.validate_identity(\"SELECT * FROM tbl SAMPLE 5\")\n        self.validate_identity(\n            \"SELECT * FROM tbl SAMPLE 0.33, .25, .1\",\n            \"SELECT * FROM tbl SAMPLE 0.33, 0.25, 0.1\",\n        )\n\n        self.validate_all(\n            \"DATABASE tduser\",\n            read={\n                \"databricks\": \"USE tduser\",\n            },\n            write={\n                \"databricks\": \"USE tduser\",\n                \"teradata\": \"DATABASE tduser\",\n            },\n        )\n\n        self.validate_identity(\"SELECT 0x1d\", \"SELECT X'1d'\")\n        self.validate_identity(\"SELECT X'1D'\", \"SELECT X'1D'\")\n        self.validate_identity(\"SELECT x'1d'\", \"SELECT X'1d'\")\n\n        self.validate_identity(\n            \"RENAME TABLE emp TO employee\", check_command_warning=True\n        ).assert_is(exp.Command)\n\n    def test_translate(self):\n        self.validate_identity(\"TRANSLATE(x USING LATIN_TO_UNICODE)\")\n        self.validate_identity(\"TRANSLATE(x USING LATIN_TO_UNICODE WITH ERROR)\")\n\n    def test_locking(self):\n        self.validate_identity(\"LOCKING ROW FOR ACCESS SELECT * FROM table1\")\n        self.validate_identity(\"LOCKING TABLE table1 FOR ACCESS SELECT col1, col2 FROM table1\")\n        self.validate_identity(\"LOCKING ROW FOR SHARE SELECT * FROM table1\")\n        self.validate_identity(\"LOCKING DATABASE db1 FOR READ SELECT * FROM table1\")\n        self.validate_identity(\"LOCKING ROW FOR EXCLUSIVE SELECT * FROM table1\")\n        self.validate_identity(\"LOCKING VIEW view1 FOR ACCESS SELECT * FROM view1\")\n\n        # Test with more complex SELECT statements\n        self.validate_identity(\n            \"LOCKING ROW FOR ACCESS SELECT col1, col2 FROM table1 WHERE col1 > 10\"\n        )\n        self.validate_identity(\n            \"LOCKING TABLE table1 FOR ACCESS SELECT * FROM table1 JOIN table2 ON table1.id = table2.id\"\n        )\n\n        # Test that it still works in CREATE VIEW context (regression test)\n        self.validate_identity(\n            \"CREATE VIEW view_b AS LOCKING ROW FOR ACCESS SELECT COL1, COL2 FROM table_b\"\n        )\n\n    def test_update(self):\n        self.validate_all(\n            \"UPDATE A FROM schema.tableA AS A, (SELECT col1 FROM schema.tableA GROUP BY col1) AS B SET col2 = '' WHERE A.col1 = B.col1\",\n            write={\n                \"teradata\": \"UPDATE A FROM schema.tableA AS A, (SELECT col1 FROM schema.tableA GROUP BY col1) AS B SET col2 = '' WHERE A.col1 = B.col1\",\n                \"mysql\": \"UPDATE A JOIN `schema`.tableA AS A ON TRUE JOIN (SELECT col1 FROM `schema`.tableA GROUP BY col1) AS B ON TRUE SET A.col2 = '' WHERE A.col1 = B.col1\",\n            },\n        )\n\n    def test_statistics(self):\n        self.validate_identity(\"COLLECT STATISTICS ON tbl INDEX(col)\", check_command_warning=True)\n        self.validate_identity(\"COLLECT STATS ON tbl COLUMNS(col)\", check_command_warning=True)\n        self.validate_identity(\"COLLECT STATS COLUMNS(col) ON tbl\", check_command_warning=True)\n        self.validate_identity(\"HELP STATISTICS personel.employee\", check_command_warning=True)\n        self.validate_identity(\n            \"HELP STATISTICS personnel.employee FROM my_qcd\", check_command_warning=True\n        )\n\n    def test_create(self):\n        self.validate_identity(\n            \"REPLACE VIEW view_b (COL1, COL2) AS LOCKING ROW FOR ACCESS SELECT COL1, COL2 FROM table_b\",\n            \"CREATE OR REPLACE VIEW view_b (COL1, COL2) AS LOCKING ROW FOR ACCESS SELECT COL1, COL2 FROM table_b\",\n        )\n        self.validate_identity(\n            \"REPLACE VIEW view_b (COL1, COL2) AS LOCKING ROW FOR ACCESS SELECT COL1, COL2 FROM table_b\",\n            \"CREATE OR REPLACE VIEW view_b (COL1, COL2) AS LOCKING ROW FOR ACCESS SELECT COL1, COL2 FROM table_b\",\n        )\n        self.validate_identity(\"CREATE TABLE x (y INT) PRIMARY INDEX (y) PARTITION BY y INDEX (y)\")\n        self.validate_identity(\"CREATE TABLE x (y INT) PARTITION BY y INDEX (y)\")\n        self.validate_identity(\n            \"CREATE MULTISET VOLATILE TABLE my_table (id INT) PRIMARY INDEX (id) ON COMMIT PRESERVE ROWS\"\n        )\n        self.validate_identity(\n            \"CREATE SET VOLATILE TABLE my_table (id INT) PRIMARY INDEX (id) ON COMMIT DELETE ROWS\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a (b INT) PRIMARY INDEX (y) PARTITION BY RANGE_N(b BETWEEN 'a', 'b' AND 'c' EACH '1')\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a (b INT) PARTITION BY RANGE_N(b BETWEEN 0, 1 AND 2 EACH 1)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a (b INT) PARTITION BY RANGE_N(b BETWEEN *, 1 AND * EACH b) INDEX (a)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a, NO FALLBACK PROTECTION, NO LOG, NO JOURNAL, CHECKSUM=ON, NO MERGEBLOCKRATIO, BLOCKCOMPRESSION=ALWAYS (a INT)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a, NO FALLBACK PROTECTION, NO LOG, NO JOURNAL, CHECKSUM=ON, NO MERGEBLOCKRATIO, BLOCKCOMPRESSION=ALWAYS (a INT)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a, WITH JOURNAL TABLE=x.y.z, CHECKSUM=OFF, MERGEBLOCKRATIO=1, DATABLOCKSIZE=10 KBYTES (a INT)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a, BEFORE JOURNAL, AFTER JOURNAL, FREESPACE=1, DEFAULT DATABLOCKSIZE, BLOCKCOMPRESSION=DEFAULT (a INT)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a, DUAL JOURNAL, DUAL AFTER JOURNAL, MERGEBLOCKRATIO=1 PERCENT, DATABLOCKSIZE=10 KILOBYTES (a INT)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE a, DUAL BEFORE JOURNAL, LOCAL AFTER JOURNAL, MAXIMUM DATABLOCKSIZE, BLOCKCOMPRESSION=AUTOTEMP(c1 INT) (a INT)\"\n        )\n        self.validate_identity(\n            \"CREATE VOLATILE MULTISET TABLE a, NOT LOCAL AFTER JOURNAL, FREESPACE=1 PERCENT, DATABLOCKSIZE=10 BYTES, WITH NO CONCURRENT ISOLATED LOADING FOR ALL (a INT)\"\n        )\n        self.validate_identity(\n            \"CREATE VOLATILE SET TABLE example1 AS (SELECT col1, col2, col3 FROM table1) WITH DATA PRIMARY INDEX (col1) ON COMMIT PRESERVE ROWS\"\n        )\n        self.validate_identity(\n            \"CREATE SET GLOBAL TEMPORARY TABLE a, NO BEFORE JOURNAL, NO AFTER JOURNAL, MINIMUM DATABLOCKSIZE, BLOCKCOMPRESSION=NEVER (a INT)\"\n        )\n        self.validate_all(\n            \"\"\"\n            CREATE SET TABLE test, NO FALLBACK, NO BEFORE JOURNAL, NO AFTER JOURNAL,\n            CHECKSUM = DEFAULT (x INT, y INT, z CHAR(30), a INT, b DATE, e INT)\n            PRIMARY INDEX (a),\n            INDEX(x, y)\n            \"\"\",\n            write={\n                \"teradata\": \"CREATE SET TABLE test, NO FALLBACK, NO BEFORE JOURNAL, NO AFTER JOURNAL, CHECKSUM=DEFAULT (x INT, y INT, z CHAR(30), a INT, b DATE, e INT) PRIMARY INDEX (a) INDEX (x, y)\",\n            },\n        )\n        self.validate_all(\n            \"REPLACE VIEW a AS (SELECT b FROM c)\",\n            write={\"teradata\": \"CREATE OR REPLACE VIEW a AS (SELECT b FROM c)\"},\n        )\n        self.validate_all(\n            \"CREATE VOLATILE TABLE a\",\n            write={\n                \"teradata\": \"CREATE VOLATILE TABLE a\",\n                \"bigquery\": \"CREATE TABLE a\",\n                \"clickhouse\": \"CREATE TABLE a\",\n                \"databricks\": \"CREATE TABLE a\",\n                \"drill\": \"CREATE TABLE a\",\n                \"duckdb\": \"CREATE TABLE a\",\n                \"hive\": \"CREATE TABLE a\",\n                \"mysql\": \"CREATE TABLE a\",\n                \"oracle\": \"CREATE TABLE a\",\n                \"postgres\": \"CREATE TABLE a\",\n                \"presto\": \"CREATE TABLE a\",\n                \"redshift\": \"CREATE TABLE a\",\n                \"snowflake\": \"CREATE TABLE a\",\n                \"spark\": \"CREATE TABLE a\",\n                \"sqlite\": \"CREATE TABLE a\",\n                \"starrocks\": \"CREATE TABLE a\",\n                \"tableau\": \"CREATE TABLE a\",\n                \"trino\": \"CREATE TABLE a\",\n                \"tsql\": \"CREATE TABLE a\",\n            },\n        )\n        self.validate_identity(\n            \"CREATE TABLE db.foo (id INT NOT NULL, valid_date DATE FORMAT 'YYYY-MM-DD', measurement INT COMPRESS)\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE db.foo (id INT NOT NULL, valid_date DATE FORMAT 'YYYY-MM-DD', measurement INT COMPRESS (1, 2, 3))\"\n        )\n        self.validate_identity(\n            \"CREATE TABLE db.foo (id INT NOT NULL, valid_date DATE FORMAT 'YYYY-MM-DD' COMPRESS (CAST('9999-09-09' AS DATE)), measurement INT)\"\n        )\n\n    def test_insert(self):\n        self.validate_all(\n            \"INS INTO x SELECT * FROM y\", write={\"teradata\": \"INSERT INTO x SELECT * FROM y\"}\n        )\n\n    def test_mod(self):\n        self.validate_all(\"a MOD b\", write={\"teradata\": \"a MOD b\", \"mysql\": \"a % b\"})\n\n    def test_power(self):\n        self.validate_all(\"a ** b\", write={\"teradata\": \"a ** b\", \"mysql\": \"POWER(a, b)\"})\n\n    def test_abbrev(self):\n        self.validate_identity(\"a LT b\", \"a < b\")\n        self.validate_identity(\"a LE b\", \"a <= b\")\n        self.validate_identity(\"a GT b\", \"a > b\")\n        self.validate_identity(\"a GE b\", \"a >= b\")\n        self.validate_identity(\"a ^= b\", \"a <> b\")\n        self.validate_identity(\"a NE b\", \"a <> b\")\n        self.validate_identity(\"a NOT= b\", \"a <> b\")\n        self.validate_identity(\"a EQ b\", \"a = b\")\n        self.validate_identity(\"SEL a FROM b\", \"SELECT a FROM b\")\n        self.validate_identity(\n            \"SELECT col1, col2 FROM dbc.table1 WHERE col1 EQ 'value1' MINUS SELECT col1, col2 FROM dbc.table2\",\n            \"SELECT col1, col2 FROM dbc.table1 WHERE col1 = 'value1' EXCEPT SELECT col1, col2 FROM dbc.table2\",\n        )\n        self.validate_identity(\"UPD a SET b = 1\", \"UPDATE a SET b = 1\")\n        self.validate_identity(\"DEL FROM a\", \"DELETE FROM a\")\n\n    def test_datatype(self):\n        self.validate_all(\n            \"CREATE TABLE z (a ST_GEOMETRY(1))\",\n            write={\n                \"teradata\": \"CREATE TABLE z (a ST_GEOMETRY(1))\",\n                \"redshift\": \"CREATE TABLE z (a GEOMETRY(1))\",\n            },\n        )\n\n        self.validate_identity(\"CREATE TABLE z (a SYSUDTLIB.INT)\")\n\n    def test_cast(self):\n        self.validate_all(\n            \"CAST('1992-01' AS DATE FORMAT 'YYYY-DD')\",\n            read={\n                \"bigquery\": \"CAST('1992-01' AS DATE FORMAT 'YYYY-DD')\",\n            },\n            write={\n                \"teradata\": \"CAST('1992-01' AS DATE FORMAT 'YYYY-DD')\",\n                \"bigquery\": \"PARSE_DATE('%Y-%d', '1992-01')\",\n                \"databricks\": \"TO_DATE('1992-01', 'yyyy-dd')\",\n                \"mysql\": \"STR_TO_DATE('1992-01', '%Y-%d')\",\n                \"spark\": \"TO_DATE('1992-01', 'yyyy-dd')\",\n                \"\": \"STR_TO_DATE('1992-01', '%Y-%d')\",\n            },\n        )\n        self.validate_identity(\"CAST('1992-01' AS FORMAT 'YYYY-DD')\")\n\n        self.validate_all(\n            \"TRYCAST('-2.5' AS DECIMAL(5, 2))\",\n            read={\n                \"snowflake\": \"TRY_CAST('-2.5' AS DECIMAL(5, 2))\",\n            },\n            write={\n                \"snowflake\": \"TRY_CAST('-2.5' AS DECIMAL(5, 2))\",\n                \"teradata\": \"TRYCAST('-2.5' AS DECIMAL(5, 2))\",\n            },\n        )\n\n    def test_format_override(self):\n        # Teradata column format overrides use the `(FORMAT <format_string>)` syntax.\n        # https://docs.teradata.com/r/Enterprise_IntelliFlex_VMware/SQL-Data-Types-and-Literals/Data-Type-Formats-and-Format-Phrases/FORMAT\n        self.validate_identity(\"SELECT ('a' || 'b') (FORMAT '...')\")\n        self.validate_identity(\"SELECT Col1 (FORMAT '+9999') FROM Test1\")\n        self.validate_identity(\"SELECT date_col (FORMAT 'YYYY-MM-DD') FROM t\")\n        self.validate_identity(\n            \"SELECT CAST(Col1 AS INTEGER) FROM Test1\",\n            \"SELECT CAST(Col1 AS INT) FROM Test1\",\n        )\n\n    def test_time(self):\n        self.validate_identity(\"CAST(CURRENT_TIMESTAMP(6) AS TIMESTAMP WITH TIME ZONE)\")\n\n        self.validate_all(\n            \"CURRENT_TIMESTAMP\",\n            read={\n                \"teradata\": \"CURRENT_TIMESTAMP\",\n                \"snowflake\": \"CURRENT_TIMESTAMP()\",\n            },\n        )\n\n        self.validate_all(\n            \"SELECT '2023-01-01' + INTERVAL '5' YEAR\",\n            read={\n                \"teradata\": \"SELECT '2023-01-01' + INTERVAL '5' YEAR\",\n                \"snowflake\": \"SELECT DATEADD(YEAR, 5, '2023-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '2023-01-01' - INTERVAL '5' YEAR\",\n            read={\n                \"teradata\": \"SELECT '2023-01-01' - INTERVAL '5' YEAR\",\n                \"snowflake\": \"SELECT DATEADD(YEAR, -5, '2023-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '2023-01-01' - INTERVAL '5' YEAR\",\n            read={\n                \"teradata\": \"SELECT '2023-01-01' - INTERVAL '5' YEAR\",\n                \"sqlite\": \"SELECT DATE_SUB('2023-01-01', 5, YEAR)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '2023-01-01' + INTERVAL '5' YEAR\",\n            read={\n                \"teradata\": \"SELECT '2023-01-01' + INTERVAL '5' YEAR\",\n                \"sqlite\": \"SELECT DATE_SUB('2023-01-01', -5, YEAR)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT (90 * INTERVAL '1' DAY)\",\n            read={\n                \"teradata\": \"SELECT (90 * INTERVAL '1' DAY)\",\n                \"snowflake\": \"SELECT INTERVAL '1' QUARTER\",\n            },\n        )\n        self.validate_all(\n            \"SELECT (7 * INTERVAL '1' DAY)\",\n            read={\n                \"teradata\": \"SELECT (7 * INTERVAL '1' DAY)\",\n                \"snowflake\": \"SELECT INTERVAL '1' WEEK\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '2023-01-01' + (90 * INTERVAL '5' DAY)\",\n            read={\n                \"teradata\": \"SELECT '2023-01-01' + (90 * INTERVAL '5' DAY)\",\n                \"snowflake\": \"SELECT DATEADD(QUARTER, 5, '2023-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT '2023-01-01' + (7 * INTERVAL '5' DAY)\",\n            read={\n                \"teradata\": \"SELECT '2023-01-01' + (7 * INTERVAL '5' DAY)\",\n                \"snowflake\": \"SELECT DATEADD(WEEK, 5, '2023-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"CAST(TO_CHAR(x, 'Q') AS INT)\",\n            read={\n                \"teradata\": \"CAST(TO_CHAR(x, 'Q') AS INT)\",\n                \"snowflake\": \"DATE_PART(QUARTER, x)\",\n                \"bigquery\": \"EXTRACT(QUARTER FROM x)\",\n            },\n        )\n        self.validate_all(\n            \"EXTRACT(MONTH FROM x)\",\n            read={\n                \"teradata\": \"EXTRACT(MONTH FROM x)\",\n                \"snowflake\": \"DATE_PART(MONTH, x)\",\n                \"bigquery\": \"EXTRACT(MONTH FROM x)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(TO_CHAR(x, 'Q') AS INT)\",\n            read={\n                \"snowflake\": \"quarter(x)\",\n                \"teradata\": \"CAST(TO_CHAR(x, 'Q') AS INT)\",\n            },\n        )\n\n    def test_query_band(self):\n        self.validate_identity(\"SET QUERY_BAND = 'app=myapp;' FOR SESSION\")\n        self.validate_identity(\"SET QUERY_BAND = 'app=myapp;user=john;' FOR TRANSACTION\")\n        self.validate_identity(\"SET QUERY_BAND = 'priority=high;' UPDATE FOR SESSION\")\n        self.validate_identity(\"SET QUERY_BAND = 'workload=batch;' UPDATE FOR TRANSACTION\")\n        self.validate_identity(\"SET QUERY_BAND = 'org=Finance;report=Fin123;' FOR SESSION\")\n        self.validate_identity(\"SET QUERY_BAND = NONE FOR SESSION\")\n        self.validate_identity(\"SET QUERY_BAND = NONE FOR SESSION VOLATILE\")\n        self.validate_identity(\"SET QUERY_BAND = 'priority=high;' UPDATE FOR SESSION VOLATILE\")\n        self.validate_identity(\n            \"SET QUERY_BAND = 'NONE' FOR SESSION\"\n        )  # quoted NONE should remain quoted\n        self.validate_identity(\"SET QUERY_BAND = '' FOR SESSION\")\n"
  },
  {
    "path": "tests/dialects/test_trino.py",
    "content": "from tests.dialects.test_dialect import Validator\n\n\nclass TestTrino(Validator):\n    dialect = \"trino\"\n\n    def test_trino(self):\n        self.validate_identity(\"REFRESH MATERIALIZED VIEW mynamespace.test_view\")\n        self.validate_identity(\"JSON_QUERY(m.properties, 'lax $.area' OMIT QUOTES NULL ON ERROR)\")\n        self.validate_identity(\"JSON_EXTRACT(content, json_path)\")\n        self.validate_identity(\"JSON_QUERY(content, 'lax $.HY.*')\")\n        self.validate_identity(\"JSON_QUERY(content, 'strict $.HY.*' WITH WRAPPER)\")\n        self.validate_identity(\"JSON_QUERY(content, 'strict $.HY.*' WITH ARRAY WRAPPER)\")\n        self.validate_identity(\"JSON_QUERY(content, 'strict $.HY.*' WITH UNCONDITIONAL WRAPPER)\")\n        self.validate_identity(\"JSON_QUERY(content, 'strict $.HY.*' WITHOUT CONDITIONAL WRAPPER)\")\n        self.validate_identity(\"JSON_QUERY(description, 'strict $.comment' KEEP QUOTES)\")\n        self.validate_identity(\n            \"JSON_QUERY(description, 'strict $.comment' OMIT QUOTES ON SCALAR STRING)\"\n        )\n        self.validate_identity(\n            \"JSON_QUERY(content, 'strict $.HY.*' WITH UNCONDITIONAL WRAPPER KEEP QUOTES)\"\n        )\n        self.validate_identity(\n            \"SELECT TIMESTAMP '2012-10-31 01:00 -2'\",\n            \"SELECT CAST('2012-10-31 01:00 -2' AS TIMESTAMP WITH TIME ZONE)\",\n        )\n        self.validate_identity(\n            \"SELECT TIMESTAMP '2012-10-31 01:00 +2'\",\n            \"SELECT CAST('2012-10-31 01:00 +2' AS TIMESTAMP WITH TIME ZONE)\",\n        )\n\n        self.validate_all(\n            \"SELECT TIMESTAMP '2012-10-31 01:00:00 +02:00'\",\n            write={\n                \"duckdb\": \"SELECT CAST('2012-10-31 01:00:00 +02:00' AS TIMESTAMPTZ)\",\n                \"trino\": \"SELECT CAST('2012-10-31 01:00:00 +02:00' AS TIMESTAMP WITH TIME ZONE)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT('%s', 123)\",\n            write={\n                \"duckdb\": \"SELECT FORMAT('{}', 123)\",\n                \"snowflake\": \"SELECT TO_CHAR(123)\",\n                \"trino\": \"SELECT FORMAT('%s', 123)\",\n            },\n        )\n\n        self.validate_identity(\n            \"SELECT * FROM tbl MATCH_RECOGNIZE (PARTITION BY id ORDER BY col MEASURES FIRST(col, 2) AS col1, LAST(col, 2) AS col2 PATTERN (B* A) DEFINE A AS col = 1)\"\n        )\n\n        self.validate_identity(\"SELECT VERSION()\")\n\n    def test_listagg(self):\n        self.validate_identity(\n            \"SELECT LISTAGG(DISTINCT col, ',') WITHIN GROUP (ORDER BY col ASC) FROM tbl\"\n        )\n        self.validate_identity(\n            \"SELECT LISTAGG(col, '; ' ON OVERFLOW ERROR) WITHIN GROUP (ORDER BY col ASC) FROM tbl\"\n        )\n        self.validate_identity(\n            \"SELECT LISTAGG(col, '; ' ON OVERFLOW TRUNCATE WITH COUNT) WITHIN GROUP (ORDER BY col ASC) FROM tbl\"\n        )\n        self.validate_identity(\n            \"SELECT LISTAGG(col, '; ' ON OVERFLOW TRUNCATE WITHOUT COUNT) WITHIN GROUP (ORDER BY col ASC) FROM tbl\"\n        )\n        self.validate_identity(\n            \"SELECT LISTAGG(col, '; ' ON OVERFLOW TRUNCATE '...' WITH COUNT) WITHIN GROUP (ORDER BY col ASC) FROM tbl\"\n        )\n        self.validate_identity(\n            \"SELECT LISTAGG(col, '; ' ON OVERFLOW TRUNCATE '...' WITHOUT COUNT) WITHIN GROUP (ORDER BY col ASC) FROM tbl\"\n        )\n        self.validate_identity(\n            \"SELECT LISTAGG(col) WITHIN GROUP (ORDER BY col DESC) FROM tbl\",\n            \"SELECT LISTAGG(col, ',') WITHIN GROUP (ORDER BY col DESC) FROM tbl\",\n        )\n\n    def test_trim(self):\n        self.validate_identity(\"SELECT TRIM('!' FROM '!foo!')\")\n        self.validate_identity(\"SELECT TRIM(BOTH '$' FROM '$var$')\")\n        self.validate_identity(\"SELECT TRIM(TRAILING 'ER' FROM UPPER('worker'))\")\n        self.validate_identity(\n            \"SELECT TRIM(LEADING FROM '  abcd')\",\n            \"SELECT LTRIM('  abcd')\",\n        )\n        self.validate_identity(\n            \"SELECT TRIM('!foo!', '!')\",\n            \"SELECT TRIM('!' FROM '!foo!')\",\n        )\n\n    def test_ddl(self):\n        self.validate_identity(\"ALTER TABLE users RENAME TO people\")\n        self.validate_identity(\"ALTER TABLE IF EXISTS users RENAME TO people\")\n        self.validate_identity(\"ALTER TABLE users ADD COLUMN zip VARCHAR\")\n        self.validate_identity(\"ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS zip VARCHAR\")\n        self.validate_identity(\"ALTER TABLE users DROP COLUMN zip\")\n        self.validate_identity(\"ALTER TABLE IF EXISTS users DROP COLUMN IF EXISTS zip\")\n        self.validate_identity(\"ALTER TABLE users RENAME COLUMN id TO user_id\")\n        self.validate_identity(\"ALTER TABLE IF EXISTS users RENAME COLUMN IF EXISTS id TO user_id\")\n        self.validate_identity(\"ALTER TABLE users ALTER COLUMN id SET DATA TYPE BIGINT\")\n        self.validate_identity(\"ALTER TABLE users ALTER COLUMN id DROP NOT NULL\")\n        self.validate_identity(\n            \"ALTER TABLE people SET AUTHORIZATION alice\", check_command_warning=True\n        )\n        self.validate_identity(\n            \"ALTER TABLE people SET AUTHORIZATION ROLE PUBLIC\", check_command_warning=True\n        )\n        self.validate_identity(\n            \"ALTER TABLE people SET PROPERTIES x = 'y'\", check_command_warning=True\n        )\n        self.validate_identity(\n            \"ALTER TABLE people SET PROPERTIES foo = 123, 'foo bar' = 456\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"ALTER TABLE people SET PROPERTIES x = DEFAULT\", check_command_warning=True\n        )\n        self.validate_identity(\"ALTER VIEW people RENAME TO users\")\n        self.validate_identity(\n            \"ALTER VIEW people SET AUTHORIZATION alice\", check_command_warning=True\n        )\n        self.validate_identity(\"CREATE SCHEMA foo WITH (LOCATION='s3://bucket/foo')\")\n        self.validate_identity(\n            \"CREATE TABLE foo.bar WITH (LOCATION='s3://bucket/foo/bar') AS SELECT 1\"\n        )\n\n        # Hive connector syntax (partitioned_by)\n        self.validate_identity(\n            \"CREATE TABLE foo (a VARCHAR, b INTEGER, c DATE) WITH (PARTITIONED_BY=ARRAY['a', 'b'])\"\n        )\n        self.validate_identity(\n            'CREATE TABLE \"foo\" (\"a\" VARCHAR, \"b\" INTEGER, \"c\" DATE) WITH (PARTITIONED_BY=ARRAY[\\'a\\', \\'b\\'])',\n            identify=True,\n        )\n\n        # Iceberg connector syntax (partitioning, can contain Iceberg transform expressions)\n        self.validate_identity(\n            \"CREATE TABLE foo (a VARCHAR, b INTEGER, c DATE) WITH (PARTITIONING=ARRAY['a', 'bucket(4, b)', 'month(c)'])\",\n        )\n        self.validate_identity(\n            'CREATE TABLE \"foo\" (\"a\" VARCHAR, \"b\" INTEGER, \"c\" DATE) WITH (PARTITIONING=ARRAY[\\'a\\', \\'bucket(4, b)\\', \\'month(c)\\'])',\n            identify=True,\n        )\n\n    def test_analyze(self):\n        self.validate_identity(\"ANALYZE tbl\")\n        self.validate_identity(\"ANALYZE tbl WITH (prop1=val1, prop2=val2)\")\n\n    def test_json_value(self):\n        self.validate_identity(\n            \"JSON_VALUE(jl.extra_attributes, 'lax $.amount_source' RETURNING VARCHAR)\"\n        )\n\n        json_doc = \"\"\"'{\"item\": \"shoes\", \"price\": \"49.95\"}'\"\"\"\n        self.validate_identity(f\"\"\"SELECT JSON_VALUE({json_doc}, 'strict $.price')\"\"\")\n        self.validate_identity(\n            f\"\"\"SELECT JSON_VALUE({json_doc}, 'lax $.price' RETURNING DECIMAL(4, 2))\"\"\"\n        )\n\n        for on_option in (\"NULL\", \"ERROR\", \"DEFAULT 1\"):\n            self.validate_identity(\n                f\"\"\"SELECT JSON_VALUE({json_doc}, 'lax $.price' RETURNING DECIMAL(4, 2) {on_option} ON EMPTY {on_option} ON ERROR) AS price\"\"\"\n            )\n\n    def test_array_first(self):\n        self.validate_identity(\"SELECT ARRAY_FIRST(ARRAY['a', 'b']) FROM tbl\")\n        self.validate_identity(\"SELECT ARRAY_FIRST(ARRAY['a', 'b'], x -> x = 'b') FROM tbl\")\n"
  },
  {
    "path": "tests/dialects/test_tsql.py",
    "content": "from sqlglot import exp, parse_one\nfrom sqlglot.errors import ParseError, UnsupportedError\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom tests.dialects.test_dialect import Validator\n\n\nclass TestTSQL(Validator):\n    dialect = \"tsql\"\n\n    def test_tsql(self):\n        self.validate_all(\n            \"WITH x AS (SELECT 1 AS [1]) SELECT TOP 0 * FROM (SELECT * FROM x UNION SELECT * FROM x) AS _l_0 ORDER BY 1\",\n            read={\n                \"\": \"WITH x AS (SELECT 1) SELECT * FROM x UNION SELECT * FROM x ORDER BY 1 LIMIT 0\",\n            },\n        )\n\n        # https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/ms187879(v=sql.105)?redirectedfrom=MSDN\n        # tsql allows .. which means use the default schema\n        self.validate_identity(\"SELECT * FROM a..b\")\n\n        self.validate_identity(\"SELECT ATN2(x, y)\")\n        self.validate_identity(\"SELECT EXP(1)\")\n        self.validate_identity(\"SELECT SYSDATETIMEOFFSET()\")\n        self.validate_identity(\"SELECT COMPRESS('Hello World')\")\n        self.validate_identity(\"GO\").assert_is(exp.Command)\n        self.validate_identity(\"SELECT go\").selects[0].assert_is(exp.Column)\n        self.validate_identity(\"CREATE view a.b.c\", \"CREATE VIEW b.c\")\n        self.validate_identity(\"DROP view a.b.c\", \"DROP VIEW b.c\")\n        self.validate_identity(\"ROUND(x, 1, 0)\")\n        self.validate_identity(\n            \"EXEC MyProc @id = 7, @name = 'Lochristi'\",\n            \"EXECUTE MyProc @id = 7, @name = 'Lochristi'\",\n        )\n        self.validate_identity(\"SELECT TRIM('     test    ') AS Result\")\n        self.validate_identity(\"SELECT TRIM('.,! ' FROM '     #     test    .') AS Result\")\n        self.validate_identity(\"SELECT * FROM t TABLESAMPLE (10 PERCENT)\")\n        self.validate_identity(\"SELECT * FROM t TABLESAMPLE (20 ROWS)\")\n        self.validate_identity(\"SELECT * FROM t TABLESAMPLE (10 PERCENT) REPEATABLE (123)\")\n        self.validate_identity(\"SELECT CONCAT(column1, column2)\")\n        self.validate_identity(\"SELECT TestSpecialChar.Test# FROM TestSpecialChar\")\n        self.validate_identity(\"SELECT TestSpecialChar.Test@ FROM TestSpecialChar\")\n        self.validate_identity(\"SELECT TestSpecialChar.Test$ FROM TestSpecialChar\")\n        self.validate_identity(\"SELECT TestSpecialChar.Test_ FROM TestSpecialChar\")\n        self.validate_identity(\"SELECT TOP (2 + 1) 1\")\n        self.validate_identity(\"SELECT * FROM t WHERE NOT c\", \"SELECT * FROM t WHERE NOT c <> 0\")\n        self.validate_identity(\"1 AND true\", \"1 <> 0 AND (1 = 1)\")\n        self.validate_identity(\"CAST(x AS int) OR y\", \"CAST(x AS INTEGER) <> 0 OR y <> 0\")\n        self.validate_identity(\"TRUNCATE TABLE t1 WITH (PARTITIONS(1, 2 TO 5, 10 TO 20, 84))\")\n        self.validate_identity(\n            \"WITH t1 AS (SELECT 1 AS a), t2 AS (SELECT 1 AS a) SELECT TOP 10 a FROM t1 UNION ALL SELECT TOP 10 a FROM t2\"\n        )\n        self.validate_identity(\n            \"SELECT TOP 10 s.RECORDID, n.c.VALUE('(/*:FORM_ROOT/*:SOME_TAG)[1]', 'float') AS SOME_TAG_VALUE FROM source_table.dbo.source_data AS s(nolock) CROSS APPLY FormContent.nodes('/*:FORM_ROOT') AS N(C)\"\n        )\n        self.validate_identity(\n            \"CREATE CLUSTERED INDEX [IX_OfficeTagDetail_TagDetailID] ON [dbo].[OfficeTagDetail]([TagDetailID] ASC)\"\n        )\n        self.validate_identity(\n            \"CREATE INDEX [x] ON [y]([z] ASC) WITH (allow_page_locks=on) ON X([y])\"\n        )\n        self.validate_identity(\n            \"CREATE INDEX [x] ON [y]([z] ASC) WITH (allow_page_locks=on) ON PRIMARY\"\n        )\n        self.validate_identity(\n            \"COPY INTO test_1 FROM 'path' WITH (FORMAT_NAME = test, FILE_TYPE = 'CSV', CREDENTIAL = (IDENTITY='Shared Access Signature', SECRET='token'), FIELDTERMINATOR = ';', ROWTERMINATOR = '0X0A', ENCODING = 'UTF8', DATEFORMAT = 'ymd', MAXERRORS = 10, ERRORFILE = 'errorsfolder', IDENTITY_INSERT = 'ON')\"\n        )\n        self.validate_identity(\n            \"WITH t1 AS (SELECT 1 AS a), t2 AS (SELECT 1 AS a) SELECT TOP 10 a FROM t1 UNION ALL SELECT TOP 10 a FROM t2 ORDER BY a DESC\"\n        )\n        self.validate_identity(\n            \"WITH t1 AS (SELECT 1 AS a), t2 AS (SELECT 1 AS a) SELECT COUNT(*) FROM (SELECT TOP 10 a FROM t1 UNION ALL SELECT TOP 10 a FROM t2 ORDER BY a DESC) AS t\"\n        )\n        self.validate_identity(\n            'SELECT 1 AS \"[x]\"',\n            \"SELECT 1 AS [[x]]]\",\n        )\n        self.validate_identity(\n            \"INSERT INTO foo.bar WITH cte AS (SELECT 1 AS one) SELECT * FROM cte\",\n            \"WITH cte AS (SELECT 1 AS one) INSERT INTO foo.bar SELECT * FROM cte\",\n        )\n\n        self.assertEqual(\n            annotate_types(self.validate_identity(\"SELECT 1 WHERE EXISTS(SELECT 1)\")).sql(\"tsql\"),\n            \"SELECT 1 WHERE EXISTS(SELECT 1)\",\n        )\n\n        self.validate_all(\n            \"CREATE TABLE test_table([ID] [BIGINT] NOT NULL,[EffectiveFrom] [DATETIME2] (3) NOT NULL)\",\n            write={\n                \"spark\": \"CREATE TABLE test_table (`ID` BIGINT NOT NULL, `EffectiveFrom` TIMESTAMP NOT NULL)\",\n                \"tsql\": \"CREATE TABLE test_table ([ID] BIGINT NOT NULL, [EffectiveFrom] DATETIME2(3) NOT NULL)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CONVERT(DATETIME, '2006-04-25T15:50:59.997', 126)\",\n            write={\n                \"duckdb\": \"SELECT STRPTIME('2006-04-25T15:50:59.997', '%Y-%m-%dT%H:%M:%S.%f')\",\n                \"tsql\": \"SELECT CONVERT(DATETIME, '2006-04-25T15:50:59.997', 126)\",\n            },\n        )\n        self.validate_all(\n            \"WITH A AS (SELECT 2 AS value), C AS (SELECT * FROM A) SELECT * INTO TEMP_NESTED_WITH FROM (SELECT * FROM C) AS temp\",\n            read={\n                \"snowflake\": \"CREATE TABLE TEMP_NESTED_WITH AS WITH C AS (WITH A AS (SELECT 2 AS value) SELECT * FROM A) SELECT * FROM C\",\n                \"tsql\": \"WITH A AS (SELECT 2 AS value), C AS (SELECT * FROM A) SELECT * INTO TEMP_NESTED_WITH FROM (SELECT * FROM C) AS temp\",\n            },\n            write={\n                \"snowflake\": \"CREATE TABLE TEMP_NESTED_WITH AS WITH A AS (SELECT 2 AS value), C AS (SELECT * FROM A) SELECT * FROM (SELECT * FROM C) AS temp\",\n            },\n        )\n        self.validate_all(\n            \"SELECT IIF(cond <> 0, 'True', 'False')\",\n            read={\n                \"spark\": \"SELECT IF(cond, 'True', 'False')\",\n                \"sqlite\": \"SELECT IIF(cond, 'True', 'False')\",\n                \"tsql\": \"SELECT IIF(cond <> 0, 'True', 'False')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TRIM(BOTH 'a' FROM a)\",\n            read={\n                \"mysql\": \"SELECT TRIM(BOTH 'a' FROM a)\",\n            },\n            write={\n                \"mysql\": \"SELECT TRIM(BOTH 'a' FROM a)\",\n                \"tsql\": \"SELECT TRIM(BOTH 'a' FROM a)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TIMEFROMPARTS(23, 59, 59, 0, 0)\",\n            read={\n                \"duckdb\": \"SELECT MAKE_TIME(23, 59, 59)\",\n                \"mysql\": \"SELECT MAKETIME(23, 59, 59)\",\n                \"postgres\": \"SELECT MAKE_TIME(23, 59, 59)\",\n                \"snowflake\": \"SELECT TIME_FROM_PARTS(23, 59, 59)\",\n            },\n            write={\n                \"tsql\": \"SELECT TIMEFROMPARTS(23, 59, 59, 0, 0)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATETIMEFROMPARTS(2013, 4, 5, 12, 00, 00, 0)\",\n            read={\n                # The nanoseconds are ignored since T-SQL doesn't support that precision\n                \"snowflake\": \"SELECT TIMESTAMP_FROM_PARTS(2013, 4, 5, 12, 00, 00, 987654321)\"\n            },\n            write={\n                \"duckdb\": \"SELECT MAKE_TIMESTAMP(2013, 4, 5, 12, 00, 00 + (0 / 1000.0))\",\n                \"snowflake\": \"SELECT TIMESTAMP_FROM_PARTS(2013, 4, 5, 12, 00, 00, 0 * 1000000)\",\n                \"tsql\": \"SELECT DATETIMEFROMPARTS(2013, 4, 5, 12, 00, 00, 0)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TOP 1 * FROM (SELECT x FROM t1 UNION ALL SELECT x FROM t2) AS _l_0\",\n            read={\n                \"\": \"SELECT x FROM t1 UNION ALL SELECT x FROM t2 LIMIT 1\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(c) AS (SELECT 1) SELECT * INTO foo FROM (SELECT c AS c FROM t) AS temp\",\n            read={\n                \"duckdb\": \"CREATE TABLE foo AS WITH t(c) AS (SELECT 1) SELECT c FROM t\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(c) AS (SELECT 1) SELECT * INTO foo FROM (SELECT c AS c FROM t) AS temp\",\n            write={\n                \"duckdb\": \"CREATE TABLE foo AS WITH t(c) AS (SELECT 1) SELECT * FROM (SELECT c AS c FROM t) AS temp\",\n                \"postgres\": \"WITH t(c) AS (SELECT 1) SELECT * INTO foo FROM (SELECT c AS c FROM t) AS temp\",\n                \"oracle\": \"WITH t(c) AS (SELECT 1) SELECT * INTO foo FROM (SELECT c AS c FROM t) temp\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(c) AS (SELECT 1) SELECT * INTO UNLOGGED #foo FROM (SELECT c AS c FROM t) AS temp\",\n            write={\n                \"duckdb\": \"CREATE TEMPORARY TABLE foo AS WITH t(c) AS (SELECT 1) SELECT * FROM (SELECT c AS c FROM t) AS temp\",\n                \"postgres\": \"WITH t(c) AS (SELECT 1) SELECT * INTO TEMPORARY foo FROM (SELECT c AS c FROM t) AS temp\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(c) AS (SELECT 1) SELECT c INTO #foo FROM t\",\n            read={\n                \"tsql\": \"WITH t(c) AS (SELECT 1) SELECT c INTO #foo FROM t\",\n                \"postgres\": \"WITH t(c) AS (SELECT 1) SELECT c INTO TEMPORARY foo FROM t\",\n            },\n            write={\n                \"tsql\": \"WITH t(c) AS (SELECT 1) SELECT c INTO #foo FROM t\",\n                \"postgres\": \"WITH t(c) AS (SELECT 1) SELECT c INTO TEMPORARY foo FROM t\",\n                \"duckdb\": \"CREATE TEMPORARY TABLE foo AS WITH t(c) AS (SELECT 1) SELECT c FROM t\",\n                \"snowflake\": \"CREATE TEMPORARY TABLE foo AS WITH t(c) AS (SELECT 1) SELECT c FROM t\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(c) AS (SELECT 1) SELECT * INTO UNLOGGED foo FROM (SELECT c AS c FROM t) AS temp\",\n            write={\n                \"duckdb\": \"CREATE TABLE foo AS WITH t(c) AS (SELECT 1) SELECT * FROM (SELECT c AS c FROM t) AS temp\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(c) AS (SELECT 1) SELECT * INTO UNLOGGED foo FROM (SELECT c AS c FROM t) AS temp\",\n            write={\n                \"duckdb\": \"CREATE TABLE foo AS WITH t(c) AS (SELECT 1) SELECT * FROM (SELECT c AS c FROM t) AS temp\",\n            },\n        )\n        self.validate_all(\n            \"WITH y AS (SELECT 2 AS c) INSERT INTO #t SELECT * FROM y\",\n            write={\n                \"duckdb\": \"WITH y AS (SELECT 2 AS c) INSERT INTO t SELECT * FROM y\",\n                \"postgres\": \"WITH y AS (SELECT 2 AS c) INSERT INTO t SELECT * FROM y\",\n            },\n        )\n        self.validate_all(\n            \"WITH y AS (SELECT 2 AS c) INSERT INTO t SELECT * FROM y\",\n            read={\n                \"duckdb\": \"WITH y AS (SELECT 2 AS c) INSERT INTO t SELECT * FROM y\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(c) AS (SELECT 1) SELECT 1 AS c UNION (SELECT c FROM t)\",\n            read={\n                \"duckdb\": \"SELECT 1 AS c UNION (WITH t(c) AS (SELECT 1) SELECT c FROM t)\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(c) AS (SELECT 1) MERGE INTO x AS z USING (SELECT c AS c FROM t) AS y ON a = b WHEN MATCHED THEN UPDATE SET a = y.b\",\n            read={\n                \"postgres\": \"MERGE INTO x AS z USING (WITH t(c) AS (SELECT 1) SELECT c FROM t) AS y ON a = b WHEN MATCHED THEN UPDATE SET a = y.b\",\n            },\n        )\n        self.validate_all(\n            \"WITH t(n) AS (SELECT 1 AS n UNION ALL SELECT n + 1 AS n FROM t WHERE n < 4) SELECT * FROM (SELECT SUM(n) AS s4 FROM t) AS subq\",\n            read={\n                \"duckdb\": \"SELECT * FROM (WITH RECURSIVE t(n) AS (SELECT 1 AS n UNION ALL SELECT n + 1 AS n FROM t WHERE n < 4) SELECT SUM(n) AS s4 FROM t) AS subq\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE #mytemptable (a INTEGER)\",\n            read={\n                \"duckdb\": \"CREATE TEMPORARY TABLE mytemptable (a INT)\",\n            },\n            write={\n                \"tsql\": \"CREATE TABLE #mytemptable (a INTEGER)\",\n                \"snowflake\": \"CREATE TEMPORARY TABLE mytemptable (a INT)\",\n                \"duckdb\": \"CREATE TEMPORARY TABLE mytemptable (a INT)\",\n                \"oracle\": \"CREATE GLOBAL TEMPORARY TABLE mytemptable (a INT)\",\n                \"hive\": \"CREATE TEMPORARY TABLE mytemptable (a INT)\",\n                \"spark2\": \"CREATE TEMPORARY TABLE mytemptable (a INT) USING PARQUET\",\n                \"spark\": \"CREATE TEMPORARY TABLE mytemptable (a INT) USING PARQUET\",\n                \"databricks\": \"CREATE TEMPORARY TABLE mytemptable (a INT) USING PARQUET\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE #mytemp (a INTEGER, b CHAR(2), c TIME(4), d FLOAT(24))\",\n            write={\n                \"spark\": \"CREATE TEMPORARY TABLE mytemp (a INT, b CHAR(2), c TIMESTAMP, d FLOAT) USING PARQUET\",\n                \"tsql\": \"CREATE TABLE #mytemp (a INTEGER, b CHAR(2), c TIME(4), d FLOAT(24))\",\n            },\n        )\n        self.validate_all(\n            \"\"\"CREATE TABLE [dbo].[mytable](\n                [email] [varchar](255) NOT NULL,\n                CONSTRAINT [UN_t_mytable] UNIQUE NONCLUSTERED\n                (\n                    [email] ASC\n                )\n                )\"\"\",\n            write={\n                \"hive\": \"CREATE TABLE `dbo`.`mytable` (`email` VARCHAR(255) NOT NULL)\",\n                \"spark2\": \"CREATE TABLE `dbo`.`mytable` (`email` VARCHAR(255) NOT NULL)\",\n                \"spark\": \"CREATE TABLE `dbo`.`mytable` (`email` VARCHAR(255) NOT NULL)\",\n                \"databricks\": \"CREATE TABLE `dbo`.`mytable` (`email` VARCHAR(255) NOT NULL)\",\n            },\n        )\n\n        self.validate_all(\n            \"CREATE TABLE x ( A INTEGER NOT NULL, B INTEGER NULL )\",\n            write={\n                \"tsql\": \"CREATE TABLE x (A INTEGER NOT NULL, B INTEGER NULL)\",\n                \"hive\": \"CREATE TABLE x (A INT NOT NULL, B INT)\",\n            },\n        )\n\n        self.validate_identity(\n            'CREATE TABLE x (CONSTRAINT \"pk_mytable\" UNIQUE NONCLUSTERED (a DESC)) ON b (c)',\n            \"CREATE TABLE x (CONSTRAINT [pk_mytable] UNIQUE NONCLUSTERED (a DESC)) ON b (c)\",\n        )\n\n        self.validate_all(\n            \"\"\"CREATE TABLE x ([zip_cd] VARCHAR(5) NULL NOT FOR REPLICATION, [zip_cd_mkey] VARCHAR(5) NOT NULL, CONSTRAINT [pk_mytable] PRIMARY KEY CLUSTERED ([zip_cd_mkey] ASC) WITH (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF) ON [INDEX]) ON [SECONDARY]\"\"\",\n            write={\n                \"tsql\": \"CREATE TABLE x ([zip_cd] VARCHAR(5) NULL NOT FOR REPLICATION, [zip_cd_mkey] VARCHAR(5) NOT NULL, CONSTRAINT [pk_mytable] PRIMARY KEY CLUSTERED ([zip_cd_mkey] ASC) WITH (PAD_INDEX=ON, STATISTICS_NORECOMPUTE=OFF) ON [INDEX]) ON [SECONDARY]\",\n                \"spark2\": \"CREATE TABLE x (`zip_cd` VARCHAR(5), `zip_cd_mkey` VARCHAR(5) NOT NULL, CONSTRAINT `pk_mytable` PRIMARY KEY (`zip_cd_mkey`))\",\n            },\n        )\n\n        self.validate_identity(\"CREATE TABLE x (A INTEGER NOT NULL, B INTEGER NULL)\")\n\n        self.validate_all(\n            \"CREATE TABLE x ( A INTEGER NOT NULL, B INTEGER NULL )\",\n            write={\n                \"hive\": \"CREATE TABLE x (A INT NOT NULL, B INT)\",\n            },\n        )\n\n        self.validate_identity(\n            \"CREATE TABLE tbl (a AS (x + 1) PERSISTED, b AS (y + 2), c AS (y / 3) PERSISTED NOT NULL)\"\n        )\n\n        self.validate_identity(\n            \"CREATE TABLE [db].[tbl]([a] [int])\",\n            \"CREATE TABLE [db].[tbl] ([a] INTEGER)\",\n        )\n\n        self.validate_identity(\"SELECT a = 1\", \"SELECT 1 AS a\").selects[0].assert_is(\n            exp.Alias\n        ).args[\"alias\"].assert_is(exp.Identifier)\n\n        self.validate_all(\n            \"IF OBJECT_ID('tempdb.dbo.#TempTableName', 'U') IS NOT NULL BEGIN DROP TABLE #TempTableName; END\",\n            write={\n                \"tsql\": \"IF NOT OBJECT_ID('tempdb.dbo.#TempTableName', 'U') IS NULL BEGIN DROP TABLE #TempTableName; END\",\n                \"spark\": \"DROP TABLE IF EXISTS TempTableName\",\n            },\n        )\n\n        self.validate_all(\n            \"IF OBJECT_ID('tempdb.dbo.#TempTableName') IS NOT NULL BEGIN DROP TABLE #TempTableName; END\",\n            write={\n                \"tsql\": \"IF NOT OBJECT_ID('tempdb.dbo.#TempTableName') IS NULL BEGIN DROP TABLE #TempTableName; END\",\n                \"spark\": \"DROP TABLE IF EXISTS TempTableName\",\n            },\n        )\n\n        self.validate_identity(\n            \"MERGE INTO mytable WITH (HOLDLOCK) AS T USING mytable_merge AS S \"\n            \"ON (T.user_id = S.user_id) WHEN NOT MATCHED THEN INSERT (c1, c2) VALUES (S.c1, S.c2)\"\n        )\n        self.validate_identity(\"UPDATE STATISTICS x\", check_command_warning=True)\n        self.validate_identity(\"UPDATE x SET y = 1 OUTPUT x.a, x.b INTO @y FROM y\")\n        self.validate_identity(\"UPDATE x SET y = 1 OUTPUT x.a, x.b FROM y\")\n        self.validate_identity(\"INSERT INTO x (y) OUTPUT x.a, x.b INTO l SELECT * FROM z\")\n        self.validate_identity(\"INSERT INTO x (y) OUTPUT x.a, x.b SELECT * FROM z\")\n        self.validate_identity(\"DELETE x OUTPUT x.a FROM z\")\n        self.validate_identity(\"SELECT * FROM t WITH (TABLOCK, INDEX(myindex))\")\n        self.validate_identity(\"SELECT * FROM t WITH (NOWAIT)\")\n        self.validate_identity(\"SELECT CASE WHEN a > 1 THEN b END\")\n        self.validate_identity(\"SELECT * FROM taxi ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY\")\n        self.validate_identity(\"END\")\n        self.validate_identity(\"@x\")\n        self.validate_identity(\"#x\")\n        self.validate_identity(\"PRINT @TestVariable\", check_command_warning=True)\n        self.validate_identity(\"SELECT Employee_ID, Department_ID FROM @MyTableVar\")\n        self.validate_identity(\"INSERT INTO @TestTable VALUES (1, 'Value1', 12, 20)\")\n        self.validate_identity(\"SELECT * FROM #foo\")\n        self.validate_identity(\"SELECT * FROM ##foo\")\n        self.validate_identity(\"SELECT a = 1\", \"SELECT 1 AS a\")\n        self.validate_identity(\n            \"DECLARE @TestVariable AS VARCHAR(100) = 'Save Our Planet'\",\n            \"DECLARE @TestVariable VARCHAR(100) = 'Save Our Planet'\",\n        )\n        self.validate_identity(\n            \"SELECT a = 1 UNION ALL SELECT a = b\", \"SELECT 1 AS a UNION ALL SELECT b AS a\"\n        )\n        self.validate_identity(\n            \"SELECT x FROM @MyTableVar AS m JOIN Employee ON m.EmployeeID = Employee.EmployeeID\"\n        )\n        self.validate_identity(\n            \"SELECT DISTINCT DepartmentName, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY BaseRate) OVER (PARTITION BY DepartmentName) AS MedianCont FROM dbo.DimEmployee\"\n        )\n        self.validate_identity(\n            'SELECT \"x\".\"y\" FROM foo',\n            \"SELECT [x].[y] FROM foo\",\n        )\n\n        self.validate_all(\n            \"SELECT * FROM t ORDER BY (SELECT NULL) OFFSET 2 ROWS\",\n            read={\n                \"postgres\": \"SELECT * FROM t OFFSET 2\",\n            },\n            write={\n                \"postgres\": \"SELECT * FROM t ORDER BY (SELECT NULL) NULLS FIRST OFFSET 2\",\n                \"tsql\": \"SELECT * FROM t ORDER BY (SELECT NULL) OFFSET 2 ROWS\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM t ORDER BY (SELECT NULL) OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY\",\n            read={\n                \"duckdb\": \"SELECT * FROM t LIMIT 10 OFFSET 5\",\n                \"sqlite\": \"SELECT * FROM t LIMIT 5, 10\",\n                \"tsql\": \"SELECT * FROM t ORDER BY (SELECT NULL) OFFSET 5 ROWS FETCH FIRST 10 ROWS ONLY\",\n            },\n            write={\n                \"duckdb\": \"SELECT * FROM t ORDER BY (SELECT NULL) NULLS FIRST LIMIT 10 OFFSET 5\",\n                \"sqlite\": \"SELECT * FROM t ORDER BY (SELECT NULL) LIMIT 10 OFFSET 5\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST([a].[b] AS SMALLINT) FROM foo\",\n            write={\n                \"tsql\": \"SELECT CAST([a].[b] AS SMALLINT) FROM foo\",\n                \"spark\": \"SELECT CAST(`a`.`b` AS SMALLINT) FROM foo\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(INT, CONVERT(NUMERIC, '444.75'))\",\n            write={\n                \"mysql\": \"CAST(CAST('444.75' AS DECIMAL) AS SIGNED)\",\n                \"tsql\": \"CONVERT(INTEGER, CONVERT(NUMERIC, '444.75'))\",\n            },\n        )\n        self.validate_all(\n            \"STRING_AGG(x, y) WITHIN GROUP (ORDER BY z DESC)\",\n            write={\n                \"tsql\": \"STRING_AGG(x, y) WITHIN GROUP (ORDER BY z DESC)\",\n                \"mysql\": \"GROUP_CONCAT(x ORDER BY z DESC SEPARATOR y)\",\n                \"sqlite\": \"GROUP_CONCAT(x, y)\",\n                \"postgres\": \"STRING_AGG(x, y ORDER BY z DESC NULLS LAST)\",\n            },\n        )\n        self.validate_all(\n            \"STRING_AGG(x, '|') WITHIN GROUP (ORDER BY z ASC)\",\n            write={\n                \"tsql\": \"STRING_AGG(x, '|') WITHIN GROUP (ORDER BY z ASC)\",\n                \"mysql\": \"GROUP_CONCAT(x ORDER BY z ASC SEPARATOR '|')\",\n                \"sqlite\": \"GROUP_CONCAT(x, '|')\",\n                \"postgres\": \"STRING_AGG(x, '|' ORDER BY z ASC NULLS FIRST)\",\n            },\n        )\n        self.validate_all(\n            \"STRING_AGG(x, '|')\",\n            write={\n                \"tsql\": \"STRING_AGG(x, '|')\",\n                \"mysql\": \"GROUP_CONCAT(x SEPARATOR '|')\",\n                \"sqlite\": \"GROUP_CONCAT(x, '|')\",\n                \"postgres\": \"STRING_AGG(x, '|')\",\n            },\n        )\n        self.validate_all(\n            \"HASHBYTES('SHA1', x)\",\n            read={\n                \"snowflake\": \"SHA1(x)\",\n                \"spark\": \"SHA(x)\",\n            },\n            write={\n                \"snowflake\": \"SHA1(x)\",\n                \"spark\": \"SHA(x)\",\n                \"tsql\": \"HASHBYTES('SHA1', x)\",\n            },\n        )\n        self.validate_all(\n            \"HASHBYTES('SHA2_256', x)\",\n            read={\n                \"spark\": \"SHA2(x, 256)\",\n            },\n            write={\n                \"tsql\": \"HASHBYTES('SHA2_256', x)\",\n                \"spark\": \"SHA2(x, 256)\",\n            },\n        )\n        self.validate_all(\n            \"HASHBYTES('SHA2_512', x)\",\n            read={\n                \"spark\": \"SHA2(x, 512)\",\n            },\n            write={\n                \"tsql\": \"HASHBYTES('SHA2_512', x)\",\n                \"spark\": \"SHA2(x, 512)\",\n            },\n        )\n        self.validate_all(\n            \"HASHBYTES('MD5', 'x')\",\n            read={\n                \"spark\": \"MD5('x')\",\n            },\n            write={\n                \"tsql\": \"HASHBYTES('MD5', 'x')\",\n                \"spark\": \"MD5('x')\",\n            },\n        )\n        self.validate_identity(\"HASHBYTES('MD2', 'x')\")\n        self.validate_identity(\"LOG(n)\")\n        self.validate_identity(\"LOG(n, b)\")\n\n        self.validate_all(\n            \"STDEV(x)\",\n            read={\n                \"\": \"STDDEV(x)\",\n            },\n            write={\n                \"\": \"STDDEV(x)\",\n                \"tsql\": \"STDEV(x)\",\n            },\n        )\n\n        # Check that TRUE and FALSE dont get expanded to (1=1) or (1=0) when used in a VALUES expression\n        self.validate_identity(\n            \"SELECT val FROM (VALUES ((TRUE), (FALSE), (NULL))) AS t(val)\",\n            write_sql=\"SELECT val FROM (VALUES ((1), (0), (NULL))) AS t(val)\",\n        )\n        self.validate_identity(\"'a' + 'b'\")\n        self.validate_identity(\n            \"'a' || 'b'\",\n            \"'a' + 'b'\",\n        )\n\n        self.validate_identity(\n            \"CREATE TABLE db.t1 (a INTEGER, b VARCHAR(50), CONSTRAINT c PRIMARY KEY (a DESC))\",\n        )\n        self.validate_identity(\n            \"CREATE TABLE db.t1 (a INTEGER, b INTEGER, CONSTRAINT c PRIMARY KEY (a DESC, b))\"\n        )\n\n        self.validate_all(\n            \"SCHEMA_NAME(id)\",\n            write={\n                \"sqlite\": \"'main'\",\n                \"mysql\": \"SCHEMA()\",\n                \"postgres\": \"CURRENT_SCHEMA\",\n                \"tsql\": \"SCHEMA_NAME(id)\",\n            },\n        )\n\n        with self.assertRaises(ParseError):\n            parse_one(\"SELECT begin\", read=\"tsql\")\n\n        self.validate_identity(\"CREATE PROCEDURE test(@v1 INTEGER = 1, @v2 CHAR(1) = 'c')\")\n        self.validate_identity(\n            \"DECLARE @v1 AS INTEGER = 1, @v2 AS CHAR(1) = 'c'\",\n            \"DECLARE @v1 INTEGER = 1, @v2 CHAR(1) = 'c'\",\n        )\n\n        for output in (\"OUT\", \"OUTPUT\", \"READONLY\"):\n            self.validate_identity(\n                f\"CREATE PROCEDURE test(@v1 INTEGER = 1 {output}, @v2 CHAR(1) {output})\"\n            )\n\n        self.validate_identity(\n            \"CREATE PROCEDURE test(@v1 AS INTEGER = 1, @v2 AS CHAR(1) = 'c')\",\n            \"CREATE PROCEDURE test(@v1 INTEGER = 1, @v2 CHAR(1) = 'c')\",\n        )\n\n        for order_by in (\"\", \" ORDER BY c\"):\n            for json_clause in (\"\", \" NULL ON NULL\", \" ABSENT ON NULL\"):\n                with self.subTest(f\"Testing JSON_ARRAYAGG with options: {order_by}, {json_clause}\"):\n                    self.validate_identity(f\"JSON_ARRAYAGG(c{order_by}{json_clause})\")\n\n        self.validate_all(\n            \"JSON_ARRAYAGG(c1 ORDER BY c1)\",\n            write={\n                \"tsql\": \"JSON_ARRAYAGG(c1 ORDER BY c1)\",\n                \"postgres\": \"JSON_AGG(c1 ORDER BY c1 NULLS FIRST)\",\n            },\n        )\n        self.validate_identity(\"CEILING(2)\")\n\n        self.validate_identity(\"OBJECT_ID('foo')\")\n        self.validate_identity(\"OBJECT_ID('foo', 'U')\")\n\n    def test_option(self):\n        possible_options = [\n            \"HASH GROUP\",\n            \"ORDER GROUP\",\n            \"CONCAT UNION\",\n            \"HASH UNION\",\n            \"MERGE UNION\",\n            \"LOOP JOIN\",\n            \"MERGE JOIN\",\n            \"HASH JOIN\",\n            \"DISABLE_OPTIMIZED_PLAN_FORCING\",\n            \"EXPAND VIEWS\",\n            \"FAST 15\",\n            \"FORCE ORDER\",\n            \"FORCE EXTERNALPUSHDOWN\",\n            \"DISABLE EXTERNALPUSHDOWN\",\n            \"FORCE SCALEOUTEXECUTION\",\n            \"DISABLE SCALEOUTEXECUTION\",\n            \"IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX\",\n            \"KEEP PLAN\",\n            \"KEEPFIXED PLAN\",\n            \"MAX_GRANT_PERCENT = 5\",\n            \"MIN_GRANT_PERCENT = 10\",\n            \"MAXDOP 13\",\n            \"MAXRECURSION 8\",\n            \"NO_PERFORMANCE_SPOOL\",\n            \"OPTIMIZE FOR UNKNOWN\",\n            \"PARAMETERIZATION SIMPLE\",\n            \"PARAMETERIZATION FORCED\",\n            \"QUERYTRACEON 99\",\n            \"RECOMPILE\",\n            \"ROBUST PLAN\",\n            \"USE PLAN N'<xml_plan>'\",\n            \"LABEL = 'MyLabel'\",\n        ]\n\n        possible_statements = [\n            # These should be un-commented once support for the OPTION clause is added for DELETE, MERGE and UPDATE\n            # \"DELETE FROM Table1\",\n            # \"MERGE INTO Locations AS T USING locations_stage AS S ON T.LocationID = S.LocationID WHEN MATCHED THEN UPDATE SET LocationName = S.LocationName\",\n            # \"UPDATE Customers SET ContactName = 'Alfred Schmidt', City = 'Frankfurt' WHERE CustomerID = 1\",\n            \"SELECT * FROM Table1\",\n            \"SELECT * FROM Table1 WHERE id = 2\",\n            \"UPDATE t1 SET k = t2.k FROM t2\",\n        ]\n\n        for statement in possible_statements:\n            for option in possible_options:\n                query = f\"{statement} OPTION({option})\"\n                result = self.validate_identity(query)\n                options = result.args.get(\"options\")\n                self.assertIsInstance(options, list, f\"When parsing query {query}\")\n                is_query_options = map(lambda o: isinstance(o, exp.QueryOption), options)\n                self.assertTrue(all(is_query_options), f\"When parsing query {query}\")\n\n            self.validate_identity(\n                f\"{statement} OPTION(RECOMPILE, USE PLAN N'<xml_plan>', MAX_GRANT_PERCENT = 5)\"\n            )\n\n        raising_queries = [\n            # Missing parentheses\n            \"SELECT * FROM Table1 OPTION HASH GROUP\",\n            # Must be followed by 'PLAN\"\n            \"SELECT * FROM Table1 OPTION(KEEPFIXED)\",\n            # Missing commas\n            \"SELECT * FROM Table1 OPTION(HASH GROUP HASH GROUP)\",\n        ]\n        for query in raising_queries:\n            with self.assertRaises(ParseError, msg=f\"When running '{query}'\"):\n                self.parse_one(query)\n\n        self.validate_all(\n            \"SELECT col FROM t OPTION(LABEL = 'foo')\",\n            write={\n                \"tsql\": \"SELECT col FROM t OPTION(LABEL = 'foo')\",\n                \"databricks\": UnsupportedError,\n            },\n        )\n\n    def test_for_xml(self):\n        xml_possible_options = [\n            \"RAW('ElementName')\",\n            \"RAW('ElementName'), BINARY BASE64\",\n            \"RAW('ElementName'), TYPE\",\n            \"RAW('ElementName'), ROOT('RootName')\",\n            \"RAW('ElementName'), BINARY BASE64, TYPE\",\n            \"RAW('ElementName'), BINARY BASE64, ROOT('RootName')\",\n            \"RAW('ElementName'), TYPE, ROOT('RootName')\",\n            \"RAW('ElementName'), BINARY BASE64, TYPE, ROOT('RootName')\",\n            \"RAW('ElementName'), XMLDATA\",\n            \"RAW('ElementName'), XMLSCHEMA('TargetNameSpaceURI')\",\n            \"RAW('ElementName'), XMLDATA, ELEMENTS XSINIL\",\n            \"RAW('ElementName'), XMLSCHEMA('TargetNameSpaceURI'), ELEMENTS ABSENT\",\n            \"RAW('ElementName'), XMLDATA, ELEMENTS ABSENT\",\n            \"RAW('ElementName'), XMLSCHEMA('TargetNameSpaceURI'), ELEMENTS XSINIL\",\n            \"AUTO\",\n            \"AUTO, BINARY BASE64\",\n            \"AUTO, TYPE\",\n            \"AUTO, ROOT('RootName')\",\n            \"AUTO, BINARY BASE64, TYPE\",\n            \"AUTO, TYPE, ROOT('RootName')\",\n            \"AUTO, BINARY BASE64, TYPE, ROOT('RootName')\",\n            \"AUTO, XMLDATA\",\n            \"AUTO, XMLSCHEMA('TargetNameSpaceURI')\",\n            \"AUTO, XMLDATA, ELEMENTS XSINIL\",\n            \"AUTO, XMLSCHEMA('TargetNameSpaceURI'), ELEMENTS ABSENT\",\n            \"AUTO, XMLDATA, ELEMENTS ABSENT\",\n            \"AUTO, XMLSCHEMA('TargetNameSpaceURI'), ELEMENTS XSINIL\",\n            \"EXPLICIT\",\n            \"EXPLICIT, BINARY BASE64\",\n            \"EXPLICIT, TYPE\",\n            \"EXPLICIT, ROOT('RootName')\",\n            \"EXPLICIT, BINARY BASE64, TYPE\",\n            \"EXPLICIT, TYPE, ROOT('RootName')\",\n            \"EXPLICIT, BINARY BASE64, TYPE, ROOT('RootName')\",\n            \"EXPLICIT, XMLDATA\",\n            \"EXPLICIT, XMLDATA, BINARY BASE64\",\n            \"EXPLICIT, XMLDATA, TYPE\",\n            \"EXPLICIT, XMLDATA, ROOT('RootName')\",\n            \"EXPLICIT, XMLDATA, BINARY BASE64, TYPE\",\n            \"EXPLICIT, XMLDATA, BINARY BASE64, TYPE, ROOT('RootName')\",\n            \"PATH('ElementName')\",\n            \"PATH('ElementName'), BINARY BASE64\",\n            \"PATH('ElementName'), TYPE\",\n            \"PATH('ElementName'), ROOT('RootName')\",\n            \"PATH('ElementName'), BINARY BASE64, TYPE\",\n            \"PATH('ElementName'), TYPE, ROOT('RootName')\",\n            \"PATH('ElementName'), BINARY BASE64, TYPE, ROOT('RootName')\",\n            \"PATH('ElementName'), ELEMENTS XSINIL\",\n            \"PATH('ElementName'), ELEMENTS ABSENT\",\n            \"PATH('ElementName'), BINARY BASE64, ELEMENTS XSINIL\",\n            \"PATH('ElementName'), TYPE, ELEMENTS ABSENT\",\n            \"PATH('ElementName'), ROOT('RootName'), ELEMENTS XSINIL\",\n            \"PATH('ElementName'), BINARY BASE64, TYPE, ROOT('RootName'), ELEMENTS ABSENT\",\n        ]\n\n        for xml_option in xml_possible_options:\n            with self.subTest(f\"Testing FOR XML option: {xml_option}\"):\n                self.validate_identity(f\"SELECT * FROM t FOR XML {xml_option}\")\n\n        self.validate_identity(\n            \"SELECT * FROM t FOR XML PATH, BINARY BASE64, ELEMENTS XSINIL\",\n            \"\"\"SELECT\n  *\nFROM t\nFOR XML\n  PATH,\n  BINARY BASE64,\n  ELEMENTS XSINIL\"\"\",\n            pretty=True,\n        )\n\n    def test_types(self):\n        self.validate_identity(\"CAST(x AS XML)\")\n        self.validate_identity(\"CAST(x AS UNIQUEIDENTIFIER)\")\n        self.validate_identity(\"CAST(x AS MONEY)\")\n        self.validate_identity(\"CAST(x AS SMALLMONEY)\")\n        self.validate_identity(\"CAST(x AS IMAGE)\")\n        self.validate_identity(\"CAST(x AS SQL_VARIANT)\")\n        self.validate_identity(\"CAST(x AS BIT)\")\n\n        self.validate_all(\n            \"CAST(x AS DATETIME2(6))\",\n            write={\n                \"hive\": \"CAST(x AS TIMESTAMP)\",\n            },\n        )\n        self.validate_all(\n            \"CAST(x AS ROWVERSION)\",\n            read={\n                \"tsql\": \"CAST(x AS TIMESTAMP)\",\n            },\n            write={\n                \"tsql\": \"CAST(x AS ROWVERSION)\",\n                \"hive\": \"CAST(x AS BINARY)\",\n            },\n        )\n\n        for temporal_type in (\"SMALLDATETIME\", \"DATETIME\", \"DATETIME2\"):\n            self.validate_all(\n                f\"CAST(x AS {temporal_type})\",\n                read={\n                    \"\": f\"CAST(x AS {temporal_type})\",\n                },\n                write={\n                    \"mysql\": \"CAST(x AS DATETIME)\",\n                    \"duckdb\": \"CAST(x AS TIMESTAMP)\",\n                    \"tsql\": f\"CAST(x AS {temporal_type})\",\n                },\n            )\n\n    def test_types_ints(self):\n        self.validate_all(\n            \"CAST(X AS INT)\",\n            write={\n                \"hive\": \"CAST(X AS INT)\",\n                \"spark2\": \"CAST(X AS INT)\",\n                \"spark\": \"CAST(X AS INT)\",\n                \"tsql\": \"CAST(X AS INTEGER)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(X AS BIGINT)\",\n            write={\n                \"hive\": \"CAST(X AS BIGINT)\",\n                \"spark2\": \"CAST(X AS BIGINT)\",\n                \"spark\": \"CAST(X AS BIGINT)\",\n                \"tsql\": \"CAST(X AS BIGINT)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(X AS SMALLINT)\",\n            write={\n                \"hive\": \"CAST(X AS SMALLINT)\",\n                \"spark2\": \"CAST(X AS SMALLINT)\",\n                \"spark\": \"CAST(X AS SMALLINT)\",\n                \"tsql\": \"CAST(X AS SMALLINT)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(X AS TINYINT)\",\n            read={\n                \"duckdb\": \"CAST(X AS UTINYINT)\",\n            },\n            write={\n                \"duckdb\": \"CAST(X AS UTINYINT)\",\n                \"hive\": \"CAST(X AS SMALLINT)\",\n                \"spark2\": \"CAST(X AS SMALLINT)\",\n                \"spark\": \"CAST(X AS SMALLINT)\",\n                \"tsql\": \"CAST(X AS TINYINT)\",\n            },\n        )\n\n    def test_types_decimals(self):\n        self.validate_all(\n            \"CAST(x as FLOAT)\",\n            write={\n                \"spark\": \"CAST(x AS FLOAT)\",\n                \"tsql\": \"CAST(x AS FLOAT)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as FLOAT(32))\",\n            write={\"tsql\": \"CAST(x AS FLOAT(32))\", \"hive\": \"CAST(x AS FLOAT)\"},\n        )\n\n        self.validate_all(\n            \"CAST(x as FLOAT(64))\",\n            write={\"tsql\": \"CAST(x AS FLOAT(64))\", \"spark\": \"CAST(x AS DOUBLE)\"},\n        )\n\n        self.validate_all(\n            \"CAST(x as FLOAT(6))\", write={\"tsql\": \"CAST(x AS FLOAT(6))\", \"hive\": \"CAST(x AS FLOAT)\"}\n        )\n\n        self.validate_all(\n            \"CAST(x as FLOAT(36))\",\n            write={\"tsql\": \"CAST(x AS FLOAT(36))\", \"hive\": \"CAST(x AS DOUBLE)\"},\n        )\n\n        self.validate_all(\n            \"CAST(x as FLOAT(99))\",\n            write={\"tsql\": \"CAST(x AS FLOAT(99))\", \"hive\": \"CAST(x AS DOUBLE)\"},\n        )\n\n        self.validate_all(\n            \"CAST(x as DOUBLE)\",\n            write={\n                \"spark\": \"CAST(x AS DOUBLE)\",\n                \"tsql\": \"CAST(x AS FLOAT)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as DECIMAL(15, 4))\",\n            write={\n                \"spark\": \"CAST(x AS DECIMAL(15, 4))\",\n                \"tsql\": \"CAST(x AS NUMERIC(15, 4))\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as NUMERIC(13,3))\",\n            write={\n                \"spark\": \"CAST(x AS DECIMAL(13, 3))\",\n                \"tsql\": \"CAST(x AS NUMERIC(13, 3))\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as MONEY)\",\n            write={\n                \"spark\": \"CAST(x AS DECIMAL(15, 4))\",\n                \"tsql\": \"CAST(x AS MONEY)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as SMALLMONEY)\",\n            write={\n                \"spark\": \"CAST(x AS DECIMAL(6, 4))\",\n                \"tsql\": \"CAST(x AS SMALLMONEY)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as REAL)\",\n            write={\n                \"spark\": \"CAST(x AS FLOAT)\",\n                \"tsql\": \"CAST(x AS FLOAT)\",\n            },\n        )\n\n    def test_types_string(self):\n        self.validate_all(\n            \"CAST(x as CHAR(1))\",\n            write={\n                \"spark\": \"CAST(x AS CHAR(1))\",\n                \"tsql\": \"CAST(x AS CHAR(1))\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as VARCHAR(2))\",\n            write={\n                \"spark\": \"CAST(x AS VARCHAR(2))\",\n                \"tsql\": \"CAST(x AS VARCHAR(2))\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as NCHAR(1))\",\n            write={\n                \"spark\": \"CAST(x AS CHAR(1))\",\n                \"tsql\": \"CAST(x AS NCHAR(1))\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as NVARCHAR(2))\",\n            write={\n                \"spark\": \"CAST(x AS VARCHAR(2))\",\n                \"tsql\": \"CAST(x AS NVARCHAR(2))\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as UNIQUEIDENTIFIER)\",\n            write={\n                \"spark\": \"CAST(x AS STRING)\",\n                \"tsql\": \"CAST(x AS UNIQUEIDENTIFIER)\",\n            },\n        )\n\n    def test_types_date(self):\n        self.validate_all(\n            \"CAST(x as DATE)\",\n            write={\n                \"spark\": \"CAST(x AS DATE)\",\n                \"tsql\": \"CAST(x AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as DATE)\",\n            write={\n                \"spark\": \"CAST(x AS DATE)\",\n                \"tsql\": \"CAST(x AS DATE)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as TIME(4))\",\n            write={\n                \"spark\": \"CAST(x AS TIMESTAMP)\",\n                \"tsql\": \"CAST(x AS TIME(4))\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as DATETIME2)\",\n            write={\n                \"spark\": \"CAST(x AS TIMESTAMP)\",\n                \"tsql\": \"CAST(x AS DATETIME2)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as DATETIMEOFFSET)\",\n            write={\n                \"spark\": \"CAST(x AS TIMESTAMP)\",\n                \"tsql\": \"CAST(x AS DATETIMEOFFSET)\",\n            },\n        )\n\n        self.validate_all(\n            \"CREATE TABLE t (col1 DATETIME2(2))\",\n            read={\n                \"snowflake\": \"CREATE TABLE t (col1 TIMESTAMP_NTZ(2))\",\n            },\n            write={\n                \"tsql\": \"CREATE TABLE t (col1 DATETIME2(2))\",\n            },\n        )\n\n    def test_types_bin(self):\n        self.validate_all(\n            \"CAST(x as BIT)\",\n            write={\n                \"spark\": \"CAST(x AS BOOLEAN)\",\n                \"tsql\": \"CAST(x AS BIT)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x as VARBINARY)\",\n            write={\n                \"spark\": \"CAST(x AS BINARY)\",\n                \"tsql\": \"CAST(x AS VARBINARY)\",\n            },\n        )\n\n        self.validate_all(\n            \"CAST(x AS BOOLEAN)\",\n            write={\"tsql\": \"CAST(x AS BIT)\"},\n        )\n\n        self.validate_all(\"a = TRUE\", write={\"tsql\": \"a = 1\"})\n\n        self.validate_all(\"a != FALSE\", write={\"tsql\": \"a <> 0\"})\n\n        self.validate_all(\"a IS TRUE\", write={\"tsql\": \"a = 1\"})\n\n        self.validate_all(\"a IS NOT FALSE\", write={\"tsql\": \"NOT a = 0\"})\n\n        self.validate_all(\n            \"CASE WHEN a IN (TRUE) THEN 'y' ELSE 'n' END\",\n            write={\"tsql\": \"CASE WHEN a IN (1) THEN 'y' ELSE 'n' END\"},\n        )\n\n        self.validate_all(\n            \"CASE WHEN a NOT IN (FALSE) THEN 'y' ELSE 'n' END\",\n            write={\"tsql\": \"CASE WHEN NOT a IN (0) THEN 'y' ELSE 'n' END\"},\n        )\n\n        self.validate_all(\"SELECT TRUE, FALSE\", write={\"tsql\": \"SELECT 1, 0\"})\n\n        self.validate_all(\"SELECT TRUE AS a, FALSE AS b\", write={\"tsql\": \"SELECT 1 AS a, 0 AS b\"})\n\n        self.validate_all(\n            \"SELECT 1 FROM a WHERE TRUE\", write={\"tsql\": \"SELECT 1 FROM a WHERE (1 = 1)\"}\n        )\n\n        self.validate_all(\n            \"CASE WHEN TRUE THEN 'y' WHEN FALSE THEN 'n' ELSE NULL END\",\n            write={\"tsql\": \"CASE WHEN (1 = 1) THEN 'y' WHEN (1 = 0) THEN 'n' ELSE NULL END\"},\n        )\n\n    def test_ddl(self):\n        for colstore in (\"NONCLUSTERED COLUMNSTORE\", \"CLUSTERED COLUMNSTORE\"):\n            self.validate_identity(f\"CREATE {colstore} INDEX index_name ON foo.bar\")\n\n        for view_attr in (\"ENCRYPTION\", \"SCHEMABINDING\", \"VIEW_METADATA\"):\n            self.validate_identity(f\"CREATE VIEW a.b WITH {view_attr} AS SELECT * FROM x\")\n\n        self.validate_identity(\"ALTER TABLE dbo.DocExe DROP CONSTRAINT FK_Column_B\").assert_is(\n            exp.Alter\n        ).args[\"actions\"][0].assert_is(exp.Drop)\n\n        for clustered_keyword in (\"CLUSTERED\", \"NONCLUSTERED\"):\n            self.validate_identity(\n                'CREATE TABLE \"dbo\".\"benchmark\" ('\n                '\"name\" CHAR(7) NOT NULL, '\n                '\"internal_id\" VARCHAR(10) NOT NULL, '\n                f'UNIQUE {clustered_keyword} (\"internal_id\" ASC))',\n                \"CREATE TABLE [dbo].[benchmark] (\"\n                \"[name] CHAR(7) NOT NULL, \"\n                \"[internal_id] VARCHAR(10) NOT NULL, \"\n                f\"UNIQUE {clustered_keyword} ([internal_id] ASC))\",\n            )\n\n        self.validate_identity(\"CREATE SCHEMA testSchema\")\n        self.validate_identity(\"CREATE VIEW t AS WITH cte AS (SELECT 1 AS c) SELECT c FROM cte\")\n        self.validate_identity(\"ALTER TABLE tbl SET (SYSTEM_VERSIONING=OFF)\")\n        self.validate_identity(\"ALTER TABLE tbl SET (FILESTREAM_ON = 'test')\")\n        self.validate_identity(\"ALTER TABLE tbl SET (DATA_DELETION=ON)\")\n        self.validate_identity(\"ALTER TABLE tbl SET (DATA_DELETION=OFF)\")\n        self.validate_identity(\n            \"ALTER TABLE t1 WITH CHECK ADD CONSTRAINT ctr FOREIGN KEY (c1) REFERENCES t2 (c2)\"\n        )\n        self.validate_identity(\n            \"ALTER TABLE tbl SET (SYSTEM_VERSIONING=ON(HISTORY_TABLE=db.tbl, DATA_CONSISTENCY_CHECK=OFF, HISTORY_RETENTION_PERIOD=5 DAYS))\"\n        )\n        self.validate_identity(\n            \"ALTER TABLE tbl SET (SYSTEM_VERSIONING=ON(HISTORY_TABLE=db.tbl, HISTORY_RETENTION_PERIOD=INFINITE))\"\n        )\n        self.validate_identity(\n            \"ALTER TABLE tbl SET (DATA_DELETION=ON(FILTER_COLUMN=col, RETENTION_PERIOD=5 MONTHS))\"\n        )\n\n        self.validate_identity(\"ALTER VIEW v AS SELECT a, b, c, d FROM foo\")\n        self.validate_identity(\"ALTER VIEW v AS SELECT * FROM foo WHERE c > 100\")\n        self.validate_identity(\n            \"ALTER VIEW v WITH SCHEMABINDING AS SELECT * FROM foo WHERE c > 100\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"ALTER VIEW v WITH ENCRYPTION AS SELECT * FROM foo WHERE c > 100\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"ALTER VIEW v WITH VIEW_METADATA AS SELECT * FROM foo WHERE c > 100\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"CREATE COLUMNSTORE INDEX index_name ON foo.bar\",\n            \"CREATE NONCLUSTERED COLUMNSTORE INDEX index_name ON foo.bar\",\n        )\n        self.validate_identity(\n            \"CREATE PROCEDURE foo AS BEGIN DELETE FROM bla WHERE foo < CURRENT_TIMESTAMP - 7; END\",\n            \"CREATE PROCEDURE foo AS BEGIN DELETE FROM bla WHERE foo < GETDATE() - 7; END\",\n        )\n        self.validate_identity(\n            \"INSERT INTO Production.UpdatedInventory SELECT ProductID, LocationID, NewQty, PreviousQty FROM (MERGE INTO Production.ProductInventory AS pi USING (SELECT ProductID, SUM(OrderQty) FROM Sales.SalesOrderDetail AS sod INNER JOIN Sales.SalesOrderHeader AS soh ON sod.SalesOrderID = soh.SalesOrderID AND soh.OrderDate BETWEEN '20030701' AND '20030731' GROUP BY ProductID) AS src(ProductID, OrderQty) ON pi.ProductID = src.ProductID WHEN MATCHED AND pi.Quantity - src.OrderQty >= 0 THEN UPDATE SET pi.Quantity = pi.Quantity - src.OrderQty WHEN MATCHED AND pi.Quantity - src.OrderQty <= 0 THEN DELETE OUTPUT $action, Inserted.ProductID, Inserted.LocationID, Inserted.Quantity AS NewQty, Deleted.Quantity AS PreviousQty) AS Changes(Action, ProductID, LocationID, NewQty, PreviousQty) WHERE Action = 'UPDATE'\",\n            \"\"\"INSERT INTO Production.UpdatedInventory\nSELECT\n  ProductID,\n  LocationID,\n  NewQty,\n  PreviousQty\nFROM (\n  MERGE INTO Production.ProductInventory AS pi\n  USING (\n    SELECT\n      ProductID,\n      SUM(OrderQty)\n    FROM Sales.SalesOrderDetail AS sod\n    INNER JOIN Sales.SalesOrderHeader AS soh\n      ON sod.SalesOrderID = soh.SalesOrderID\n      AND soh.OrderDate BETWEEN '20030701' AND '20030731'\n    GROUP BY\n      ProductID\n  ) AS src(ProductID, OrderQty)\n  ON pi.ProductID = src.ProductID\n  WHEN MATCHED AND pi.Quantity - src.OrderQty >= 0 THEN UPDATE SET\n    pi.Quantity = pi.Quantity - src.OrderQty\n  WHEN MATCHED AND pi.Quantity - src.OrderQty <= 0 THEN DELETE\n  OUTPUT $action, Inserted.ProductID, Inserted.LocationID, Inserted.Quantity AS NewQty, Deleted.Quantity AS PreviousQty\n) AS Changes(Action, ProductID, LocationID, NewQty, PreviousQty)\nWHERE\n  Action = 'UPDATE'\"\"\",\n            pretty=True,\n        )\n\n        self.validate_all(\n            \"CREATE TABLE [#temptest] (name INTEGER)\",\n            read={\n                \"duckdb\": \"CREATE TEMPORARY TABLE 'temptest' (name INTEGER)\",\n                \"tsql\": \"CREATE TABLE [#temptest] (name INTEGER)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE tbl (id INTEGER IDENTITY PRIMARY KEY)\",\n            read={\n                \"mysql\": \"CREATE TABLE tbl (id INT AUTO_INCREMENT PRIMARY KEY)\",\n                \"tsql\": \"CREATE TABLE tbl (id INTEGER IDENTITY PRIMARY KEY)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE tbl (id INTEGER NOT NULL IDENTITY(10, 1) PRIMARY KEY)\",\n            read={\n                \"postgres\": \"CREATE TABLE tbl (id INT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 10) PRIMARY KEY)\",\n                \"tsql\": \"CREATE TABLE tbl (id INTEGER NOT NULL IDENTITY(10, 1) PRIMARY KEY)\",\n            },\n            write={\n                \"databricks\": \"CREATE TABLE tbl (id BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 10 INCREMENT BY 1) PRIMARY KEY)\",\n                \"postgres\": \"CREATE TABLE tbl (id INT NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 10 INCREMENT BY 1) PRIMARY KEY)\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE x (a UNIQUEIDENTIFIER, b VARBINARY)\",\n            write={\n                \"duckdb\": \"CREATE TABLE x (a UUID, b BLOB)\",\n                \"presto\": \"CREATE TABLE x (a UUID, b VARBINARY)\",\n                \"spark\": \"CREATE TABLE x (a STRING, b BINARY)\",\n                \"postgres\": \"CREATE TABLE x (a UUID, b BYTEA)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * INTO foo.bar.baz FROM (SELECT * FROM a.b.c) AS temp\",\n            read={\n                \"\": \"CREATE TABLE foo.bar.baz AS SELECT * FROM a.b.c\",\n                \"duckdb\": \"CREATE TABLE foo.bar.baz AS (SELECT * FROM a.b.c)\",\n            },\n        )\n        self.validate_all(\n            \"IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = object_id('db.tbl') AND name = 'idx') EXEC('CREATE INDEX idx ON db.tbl')\",\n            read={\n                \"\": \"CREATE INDEX IF NOT EXISTS idx ON db.tbl\",\n            },\n        )\n\n        self.validate_all(\n            \"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'foo') EXEC('CREATE SCHEMA foo')\",\n            read={\n                \"\": \"CREATE SCHEMA IF NOT EXISTS foo\",\n            },\n        )\n        self.validate_all(\n            \"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'baz' AND TABLE_SCHEMA = 'bar' AND TABLE_CATALOG = 'foo') EXEC('CREATE TABLE foo.bar.baz (a INTEGER)')\",\n            read={\n                \"\": \"CREATE TABLE IF NOT EXISTS foo.bar.baz (a INTEGER)\",\n            },\n        )\n        self.validate_all(\n            \"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'baz' AND TABLE_SCHEMA = 'bar' AND TABLE_CATALOG = 'foo') EXEC('SELECT * INTO foo.bar.baz FROM (SELECT ''2020'' AS z FROM a.b.c) AS temp')\",\n            read={\n                \"\": \"CREATE TABLE IF NOT EXISTS foo.bar.baz AS SELECT '2020' AS z FROM a.b.c\",\n            },\n        )\n        self.validate_all(\n            \"IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'baz' AND TABLE_SCHEMA = 'bar' AND TABLE_CATALOG = 'foo') EXEC('WITH cte1 AS (SELECT 1 AS col_a), cte2 AS (SELECT 1 AS col_b) SELECT * INTO foo.bar.baz FROM (SELECT col_a FROM cte1 UNION ALL SELECT col_b FROM cte2) AS temp')\",\n            read={\n                \"\": \"CREATE TABLE IF NOT EXISTS foo.bar.baz AS WITH cte1 AS (SELECT 1 AS col_a), cte2 AS (SELECT 1 AS col_b) SELECT col_a FROM cte1 UNION ALL SELECT col_b FROM cte2\"\n            },\n        )\n        self.validate_all(\n            \"CREATE OR ALTER VIEW a.b AS SELECT 1\",\n            read={\n                \"\": \"CREATE OR REPLACE VIEW a.b AS SELECT 1\",\n            },\n            write={\n                \"tsql\": \"CREATE OR ALTER VIEW a.b AS SELECT 1\",\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE a ADD b INTEGER, c INTEGER\",\n            read={\n                \"\": \"ALTER TABLE a ADD COLUMN b INT, ADD COLUMN c INT\",\n            },\n            write={\n                \"\": \"ALTER TABLE a ADD COLUMN b INT, ADD COLUMN c INT\",\n                \"tsql\": \"ALTER TABLE a ADD b INTEGER, c INTEGER\",\n            },\n        )\n        self.validate_all(\n            \"ALTER TABLE a ALTER COLUMN b INTEGER\",\n            read={\n                \"\": \"ALTER TABLE a ALTER COLUMN b INT\",\n            },\n            write={\n                \"\": \"ALTER TABLE a ALTER COLUMN b SET DATA TYPE INT\",\n                \"tsql\": \"ALTER TABLE a ALTER COLUMN b INTEGER\",\n            },\n        )\n        self.validate_all(\n            \"CREATE TABLE #mytemp (a INTEGER, b CHAR(2), c TIME(4), d FLOAT(24))\",\n            write={\n                \"spark\": \"CREATE TEMPORARY TABLE mytemp (a INT, b CHAR(2), c TIMESTAMP, d FLOAT) USING PARQUET\",\n                \"tsql\": \"CREATE TABLE #mytemp (a INTEGER, b CHAR(2), c TIME(4), d FLOAT(24))\",\n            },\n        )\n\n        constraint = self.validate_identity(\n            \"ALTER TABLE tbl ADD CONSTRAINT cnstr PRIMARY KEY CLUSTERED (ID), CONSTRAINT cnstr2 UNIQUE CLUSTERED (ID)\"\n        ).find(exp.AddConstraint)\n        assert constraint\n        assert len(list(constraint.find_all(exp.Constraint))) == 2\n\n    def test_transaction(self):\n        self.validate_identity(\"BEGIN TRANSACTION\")\n        self.validate_all(\"BEGIN TRAN\", write={\"tsql\": \"BEGIN TRANSACTION\"})\n        self.validate_identity(\"BEGIN TRANSACTION transaction_name\")\n        self.validate_identity(\"BEGIN TRANSACTION @tran_name_variable\")\n        self.validate_identity(\"BEGIN TRANSACTION transaction_name WITH MARK 'description'\")\n\n    def test_commit(self):\n        self.validate_all(\"COMMIT\", write={\"tsql\": \"COMMIT TRANSACTION\"})\n        self.validate_all(\"COMMIT TRAN\", write={\"tsql\": \"COMMIT TRANSACTION\"})\n        self.validate_identity(\"COMMIT TRANSACTION\")\n        self.validate_identity(\"COMMIT TRANSACTION transaction_name\")\n        self.validate_identity(\"COMMIT TRANSACTION @tran_name_variable\")\n\n        self.validate_identity(\n            \"COMMIT TRANSACTION @tran_name_variable WITH (DELAYED_DURABILITY = ON)\"\n        )\n        self.validate_identity(\n            \"COMMIT TRANSACTION transaction_name WITH (DELAYED_DURABILITY = OFF)\"\n        )\n\n    def test_rollback(self):\n        self.validate_all(\"ROLLBACK\", write={\"tsql\": \"ROLLBACK TRANSACTION\"})\n        self.validate_all(\"ROLLBACK TRAN\", write={\"tsql\": \"ROLLBACK TRANSACTION\"})\n        self.validate_identity(\"ROLLBACK TRANSACTION\")\n        self.validate_identity(\"ROLLBACK TRANSACTION transaction_name\")\n        self.validate_identity(\"ROLLBACK TRANSACTION @tran_name_variable\")\n\n    def test_udf(self):\n        self.validate_identity(\n            \"DECLARE @DWH_DateCreated AS DATETIME2 = CONVERT(DATETIME2, GETDATE(), 104)\",\n            \"DECLARE @DWH_DateCreated DATETIME2 = CONVERT(DATETIME2, GETDATE(), 104)\",\n        )\n        self.validate_identity(\n            \"CREATE PROCEDURE foo @a INTEGER, @b INTEGER AS SELECT @a = SUM(bla) FROM baz AS bar\"\n        )\n        self.validate_identity(\n            \"CREATE PROC foo @ID INTEGER, @AGE INTEGER AS SELECT DB_NAME(@ID) AS ThatDB\"\n        )\n        self.validate_identity(\"CREATE PROC foo AS SELECT BAR() AS baz\")\n        self.validate_identity(\"CREATE PROCEDURE foo AS SELECT BAR() AS baz\")\n\n        self.validate_identity(\"CREATE PROCEDURE foo WITH ENCRYPTION AS SELECT 1\")\n        self.validate_identity(\"CREATE PROCEDURE foo WITH RECOMPILE AS SELECT 1\")\n        self.validate_identity(\"CREATE PROCEDURE foo WITH SCHEMABINDING AS SELECT 1\")\n        self.validate_identity(\"CREATE PROCEDURE foo WITH NATIVE_COMPILATION AS SELECT 1\")\n        self.validate_identity(\"CREATE PROCEDURE foo WITH EXECUTE AS OWNER AS SELECT 1\")\n        self.validate_identity(\"CREATE PROCEDURE foo WITH EXECUTE AS 'username' AS SELECT 1\")\n        self.validate_identity(\n            \"CREATE PROCEDURE foo WITH EXECUTE AS OWNER, SCHEMABINDING, NATIVE_COMPILATION AS SELECT 1\"\n        )\n\n        self.validate_identity(\"CREATE FUNCTION foo(@bar INTEGER) RETURNS TABLE AS RETURN SELECT 1\")\n        self.validate_identity(\"CREATE FUNCTION dbo.ISOweek(@DATE DATETIME2) RETURNS INTEGER\")\n\n        # The following two cases don't necessarily correspond to valid TSQL, but they are used to verify\n        # that the syntax RETURNS @return_variable TABLE <table_type_definition> ... is parsed correctly.\n        #\n        # See also \"Transact-SQL Multi-Statement Table-Valued Function Syntax\"\n        # https://learn.microsoft.com/en-us/sql/t-sql/statements/create-function-transact-sql?view=sql-server-ver16\n        self.validate_identity(\n            \"CREATE FUNCTION foo(@bar INTEGER) RETURNS @foo TABLE (x INTEGER, y NUMERIC) AS RETURN SELECT 1\"\n        )\n        self.validate_identity(\n            \"CREATE FUNCTION foo() RETURNS @contacts TABLE (first_name VARCHAR(50), phone VARCHAR(25)) AS SELECT @fname, @phone\"\n        )\n\n        self.validate_all(\n            \"\"\"\n            CREATE FUNCTION udfProductInYear (\n                @model_year INT\n            )\n            RETURNS TABLE\n            AS\n            RETURN\n                SELECT\n                    product_name,\n                    model_year,\n                    list_price\n                FROM\n                    production.products\n                WHERE\n                    model_year = @model_year\n            \"\"\",\n            write={\n                \"tsql\": \"\"\"CREATE FUNCTION udfProductInYear(\n    @model_year INTEGER\n)\nRETURNS TABLE AS\nRETURN SELECT\n  product_name,\n  model_year,\n  list_price\nFROM production.products\nWHERE\n  model_year = @model_year\"\"\",\n            },\n            pretty=True,\n        )\n\n    def test_procedure_keywords(self):\n        self.validate_identity(\"BEGIN\")\n        self.validate_identity(\"END\")\n        self.validate_identity(\"SET XACT_ABORT ON\")\n\n    def test_charindex(self):\n        self.validate_identity(\n            \"SELECT CAST(SUBSTRING('ABCD~1234', CHARINDEX('~', 'ABCD~1234') + 1, LEN('ABCD~1234')) AS BIGINT)\"\n        )\n\n        self.validate_all(\n            \"CHARINDEX(x, y, 9)\",\n            read={\n                \"spark\": \"LOCATE(x, y, 9)\",\n            },\n            write={\n                \"spark\": \"LOCATE(x, y, 9)\",\n                \"tsql\": \"CHARINDEX(x, y, 9)\",\n            },\n        )\n        self.validate_all(\n            \"CHARINDEX(x, y)\",\n            read={\n                \"spark\": \"LOCATE(x, y)\",\n            },\n            write={\n                \"spark\": \"LOCATE(x, y)\",\n                \"tsql\": \"CHARINDEX(x, y)\",\n            },\n        )\n        self.validate_all(\n            \"CHARINDEX('sub', 'testsubstring', 3)\",\n            read={\n                \"spark\": \"LOCATE('sub', 'testsubstring', 3)\",\n            },\n            write={\n                \"spark\": \"LOCATE('sub', 'testsubstring', 3)\",\n                \"tsql\": \"CHARINDEX('sub', 'testsubstring', 3)\",\n            },\n        )\n        self.validate_all(\n            \"CHARINDEX('sub', 'testsubstring')\",\n            read={\n                \"spark\": \"LOCATE('sub', 'testsubstring')\",\n            },\n            write={\n                \"spark\": \"LOCATE('sub', 'testsubstring')\",\n                \"tsql\": \"CHARINDEX('sub', 'testsubstring')\",\n            },\n        )\n\n    def test_len(self):\n        self.validate_all(\n            \"LEN(x)\", read={\"\": \"LENGTH(x)\"}, write={\"spark\": \"LENGTH(CAST(x AS STRING))\"}\n        )\n        self.validate_all(\n            \"RIGHT(x, 1)\",\n            read={\"\": \"RIGHT(CAST(x AS STRING), 1)\"},\n            write={\"spark\": \"RIGHT(CAST(x AS STRING), 1)\"},\n        )\n        self.validate_all(\n            \"LEFT(x, 1)\",\n            read={\"\": \"LEFT(CAST(x AS STRING), 1)\"},\n            write={\"spark\": \"LEFT(CAST(x AS STRING), 1)\"},\n        )\n        self.validate_all(\"LEN(1)\", write={\"tsql\": \"LEN(1)\", \"spark\": \"LENGTH(CAST(1 AS STRING))\"})\n        self.validate_all(\"LEN('x')\", write={\"tsql\": \"LEN('x')\", \"spark\": \"LENGTH('x')\"})\n\n    def test_replicate(self):\n        self.validate_all(\n            \"REPLICATE('x', 2)\",\n            write={\n                \"spark\": \"REPEAT('x', 2)\",\n                \"tsql\": \"REPLICATE('x', 2)\",\n            },\n        )\n\n    def test_isnull(self):\n        self.validate_identity(\"ISNULL(x, y)\")\n        self.validate_all(\"ISNULL(x, y)\", write={\"spark\": \"COALESCE(x, y)\"})\n\n    def test_json(self):\n        self.validate_identity(\n            \"\"\"JSON_QUERY(REPLACE(REPLACE(x , '''', '\"'), '\"\"', '\"'))\"\"\",\n            \"\"\"ISNULL(JSON_QUERY(REPLACE(REPLACE(x, '''', '\"'), '\"\"', '\"'), '$'), JSON_VALUE(REPLACE(REPLACE(x, '''', '\"'), '\"\"', '\"'), '$'))\"\"\",\n        )\n\n        self.validate_all(\n            \"JSON_QUERY(r.JSON, '$.Attr_INT')\",\n            write={\n                \"spark\": \"GET_JSON_OBJECT(r.JSON, '$.Attr_INT')\",\n                \"tsql\": \"ISNULL(JSON_QUERY(r.JSON, '$.Attr_INT'), JSON_VALUE(r.JSON, '$.Attr_INT'))\",\n            },\n        )\n        self.validate_all(\n            \"JSON_VALUE(r.JSON, '$.Attr_INT')\",\n            write={\n                \"spark\": \"GET_JSON_OBJECT(r.JSON, '$.Attr_INT')\",\n                \"tsql\": \"ISNULL(JSON_QUERY(r.JSON, '$.Attr_INT'), JSON_VALUE(r.JSON, '$.Attr_INT'))\",\n            },\n        )\n\n    def test_datefromparts(self):\n        self.validate_all(\n            \"SELECT DATEFROMPARTS('2020', 10, 01)\",\n            write={\n                \"spark\": \"SELECT MAKE_DATE('2020', 10, 01)\",\n                \"tsql\": \"SELECT DATEFROMPARTS('2020', 10, 01)\",\n            },\n        )\n\n    def test_datename(self):\n        self.validate_all(\n            \"SELECT DATENAME(mm, '1970-01-01')\",\n            write={\n                \"spark\": \"SELECT DATE_FORMAT(CAST('1970-01-01' AS TIMESTAMP), 'MMMM')\",\n                \"tsql\": \"SELECT FORMAT(CAST('1970-01-01' AS DATETIME2), 'MMMM')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATENAME(dw, '1970-01-01')\",\n            write={\n                \"spark\": \"SELECT DATE_FORMAT(CAST('1970-01-01' AS TIMESTAMP), 'EEEE')\",\n                \"tsql\": \"SELECT FORMAT(CAST('1970-01-01' AS DATETIME2), 'dddd')\",\n            },\n        )\n\n    def test_datepart(self):\n        datepart_formats = (\n            ((\"QUARTER\", \"qq\", \"q\"), \"QUARTER\"),\n            ((\"YEAR\", \"yy\", \"yyyy\"), \"YEAR\"),\n            ((\"HOUR\", \"hh\"), \"HOUR\"),\n            ((\"MINUTE\", \"mi\", \"n\"), \"MINUTE\"),\n            ((\"SECOND\", \"ss\", \"s\"), \"SECOND\"),\n            ((\"MILLISECOND\", \"ms\"), \"MILLISECOND\"),\n            ((\"MICROSECOND\", \"mcs\"), \"MICROSECOND\"),\n            ((\"NANOSECOND\", \"ns\"), \"NANOSECOND\"),\n            ((\"WEEKDAY\", \"dw\"), \"WEEKDAY\"),\n            ((\"TZOFFSET\", \"tz\"), \"TZOFFSET\"),\n            ((\"MONTH\", \"mm\", \"m\"), \"MONTH\"),\n            ((\"DAYOFYEAR\", \"dy\", \"y\"), \"DAYOFYEAR\"),\n            ((\"DAY\", \"dd\", \"d\"), \"DAY\"),\n        )\n\n        for formats, canonical in datepart_formats:\n            for fmt in formats:\n                with self.subTest(f\"Testing DATEPART where part is: {fmt}\"):\n                    self.validate_identity(\n                        f\"DATEPART({fmt}, x)\",\n                        f\"DATEPART({canonical}, x)\",\n                    )\n\n        select_datepart_formats = (\n            ((\"WEEK\", \"WW\", \"WK\"), \"WEEK\"),\n            ((\"ISOWK\", \"ISOWW\", \"ISO_WEEK\"), \"ISO_WEEK\"),\n        )\n\n        for formats, canonical in select_datepart_formats:\n            for fmt in formats:\n                with self.subTest(f\"Testing DATEPART where part is: {fmt}\"):\n                    self.validate_identity(\n                        f\"SELECT DATEPART({fmt}, '2024-11-21')\",\n                        f\"SELECT DATEPART({canonical}, '2024-11-21')\",\n                    )\n\n        self.validate_all(\n            \"SELECT DATEPART(month,'1970-01-01')\",\n            write={\n                \"spark\": \"SELECT EXTRACT(month FROM '1970-01-01')\",\n                \"tsql\": \"SELECT DATEPART(month, '1970-01-01')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEPART(YEAR, CAST('2017-01-01' AS DATE))\",\n            read={\n                \"postgres\": \"SELECT DATE_PART('YEAR', '2017-01-01'::DATE)\",\n            },\n            write={\n                \"postgres\": \"SELECT EXTRACT(YEAR FROM CAST('2017-01-01' AS DATE))\",\n                \"spark\": \"SELECT EXTRACT(YEAR FROM CAST('2017-01-01' AS DATE))\",\n                \"tsql\": \"SELECT DATEPART(YEAR, CAST('2017-01-01' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEPART(month, CAST('2017-03-01' AS DATE))\",\n            read={\n                \"postgres\": \"SELECT DATE_PART('month', '2017-03-01'::DATE)\",\n            },\n            write={\n                \"postgres\": \"SELECT EXTRACT(month FROM CAST('2017-03-01' AS DATE))\",\n                \"spark\": \"SELECT EXTRACT(month FROM CAST('2017-03-01' AS DATE))\",\n                \"tsql\": \"SELECT DATEPART(month, CAST('2017-03-01' AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATEPART(day, CAST('2017-01-02' AS DATE))\",\n            read={\n                \"postgres\": \"SELECT DATE_PART('day', '2017-01-02'::DATE)\",\n            },\n            write={\n                \"postgres\": \"SELECT EXTRACT(day FROM CAST('2017-01-02' AS DATE))\",\n                \"spark\": \"SELECT EXTRACT(day FROM CAST('2017-01-02' AS DATE))\",\n                \"tsql\": \"SELECT DATEPART(day, CAST('2017-01-02' AS DATE))\",\n            },\n        )\n        self.validate_identity(\n            'SELECT DATEPART(\"dd\", x)',\n            \"SELECT DATEPART(DAY, x)\",\n        )\n\n    def test_convert(self):\n        self.validate_all(\n            \"CONVERT(NVARCHAR(200), x)\",\n            write={\n                \"spark\": \"CAST(x AS VARCHAR(200))\",\n                \"tsql\": \"CONVERT(NVARCHAR(200), x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(NVARCHAR, x)\",\n            write={\n                \"spark\": \"CAST(x AS VARCHAR(30))\",\n                \"tsql\": \"CONVERT(NVARCHAR, x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(NVARCHAR(MAX), x)\",\n            write={\n                \"spark\": \"CAST(x AS STRING)\",\n                \"tsql\": \"CONVERT(NVARCHAR(MAX), x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(VARCHAR(200), x)\",\n            write={\n                \"spark\": \"CAST(x AS VARCHAR(200))\",\n                \"tsql\": \"CONVERT(VARCHAR(200), x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(VARCHAR, x)\",\n            write={\n                \"spark\": \"CAST(x AS VARCHAR(30))\",\n                \"tsql\": \"CONVERT(VARCHAR, x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(VARCHAR(MAX), x)\",\n            write={\n                \"spark\": \"CAST(x AS STRING)\",\n                \"tsql\": \"CONVERT(VARCHAR(MAX), x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(CHAR(40), x)\",\n            write={\n                \"spark\": \"CAST(x AS CHAR(40))\",\n                \"tsql\": \"CONVERT(CHAR(40), x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(CHAR, x)\",\n            write={\n                \"spark\": \"CAST(x AS CHAR(30))\",\n                \"tsql\": \"CONVERT(CHAR, x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(NCHAR(40), x)\",\n            write={\n                \"spark\": \"CAST(x AS CHAR(40))\",\n                \"tsql\": \"CONVERT(NCHAR(40), x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(NCHAR, x)\",\n            write={\n                \"spark\": \"CAST(x AS CHAR(30))\",\n                \"tsql\": \"CONVERT(NCHAR, x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(VARCHAR, x, 121)\",\n            write={\n                \"spark\": \"CAST(DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS') AS VARCHAR(30))\",\n                \"tsql\": \"CONVERT(VARCHAR, x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(VARCHAR(40), x, 121)\",\n            write={\n                \"spark\": \"CAST(DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS') AS VARCHAR(40))\",\n                \"tsql\": \"CONVERT(VARCHAR(40), x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(VARCHAR(MAX), x, 121)\",\n            write={\n                \"spark\": \"CAST(DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS') AS STRING)\",\n                \"tsql\": \"CONVERT(VARCHAR(MAX), x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(NVARCHAR, x, 121)\",\n            write={\n                \"spark\": \"CAST(DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS') AS VARCHAR(30))\",\n                \"tsql\": \"CONVERT(NVARCHAR, x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(NVARCHAR(40), x, 121)\",\n            write={\n                \"spark\": \"CAST(DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS') AS VARCHAR(40))\",\n                \"tsql\": \"CONVERT(NVARCHAR(40), x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(NVARCHAR(MAX), x, 121)\",\n            write={\n                \"spark\": \"CAST(DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS') AS STRING)\",\n                \"tsql\": \"CONVERT(NVARCHAR(MAX), x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(DATE, x, 121)\",\n            write={\n                \"spark\": \"TO_DATE(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS')\",\n                \"tsql\": \"CONVERT(DATE, x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(DATETIME, x, 121)\",\n            write={\n                \"spark\": \"TO_TIMESTAMP(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS')\",\n                \"tsql\": \"CONVERT(DATETIME, x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(DATETIME2, x, 121)\",\n            write={\n                \"spark\": \"TO_TIMESTAMP(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS')\",\n                \"tsql\": \"CONVERT(DATETIME2, x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(INT, x)\",\n            write={\n                \"spark\": \"CAST(x AS INT)\",\n                \"tsql\": \"CONVERT(INTEGER, x)\",\n            },\n        )\n        self.validate_all(\n            \"CONVERT(INT, x, 121)\",\n            write={\n                \"spark\": \"CAST(x AS INT)\",\n                \"tsql\": \"CONVERT(INTEGER, x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"TRY_CONVERT(NVARCHAR, x, 121)\",\n            write={\n                \"spark\": \"TRY_CAST(DATE_FORMAT(x, 'yyyy-MM-dd HH:mm:ss.SSSSSS') AS VARCHAR(30))\",\n                \"tsql\": \"TRY_CONVERT(NVARCHAR, x, 121)\",\n            },\n        )\n        self.validate_all(\n            \"TRY_CONVERT(INT, x)\",\n            write={\n                \"spark\": \"TRY_CAST(x AS INT)\",\n                \"tsql\": \"TRY_CONVERT(INTEGER, x)\",\n            },\n        )\n        self.validate_all(\n            \"TRY_CAST(x AS INT)\",\n            write={\n                \"spark\": \"TRY_CAST(x AS INT)\",\n                \"tsql\": \"TRY_CAST(x AS INTEGER)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CONVERT(VARCHAR(10), testdb.dbo.test.x, 120) y FROM testdb.dbo.test\",\n            write={\n                \"mysql\": \"SELECT CAST(DATE_FORMAT(testdb.dbo.test.x, '%Y-%m-%d %T') AS CHAR(10)) AS y FROM testdb.dbo.test\",\n                \"spark\": \"SELECT CAST(DATE_FORMAT(testdb.dbo.test.x, 'yyyy-MM-dd HH:mm:ss') AS VARCHAR(10)) AS y FROM testdb.dbo.test\",\n                \"tsql\": \"SELECT CONVERT(VARCHAR(10), testdb.dbo.test.x, 120) AS y FROM testdb.dbo.test\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CONVERT(VARCHAR(10), y.x) z FROM testdb.dbo.test y\",\n            write={\n                \"mysql\": \"SELECT CAST(y.x AS CHAR(10)) AS z FROM testdb.dbo.test AS y\",\n                \"spark\": \"SELECT CAST(y.x AS VARCHAR(10)) AS z FROM testdb.dbo.test AS y\",\n                \"tsql\": \"SELECT CONVERT(VARCHAR(10), y.x) AS z FROM testdb.dbo.test AS y\",\n            },\n        )\n        self.validate_all(\n            \"SELECT CAST((SELECT x FROM y) AS VARCHAR) AS test\",\n            write={\n                \"spark\": \"SELECT CAST((SELECT x FROM y) AS STRING) AS test\",\n                \"tsql\": \"SELECT CAST((SELECT x FROM y) AS VARCHAR) AS test\",\n            },\n        )\n\n    def test_add_date(self):\n        self.validate_identity(\"SELECT DATEADD(YEAR, 1, '2017/08/25')\")\n\n        self.validate_all(\n            \"DATEADD(year, 50, '2006-07-31')\",\n            write={\"bigquery\": \"DATE_ADD('2006-07-31', INTERVAL 50 YEAR)\"},\n        )\n        self.validate_all(\n            \"SELECT DATEADD(year, 1, '2017/08/25')\",\n            write={\"spark\": \"SELECT ADD_MONTHS('2017/08/25', 12)\"},\n        )\n        self.validate_all(\n            \"SELECT DATEADD(qq, 1, '2017/08/25')\",\n            write={\"spark\": \"SELECT ADD_MONTHS('2017/08/25', 3)\"},\n        )\n        self.validate_all(\n            \"SELECT DATEADD(wk, 1, '2017/08/25')\",\n            write={\n                \"spark\": \"SELECT DATE_ADD('2017/08/25', 7)\",\n                \"databricks\": \"SELECT DATEADD(WEEK, 1, '2017/08/25')\",\n            },\n        )\n\n    def test_date_diff(self):\n        self.validate_identity(\"SELECT DATEDIFF(HOUR, 1.5, '2021-01-01')\")\n        self.validate_identity(\"SELECT DATEDIFF_BIG(HOUR, 1.5, '2021-01-01')\")\n\n        for fnc in [\"DATEDIFF\", \"DATEDIFF_BIG\"]:\n            with self.subTest(f\"Transpiling T-SQL's {fnc}\"):\n                self.validate_all(\n                    f\"SELECT {fnc}(quarter, 0, '2021-01-01')\",\n                    write={\n                        \"tsql\": f\"SELECT {fnc}(QUARTER, CAST('1900-01-01' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))\",\n                        \"spark\": \"SELECT DATEDIFF(QUARTER, CAST('1900-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))\",\n                        \"duckdb\": \"SELECT DATE_DIFF('QUARTER', CAST('1900-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))\",\n                    },\n                )\n                self.validate_all(\n                    f\"SELECT {fnc}(day, 1, '2021-01-01')\",\n                    write={\n                        \"tsql\": f\"SELECT {fnc}(DAY, CAST('1900-01-02' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))\",\n                        \"spark\": \"SELECT DATEDIFF(DAY, CAST('1900-01-02' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))\",\n                        \"duckdb\": \"SELECT DATE_DIFF('DAY', CAST('1900-01-02' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))\",\n                    },\n                )\n                self.validate_all(\n                    f\"SELECT {fnc}(year, '2020-01-01', '2021-01-01')\",\n                    write={\n                        \"tsql\": f\"SELECT {fnc}(YEAR, CAST('2020-01-01' AS DATETIME2), CAST('2021-01-01' AS DATETIME2))\",\n                        \"spark\": \"SELECT DATEDIFF(YEAR, CAST('2020-01-01' AS TIMESTAMP), CAST('2021-01-01' AS TIMESTAMP))\",\n                        \"spark2\": \"SELECT CAST(MONTHS_BETWEEN(CAST('2021-01-01' AS TIMESTAMP), CAST('2020-01-01' AS TIMESTAMP)) / 12 AS INT)\",\n                    },\n                )\n                self.validate_all(\n                    f\"SELECT {fnc}(mm, 'start', 'end')\",\n                    write={\n                        \"databricks\": \"SELECT DATEDIFF(MONTH, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))\",\n                        \"spark2\": \"SELECT CAST(MONTHS_BETWEEN(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP)) AS INT)\",\n                        \"tsql\": f\"SELECT {fnc}(MONTH, CAST('start' AS DATETIME2), CAST('end' AS DATETIME2))\",\n                    },\n                )\n                self.validate_all(\n                    f\"SELECT {fnc}(quarter, 'start', 'end')\",\n                    write={\n                        \"databricks\": \"SELECT DATEDIFF(QUARTER, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))\",\n                        \"spark\": \"SELECT DATEDIFF(QUARTER, CAST('start' AS TIMESTAMP), CAST('end' AS TIMESTAMP))\",\n                        \"spark2\": \"SELECT CAST(MONTHS_BETWEEN(CAST('end' AS TIMESTAMP), CAST('start' AS TIMESTAMP)) / 3 AS INT)\",\n                        \"tsql\": f\"SELECT {fnc}(QUARTER, CAST('start' AS DATETIME2), CAST('end' AS DATETIME2))\",\n                    },\n                )\n\n                # Check superfluous casts arent added. ref: https://github.com/TobikoData/sqlmesh/issues/2672\n                self.validate_all(\n                    f\"SELECT {fnc}(DAY, CAST(a AS DATETIME2), CAST(b AS DATETIME2)) AS x FROM foo\",\n                    write={\n                        \"tsql\": f\"SELECT {fnc}(DAY, CAST(a AS DATETIME2), CAST(b AS DATETIME2)) AS x FROM foo\",\n                        \"clickhouse\": \"SELECT DATE_DIFF(DAY, CAST(CAST(a AS Nullable(DateTime)) AS DateTime64(6)), CAST(CAST(b AS Nullable(DateTime)) AS DateTime64(6))) AS x FROM foo\",\n                    },\n                )\n\n                self.validate_identity(\n                    f\"SELECT DATEADD(DAY, {fnc}(DAY, -3, GETDATE()), '08:00:00')\",\n                    f\"SELECT DATEADD(DAY, {fnc}(DAY, CAST('1899-12-29' AS DATETIME2), CAST(GETDATE() AS DATETIME2)), '08:00:00')\",\n                )\n\n    def test_lateral_subquery(self):\n        self.validate_all(\n            \"SELECT x.a, x.b, t.v, t.y FROM x CROSS APPLY (SELECT v, y FROM t) t(v, y)\",\n            write={\n                \"spark\": \"SELECT x.a, x.b, t.v, t.y FROM x INNER JOIN LATERAL (SELECT v, y FROM t) AS t(v, y)\",\n                \"postgres\": \"SELECT x.a, x.b, t.v, t.y FROM x INNER JOIN LATERAL (SELECT v, y FROM t) AS t(v, y) ON TRUE\",\n                \"tsql\": \"SELECT x.a, x.b, t.v, t.y FROM x CROSS APPLY (SELECT v, y FROM t) AS t(v, y)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT x.a, x.b, t.v, t.y FROM x OUTER APPLY (SELECT v, y FROM t) t(v, y)\",\n            write={\n                \"spark\": \"SELECT x.a, x.b, t.v, t.y FROM x LEFT JOIN LATERAL (SELECT v, y FROM t) AS t(v, y)\",\n                \"postgres\": \"SELECT x.a, x.b, t.v, t.y FROM x LEFT JOIN LATERAL (SELECT v, y FROM t) AS t(v, y) ON TRUE\",\n                \"tsql\": \"SELECT x.a, x.b, t.v, t.y FROM x OUTER APPLY (SELECT v, y FROM t) AS t(v, y)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT x.a, x.b, t.v, t.y, s.v, s.y FROM x OUTER APPLY (SELECT v, y FROM t) t(v, y) OUTER APPLY (SELECT v, y FROM t) s(v, y) LEFT JOIN z ON z.id = s.id\",\n            write={\n                \"spark\": \"SELECT x.a, x.b, t.v, t.y, s.v, s.y FROM x LEFT JOIN LATERAL (SELECT v, y FROM t) AS t(v, y) LEFT JOIN LATERAL (SELECT v, y FROM t) AS s(v, y) LEFT JOIN z ON z.id = s.id\",\n                \"postgres\": \"SELECT x.a, x.b, t.v, t.y, s.v, s.y FROM x LEFT JOIN LATERAL (SELECT v, y FROM t) AS t(v, y) ON TRUE LEFT JOIN LATERAL (SELECT v, y FROM t) AS s(v, y) ON TRUE LEFT JOIN z ON z.id = s.id\",\n                \"tsql\": \"SELECT x.a, x.b, t.v, t.y, s.v, s.y FROM x OUTER APPLY (SELECT v, y FROM t) AS t(v, y) OUTER APPLY (SELECT v, y FROM t) AS s(v, y) LEFT JOIN z ON z.id = s.id\",\n            },\n        )\n\n    def test_lateral_table_valued_function(self):\n        self.validate_all(\n            \"SELECT t.x, y.z FROM x CROSS APPLY tvfTest(t.x) y(z)\",\n            write={\n                \"spark\": \"SELECT t.x, y.z FROM x INNER JOIN LATERAL TVFTEST(t.x) AS y(z)\",\n                \"postgres\": \"SELECT t.x, y.z FROM x INNER JOIN LATERAL TVFTEST(t.x) AS y(z) ON TRUE\",\n                \"tsql\": \"SELECT t.x, y.z FROM x CROSS APPLY TVFTEST(t.x) AS y(z)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT t.x, y.z FROM x OUTER APPLY tvfTest(t.x)y(z)\",\n            write={\n                \"spark\": \"SELECT t.x, y.z FROM x LEFT JOIN LATERAL TVFTEST(t.x) AS y(z)\",\n                \"postgres\": \"SELECT t.x, y.z FROM x LEFT JOIN LATERAL TVFTEST(t.x) AS y(z) ON TRUE\",\n                \"tsql\": \"SELECT t.x, y.z FROM x OUTER APPLY TVFTEST(t.x) AS y(z)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT t.x, y.z FROM x OUTER APPLY a.b.tvfTest(t.x)y(z)\",\n            write={\n                \"spark\": \"SELECT t.x, y.z FROM x LEFT JOIN LATERAL a.b.tvfTest(t.x) AS y(z)\",\n                \"postgres\": \"SELECT t.x, y.z FROM x LEFT JOIN LATERAL a.b.tvfTest(t.x) AS y(z) ON TRUE\",\n                \"tsql\": \"SELECT t.x, y.z FROM x OUTER APPLY a.b.tvfTest(t.x) AS y(z)\",\n            },\n        )\n\n    def test_top(self):\n        self.validate_all(\n            \"SELECT DISTINCT TOP 3 * FROM A\",\n            read={\n                \"spark\": \"SELECT DISTINCT * FROM A LIMIT 3\",\n            },\n            write={\n                \"spark\": \"SELECT DISTINCT * FROM A LIMIT 3\",\n                \"teradata\": \"SELECT DISTINCT TOP 3 * FROM A\",\n                \"tsql\": \"SELECT DISTINCT TOP 3 * FROM A\",\n            },\n        )\n        self.validate_all(\n            \"SELECT TOP (3) * FROM A\",\n            write={\n                \"spark\": \"SELECT * FROM A LIMIT 3\",\n            },\n        )\n        self.validate_identity(\n            \"CREATE TABLE schema.table AS SELECT a, id FROM (SELECT a, (SELECT id FROM tb ORDER BY t DESC LIMIT 1) as id FROM tbl) AS _subquery\",\n            \"SELECT * INTO schema.table FROM (SELECT a AS a, id AS id FROM (SELECT a AS a, (SELECT TOP 1 id FROM tb ORDER BY t DESC) AS id FROM tbl) AS _subquery) AS temp\",\n        )\n        self.validate_identity(\"SELECT TOP 10 PERCENT\")\n        self.validate_identity(\"SELECT TOP 10 PERCENT WITH TIES\")\n\n    def test_format(self):\n        self.validate_identity(\"SELECT FORMAT(foo, 'dddd', 'de-CH')\")\n        self.validate_identity(\"SELECT FORMAT(EndOfDayRate, 'N', 'en-us')\")\n        self.validate_identity(\"SELECT FORMAT('01-01-1991', 'd.mm.yyyy')\")\n        self.validate_identity(\"SELECT FORMAT(12345, '###.###.###')\")\n        self.validate_identity(\"SELECT FORMAT(1234567, 'f')\")\n\n        self.validate_all(\n            \"SELECT FORMAT(1000000.01,'###,###.###')\",\n            write={\n                \"spark\": \"SELECT FORMAT_NUMBER(1000000.01, '###,###.###')\",\n                \"tsql\": \"SELECT FORMAT(1000000.01, '###,###.###')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT(1234567, 'f')\",\n            write={\n                \"spark\": \"SELECT FORMAT_NUMBER(1234567, 'f')\",\n                \"tsql\": \"SELECT FORMAT(1234567, 'f')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT('01-01-1991', 'dd.mm.yyyy')\",\n            write={\n                \"spark\": \"SELECT DATE_FORMAT('01-01-1991', 'dd.mm.yyyy')\",\n                \"tsql\": \"SELECT FORMAT('01-01-1991', 'dd.mm.yyyy')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT(date_col, 'dd.mm.yyyy')\",\n            write={\n                \"spark\": \"SELECT DATE_FORMAT(date_col, 'dd.mm.yyyy')\",\n                \"tsql\": \"SELECT FORMAT(date_col, 'dd.mm.yyyy')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT(date_col, 'm')\",\n            write={\n                \"spark\": \"SELECT DATE_FORMAT(date_col, 'MMMM d')\",\n                \"tsql\": \"SELECT FORMAT(date_col, 'MMMM d')\",\n            },\n        )\n        self.validate_all(\n            \"SELECT FORMAT(num_col, 'c')\",\n            write={\n                \"spark\": \"SELECT FORMAT_NUMBER(num_col, 'c')\",\n                \"tsql\": \"SELECT FORMAT(num_col, 'c')\",\n            },\n        )\n\n    def test_string(self):\n        self.validate_all(\n            \"SELECT N'test'\",\n            write={\"spark\": \"SELECT 'test'\"},\n        )\n        self.validate_all(\n            \"SELECT n'test'\",\n            write={\"spark\": \"SELECT 'test'\"},\n        )\n        self.validate_all(\n            \"SELECT '''test'''\",\n            write={\"spark\": r\"SELECT '\\'test\\''\"},\n        )\n\n    def test_eomonth(self):\n        self.validate_all(\n            \"EOMONTH(GETDATE())\",\n            read={\n                \"spark\": \"LAST_DAY(CURRENT_TIMESTAMP())\",\n            },\n            write={\n                \"bigquery\": \"LAST_DAY(CAST(CURRENT_TIMESTAMP() AS DATE))\",\n                \"clickhouse\": \"LAST_DAY(CAST(CURRENT_TIMESTAMP() AS Nullable(DATE)))\",\n                \"duckdb\": \"LAST_DAY(CAST(CURRENT_TIMESTAMP AS DATE))\",\n                \"mysql\": \"LAST_DAY(DATE(CURRENT_TIMESTAMP()))\",\n                \"postgres\": \"CAST(DATE_TRUNC('MONTH', CAST(CURRENT_TIMESTAMP AS DATE)) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)\",\n                \"presto\": \"LAST_DAY_OF_MONTH(CAST(CAST(CURRENT_TIMESTAMP AS TIMESTAMP) AS DATE))\",\n                \"redshift\": \"LAST_DAY(CAST(GETDATE() AS DATE))\",\n                \"snowflake\": \"LAST_DAY(TO_DATE(CURRENT_TIMESTAMP()))\",\n                \"spark\": \"LAST_DAY(TO_DATE(CURRENT_TIMESTAMP()))\",\n                \"tsql\": \"EOMONTH(CAST(GETDATE() AS DATE))\",\n            },\n        )\n        self.validate_all(\n            \"EOMONTH(GETDATE(), -1)\",\n            write={\n                \"bigquery\": \"LAST_DAY(DATE_ADD(CAST(CURRENT_TIMESTAMP() AS DATE), INTERVAL -1 MONTH))\",\n                \"clickhouse\": \"LAST_DAY(DATE_ADD(MONTH, -1, CAST(CURRENT_TIMESTAMP() AS Nullable(DATE))))\",\n                \"duckdb\": \"LAST_DAY(CAST(CURRENT_TIMESTAMP AS DATE) + INTERVAL (-1) MONTH)\",\n                \"mysql\": \"LAST_DAY(DATE_ADD(CURRENT_TIMESTAMP(), INTERVAL -1 MONTH))\",\n                \"postgres\": \"CAST(DATE_TRUNC('MONTH', CAST(CURRENT_TIMESTAMP AS DATE) + INTERVAL '-1 MONTH') + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)\",\n                \"presto\": \"LAST_DAY_OF_MONTH(DATE_ADD('MONTH', -1, CAST(CAST(CURRENT_TIMESTAMP AS TIMESTAMP) AS DATE)))\",\n                \"redshift\": \"LAST_DAY(DATEADD(MONTH, -1, CAST(GETDATE() AS DATE)))\",\n                \"snowflake\": \"LAST_DAY(DATEADD(MONTH, -1, TO_DATE(CURRENT_TIMESTAMP())))\",\n                \"spark\": \"LAST_DAY(ADD_MONTHS(TO_DATE(CURRENT_TIMESTAMP()), -1))\",\n                \"tsql\": \"EOMONTH(DATEADD(MONTH, -1, CAST(GETDATE() AS DATE)))\",\n            },\n        )\n\n    def test_identifier_prefixes(self):\n        self.assertTrue(\n            self.validate_identity(\"#x\")\n            .assert_is(exp.Column)\n            .this.assert_is(exp.Identifier)\n            .args.get(\"temporary\")\n        )\n        self.assertTrue(\n            self.validate_identity(\"##x\")\n            .assert_is(exp.Column)\n            .this.assert_is(exp.Identifier)\n            .args.get(\"global_\")\n        )\n\n        self.validate_identity(\"@x\").assert_is(exp.Parameter).this.assert_is(exp.Var)\n        self.validate_identity(\"SELECT * FROM @x\").args[\"from_\"].this.assert_is(\n            exp.Table\n        ).this.assert_is(exp.Parameter).this.assert_is(exp.Var)\n\n        self.validate_all(\n            \"SELECT @x\",\n            write={\n                \"databricks\": \"SELECT ${x}\",\n                \"hive\": \"SELECT ${x}\",\n                \"spark\": \"SELECT ${x}\",\n                \"tsql\": \"SELECT @x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM #mytemptable\",\n            write={\n                \"duckdb\": \"SELECT * FROM mytemptable\",\n                \"spark\": \"SELECT * FROM mytemptable\",\n                \"tsql\": \"SELECT * FROM #mytemptable\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM ##mytemptable\",\n            write={\n                \"duckdb\": \"SELECT * FROM mytemptable\",\n                \"spark\": \"SELECT * FROM mytemptable\",\n                \"tsql\": \"SELECT * FROM ##mytemptable\",\n            },\n        )\n\n    def test_temporal_table(self):\n        self.validate_identity(\n            \"\"\"CREATE TABLE test (\"data\" CHAR(7), \"valid_from\" DATETIME2(2) GENERATED ALWAYS AS ROW START NOT NULL, \"valid_to\" DATETIME2(2) GENERATED ALWAYS AS ROW END NOT NULL, PERIOD FOR SYSTEM_TIME (\"valid_from\", \"valid_to\")) WITH(SYSTEM_VERSIONING=ON)\"\"\",\n            \"CREATE TABLE test ([data] CHAR(7), [valid_from] DATETIME2(2) GENERATED ALWAYS AS ROW START NOT NULL, [valid_to] DATETIME2(2) GENERATED ALWAYS AS ROW END NOT NULL, PERIOD FOR SYSTEM_TIME ([valid_from], [valid_to])) WITH(SYSTEM_VERSIONING=ON)\",\n        )\n        self.validate_identity(\n            \"\"\"CREATE TABLE test ([data] CHAR(7), [valid_from] DATETIME2(2) GENERATED ALWAYS AS ROW START HIDDEN NOT NULL, [valid_to] DATETIME2(2) GENERATED ALWAYS AS ROW END HIDDEN NOT NULL, PERIOD FOR SYSTEM_TIME ([valid_from], [valid_to])) WITH(SYSTEM_VERSIONING=ON(HISTORY_TABLE=[dbo].[benchmark_history], DATA_CONSISTENCY_CHECK=ON))\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE TABLE test ([data] CHAR(7), [valid_from] DATETIME2(2) GENERATED ALWAYS AS ROW START NOT NULL, [valid_to] DATETIME2(2) GENERATED ALWAYS AS ROW END NOT NULL, PERIOD FOR SYSTEM_TIME ([valid_from], [valid_to])) WITH(SYSTEM_VERSIONING=ON(HISTORY_TABLE=[dbo].[benchmark_history], DATA_CONSISTENCY_CHECK=ON))\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE TABLE test ([data] CHAR(7), [valid_from] DATETIME2(2) GENERATED ALWAYS AS ROW START NOT NULL, [valid_to] DATETIME2(2) GENERATED ALWAYS AS ROW END NOT NULL, PERIOD FOR SYSTEM_TIME ([valid_from], [valid_to])) WITH(SYSTEM_VERSIONING=ON(HISTORY_TABLE=[dbo].[benchmark_history], DATA_CONSISTENCY_CHECK=OFF))\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE TABLE test ([data] CHAR(7), [valid_from] DATETIME2(2) GENERATED ALWAYS AS ROW START NOT NULL, [valid_to] DATETIME2(2) GENERATED ALWAYS AS ROW END NOT NULL, PERIOD FOR SYSTEM_TIME ([valid_from], [valid_to])) WITH(SYSTEM_VERSIONING=ON(HISTORY_TABLE=[dbo].[benchmark_history]))\"\"\"\n        )\n        self.validate_identity(\n            \"\"\"CREATE TABLE test ([data] CHAR(7), [valid_from] DATETIME2(2) GENERATED ALWAYS AS ROW START NOT NULL, [valid_to] DATETIME2(2) GENERATED ALWAYS AS ROW END NOT NULL, PERIOD FOR SYSTEM_TIME ([valid_from], [valid_to])) WITH(SYSTEM_VERSIONING=ON(HISTORY_TABLE=[dbo].[benchmark_history]))\"\"\"\n        )\n\n    def test_system_time(self):\n        self.validate_identity(\"SELECT [x] FROM [a].[b] FOR SYSTEM_TIME AS OF 'foo'\")\n        self.validate_identity(\"SELECT [x] FROM [a].[b] FOR SYSTEM_TIME AS OF 'foo' AS alias\")\n        self.validate_identity(\"SELECT [x] FROM [a].[b] FOR SYSTEM_TIME FROM c TO d\")\n        self.validate_identity(\"SELECT [x] FROM [a].[b] FOR SYSTEM_TIME BETWEEN c AND d\")\n        self.validate_identity(\"SELECT [x] FROM [a].[b] FOR SYSTEM_TIME CONTAINED IN (c, d)\")\n        self.validate_identity(\"SELECT [x] FROM [a].[b] FOR SYSTEM_TIME ALL AS alias\")\n\n    def test_current_user(self):\n        self.validate_all(\n            \"SUSER_NAME()\",\n            write={\"spark\": \"CURRENT_USER()\"},\n        )\n        self.validate_all(\n            \"SUSER_SNAME()\",\n            write={\"spark\": \"CURRENT_USER()\"},\n        )\n        self.validate_all(\n            \"SYSTEM_USER()\",\n            write={\"spark\": \"CURRENT_USER()\"},\n        )\n        self.validate_all(\n            \"SYSTEM_USER\",\n            write={\"spark\": \"CURRENT_USER()\"},\n        )\n\n    def test_hints(self):\n        self.validate_all(\n            \"SELECT x FROM a INNER HASH JOIN b ON b.id = a.id\",\n            write={\"spark\": \"SELECT x FROM a INNER JOIN b ON b.id = a.id\"},\n        )\n        self.validate_all(\n            \"SELECT x FROM a INNER LOOP JOIN b ON b.id = a.id\",\n            write={\"spark\": \"SELECT x FROM a INNER JOIN b ON b.id = a.id\"},\n        )\n        self.validate_all(\n            \"SELECT x FROM a INNER REMOTE JOIN b ON b.id = a.id\",\n            write={\"spark\": \"SELECT x FROM a INNER JOIN b ON b.id = a.id\"},\n        )\n        self.validate_all(\n            \"SELECT x FROM a INNER MERGE JOIN b ON b.id = a.id\",\n            write={\"spark\": \"SELECT x FROM a INNER JOIN b ON b.id = a.id\"},\n        )\n        self.validate_all(\n            \"SELECT x FROM a WITH (NOLOCK)\",\n            write={\n                \"spark\": \"SELECT x FROM a\",\n                \"tsql\": \"SELECT x FROM a WITH (NOLOCK)\",\n                \"\": \"SELECT x FROM a WITH (NOLOCK)\",\n            },\n        )\n        self.validate_identity(\"SELECT x FROM a INNER LOOP JOIN b ON b.id = a.id\")\n\n    def test_openjson(self):\n        self.validate_identity(\"SELECT * FROM OPENJSON(@json)\")\n\n        self.validate_all(\n            \"\"\"SELECT [key], value FROM OPENJSON(@json,'$.path.to.\"sub-object\"')\"\"\",\n            write={\n                \"tsql\": \"\"\"SELECT [key], value FROM OPENJSON(@json, '$.path.to.\"sub-object\"')\"\"\",\n            },\n        )\n        self.validate_all(\n            \"SELECT * FROM OPENJSON(@array) WITH (month VARCHAR(3), temp int, month_id tinyint '$.sql:identity()') as months\",\n            write={\n                \"tsql\": \"SELECT * FROM OPENJSON(@array) WITH (month VARCHAR(3), temp INTEGER, month_id TINYINT '$.sql:identity()') AS months\",\n            },\n        )\n        self.validate_all(\n            \"\"\"\n            SELECT *\n            FROM OPENJSON ( @json )\n            WITH (\n                          Number   VARCHAR(200)   '$.Order.Number',\n                          Date     DATETIME       '$.Order.Date',\n                          Customer VARCHAR(200)   '$.AccountNumber',\n                          Quantity INT            '$.Item.Quantity',\n                          [Order]  NVARCHAR(MAX)  AS JSON\n             )\n            \"\"\",\n            write={\n                \"tsql\": \"\"\"SELECT\n  *\nFROM OPENJSON(@json) WITH (\n    Number VARCHAR(200) '$.Order.Number',\n    Date DATETIME '$.Order.Date',\n    Customer VARCHAR(200) '$.AccountNumber',\n    Quantity INTEGER '$.Item.Quantity',\n    [Order] NVARCHAR(MAX) AS JSON\n)\"\"\"\n            },\n            pretty=True,\n        )\n\n    def test_set(self):\n        self.validate_all(\n            \"SET KEY VALUE\",\n            write={\n                \"tsql\": \"SET KEY VALUE\",\n                \"duckdb\": \"SET KEY = VALUE\",\n                \"spark\": \"SET KEY = VALUE\",\n            },\n        )\n        self.validate_all(\n            \"SET @count = (SELECT COUNT(1) FROM x)\",\n            write={\n                \"databricks\": \"SET count = (SELECT COUNT(1) FROM x)\",\n                \"tsql\": \"SET @count = (SELECT COUNT(1) FROM x)\",\n                \"spark\": \"SET count = (SELECT COUNT(1) FROM x)\",\n            },\n        )\n\n    def test_qualify_derived_table_outputs(self):\n        self.validate_identity(\n            \"WITH t AS (SELECT 1) SELECT * FROM t\",\n            \"WITH t AS (SELECT 1 AS [1]) SELECT * FROM t\",\n        )\n        self.validate_identity(\n            'WITH t AS (SELECT \"c\") SELECT * FROM t',\n            \"WITH t AS (SELECT [c] AS [c]) SELECT * FROM t\",\n        )\n        self.validate_identity(\n            \"SELECT * FROM (SELECT 1) AS subq\",\n            \"SELECT * FROM (SELECT 1 AS [1]) AS subq\",\n        )\n        self.validate_identity(\n            'SELECT * FROM (SELECT \"c\") AS subq',\n            \"SELECT * FROM (SELECT [c] AS [c]) AS subq\",\n        )\n\n        self.validate_all(\n            \"WITH t1(c) AS (SELECT 1), t2 AS (SELECT CAST(c AS INTEGER) AS c FROM t1) SELECT * FROM t2\",\n            read={\n                \"duckdb\": \"WITH t1(c) AS (SELECT 1), t2 AS (SELECT CAST(c AS INTEGER) FROM t1) SELECT * FROM t2\",\n            },\n        )\n\n    def test_declare(self):\n        # supported cases\n        self.validate_identity(\"DECLARE @X INT\", \"DECLARE @X INTEGER\")\n        self.validate_identity(\"DECLARE @X INT = 1\", \"DECLARE @X INTEGER = 1\")\n        self.validate_identity(\n            \"DECLARE @X INT, @Y VARCHAR(10)\", \"DECLARE @X INTEGER, @Y VARCHAR(10)\"\n        )\n        self.validate_identity(\n            \"declare @X int = (select col from table where id = 1)\",\n            \"DECLARE @X INTEGER = (SELECT col FROM table WHERE id = 1)\",\n        )\n        self.validate_identity(\n            \"declare @X TABLE (Id INT NOT NULL, Name VARCHAR(100) NOT NULL)\",\n            \"DECLARE @X TABLE (Id INTEGER NOT NULL, Name VARCHAR(100) NOT NULL)\",\n        )\n        self.validate_identity(\n            \"declare @X TABLE (Id INT NOT NULL, constraint PK_Id primary key (Id))\",\n            \"DECLARE @X TABLE (Id INTEGER NOT NULL, CONSTRAINT PK_Id PRIMARY KEY (Id))\",\n        )\n        self.validate_identity(\n            \"declare @X UserDefinedTableType\",\n            \"DECLARE @X UserDefinedTableType\",\n        )\n        self.validate_identity(\n            \"DECLARE @MyTableVar TABLE (EmpID INT NOT NULL, PRIMARY KEY CLUSTERED (EmpID), UNIQUE NONCLUSTERED (EmpID), INDEX CustomNonClusteredIndex NONCLUSTERED (EmpID))\",\n            check_command_warning=True,\n        )\n        self.validate_identity(\n            \"DECLARE vendor_cursor CURSOR FOR SELECT VendorID, Name FROM Purchasing.Vendor WHERE PreferredVendorStatus = 1 ORDER BY VendorID\",\n            check_command_warning=True,\n        )\n\n    def test_scope_resolution_op(self):\n        # we still want to support :: casting shorthand for tsql\n        self.validate_identity(\"x::int\", \"CAST(x AS INTEGER)\")\n        self.validate_identity(\"x::varchar\", \"CAST(x AS VARCHAR)\")\n        self.validate_identity(\"x::varchar(MAX)\", \"CAST(x AS VARCHAR(MAX))\")\n\n        for lhs, rhs in (\n            (\"\", \"FOO(a, b)\"),\n            (\"bar\", \"baZ(1, 2)\"),\n            (\"LOGIN\", \"EricKurjan\"),\n            (\"GEOGRAPHY\", \"Point(latitude, longitude, 4326)\"),\n            (\n                \"GEOGRAPHY\",\n                \"STGeomFromText('POLYGON((-122.358 47.653 , -122.348 47.649, -122.348 47.658, -122.358 47.658, -122.358 47.653))', 4326)\",\n            ),\n        ):\n            with self.subTest(f\"Scope resolution, LHS: {lhs}, RHS: {rhs}\"):\n                expr = self.validate_identity(f\"{lhs}::{rhs}\")\n                base_sql = expr.sql()\n                self.assertEqual(base_sql, f\"SCOPE_RESOLUTION({lhs + ', ' if lhs else ''}{rhs})\")\n                self.assertEqual(parse_one(base_sql).sql(\"tsql\"), f\"{lhs}::{rhs}\")\n\n    def test_count(self):\n        count = annotate_types(self.validate_identity(\"SELECT COUNT(1) FROM x\"))\n        self.assertEqual(count.expressions[0].type.this, exp.DataType.Type.INT)\n\n        count_big = annotate_types(self.validate_identity(\"SELECT COUNT_BIG(1) FROM x\"))\n        self.assertEqual(count_big.expressions[0].type.this, exp.DataType.Type.BIGINT)\n\n        self.validate_all(\n            \"SELECT COUNT_BIG(1) FROM x\",\n            read={\n                \"duckdb\": \"SELECT COUNT(1) FROM x\",\n                \"spark\": \"SELECT COUNT(1) FROM x\",\n            },\n            write={\n                \"duckdb\": \"SELECT COUNT(1) FROM x\",\n                \"spark\": \"SELECT COUNT(1) FROM x\",\n                \"tsql\": \"SELECT COUNT_BIG(1) FROM x\",\n            },\n        )\n        self.validate_all(\n            \"SELECT COUNT(1) FROM x\",\n            write={\n                \"duckdb\": \"SELECT COUNT(1) FROM x\",\n                \"spark\": \"SELECT COUNT(1) FROM x\",\n                \"tsql\": \"SELECT COUNT(1) FROM x\",\n            },\n        )\n\n    def test_grant(self):\n        self.validate_identity(\"GRANT EXECUTE ON TestProc TO User2\")\n        self.validate_identity(\"GRANT EXECUTE ON TestProc TO TesterRole WITH GRANT OPTION\")\n        self.validate_identity(\n            \"GRANT EXECUTE ON TestProc TO User2 AS TesterRole\", check_command_warning=True\n        )\n\n    def test_revoke(self):\n        self.validate_identity(\"REVOKE EXECUTE ON TestProc FROM User2\")\n        self.validate_identity(\"REVOKE EXECUTE ON TestProc FROM TesterRole\")\n\n    def test_parsename(self):\n        for i in range(4):\n            with self.subTest(\"Testing PARSENAME <-> SPLIT_PART\"):\n                self.validate_all(\n                    f\"SELECT PARSENAME('1.2.3', {i})\",\n                    read={\n                        \"spark\": f\"SELECT SPLIT_PART('1.2.3', '.', {4 - i})\",\n                        \"databricks\": f\"SELECT SPLIT_PART('1.2.3', '.', {4 - i})\",\n                    },\n                    write={\n                        \"spark\": f\"SELECT SPLIT_PART('1.2.3', '.', {4 - i})\",\n                        \"databricks\": f\"SELECT SPLIT_PART('1.2.3', '.', {4 - i})\",\n                        \"tsql\": f\"SELECT PARSENAME('1.2.3', {i})\",\n                    },\n                )\n\n        # Test non-dot delimiter\n        self.validate_all(\n            \"SELECT SPLIT_PART('1,2,3', ',', 1)\",\n            write={\n                \"spark\": \"SELECT SPLIT_PART('1,2,3', ',', 1)\",\n                \"databricks\": \"SELECT SPLIT_PART('1,2,3', ',', 1)\",\n                \"tsql\": UnsupportedError,\n            },\n        )\n\n        # Test column-type parameters\n        self.validate_all(\n            \"WITH t AS (SELECT 'a.b.c' AS value, 1 AS idx) SELECT SPLIT_PART(value, '.', idx) FROM t\",\n            write={\n                \"spark\": \"WITH t AS (SELECT 'a.b.c' AS value, 1 AS idx) SELECT SPLIT_PART(value, '.', idx) FROM t\",\n                \"databricks\": \"WITH t AS (SELECT 'a.b.c' AS value, 1 AS idx) SELECT SPLIT_PART(value, '.', idx) FROM t\",\n                \"tsql\": UnsupportedError,\n            },\n        )\n\n    def test_next_value_for(self):\n        self.validate_identity(\n            \"SELECT NEXT VALUE FOR db.schema.sequence_name OVER (ORDER BY foo), col\"\n        )\n        self.validate_all(\n            \"SELECT NEXT VALUE FOR db.schema.sequence_name\",\n            read={\n                \"oracle\": \"SELECT NEXT VALUE FOR db.schema.sequence_name\",\n                \"tsql\": \"SELECT NEXT VALUE FOR db.schema.sequence_name\",\n            },\n            write={\n                \"oracle\": \"SELECT NEXT VALUE FOR db.schema.sequence_name\",\n            },\n        )\n\n    # string literals in the DATETRUNC are casted as DATETIME2\n    def test_datetrunc(self):\n        self.validate_all(\n            \"SELECT DATETRUNC(month, 'foo')\",\n            write={\n                \"duckdb\": \"SELECT DATE_TRUNC('MONTH', CAST('foo' AS TIMESTAMP))\",\n                \"tsql\": \"SELECT DATETRUNC(MONTH, CAST('foo' AS DATETIME2))\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATETRUNC(month, foo)\",\n            write={\n                \"duckdb\": \"SELECT DATE_TRUNC('MONTH', foo)\",\n                \"tsql\": \"SELECT DATETRUNC(MONTH, foo)\",\n            },\n        )\n        self.validate_all(\n            \"SELECT DATETRUNC(year, CAST('foo1' AS date))\",\n            write={\n                \"duckdb\": \"SELECT DATE_TRUNC('YEAR', CAST('foo1' AS DATE))\",\n                \"tsql\": \"SELECT DATETRUNC(YEAR, CAST('foo1' AS DATE))\",\n            },\n        )\n\n    def test_numeric_trunc(self):\n        # T-SQL doesn't have native TRUNC - uses ROUND with third parameter = 1\n        # Cross-dialect transpilation: other dialects' TRUNC -> T-SQL ROUND(x, n, 1)\n        self.validate_all(\n            \"ROUND(3.14159, 2, 1)\",\n            read={\n                \"oracle\": \"TRUNC(3.14159, 2)\",\n                \"postgres\": \"TRUNC(3.14159, 2)\",\n                \"mysql\": \"TRUNCATE(3.14159, 2)\",\n            },\n            write={\n                \"tsql\": \"ROUND(3.14159, 2, 1)\",\n            },\n        )\n\n    def test_collation_parse(self):\n        self.validate_identity(\"ALTER TABLE a ALTER COLUMN b CHAR(10) COLLATE abc\").assert_is(\n            exp.Alter\n        ).args.get(\"actions\")[0].args.get(\"collate\").this.assert_is(exp.Var)\n\n    def test_odbc_date_literals(self):\n        for value, cls in [\n            (\"{d'2024-01-01'}\", exp.Date),\n            (\"{t'12:00:00'}\", exp.Time),\n            (\"{ts'2024-01-01 12:00:00'}\", exp.Timestamp),\n        ]:\n            with self.subTest(f\"Testing ODBC date literal: {value}\"):\n                sql = f\"INSERT INTO tab(ds) VALUES ({value})\"\n                expr = self.parse_one(sql)\n                self.assertIsInstance(expr, exp.Insert)\n                self.assertIsInstance(expr.expression.expressions[0].expressions[0], cls)\n\n    def test_create_trigger(self):\n        self.validate_identity(\n            \"CREATE TRIGGER reminder ON customers AFTER INSERT AS BEGIN INSERT INTO audit_log (customer_id, action, created_at) SELECT id, 'INSERT', GETDATE() FROM inserted END\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"CREATE TRIGGER updview ON vw_employees INSTEAD OF UPDATE AS BEGIN UPDATE employees SET salary = inserted.salary FROM inserted WHERE employees.id = inserted.id END\",\n            check_command_warning=True,\n        )\n\n        self.validate_identity(\n            \"CREATE TRIGGER ddl_trig ON DATABASE FOR CREATE_TABLE AS BEGIN INSERT INTO schema_changes (event_type, event_time, login_name) VALUES ('CREATE_TABLE', GETDATE(), SYSTEM_USER) END\",\n            check_command_warning=True,\n        )\n\n    def test_procedures(self):\n        self.validate_identity(\"SELECT 1; SELECT 2\").assert_is(exp.Block)\n\n        sqls = [\n            \"EXECUTE test @in1 = 100, @in2\",\n            \"EXECUTE sp_executesql @payload, @param_str, @param1 = value1, @param2 = value2\",\n            \"EXECUTE sp_executesql @stmt = @payload, @params = param_str, @param1 = value1, @param2 = value2\",\n            \"\"\"\n            CREATE\n            PROCEDURE test1\n            AS\n            BEGIN\n                SELECT 1;\n                SELECT 2;\n                SELECT 3;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE test2(@in1 INTEGER, @c CHAR(1))\n            AS\n            BEGIN\n                IF @in1 > 1 AND @c = 'c'\n                BEGIN\n                    SELECT col1 FROM t WHERE t.col2 = @in1;\n                END;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE test(@in1 INTEGER)\n            AS\n            BEGIN\n                SELECT 1;\n                IF @in1 > 1\n                BEGIN\n                    SELECT 1;\n                    SELECT 2;\n                END;\n                ELSE\n                BEGIN\n                    SELECT 3;\n                    SELECT 4;\n                END;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE test(@in1 INTEGER)\n            AS\n            BEGIN\n                IF @in1 > 1\n                BEGIN\n                    SELECT col1 FROM t WHERE t.col2 = @in1;\n                    SELECT 100;\n                END;\n                IF @in1 > 1\n                BEGIN\n                    SELECT col2 FROM t1;\n                END;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE test(@in1 INTEGER)\n            AS\n            BEGIN\n                DECLARE @q1 INTEGER, @q2 INTEGER, @q3 INTEGER;\n                SET @q1 = (SELECT MAX(col1) FROM t1);\n                SET @q2 = (SELECT MIN(col1) FROM t2);\n                IF @in1 > 1\n                BEGIN\n                    SELECT 3;\n                    SET @q3 = (SELECT MAX(col2) FROM t1);\n                    IF @q3 < 5\n                    BEGIN\n                        SELECT 1;\n                        SELECT 2;\n                    END;\n                END;\n                IF @in1 > 1\n                BEGIN\n                    SELECT 1;\n                END;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE test(@in1 INTEGER)\n            AS\n            BEGIN\n                SELECT 1;\n                IF @in1 > 1\n                BEGIN\n                    SELECT 3;\n                END;\n                ELSE\n                BEGIN\n                    SELECT 4;\n                    SELECT 5;\n                    IF @in1 < 0\n                    BEGIN\n                        SELECT 1;\n                    END;\n                END;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE test(@in1 INTEGER, @c CHAR(1))\n            AS\n            BEGIN\n                WHILE @in1 > 100\n                BEGIN\n                    SELECT col1 FROM t WHERE t.col2 = @in1 AND t.col3 = @c;\n                    SET @in1 = @in1 - 1;\n                END;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE test(@in1 INTEGER)\n            AS\n            BEGIN\n                DECLARE @temp INTEGER;\n                WHILE @in1 > 100\n                BEGIN\n                    SET @temp = (SELECT MAX(col1) FROM t WHERE t.col2 = @in1);\n                    SET @in1 = @in1 - @temp;\n                END;\n                SET @in1 = 50;\n                WHILE @in1 > 5\n                BEGIN\n                    SELECT col2 FROM t1 WHERE t1.col3 = @in1;\n                    SET @in1 = @in1 - 1;\n                END;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE dbo.test(@in1 INTEGER = 5, @in2 VARCHAR(40) = 'empty', @in3 INTEGER = 1)\n            AS\n            BEGIN\n                INSERT INTO t (id, col1, col2) VALUES (@in1, @in2, @in3);\n            END;\n            CREATE PROCEDURE c.s.test2\n            AS\n            BEGIN\n                EXECUTE dbo.test;\n                DECLARE @i INTEGER = 0;\n                WHILE @i < 100\n                BEGIN\n                    EXECUTE test @in2 = 'temp_new';\n                    SET @i = @i + 100;\n                END;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE DropTableIfExists\n                @TableName NVARCHAR(128)\n            AS\n            BEGIN\n                DECLARE @SQL NVARCHAR(MAX);\n                SET @SQL = N'DROP TABLE IF EXISTS [' + @TableName + ']';\n                EXECUTE sp_executesql 'SELECT 1 AS c';\n                EXECUTE sp_executesql N'SELECT 1 AS c';\n                EXECUTE sp_executesql @SQL;\n                EXECUTE sp_executesql @stmt = @SQL;\n            END\n            \"\"\",\n            \"\"\"\n            CREATE PROCEDURE test\n            AS\n            BEGIN\n                DECLARE @x INTEGER = 100;\n                IF @x > ANY (SELECT 100)\n                BEGIN\n                    SET @x = 100;\n                END;\n                ELSE\n                BEGIN\n                    SET @x = 0;\n                END;\n            END\n            \"\"\",\n        ]\n        for sql in sqls:\n            ast = parse_one(sql, read=\"tsql\")\n            expected_sql = \" \".join(line for line in (l.strip() for l in sql.splitlines()) if line)\n            roundtripped_sql = ast.sql(\"tsql\")\n            with self.subTest(f\"Testing: {sql}\"):\n                self.assertEqual(expected_sql, roundtripped_sql)\n\n        self.validate_identity(\n            \"EXEC sp_executesql @payload\", \"EXECUTE sp_executesql @payload\"\n        ).assert_is(exp.ExecuteSql)\n\n        sql = \"\"\"\n            CREATE procedure [TRANSF].[SP_Merge_Sales_Real]\n                @Loadid INTEGER\n               ,@NumberOfRows INTEGER\n            WITH EXECUTE AS OWNER, SCHEMABINDING, NATIVE_COMPILATION\n            AS\n            BEGIN\n                SET XACT_ABORT ON;\n\n                DECLARE @DWH_DateCreated AS DATETIME = CONVERT(DATETIME, getdate(), 104);\n                DECLARE @DWH_DateModified DATETIME2 = CONVERT(DATETIME2, GETDATE(), 104);\n                DECLARE @DWH_IdUserCreated INTEGER = SUSER_ID (CURRENT_USER());\n                DECLARE @DWH_IdUserModified INTEGER = SUSER_ID (SYSTEM_USER);\n\n                DECLARE @SalesAmountBefore float;\n                SELECT @SalesAmountBefore=SUM(SalesAmount) FROM TRANSF.[Pre_Merge_Sales_Real] S;\n            END\n        \"\"\"\n\n        expected_sqls = [\n            \"CREATE PROCEDURE [TRANSF].[SP_Merge_Sales_Real] @Loadid INTEGER, @NumberOfRows INTEGER WITH EXECUTE AS OWNER, SCHEMABINDING, NATIVE_COMPILATION AS BEGIN SET XACT_ABORT ON\",\n            \"DECLARE @DWH_DateCreated DATETIME = CONVERT(DATETIME, GETDATE(), 104)\",\n            \"DECLARE @DWH_DateModified DATETIME2 = CONVERT(DATETIME2, GETDATE(), 104)\",\n            \"DECLARE @DWH_IdUserCreated INTEGER = SUSER_ID(CURRENT_USER())\",\n            \"DECLARE @DWH_IdUserModified INTEGER = SUSER_ID(CURRENT_USER())\",\n            \"DECLARE @SalesAmountBefore FLOAT\",\n            \"SELECT @SalesAmountBefore = SUM(SalesAmount) FROM TRANSF.[Pre_Merge_Sales_Real] AS S\",\n            \"END\",\n        ]\n\n        for expr, expected_sql in zip(parse_one(sql, read=\"tsql\").expressions, expected_sqls):\n            self.assertEqual(expr.sql(dialect=\"tsql\"), expected_sql)\n\n        sql = \"\"\"\n            CREATE PROC [dbo].[transform_proc] AS\n\n            DECLARE @CurrentDate VARCHAR(20);\n            SET @CurrentDate = CONVERT(VARCHAR(20), GETDATE(), 120);\n\n            CREATE TABLE [target_schema].[target_table]\n            (a INTEGER)\n            WITH (DISTRIBUTION = REPLICATE, HEAP);\n        \"\"\"\n\n        expected_sqls = [\n            \"CREATE PROC [dbo].[transform_proc] AS DECLARE @CurrentDate VARCHAR(20)\",\n            \"SET @CurrentDate = CONVERT(VARCHAR(20), GETDATE(), 120)\",\n            \"CREATE TABLE [target_schema].[target_table] (a INTEGER) WITH (DISTRIBUTION=REPLICATE, HEAP)\",\n        ]\n\n        for expr, expected_sql in zip(parse_one(sql, read=\"tsql\").expressions, expected_sqls):\n            self.assertEqual(expr.sql(dialect=\"tsql\"), expected_sql)\n\n        self.validate_identity(\n            \"IF ((@x = @y AND GETDATE() = GETDATE()) OR (GETDATE() = @t)) BEGIN SET @query_result = (SELECT MAX(id) + 1 FROM t); END\",\n            \"IF (@x = @y AND GETDATE() = GETDATE()) OR (GETDATE() = @t) BEGIN SET @query_result = (SELECT MAX(id) + 1 FROM t); END\",\n        )\n"
  },
  {
    "path": "tests/fixtures/identity.sql",
    "content": "SUM(1)\nSUM(CASE WHEN x > 1 THEN 1 ELSE 0 END) / y\n1\n(1)\n1.\n(1.)\n1.0\n(1.0)\n1E2\n1E+2\n1E-2\n1.1E10\n1.12e-10\n-11.023E7 * 3\n0.2\n(1 * 2) / (3 - 5)\n((TRUE))\n''\n''''\n'x'\n'\\x'\n\"x\"\n'\\z'\n'\\\\z'\n'\\\\\\z'\n'\\\\\\\\z'\n'\\\\\\\\\\z'\n'\\\\\\\\\\\\z'\n'\\n'\n'\\\\n'\n'\\\\\\n'\n'\\\\\\\\n'\n'\\\\\\\\\\n'\n'\\\\\\\\\\\\n'\n\"\"\n\"\"\"x\"\"\"\nN'abc'\nx\nx % 1\nx < 1\nx <= 1\nx > 1\nx >= 1\nx <> 1\nx = y OR x > 1\nx & 1\nx | 1\nx ^ 1\n~x\nx << 1\nx >> 1\nx >> 1 | 1 & 1 ^ 1\nx || y\nx[:]\nx[1:]\nx[:2]\nx[1:2]\nx[-4:-1]\n1 - -1\n- -5\ndec.x + y\na.filter\na.b.c\na.b.c.d\na.b.c.d.e\na.b.c.d.e[0]\na.b.c.d.e[0].f\na[0][0].b.c[1].d.e.f[1][1]\na[0].b[1]\na[0].b.c['d']\na.b.C()\na['x'].b.C()\na.B()\na['x'].C()\nint.x\nmap.x\nSELECT update\nSELECT x.update\nSELECT call.x\nSELECT end\na.b.INT(1.234)\nINT(x / 100)\ntime * 100\nint * 100\ndec + 1\nx IN (-1, 1)\nx IN ('a', 'a''a')\nx IN ((1))\nx BETWEEN -1 AND 1\nx BETWEEN 'a' || b AND 'c' || d\n((a, b) AS c)\nNOT x IS NULL\nx IS TRUE\nx IS FALSE\nx IS TRUE IS TRUE\nx LIKE y IS TRUE\nTRIM('a' || 'b')\nMAP()\nGREATEST(x)\nLEAST(y)\nMAX(a, b)\nMIN(a, b)\ntime\nzone\nARRAY<TEXT>\nCURRENT_DATE\nCURRENT_DATE('UTC')\nCURRENT_DATE AT TIME ZONE 'UTC'\nCURRENT_DATE AT TIME ZONE zone_column\nCURRENT_DATE AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Tokio'\nf1 AT TIME ZONE INTERVAL '-10:00' AS f1\nARRAY()\nARRAY(1, 2)\nARRAY(time, foo)\nARRAY(foo, time)\nARRAY(LENGTH(waiter_name) > 0)\nARRAY_CONTAINS(x, 1)\nx.EXTRACT(1)\nEXTRACT(X FROM y)\nEXTRACT(DATE FROM y)\nEXTRACT(WEEK(monday) FROM created_at)\nCONCAT_WS('-', 'a', 'b')\nCONCAT_WS('-', 'a', 'b', 'c')\nPOSEXPLODE(\"x\") AS (\"a\", \"b\")\nPOSEXPLODE(\"x\") AS (\"a\", \"b\", \"c\")\nSTR_POSITION(haystack, needle)\nSTR_POSITION(haystack, needle, pos)\nLEVENSHTEIN('gumbo', 'gambol', 2, 1, 1)\nSPLIT(SPLIT(referrer, 'utm_source=')[OFFSET(1)], \"&\")[OFFSET(0)]\nx[ORDINAL(1)][SAFE_OFFSET(2)]\nx GLOB '??-*'\nx GLOB y\nILIKE(x, 'z')\nx LIKE SUBSTRING('abc', 1, 1)\nx LIKE y\nx LIKE a.y\nx LIKE '%y%'\nx ILIKE '%y%'\nx LIKE '%y%' ESCAPE '\\'\nx ILIKE '%y%' ESCAPE '\\'\n1 AS escape\nINTERVAL '1' DAY\nINTERVAL '1' MONTH\nINTERVAL '1' YEAR\nINTERVAL '1' HOUR TO SECOND\nINTERVAL '-1' CURRENT_DATE\nINTERVAL '-31' CAST(GETDATE() AS DATE)\nINTERVAL (1 + 3) DAYS\nINTERVAL '1' DAY * 5\n5 * INTERVAL '1' DAY\nCASE WHEN TRUE THEN INTERVAL '15' DAYS END\nCASE WHEN TRUE THEN 1 ELSE interval END\nCASE WHEN TRUE THEN 1 ELSE \"INTERVAL\" END\nSELECT asof FROM x\nSELECT * WHERE interval IS NULL\nSELECT * WHERE NOT interval IS NULL\nSELECT * WHERE INTERVAL \"is\" > 1\nSELECT * WHERE INTERVAL x.is > 1\nCAST('45' AS INTERVAL DAYS)\nCAST(x AS UUID)\nFILTER(a, x -> x.a.b.c.d.e.f.g)\nFILTER(a, x -> FOO(x.a.b.c.d.e.f.g) + x.a.b.c.d.e.f.g)\nTIMESTAMP_FROM_PARTS(2019, 1, 10, 2, 3, 4, 123456789, 'America/Los_Angeles')\nTIMESTAMPDIFF(CURRENT_TIMESTAMP(), 1, DAY)\nTIME_FROM_PARTS(14, 30, 45, 123456789)\nDATETIME_DIFF(CURRENT_DATE, 1, DAY)\nQUANTILE(x, 0.5)\nREGEXP_REPLACE('new york', '(\\w)(\\w*)', x -> UPPER(x[1]) || LOWER(x[2]))\nREGEXP_LIKE('new york', '.')\nREGEXP_SPLIT('new york', '.')\nSPLIT('new york', '.')\nX((y AS z)).1\nX(a.b = 1)\n(x AS y, y AS z)\nREPLACE('new york', ' ', '_')\nREPLACE('new york', ' ')\nDATE(x) = DATE(y)\nTIMESTAMP(DATE(x))\nTIMESTAMP_TRUNC(COALESCE(time_field, CURRENT_TIMESTAMP()), DAY)\nMONTHNAME(x)\nMONTHS_BETWEEN(CAST('2019-03-15' AS DATE), CAST('2019-02-15' AS DATE))\nCOUNT(DISTINCT CASE WHEN DATE_TRUNC('ISOWEEK', DATE(time_field)) = DATE_TRUNC('ISOWEEK', DATE(time_field2)) THEN report_id ELSE NULL END)\nCOUNT(a, b)\nx[y - 1]\nCASE WHEN SUM(x) > 3 THEN 1 END OVER (PARTITION BY x)\nANY(x) OVER (PARTITION BY x)\nSUM(ROW() OVER (PARTITION BY x))\nSUM(ROW() OVER (PARTITION BY x + 1))\nSUM(ROW() OVER (PARTITION BY x AND y))\nSUM(x) OVER (w ORDER BY y)\n(ROW() OVER ())\nCASE WHEN (x > 1) THEN 1 ELSE 0 END\nCASE (1) WHEN 1 THEN 1 ELSE 0 END\nCASE 1 WHEN 1 THEN 1 ELSE 0 END\nCASE 1 WHEN 1 THEN timestamp ELSE date END\nx AT TIME ZONE 'UTC'\nCAST('2025-11-20 00:00:00+00' AS TIMESTAMP) AT TIME ZONE 'Africa/Cairo'\nSET x = 1\nSET x = ';'\nSET variable = value\nSET GLOBAL variable = value\nSET LOCAL variable = value\n@x\n@\"x\"\nCOMMIT\nUSE db\nUSE ROLE x\nUSE WAREHOUSE x\nUSE DATABASE x\nUSE SCHEMA x.y\nUSE CATALOG abc\nNOT 1\nNOT NOT 1\nSELECT * FROM test\nSELECT * FROM db.FOO()\nSELECT *, 1 FROM test\nSELECT * FROM a.b\nSELECT * FROM a.b.c\nSELECT * FROM table\nSELECT 1\nSELECT 1 FROM test\nSELECT * FROM a, b, (SELECT 1) AS c\nSELECT a FROM test\nSELECT 1 AS filter\nSELECT 1 AS \"quoted alias\"\nSELECT SUM(x) AS filter\nSELECT 1 AS range FROM test\nSELECT 1 AS count FROM test\nSELECT 1 AS comment FROM test\nSELECT 1 AS numeric FROM test\nSELECT 1 AS number FROM test\nSELECT COALESCE(offset, 1)\nSELECT t.count\nSELECT DISTINCT x FROM test\nSELECT DISTINCT x, y FROM test\nSELECT DISTINCT TIMESTAMP_TRUNC(time_field, MONTH) AS time_value FROM \"table\"\nSELECT DISTINCT ON (x) x, y FROM z\nSELECT DISTINCT ON (x, y + 1) * FROM z\nSELECT DISTINCT ON (x.y) * FROM z\nSELECT DISTINCT FROM_SOMETHING\nSELECT top.x\nSELECT TIMESTAMP(DATE_TRUNC('MONTH', DATE(time_field))) AS time_value FROM \"table\"\nSELECT GREATEST((3 + 1), LEAST(3, 4))\nSELECT TRANSFORM(a, b -> b) AS x\nSELECT AGGREGATE(a, (a, b) -> a + b) AS x\nSELECT COUNT(DISTINCT a, b)\nSELECT COUNT(DISTINCT a, b + 1)\nSELECT SUM(DISTINCT x)\nSELECT TRUNC(a, b)\nSELECT ARRAY_AGG(STRUCT(x, x AS y) ORDER BY z DESC) AS x\nSELECT LAG(x) OVER (ORDER BY y) AS x\nSELECT LEAD(a) OVER (ORDER BY b) AS a\nSELECT LEAD(a, 1) OVER (PARTITION BY a ORDER BY a) AS x\nSELECT LEAD(a, 1, b) OVER (PARTITION BY a ORDER BY a) AS x\nSELECT X((a, b) -> a + b, z -> z) AS x\nSELECT X(a -> a + (\"z\" - 1))\nSELECT test.* FROM test\nSELECT a AS b FROM test\nSELECT \"a\".\"b\" FROM \"a\"\nSELECT \"a\".b FROM a\nSELECT a.b FROM \"a\"\nSELECT a.b FROM a\nSELECT '\"hi' AS x FROM x\nSELECT 1 AS \"|sum\" FROM x\nSELECT '\\\"hi' AS x FROM x\nSELECT 1 AS b FROM test\nSELECT 1 AS \"b\" FROM test\nSELECT 1 + 1 FROM test\nSELECT 1 - 1 FROM test\nSELECT 1 * 1 FROM test\nSELECT 1 % 1 FROM test\nSELECT 1 / 1 FROM test\nSELECT 1 < 2 FROM test\nSELECT 1 <= 2 FROM test\nSELECT 1 > 2 FROM test\nSELECT 1 >= 2 FROM test\nSELECT 1 <> 2 FROM test\nSELECT JSON_EXTRACT(x, '$.name')\nSELECT JSON_EXTRACT_SCALAR(x, '$.name')\nSELECT x LIKE '%x%' FROM test\nSELECT * FROM test LIMIT 100\nSELECT * FROM test LIMIT 1 + 1\nSELECT * FROM test LIMIT 100 OFFSET 200\nSELECT * FROM test LIMIT (SELECT 1)\nSELECT * FROM test LIMIT (SELECT 1) OFFSET (SELECT 1)\nSELECT * FROM test FETCH FIRST ROWS ONLY\nSELECT * FROM test FETCH FIRST 1 ROWS ONLY\nSELECT * FROM test ORDER BY id DESC FETCH FIRST 10 ROWS WITH TIES\nSELECT * FROM test ORDER BY id DESC FETCH FIRST 10 PERCENT ROWS WITH TIES\nSELECT * FROM test ORDER BY always DESC\nSELECT * FROM test FETCH NEXT 1 ROWS ONLY\nSELECT (1 > 2) AS x FROM test\nSELECT NOT (1 > 2) FROM test\nSELECT 1 + 2 AS x FROM test\nSELECT a, b, 1 < 1 FROM test\nSELECT a FROM test WHERE NOT FALSE\nSELECT a FROM test WHERE a = 1\nSELECT a FROM test WHERE a = 1 AND b = 2\nSELECT a FROM test WHERE a IN (SELECT b FROM z)\nSELECT a FROM test WHERE a IN ((SELECT 1), 2)\nSELECT * FROM x WHERE y IN ((SELECT 1) EXCEPT (SELECT 2))\nSELECT * FROM x WHERE y IN (SELECT 1 UNION SELECT 2)\nSELECT * FROM x WHERE y IN ((SELECT 1 UNION SELECT 2))\nSELECT * FROM x WHERE y IN (WITH z AS (SELECT 1) SELECT * FROM z)\nSELECT a FROM test WHERE (a > 1)\nSELECT a FROM test WHERE a > (SELECT 1 FROM x GROUP BY y)\nSELECT a FROM test WHERE EXISTS(SELECT 1)\nSELECT a FROM test WHERE EXISTS(SELECT * FROM x UNION SELECT * FROM Y) OR TRUE\nSELECT a FROM test WHERE TRUE OR NOT EXISTS(SELECT * FROM x)\nSELECT a AS any, b AS some, c AS all, d AS exists FROM test WHERE a = ANY (SELECT 1)\nSELECT a FROM test WHERE a > ALL (SELECT 1)\nSELECT a FROM test WHERE (a, b) IN (SELECT 1, 2)\nSELECT X((SELECT 1) UNION (SELECT 2))\nSELECT a FROM test ORDER BY a\nSELECT a FROM test ORDER BY a, b\nSELECT x FROM tests ORDER BY a DESC, b DESC, c\nSELECT a FROM test ORDER BY a > 1\nSELECT * FROM test ORDER BY DATE DESC, TIMESTAMP DESC\nSELECT a, b FROM test GROUP BY 1\nSELECT a, b FROM test GROUP BY a\nSELECT a, b FROM test WHERE a = 1 GROUP BY a HAVING a = 2\nSELECT a, b FROM test WHERE a = 1 GROUP BY a HAVING a = 2 ORDER BY a\nSELECT a, b FROM test WHERE a = 1 GROUP BY CASE 1 WHEN 1 THEN 1 END\nSELECT a FROM test GROUP BY GROUPING SETS (())\nSELECT a FROM test GROUP BY GROUPING SETS (x, ())\nSELECT a FROM test GROUP BY GROUPING SETS (x, (x, y), (x, y, z), q)\nSELECT a FROM test GROUP BY CUBE (x)\nSELECT a FROM test GROUP BY ROLLUP (x)\nSELECT t.a FROM test AS t GROUP BY ROLLUP (t.x)\nSELECT a FROM test GROUP BY GROUPING SETS ((x, y)), ROLLUP (b)\nSELECT a FROM test GROUP BY CUBE (x), ROLLUP (x, y, z)\nSELECT CASE WHEN a < b THEN 1 WHEN a < c THEN 2 ELSE 3 END FROM test\nSELECT CASE 1 WHEN 1 THEN 1 ELSE 2 END\nSELECT CASE 1 WHEN 1 THEN MAP('a', 'b') ELSE MAP('b', 'c') END['a']\nSELECT CASE 1 + 2 WHEN 1 THEN 1 ELSE 2 END\nSELECT CASE TEST(1) + x[0] WHEN 1 THEN 1 ELSE 2 END\nSELECT CASE x[0] WHEN 1 THEN 1 ELSE 2 END\nSELECT CASE a.b WHEN 1 THEN 1 ELSE 2 END\nSELECT CASE CASE x > 1 WHEN TRUE THEN 1 END WHEN 1 THEN 1 ELSE 2 END\nSELECT a FROM (SELECT a FROM test) AS x\nSELECT a FROM (SELECT a FROM (SELECT a FROM test) AS y) AS x\nSELECT a FROM test WHERE a IN (1, 2, 3) OR b BETWEEN 1 AND 4\nSELECT a FROM test AS x TABLESAMPLE (BUCKET 1 OUT OF 5)\nSELECT a FROM test TABLESAMPLE (BUCKET 1 OUT OF 5)\nSELECT a FROM test TABLESAMPLE (BUCKET 1 OUT OF 5 ON x)\nSELECT a FROM test TABLESAMPLE (BUCKET 1 OUT OF 5 ON RAND())\nSELECT a FROM test TABLESAMPLE (0.1 PERCENT)\nSELECT a FROM test TABLESAMPLE (100 ROWS)\nSELECT a FROM test PIVOT(SUM(x) FOR y IN ('z', 'q'))\nSELECT 1 FROM a.b.table1 AS t UNPIVOT((c3) FOR c4 IN (a, b))\nSELECT a FROM test PIVOT(SOMEAGG(x, y, z) FOR q IN (1))\nSELECT a FROM test PIVOT(SUM(x) FOR y IN ('z', 'q')) PIVOT(MAX(b) FOR c IN ('d'))\nSELECT a FROM (SELECT a, b FROM test) PIVOT(SUM(x) FOR y IN ('z', 'q'))\nSELECT a FROM test UNPIVOT(x FOR y IN (z, q)) AS x\nSELECT a FROM test PIVOT(SUM(x) FOR y IN ('z', 'q')) UNPIVOT(x FOR y IN (z, q)) AS x\nSELECT ABS(a) FROM test\nSELECT AVG(a) FROM test\nSELECT CEIL(a) FROM test\nSELECT CEIL(a, b) FROM test\nSELECT COUNT(a) FROM test\nSELECT COUNT(1) FROM test\nSELECT COUNT(*) FROM test\nSELECT COUNT() FROM test\nSELECT COUNT(DISTINCT a) FROM test\nSELECT EXP(a) FROM test\nSELECT FLOOR(a) FROM test\nSELECT FLOOR(a, b) FROM test\nSELECT FIRST_VALUE(a) FROM test\nSELECT GREATEST(a, b, c) FROM test\nSELECT LAST_VALUE(a) FROM test\nSELECT LAST_VALUE(a) IGNORE NULLS OVER () + 1\nSELECT LN(a) FROM test\nSELECT MAX(a) FROM test\nSELECT MIN(a) FROM test\nSELECT POWER(a, 2) FROM test\nSELECT QUANTILE(a, 0.95) FROM test\nSELECT ROUND(a) FROM test\nSELECT ROUND(a, 2) FROM test\nSELECT SUM(a) FROM test\nSELECT SQRT(a) FROM test\nSELECT STDDEV(a) FROM test\nSELECT STDDEV_POP(a) FROM test\nSELECT STDDEV_SAMP(a) FROM test\nSELECT VARIANCE(a) FROM test\nSELECT VARIANCE_POP(a) FROM test\nSELECT CAST(a AS INT) FROM test\nSELECT CAST(a AS DATETIME) FROM test\nSELECT CAST(a AS VARCHAR) FROM test\nSELECT CAST(a < 1 AS INT) FROM test\nSELECT CAST(a IS NULL AS INT) FROM test\nSELECT COUNT(CAST(1 < 2 AS INT)) FROM test\nSELECT COUNT(CASE WHEN CAST(1 < 2 AS BOOLEAN) THEN 1 END) FROM test\nSELECT CAST(a AS DECIMAL) FROM test\nSELECT CAST(a AS DECIMAL(1)) FROM test\nSELECT CAST(a AS DECIMAL(1, 2)) FROM test\nSELECT CAST(a AS MAP<INT, INT>) FROM test\nSELECT CAST(a AS TIMESTAMP) FROM test\nSELECT CAST(a AS DATE) FROM test\nSELECT CAST(a AS ARRAY<INT>) FROM test\nSELECT CAST(a AS VARIANT) FROM test\nSELECT TRY_CAST(a AS INT) FROM test\nSELECT COALESCE(a, b, c) FROM test\nSELECT ANY_VALUE(a) FROM test\nSELECT 1 FROM a JOIN b ON a.x = b.x\nSELECT 1 FROM a JOIN b AS c ON a.x = b.x\nSELECT 1 FROM a INNER JOIN b ON a.x = b.x\nSELECT 1 FROM a LEFT JOIN b ON a.x = b.x\nSELECT 1 FROM a RIGHT JOIN b ON a.x = b.x\nSELECT 1 FROM a CROSS JOIN b ON a.x = b.x\nSELECT 1 FROM a SEMI JOIN b ON a.x = b.x\nSELECT 1 FROM a LEFT SEMI JOIN b ON a.x = b.x\nSELECT 1 FROM a LEFT ANTI JOIN b ON a.x = b.x\nSELECT 1 FROM a RIGHT SEMI JOIN b ON a.x = b.x\nSELECT 1 FROM a RIGHT ANTI JOIN b ON a.x = b.x\nSELECT 1 FROM a JOIN b USING (x)\nSELECT 1 FROM a JOIN b USING (x, y, z)\nSELECT 1 FROM a JOIN (SELECT a FROM c) AS b ON a.x = b.x AND a.x < 2\nSELECT 1 FROM a UNION SELECT 2 FROM b\nSELECT 1 FROM a UNION ALL SELECT 2 FROM b\nSELECT 1 FROM a JOIN b ON a.foo = b.bar JOIN c ON a.foo = c.bar\nSELECT 1 FROM a LEFT JOIN b ON a.foo = b.bar JOIN c ON a.foo = c.bar\nSELECT 1 FROM a LEFT INNER JOIN b ON a.foo = b.bar\nSELECT 1 FROM a LEFT OUTER JOIN b ON a.foo = b.bar\nSELECT 1 FROM a NATURAL JOIN b\nSELECT 1 FROM a NATURAL LEFT JOIN b\nSELECT 1 FROM a NATURAL LEFT OUTER JOIN b\nSELECT 1 FROM a OUTER JOIN b ON a.foo = b.bar\nSELECT 1 FROM a FULL JOIN b ON a.foo = b.bar\nSELECT 1 FROM a JOIN b JOIN c ON b.id = c.id ON a.id = b.id\nSELECT * FROM a JOIN b JOIN c USING (id) USING (id)\nSELECT 1 UNION ALL SELECT 2\nSELECT 1 EXCEPT SELECT 2\nSELECT 1 EXCEPT SELECT 2\nSELECT 1 INTERSECT SELECT 2\nSELECT 1 INTERSECT SELECT 2\nSELECT 1 AS delete, 2 AS alter\nSELECT * FROM (x)\nSELECT * FROM ((x))\nSELECT * FROM (((x)))\nSELECT * FROM ((SELECT 1))\nSELECT * FROM (x CROSS JOIN foo LATERAL VIEW EXPLODE(y))\nSELECT * FROM (SELECT 1) AS x\nSELECT * FROM (SELECT 1 UNION SELECT 2) AS x\nSELECT * FROM (SELECT 1 UNION ALL SELECT 2) AS x\nSELECT * FROM (SELECT 1 UNION ALL SELECT 2)\nSELECT * FROM ((SELECT 1) AS a UNION ALL (SELECT 2) AS b)\nSELECT * FROM ((SELECT 1) AS a(b))\nSELECT * FROM ((SELECT 1) UNION (SELECT 2) UNION (SELECT 3))\nSELECT * FROM (table1 AS t1 LEFT JOIN table2 AS t2 ON 1 = 1)\nSELECT * FROM (tbl1 LEFT JOIN tbl2 ON 1 = 1)\nSELECT * FROM (tbl1, tbl2 JOIN tbl3 ON TRUE)\nSELECT * FROM (tbl1 CROSS JOIN tbl2)\nSELECT * FROM (tbl1 CROSS JOIN tbl2) AS t\nSELECT * FROM (tbl AS tbl) AS t\nSELECT * FROM (tbl1 JOIN (tbl2 CROSS JOIN tbl3) ON bla = foo)\nSELECT * FROM (tbl1, LATERAL (SELECT * FROM bla) AS tbl)\nSELECT * FROM x AS y(a, b)\nSELECT * EXCEPT (a, b)\nSELECT * EXCEPT (a, b) FROM y\nSELECT * REPLACE (a AS b, b AS C)\nSELECT * REPLACE (a + 1 AS b, b AS C)\nSELECT * EXCEPT (a, b) REPLACE (a AS b, b AS C)\nSELECT * EXCEPT (a, b) REPLACE (a AS b, b AS C) FROM y\nSELECT a.* EXCEPT (a, b), b.* REPLACE (a AS b, b AS C)\nSELECT a.* EXCEPT (a, b), b.* REPLACE (a AS b, b AS C) FROM x\nSELECT A.* EXCEPT (A.COL_1) FROM TABLE_1 AS A\nSELECT zoo, animals FROM (VALUES ('oakland', ARRAY('a', 'b')), ('sf', ARRAY('b', 'c'))) AS t(zoo, animals)\nSELECT zoo, animals FROM UNNEST(ARRAY(STRUCT('oakland' AS zoo, ARRAY('a', 'b') AS animals), STRUCT('sf' AS zoo, ARRAY('b', 'c') AS animals))) AS t(zoo, animals)\nWITH a AS (SELECT 1) SELECT 1 UNION ALL SELECT 2\nWITH a AS (SELECT 1) SELECT 1 UNION SELECT 2\nWITH a AS (SELECT 1) SELECT 1 INTERSECT SELECT 2\nWITH a AS (SELECT 1) SELECT 1 EXCEPT SELECT 2\nWITH a AS (SELECT 1) SELECT 1 EXCEPT SELECT 2\n(SELECT 1) UNION (SELECT 2)\n(SELECT 1) UNION SELECT 2\nSELECT 1 UNION (SELECT 2)\n(SELECT 1) ORDER BY x LIMIT 1 OFFSET 1\n(SELECT 1 UNION SELECT 2) UNION (SELECT 2 UNION ALL SELECT 3)\n(SELECT 1 UNION SELECT 2) ORDER BY x LIMIT 1 OFFSET 1\nSELECT 1 UNION (SELECT 2) ORDER BY x\n(SELECT 1) UNION SELECT 2 ORDER BY x\nSELECT * FROM (((SELECT 1) UNION SELECT 2) ORDER BY x LIMIT 1 OFFSET 1)\nSELECT * FROM ((SELECT 1 AS x) CROSS JOIN (SELECT 2 AS y)) AS z\n((SELECT 1) EXCEPT (SELECT 2))\n((SELECT 1)) LIMIT 1\nVALUES (1) UNION SELECT * FROM x\nWITH a AS (SELECT 1) SELECT a.* FROM a\nWITH a AS (SELECT 1), b AS (SELECT 2) SELECT a.*, b.* FROM a CROSS JOIN b\nWITH a AS (WITH b AS (SELECT 1 AS x) SELECT b.x FROM b) SELECT a.x FROM a\nWITH RECURSIVE T(n) AS (VALUES (1) UNION ALL SELECT n + 1 FROM t WHERE n < 100) SELECT SUM(n) FROM t\nWITH RECURSIVE T(n, m) AS (VALUES (1, 2) UNION ALL SELECT n + 1, n + 2 FROM t) SELECT SUM(n) FROM t\nWITH baz AS (SELECT 1 AS col) UPDATE bar SET cid = baz.col1 FROM baz\nSELECT * FROM (WITH y AS (SELECT 1 AS z) SELECT z FROM y) AS x\nSELECT RANK() OVER () FROM x\nSELECT RANK() OVER () AS y FROM x\nSELECT RANK() OVER (PARTITION BY a) FROM x\nSELECT RANK() OVER (PARTITION BY a, b) FROM x\nSELECT RANK() OVER (ORDER BY a) FROM x\nSELECT RANK() OVER (ORDER BY a, b) FROM x\nSELECT RANK() OVER (PARTITION BY a ORDER BY a) FROM x\nSELECT RANK() OVER (PARTITION BY a, b ORDER BY a, b DESC) FROM x\nSELECT SUM(x) OVER (PARTITION BY a) AS y FROM x\nSELECT SUM(x) OVER (PARTITION BY a ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\nSELECT SUM(x) OVER (PARTITION BY a ORDER BY b ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\nSELECT SUM(x) OVER (PARTITION BY a ORDER BY b ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\nSELECT SUM(x) OVER (PARTITION BY a ORDER BY b RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND CURRENT ROW)\nSELECT SUM(x) OVER (PARTITION BY a ORDER BY b RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND INTERVAL '2' DAYS FOLLOWING)\nSELECT SUM(x) OVER (PARTITION BY a ORDER BY b RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND UNBOUNDED FOLLOWING)\nSELECT SUM(x) OVER (PARTITION BY a ROWS BETWEEN UNBOUNDED PRECEDING AND PRECEDING)\nSELECT SUM(x) OVER (PARTITION BY a ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)\nSELECT SUM(x) OVER (PARTITION BY a ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)\nSELECT SUM(x) OVER (PARTITION BY a RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)\nSELECT SUM(x) OVER (PARTITION BY a RANGE BETWEEN 1 AND 3)\nSELECT SUM(x) OVER (PARTITION BY a RANGE BETWEEN 1 FOLLOWING AND 3)\nSELECT SUM(x) OVER (PARTITION BY a RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING)\nSELECT AVG(x) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) FROM t\nSELECT SUM(x) OVER (PARTITION BY a ORDER BY date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)\nSELECT LISTAGG(x) WITHIN GROUP (ORDER BY x) AS y\nSELECT LISTAGG(x) WITHIN GROUP (ORDER BY x DESC)\nSELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x)\nSELECT PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY x)\nSELECT SUM(x) FILTER(WHERE x > 1)\nSELECT SUM(x) FILTER(WHERE x > 1) OVER (ORDER BY y)\nSELECT COUNT(DISTINCT a) OVER (PARTITION BY c ORDER BY d ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)\nSELECT a['1'], b[0], x.c[0], \"x\".d['1'] FROM x\nSELECT ARRAY(1, 2, 3) FROM x\nSELECT ARRAY(ARRAY(1), ARRAY(2)) FROM x\nSELECT MAP[ARRAY(1), ARRAY(2)] FROM x\nSELECT MAP(ARRAY(1), ARRAY(2)) FROM x\nSELECT MAX(ARRAY(1, 2, 3)) FROM x\nSELECT ARRAY(ARRAY(0))[0][0] FROM x\nSELECT MAP[ARRAY('x'), ARRAY(0)]['x'] FROM x\nSELECT student, score FROM tests LATERAL VIEW EXPLODE(scores)\nSELECT student, score FROM tests LATERAL VIEW EXPLODE(scores) AS score\nSELECT student, score FROM tests LATERAL VIEW EXPLODE(scores) t AS score\nSELECT student, score FROM tests LATERAL VIEW EXPLODE(scores) t AS score, name\nSELECT student, score FROM tests LATERAL VIEW OUTER EXPLODE(scores) t AS score, name\nSELECT tf.* FROM (SELECT 0) AS t LATERAL VIEW STACK(1, 2) tf\nSELECT tf.* FROM (SELECT 0) AS t LATERAL VIEW STACK(1, 2) tf AS col0, col1, col2\nSELECT student, score FROM tests CROSS JOIN UNNEST(scores) AS t(score)\nSELECT student, score FROM tests CROSS JOIN UNNEST(scores) AS t(a, b)\nSELECT student, score FROM tests CROSS JOIN UNNEST(scores) WITH ORDINALITY AS t(a, b)\nSELECT student, score FROM tests CROSS JOIN UNNEST(x.scores) AS t(score)\nSELECT student, score FROM tests CROSS JOIN UNNEST(ARRAY(x.scores)) AS t(score)\nCREATE TABLE foo AS (SELECT 1) UNION ALL (SELECT 2)\nCREATE TABLE foo (id INT PRIMARY KEY ASC)\nCREATE TABLE a.b AS SELECT 1\nCREATE TABLE a.b AS SELECT 1 WITH DATA AND STATISTICS\nCREATE TABLE a.b AS SELECT 1 WITH NO DATA AND NO STATISTICS\nCREATE TABLE a.b AS (SELECT 1) NO PRIMARY INDEX\nCREATE TABLE a.b AS (SELECT 1) UNIQUE PRIMARY INDEX index1 (a) UNIQUE INDEX index2 (b)\nCREATE TABLE a.b AS (SELECT 1) PRIMARY AMP INDEX index1 (a) UNIQUE INDEX index2 (b)\nCREATE TABLE a.b AS SELECT a FROM a.c\nCREATE TABLE IF NOT EXISTS x AS SELECT a FROM d\nCREATE TABLE z (a INT, b VARCHAR, c VARCHAR(100), d DECIMAL(5, 3))\nCREATE TABLE z (end INT)\nCREATE TABLE z (bucket INT)\nCREATE TABLE z (truncate INT)\nCREATE TABLE z (a ARRAY<TEXT>, b MAP<TEXT, DOUBLE>, c DECIMAL(5, 3))\nCREATE TABLE z (a INT, b VARCHAR COMMENT 'z', c VARCHAR(100) COMMENT 'z', d DECIMAL(5, 3))\nCREATE TABLE z (a INT(11) DEFAULT UUID())\nCREATE TABLE z (n INT DEFAULT 0 NOT NULL)\nCREATE TABLE z (a INT(11) DEFAULT NULL COMMENT '客户id')\nCREATE TABLE z (a INT(11) NOT NULL DEFAULT 1)\nCREATE TABLE z (a INT(11) NOT NULL DEFAULT -1)\nCREATE TABLE z (a INT(11) NOT NULL COLLATE utf8_bin AUTO_INCREMENT)\nCREATE TABLE z (a INT, PRIMARY KEY (a))\nCREATE TABLE z WITH (FORMAT='parquet') AS SELECT 1\nCREATE TABLE z WITH (FORMAT='ORC', x='2') AS SELECT 1\nCREATE TABLE z WITH (TABLE_FORMAT='iceberg', FORMAT='parquet') AS SELECT 1\nCREATE TABLE z WITH (TABLE_FORMAT='iceberg', FORMAT='ORC', x='2') AS SELECT 1\nCREATE TABLE z (z INT) WITH (PARTITIONED_BY=(x INT, y INT))\nCREATE TABLE z (z INT) WITH (PARTITIONED_BY=(x INT)) AS SELECT 1\nCREATE TABLE z AS (WITH cte AS (SELECT 1) SELECT * FROM cte)\nCREATE TABLE z AS ((WITH cte AS (SELECT 1) SELECT * FROM cte))\nCREATE TABLE z (a INT UNIQUE)\nCREATE TABLE z (a INT AUTO_INCREMENT)\nCREATE TABLE z (a INT UNIQUE AUTO_INCREMENT)\nCREATE TABLE z (a INT REFERENCES parent (b, c))\nCREATE TABLE z (a INT PRIMARY KEY, b INT REFERENCES foo (id))\nCREATE TABLE z (a INT, FOREIGN KEY (a) REFERENCES parent (b, c))\nCREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON DELETE NO ACTION)\nCREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON DELETE CASCADE)\nCREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON DELETE SET NULL)\nCREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON DELETE SET DEFAULT)\nCREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON UPDATE NO ACTION)\nCREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON UPDATE CASCADE)\nCREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON UPDATE SET NULL)\nCREATE TABLE foo (bar INT REFERENCES baz (baz_id) ON UPDATE SET DEFAULT)\nCREATE TABLE asd AS SELECT asd FROM asd WITH NO DATA\nCREATE TABLE asd AS SELECT asd FROM asd WITH DATA\nCREATE TABLE products (x INT GENERATED BY DEFAULT AS IDENTITY)\nCREATE TABLE products (x INT GENERATED BY DEFAULT ON NULL AS IDENTITY)\nCREATE TABLE products (x INT GENERATED ALWAYS AS IDENTITY)\nCREATE TABLE konyvszerzo (szerzo_azon INT CONSTRAINT konyvszerzo_szerzo_fk REFERENCES szerzo)\nCREATE TABLE IF NOT EXISTS customer (pk BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (INCREMENT BY 1))\nCREATE TABLE customer (pk BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 10 INCREMENT BY 1 MINVALUE -1 MAXVALUE 1 NO CYCLE))\nCREATE TABLE customer (pk BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 10))\nCREATE TABLE customer (pk BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (CYCLE))\nCREATE TABLE customer (period INT NOT NULL)\nCREATE TABLE foo (baz_id INT REFERENCES baz (id) DEFERRABLE)\nCREATE TABLE foo (baz CHAR(4) CHARACTER SET LATIN UPPERCASE NOT CASESPECIFIC COMPRESS 'a')\nCREATE TABLE db.foo (id INT NOT NULL, valid_date DATE FORMAT 'YYYY-MM-DD', measurement INT COMPRESS)\nCREATE TABLE foo (baz DATE FORMAT 'YYYY/MM/DD' TITLE 'title' INLINE LENGTH 1 COMPRESS ('a', 'b'))\nCREATE TABLE t (title TEXT)\nCREATE TABLE foo (baz INT, inline TEXT)\nCREATE ALGORITHM=UNDEFINED DEFINER=foo@% VIEW a SQL SECURITY DEFINER AS (SELECT a FROM b)\nCREATE TEMPORARY TABLE x AS SELECT a FROM d\nCREATE TEMPORARY TABLE IF NOT EXISTS x AS SELECT a FROM d\nCREATE TABLE a (b INT) ON COMMIT PRESERVE ROWS\nCREATE VIEW x AS SELECT a FROM b\nCREATE VIEW IF NOT EXISTS x AS SELECT a FROM b\nCREATE VIEW z (a, b COMMENT 'b', c COMMENT 'c') AS SELECT a, b, c FROM d\nCREATE VIEW IF NOT EXISTS z (a, b COMMENT 'b', c COMMENT 'c') AS SELECT a, b, c FROM d\nCREATE OR REPLACE VIEW x AS SELECT *\nCREATE OR REPLACE TEMPORARY VIEW x AS SELECT *\nCREATE TEMPORARY VIEW x AS SELECT a FROM d\nCREATE TEMPORARY VIEW IF NOT EXISTS x AS SELECT a FROM d\nCREATE TEMPORARY VIEW x AS WITH y AS (SELECT 1) SELECT * FROM y\nCREATE MATERIALIZED VIEW x.y.z AS SELECT a FROM b\nCREATE VIEW z (a, b)\nCREATE VIEW z (a, b COMMENT 'b', c COMMENT 'c')\nCREATE VIEW z AS LOCKING ROW FOR ACCESS SELECT a FROM b\nCREATE TEMPORARY FUNCTION f\nCREATE TEMPORARY FUNCTION f AS 'g'\nCREATE FUNCTION f\nCREATE FUNCTION f AS 'g'\nCREATE FUNCTION a(b INT, c VARCHAR) AS 'SELECT 1'\nCREATE FUNCTION a() LANGUAGE sql\nCREATE FUNCTION a() LANGUAGE sql RETURNS INT\nCREATE FUNCTION a.b(x INT) RETURNS INT AS RETURN x + 1\nCREATE FUNCTION a.b(x TEXT) RETURNS TEXT CONTAINS SQL AS RETURN x\nCREATE FUNCTION a.b(x TEXT) RETURNS TEXT LANGUAGE SQL MODIFIES SQL DATA AS RETURN x\nCREATE FUNCTION a.b(x TEXT) LANGUAGE SQL READS SQL DATA RETURNS TEXT AS RETURN x\nCREATE FUNCTION a.b.c()\nCREATE INDEX abc ON t(a)\nCREATE INDEX \"abc\" ON t(a)\nCREATE INDEX abc ON t(a, b, b)\nCREATE INDEX abc ON t(a NULLS LAST)\nCREATE INDEX pointloc ON points USING GIST(BOX(location, location))\nCREATE UNIQUE INDEX abc ON t(a, b, b)\nCREATE UNIQUE INDEX IF NOT EXISTS my_idx ON tbl(a, b)\nCREATE SCHEMA x\nCREATE SCHEMA IF NOT EXISTS y\nCREATE DATABASE x\nCREATE DATABASE IF NOT EXISTS y\nCREATE PROCEDURE IF NOT EXISTS a.b.c() AS 'DECLARE BEGIN; END'\nCREATE TABLE T3 AS (SELECT DISTINCT A FROM T1 EXCEPT (SELECT A FROM T2) LIMIT 1)\nDESCRIBE x\nDESCRIBE EXTENDED a.b\nDESCRIBE FORMATTED a.b\nDESCRIBE SELECT 1\nDROP INDEX a.b.c\nDROP FUNCTION a.b.c (INT)\nDROP MATERIALIZED VIEW x.y.z\nCACHE TABLE x\nCACHE LAZY TABLE x\nCACHE LAZY TABLE x OPTIONS('storageLevel' = 'value')\nCACHE LAZY TABLE x OPTIONS(N'storageLevel' = 'value')\nCACHE LAZY TABLE x OPTIONS('storageLevel' = 'value') AS SELECT 1\nCACHE LAZY TABLE x OPTIONS('storageLevel' = 'value') AS WITH a AS (SELECT 1) SELECT a.* FROM a\nCACHE LAZY TABLE x AS WITH a AS (SELECT 1) SELECT a.* FROM a\nCACHE TABLE x AS WITH a AS (SELECT 1) SELECT a.* FROM a\nCACHE TABLE x AS (SELECT 1 AS y)\nDROP PROCEDURE a.b.c (INT)\nINSERT OVERWRITE TABLE a.b PARTITION(ds) SELECT x FROM y\nINSERT OVERWRITE TABLE a.b PARTITION(ds = 'YYYY-MM-DD') SELECT x FROM y\nINSERT OVERWRITE TABLE a.b PARTITION(ds, hour) SELECT x FROM y\nINSERT OVERWRITE TABLE a.b PARTITION(ds = 'YYYY-MM-DD', hour = 'hh') SELECT x FROM y\nINSERT INTO a.b PARTITION(DAY = '2024-04-14') (col1, col2) SELECT x FROM y\nDELETE FROM x WHERE y > 1\nDELETE FROM y\nDELETE FROM event USING sales WHERE event.eventid = sales.eventid\nDELETE FROM event USING sales, bla WHERE event.eventid = sales.eventid\nDELETE FROM event USING sales AS s WHERE event.eventid = s.eventid\nDELETE FROM event AS event USING sales AS s WHERE event.eventid = s.eventid\nDROP TABLE a\nDROP TABLE a.b\nDROP TABLE IF EXISTS a\nDROP TABLE IF EXISTS a.b\nDROP TABLE a CASCADE\nDROP TABLE s_hajo CASCADE CONSTRAINTS\nDROP TABLE a PURGE\nDROP VIEW a\nDROP VIEW a.b\nDROP VIEW IF EXISTS a\nDROP VIEW IF EXISTS a.b\nUSE db\nBEGIN\nROLLBACK\nROLLBACK TO b\nINSERT INTO x SELECT * FROM y\nINSERT INTO x (SELECT * FROM y)\nINSERT INTO x WITH y AS (SELECT 1) SELECT * FROM y\nINSERT INTO x.z IF EXISTS SELECT * FROM y\nINSERT INTO x VALUES (1, 'a', 2.0)\nINSERT INTO x VALUES (1, 'a', 2.0), (1, 'a', 3.0), (X(), y[1], z.x)\nINSERT INTO y (a, b, c) SELECT a, b, c FROM x\nINSERT INTO y (SELECT 1) UNION (SELECT 2)\nINSERT INTO result_table (WITH test AS (SELECT * FROM source_table) SELECT * FROM test)\nINSERT INTO \"tests_user\" (\"username\", \"first_name\", \"last_name\") VALUES ('fiara', 'Fiara', 'Ironhide') RETURNING \"tests_user\".\"id\"\nINSERT INTO t1 (tc1 /* tc1 */, tc2 /* tc2 */) SELECT c1 /* sc1 */, c2 /* sc2 */ FROM t\nINSERT INTO t1 (\"tc1\" /* tc1 */, \"tc2\" /* tc2 */) SELECT \"c1\" /* sc1 */, \"c2\" /* sc2 */ FROM t\nINSERT OVERWRITE TABLE x IF EXISTS SELECT * FROM y\nINSERT OVERWRITE TABLE a.b IF EXISTS SELECT * FROM y\nINSERT OVERWRITE DIRECTORY 'x' SELECT 1\nINSERT OVERWRITE LOCAL DIRECTORY 'x' SELECT 1\nINSERT OVERWRITE LOCAL DIRECTORY 'x' ROW FORMAT DELIMITED FIELDS TERMINATED BY '1' COLLECTION ITEMS TERMINATED BY '2' MAP KEYS TERMINATED BY '3' LINES TERMINATED BY '4' NULL DEFINED AS '5' SELECT 1\nLOAD DATA INPATH 'x' INTO TABLE y PARTITION(ds = 'yyyy')\nLOAD DATA LOCAL INPATH 'x' INTO TABLE y PARTITION(ds = 'yyyy')\nLOAD DATA LOCAL INPATH 'x' INTO TABLE y PARTITION(ds = 'yyyy') INPUTFORMAT 'y'\nLOAD DATA LOCAL INPATH 'x' INTO TABLE y PARTITION(ds = 'yyyy') INPUTFORMAT 'y' SERDE 'z'\nLOAD DATA INPATH 'x' INTO TABLE y INPUTFORMAT 'y' SERDE 'z'\nLOAD DATA INPATH 'x' INTO TABLE y.b INPUTFORMAT 'y' SERDE 'z'\nSELECT 1 FROM PARQUET_SCAN('/x/y/*') AS y\nUNCACHE TABLE x\nUNCACHE TABLE IF EXISTS x\nUPDATE tbl_name SET foo = 123\nUPDATE tbl_name SET foo = 123, bar = 345\nUPDATE db.tbl_name SET foo = 123 WHERE tbl_name.bar = 234\nUPDATE db.tbl_name SET foo = 123, foo_1 = 234 WHERE tbl_name.bar = 234\nUPDATE products SET price = price * 1.10 WHERE price <= 99.99 RETURNING name, price AS new_price\nUPDATE t1 AS a, t2 AS b, t3 AS c LEFT JOIN t4 AS d ON c.id = d.id SET a.id = 1\nCOMMENT ON COLUMN my_schema.my_table.my_column IS 'Employee ID number'\nCOMMENT ON DATABASE my_database IS 'Development Database'\nCOMMENT ON PROCEDURE my_proc(integer, integer) IS 'Runs a report'\nCOMMENT ON TABLE my_schema.my_table IS 'Employee Information'\nCOMMENT ON TABLE my_schema.my_table IS N'National String'\nWITH a AS (SELECT 1) INSERT INTO b SELECT * FROM a\nWITH a AS (SELECT * FROM b) UPDATE a SET col = 1\nWITH a AS (SELECT * FROM b) CREATE TABLE b AS SELECT * FROM a\nWITH a AS (SELECT * FROM b) DELETE FROM a\nSELECT ? AS ? FROM x WHERE b BETWEEN ? AND ? GROUP BY ?, 1 LIMIT ?\nSELECT :hello, ? FROM x LIMIT :my_limit\nSELECT a FROM b WHERE c IS ?\nSELECT * FROM x OFFSET @skip FETCH NEXT @take ROWS ONLY\nWITH a AS ((SELECT b.foo AS foo, b.bar AS bar FROM b) UNION ALL (SELECT c.foo AS foo, c.bar AS bar FROM c)) SELECT * FROM a\nWITH a AS ((SELECT 1 AS b) UNION ALL (SELECT 1 AS b)) SELECT * FROM a\nSELECT (WITH x AS (SELECT 1 AS y) SELECT * FROM x) AS z\nSELECT ((SELECT 1) + 1)\nSELECT ((SELECT 0) UNION (SELECT 1) ORDER BY 1 OFFSET 1)\nSELECT * FROM x WHERE y IN ((SELECT 1) UNION (SELECT 2) OFFSET 2)\nSELECT * FROM project.dataset.INFORMATION_SCHEMA.TABLES\nSELECT CAST(x AS INT) /* comment */ FROM foo\nSELECT c /* c1 */ AS alias /* c2 */\nSELECT a /* x */, b /* x */\nSELECT a /* x */ /* y */ /* z */, b /* k */ /* m */\nSELECT * FROM foo /* x */, bla /* x */\nSELECT 1 /* comment */ + 1\nSELECT 1 /* c1 */ + 2 /* c2 */\nSELECT 1 /* c1 */ + /* c2 */ 2 /* c3 */\nSELECT 1 /* c1 */ + 2 /* c2 */ + 3 /* c3 */\nSELECT 1 /* c1 */ + 2 /* c2 */, 3 /* c3 */\nSELECT x FROM a.b.c /* x */, e.f.g /* x */\nSELECT FOO(x /* c */) /* FOO */, b /* b */\nSELECT FOO(x /* c1 */ + y /* c2 */ + BLA(5 /* c3 */)) FROM (VALUES (1 /* c4 */, \"test\" /* c5 */)) /* c6 */\nINSERT INTO foo SELECT * FROM bar /* comment */\n/* c */ WITH x AS (SELECT 1) SELECT * FROM x\nSELECT a FROM x WHERE a COLLATE 'utf8_general_ci' = 'b'\nSELECT x AS INTO FROM bla\nALTER TABLE integers ADD COLUMN k INT\nALTER TABLE integers ADD COLUMN k INT FIRST\nALTER TABLE integers ADD COLUMN k INT AFTER m\nALTER TABLE integers ADD COLUMN IF NOT EXISTS k INT\nALTER TABLE IF EXISTS integers ADD COLUMN k INT\nALTER TABLE integers ADD COLUMN l INT DEFAULT 10\nALTER TABLE measurements ADD COLUMN mtime TIMESTAMPTZ DEFAULT NOW()\nALTER TABLE integers DROP COLUMN k\nALTER TABLE integers DROP COLUMN IF EXISTS k\nALTER TABLE integers DROP COLUMN k CASCADE\nALTER TABLE integers ALTER COLUMN i SET DATA TYPE VARCHAR\nALTER TABLE integers ALTER COLUMN i SET DATA TYPE VARCHAR USING CONCAT(i, '_', j)\nALTER TABLE integers ALTER COLUMN i SET DEFAULT 10\nALTER TABLE integers ALTER COLUMN i DROP DEFAULT\nALTER TABLE ingredients ALTER COLUMN amount COMMENT 'tablespoons'\nALTER TABLE mydataset.mytable DROP COLUMN A, DROP COLUMN IF EXISTS B\nALTER TABLE mydataset.mytable ADD COLUMN A TEXT, ADD COLUMN IF NOT EXISTS B INT\nALTER TABLE orders DROP PARTITION(dt = '2014-05-14', country = 'IN')\nALTER TABLE orders DROP IF EXISTS PARTITION(dt = '2014-05-14', country = 'IN')\nALTER TABLE orders DROP PARTITION(dt = '2014-05-14', country = 'IN'), PARTITION(dt = '2014-05-15', country = 'IN')\nALTER TABLE mydataset.mytable DELETE WHERE x = 1\nALTER TABLE table1 RENAME COLUMN c1 TO c2\nALTER TABLE table1 RENAME COLUMN IF EXISTS c1 TO c2\nALTER TABLE table1 RENAME TO table2\nALTER VIEW view1 AS SELECT a, b, c FROM table1\nALTER VIEW view1 AS SELECT a, b, c FROM table1 UNION ALL SELECT a, b, c FROM table2\nALTER VIEW view1 AS SELECT a, b, c FROM table1 UNION ALL SELECT a, b, c FROM table2 LIMIT 100\nSELECT div.a FROM test_table AS div\nWITH view AS (SELECT 1 AS x) SELECT * FROM view\nARRAY<STRUCT<INT, DOUBLE, ARRAY<INT>>>\nSTRUCT<int INT>\nSELECT CAST(NULL AS ARRAY<INT>) IS NULL AS array_is_null\nALTER TABLE \"schema\".\"tablename\" ADD CONSTRAINT \"CHK_Name\" CHECK (NOT \"IdDwh\" IS NULL AND \"IdDwh\" <> (0))\nALTER TABLE persons ADD CONSTRAINT persons_pk PRIMARY KEY (first_name, last_name)\nALTER TABLE pets ADD CONSTRAINT pets_persons_fk FOREIGN KEY (owner_first_name, owner_last_name) REFERENCES persons\nALTER TABLE pets ADD CONSTRAINT pets_name_not_cute_chk CHECK (LENGTH(name) < 20)\nALTER TABLE people10m ADD CONSTRAINT dateWithinRange CHECK (birthDate > '1900-01-01')\nALTER TABLE people10m ADD CONSTRAINT validIds CHECK (id > 1 AND id < 99999999) ENFORCED\nALTER TABLE ct ADD CONSTRAINT ct_id_fk FOREIGN KEY (id) REFERENCES et (fid) DEFERRABLE INITIALLY DEFERRED\nALTER TABLE baa ADD CONSTRAINT boo PRIMARY KEY (x, y) NOT ENFORCED DEFERRABLE INITIALLY DEFERRED NORELY\nALTER TABLE baa ADD CONSTRAINT boo PRIMARY KEY (x, y) NOT ENFORCED DEFERRABLE INITIALLY DEFERRED NORELY\nALTER TABLE baa ADD CONSTRAINT boo FOREIGN KEY (x, y) REFERENCES persons ON UPDATE NO ACTION ON DELETE NO ACTION MATCH FULL\nALTER TABLE a ADD PRIMARY KEY (x, y) NOT ENFORCED\nALTER TABLE a ADD FOREIGN KEY (x, y) REFERENCES bla\nALTER TABLE s_ut ADD CONSTRAINT s_ut_uq UNIQUE hajo\nSELECT partition FROM a\nSELECT end FROM a\nSELECT id FROM b.a AS a QUALIFY ROW_NUMBER() OVER (PARTITION BY br ORDER BY sadf DESC) = 1\nSELECT * FROM x WHERE a GROUP BY a HAVING b SORT BY s ORDER BY c LIMIT d\nSELECT LEFT.FOO FROM BLA AS LEFT\nSELECT RIGHT.FOO FROM BLA AS RIGHT\nSELECT LEFT FROM LEFT LEFT JOIN RIGHT RIGHT JOIN LEFT\nSELECT * FROM x WHERE name ILIKE ANY XXX('a', 'b')\nSELECT * FROM x WHERE name LIKE ANY XXX('a', 'b')\na OVERLAPS b\nPRAGMA quick_check\nPRAGMA QUICK_CHECK(0)\nPRAGMA QUICK_CHECK('sqlite_master')\nPRAGMA schema.quick_check\nPRAGMA schema.QUICK_CHECK(0)\nPRAGMA schema.QUICK_CHECK('sqlite_master')\nPRAGMA synchronous = 2\nPRAGMA synchronous = FULL\nPRAGMA memory_limit = '1GB'\nPRAGMA schema.synchronous = 2\nPRAGMA schema.synchronous = FULL\nPRAGMA schema.memory_limit = '1GB'\nJSON_OBJECT()\nJSON_OBJECT(*)\nJSON_OBJECT('key1': 1, 'key2': TRUE)\nJSON_OBJECT('id': '5', 'fld1': 'bla', 'fld2': 'bar')\nJSON_OBJECT('x': NULL, 'y': 1 NULL ON NULL)\nJSON_OBJECT('x': NULL, 'y': 1 WITH UNIQUE KEYS)\nJSON_OBJECT('x': NULL, 'y': 1 ABSENT ON NULL WITH UNIQUE KEYS)\nJSON_OBJECT('x': 1 RETURNING VARCHAR(100))\nJSON_OBJECT('x': 1 RETURNING VARBINARY FORMAT JSON ENCODING UTF8)\nPRIOR AS x\nSELECT if.x\nSELECT PERCENTILE_CONT(x, 0.5) OVER ()\nWITH my_cte AS (SELECT 'a' AS desc) SELECT desc AS description FROM my_cte\nWITH my_cte AS (SELECT 'a' AS asc) SELECT asc AS description FROM my_cte\nSELECT * FROM case\nSELECT * FROM schema.case\nSELECT * FROM current_date\nSELECT * FROM schema.current_date\nSELECT /*+ SOME_HINT(foo) */ 1\nSELECT /*+ REBALANCE */ * FROM foo\nSELECT * FROM (tbl1 CROSS JOIN (SELECT * FROM tbl2) AS t1)\n/* comment1 */ INSERT INTO x /* comment2 */ VALUES (1, 2, 3)\n/* comment1 */ UPDATE tbl /* comment2 */ SET x = 2 WHERE x < 2\n/* comment1 */ DELETE FROM x /* comment2 */ WHERE y > 1\n/* comment */ CREATE TABLE foo AS SELECT 1\nSELECT next, transform, if\nSELECT \"any\", \"case\", \"if\", \"next\"\nSELECT x FROM y ORDER BY x ASC\nKILL '123'\nKILL CONNECTION 123\nKILL QUERY '123'\nCHR(97)\nSELECT * FROM UNNEST(x) WITH ORDINALITY UNION ALL SELECT * FROM UNNEST(y) WITH ORDINALITY\nSELECT x FROM t1 UNION ALL SELECT x FROM t2 LIMIT 1\nSELECT x FROM t1 UNION ALL SELECT x FROM t2 UNION ALL SELECT x FROM t3 LIMIT 1\nWITH use(use) AS (SELECT 1) SELECT use FROM use\nSELECT recursive FROM t\nSELECT (ROW_NUMBER() OVER (PARTITION BY user ORDER BY date ASC) - ROW_NUMBER() OVER (PARTITION BY user, segment ORDER BY date ASC)) AS group_id FROM example_table\nCAST(foo AS BPCHAR)\nvalues\nSELECT values\nSELECT values AS values FROM t WHERE values + 1 > 3\nSELECT truncate\nSELECT only\nTRUNC(a, b)\nSELECT enum\nSELECT unlogged\nSELECT name\nSELECT copy\nSELECT rollup\nSELECT unnest\nSELECT cube, cube.x FROM cube\nSELECT * FROM a STRAIGHT_JOIN b\nSELECT COUNT(DISTINCT \"foo bar\") FROM (SELECT 1 AS \"foo bar\") AS t\nSELECT vector\nWITH all AS (SELECT 1 AS count) SELECT all.count FROM all\nSELECT rename\nGRANT SELECT ON TABLE tbl TO user\nGRANT SELECT, INSERT ON FUNCTION tbl TO user\nGRANT SELECT ON orders TO ROLE PUBLIC\nGRANT SELECT ON nation TO alice WITH GRANT OPTION\nGRANT DELETE ON SCHEMA finance TO bob\nREVOKE SELECT ON TABLE tbl FROM user\nREVOKE SELECT, INSERT ON FUNCTION tbl FROM user\nREVOKE SELECT ON orders FROM ROLE PUBLIC\nREVOKE GRANT OPTION FOR SELECT ON nation FROM alice\nREVOKE DELETE ON SCHEMA finance FROM bob CASCADE\nREVOKE INSERT ON TABLE orders FROM user RESTRICT\nSELECT attach\nSELECT detach\nSELECT 1 OFFSET 1\nSELECT 1 LIMIT 1\nCAST(x AS INT128)\nCAST(x AS UINT128)\nCAST(x AS UINT256)\nSELECT export\nSELECT ARG_MAX(DISTINCT selected_col, filtered_col) FROM table\nSELECT ARG_MIN(DISTINCT selected_col, filtered_col) FROM table\na.b.c.D()\na.b.c.d.e.f.G()\nSELECT CUME_DIST() OVER (ORDER BY 1) FROM (SELECT 1)\nSELECT DENSE_RANK() OVER (ORDER BY 1) FROM (SELECT 1)\nSELECT NTILE(1) OVER (ORDER BY 1) FROM (SELECT 1)\nSELECT RANK() OVER (ORDER BY 1) FROM (SELECT 1)\nSELECT PERCENT_RANK() OVER (ORDER BY 1) FROM (SELECT 1)\nSELECT ACOS(x)\nSELECT ACOSH(x)\nSELECT ASIN(x)\nSELECT ASINH(x)\nSELECT ATAN(x)\nSELECT ATANH(x)\nSELECT ATAN2(x, y)\nSELECT COT(x)\nSELECT COTH(x)\nSELECT CSC(x)\nSELECT CSCH(x)\nSELECT RADIANS(x)\nSELECT SEC(x)\nSELECT SECH(x)\nSELECT UTC_DATE\nSELECT UTC_TIME\nSELECT UTC_TIMESTAMP\nSELECT SIN(x)\nSELECT SINH(x)\nSELECT TANH(x)\nSELECT COSINE_DISTANCE(v1, v2)\nSELECT EUCLIDEAN_DISTANCE(v1, v2)\nFOO(values.c)\ncase.*\nSELECT unknown\nSELECT test.Unknown FROM test\nSELECT lock\nSELECT a FROM test GROUP BY GROUPING SETS ((x + y, z))\nSELECT (LEAD(foo1, 1, 0)) OVER (PARTITION BY foo2 ORDER BY foo3) FROM t\nSELECT LAST_VALUE(CASE WHEN interval <> 'foo' THEN interval END) IGNORE NULLS FROM t\nWITH UNNEST AS (SELECT 1 AS UNNEST) SELECT UNNEST FROM UNNEST\nSELECT * FROM tbl GROUP BY GROUPING SETS ((a + 1, b * 1), c, CUBE (a, b), ROLLUP (c, d), (a + y, b * 1), ())\nSELECT * FROM tbl GROUP BY GROUPING SETS (GROUPING SETS (course), GROUPING SETS (type), CUBE (a), ROLLUP (b))\nSELECT analyze FROM (SELECT 1 AS analyze)\nSELECT 'Ac' ILIKE 'a%c' ESCAPE NULL\nSELECT CURRENT_DATABASE()\nSELECT CURRENT_SCHEMAS(arg_bool)\nSELECT UNIFORM(1, 10, 5)\nSELECT UNIFORM(1, 10)\nSELECT CURRENT_TIMEZONE()\nSELECT NUMRANGE(1.1, 2.2) -|- NUMRANGE(2.2, 3.3)\nCREATE TABLE t (a VARCHAR, check INT)\n"
  },
  {
    "path": "tests/fixtures/jsonpath/LICENSE",
    "content": "jsonpath-compliance-test-suite\r\nThe BSD-2 license (the \"License\") set forth below applies to all parts of the jsonpath-compliance-test-suite project. You may not use this file except in compliance with the License.\r\n\r\nBSD-2 License\r\n\r\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\r\n\r\nRedistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\r\n\r\nRedistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\r\n\r\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n\r\n\r\n\r\n"
  },
  {
    "path": "tests/fixtures/jsonpath/cts.json",
    "content": "{\n  \"description\": \"JSONPath Compliance Test Suite. This file is autogenerated, do not edit.\",\n  \"tests\": [\n    {\n      \"name\": \"basic, root\",\n      \"selector\": \"$\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": [\n        [\n          \"first\",\n          \"second\"\n        ]\n      ]\n    },\n    {\n      \"name\": \"basic, no leading whitespace\",\n      \"selector\": \" $\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"basic, no trailing whitespace\",\n      \"selector\": \"$ \",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"basic, name shorthand\",\n      \"selector\": \"$.a\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"basic, name shorthand, extended unicode ☺\",\n      \"selector\": \"$.☺\",\n      \"document\": {\n        \"☺\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"basic, name shorthand, underscore\",\n      \"selector\": \"$._\",\n      \"document\": {\n        \"_\": \"A\",\n        \"_foo\": \"B\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"basic, name shorthand, symbol\",\n      \"selector\": \"$.&\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"basic, name shorthand, number\",\n      \"selector\": \"$.1\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"basic, name shorthand, absent data\",\n      \"selector\": \"$.c\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": []\n    },\n    {\n      \"name\": \"basic, name shorthand, array data\",\n      \"selector\": \"$.a\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"basic, wildcard shorthand, object data\",\n      \"selector\": \"$.*\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": [\n        \"A\",\n        \"B\"\n      ]\n    },\n    {\n      \"name\": \"basic, wildcard shorthand, array data\",\n      \"selector\": \"$.*\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": [\n        \"first\",\n        \"second\"\n      ]\n    },\n    {\n      \"name\": \"basic, wildcard selector, array data\",\n      \"selector\": \"$[*]\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": [\n        \"first\",\n        \"second\"\n      ]\n    },\n    {\n      \"name\": \"basic, wildcard shorthand, then name shorthand\",\n      \"selector\": \"$.*.a\",\n      \"document\": {\n        \"x\": {\n          \"a\": \"Ax\",\n          \"b\": \"Bx\"\n        },\n        \"y\": {\n          \"a\": \"Ay\",\n          \"b\": \"By\"\n        }\n      },\n      \"result\": [\n        \"Ax\",\n        \"Ay\"\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors\",\n      \"selector\": \"$[0,2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        0,\n        2\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, space instead of comma\",\n      \"selector\": \"$[0 2]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"basic, multiple selectors, name and index, array data\",\n      \"selector\": \"$['a',1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        1\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, name and index, object data\",\n      \"selector\": \"$['a',1]\",\n      \"document\": {\n        \"a\": 1,\n        \"b\": 2\n      },\n      \"result\": [\n        1\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, index and slice\",\n      \"selector\": \"$[1,5:7]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        1,\n        5,\n        6\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, index and slice, overlapping\",\n      \"selector\": \"$[1,0:3]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        1,\n        0,\n        1,\n        2\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, duplicate index\",\n      \"selector\": \"$[1,1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        1,\n        1\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, wildcard and index\",\n      \"selector\": \"$[*,1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9,\n        1\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, wildcard and name\",\n      \"selector\": \"$[*,'a']\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": [\n        \"A\",\n        \"B\",\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, wildcard and slice\",\n      \"selector\": \"$[*,0:2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9,\n        0,\n        1\n      ]\n    },\n    {\n      \"name\": \"basic, multiple selectors, multiple wildcards\",\n      \"selector\": \"$[*,*]\",\n      \"document\": [\n        0,\n        1,\n        2\n      ],\n      \"result\": [\n        0,\n        1,\n        2,\n        0,\n        1,\n        2\n      ]\n    },\n    {\n      \"name\": \"basic, empty segment\",\n      \"selector\": \"$[]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"basic, descendant segment, index\",\n      \"selector\": \"$..[1]\",\n      \"document\": {\n        \"o\": [\n          0,\n          1,\n          [\n            2,\n            3\n          ]\n        ]\n      },\n      \"result\": [\n        1,\n        3\n      ]\n    },\n    {\n      \"name\": \"basic, descendant segment, name shorthand\",\n      \"selector\": \"$..a\",\n      \"document\": {\n        \"o\": [\n          {\n            \"a\": \"b\"\n          },\n          {\n            \"a\": \"c\"\n          }\n        ]\n      },\n      \"result\": [\n        \"b\",\n        \"c\"\n      ]\n    },\n    {\n      \"name\": \"basic, descendant segment, wildcard shorthand, array data\",\n      \"selector\": \"$..*\",\n      \"document\": [\n        0,\n        1\n      ],\n      \"result\": [\n        0,\n        1\n      ]\n    },\n    {\n      \"name\": \"basic, descendant segment, wildcard selector, array data\",\n      \"selector\": \"$..[*]\",\n      \"document\": [\n        0,\n        1\n      ],\n      \"result\": [\n        0,\n        1\n      ]\n    },\n    {\n      \"name\": \"basic, descendant segment, wildcard shorthand, object data\",\n      \"selector\": \"$..*\",\n      \"document\": {\n        \"a\": \"b\"\n      },\n      \"result\": [\n        \"b\"\n      ]\n    },\n    {\n      \"name\": \"basic, descendant segment, wildcard shorthand, nested data\",\n      \"selector\": \"$..*\",\n      \"document\": {\n        \"o\": [\n          {\n            \"a\": \"b\"\n          }\n        ]\n      },\n      \"result\": [\n        [\n          {\n            \"a\": \"b\"\n          }\n        ],\n        {\n          \"a\": \"b\"\n        },\n        \"b\"\n      ]\n    },\n    {\n      \"name\": \"basic, descendant segment, multiple selectors\",\n      \"selector\": \"$..['a','d']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        \"b\",\n        \"e\",\n        \"c\",\n        \"f\"\n      ]\n    },\n    {\n      \"name\": \"basic, bald descendant segment\",\n      \"selector\": \"$..\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, existence\",\n      \"selector\": \"$[?@.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, existence, present with null\",\n      \"selector\": \"$[?@.a]\",\n      \"document\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals string, single quotes\",\n      \"selector\": \"$[?@.a=='b']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals numeric string, single quotes\",\n      \"selector\": \"$[?@.a=='1']\",\n      \"document\": [\n        {\n          \"a\": \"1\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"1\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals string, double quotes\",\n      \"selector\": \"$[?@.a==\\\"b\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals numeric string, double quotes\",\n      \"selector\": \"$[?@.a==\\\"1\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"1\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"1\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number\",\n      \"selector\": \"$[?@.a==1]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 2,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"1\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals null\",\n      \"selector\": \"$[?@.a==null]\",\n      \"document\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals null, absent from data\",\n      \"selector\": \"$[?@.a==null]\",\n      \"document\": [\n        {\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"filter, equals true\",\n      \"selector\": \"$[?@.a==true]\",\n      \"document\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals false\",\n      \"selector\": \"$[?@.a==false]\",\n      \"document\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, deep equality, arrays\",\n      \"selector\": \"$[?@.a==@.b]\",\n      \"document\": [\n        {\n          \"a\": false,\n          \"b\": [\n            1,\n            2\n          ]\n        },\n        {\n          \"a\": [\n            [\n              1,\n              [\n                2\n              ]\n            ]\n          ],\n          \"b\": [\n            [\n              1,\n              [\n                2\n              ]\n            ]\n          ]\n        },\n        {\n          \"a\": [\n            [\n              1,\n              [\n                2\n              ]\n            ]\n          ],\n          \"b\": [\n            [\n              [\n                2\n              ],\n              1\n            ]\n          ]\n        },\n        {\n          \"a\": [\n            [\n              1,\n              [\n                2\n              ]\n            ]\n          ],\n          \"b\": [\n            [\n              1,\n              2\n            ]\n          ]\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": [\n            [\n              1,\n              [\n                2\n              ]\n            ]\n          ],\n          \"b\": [\n            [\n              1,\n              [\n                2\n              ]\n            ]\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, deep equality, objects\",\n      \"selector\": \"$[?@.a==@.b]\",\n      \"document\": [\n        {\n          \"a\": false,\n          \"b\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          }\n        },\n        {\n          \"a\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          },\n          \"b\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          }\n        },\n        {\n          \"a\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          },\n          \"b\": {\n            \"y\": {\n              \"z\": 1\n            },\n            \"x\": 1\n          }\n        },\n        {\n          \"a\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          },\n          \"b\": {\n            \"x\": 1\n          }\n        },\n        {\n          \"a\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          },\n          \"b\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 2\n            }\n          }\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          },\n          \"b\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          }\n        },\n        {\n          \"a\": {\n            \"x\": 1,\n            \"y\": {\n              \"z\": 1\n            }\n          },\n          \"b\": {\n            \"y\": {\n              \"z\": 1\n            },\n            \"x\": 1\n          }\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals string, single quotes\",\n      \"selector\": \"$[?@.a!='b']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals numeric string, single quotes\",\n      \"selector\": \"$[?@.a!='1']\",\n      \"document\": [\n        {\n          \"a\": \"1\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals string, single quotes, different type\",\n      \"selector\": \"$[?@.a!='b']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals string, double quotes\",\n      \"selector\": \"$[?@.a!=\\\"b\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals numeric string, double quotes\",\n      \"selector\": \"$[?@.a!=\\\"1\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"1\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals string, double quotes, different types\",\n      \"selector\": \"$[?@.a!=\\\"b\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals number\",\n      \"selector\": \"$[?@.a!=1]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 2,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"1\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 2,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"1\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals number, different types\",\n      \"selector\": \"$[?@.a!=1]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals null\",\n      \"selector\": \"$[?@.a!=null]\",\n      \"document\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals null, absent from data\",\n      \"selector\": \"$[?@.a!=null]\",\n      \"document\": [\n        {\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals true\",\n      \"selector\": \"$[?@.a!=true]\",\n      \"document\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not-equals false\",\n      \"selector\": \"$[?@.a!=false]\",\n      \"document\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than string, single quotes\",\n      \"selector\": \"$[?@.a<'c']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than string, double quotes\",\n      \"selector\": \"$[?@.a<\\\"c\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than number\",\n      \"selector\": \"$[?@.a<10]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 10,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 20,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than null\",\n      \"selector\": \"$[?@.a<null]\",\n      \"document\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"filter, less than true\",\n      \"selector\": \"$[?@.a<true]\",\n      \"document\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"filter, less than false\",\n      \"selector\": \"$[?@.a<false]\",\n      \"document\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"filter, less than or equal to string, single quotes\",\n      \"selector\": \"$[?@.a<='c']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than or equal to string, double quotes\",\n      \"selector\": \"$[?@.a<=\\\"c\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than or equal to number\",\n      \"selector\": \"$[?@.a<=10]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 10,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 20,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 10,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than or equal to null\",\n      \"selector\": \"$[?@.a<=null]\",\n      \"document\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than or equal to true\",\n      \"selector\": \"$[?@.a<=true]\",\n      \"document\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, less than or equal to false\",\n      \"selector\": \"$[?@.a<=false]\",\n      \"document\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than string, single quotes\",\n      \"selector\": \"$[?@.a>'c']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than string, double quotes\",\n      \"selector\": \"$[?@.a>\\\"c\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than number\",\n      \"selector\": \"$[?@.a>10]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 10,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 20,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 20,\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than null\",\n      \"selector\": \"$[?@.a>null]\",\n      \"document\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"filter, greater than true\",\n      \"selector\": \"$[?@.a>true]\",\n      \"document\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"filter, greater than false\",\n      \"selector\": \"$[?@.a>false]\",\n      \"document\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"filter, greater than or equal to string, single quotes\",\n      \"selector\": \"$[?@.a>='c']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than or equal to string, double quotes\",\n      \"selector\": \"$[?@.a>=\\\"c\\\"]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than or equal to number\",\n      \"selector\": \"$[?@.a>=10]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 10,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 20,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 10,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 20,\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than or equal to null\",\n      \"selector\": \"$[?@.a>=null]\",\n      \"document\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than or equal to true\",\n      \"selector\": \"$[?@.a>=true]\",\n      \"document\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": true,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, greater than or equal to false\",\n      \"selector\": \"$[?@.a>=false]\",\n      \"document\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": false,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, exists and not-equals null, absent from data\",\n      \"selector\": \"$[?@.a&&@.a!=null]\",\n      \"document\": [\n        {\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, and\",\n      \"selector\": \"$[?@.a>0&&@.a<10]\",\n      \"document\": [\n        {\n          \"a\": -10,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 5,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 20,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 5,\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, or\",\n      \"selector\": \"$[?@.a=='b'||@.a=='d']\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not expression\",\n      \"selector\": \"$[?!(@.a=='b')]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not exists\",\n      \"selector\": \"$[?!@.a]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, not exists, data null\",\n      \"selector\": \"$[?!@.a]\",\n      \"document\": [\n        {\n          \"a\": null,\n          \"d\": \"e\"\n        },\n        {\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, non-singular query in comparison, slice\",\n      \"selector\": \"$[?@[0:0]==0]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, non-singular query in comparison, all children\",\n      \"selector\": \"$[?@[*]==0]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, non-singular query in comparison, descendants\",\n      \"selector\": \"$[?@..a==0]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, non-singular query in comparison, combined\",\n      \"selector\": \"$[?@.a[*].a==0]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, nested\",\n      \"selector\": \"$[?@[?@>1]]\",\n      \"document\": [\n        [\n          0\n        ],\n        [\n          0,\n          1\n        ],\n        [\n          0,\n          1,\n          2\n        ],\n        [\n          42\n        ]\n      ],\n      \"result\": [\n        [\n          0,\n          1,\n          2\n        ],\n        [\n          42\n        ]\n      ]\n    },\n    {\n      \"name\": \"filter, relative non-singular query, index, equal\",\n      \"selector\": \"$[?(@[0, 0]==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, index, not equal\",\n      \"selector\": \"$[?(@[0, 0]!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, index, less-or-equal\",\n      \"selector\": \"$[?(@[0, 0]<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, name, equal\",\n      \"selector\": \"$[?(@['a', 'a']==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, name, not equal\",\n      \"selector\": \"$[?(@['a', 'a']!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, name, less-or-equal\",\n      \"selector\": \"$[?(@['a', 'a']<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, combined, equal\",\n      \"selector\": \"$[?(@[0, '0']==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, combined, not equal\",\n      \"selector\": \"$[?(@[0, '0']!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, combined, less-or-equal\",\n      \"selector\": \"$[?(@[0, '0']<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, wildcard, equal\",\n      \"selector\": \"$[?(@.*==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, wildcard, not equal\",\n      \"selector\": \"$[?(@.*!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, wildcard, less-or-equal\",\n      \"selector\": \"$[?(@.*<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, slice, equal\",\n      \"selector\": \"$[?(@[0:0]==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, slice, not equal\",\n      \"selector\": \"$[?(@[0:0]!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, relative non-singular query, slice, less-or-equal\",\n      \"selector\": \"$[?(@[0:0]<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, index, equal\",\n      \"selector\": \"$[?($[0, 0]==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, index, not equal\",\n      \"selector\": \"$[?($[0, 0]!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, index, less-or-equal\",\n      \"selector\": \"$[?($[0, 0]<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, name, equal\",\n      \"selector\": \"$[?($['a', 'a']==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, name, not equal\",\n      \"selector\": \"$[?($['a', 'a']!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, name, less-or-equal\",\n      \"selector\": \"$[?($['a', 'a']<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, combined, equal\",\n      \"selector\": \"$[?($[0, '0']==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, combined, not equal\",\n      \"selector\": \"$[?($[0, '0']!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, combined, less-or-equal\",\n      \"selector\": \"$[?($[0, '0']<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, wildcard, equal\",\n      \"selector\": \"$[?($.*==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, wildcard, not equal\",\n      \"selector\": \"$[?($.*!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, wildcard, less-or-equal\",\n      \"selector\": \"$[?($.*<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, slice, equal\",\n      \"selector\": \"$[?($[0:0]==42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, slice, not equal\",\n      \"selector\": \"$[?($[0:0]!=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, absolute non-singular query, slice, less-or-equal\",\n      \"selector\": \"$[?($[0:0]<=42)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, multiple selectors\",\n      \"selector\": \"$[?@.a,?@.b]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, multiple selectors, comparison\",\n      \"selector\": \"$[?@.a=='b',?@.b=='x']\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, multiple selectors, overlapping\",\n      \"selector\": \"$[?@.a,?@.d]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, multiple selectors, filter and index\",\n      \"selector\": \"$[?@.a,1]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, multiple selectors, filter and wildcard\",\n      \"selector\": \"$[?@.a,*]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, multiple selectors, filter and slice\",\n      \"selector\": \"$[?@.a,1:]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"g\": \"h\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"g\": \"h\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, multiple selectors, comparison filter, index and slice\",\n      \"selector\": \"$[1, ?@.a=='b', 1:]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, zero and negative zero\",\n      \"selector\": \"$[?@.a==-0]\",\n      \"document\": [\n        {\n          \"a\": 0,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 0.1,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"0\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 0,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, with and without decimal fraction\",\n      \"selector\": \"$[?@.a==1.0]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 2,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"1\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, exponent\",\n      \"selector\": \"$[?@.a==1e2]\",\n      \"document\": [\n        {\n          \"a\": 100,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 100.1,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"100\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 100,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, positive exponent\",\n      \"selector\": \"$[?@.a==1e+2]\",\n      \"document\": [\n        {\n          \"a\": 100,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 100.1,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"100\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 100,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, negative exponent\",\n      \"selector\": \"$[?@.a==1e-2]\",\n      \"document\": [\n        {\n          \"a\": 0.01,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 0.02,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"0.01\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 0.01,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, decimal fraction\",\n      \"selector\": \"$[?@.a==1.1]\",\n      \"document\": [\n        {\n          \"a\": 1.1,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"1.1\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1.1,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, decimal fraction, no fractional digit\",\n      \"selector\": \"$[?@.a==1.]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"filter, equals number, decimal fraction, exponent\",\n      \"selector\": \"$[?@.a==1.1e2]\",\n      \"document\": [\n        {\n          \"a\": 110,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 110.1,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"110\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 110,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, decimal fraction, positive exponent\",\n      \"selector\": \"$[?@.a==1.1e+2]\",\n      \"document\": [\n        {\n          \"a\": 110,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 110.1,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"110\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 110,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals number, decimal fraction, negative exponent\",\n      \"selector\": \"$[?@.a==1.1e-2]\",\n      \"document\": [\n        {\n          \"a\": 0.011,\n          \"d\": \"e\"\n        },\n        {\n          \"a\": 0.012,\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"0.011\",\n          \"d\": \"g\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 0.011,\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"filter, equals, special nothing\",\n      \"selector\": \"$.values[?length(@.a) == value($..c)]\",\n      \"document\": {\n        \"c\": \"cd\",\n        \"values\": [\n          {\n            \"a\": \"ab\"\n          },\n          {\n            \"c\": \"d\"\n          },\n          {\n            \"a\": null\n          }\n        ]\n      },\n      \"result\": [\n        {\n          \"c\": \"d\"\n        },\n        {\n          \"a\": null\n        }\n      ]\n    },\n    {\n      \"name\": \"index selector, first element\",\n      \"selector\": \"$[0]\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": [\n        \"first\"\n      ]\n    },\n    {\n      \"name\": \"index selector, second element\",\n      \"selector\": \"$[1]\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": [\n        \"second\"\n      ]\n    },\n    {\n      \"name\": \"index selector, out of bound\",\n      \"selector\": \"$[2]\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"index selector, overflowing index\",\n      \"selector\": \"$[231584178474632390847141970017375815706539969331281128078915168015826259279872]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"index selector, not actually an index, overflowing index leads into general text\",\n      \"selector\": \"$[231584178474632390847141970017375815706539969331281128078915168SomeRandomText]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"index selector, negative\",\n      \"selector\": \"$[-1]\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": [\n        \"second\"\n      ]\n    },\n    {\n      \"name\": \"index selector, more negative\",\n      \"selector\": \"$[-2]\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": [\n        \"first\"\n      ]\n    },\n    {\n      \"name\": \"index selector, negative out of bound\",\n      \"selector\": \"$[-3]\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"index selector, on object\",\n      \"selector\": \"$[0]\",\n      \"document\": {\n        \"foo\": 1\n      },\n      \"result\": []\n    },\n    {\n      \"name\": \"index selector, leading 0\",\n      \"selector\": \"$[01]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"index selector, leading -0\",\n      \"selector\": \"$[-01]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes\",\n      \"selector\": \"$[\\\"a\\\"]\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, absent data\",\n      \"selector\": \"$[\\\"c\\\"]\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": []\n    },\n    {\n      \"name\": \"name selector, double quotes, array data\",\n      \"selector\": \"$[\\\"a\\\"]\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0000\",\n      \"selector\": \"$[\\\"\\u0000\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0001\",\n      \"selector\": \"$[\\\"\\u0001\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0002\",\n      \"selector\": \"$[\\\"\\u0002\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0003\",\n      \"selector\": \"$[\\\"\\u0003\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0004\",\n      \"selector\": \"$[\\\"\\u0004\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0005\",\n      \"selector\": \"$[\\\"\\u0005\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0006\",\n      \"selector\": \"$[\\\"\\u0006\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0007\",\n      \"selector\": \"$[\\\"\\u0007\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0008\",\n      \"selector\": \"$[\\\"\\b\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0009\",\n      \"selector\": \"$[\\\"\\t\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+000A\",\n      \"selector\": \"$[\\\"\\n\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+000B\",\n      \"selector\": \"$[\\\"\\u000b\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+000C\",\n      \"selector\": \"$[\\\"\\f\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+000D\",\n      \"selector\": \"$[\\\"\\r\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+000E\",\n      \"selector\": \"$[\\\"\\u000e\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+000F\",\n      \"selector\": \"$[\\\"\\u000f\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0010\",\n      \"selector\": \"$[\\\"\\u0010\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0011\",\n      \"selector\": \"$[\\\"\\u0011\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0012\",\n      \"selector\": \"$[\\\"\\u0012\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0013\",\n      \"selector\": \"$[\\\"\\u0013\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0014\",\n      \"selector\": \"$[\\\"\\u0014\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0015\",\n      \"selector\": \"$[\\\"\\u0015\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0016\",\n      \"selector\": \"$[\\\"\\u0016\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0017\",\n      \"selector\": \"$[\\\"\\u0017\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0018\",\n      \"selector\": \"$[\\\"\\u0018\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0019\",\n      \"selector\": \"$[\\\"\\u0019\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+001A\",\n      \"selector\": \"$[\\\"\\u001a\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+001B\",\n      \"selector\": \"$[\\\"\\u001b\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+001C\",\n      \"selector\": \"$[\\\"\\u001c\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+001D\",\n      \"selector\": \"$[\\\"\\u001d\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+001E\",\n      \"selector\": \"$[\\\"\\u001e\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+001F\",\n      \"selector\": \"$[\\\"\\u001f\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded U+0020\",\n      \"selector\": \"$[\\\" \\\"]\",\n      \"document\": {\n        \" \": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped double quote\",\n      \"selector\": \"$[\\\"\\\\\\\"\\\"]\",\n      \"document\": {\n        \"\\\"\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped reverse solidus\",\n      \"selector\": \"$[\\\"\\\\\\\\\\\"]\",\n      \"document\": {\n        \"\\\\\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped solidus\",\n      \"selector\": \"$[\\\"\\\\/\\\"]\",\n      \"document\": {\n        \"/\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped backspace\",\n      \"selector\": \"$[\\\"\\\\b\\\"]\",\n      \"document\": {\n        \"\\b\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped form feed\",\n      \"selector\": \"$[\\\"\\\\f\\\"]\",\n      \"document\": {\n        \"\\f\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped line feed\",\n      \"selector\": \"$[\\\"\\\\n\\\"]\",\n      \"document\": {\n        \"\\n\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped carriage return\",\n      \"selector\": \"$[\\\"\\\\r\\\"]\",\n      \"document\": {\n        \"\\r\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped tab\",\n      \"selector\": \"$[\\\"\\\\t\\\"]\",\n      \"document\": {\n        \"\\t\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped ☺, upper case hex\",\n      \"selector\": \"$[\\\"\\\\u263A\\\"]\",\n      \"document\": {\n        \"☺\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, escaped ☺, lower case hex\",\n      \"selector\": \"$[\\\"\\\\u263a\\\"]\",\n      \"document\": {\n        \"☺\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, surrogate pair 𝄞\",\n      \"selector\": \"$[\\\"\\\\uD834\\\\uDD1E\\\"]\",\n      \"document\": {\n        \"𝄞\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, surrogate pair 😀\",\n      \"selector\": \"$[\\\"\\\\uD83D\\\\uDE00\\\"]\",\n      \"document\": {\n        \"😀\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, double quotes, invalid escaped single quote\",\n      \"selector\": \"$[\\\"\\\\'\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, embedded double quote\",\n      \"selector\": \"$[\\\"\\\"\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, incomplete escape\",\n      \"selector\": \"$[\\\"\\\\\\\"]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes\",\n      \"selector\": \"$['a']\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, absent data\",\n      \"selector\": \"$['c']\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\"\n      },\n      \"result\": []\n    },\n    {\n      \"name\": \"name selector, single quotes, array data\",\n      \"selector\": \"$['a']\",\n      \"document\": [\n        \"first\",\n        \"second\"\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0000\",\n      \"selector\": \"$['\\u0000']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0001\",\n      \"selector\": \"$['\\u0001']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0002\",\n      \"selector\": \"$['\\u0002']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0003\",\n      \"selector\": \"$['\\u0003']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0004\",\n      \"selector\": \"$['\\u0004']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0005\",\n      \"selector\": \"$['\\u0005']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0006\",\n      \"selector\": \"$['\\u0006']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0007\",\n      \"selector\": \"$['\\u0007']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0008\",\n      \"selector\": \"$['\\b']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0009\",\n      \"selector\": \"$['\\t']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+000A\",\n      \"selector\": \"$['\\n']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+000B\",\n      \"selector\": \"$['\\u000b']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+000C\",\n      \"selector\": \"$['\\f']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+000D\",\n      \"selector\": \"$['\\r']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+000E\",\n      \"selector\": \"$['\\u000e']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+000F\",\n      \"selector\": \"$['\\u000f']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0010\",\n      \"selector\": \"$['\\u0010']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0011\",\n      \"selector\": \"$['\\u0011']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0012\",\n      \"selector\": \"$['\\u0012']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0013\",\n      \"selector\": \"$['\\u0013']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0014\",\n      \"selector\": \"$['\\u0014']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0015\",\n      \"selector\": \"$['\\u0015']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0016\",\n      \"selector\": \"$['\\u0016']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0017\",\n      \"selector\": \"$['\\u0017']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0018\",\n      \"selector\": \"$['\\u0018']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0019\",\n      \"selector\": \"$['\\u0019']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+001A\",\n      \"selector\": \"$['\\u001a']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+001B\",\n      \"selector\": \"$['\\u001b']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+001C\",\n      \"selector\": \"$['\\u001c']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+001D\",\n      \"selector\": \"$['\\u001d']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+001E\",\n      \"selector\": \"$['\\u001e']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+001F\",\n      \"selector\": \"$['\\u001f']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded U+0020\",\n      \"selector\": \"$[' ']\",\n      \"document\": {\n        \" \": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped single quote\",\n      \"selector\": \"$['\\\\'']\",\n      \"document\": {\n        \"'\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped reverse solidus\",\n      \"selector\": \"$['\\\\\\\\']\",\n      \"document\": {\n        \"\\\\\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped solidus\",\n      \"selector\": \"$['\\\\/']\",\n      \"document\": {\n        \"/\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped backspace\",\n      \"selector\": \"$['\\\\b']\",\n      \"document\": {\n        \"\\b\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped form feed\",\n      \"selector\": \"$['\\\\f']\",\n      \"document\": {\n        \"\\f\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped line feed\",\n      \"selector\": \"$['\\\\n']\",\n      \"document\": {\n        \"\\n\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped carriage return\",\n      \"selector\": \"$['\\\\r']\",\n      \"document\": {\n        \"\\r\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped tab\",\n      \"selector\": \"$['\\\\t']\",\n      \"document\": {\n        \"\\t\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped ☺, upper case hex\",\n      \"selector\": \"$['\\\\u263A']\",\n      \"document\": {\n        \"☺\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, escaped ☺, lower case hex\",\n      \"selector\": \"$['\\\\u263a']\",\n      \"document\": {\n        \"☺\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, surrogate pair 𝄞\",\n      \"selector\": \"$['\\\\uD834\\\\uDD1E']\",\n      \"document\": {\n        \"𝄞\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, surrogate pair 😀\",\n      \"selector\": \"$['\\\\uD83D\\\\uDE00']\",\n      \"document\": {\n        \"😀\": \"A\"\n      },\n      \"result\": [\n        \"A\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, invalid escaped double quote\",\n      \"selector\": \"$['\\\\\\\"']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, embedded single quote\",\n      \"selector\": \"$[''']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, single quotes, incomplete escape\",\n      \"selector\": \"$['\\\\']\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"name selector, double quotes, empty\",\n      \"selector\": \"$[\\\"\\\"]\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\",\n        \"\": \"C\"\n      },\n      \"result\": [\n        \"C\"\n      ]\n    },\n    {\n      \"name\": \"name selector, single quotes, empty\",\n      \"selector\": \"$['']\",\n      \"document\": {\n        \"a\": \"A\",\n        \"b\": \"B\",\n        \"\": \"C\"\n      },\n      \"result\": [\n        \"C\"\n      ]\n    },\n    {\n      \"name\": \"slice selector, slice selector\",\n      \"selector\": \"$[1:3]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        1,\n        2\n      ]\n    },\n    {\n      \"name\": \"slice selector, slice selector with step\",\n      \"selector\": \"$[1:6:2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        1,\n        3,\n        5\n      ]\n    },\n    {\n      \"name\": \"slice selector, slice selector with everything omitted, short form\",\n      \"selector\": \"$[:]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3\n      ],\n      \"result\": [\n        0,\n        1,\n        2,\n        3\n      ]\n    },\n    {\n      \"name\": \"slice selector, slice selector with everything omitted, long form\",\n      \"selector\": \"$[::]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3\n      ],\n      \"result\": [\n        0,\n        1,\n        2,\n        3\n      ]\n    },\n    {\n      \"name\": \"slice selector, slice selector with start omitted\",\n      \"selector\": \"$[:2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        0,\n        1\n      ]\n    },\n    {\n      \"name\": \"slice selector, slice selector with start and end omitted\",\n      \"selector\": \"$[::2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        0,\n        2,\n        4,\n        6,\n        8\n      ]\n    },\n    {\n      \"name\": \"slice selector, negative step with default start and end\",\n      \"selector\": \"$[::-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3\n      ],\n      \"result\": [\n        3,\n        2,\n        1,\n        0\n      ]\n    },\n    {\n      \"name\": \"slice selector, negative step with default start\",\n      \"selector\": \"$[:0:-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3\n      ],\n      \"result\": [\n        3,\n        2,\n        1\n      ]\n    },\n    {\n      \"name\": \"slice selector, negative step with default end\",\n      \"selector\": \"$[2::-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3\n      ],\n      \"result\": [\n        2,\n        1,\n        0\n      ]\n    },\n    {\n      \"name\": \"slice selector, larger negative step\",\n      \"selector\": \"$[::-2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3\n      ],\n      \"result\": [\n        3,\n        1\n      ]\n    },\n    {\n      \"name\": \"slice selector, negative range with default step\",\n      \"selector\": \"$[-1:-3]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"slice selector, negative range with negative step\",\n      \"selector\": \"$[-1:-3:-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        9,\n        8\n      ]\n    },\n    {\n      \"name\": \"slice selector, negative range with larger negative step\",\n      \"selector\": \"$[-1:-6:-2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        9,\n        7,\n        5\n      ]\n    },\n    {\n      \"name\": \"slice selector, larger negative range with larger negative step\",\n      \"selector\": \"$[-1:-7:-2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        9,\n        7,\n        5\n      ]\n    },\n    {\n      \"name\": \"slice selector, negative from, positive to\",\n      \"selector\": \"$[-5:7]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        5,\n        6\n      ]\n    },\n    {\n      \"name\": \"slice selector, negative from\",\n      \"selector\": \"$[-2:]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        8,\n        9\n      ]\n    },\n    {\n      \"name\": \"slice selector, positive from, negative to\",\n      \"selector\": \"$[1:-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8\n      ]\n    },\n    {\n      \"name\": \"slice selector, negative from, positive to, negative step\",\n      \"selector\": \"$[-1:1:-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        9,\n        8,\n        7,\n        6,\n        5,\n        4,\n        3,\n        2\n      ]\n    },\n    {\n      \"name\": \"slice selector, positive from, negative to, negative step\",\n      \"selector\": \"$[7:-5:-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        7,\n        6\n      ]\n    },\n    {\n      \"name\": \"slice selector, too many colons\",\n      \"selector\": \"$[1:2:3:4]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"slice selector, non-integer array index\",\n      \"selector\": \"$[1:2:a]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"slice selector, zero step\",\n      \"selector\": \"$[1:2:0]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"slice selector, empty range\",\n      \"selector\": \"$[2:2]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"slice selector, slice selector with everything omitted with empty array\",\n      \"selector\": \"$[:]\",\n      \"document\": [],\n      \"result\": []\n    },\n    {\n      \"name\": \"slice selector, negative step with empty array\",\n      \"selector\": \"$[::-1]\",\n      \"document\": [],\n      \"result\": []\n    },\n    {\n      \"name\": \"slice selector, maximal range with positive step\",\n      \"selector\": \"$[0:10]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ]\n    },\n    {\n      \"name\": \"slice selector, maximal range with negative step\",\n      \"selector\": \"$[9:0:-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        9,\n        8,\n        7,\n        6,\n        5,\n        4,\n        3,\n        2,\n        1\n      ]\n    },\n    {\n      \"name\": \"slice selector, excessively large to value\",\n      \"selector\": \"$[2:113667776004]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ]\n    },\n    {\n      \"name\": \"slice selector, excessively small from value\",\n      \"selector\": \"$[-113667776004:1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        0\n      ]\n    },\n    {\n      \"name\": \"slice selector, excessively large from value with negative step\",\n      \"selector\": \"$[113667776004:0:-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        9,\n        8,\n        7,\n        6,\n        5,\n        4,\n        3,\n        2,\n        1\n      ]\n    },\n    {\n      \"name\": \"slice selector, excessively small to value with negative step\",\n      \"selector\": \"$[3:-113667776004:-1]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        3,\n        2,\n        1,\n        0\n      ]\n    },\n    {\n      \"name\": \"slice selector, excessively large step\",\n      \"selector\": \"$[1:10:113667776004]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        1\n      ]\n    },\n    {\n      \"name\": \"slice selector, excessively small step\",\n      \"selector\": \"$[-1:-10:-113667776004]\",\n      \"document\": [\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9\n      ],\n      \"result\": [\n        9\n      ]\n    },\n    {\n      \"name\": \"slice selector, overflowing to value\",\n      \"selector\": \"$[2:231584178474632390847141970017375815706539969331281128078915168015826259279872]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"slice selector, underflowing from value\",\n      \"selector\": \"$[-231584178474632390847141970017375815706539969331281128078915168015826259279872:1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"slice selector, overflowing from value with negative step\",\n      \"selector\": \"$[231584178474632390847141970017375815706539969331281128078915168015826259279872:0:-1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"slice selector, underflowing to value with negative step\",\n      \"selector\": \"$[3:-231584178474632390847141970017375815706539969331281128078915168015826259279872:-1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"slice selector, overflowing step\",\n      \"selector\": \"$[1:10:231584178474632390847141970017375815706539969331281128078915168015826259279872]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"slice selector, underflowing step\",\n      \"selector\": \"$[-1:-10:-231584178474632390847141970017375815706539969331281128078915168015826259279872]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, count, count function\",\n      \"selector\": \"$[?count(@..*)>2]\",\n      \"document\": [\n        {\n          \"a\": [\n            1,\n            2,\n            3\n          ]\n        },\n        {\n          \"a\": [\n            1\n          ],\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": [\n            1,\n            2,\n            3\n          ]\n        },\n        {\n          \"a\": [\n            1\n          ],\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, count, single-node arg\",\n      \"selector\": \"$[?count(@.a)>1]\",\n      \"document\": [\n        {\n          \"a\": [\n            1,\n            2,\n            3\n          ]\n        },\n        {\n          \"a\": [\n            1\n          ],\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, count, multiple-selector arg\",\n      \"selector\": \"$[?count(@['a','d'])>1]\",\n      \"document\": [\n        {\n          \"a\": [\n            1,\n            2,\n            3\n          ]\n        },\n        {\n          \"a\": [\n            1\n          ],\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": [\n            1\n          ],\n          \"d\": \"f\"\n        },\n        {\n          \"a\": 1,\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, count, non-query arg, number\",\n      \"selector\": \"$[?count(1)>2]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, count, non-query arg, string\",\n      \"selector\": \"$[?count('string')>2]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, count, non-query arg, true\",\n      \"selector\": \"$[?count(true)>2]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, count, non-query arg, false\",\n      \"selector\": \"$[?count(false)>2]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, count, non-query arg, null\",\n      \"selector\": \"$[?count(null)>2]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, count, result must be compared\",\n      \"selector\": \"$[?count(@..*)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, count, no params\",\n      \"selector\": \"$[?count()==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, count, too many params\",\n      \"selector\": \"$[?count(@.a,@.b)==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, length, string data\",\n      \"selector\": \"$[?length(@.a)>=2]\",\n      \"document\": [\n        {\n          \"a\": \"ab\"\n        },\n        {\n          \"a\": \"d\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"ab\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, length, string data, unicode\",\n      \"selector\": \"$[?length(@)==2]\",\n      \"document\": [\n        \"☺\",\n        \"☺☺\",\n        \"☺☺☺\",\n        \"ж\",\n        \"жж\",\n        \"жжж\",\n        \"磨\",\n        \"阿美\",\n        \"形声字\"\n      ],\n      \"result\": [\n        \"☺☺\",\n        \"жж\",\n        \"阿美\"\n      ]\n    },\n    {\n      \"name\": \"functions, length, array data\",\n      \"selector\": \"$[?length(@.a)>=2]\",\n      \"document\": [\n        {\n          \"a\": [\n            1,\n            2,\n            3\n          ]\n        },\n        {\n          \"a\": [\n            1\n          ]\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": [\n            1,\n            2,\n            3\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, length, missing data\",\n      \"selector\": \"$[?length(@.a)>=2]\",\n      \"document\": [\n        {\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, length, number arg\",\n      \"selector\": \"$[?length(1)>=2]\",\n      \"document\": [\n        {\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, length, true arg\",\n      \"selector\": \"$[?length(true)>=2]\",\n      \"document\": [\n        {\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, length, false arg\",\n      \"selector\": \"$[?length(false)>=2]\",\n      \"document\": [\n        {\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, length, null arg\",\n      \"selector\": \"$[?length(null)>=2]\",\n      \"document\": [\n        {\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, length, result must be compared\",\n      \"selector\": \"$[?length(@.a)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, length, no params\",\n      \"selector\": \"$[?length()==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, length, too many params\",\n      \"selector\": \"$[?length(@.a,@.b)==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, length, non-singular query arg\",\n      \"selector\": \"$[?length(@.*)<3]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, length, arg is a function expression\",\n      \"selector\": \"$.values[?length(@.a)==length(value($..c))]\",\n      \"document\": {\n        \"c\": \"cd\",\n        \"values\": [\n          {\n            \"a\": \"ab\"\n          },\n          {\n            \"a\": \"d\"\n          }\n        ]\n      },\n      \"result\": [\n        {\n          \"a\": \"ab\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, length, arg is special nothing\",\n      \"selector\": \"$[?length(value(@.a))>0]\",\n      \"document\": [\n        {\n          \"a\": \"ab\"\n        },\n        {\n          \"c\": \"d\"\n        },\n        {\n          \"a\": null\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"ab\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, match, found match\",\n      \"selector\": \"$[?match(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"ab\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"ab\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, match, double quotes\",\n      \"selector\": \"$[?match(@.a, \\\"a.*\\\")]\",\n      \"document\": [\n        {\n          \"a\": \"ab\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"ab\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, match, regex from the document\",\n      \"selector\": \"$.values[?match(@, $.regex)]\",\n      \"document\": {\n        \"regex\": \"b.?b\",\n        \"values\": [\n          \"abc\",\n          \"bcd\",\n          \"bab\",\n          \"bba\",\n          \"bbab\",\n          \"b\",\n          true,\n          [],\n          {}\n        ]\n      },\n      \"result\": [\n        \"bab\"\n      ]\n    },\n    {\n      \"name\": \"functions, match, don't select match\",\n      \"selector\": \"$[?!match(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"ab\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, match, not a match\",\n      \"selector\": \"$[?match(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"bc\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, match, select non-match\",\n      \"selector\": \"$[?!match(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"bc\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"bc\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, match, non-string first arg\",\n      \"selector\": \"$[?match(1, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"bc\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, match, non-string second arg\",\n      \"selector\": \"$[?match(@.a, 1)]\",\n      \"document\": [\n        {\n          \"a\": \"bc\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, match, filter, match function, unicode char class, uppercase\",\n      \"selector\": \"$[?match(@, '\\\\\\\\p{Lu}')]\",\n      \"document\": [\n        \"ж\",\n        \"Ж\",\n        \"1\",\n        \"жЖ\",\n        true,\n        [],\n        {}\n      ],\n      \"result\": [\n        \"Ж\"\n      ]\n    },\n    {\n      \"name\": \"functions, match, filter, match function, unicode char class negated, uppercase\",\n      \"selector\": \"$[?match(@, '\\\\\\\\P{Lu}')]\",\n      \"document\": [\n        \"ж\",\n        \"Ж\",\n        \"1\",\n        true,\n        [],\n        {}\n      ],\n      \"result\": [\n        \"ж\",\n        \"1\"\n      ]\n    },\n    {\n      \"name\": \"functions, match, filter, match function, unicode, surrogate pair\",\n      \"selector\": \"$[?match(@, 'a.b')]\",\n      \"document\": [\n        \"a𐄁b\",\n        \"ab\",\n        \"1\",\n        true,\n        [],\n        {}\n      ],\n      \"result\": [\n        \"a𐄁b\"\n      ]\n    },\n    {\n      \"name\": \"functions, match, result cannot be compared\",\n      \"selector\": \"$[?match(@.a, 'a.*')==true]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, match, too few params\",\n      \"selector\": \"$[?match(@.a)==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, match, too many params\",\n      \"selector\": \"$[?match(@.a,@.b,@.c)==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, match, arg is a function expression\",\n      \"selector\": \"$.values[?match(@.a, value($..['regex']))]\",\n      \"document\": {\n        \"regex\": \"a.*\",\n        \"values\": [\n          {\n            \"a\": \"ab\"\n          },\n          {\n            \"a\": \"ba\"\n          }\n        ]\n      },\n      \"result\": [\n        {\n          \"a\": \"ab\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, search, at the end\",\n      \"selector\": \"$[?search(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"the end is ab\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"the end is ab\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, search, double quotes\",\n      \"selector\": \"$[?search(@.a, \\\"a.*\\\")]\",\n      \"document\": [\n        {\n          \"a\": \"the end is ab\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"the end is ab\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, search, at the start\",\n      \"selector\": \"$[?search(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"ab is at the start\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"ab is at the start\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, search, in the middle\",\n      \"selector\": \"$[?search(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"contains two matches\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"contains two matches\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, search, regex from the document\",\n      \"selector\": \"$.values[?search(@, $.regex)]\",\n      \"document\": {\n        \"regex\": \"b.?b\",\n        \"values\": [\n          \"abc\",\n          \"bcd\",\n          \"bab\",\n          \"bba\",\n          \"bbab\",\n          \"b\",\n          true,\n          [],\n          {}\n        ]\n      },\n      \"result\": [\n        \"bab\",\n        \"bba\",\n        \"bbab\"\n      ]\n    },\n    {\n      \"name\": \"functions, search, don't select match\",\n      \"selector\": \"$[?!search(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"contains two matches\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, search, not a match\",\n      \"selector\": \"$[?search(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"bc\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, search, select non-match\",\n      \"selector\": \"$[?!search(@.a, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"bc\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"bc\"\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, search, non-string first arg\",\n      \"selector\": \"$[?search(1, 'a.*')]\",\n      \"document\": [\n        {\n          \"a\": \"bc\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, search, non-string second arg\",\n      \"selector\": \"$[?search(@.a, 1)]\",\n      \"document\": [\n        {\n          \"a\": \"bc\"\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, search, filter, search function, unicode char class, uppercase\",\n      \"selector\": \"$[?search(@, '\\\\\\\\p{Lu}')]\",\n      \"document\": [\n        \"ж\",\n        \"Ж\",\n        \"1\",\n        \"жЖ\",\n        true,\n        [],\n        {}\n      ],\n      \"result\": [\n        \"Ж\",\n        \"жЖ\"\n      ]\n    },\n    {\n      \"name\": \"functions, search, filter, search function, unicode char class negated, uppercase\",\n      \"selector\": \"$[?search(@, '\\\\\\\\P{Lu}')]\",\n      \"document\": [\n        \"ж\",\n        \"Ж\",\n        \"1\",\n        true,\n        [],\n        {}\n      ],\n      \"result\": [\n        \"ж\",\n        \"1\"\n      ]\n    },\n    {\n      \"name\": \"functions, search, filter, search function, unicode, surrogate pair\",\n      \"selector\": \"$[?search(@, 'a.b')]\",\n      \"document\": [\n        \"a𐄁bc\",\n        \"abc\",\n        \"1\",\n        true,\n        [],\n        {}\n      ],\n      \"result\": [\n        \"a𐄁bc\"\n      ]\n    },\n    {\n      \"name\": \"functions, search, result cannot be compared\",\n      \"selector\": \"$[?search(@.a, 'a.*')==true]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, search, too few params\",\n      \"selector\": \"$[?search(@.a)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, search, too many params\",\n      \"selector\": \"$[?search(@.a,@.b,@.c)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, search, arg is a function expression\",\n      \"selector\": \"$.values[?search(@, value($..['regex']))]\",\n      \"document\": {\n        \"regex\": \"b.?b\",\n        \"values\": [\n          \"abc\",\n          \"bcd\",\n          \"bab\",\n          \"bba\",\n          \"bbab\",\n          \"b\",\n          true,\n          [],\n          {}\n        ]\n      },\n      \"result\": [\n        \"bab\",\n        \"bba\",\n        \"bbab\"\n      ]\n    },\n    {\n      \"name\": \"functions, value, single-value nodelist\",\n      \"selector\": \"$[?value(@.*)==4]\",\n      \"document\": [\n        [\n          4\n        ],\n        {\n          \"foo\": 4\n        },\n        [\n          5\n        ],\n        {\n          \"foo\": 5\n        },\n        4\n      ],\n      \"result\": [\n        [\n          4\n        ],\n        {\n          \"foo\": 4\n        }\n      ]\n    },\n    {\n      \"name\": \"functions, value, multi-value nodelist\",\n      \"selector\": \"$[?value(@.*)==4]\",\n      \"document\": [\n        [\n          4,\n          4\n        ],\n        {\n          \"foo\": 4,\n          \"bar\": 4\n        }\n      ],\n      \"result\": []\n    },\n    {\n      \"name\": \"functions, value, too few params\",\n      \"selector\": \"$[?value()==4]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, value, too many params\",\n      \"selector\": \"$[?value(@.a,@.b)==4]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"functions, value, result must be compared\",\n      \"selector\": \"$[?value(@.a)]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, filter, space between question mark and expression\",\n      \"selector\": \"$[? @.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, newline between question mark and expression\",\n      \"selector\": \"$[?\\n@.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, tab between question mark and expression\",\n      \"selector\": \"$[?\\t@.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, return between question mark and expression\",\n      \"selector\": \"$[?\\r@.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, space between question mark and parenthesized expression\",\n      \"selector\": \"$[? (@.a)]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, newline between question mark and parenthesized expression\",\n      \"selector\": \"$[?\\n(@.a)]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, tab between question mark and parenthesized expression\",\n      \"selector\": \"$[?\\t(@.a)]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, return between question mark and parenthesized expression\",\n      \"selector\": \"$[?\\r(@.a)]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, space between parenthesized expression and bracket\",\n      \"selector\": \"$[?(@.a) ]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, newline between parenthesized expression and bracket\",\n      \"selector\": \"$[?(@.a)\\n]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, tab between parenthesized expression and bracket\",\n      \"selector\": \"$[?(@.a)\\t]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, return between parenthesized expression and bracket\",\n      \"selector\": \"$[?(@.a)\\r]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, space between bracket and question mark\",\n      \"selector\": \"$[ ?@.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, newline between bracket and question mark\",\n      \"selector\": \"$[\\n?@.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, tab between bracket and question mark\",\n      \"selector\": \"$[\\t?@.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, filter, return between bracket and question mark\",\n      \"selector\": \"$[\\r?@.a]\",\n      \"document\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        },\n        {\n          \"b\": \"c\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"b\",\n          \"d\": \"e\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, space between function name and parenthesis\",\n      \"selector\": \"$[?count (@.*)==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, functions, newline between function name and parenthesis\",\n      \"selector\": \"$[?count\\n(@.*)==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, functions, tab between function name and parenthesis\",\n      \"selector\": \"$[?count\\t(@.*)==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, functions, return between function name and parenthesis\",\n      \"selector\": \"$[?count\\r(@.*)==1]\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, functions, space between parenthesis and arg\",\n      \"selector\": \"$[?count( @.*)==1]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, newline between parenthesis and arg\",\n      \"selector\": \"$[?count(\\n@.*)==1]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, tab between parenthesis and arg\",\n      \"selector\": \"$[?count(\\t@.*)==1]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, return between parenthesis and arg\",\n      \"selector\": \"$[?count(\\r@.*)==1]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, space between arg and comma\",\n      \"selector\": \"$[?search(@ ,'[a-z]+')]\",\n      \"document\": [\n        \"foo\",\n        \"123\"\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, newline between arg and comma\",\n      \"selector\": \"$[?search(@\\n,'[a-z]+')]\",\n      \"document\": [\n        \"foo\",\n        \"123\"\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, tab between arg and comma\",\n      \"selector\": \"$[?search(@\\t,'[a-z]+')]\",\n      \"document\": [\n        \"foo\",\n        \"123\"\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, return between arg and comma\",\n      \"selector\": \"$[?search(@\\r,'[a-z]+')]\",\n      \"document\": [\n        \"foo\",\n        \"123\"\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, space between comma and arg\",\n      \"selector\": \"$[?search(@, '[a-z]+')]\",\n      \"document\": [\n        \"foo\",\n        \"123\"\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, newline between comma and arg\",\n      \"selector\": \"$[?search(@,\\n'[a-z]+')]\",\n      \"document\": [\n        \"foo\",\n        \"123\"\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, tab between comma and arg\",\n      \"selector\": \"$[?search(@,\\t'[a-z]+')]\",\n      \"document\": [\n        \"foo\",\n        \"123\"\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, return between comma and arg\",\n      \"selector\": \"$[?search(@,\\r'[a-z]+')]\",\n      \"document\": [\n        \"foo\",\n        \"123\"\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, space between arg and parenthesis\",\n      \"selector\": \"$[?count(@.* )==1]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, newline between arg and parenthesis\",\n      \"selector\": \"$[?count(@.*\\n)==1]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, tab between arg and parenthesis\",\n      \"selector\": \"$[?count(@.*\\t)==1]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, return between arg and parenthesis\",\n      \"selector\": \"$[?count(@.*\\r)==1]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, spaces in a relative singular selector\",\n      \"selector\": \"$[?length(@ .a .b) == 3]\",\n      \"document\": [\n        {\n          \"a\": {\n            \"b\": \"foo\"\n          }\n        },\n        {}\n      ],\n      \"result\": [\n        {\n          \"a\": {\n            \"b\": \"foo\"\n          }\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, newlines in a relative singular selector\",\n      \"selector\": \"$[?length(@\\n.a\\n.b) == 3]\",\n      \"document\": [\n        {\n          \"a\": {\n            \"b\": \"foo\"\n          }\n        },\n        {}\n      ],\n      \"result\": [\n        {\n          \"a\": {\n            \"b\": \"foo\"\n          }\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, tabs in a relative singular selector\",\n      \"selector\": \"$[?length(@\\t.a\\t.b) == 3]\",\n      \"document\": [\n        {\n          \"a\": {\n            \"b\": \"foo\"\n          }\n        },\n        {}\n      ],\n      \"result\": [\n        {\n          \"a\": {\n            \"b\": \"foo\"\n          }\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, returns in a relative singular selector\",\n      \"selector\": \"$[?length(@\\r.a\\r.b) == 3]\",\n      \"document\": [\n        {\n          \"a\": {\n            \"b\": \"foo\"\n          }\n        },\n        {}\n      ],\n      \"result\": [\n        {\n          \"a\": {\n            \"b\": \"foo\"\n          }\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, spaces in an absolute singular selector\",\n      \"selector\": \"$..[?length(@)==length($ [0] .a)]\",\n      \"document\": [\n        {\n          \"a\": \"foo\"\n        },\n        {}\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, newlines in an absolute singular selector\",\n      \"selector\": \"$..[?length(@)==length($\\n[0]\\n.a)]\",\n      \"document\": [\n        {\n          \"a\": \"foo\"\n        },\n        {}\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, tabs in an absolute singular selector\",\n      \"selector\": \"$..[?length(@)==length($\\t[0]\\t.a)]\",\n      \"document\": [\n        {\n          \"a\": \"foo\"\n        },\n        {}\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, functions, returns in an absolute singular selector\",\n      \"selector\": \"$..[?length(@)==length($\\r[0]\\r.a)]\",\n      \"document\": [\n        {\n          \"a\": \"foo\"\n        },\n        {}\n      ],\n      \"result\": [\n        \"foo\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space before ||\",\n      \"selector\": \"$[?@.a ||@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"c\": 3\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline before ||\",\n      \"selector\": \"$[?@.a\\n||@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"c\": 3\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab before ||\",\n      \"selector\": \"$[?@.a\\t||@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"c\": 3\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return before ||\",\n      \"selector\": \"$[?@.a\\r||@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"c\": 3\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space after ||\",\n      \"selector\": \"$[?@.a|| @.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"c\": 3\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline after ||\",\n      \"selector\": \"$[?@.a||\\n@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"c\": 3\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab after ||\",\n      \"selector\": \"$[?@.a||\\t@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"c\": 3\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return after ||\",\n      \"selector\": \"$[?@.a||\\r@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"c\": 3\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space before &&\",\n      \"selector\": \"$[?@.a &&@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline before &&\",\n      \"selector\": \"$[?@.a\\n&&@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab before &&\",\n      \"selector\": \"$[?@.a\\t&&@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return before &&\",\n      \"selector\": \"$[?@.a\\r&&@.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space after &&\",\n      \"selector\": \"$[?@.a&& @.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline after &&\",\n      \"selector\": \"$[?@.a&& @.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab after &&\",\n      \"selector\": \"$[?@.a&& @.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return after &&\",\n      \"selector\": \"$[?@.a&& @.b]\",\n      \"document\": [\n        {\n          \"a\": 1\n        },\n        {\n          \"b\": 2\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space before ==\",\n      \"selector\": \"$[?@.a ==@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline before ==\",\n      \"selector\": \"$[?@.a\\n==@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab before ==\",\n      \"selector\": \"$[?@.a\\t==@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return before ==\",\n      \"selector\": \"$[?@.a\\r==@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space after ==\",\n      \"selector\": \"$[?@.a== @.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline after ==\",\n      \"selector\": \"$[?@.a==\\n@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab after ==\",\n      \"selector\": \"$[?@.a==\\t@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return after ==\",\n      \"selector\": \"$[?@.a==\\r@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space before !=\",\n      \"selector\": \"$[?@.a !=@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline before !=\",\n      \"selector\": \"$[?@.a\\n!=@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab before !=\",\n      \"selector\": \"$[?@.a\\t!=@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return before !=\",\n      \"selector\": \"$[?@.a\\r!=@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space after !=\",\n      \"selector\": \"$[?@.a!= @.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline after !=\",\n      \"selector\": \"$[?@.a!=\\n@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab after !=\",\n      \"selector\": \"$[?@.a!=\\t@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return after !=\",\n      \"selector\": \"$[?@.a!=\\r@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space before <\",\n      \"selector\": \"$[?@.a <@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline before <\",\n      \"selector\": \"$[?@.a\\n<@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab before <\",\n      \"selector\": \"$[?@.a\\t<@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return before <\",\n      \"selector\": \"$[?@.a\\r<@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space after <\",\n      \"selector\": \"$[?@.a< @.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline after <\",\n      \"selector\": \"$[?@.a<\\n@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab after <\",\n      \"selector\": \"$[?@.a<\\t@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return after <\",\n      \"selector\": \"$[?@.a<\\r@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space before >\",\n      \"selector\": \"$[?@.b >@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline before >\",\n      \"selector\": \"$[?@.b\\n>@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab before >\",\n      \"selector\": \"$[?@.b\\t>@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return before >\",\n      \"selector\": \"$[?@.b\\r>@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space after >\",\n      \"selector\": \"$[?@.b> @.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline after >\",\n      \"selector\": \"$[?@.b>\\n@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab after >\",\n      \"selector\": \"$[?@.b>\\t@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return after >\",\n      \"selector\": \"$[?@.b>\\r@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space before <=\",\n      \"selector\": \"$[?@.a <=@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline before <=\",\n      \"selector\": \"$[?@.a\\n<=@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab before <=\",\n      \"selector\": \"$[?@.a\\t<=@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return before <=\",\n      \"selector\": \"$[?@.a\\r<=@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space after <=\",\n      \"selector\": \"$[?@.a<= @.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline after <=\",\n      \"selector\": \"$[?@.a<=\\n@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab after <=\",\n      \"selector\": \"$[?@.a<=\\t@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return after <=\",\n      \"selector\": \"$[?@.a<=\\r@.b]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space before >=\",\n      \"selector\": \"$[?@.b >=@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline before >=\",\n      \"selector\": \"$[?@.b\\n>=@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab before >=\",\n      \"selector\": \"$[?@.b\\t>=@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return before >=\",\n      \"selector\": \"$[?@.b\\r>=@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space after >=\",\n      \"selector\": \"$[?@.b>= @.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline after >=\",\n      \"selector\": \"$[?@.b>=\\n@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab after >=\",\n      \"selector\": \"$[?@.b>=\\t@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return after >=\",\n      \"selector\": \"$[?@.b>=\\r@.a]\",\n      \"document\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        },\n        {\n          \"a\": 2,\n          \"b\": 1\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": 1,\n          \"b\": 1\n        },\n        {\n          \"a\": 1,\n          \"b\": 2\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space between logical not and test expression\",\n      \"selector\": \"$[?! @.a]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline between logical not and test expression\",\n      \"selector\": \"$[?!\\n@.a]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab between logical not and test expression\",\n      \"selector\": \"$[?!\\t@.a]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return between logical not and test expression\",\n      \"selector\": \"$[?!\\r@.a]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, space between logical not and parenthesized expression\",\n      \"selector\": \"$[?! (@.a=='b')]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, newline between logical not and parenthesized expression\",\n      \"selector\": \"$[?!\\n(@.a=='b')]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, tab between logical not and parenthesized expression\",\n      \"selector\": \"$[?!\\t(@.a=='b')]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, operators, return between logical not and parenthesized expression\",\n      \"selector\": \"$[?!\\r(@.a=='b')]\",\n      \"document\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"b\",\n          \"d\": \"f\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ],\n      \"result\": [\n        {\n          \"a\": \"a\",\n          \"d\": \"e\"\n        },\n        {\n          \"a\": \"d\",\n          \"d\": \"f\"\n        }\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, space between root and bracket\",\n      \"selector\": \"$ ['a']\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between root and bracket\",\n      \"selector\": \"$\\n['a']\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between root and bracket\",\n      \"selector\": \"$\\t['a']\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, return between root and bracket\",\n      \"selector\": \"$\\r['a']\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, space between bracket and bracket\",\n      \"selector\": \"$['a'] ['b']\",\n      \"document\": {\n        \"a\": {\n          \"b\": \"ab\"\n        }\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between root and bracket\",\n      \"selector\": \"$['a'] \\n['b']\",\n      \"document\": {\n        \"a\": {\n          \"b\": \"ab\"\n        }\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between root and bracket\",\n      \"selector\": \"$['a'] \\t['b']\",\n      \"document\": {\n        \"a\": {\n          \"b\": \"ab\"\n        }\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, return between root and bracket\",\n      \"selector\": \"$['a'] \\r['b']\",\n      \"document\": {\n        \"a\": {\n          \"b\": \"ab\"\n        }\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, space between root and dot\",\n      \"selector\": \"$ .a\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between root and dot\",\n      \"selector\": \"$\\n.a\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between root and dot\",\n      \"selector\": \"$\\t.a\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, return between root and dot\",\n      \"selector\": \"$\\r.a\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, space between dot and name\",\n      \"selector\": \"$. a\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between dot and name\",\n      \"selector\": \"$.\\na\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between dot and name\",\n      \"selector\": \"$.\\ta\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, selectors, return between dot and name\",\n      \"selector\": \"$.\\ra\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, selectors, space between recursive descent and name\",\n      \"selector\": \"$.. a\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between recursive descent and name\",\n      \"selector\": \"$..\\na\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between recursive descent and name\",\n      \"selector\": \"$..\\ta\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, selectors, return between recursive descent and name\",\n      \"selector\": \"$..\\ra\",\n      \"invalid_selector\": true\n    },\n    {\n      \"name\": \"whitespace, selectors, space between bracket and selector\",\n      \"selector\": \"$[ 'a']\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between bracket and selector\",\n      \"selector\": \"$[\\n'a']\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between bracket and selector\",\n      \"selector\": \"$[\\t'a']\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, return between bracket and selector\",\n      \"selector\": \"$[\\r'a']\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, space between selector and bracket\",\n      \"selector\": \"$['a' ]\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between selector and bracket\",\n      \"selector\": \"$['a'\\n]\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between selector and bracket\",\n      \"selector\": \"$['a'\\t]\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, return between selector and bracket\",\n      \"selector\": \"$['a'\\r]\",\n      \"document\": {\n        \"a\": \"ab\"\n      },\n      \"result\": [\n        \"ab\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, space between selector and comma\",\n      \"selector\": \"$['a' ,'b']\",\n      \"document\": {\n        \"a\": \"ab\",\n        \"b\": \"bc\"\n      },\n      \"result\": [\n        \"ab\",\n        \"bc\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between selector and comma\",\n      \"selector\": \"$['a'\\n,'b']\",\n      \"document\": {\n        \"a\": \"ab\",\n        \"b\": \"bc\"\n      },\n      \"result\": [\n        \"ab\",\n        \"bc\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between selector and comma\",\n      \"selector\": \"$['a'\\t,'b']\",\n      \"document\": {\n        \"a\": \"ab\",\n        \"b\": \"bc\"\n      },\n      \"result\": [\n        \"ab\",\n        \"bc\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, return between selector and comma\",\n      \"selector\": \"$['a'\\r,'b']\",\n      \"document\": {\n        \"a\": \"ab\",\n        \"b\": \"bc\"\n      },\n      \"result\": [\n        \"ab\",\n        \"bc\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, space between comma and selector\",\n      \"selector\": \"$['a', 'b']\",\n      \"document\": {\n        \"a\": \"ab\",\n        \"b\": \"bc\"\n      },\n      \"result\": [\n        \"ab\",\n        \"bc\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, newline between comma and selector\",\n      \"selector\": \"$['a',\\n'b']\",\n      \"document\": {\n        \"a\": \"ab\",\n        \"b\": \"bc\"\n      },\n      \"result\": [\n        \"ab\",\n        \"bc\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, tab between comma and selector\",\n      \"selector\": \"$['a',\\t'b']\",\n      \"document\": {\n        \"a\": \"ab\",\n        \"b\": \"bc\"\n      },\n      \"result\": [\n        \"ab\",\n        \"bc\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, selectors, return between comma and selector\",\n      \"selector\": \"$['a',\\r'b']\",\n      \"document\": {\n        \"a\": \"ab\",\n        \"b\": \"bc\"\n      },\n      \"result\": [\n        \"ab\",\n        \"bc\"\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, space between start and colon\",\n      \"selector\": \"$[1 :5:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, newline between start and colon\",\n      \"selector\": \"$[1\\n:5:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, tab between start and colon\",\n      \"selector\": \"$[1\\t:5:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, return between start and colon\",\n      \"selector\": \"$[1\\r:5:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, space between colon and end\",\n      \"selector\": \"$[1: 5:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, newline between colon and end\",\n      \"selector\": \"$[1:\\n5:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, tab between colon and end\",\n      \"selector\": \"$[1:\\t5:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, return between colon and end\",\n      \"selector\": \"$[1:\\r5:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, space between end and colon\",\n      \"selector\": \"$[1:5 :2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, newline between end and colon\",\n      \"selector\": \"$[1:5\\n:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, tab between end and colon\",\n      \"selector\": \"$[1:5\\t:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, return between end and colon\",\n      \"selector\": \"$[1:5\\r:2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, space between colon and step\",\n      \"selector\": \"$[1:5: 2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, newline between colon and step\",\n      \"selector\": \"$[1:5:\\n2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, tab between colon and step\",\n      \"selector\": \"$[1:5:\\t2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    },\n    {\n      \"name\": \"whitespace, slice, return between colon and step\",\n      \"selector\": \"$[1:5:\\r2]\",\n      \"document\": [\n        1,\n        2,\n        3,\n        4,\n        5,\n        6\n      ],\n      \"result\": [\n        2,\n        4\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "tests/fixtures/optimizer/annotate_functions.sql",
    "content": "--------------------------------------\n-- Dialect\n--------------------------------------\nABS(1);\nINT;\n\nABS(1.5);\nDOUBLE;\n\nGREATEST(1, 2, 3);\nINT;\n\nGREATEST(1, 2.5, 3);\nDOUBLE;\n\nLEAST(1, 2, 3);\nINT;\n\nLEAST(1, 2.5, 3);\nDOUBLE;\n\nCURRENT_TIME();\nTIME;\n\nLOCALTIME();\nTIME;\n\nTIME_ADD(CAST('09:05:03' AS TIME), INTERVAL 2 HOUR);\nTIME;\n\nTIME_SUB(CAST('09:05:03' AS TIME), INTERVAL 2 HOUR);\nTIME;\n\nSORT_ARRAY(ARRAY(tbl.str_col));\nARRAY<STRING>;\n\nSORT_ARRAY(ARRAY(tbl.double_col));\nARRAY<DOUBLE>;\n\nSORT_ARRAY(ARRAY(tbl.bigint_col));\nARRAY<BIGINT>;\n\ntbl.bigint || tbl.str_col;\nVARCHAR;\n\ntbl.str_col || tbl.bigint;\nVARCHAR;\n\nARRAY_REVERSE(['a', 'b']);\nARRAY<VARCHAR>;\n\nARRAY_REVERSE([1, 1.5]);\nARRAY<DOUBLE>;\n\nARRAY_SLICE([1, 1.5], 1, 2);\nARRAY<DOUBLE>;\n\nFROM_BASE32(tbl.str_col);\nBINARY;\n\nFROM_BASE64(tbl.str_col);\nBINARY;\n\nANY_VALUE(tbl.bool_col);\nBOOLEAN;\n\nANY_VALUE(tbl.bigint_col);\nBIGINT;\n\nANY_VALUE(tbl.date_col);\nDATE;\n\nANY_VALUE(tbl.str_col);\nSTRING;\n\nANY_VALUE(tbl.array_col);\nARRAY<STRING>;\n\nCURRENT_SCHEMA();\nVARCHAR;\n\nMONTHNAME(tbl.date_col);\nVARCHAR;\n\nCHR(65);\nVARCHAR;\n\nCOUNTIF(tbl.bigint_col > 1);\nBIGINT;\n\nLAST_VALUE(tbl.bigint_col) OVER (ORDER BY tbl.bigint_col);\nBIGINT;\n\nTO_BASE32(tbl.bytes_col);\nVARCHAR;\n\nTO_BASE64(tbl.bytes_col);\nVARCHAR;\n\nUNIX_SECONDS(tbl.timestamp_col);\nBIGINT;\n\nSTARTS_WITH(tbl.str_col, prefix);\nBOOLEAN;\n\nENDS_WITH(tbl.str_col, suffix);\nBOOLEAN;\n\nASCII('A');\nINT;\n\nUNICODE('bcd');\nINT;\n\nLAST_DAY(tbl.timestamp_col);\nDATE;\n\n# dialect: snowflake\nNEXT_DAY(tbl.date_col, 'MONDAY');\nDATE;\n\nJUSTIFY_DAYS(INTERVAL '1' DAY);\nINTERVAL;\n\nJUSTIFY_HOURS(INTERVAL '1' HOUR);\nINTERVAL;\n\nJUSTIFY_INTERVAL(INTERVAL '1' HOUR);\nINTERVAL;\n\nUNIX_MICROS(CAST('2008-12-25 15:30:00+00' AS TIMESTAMP));\nBIGINT;\n\nUNIX_MILLIS(CAST('2008-12-25 15:30:00+00' AS TIMESTAMP));\nBIGINT;\n\nKURTOSIS(tbl.double_col);\nDOUBLE;\n\nKURTOSIS(tbl.int_col);\nDOUBLE;\n\nLENGTH(tbl.str_col);\nINT;\n\nLENGTH(tbl.bin_col);\nINT;\n\nDAYNAME(tbl.date_col);\nVARCHAR;\n\nCBRT(tbl.int_col);\nDOUBLE;\n\nCBRT(tbl.double_col);\nDOUBLE;\n\nISINF(tbl.float_col);\nBOOLEAN;\n\nISNAN(tbl.float_col);\nBOOLEAN;\n\nCURRENT_CATALOG();\nVARCHAR;\n\nCURRENT_USER();\nVARCHAR;\n\nSESSION_USER();\nVARCHAR;\n\nRAND();\nDOUBLE;\n\nDEGREES(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nTO_BINARY('test');\nBINARY;\n\n# dialect: snowflake\nTO_BINARY('test', 'HEX');\nBINARY;\n\nARRAY_CONTAINS(tbl.array_col, '1');\nBOOLEAN;\n\n--------------------------------------\n-- Spark2 / Spark3 / Databricks\n--------------------------------------\n\n# dialect: spark2, spark, databricks\nSUBSTRING(tbl.str_col, 0, 0);\nSTRING;\n\n# dialect: spark2, spark, databricks\nSUBSTRING(tbl.bin_col, 0, 0);\nBINARY;\n\n# dialect: spark2, spark, databricks\nCONCAT(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: spark2, spark, databricks\nCONCAT(tbl.bin_col, tbl.str_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nCONCAT(tbl.str_col, tbl.bin_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nCONCAT(tbl.str_col, tbl.str_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nCONCAT(tbl.str_col, unknown);\nSTRING;\n\n# dialect: spark2, spark, databricks\nCONCAT(tbl.bin_col, unknown);\nUNKNOWN;\n\n# dialect: spark2, spark, databricks\nCONCAT(unknown, unknown);\nUNKNOWN;\n\n# dialect: spark2, spark, databricks\nLPAD(tbl.bin_col, 1, tbl.bin_col);\nBINARY;\n\n# dialect: spark2, spark, databricks\nRPAD(tbl.bin_col, 1, tbl.bin_col);\nBINARY;\n\n# dialect: spark2, spark, databricks\nLPAD(tbl.bin_col, 1, tbl.str_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nRPAD(tbl.bin_col, 1, tbl.str_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nLPAD(tbl.str_col, 1, tbl.bin_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nRPAD(tbl.str_col, 1, tbl.bin_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nLPAD(tbl.str_col, 1, tbl.str_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nRPAD(tbl.str_col, 1, tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nIF(cond, tbl.double_col, tbl.bigint_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nIF(cond, tbl.bigint_col, tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark\nIF(cond, tbl.double_col, tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark\nIF(cond, tbl.str_col, tbl.double_col);\nSTRING;\n\n# dialect: databricks\nIF(cond, tbl.str_col, tbl.double_col);\nDOUBLE;\n\n# dialect: databricks\nIF(cond, tbl.double_col, tbl.str_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark\nIF(cond, tbl.date_col, tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark\nIF(cond, tbl.str_col, tbl.date_col);\nSTRING;\n\n# dialect: databricks\nIF(cond, tbl.date_col, tbl.str_col);\nDATE;\n\n# dialect: databricks\nIF(cond, tbl.str_col, tbl.date_col);\nDATE;\n\n# dialect: hive, spark2, spark, databricks\nIF(cond, tbl.date_col, tbl.timestamp_col);\nTIMESTAMP;\n\n# dialect: hive, spark2, spark, databricks\nIF(cond, tbl.timestamp_col, tbl.date_col);\nTIMESTAMP;\n\n# dialect: hive, spark2, spark, databricks\nIF(cond, NULL, tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nIF(cond, tbl.str_col, NULL);\nSTRING;\n\n# dialect: hive, spark2, spark\nCOALESCE(tbl.str_col, tbl.date_col, tbl.bigint_col);\nSTRING;\n\n# dialect: hive, spark2, spark\nCOALESCE(tbl.date_col, tbl.str_col, tbl.bigint_col);\nSTRING;\n\n# dialect: hive, spark2, spark\nCOALESCE(tbl.date_col, tbl.bigint_col, tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark\nCOALESCE(tbl.str_col, tbl.date_col, tbl.bigint_col);\nSTRING;\n\n# dialect: hive, spark2, spark\nCOALESCE(tbl.date_col, tbl.str_col, tbl.bigint_col);\nSTRING;\n\n# dialect: hive, spark2, spark\nCOALESCE(tbl.date_col, NULL, tbl.bigint_col, tbl.str_col);\nSTRING;\n\n# dialect: databricks\nCOALESCE(tbl.str_col, tbl.bigint_col);\nBIGINT;\n\n# dialect: databricks\nCOALESCE(tbl.bigint_col, tbl.str_col);\nBIGINT;\n\n# dialect: databricks\nCOALESCE(tbl.str_col, NULL, tbl.bigint_col);\nBIGINT;\n\n# dialect: databricks\nCOALESCE(tbl.bigint_col, NULL, tbl.str_col);\nBIGINT;\n\n# dialect: databricks\nCOALESCE(tbl.bool_col, tbl.str_col);\nBOOLEAN;\n\n# dialect: hive, spark2, spark\nCOALESCE(tbl.interval_col, tbl.str_col);\nSTRING;\n\n# dialect: databricks\nCOALESCE(tbl.interval_col, tbl.str_col);\nINTERVAL;\n\n# dialect: databricks\nCOALESCE(tbl.bin_col, tbl.str_col);\nBINARY;\n\n# dialect: spark, databricks\nLOCALTIMESTAMP();\nTIMESTAMPNTZ;\n\n# dialect: hive, spark2, spark, databricks\nENCODE(tbl.str_col, tbl.str_col);\nBINARY;\n\n# dialect: hive, spark2, spark, databricks\nENCODE(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: spark, databricks\nCURRENT_TIMEZONE();\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nUNIX_TIMESTAMP();\nBIGINT;\n\n# dialect: hive, spark2, spark, databricks\nACOS(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nACOS(tbl.double_col);\nDOUBLE;\n\n# dialect: spark2, spark, databricks\nATAN2(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: spark2, spark, databricks\nATAN2(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: spark2, spark, databricks\nATAN2(tbl.double_col, tbl.int_col);\nDOUBLE;\n\n# dialect: spark, databricks\nACOSH(tbl.double_col);\nDOUBLE;\n\n# dialect: spark, databricks\nACOSH(tbl.int_col);\nDOUBLE;\n\n# dialect: spark2, spark, databricks\nCOT(tbl.int_col);\nDOUBLE;\n\n# dialect: spark2, spark, databricks\nCOT(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCOSH(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCOSH(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nSINH(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nSINH(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nTANH(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nTANH(tbl.int_col);\nDOUBLE;\n\n# dialect: spark, databricks\nTO_BINARY(tbl.str_col, tbl.str_col);\nBINARY;\n\n# dialect: spark, databricks\nTO_BINARY(tbl.int_col, tbl.str_col);\nBINARY;\n\n# dialect: spark, databricks\nTO_BINARY(tbl.double_col, tbl.str_col);\nBINARY;\n\n# dialect: hive, spark2, spark, databricks\nSHA(tbl.str_col);\nVARCHAR;\n\n# dialect: hive, spark2, spark, databricks\nSHA1(tbl.str_col);\nVARCHAR;\n\n# dialect: hive, spark2, spark, databricks\nSHA2(tbl.str_col, tbl.int_col);\nVARCHAR;\n\n# dialect: hive, spark2, spark, databricks\nSPACE(tbl.int_col);\nVARCHAR;\n\n# dialect: spark2, spark, databricks\nRANDN();\nDOUBLE;\n\n# dialect: spark2, spark, databricks\nBIT_LENGTH(tbl.str_col);\nINT;\n\n# dialect: hive, spark2, spark, databricks\nASIN(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nASIN(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nSIN(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nSIN(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCOS(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCOS(tbl.double_col);\nDOUBLE;\n\n# dialect: spark, databricks\nASINH(tbl.int_col);\nDOUBLE;\n\n# dialect: spark, databricks\nASINH(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nATAN(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nATAN(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nTAN(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nTAN(tbl.double_col);\nDOUBLE;\n\n# dialect: spark, databricks\nATANH(tbl.double_col);\nDOUBLE;\n\n# dialect: spark, databricks\nATANH(tbl.int_col);\nDOUBLE;\n\n# dialect: spark, databricks\nSEC(tbl.int_col);\nDOUBLE;\n\n# dialect: spark, databricks\nSEC(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCORR(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCORR(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCBRT(tbl.double_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCBRT(tbl.int_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nCURRENT_CATALOG();\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nCURRENT_DATABASE();\nSTRING;\n\n# dialect: spark, databricks\nDATE_FROM_UNIX_DATE(tbl.int_col);\nDATE;\n\n# dialect: hive, spark2, spark, databricks\nMONTHS_BETWEEN(tbl.timestamp_col, tbl.timestamp_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nMONTHS_BETWEEN(tbl.timestamp_col, tbl.timestamp_col, tbl.bool_col);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nMONTH(tbl.date_col);\nINT;\n\n# dialect: spark, databricks\nMONTHNAME(tbl.date_col);\nSTRING;\n\n# dialect: hive, spark, databricks\nCURRENT_SCHEMA();\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nCURRENT_USER();\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nUNHEX(tbl.str_col);\nBINARY;\n\n# dialect: hive, spark2, spark, databricks\nHEX(tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nHEX(tbl.int_col);\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nSOUNDEX(tbl.str_col);\nSTRING;\n\n# dialect: spark, databricks\nSESSION_USER();\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nFACTORIAL(tbl.int_col);\nBIGINT;\n\n# dialect: spark, databricks\nARRAY_SIZE(tbl.array_col);\nINT;\n\n# dialect: hive, spark2, spark, databricks\nQUARTER(tbl.date_col);\nINT;\n\n# dialect: hive, spark2, spark, databricks\nSECOND(tbl.timestamp_col);\nINT;\n\n# dialect: hive, spark2, spark, databricks\nMD5(tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nHOUR(tbl.timestamp_col);\nINT;\n\n# dialect: spark, databricks\nBITMAP_COUNT(tbl.bin_col);\nBIGINT;\n\n# dialect: spark, databricks\nRANDSTR(tbl.int_col);\nSTRING;\n\n# dialect: spark, databricks\nRANDSTR(tbl.int_col, tbl.int_col);\nSTRING;\n\n# dialect: spark, databricks\nCOLLATION(tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nREPEAT(tbl.str_col, tbl.int_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nFORMAT_STRING(tbl.str_col, tbl.int_col, tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nREPLACE(tbl.str_col, tbl.str_col, tbl.str_col);\nSTRING;\n\n# dialect: spark, databricks\nOVERLAY(tbl.str_col PLACING tbl.str_col FROM tbl.int_col);\nSTRING;\n\n# dialect: spark, databricks\nOVERLAY(tbl.bin_col PLACING tbl.bin_col FROM tbl.int_col FOR tbl.int_col);\nBINARY;\n\n# dialect: spark, databricks\nUNIX_DATE(tbl.date_col);\nINT;\n\n# dialect: hive, spark2, spark, databricks\nREVERSE(tbl.str_col);\nSTRING;\n\n# dialect: hive, spark2, spark, databricks\nREVERSE(tbl.array_col);\nARRAY<STRING>;\n\n# dialect: spark2, spark, databricks\nRIGHT(tbl.str_col, tbl.int_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nNEXT_DAY(tbl.date_col, tbl.str_col);\nDATE;\n\n# dialect: hive\nNEXT_DAY(tbl.date_col, tbl.str_col);\nVARCHAR;\n\n# dialect: hive, spark2, spark, databricks\nDAYOFWEEK(tbl.date_col);\nINT;\n\n# dialect: hive, spark2, spark, databricks\nDAYOFMONTH(tbl.date_col);\nINT;\n\n# dialect: hive, spark2, spark, databricks\nTRANSLATE(tbl.str_col, tbl.str_col, tbl.str_col);\nSTRING; \n\n# dialect: spark, databricks\nARRAY_COMPACT(tbl.array_col);\nARRAY<STRING>;\n\n# dialect: spark, databricks\nARRAY_COMPACT(array(1, 2, 3));\nARRAY<INT>;\n\n# dialect: hive, spark2, spark, databricks\nSPLIT(tbl.str_col, tbl.str_col, tbl.int_col);\nARRAY<STRING>;\n\n# dialect: hive, spark2, spark, databricks\nSPLIT(tbl.str_col, tbl.str_col);\nARRAY<STRING>;\n\n# dialect: spark2, spark, databricks\nFROM_UTC_TIMESTAMP(tbl.timestamp_col, tbl.str_col);\nTIMESTAMP;\n\n# dialect: spark2, spark, databricks\nADD_MONTHS(tbl.date_col, tbl.int_col);\nDATE;\n\n# dialect: hive\nADD_MONTHS(tbl.date_col, tbl.int_col);\nSTRING;\n\n# dialect: spark2, spark, databricks\nFILTER(tbl.array_col, x -> x > 2);\nARRAY<STRING>;\n\n# dialect: spark, databricks\nARRAY_INSERT(array(1, 2, 3, 4), 5, 5);\nARRAY<INT>;\n\n# dialect: spark, databricks\nARRAY_INSERT(tbl.array_col, tbl.int_col, tbl.str_col);\nARRAY<STRING>;\n\n# dialect: hive, spark2, spark, databricks\nARRAY_INTERSECT(tbl.array_col, tbl.array_col);\nARRAY<STRING>;\n\n# dialect: hive, spark2, spark, databricks\nARRAY_INTERSECT(array(1, 2, 3), array(1, 3, 5));\nARRAY<INT>;\n\n# dialect: hive\nPERCENTILE_APPROX(3, 0.2);\nDOUBLE;\n\n# dialect: hive\nPERCENTILE_APPROX(3, array(0.2, 0.3));\nARRAY<DOUBLE>;\n\n# dialect: hive\nPERCENTILE_APPROX(3.1, 0.2);\nDOUBLE;\n\n# dialect: hive\nPERCENTILE_APPROX(3.1, array(0.2, 0.3));\nARRAY<DOUBLE>;\n\n# dialect: hive, spark2, spark, databricks\nPERCENTILE(3, 0.2);\nDOUBLE;\n\n# dialect: hive, spark2, spark, databricks\nPERCENTILE(3, array(0.2, 0.3));\nARRAY<DOUBLE>;\n\n# dialect: spark2, spark, databricks\nPERCENTILE(3.1, 0.2);\nDOUBLE;\n\n# dialect: spark2, spark, databricks\nPERCENTILE(3.1, array(0.2, 0.3));\nARRAY<DOUBLE>;\n\n# dialect: spark2, spark, databricks\nPERCENTILE_APPROX(3.1, 0.2);\nDOUBLE;\n\n# dialect: spark2, spark, databricks\nPERCENTILE_APPROX(3, 0.2);\nINT;\n\n# dialect: spark2, spark, databricks\nPERCENTILE_APPROX(3.1, array(0.2, 0.3));\nARRAY<DOUBLE>;\n\n# dialect: spark2, spark, databricks\nPERCENTILE_APPROX(3, array(0.2, 0.3));\nARRAY<INT>;\n\n# dialect: spark2, spark, databricks\nAPPROX_PERCENTILE(3.1, array(0.2, 0.3));\nARRAY<DOUBLE>;\n\n# dialect: spark2, spark, databricks\nAPPROX_PERCENTILE(3, array(0.2, 0.3));\nARRAY<INT>;\n\n# dialect: spark, databricks\nBIT_OR(tbl.int_col);\nINT;\n\n# dialect: spark, databricks\nBIT_OR(tbl.bigint_col);\nBIGINT;\n\n# dialect: spark2, spark, databricks\nELEMENT_AT(ARRAY(1, 2, 3), 1);\nINT;\n\n# dialect: spark2, spark, databricks\nELEMENT_AT(ARRAY('1', '2','3'), 1);\nSTRING;\n\n# dialect: spark2, spark, databricks\nELEMENT_AT(MAP('a', 1, 'b', 2,'c', 3), 'b');\nINT;\n\n# dialect: spark2, spark, databricks\nELEMENT_AT(MAP('a', 'k1', 'b', 'k2', 'c', 'k3'), 'b');\nSTRING;\n\n# dialect: spark, databricks\nBIT_AND(tbl.int_col);\nINT;\n\n# dialect: spark, databricks\nBIT_AND(tbl.bigint_col);\nBIGINT;\n\n# dialect: spark, databricks\nBIT_XOR(tbl.int_col);\nINT;\n\n# dialect: spark, databricks\nBIT_XOR(tbl.bigint_col);\nBIGINT;\n\n# dialect: hive, spark2, spark, databricks\nARRAY_DISTINCT(tbl.array_col);\nARRAY<STRING>;\n\n# dialect: hive, spark2, spark, databricks\nARRAY_DISTINCT(array(1, 2, 3, null, 3));\nARRAY<INT>;\n\n# dialect: hive, spark2, spark, databricks\nARRAY_EXCEPT(array(1, 2, 3), array(1, 3, 5));\nARRAY<INT>;\n\n# dialect: hive, spark2, spark, databricks\nARRAY_EXCEPT(tbl.array_col, tbl.array_col);\nARRAY<STRING>;\n\n--------------------------------------\n-- BigQuery\n--------------------------------------\n\n# dialect: bigquery\nSIGN(1);\nINT;\n\n# dialect: bigquery\nSIGN(1.5);\nDOUBLE;\n\n# dialect: bigquery\nCEIL(1);\nDOUBLE;\n\n# dialect: bigquery\nCEIL(5.5);\nDOUBLE;\n\n# dialect: bigquery\nCEIL(tbl.bignum_col);\nBIGDECIMAL;\n\n# dialect: bigquery\nFLOOR(1);\nDOUBLE;\n\n# dialect: bigquery\nFLOOR(5.5);\nDOUBLE;\n\n# dialect: bigquery\nFLOOR(tbl.bignum_col);\nBIGDECIMAL;\n\n# dialect: bigquery\nSQRT(1);\nDOUBLE;\n\n# dialect: bigquery\nSQRT(5.5);\nDOUBLE;\n\n# dialect: bigquery\nSQRT(tbl.bignum_col);\nBIGDECIMAL;\n\n# dialect: bigquery\nLN(1);\nDOUBLE;\n\n# dialect: bigquery\nLN(5.5);\nDOUBLE;\n\n# dialect: bigquery\nLN(tbl.bignum_col);\nBIGDECIMAL;\n\n# dialect: bigquery\nLOG(1);\nDOUBLE;\n\n# dialect: bigquery\nLOG(5.5);\nDOUBLE;\n\n# dialect: bigquery\nLOG(tbl.bignum_col);\nBIGDECIMAL;\n\n# dialect: bigquery\nROUND(1);\nDOUBLE;\n\n# dialect: bigquery\nROUND(5.5);\nDOUBLE;\n\n# dialect: bigquery\nROUND(tbl.bignum_col);\nBIGDECIMAL;\n\n# dialect: bigquery\nEXP(1);\nDOUBLE;\n\n# dialect: bigquery\nEXP(5.5);\nDOUBLE;\n\n# dialect: bigquery\nEXP(tbl.bignum_col);\nBIGDECIMAL;\n\n# dialect: bigquery\nAVG(1);\nFLOAT64;\n\n# dialect: bigquery\nAVG(5.5);\nFLOAT64;\n\n# dialect: bigquery\nAVG(tbl.bignum_col);\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.int_col, tbl.int_col);\nFLOAT64;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.int_col, tbl.bignum_col);\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.int_col, tbl.double_col);\nFLOAT64;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.bignum_col, tbl.int_col);\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.bignum_col, tbl.bignum_col);\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.bignum_col, tbl.double_col);\nFLOAT64;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.double_col, tbl.int_col);\nFLOAT64;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.double_col, tbl.bignum_col);\nFLOAT64;\n\n# dialect: bigquery\nSAFE_DIVIDE(tbl.double_col, tbl.double_col);\nFLOAT64;\n\n# dialect: bigquery\nSAFE.TIMESTAMP(tbl.str_col);\nTIMESTAMPTZ;\n\n# dialect: bigquery\nTIMESTAMP(tbl.str_col);\nTIMESTAMPTZ;\n\n# dialect: bigquery\nSAFE.PARSE_DATE('%Y-%m-%d', '2024-01-15');\nDATE;\n\n# dialect: bigquery\nPARSE_DATE('%Y-%m-%d', '2024-01-15');\nDATE;\n\n# dialect: bigquery\nSAFE.PARSE_DATETIME('%Y-%m-%d %H:%M:%S', '2024-01-15 10:30:00');\nDATETIME;\n\n# dialect: bigquery\nSAFE.PARSE_TIME('%H:%M:%S', '10:30:00');\nTIME;\n\n# dialect: bigquery\nSAFE.PARSE_TIMESTAMP('%Y-%m-%d %H:%M:%S', '2024-01-15 10:30:00');\nTIMESTAMPTZ;\n\n# dialect: bigquery\nPARSE_TIMESTAMP('%Y-%m-%d %H:%M:%S', '2024-01-15 10:30:00');\nTIMESTAMPTZ;\n\n# dialect: bigquery\nCONCAT(tbl.str_col, tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nCONCAT(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nCONCAT(0, tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nCONCAT(tbl.str_col, 0);\nSTRING;\n\n# dialect: bigquery\nLEFT(tbl.str_col, 1);\nSTRING;\n\n# dialect: bigquery\nLEFT(tbl.bin_col, 1);\nBINARY;\n\n# dialect: bigquery\nRIGHT(tbl.str_col, 1);\nSTRING;\n\n# dialect: bigquery\nRIGHT(tbl.bin_col, 1);\nBINARY;\n\n# dialect: bigquery\nLOWER(tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nLOWER(tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nUPPER(tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nUPPER(tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nLPAD(tbl.str_col, 1, tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nLPAD(tbl.bin_col, 1, tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nRPAD(tbl.str_col, 1, tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nRPAD(tbl.bin_col, 1, tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nLTRIM(tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nLTRIM(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nRTRIM(tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nRTRIM(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nTRIM(tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nTRIM(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nREGEXP_EXTRACT(tbl.str_col, pattern);\nSTRING;\n\n# dialect: bigquery\nREGEXP_EXTRACT(tbl.bin_col, pattern);\nBINARY;\n\n# dialect: bigquery\nREGEXP_REPLACE(tbl.str_col, pattern, replacement);\nSTRING;\n\n# dialect: bigquery\nREGEXP_REPLACE(tbl.bin_col, pattern, replacement);\nBINARY;\n\n# dialect: bigquery\nREPEAT(tbl.str_col, 1);\nSTRING;\n\n# dialect: bigquery\nREPEAT(tbl.bin_col, 1);\nBINARY;\n\n# dialect: bigquery\nSUBSTRING(tbl.str_col, 1);\nSTRING;\n\n# dialect: bigquery\nSUBSTRING(tbl.bin_col, 1);\nBINARY;\n\n# dialect: bigquery\nSPLIT(tbl.str_col, delim);\nARRAY<STRING>;\n\n# dialect: bigquery\nSPLIT(tbl.bin_col, delim);\nARRAY<BINARY>;\n\n# dialect: bigquery\nSTRING(json_expr);\nSTRING;\n\n# dialect: bigquery\nSTRING(timestamp_expr, timezone);\nSTRING;\n\n# dialect: bigquery\nARRAY_CONCAT(['a'], ['b']);\nARRAY<STRING>;\n\n# dialect: bigquery\nARRAY_CONCAT_AGG(tbl.array_col);\nARRAY<STRING>;\n\n# dialect: bigquery\nARRAY_TO_STRING(['a'], ['b'], ',');\nSTRING;\n\n# dialect: bigquery\nARRAY_FIRST(['a', 'b']);\nSTRING;\n\n# dialect: bigquery\nARRAY_LAST(['a', 'b']);\nSTRING;\n\n# dialect: bigquery\nARRAY_FIRST([1, 1.5]);\nDOUBLE;\n\n# dialect: bigquery\nARRAY_LAST([1, 1.5]);\nDOUBLE;\n\n# dialect: bigquery\nGENERATE_ARRAY(1, 5, 0.3);\nARRAY<DOUBLE>;\n\n# dialect: bigquery\nGENERATE_ARRAY(1, 5);\nARRAY<BIGINT>;\n\n# dialect: bigquery\nGENERATE_ARRAY(1, 2.5);\nARRAY<DOUBLE>;\n\n# dialect: bigquery\nINT64(JSON '999');\nBIGINT;\n\n# dialect: bigquery\nLOGICAL_AND(tbl.bool_col);\nBOOLEAN;\n\n# dialect: bigquery\nLOGICAL_OR(tbl.bool_col);\nBOOLEAN;\n\n# dialect: bigquery\nMAKE_INTERVAL(1, 6, 15);\nINTERVAL;\n\n# dialect: bigquery\nSHA1(tbl.str_col);\nBINARY;\n\n# dialect: bigquery\nSHA256(tbl.str_col);\nBINARY;\n\n# dialect: bigquery\nSHA512(tbl.str_col);\nBINARY;\n\n# dialect: bigquery\nCORR(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: bigquery\nCOVAR_POP(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: bigquery\nCOVAR_SAMP(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: bigquery\nDATETIME(2025, 1, 1, 12, 0, 0);\nDATETIME;\n\n# dialect: bigquery\nLAG(tbl.bigint_col, 1 , 2.5) OVER (ORDER BY tbl.bigint_col);\nDOUBLE;\n\n# dialect: bigquery\nLAG(tbl.bigint_col, 1 , 2) OVER (ORDER BY tbl.bigint_col);\nBIGINT;\n\n# dialect: bigquery\nASCII('A');\nBIGINT;\n\n# dialect: bigquery\nUNICODE('bcd');\nBIGINT;\n\n# dialect: bigquery\nBIT_AND(tbl.bin_col);\nBIGINT;\n\n# dialect: bigquery\nBIT_OR(tbl.bin_col);\nBIGINT;\n\n# dialect: bigquery\nBIT_XOR(tbl.bin_col);\nBIGINT;\n\n# dialect: bigquery\nBIT_COUNT(tbl.bin_col);\nBIGINT;\n\n# dialect: bigquery\nJSON_ARRAY(10);\nJSON;\n\n# dialect: bigquery\nJSON_ARRAY(10, [1, 2]);\nJSON;\n\n# dialect: bigquery\nJSON_VALUE(JSON '{\"foo\": \"1\" }', '$.foo');\nSTRING;\n\n# dialect: bigquery\nJSON_EXTRACT_SCALAR(JSON '[\"a\",\"b\"]');\nSTRING;\n\n# dialect: bigquery\nJSON_VALUE_ARRAY(JSON '[\"a\",\"b\"]');\nARRAY<STRING>;\n\n# dialect: bigquery\nJSON_EXTRACT_STRING_ARRAY(JSON '[\"a\",\"b\"]');\nARRAY<STRING>;\n\n# dialect: bigquery\nJSON_TYPE(JSON '1');\nSTRING;\n\n# dialect: bigquery\nGENERATE_TIMESTAMP_ARRAY('2016-10-05', '2016-10-07', INTERVAL '1' DAY);\nARRAY<TIMESTAMP>;\n\n# dialect: bigquery\nTIME(15, 30, 00);\nTIME;\n\n# dialect: bigquery\nTIME(TIMESTAMP \"2008-12-25 15:30:00\");\nTIME;\n\n# dialect: bigquery\nTIME(DATETIME \"2008-12-25 15:30:00\");\nTIME;\n\n# dialect: bigquery\nTIME_TRUNC(TIME \"15:30:00\", HOUR);\nTIME;\n\n# dialect: bigquery\nDATE_FROM_UNIX_DATE(1);\nDATE;\n\n# dialect: bigquery\nDATE_TRUNC(DATE '2008-12-25', MONTH);\nDATE;\n\n# dialect: bigquery\nDATE_TRUNC(TIMESTAMP '2008-12-25', MONTH);\nTIMESTAMP;\n\n# dialect: bigquery\nDATE_TRUNC(DATETIME '2008-12-25', MONTH);\nDATETIME;\n\n# dialect: bigquery\nTIMESTAMP_TRUNC(TIMESTAMP \"2008-12-25 15:30:00+00\", DAY, \"UTC\");\nTIMESTAMP;\n\n# dialect: bigquery\nTIMESTAMP_TRUNC(DATETIME \"2008-12-25 15:30:00\", DAY);\nDATETIME;\n\n# dialect: bigquery\nPARSE_DATETIME('%a %b %e %I:%M:%S %Y', 'Thu Dec 25 07:30:00 2008');\nDATETIME;\n\n# dialect: bigquery\nFORMAT_TIME(\"%R\", TIME \"15:30:00\");\nSTRING;\n\n# dialect: bigquery\nPARSE_TIME(\"%I:%M:%S\", \"07:30:00\");\nTIME;\n\n# dialect: bigquery\nBYTE_LENGTH(\"foo\");\nBIGINT;\n\n# dialect: bigquery\nCODE_POINTS_TO_STRING([65, 255, 513, 1024]);\nSTRING;\n\n# dialect: bigquery\nREVERSE(\"abc\");\nSTRING;\n\n# dialect: bigquery\nREVERSE(tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nREVERSE(b'1a3');\nBINARY;\n\n# dialect: bigquery\nREGEXP_EXTRACT_ALL('Try `func(x)` or `func(y)`', '`(.+?)`');\nARRAY<STRING>;\n\n# dialect: bigquery\nREGEXP_EXTRACT_ALL(b'\\x48\\x65\\x6C\\x6C\\x6F', b'(\\x6C+)');\nARRAY<BINARY>;\n\n# dialect: bigquery\nREPLACE ('cherry', 'pie', 'cobbler');\nSTRING;\n\n# dialect: bigquery\nREPLACE(b'\\x48\\x65\\x6C\\x6C\\x6F', b'\\x6C\\x6C', b'\\x59\\x59');\nBINARY;\n\n# dialect: bigquery\nTRANSLATE('AaBbCc', 'abc', '1');\nSTRING;\n\n# dialect: bigquery\nTRANSLATE(b'AaBbCc', b'abc', b'123');\nBINARY;\n\n# dialect: bigquery\nSOUNDEX('foo');\nSTRING;\n\n# dialect: bigquery\nMD5('foo');\nBINARY;\n\n# dialect: bigquery\nTO_HEX(MD5('foo'));\nSTRING;\n\n# dialect: bigquery\nMAX_BY(tbl.str_col, tbl.bigint_col);\nSTRING;\n\n# dialect: bigquery\nMAX_BY(tbl.bigint_col, tbl.str_col);\nBIGINT;\n\n# dialect: bigquery\nMIN_BY(tbl.str_col, tbl.bigint_col);\nSTRING;\n\n# dialect: bigquery\nMIN_BY(tbl.bigint_col, tbl.str_col);\nBIGINT;\n\n# dialect: bigquery\nGROUPING(tbl.str_col);\nBIGINT;\n\n# dialect: bigquery\nGROUPING(tbl.bigint_col);\nBIGINT;\n\n# dialect: bigquery\nFARM_FINGERPRINT('foo');\nBIGINT;\n\n# dialect: bigquery\nFARM_FINGERPRINT(b'foo');\nBIGINT;\n\n# dialect: bigquery\nAPPROX_TOP_COUNT(tbl.str_col, 2);\nARRAY<STRUCT<STRING, BIGINT>>;\n\n# dialect: bigquery\nAPPROX_TOP_COUNT(tbl.bigint_col, 2);\nARRAY<STRUCT<BIGINT, BIGINT>>;\n\n# dialect: bigquery\nAPPROX_TOP_SUM(tbl.str_col, 1.5, 2);\nARRAY<STRUCT<STRING, BIGINT>>;\n\n# dialect: bigquery\nAPPROX_TOP_SUM(tbl.bigint_col, 1.5, 2);\nARRAY<STRUCT<BIGINT, BIGINT>>;\n\n# dialect: bigquery\nAPPROX_QUANTILES(tbl.bigint_col, 2);\nARRAY<BIGINT>;\n\n# dialect: bigquery\nAPPROX_QUANTILES(tbl.str_col, 2);\nARRAY<STRING>;\n\n# dialect: bigquery\nAPPROX_QUANTILES(DISTINCT tbl.bigint_col, 2);\nARRAY<BIGINT>;\n\n# dialect: bigquery\nAPPROX_QUANTILES(DISTINCT tbl.str_col, 2);\nARRAY<STRING>;\n\n# dialect: bigquery\nSAFE_CONVERT_BYTES_TO_STRING(b'\\xc2');\nSTRING;\n\n# dialect: bigquery\nFROM_HEX('foo');\nBINARY;\n\n# dialect: bigquery\nTO_HEX(b'foo');\nSTRING;\n\n# dialect: bigquery\nTO_CODE_POINTS('foo');\nARRAY<BIGINT>;\n\n# dialect: bigquery\nTO_CODE_POINTS(b'\\x66\\x6f\\x6f');\nARRAY<BIGINT>;\n\n# dialect: bigquery\nCODE_POINTS_TO_BYTES([65, 98]);\nBINARY;\n\n# dialect: bigquery\nPARSE_BIGNUMERIC('1.2');\nBIGDECIMAL;\n\n# dialect: bigquery\nPARSE_NUMERIC('1.2');\nDECIMAL;\n\n# dialect: bigquery\nBOOL(PARSE_JSON('true'));\nBOOLEAN;\n\n# dialect: bigquery\nFLOAT64(PARSE_JSON('9.8'));\nFLOAT64;\n\n# dialect: bigquery\nFLOAT64(PARSE_JSON('9.8'), wide_number_mode => 'round');\nFLOAT64;\n\n# dialect: bigquery\nCONTAINS_SUBSTR('aa', 'a');\nBOOLEAN;\n\n# dialect: bigquery\nCONTAINS_SUBSTR(PARSE_JSON('{\"lunch\":\"soup\"}'), 'lunch', json_scope => 'JSON_VALUES');\nBOOLEAN;\n\n# dialect: bigquery\nNORMALIZE('\\u00ea');\nSTRING;\n\n# dialect: bigquery\nNORMALIZE('\\u00ea', NFKC);\nSTRING;\n\n# dialect: bigquery\nNORMALIZE_AND_CASEFOLD('\\u00ea', NFKC);\nSTRING;\n\n# dialect: bigquery\nNORMALIZE_AND_CASEFOLD('\\u00ea', NFKC);\nSTRING;\n\n# dialect: bigquery\nOCTET_LENGTH(\"foo\");\nBIGINT;\n\n# dialect: bigquery\nREGEXP_INSTR('ab@cd-ef', '@[^-]*');\nBIGINT;\n\n# dialect: bigquery\nREGEXP_INSTR('a@cd-ef', '@[^-]*', 1, 1, 0);\nBIGINT;\n\n# dialect: bigquery\nROW_NUMBER() OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nFIRST_VALUE(tbl.bigint_col) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nFIRST_VALUE(tbl.str_col) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nFIRST_VALUE(tbl.bigint_col RESPECT NULLS) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nFIRST_VALUE(tbl.bigint_col IGNORE NULLS) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nFIRST_VALUE(tbl.str_col RESPECT NULLS) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nFIRST_VALUE(tbl.str_col IGNORE NULLS) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nNTH_VALUE(tbl.bigint_col, 2) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nNTH_VALUE(tbl.str_col, 2) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nNTH_VALUE(tbl.bigint_col, 2 RESPECT NULLS) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nNTH_VALUE(tbl.str_col, 2 RESPECT NULLS) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nNTH_VALUE(tbl.bigint_col, 2 IGNORE NULLS) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nNTH_VALUE(tbl.str_col, 2 IGNORE NULLS) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nPERCENTILE_DISC(tbl.bigint_col, 0.5) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nPERCENTILE_DISC(tbl.str_col, 0.5) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nPERCENTILE_DISC(tbl.bigint_col, 0.5 RESPECT NULLS) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nPERCENTILE_DISC(tbl.str_col, 0.5 RESPECT NULLS) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nPERCENTILE_DISC(tbl.bigint_col, 0.5 IGNORE NULLS) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nPERCENTILE_DISC(tbl.str_col, 0.5 IGNORE NULLS) OVER (ORDER BY 1);\nSTRING;\n\n# dialect: bigquery\nLEAD(tbl.bigint_col);\nBIGINT;\n\n# dialect: bigquery\nLEAD(tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nLEAD(tbl.bigint_col, 2);\nBIGINT;\n\n# dialect: bigquery\nLEAD(tbl.str_col, 2);\nSTRING;\n\n# dialect: bigquery\nFORMAT('%f %E %f %f', 1.1, 2.2, 3.4, 4.4);\nSTRING;\n\n# dialect: bigquery\nNET.HOST('http://example.com');\nSTRING;\n\n# dialect: bigquery\nNET.REG_DOMAIN('http://example.com');\nSTRING;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS NUMERIC), CAST(1 AS NUMERIC)) OVER (ORDER BY 1);\nNUMERIC;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS NUMERIC), CAST(1 AS BIGNUMERIC)) OVER (ORDER BY 1);\nBIGNUMERIC;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS NUMERIC), CAST(1 AS FLOAT64)) OVER (ORDER BY 1);\nFLOAT64;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS BIGNUMERIC), CAST(1 AS NUMERIC)) OVER (ORDER BY 1);\nBIGNUMERIC;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS BIGNUMERIC), CAST(1 AS BIGNUMERIC)) OVER (ORDER BY 1);\nBIGNUMERIC;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS BIGNUMERIC), CAST(1 AS FLOAT64)) OVER (ORDER BY 1);\nFLOAT64;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS FLOAT64), CAST(1 AS NUMERIC)) OVER (ORDER BY 1);\nFLOAT64;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS FLOAT64), CAST(1 AS BIGNUMERIC)) OVER (ORDER BY 1);\nFLOAT64;\n\n# dialect: bigquery\nPERCENTILE_CONT(CAST(1 AS FLOAT64), CAST(1 AS FLOAT64)) OVER (ORDER BY 1);\nFLOAT64;\n\n# dialect: bigquery\nCUME_DIST() OVER (ORDER BY 1);\nDOUBLE;\n\n# dialect: bigquery\nDENSE_RANK() OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nNTILE(1) OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nRANK() OVER (ORDER BY 1);\nBIGINT;\n\n# dialect: bigquery\nPERCENT_RANK() OVER (ORDER BY 1);\nDOUBLE;\n\n# dialect: bigquery\nJSON_OBJECT('foo', 10, 'bar', TRUE);\nJSON;\n\n# dialect: bigquery\nJSON_QUERY('{\"fruits\": [\"apples\", \"oranges\", \"grapes\"]}', '$.fruits');\nSTRING;\n\n# dialect: bigquery\nJSON_QUERY(JSON_OBJECT('fruits', ['apples', 'oranges', 'grapes']), '$.fruits');\nJSON;\n\n# dialect: bigquery\nJSON_EXTRACT('{\"fruits\": [\"apples\", \"oranges\", \"grapes\"]}', '$.fruits');\nSTRING;\n\n# dialect: bigquery\nJSON_EXTRACT(JSON_OBJECT('fruits', ['apples', 'oranges', 'grapes']), '$.fruits');\nJSON;\n\n# dialect: bigquery\nJSON_QUERY_ARRAY('{\"fruits\": [\"apples\", \"oranges\", \"grapes\"]}', '$.fruits');\nARRAY<STRING>;\n\n# dialect: bigquery\nJSON_QUERY_ARRAY(JSON_OBJECT('fruits', ['apples', 'oranges', 'grapes']), '$.fruits');\nARRAY<JSON>;\n\n# dialect: bigquery\nJSON_EXTRACT_ARRAY('{\"fruits\": [\"apples\", \"oranges\", \"grapes\"]}', '$.fruits');\nARRAY<STRING>;\n\n# dialect: bigquery\nJSON_EXTRACT_ARRAY(JSON_OBJECT('fruits', ['apples', 'oranges', 'grapes']), '$.fruits');\nARRAY<JSON>;\n\n# dialect: bigquery\nJSON_ARRAY_APPEND(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$', 1);\nJSON;\n\n# dialect: bigquery\nJSON_ARRAY_APPEND(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$', [1, 2], append_each_element => FALSE);\nJSON;\n\n# dialect: bigquery\nJSON_ARRAY_INSERT(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', 1);\nJSON;\n\n# dialect: bigquery\nJSON_ARRAY_INSERT(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$[1]', [1, 2], insert_each_element => FALSE);\nJSON;\n\n# dialect: bigquery\nJSON_ARRAY_INSERT(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', 1);\nJSON;\n\n# dialect: bigquery\nJSON_ARRAY_INSERT(PARSE_JSON('[\"a\", \"b\", \"c\"]'), '$[1]', [1, 2], insert_each_element => FALSE);\nJSON;\n\n# dialect: bigquery\nJSON_KEYS(PARSE_JSON('{\"a\": {\"b\":1}}'));\nARRAY<STRING>;\n\n# dialect: bigquery\nJSON_KEYS(PARSE_JSON('{\"a\": {\"b\":1}}'), 1);\nARRAY<STRING>;\n\n# dialect: bigquery\nJSON_KEYS(PARSE_JSON('{\"a\": {\"b\":1}}'), 1, node => 'lax');\nARRAY<STRING>;\n\n# dialect: bigquery\nJSON_REMOVE(PARSE_JSON('[\"a\", [\"b\", \"c\"], \"d\"]'), '$[1]', '$[1]');\nJSON;\n\n# dialect: bigquery\nJSON_SET(PARSE_JSON('{\"a\": 1}'), '$', PARSE_JSON('{\"b\": 2, \"c\": 3}'));\nJSON;\n\n# dialect: bigquery\nJSON_SET(PARSE_JSON('{\"a\": 1}'), '$.b', 999, create_if_missing => FALSE);\nJSON;\n\n# dialect: bigquery\nJSON_STRIP_NULLS(PARSE_JSON('[1, null, 2, null, [null]]'));\nJSON;\n\n# dialect: bigquery\nJSON_STRIP_NULLS(PARSE_JSON('[1, null, 2, null]'), include_arrays => FALSE);\nJSON;\n\n# dialect: bigquery\nJSON_STRIP_NULLS(PARSE_JSON('{\"a\": {\"b\": {\"c\": null}}, \"d\": [null], \"e\": [], \"f\": 1}'), include_arrays => FALSE, remove_empty => TRUE);\nJSON;\n\n# dialect: bigquery\nLAX_BOOL(PARSE_JSON('true'));\nBOOLEAN;\n\n# dialect: bigquery\nLAX_FLOAT64(PARSE_JSON('9.8'));\nDOUBLE;\n\n# dialect: bigquery\nLAX_INT64(PARSE_JSON('10'));\nBIGINT;\n\n# dialect: bigquery\nLAX_STRING(PARSE_JSON('\"str\"'));\nSTRING;\n\n# dialect: bigquery\nTO_JSON_STRING(STRUCT(1 AS id, [10, 20] AS cords));\nSTRING;\n\n# dialect: bigquery\nTO_JSON(STRUCT(1 AS id, [10, 20] AS cords));\nJSON;\n\n# dialect: bigquery\nABS(CAST(-1 AS INT64));\nINT64;\n\n# dialect: bigquery\nABS(CAST(-1 AS NUMERIC));\nNUMERIC;\n\n# dialect: bigquery\nABS(CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nABS(CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nIS_INF(1);\nBOOLEAN;\n\n# dialect: bigquery\nIS_NAN(1);\nBOOLEAN;\n\n# dialect: bigquery\nCBRT(27);\nDOUBLE;\n\n# dialect: bigquery\nRAND();\nDOUBLE;\n\n# dialect: bigquery\nACOS(0.5);\nDOUBLE;\n\n# dialect: bigquery\nACOSH(0.5);\nDOUBLE;\n\n# dialect: bigquery\nASIN(1);\nDOUBLE;\n\n# dialect: bigquery\nASINH(1);\nDOUBLE;\n\n# dialect: bigquery\nATAN(0.5);\nDOUBLE;\n\n# dialect: bigquery\nATANH(0.5);\nDOUBLE;\n\n# dialect: bigquery\nATAN2(0.5, 0.3);\nDOUBLE;\n\n# dialect: bigquery\nCOT(1);\nDOUBLE;\n\n# dialect: bigquery\nCOTH(1);\nDOUBLE;\n\n# dialect: bigquery\nCSC(1);\nDOUBLE;\n\n# dialect: bigquery\nCSCH(1);\nDOUBLE;\n\n# dialect: bigquery\nSEC(1);\nDOUBLE;\n\n# dialect: bigquery\nSECH(1);\nDOUBLE;\n\n# dialect: bigquery\nSIN(1);\nDOUBLE;\n\n# dialect: bigquery\nSINH(1);\nDOUBLE;\n\n# dialect: bigquery\nCOSINE_DISTANCE([1.0, 2.0], [3.0, 4.0]);\nDOUBLE;\n\n#dialect: bigquery\nEUCLIDEAN_DISTANCE([1.0, 2.0], [3.0, 4.0]);\nDOUBLE;\n\n# dialect: bigquery\nRANGE_BUCKET(20, [0, 10, 20, 30, 40]);\nBIGINT;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS INT64), CAST(1 AS NUMERIC));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS INT64), CAST(1 AS INT64));\nINT64;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS INT64), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS INT64), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS NUMERIC), CAST(1 AS INT64));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS NUMERIC), CAST(1 AS NUMERIC));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS NUMERIC), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS NUMERIC), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS BIGNUMERIC), CAST(1 AS INT64));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS BIGNUMERIC), CAST(1 AS NUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS BIGNUMERIC), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS BIGNUMERIC), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS FLOAT64), CAST(1 AS INT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS FLOAT64), CAST(1 AS NUMERIC));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS FLOAT64), CAST(1 AS BIGNUMERIC));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_ADD(CAST(1 AS FLOAT64), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS INT64), CAST(1 AS INT64));\nINT64;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS INT64), CAST(1 AS NUMERIC));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS INT64), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS INT64), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS NUMERIC), CAST(1 AS INT64));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS NUMERIC), CAST(1 AS NUMERIC));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS NUMERIC), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS NUMERIC), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS BIGNUMERIC), CAST(1 AS INT64));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS BIGNUMERIC), CAST(1 AS NUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS BIGNUMERIC), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS BIGNUMERIC), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS FLOAT64), CAST(1 AS INT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS FLOAT64), CAST(1 AS NUMERIC));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS FLOAT64), CAST(1 AS BIGNUMERIC));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_MULTIPLY(CAST(1 AS FLOAT64), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS INT64), CAST(1 AS INT64));\nINT64;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS INT64), CAST(1 AS NUMERIC));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS INT64), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS INT64), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS NUMERIC), CAST(1 AS INT64));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS NUMERIC), CAST(1 AS NUMERIC));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS NUMERIC), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS NUMERIC), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS BIGNUMERIC), CAST(1 AS INT64));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS BIGNUMERIC), CAST(1 AS NUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS BIGNUMERIC), CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS BIGNUMERIC), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS FLOAT64), CAST(1 AS INT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS FLOAT64), CAST(1 AS NUMERIC));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS FLOAT64), CAST(1 AS BIGNUMERIC));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_SUBTRACT(CAST(1 AS FLOAT64), CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_NEGATE(CAST(1 AS FLOAT64));\nFLOAT64;\n\n# dialect: bigquery\nSAFE_NEGATE(CAST(1 AS NUMERIC));\nNUMERIC;\n\n# dialect: bigquery\nSAFE_NEGATE(CAST(1 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nSTRING_AGG(tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nSTRING_AGG(tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nSTRING_AGG(DISTINCT tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nSTRING_AGG(tbl.str_col ORDER BY tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nSTRING_AGG(DISTINCT tbl.str_col, ',' ORDER BY tbl.str_col);\nSTRING;\n\n# dialect: bigquery\nSTRING_AGG(DISTINCT tbl.bin_col ORDER BY tbl.bin_col);\nBINARY;\n\n# dialect: bigquery\nSTRING_AGG(tbl.str_col, ',' LIMIT 10);\nSTRING;\n\n# dialect: bigquery\nSTRING_AGG(tbl.str_col, ',' ORDER BY tbl.str_col LIMIT 10);\nSTRING;\n\n# dialect: bigquery\nSTRING_AGG(DISTINCT tbl.str_col, ',' ORDER BY tbl.str_col LIMIT 10);\nSTRING;\n\n# dialect: bigquery\nSTRING_AGG(DISTINCT tbl.bin_col ORDER BY tbl.bin_col LIMIT 10);\nBINARY;\n\n# dialect: bigquery\nARRAY_AGG(tbl.int_col LIMIT 10);\nARRAY<INT>;\n\n# dialect: bigquery\nARRAY_AGG(DISTINCT tbl.str_col ORDER BY tbl.str_col LIMIT 10);\nARRAY<STRING>;\n\n# dialect: bigquery\nDATETIME_TRUNC(DATETIME \"2008-12-25 15:30:00\", DAY);\nDATETIME;\n\n# dialect: bigquery\nDATETIME_TRUNC(TIMESTAMP \"2008-12-25 15:30:00\", DAY);\nTIMESTAMP;\n\n# dialect: bigquery\nGENERATE_UUID();\nSTRING;\n\n# dialect: bigquery\nSTRUCT(tbl.str_col);\nSTRUCT<str_col STRING>;\n\n# dialect: bigquery\nLENGTH(tbl.str_col);\nBIGINT;\n\n# dialect: bigquery\nLENGTH(tbl.bin_col);\nBIGINT;\n\n# dialect: bigquery\nIF(TRUE, '2010-01-01', DATE '2020-02-02');\nDATE;\n\n# dialect: bigquery\nIF(TRUE, DATETIME '2010-01-01 00:00:00', '2020-02-02 00:00:00');\nDATETIME;\n\n# dialect: bigquery\nIF(TRUE, '00:00:00', TIME '00:01:00');\nTIME;\n\n# dialect: bigquery\nIF(TRUE, 1, CAST(2.5 AS BIGNUMERIC));\nBIGNUMERIC;\n\n# dialect: bigquery\nIF(TRUE, 1.5, 2.5);\nFLOAT64;\n\n# dialect: bigquery\nIF(TRUE, '2010-01-01 00:00:00', TIMESTAMP '2020-02-02 00:00:00');\nTIMESTAMP;\n\n# dialect: bigquery\nCOALESCE('2010-01-01', DATE '2020-02-02');\nDATE;\n\n# dialect: bigquery\nCOALESCE(DATETIME '2010-01-01 00:00:00', '2020-02-02 00:00:00');\nDATETIME;\n\n# dialect: bigquery\nIFNULL('00:00:00', TIME '00:01:00');\nTIME;\n\n# dialect: bigquery\nIFNULL(TIMESTAMP '2010-01-01 00:00:00', '2020-02-02 00:00:00');\nTIMESTAMP;\n\n# dialect: bigquery\nANY_VALUE(c2::STRING HAVING MIN c1::INT64);\nSTRING;\n\n# dialect: bigquery\nANY_VALUE(c2::STRING HAVING MAX c1::INT64);\nSTRING;\n\n# dialect: bigquery\nr'a';\nSTRING;\n\n# dialect: bigquery\nDATE_ADD(DATE '2008-12-25', INTERVAL 5 DAY);\nDATE;\n\n# dialect: bigquery\nDATE_ADD(DATE '2008-12-25', INTERVAL 2 WEEK);\nDATE;\n\n# dialect: bigquery\nDATE_ADD(DATE '2008-12-25', INTERVAL 3 MONTH);\nDATE;\n\n# dialect: bigquery\nDATE_ADD(DATE '2008-12-25', INTERVAL 1 QUARTER);\nDATE;\n\n# dialect: bigquery\nDATE_ADD(DATE '2008-12-25', INTERVAL 2 YEAR);\nDATE;\n\n# dialect: bigquery\nDATE_ADD(TIMESTAMP '2008-12-25 15:30:00', INTERVAL 5 DAY);\nTIMESTAMP;\n\n# dialect: bigquery\nDATE_ADD(TIMESTAMP '2008-12-25 15:30:00', INTERVAL 2 HOUR);\nTIMESTAMP;\n\n# dialect: bigquery\nDATE_ADD(TIMESTAMP '2008-12-25 15:30:00', INTERVAL 30 MINUTE);\nTIMESTAMP;\n\n# dialect: bigquery\nDATE_ADD(DATETIME '2008-12-25 15:30:00', INTERVAL 5 DAY);\nDATETIME;\n\n# dialect: bigquery\nDATE_ADD(DATETIME '2008-12-25 15:30:00', INTERVAL 2 WEEK);\nDATETIME;\n\n# dialect: bigquery\nDATE_ADD(DATETIME '2008-12-25 15:30:00', INTERVAL 3 MONTH);\nDATETIME;\n\n# dialect: bigquery\nDATE_ADD(DATETIME '2008-12-25 15:30:00', INTERVAL 1 QUARTER);\nDATETIME;\n\n# dialect: bigquery\nDATE_ADD(DATETIME '2008-12-25 15:30:00', INTERVAL 2 YEAR);\nDATETIME;\n\n# dialect: bigquery\nDATE_ADD(DATETIME '2008-12-25 15:30:00', INTERVAL 2 HOUR);\nDATETIME;\n\n# dialect: bigquery\nDATE_ADD(DATETIME '2008-12-25 15:30:00', INTERVAL 30 MINUTE);\nDATETIME;\n\n# dialect: bigquery\nUNIX_DATE(tbl.date_col);\nBIGINT;\n\n--------------------------------------\n-- Snowflake\n--------------------------------------\n\n# dialect: snowflake\nABS(tbl.bigint_col);\nBIGINT;\n\n# dialect: snowflake\nABS(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nADD_MONTHS(tbl.date_col, 2);\nDATE;\n\n# dialect: snowflake\nADD_MONTHS(tbl.timestamp_col, -1);\nTIMESTAMP;\n\n# dialect: snowflake\nARRAY_CONSTRUCT();\nARRAY;\n\n# dialect: snowflake\nARRAY_CONSTRUCT_COMPACT();\nARRAY;\n\n# dialect: snowflake\nARRAY_CONSTRUCT_COMPACT(1, null, 2);\nARRAY;\n\n# dialect: snowflake\nARRAY_COMPACT([1, null, 2]);\nARRAY;\n\n# dialect: snowflake\nARRAY_APPEND([1, 2, 3], 4);\nARRAY;\n\n# dialect: snowflake\nARRAY_CAT([1, 2], [3, 4]);\nARRAY;\n\n# dialect: snowflake\nARRAY_PREPEND([2, 3, 4], 1);\nARRAY;\n\n# dialect: snowflake\nARRAY_REMOVE([1, 2, 3], 2);\nARRAY;\n\n# dialect: snowflake\nARRAYS_ZIP([1, 2], [3, 4]);\nARRAY;\n\n# dialect: snowflake\nASIN(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nASINH(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nATAN(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nATAN2(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nATANH(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nCBRT(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nCBRT(tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nCBRT(tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nCOVAR_POP(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nCOVAR_SAMP(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nCOVAR_POP(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nCOVAR_SAMP(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nAI_AGG('foo', 'bar');\nVARCHAR;\n\n# dialect: snowflake\nAI_AGG(null, 'bar');\nVARCHAR;\n\n# dialect: snowflake\nAI_SUMMARIZE_AGG('foo');\nVARCHAR;\n\n# dialect: snowflake\nAI_SUMMARIZE_AGG(null);\nVARCHAR;\n\n# dialect: snowflake\nAI_CLASSIFY('text', ['travel', 'cooking']);\nVARCHAR;\n\n# dialect: snowflake\nAI_CLASSIFY('text', ['travel', 'cooking'], {'output_mode': 'multi'});\nVARCHAR;\n\n# dialect: snowflake\nASCII('A');\nINT;\n\n# dialect: snowflake\nASCII('');\nINT;\n\n# dialect: snowflake\nASCII(NULL);\nINT;\n\n# dialect: snowflake\nBASE64_DECODE_BINARY('SGVsbG8=');\nBINARY;\n\n# dialect: snowflake\nBASE64_DECODE_STRING('SGVsbG8gV29ybGQ=');\nVARCHAR;\n\n# dialect: snowflake\nBASE64_DECODE_STRING('SGVsbG8gV29ybGQ=', '+/=');\nVARCHAR;\n\n# dialect: snowflake\nBASE64_ENCODE(tbl.bin_col);\nVARCHAR;\n\n# dialect: snowflake\nBASE64_ENCODE('Hello World');\nVARCHAR;\n\n# dialect: snowflake\nBASE64_ENCODE('Hello World', 76);\nVARCHAR;\n\n# dialect: snowflake\nBASE64_ENCODE('Hello World', 76, '+/=');\nVARCHAR;\n\n# dialect: snowflake\nBIT_LENGTH('abc');\nINT;\n\n# dialect: snowflake\nBITMAP_BIT_POSITION(tbl.int_col);\nBIGINT;\n\n# dialect: snowflake\nBITMAP_BUCKET_NUMBER(tbl.int_col);\nBIGINT;\n\n# dialect: snowflake\nBITMAP_CONSTRUCT_AGG(tbl.int_col);\nBINARY;\n\n# dialect: snowflake\nBITMAP_COUNT(BITMAP_CONSTRUCT_AGG(tbl.int_col));\nBIGINT;\n\n# dialect: snowflake\nBIT_LENGTH(tbl.str_col);\nINT;\n\n# dialect: snowflake\nBIT_LENGTH(tbl.bin_col);\nINT;\n\n# dialect: snowflake\nBITNOT(5);\nINT;\n\n# dialect: snowflake\nBITNOT(tbl.bin_col);\nBINARY;\n\n# dialect: snowflake\nBIT_NOT(5);\nINT;\n\n# dialect: snowflake\nBITAND(2, 4);\nINT;\n\n# dialect: snowflake\nBITAND(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: snowflake\nBIT_AND(2, 4);\nINT;\n\n# dialect: snowflake\nBITOR(2, 4);\nINT;\n\n# dialect: snowflake\nBITOR(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: snowflake\nBITSHIFTLEFT(2, 1);\nINT;\n\n# dialect: snowflake\nBITSHIFTLEFT(tbl.bin_col, 4);\nBINARY;\n\n# dialect: snowflake\nBITSHIFTRIGHT(24, 1);\nINT;\n\n# dialect: snowflake\nBITSHIFTRIGHT(tbl.bin_col, 4);\nBINARY;\n\n# dialect: snowflake\nBITXOR(5, 3);\nINT;\n\n# dialect: snowflake\nBITXOR(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: snowflake\nBITANDAGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBITAND_AGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBIT_AND_AGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBIT_ANDAGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBITORAGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBITOR_AGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBIT_OR_AGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBIT_ORAGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBITXORAGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBITXOR_AGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBIT_XOR_AGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBIT_XORAGG(tbl.int_col);\nNUMBER(38, 0);\n\n# dialect: snowflake\nBITMAP_OR_AGG(tbl.bin_col);\nBINARY;\n\n# dialect: snowflake\nBOOLXOR_AGG(tbl.bool_col);\nBOOLEAN;\n\n# dialect: snowflake\nBOOLNOT(tbl.int_col);\nBOOLEAN;\n\n# dialect: snowflake\nBOOLNOT(NULL);\nBOOLEAN;\n\n# dialect: snowflake\nBOOLAND(1, -2);\nBOOLEAN;\n\n# dialect: snowflake\nBOOLOR(1, 0);\nBOOLEAN;\n\n# dialect: snowflake\nBOOLXOR(2, 0);\nBOOLEAN;\n\n# dialect: snowflake\nBOOLAND_AGG(tbl.bool_col);\nBOOLEAN;\n\n# dialect: snowflake\nBOOLOR_AGG(tbl.bool_col);\nBOOLEAN;\n\n# dialect: snowflake\nTO_BOOLEAN('true');\nBOOLEAN;\n\n# dialect: snowflake\nTO_BOOLEAN(1);\nBOOLEAN;\n\n# dialect: snowflake\nTO_BOOLEAN(tbl.varchar_col);\nBOOLEAN;\n\n# dialect: snowflake\nARRAY_AGG(tbl.bin_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_AGG(tbl.bool_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_AGG(tbl.date_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_AGG(tbl.double_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_AGG(tbl.str_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_UNIQUE_AGG(tbl.bin_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_UNIQUE_AGG(tbl.bool_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_UNIQUE_AGG(tbl.date_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_UNIQUE_AGG(tbl.double_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_UNIQUE_AGG(tbl.str_col);\nARRAY;\n\n# dialect: snowflake\nARRAY_UNION_AGG(tbl.array_col);\nARRAY;\n\n# dialect: snowflake\nCHARINDEX('world', 'hello world');\nINT;\n\n# dialect: snowflake\nCHARINDEX('world', 'hello world', 1);\nINT;\n\n# dialect: snowflake\nCASE WHEN score >= 90 THEN 100 WHEN score >= 80 THEN 220 END;\nINT;\n\n# dialect: snowflake\nCASE WHEN score >= 90 THEN 'A' WHEN score >= 80 THEN 'B' ELSE 'C' END;\nVARCHAR;\n\n# dialect: snowflake\nCASE WHEN score >= 90 THEN TRUE WHEN score >= 80 THEN FALSE ELSE NULL END;\nBOOLEAN;\n\n# dialect: snowflake\nCEIL(3.14);\nDOUBLE;\n\n# dialect: snowflake\nCEIL(3.14::FLOAT, 1);\nFLOAT;\n\n# dialect: snowflake\nCEIL(3.14, 1);\nDOUBLE;\n\n# dialect: snowflake\nCEIL(10::NUMERIC);\nNUMBER;\n\n# dialect: snowflake\nCHAR(65);\nVARCHAR;\n\n# dialect: snowflake\nCHR(8364);\nVARCHAR;\n\n# dialect: snowflake\nCHECK_JSON('{\"key\": \"value\", \"array\": [1, 2, 3]}');\nVARCHAR;\n\n# dialect: snowflake\nCHECK_XML('<root><key attribute=\"attr\">value</key></root>');\nVARCHAR;\n\n# dialect: snowflake\nCHECK_XML('<root><key attribute=\"attr\">value</key></root>', TRUE);\nVARCHAR;\n\n# dialect: snowflake\nCOLLATE('hello', 'utf8');\nVARCHAR;\n\n# dialect: snowflake\nCOSH(1.5);\nDOUBLE;\n\n# dialect: snowflake\nCOALESCE(42, 0, 100);\nINT;\n\n# dialect: snowflake\nCOALESCE(1.5, 2.7);\nDOUBLE;\n\n# dialect: snowflake\nCOALESCE(1::BIGINT, 2::BIGINT);\nBIGINT;\n\n# dialect: snowflake\nCOALESCE('hello', 'world');\nVARCHAR;\n\n# dialect: snowflake\nCOALESCE(CAST('2024-01-01' AS DATE), CAST('2024-12-31' AS DATE));\nDATE;\n\n# dialect: snowflake\nCAST(1.5 AS DECFLOAT);\nDECFLOAT;\n\n# dialect: snowflake\nCAST(1 AS VARCHAR);\nVARCHAR;\n\n# dialect: snowflake\nCAST('123' AS INT);\nINT;\n\n# dialect: snowflake\nCOALESCE(TRUE, FALSE);\nBOOLEAN;\n\n# dialect: snowflake\nCOUNT(*);\nBIGINT;\n\n# dialect: snowflake\nCOUNT(DISTINCT tbl.str_col);\nBIGINT;\n\n# dialect: snowflake\nCOMPRESS('Hello World', 'SNAPPY');\nBINARY;\n\n# dialect: snowflake\nCOMPRESS('Hello World', 'zlib(1)');\nBINARY;\n\n# dialect: snowflake\nDATE_PART('year', tbl.date_col);\nINT;\n\n# dialect: snowflake\nDATE_PART('month', tbl.timestamp_col);\nINT;\n\n# dialect: snowflake\nDATE_PART('day', tbl.date_col);\nINT;\n\n# dialect: snowflake\nDATEADD(HOUR, 3, TO_TIME('05:00:00'));\nTIME;\n\n# dialect: snowflake\nDATEADD(YEAR, 1, TO_TIMESTAMP('2022-05-08 14:30:00'));\nTIMESTAMP;\n\n# dialect: snowflake\nDATEADD(MONTH, 1, '2023-01-31'::DATE);\nDATE;\n\n# dialect: snowflake\nDATEADD(HOUR, 2, '2022-04-05'::DATE);\nTIMESTAMPNTZ;\n\n# dialect: snowflake\nDEGREES(PI()/3);\nDOUBLE;\n\n# dialect: snowflake\nDEGREES(1);\nDOUBLE;\n\n# dialect: snowflake\nDATE_FROM_PARTS(1977, 8, 7);\nDATE;\n\n# dialect: snowflake\nDECOMPRESS_BINARY('compressed_data', 'SNAPPY');\nBINARY;\n\n# dialect: snowflake\nDECOMPRESS_STRING('compressed_data', 'ZSTD');\nVARCHAR;\n\n# dialect: snowflake\nDIV0(10, 0);\nDOUBLE;\n\n# dialect: snowflake\nDIV0(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nDIV0NULL(10, 0);\nDOUBLE;\n\n# dialect: snowflake\nDIV0NULL(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nLPAD('Hello', 10, '*');\nVARCHAR;\n\n# dialect: snowflake\nLPAD(tbl.str_col, 10);\nVARCHAR;\n\n# dialect: snowflake\nLPAD(tbl.bin_col, 10, 0x20);\nBINARY;\n\n# dialect: snowflake\nRPAD('Hello', 10, '*');\nVARCHAR;\n\n# dialect: snowflake\nRPAD(tbl.str_col, 10);\nVARCHAR;\n\n# dialect: snowflake\nRPAD(tbl.bin_col, 10, 0x20);\nBINARY;\n\n# dialect: snowflake\nCOLLATION('hello');\nVARCHAR;\n\n# dialect: snowflake\nCOT(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nCOS(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nCONCAT('Hello', 'World!');\nVARCHAR;\n\n# dialect: snowflake\nCONCAT(tbl.str_col, tbl.str_col, tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nCONCAT_WS(':', 'one');\nVARCHAR;\n\n# dialect: snowflake\nCONCAT_WS(',', 'one', 'two', 'three');\nVARCHAR;\n\n# dialect: snowflake\nCONCAT_WS(tbl.bin_col, tbl.bin_col);\nBINARY;\n\n# dialect: snowflake\nCONTAINS('hello world', 'world');\nBOOLEAN;\n\n# dialect: snowflake\nCONTAINS(tbl.str_col, 'test');\nBOOLEAN;\n\n# dialect: snowflake\nCONTAINS(tbl.bin_col, tbl.bin_col);\nBOOLEAN;\n\n# dialect: snowflake\nCONTAINS(tbl.bin_col, NULL);\nBOOLEAN;\n\n# dialect: snowflake\nCONVERT_TIMEZONE('America/New_York', '2024-08-06 09:10:00.000');\nTIMESTAMPTZ;\n\n# dialect: snowflake\nCONVERT_TIMEZONE('America/Los_Angeles', 'America/New_York', '2024-08-06 09:10:00.000');\nTIMESTAMPNTZ;\n\n# dialect: snowflake\nCURRENT_ACCOUNT();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_ACCOUNT_NAME();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_AVAILABLE_ROLES();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_CLIENT();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_IP_ADDRESS();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_DATABASE();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_SCHEMAS();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_SECONDARY_ROLES();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_SESSION();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_STATEMENT();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_VERSION();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_TRANSACTION();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_WAREHOUSE();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_ORGANIZATION_USER();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_REGION();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_ROLE();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_ROLE_TYPE();\nVARCHAR;\n\n# dialect: snowflake\nCURRENT_ORGANIZATION_NAME();\nVARCHAR;\n\n# dialect: snowflake\nDATEDIFF('year', tbl.date_col, tbl.date_col);\nINT;\n\n# dialect: snowflake\nDATEDIFF('month', tbl.timestamp_col, tbl.timestamp_col);\nINT;\n\n# dialect: snowflake\nTIMESTAMPDIFF('year', tbl.date_col, tbl.date_col);\nINT;\n\n# dialect: snowflake\nTIMESTAMPDIFF('month', tbl.timestamp_col, tbl.timestamp_col);\nINT;\n\n# dialect: snowflake\nTIMEDIFF('year', tbl.date_col, tbl.date_col);\nINT;\n\n# dialect: snowflake\nTIMEDIFF('month', tbl.timestamp_col, tbl.timestamp_col);\nINT;\n\n# dialect: snowflake\nDATE_TRUNC('year', TO_DATE('2024-05-09'));\nDATE;\n\n# dialect: snowflake\nDATE_TRUNC('minute', TO_TIME('08:50:48'));\nTIME;\n\n# dialect: snowflake\nDATE_TRUNC('minute', TO_TIMESTAMP('2024-05-09 08:50:57.891'));\nTIMESTAMP;\n\n# dialect: snowflake\nTIMESTAMP_FROM_PARTS(2024, 5, 9, 14, 30, 45);\nTIMESTAMP;\n\n# dialect: snowflake\nTIMESTAMP_FROM_PARTS(2024, 5, 9, 14, 30, 45, 123);\nTIMESTAMP;\n\n# dialect: snowflake\nTIMESTAMP_FROM_PARTS(CAST('2024-05-09' AS DATE), CAST('14:30:45' AS TIME));\nTIMESTAMP;\n\n# dialect: snowflake\nTIMESTAMPFROMPARTS(2024, 5, 9, 14, 30, 45);\nTIMESTAMP;\n\n# dialect: snowflake\nTIMESTAMPFROMPARTS(CAST('2024-05-09' AS DATE), CAST('14:30:45' AS TIME));\nTIMESTAMP;\n\n# dialect: snowflake\nTIMESTAMP_LTZ_FROM_PARTS(2024, 5, 9, 14, 30, 45);\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nTIMESTAMP_LTZ_FROM_PARTS(2024, 5, 9, 14, 30, 45, 123);\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nTIMESTAMP_NTZ_FROM_PARTS(2024, 5, 9, 14, 30, 45);\nTIMESTAMP;\n\n# dialect: snowflake\nTIMESTAMP_NTZ_FROM_PARTS(2024, 5, 9, 14, 30, 45, 123);\nTIMESTAMP;\n\n# dialect: snowflake\nTIMESTAMP_TZ_FROM_PARTS(2024, 5, 9, 14, 30, 45, 123, 'UTC');\nTIMESTAMPTZ;\n\n# dialect: snowflake\nTIMESTAMP_TZ_FROM_PARTS(2024, 5, 9, 14, 30, 45, 123);\nTIMESTAMPTZ;\n\n# dialect: snowflake\nEDITDISTANCE('hello', 'world');\nINT;\n\n# dialect: snowflake\nEDITDISTANCE(tbl.str_col, 'test');\nINT;\n\n# dialect: snowflake\nEDITDISTANCE('hello', 'world', 3);\nINT;\n\n# dialect: snowflake\nEQUAL_NULL(1, 2);\nBOOLEAN;\n\n# dialect: snowflake\nEXTRACT(YEAR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(QUARTER FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(MONTH FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(WEEK FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(WEEKISO FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAY FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAYOFMONTH FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAYOFWEEK FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAYOFWEEKISO FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAYOFYEAR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(YEAROFWEEK FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(YEAROFWEEKISO FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(HOUR FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(MINUTE FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(SECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nINT;\n\n# dialect: snowflake\nEXTRACT(NANOSECOND FROM CAST('2026-01-06 11:45:00.123456789' AS TIMESTAMP_NTZ));\nBIGINT;\n\n# dialect: snowflake\nEXTRACT(EPOCH_SECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nBIGINT;\n\n# dialect: snowflake\nEXTRACT(EPOCH_MILLISECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nBIGINT;\n\n# dialect: snowflake\nEXTRACT(EPOCH_MICROSECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nBIGINT;\n\n# dialect: snowflake\nEXTRACT(EPOCH_NANOSECOND FROM CAST('2026-01-06 11:45:00' AS TIMESTAMP_NTZ));\nBIGINT;\n\n# dialect: snowflake\nEXTRACT(YEAR FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(QUARTER FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(MONTH FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(WEEK FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(WEEKISO FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAY FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAYOFMONTH FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAYOFWEEK FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAYOFWEEKISO FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(DAYOFYEAR FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(YEAROFWEEK FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(YEAROFWEEKISO FROM CAST('2026-01-06' AS DATE));\nINT;\n\n# dialect: snowflake\nEXTRACT(HOUR FROM CAST('11:45:00.123456789' AS TIME));\nINT;\n\n# dialect: snowflake\nEXTRACT(MINUTE FROM CAST('11:45:00.123456789' AS TIME));\nINT;\n\n# dialect: snowflake\nEXTRACT(SECOND FROM CAST('11:45:00.123456789' AS TIME));\nINT;\n\n# dialect: snowflake\nYEAR(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nYEAR(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nYEAROFWEEK(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nYEAROFWEEK(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nYEAROFWEEKISO(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nYEAROFWEEKISO(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nDAY(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nDAY(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nDAYOFMONTH(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nDAYOFMONTH(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nDAYOFWEEK(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nDAYOFWEEK(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nDAYOFWEEKISO(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nDAYOFWEEKISO(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nDAYOFYEAR(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nDAYOFYEAR(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nWEEK(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nWEEK(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nWEEKOFYEAR(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nWEEKOFYEAR(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nWEEKISO(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nWEEKISO(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nMONTH(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nMONTH(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nQUARTER(CAST('2024-05-09' AS DATE));\nTINYINT;\n\n# dialect: snowflake\nQUARTER(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nTINYINT;\n\n# dialect: snowflake\nEXP(1);\nDOUBLE;\n\n# dialect: snowflake\nEXP(5.5);\nDOUBLE;\n\n# dialect: snowflake\nFACTORIAL(5);\nBIGINT;\n\n# dialect: snowflake\nFLOOR(42);\nINT;\n\n# dialect: snowflake\nFLOOR(135.135, 1);\nDOUBLE;\n\n# dialect: snowflake\nFLOOR(tbl.bigint_col, -1);\nBIGINT;\n\n# dialect: snowflake\nGETBIT(11, 3);\nINT;\n\n# dialect: snowflake\nGROUPING(tbl.str_col);\nINT;\n\n# dialect: snowflake\nGROUPING(tbl.bigint_col);\nINT;\n\n# dialect: snowflake\nGROUPING_ID(tbl.str_col);\nBIGINT;\n\n# dialect: snowflake\nGROUPING_ID(tbl.bigint_col, tbl.str_col);\nBIGINT;\n\n# dialect: snowflake\nGREATEST(tbl.bigint_col, tbl.bigint_col);\nBIGINT;\n\n# dialect: snowflake\nGREATEST(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nGREATEST(tbl.str_col, tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nGREATEST(tbl.double_col, tbl.bigint_col);\nDOUBLE;\n\n# dialect: snowflake\nGREATEST(tbl.bigint_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nENDSWITH('hello world', 'world');\nBOOLEAN;\n\n# dialect: snowflake\nENDSWITH(tbl.str_col, 'test');\nBOOLEAN;\n\n# dialect: snowflake\nENDSWITH(tbl.bin_col, tbl.bin_col);\nBOOLEAN;\n\n# dialect: snowflake\nENDSWITH(tbl.bin_col, NULL);\nBOOLEAN;\n\n# dialect: snowflake\nGREATEST_IGNORE_NULLS(1, 2, 3);\nINT;\n\n# dialect: snowflake\nGREATEST_IGNORE_NULLS(1, 2.5, 3);\nDOUBLE;\n\n# dialect: snowflake\nGREATEST_IGNORE_NULLS('a', 'b', 'c');\nVARCHAR;\n\n# dialect: snowflake\nGREATEST_IGNORE_NULLS(CAST('2023-01-01' AS DATE), CAST('2023-01-02' AS DATE));\nDATE;\n\n# dialect: snowflake\nHASH_AGG(tbl.str_col);\nDECIMAL(19, 0);\n\n# dialect: snowflake\nLEAST_IGNORE_NULLS(1, 2, 3);\nINT;\n\n# dialect: snowflake\nLEAST_IGNORE_NULLS(1, 2.5, 3);\nDOUBLE;\n\n# dialect: snowflake\nLEAST_IGNORE_NULLS('a', 'b', 'c');\nVARCHAR;\n\n# dialect: snowflake\nLEAST_IGNORE_NULLS(CAST('2023-01-01' AS DATE), CAST('2023-01-02' AS DATE));\nDATE;\n\n# dialect: snowflake\nHEX_DECODE_BINARY('48656C6C6F');\nBINARY;\n\n# dialect: snowflake\nHEX_DECODE_STRING('48656C6C6F');\nVARCHAR;\n\n# dialect: snowflake\nHEX_ENCODE('Hello World');\nVARCHAR;\n\n# dialect: snowflake\nHEX_ENCODE('Hello World', 'upper');\nVARCHAR;\n\n# dialect: snowflake\nHEX_ENCODE('Hello World', 'lower');\nVARCHAR;\n\n# dialect: snowflake\nHOUR(CAST('08:50:57' AS TIME));\nINT;\n\n# dialect: snowflake\nINITCAP('hello world');\nVARCHAR;\n\n# dialect: snowflake\nINITCAP('hello world', ' ');\nVARCHAR;\n\n# dialect: snowflake\nINITCAP(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nIFF(TRUE, 42, 0);\nINT;\n\n# dialect: snowflake\nIFF(TRUE, 42, NULL);\nINT;\n\n# dialect: snowflake\nIFF(col1 > 0, 'yes', 'no');\nVARCHAR;\n\n# dialect: snowflake\nIFF(FALSE, 1.5, 2.7);\nDOUBLE;\n\n# dialect: snowflake\nIFF(TRUE, CAST('2024-01-01' AS DATE), CAST('2024-12-31' AS DATE));\nDATE;\n\n# dialect: snowflake\nIFNULL('hello', 'world');\nVARCHAR;\n\n# dialect: snowflake\nIFNULL(1, 2);\nINT;\n\n# dialect: snowflake\nIFNULL(1.5, 2.7);\nDOUBLE;\n\n# dialect: snowflake\nIFNULL(5::BIGINT, 10::BIGINT);\nBIGINT;\n\n# dialect: snowflake\nIFNULL(CAST('2024-01-01' AS DATE), CAST('2024-12-31' AS DATE));\nDATE;\n\n# dialect: snowflake\nIFNULL(5::BIGINT, 2.71::FLOAT);\nFLOAT;\n\n# dialect: snowflake\nIS_NULL_VALUE(payload:field);\nBOOLEAN;\n\n# dialect: snowflake\n1 IN (1, 2, 3);\nBOOLEAN;\n\n# dialect: snowflake\n1 NOT IN (1, 2, 3);\nBOOLEAN;\n\n# dialect: snowflake\nJAROWINKLER_SIMILARITY('hello', 'world');\nINT;\n\n# dialect: duckdb\nJARO_WINKLER_SIMILARITY('hello', 'world');\nDOUBLE;\n\n# dialect: snowflake\nINSERT('abc', 1, 2, 'Z');\nVARCHAR;\n\n# dialect: snowflake\nINSERT(tbl.bin_col, 1, 2, tbl.bin_col);\nBINARY;\n\n# dialect: snowflake\nKURTOSIS(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nKURTOSIS(tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nKURTOSIS(tbl.float_col);\nDOUBLE;\n\n# dialect: snowflake\nKURTOSIS(tbl.float_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nKURTOSIS(tbl.int_col);\nNUMBER(38, 12);\n\n# dialect: snowflake\nKURTOSIS(tbl.int_col) OVER (PARTITION BY 1);\nNUMBER(38, 12);\n\n# dialect: snowflake\nKURTOSIS(tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nKURTOSIS(tbl.decfloat_col) OVER (PARTITION BY 1);\nDECFLOAT;\n\n# dialect: snowflake\nLEAST(x::DECIMAL(18, 2));\nDECIMAL(18, 2);\n\n# dialect: snowflake\nLEFT('hello world', 5);\nVARCHAR;\n\n# dialect: snowflake\nLEFT(tbl.str_col, 3);\nSTRING;\n\n# dialect: snowflake\nLEFT(tbl.bin_col, 3);\nBINARY;\n\n# dialect: snowflake\nLEFT(tbl.bin_col, NULL);\nBINARY;\n\n# dialect: snowflake\nLAST_DAY(CAST('2024-05-09' AS DATE));\nDATE;\n\n# dialect: snowflake\nLAST_DAY(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nDATE;\n\n# dialect: snowflake\nLAST_DAY(CAST('2024-02-15' AS DATE), MONTH);\nDATE;\n\n# dialect: snowflake\nLEN(tbl.str_col);\nINT;\n\n# dialect: snowflake\nLEN(tbl.bin_col);\nINT;\n\n# dialect: snowflake\nLOCALTIMESTAMP;\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nLOCALTIMESTAMP();\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nLOCALTIMESTAMP(3);\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nOCTET_LENGTH(tbl.str_col);\nINT;\n\n# dialect: snowflake\nOCTET_LENGTH(tbl.bin_col);\nINT;\n\n# dialect: snowflake\nPARSE_URL('https://example.com/path');\nOBJECT;\n\n# dialect: snowflake\nPARSE_URL(tbl.str_col, 0);\nOBJECT;\n\n# dialect: snowflake\nPOSITION('abc' IN 'abcdef');\nINT;\n\n# dialect: snowflake\nPOSITION('abc', 'abcdef');\nINT;\n\n# dialect: snowflake\nPOSITION('abc', 'abcdef', 1);\nINT;\n\n# dialect: snowflake\nPREVIOUS_DAY(CAST('2024-05-09' AS DATE), 'MONDAY');\nDATE;\n\n# dialect: snowflake\nPREVIOUS_DAY(CAST('2024-05-09 08:50:57' AS TIMESTAMP), 'MONDAY');\nDATE;\n\n# dialect: snowflake\nDECODE(x, 1, 100, 2, 200, 0);\nINT;\n\n# dialect: snowflake\nDECODE(status, 'A', 'Active', 'I', 'Inactive', 'Neither');\nVARCHAR;\n\n# dialect: snowflake\nDECODE(100, 100, 1, 90, 2, 5.5);\nDOUBLE;\n\n# dialect: snowflake\nDECODE(x, 1, 100, NULL);\nINT;\n\n# dialect: snowflake\nPI();\nDOUBLE;\n\n# dialect: snowflake\nPOW(tbl.double_col, 2);\nDOUBLE;\n\n# dialect: snowflake\nRANDOM();\nBIGINT;\n\n# dialect: snowflake\nRANDOM(123);\nBIGINT;\n\n# dialect: snowflake\nRANDSTR(123, 456);\nVARCHAR;\n\n# dialect: snowflake\nRANDSTR(123, RANDOM());\nVARCHAR;\n\n# dialect: snowflake\nRADIANS(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nLOWER(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nLN(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nLOG(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nLTRIM('  hello world  ');\nVARCHAR;\n\n# dialect: snowflake\nLTRIM(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nLTRIM(NULL);\nVARCHAR;\n\n# dialect: snowflake\nMAP_CAT(CAST(col AS MAP(VARCHAR, VARCHAR)), CAST(col AS MAP(VARCHAR, VARCHAR)));\nMAP;\n\n# dialect: snowflake\nMAP_CONTAINS_KEY('k1', CAST(col AS MAP(VARCHAR, VARCHAR)));\nBOOLEAN;\n\n# dialect: snowflake\nMAP_DELETE(CAST(col AS MAP(VARCHAR, VARCHAR)), 'b');\nMAP;\n\n# dialect: snowflake\nMAP_INSERT(CAST(col AS MAP(VARCHAR, VARCHAR)), 'b', '2');\nMAP;\n\n# dialect: snowflake\nMAP_KEYS(CAST(col AS MAP(VARCHAR, VARCHAR)));\nARRAY;\n\n# dialect: snowflake\nMAP_PICK(CAST(col AS MAP(VARCHAR, VARCHAR)), 'a', 'c');\nMAP;\n\n# dialect: snowflake\nMAP_SIZE(CAST(col AS MAP(VARCHAR, VARCHAR)));\nINT;\n\n# dialect: snowflake\nMINUTE(CAST('08:50:57' AS TIME));\nINT;\n\n# dialect: snowflake\nMEDIAN(2.71::FLOAT);\nFLOAT;\n\n# dialect: snowflake\nMEDIAN(tbl.bigint_col) OVER (PARTITION BY 1);\nDECIMAL(38, 3);\n\n# dialect: snowflake\nMEDIAN(CAST(100 AS DECIMAL(10,2)));\nDECIMAL(13, 5);\n\n# dialect: snowflake\nMONTHNAME(CAST('2024-05-09' AS DATE));\nVARCHAR;\n\n# dialect: snowflake\nMONTHNAME(CAST('2024-05-09 08:50:57' AS TIMESTAMP));\nVARCHAR;\n\n# dialect: snowflake\nNORMAL(0, 1, RANDOM());\nDOUBLE;\n\n# dialect: snowflake\nNVL2(col1, col2, col3);\nUNKNOWN;\n\n# dialect: snowflake\nNVL('hello', 'world');\nVARCHAR;\n\n# dialect: snowflake\nNVL(tbl.int_col, 42);\nINT;\n\n# dialect: snowflake\nNVL(tbl.date_col, CAST('2024-01-01' AS DATE));\nDATE;\n\n# dialect: snowflake\nNVL(1, 3.14);\nDOUBLE;\n\n# dialect: snowflake\nNVL(5::BIGINT, 2.71::FLOAT);\nFLOAT;\n\n# dialect: snowflake\nNULLIF(1, 2);\nINT;\n\n# dialect: snowflake\nNULLIF(1.5, 2.7);\nDOUBLE;\n\n# dialect: snowflake\nNULLIF(5::BIGINT, 10::BIGINT);\nBIGINT;\n\n# dialect: snowflake\nNULLIF(CAST('2024-01-01' AS DATE), CAST('2024-12-31' AS DATE));\nDATE;\n\n# dialect: snowflake\nNULLIF(1::INT, 2::BIGINT);\nBIGINT;\n\n# dialect: snowflake\nNULLIF(1::INT, 2.5::DOUBLE);\nDOUBLE;\n\n# dialect: snowflake\nNULLIFZERO(5);\nINT;\n\n# dialect: snowflake\nNULLIFZERO(5::BIGINT);\nBIGINT;\n\n# dialect: snowflake\nNULLIFZERO(5.5);\nDOUBLE;\n\n# dialect: snowflake\nNULLIFZERO(5.5::FLOAT);\nFLOAT;\n\n# dialect: snowflake\nMOD(tbl.bigint_col, 3);\nBIGINT;\n\n# dialect: snowflake\nMOD(tbl.double_col, 2.5);\nDOUBLE;\n\n# dialect: snowflake\nMOD(42, 7);\nINT;\n\n# dialect: snowflake\nMONTHS_BETWEEN(tbl.date_col, CAST('2019-01-01' AS DATE));\nDOUBLE;\n\n# dialect: snowflake\nMONTHS_BETWEEN(tbl.timestamp_col, CAST('2019-02-15 01:00:00' AS TIMESTAMP));\nDOUBLE;\n\n# dialect: snowflake\nREGR_AVGX(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_AVGX(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_AVGX(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_AVGY(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_AVGY(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_AVGY(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_COUNT(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_COUNT(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nREGR_COUNT(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_COUNT(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_INTERCEPT(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_INTERCEPT(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nREGR_INTERCEPT(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_INTERCEPT(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_R2(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_R2(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nREGR_R2(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_R2(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_SXX(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SXX(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SXX(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SXX(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_SXY(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SXY(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SXY(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SXY(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_SYY(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SYY(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SYY(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SYY(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_SLOPE(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SLOPE(tbl.double_col, tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SLOPE(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_SLOPE(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_VALX(NULL, 2.0);\nDOUBLE;\n\n# dialect: snowflake\nREGR_VALX(NULL, NULL);\nDOUBLE;\n\n# dialect: snowflake\nREGR_VALX(2.0, NULL);\nDOUBLE;\n\n# dialect: snowflake\nREGR_VALX(1.0, 2.0);\nDOUBLE;\n\n# dialect: snowflake\nREGR_VALX(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_VALX(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nREGR_VALY(1.0, 2.0);\nDOUBLE;\n\n# dialect: snowflake\nREGR_VALY(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: snowflake\nREGR_VALY(tbl.decfloat_col, tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\n'foo' REGEXP 'bar';\nBOOLEAN;\n\n# dialect: snowflake\n'foo' NOT REGEXP 'bar';\nBOOLEAN;\n\n# dialect: snowflake\n'text123' REGEXP '^[a-z]+[0-9]+$';\nBOOLEAN;\n\n# dialect: snowflake\nREGEXP_LIKE('foo', 'bar');\nBOOLEAN;\n\n# dialect: snowflake\nREGEXP_LIKE(NULL, 'bar');\nBOOLEAN;\n\n# dialect: snowflake\nREGEXP_LIKE('foo', 'bar', 'baz');\nBOOLEAN;\n\n# dialect: snowflake\nREGEXP_LIKE('foo', NULL, 'baz');\nBOOLEAN;\n\n# dialect: snowflake\nREGEXP_COUNT('hello world', 'l');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nREGEXP_COUNT('hello world', 'l', 1);\nDECIMAL(38, 0);\n\n# dialect: snowflake\nREGEXP_COUNT('hello world', 'l', 1, 'i');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nREGEXP_EXTRACT_ALL('hello world', 'world');\nARRAY;\n\n# dialect: snowflake\nREGEXP_EXTRACT_ALL('hello world', 'world', 1);\nARRAY;\n\n# dialect: snowflake\nREGEXP_EXTRACT_ALL('hello world', 'world', 1, 1);\nARRAY;\n\n# dialect: snowflake\nREGEXP_EXTRACT_ALL('hello world', 'world', 1, 1, 'i');\nARRAY;\n\n# dialect: snowflake\nREGEXP_EXTRACT_ALL('hello world', 'world', 1, 1, 'i', 0);\nARRAY;\n\n# dialect: snowflake\nREGEXP_INSTR('hello world', 'world');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nREGEXP_INSTR('hello world', 'world', 1, 1, 0);\nDECIMAL(38, 0);\n\n# dialect: snowflake\nREGEXP_INSTR('hello world', 'world', 1, 1, 0, 'i');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nREGEXP_INSTR('hello world', 'world', 1, 1, 0, 'i', 1);\nDECIMAL(38, 0);\n\n# dialect: snowflake\nREGEXP_REPLACE('hello world', 'world', 'universe');\nVARCHAR;\n\n# dialect: snowflake\nREGEXP_REPLACE('hello world', 'world', NULL);\nVARCHAR;\n\n# dialect: snowflake\nREGEXP_REPLACE('hello world', 'world', 'universe', 1, 1, 'i');\nVARCHAR;\n\n# dialect: snowflake\nREGEXP_SUBSTR('hello world', 'world');\nVARCHAR;\n\n# dialect: snowflake\nREGEXP_SUBSTR(NULL, 'world');\nVARCHAR;\n\n# dialect: snowflake\nREGEXP_SUBSTR('hello world', NULL);\nVARCHAR;\n\n# dialect: snowflake\nREGEXP_SUBSTR('hello world', 'world', 1);\nVARCHAR;\n\n# dialect: snowflake\nREGEXP_SUBSTR('hello world', 'world', 1, 1, 'e', NULL);\nVARCHAR;\n\n# dialect: snowflake\nREGEXP_SUBSTR_ALL('hello world', 'world');\nARRAY;\n\n# dialect: snowflake\nREGEXP_SUBSTR_ALL('hello world', 'world', 1);\nARRAY;\n\n# dialect: snowflake\nREGEXP_SUBSTR_ALL('hello world', 'world', 1, 1);\nARRAY;\n\n# dialect: snowflake\nREGEXP_SUBSTR_ALL('hello world', 'world', 1, 1, 'i');\nARRAY;\n\n# dialect: snowflake\nREGEXP_SUBSTR_ALL('hello world', 'world', 1, 1, 'i', 0);\nARRAY;\n\n# dialect: snowflake\nREPEAT('hello', 3);\nVARCHAR;\n\n# dialect: snowflake\nREPEAT(tbl.str_col, 2);\nVARCHAR;\n\n# dialect: snowflake\nREPEAT('hello', NULL);\nVARCHAR;\n\n# dialect: snowflake\nREPLACE(tbl.str_col, 'old', 'new');\nVARCHAR;\n\n# dialect: snowflake\nREPLACE('hello', 'old', NULL);\nVARCHAR;\n\n# dialect: snowflake\nREVERSE('Hello, world!');\nVARCHAR;\n\n# dialect: snowflake\nREVERSE(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nREVERSE(tbl.bin_col);\nBINARY;\n\n# dialect: snowflake\nREVERSE(NULL);\nVARCHAR;\n\n# dialect: snowflake\nROUND(42);\nINT;\n\n# dialect: snowflake\nROUND(tbl.bigint_col, -1);\nBIGINT;\n\n# dialect: snowflake\nROUND(tbl.double_col, 0, 'HALF_TO_EVEN');\nDOUBLE;\n\n# dialect: snowflake\nROUND(CAST(3.14 AS FLOAT), 1);\nFLOAT;\n\n# dialect: snowflake\nROUND(CAST(1.5 AS DECFLOAT), 0);\nDECFLOAT;\n\n# dialect: snowflake\nFLOOR(CAST(3.7 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nFLOOR(CAST(3.7 AS FLOAT));\nFLOAT;\n\n# dialect: snowflake\nFLOOR(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nCEIL(CAST(3.2 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nCEIL(CAST(3.2 AS FLOAT));\nFLOAT;\n\n# dialect: snowflake\nCEIL(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nSQRT(CAST(16 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nSQRT(CAST(16 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nEXP(CAST(2 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nEXP(CAST(2 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nLN(CAST(10 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nLN(CAST(10 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nLOG(CAST(100 AS DECFLOAT), 10);\nDECFLOAT;\n\n# dialect: snowflake\nLOG(CAST(100 AS DOUBLE), 10);\nDOUBLE;\n\n# dialect: snowflake\nPOW(CAST(2 AS DECFLOAT), 3);\nDECFLOAT;\n\n# dialect: snowflake\nPOW(CAST(2 AS DOUBLE), 3);\nDOUBLE;\n\n# dialect: snowflake\nSIN(CAST(1.5 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nSIN(CAST(1.5 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nCOS(CAST(1.5 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nCOS(CAST(1.5 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nTAN(CAST(1.5 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nTAN(CAST(1.5 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nCOT(CAST(1.5 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nCOT(CAST(1.5 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nASIN(CAST(0.5 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nASIN(CAST(0.5 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nACOS(CAST(0.5 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nACOS(CAST(0.5 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nATAN(CAST(1 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nATAN(CAST(1 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nATAN2(CAST(1 AS DECFLOAT), 1);\nDECFLOAT;\n\n# dialect: snowflake\nATAN2(CAST(1 AS DOUBLE), 1);\nDOUBLE;\n\n# dialect: snowflake\nDEGREES(CAST(3.14159 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nDEGREES(CAST(3.14159 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nRADIANS(CAST(180 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nRADIANS(CAST(180 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nTANH(CAST(1 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nTANH(CAST(1 AS DOUBLE));\nDOUBLE;\n\n# dialect: snowflake\nTO_DECFLOAT('123.456');\nDECFLOAT;\n\n# dialect: snowflake\nTO_DECFLOAT('123.456', '999.999');\nDECFLOAT;\n\n# dialect: snowflake\nTRY_TO_DECFLOAT('123.456');\nDECFLOAT;\n\n# dialect: snowflake\nTRY_TO_DECFLOAT('invalid');\nDECFLOAT;\n\n# dialect: snowflake\nTRY_TO_BINARY('48656C6C6F');\nBINARY;\n\n# dialect: snowflake\nTRY_TO_BINARY('48656C6C6F', 'HEX');\nBINARY;\n\n# dialect: snowflake\nTRY_TO_BOOLEAN('true');\nBOOLEAN;\n\n# dialect: snowflake\nTO_DATE('2024-01-31');\nDATE;\n\n# dialect: snowflake\nTO_DATE('2024-01-31', 'AUTO');\nDATE;\n\n# dialect: snowflake\nTRY_TO_DATE('2024-01-31');\nDATE;\n\n# dialect: snowflake\nTRY_TO_DATE('2024-01-31', 'AUTO');\nDATE;\n\n# dialect: snowflake\nTO_DECIMAL('123.45');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTO_DECIMAL('123.45', '999.99');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTO_DECIMAL('123.45', '999.99', 10, 2);\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_DECIMAL('123.45');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_DECIMAL('123.45', '999.99');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_DECIMAL('123.45', '999.99', 10, 2);\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTO_DOUBLE('123.456');\nDOUBLE;\n\n# dialect: snowflake\nTO_DOUBLE('123.456', '999.99');\nDOUBLE;\n\n# dialect: snowflake\nTRY_TO_DOUBLE('123.456');\nDOUBLE;\n\n# dialect: snowflake\nTRY_TO_DOUBLE('123.456', '999.99');\nDOUBLE;\n\n# dialect: snowflake\nTO_FILE(tbl.obj_col);\nFILE;\n\n# dialect: snowflake\nTO_FILE('file.csv');\nFILE;\n\n# dialect: snowflake\nTO_FILE('file.csv', '/relativepath/');\nFILE;\n\n# dialect: snowflake\nTRY_TO_FILE(tbl.obj_col);\nFILE;\n\n# dialect: snowflake\nTRY_TO_FILE('file.csv');\nFILE;\n\n# dialect: snowflake\nTRY_TO_FILE('file.csv', '/relativepath/');\nFILE;\n\n# dialect: snowflake\nTO_NUMBER('123.45');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTO_NUMBER('123.45', '999.99');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTO_NUMBER('123.45', '999.99', 10, 2);\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_NUMBER('123.45');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_NUMBER('123.45', '999.99');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_NUMBER('123.45', '999.99', 10, 2);\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_NUMERIC('123.45');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_NUMERIC('123.45', '999.99');\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTRY_TO_NUMERIC('123.45', '999.99', 10, 2);\nDECIMAL(38, 0);\n\n# dialect: snowflake\nTO_TIME('12:30:00');\nTIME;\n\n# dialect: snowflake\nTO_TIME('12:30:00', 'AUTO');\nTIME;\n\n# dialect: snowflake\nTRY_TO_TIME('12:30:00');\nTIME;\n\n# dialect: snowflake\nTRY_TO_TIME('12:30:00', 'AUTO');\nTIME;\n\n# dialect: snowflake\nTO_TIME('093000', 'HH24MISS');\nTIME;\n\n# dialect: snowflake\nTRY_TO_TIME('093000', 'HH24MISS');\nTIME;\n\n# dialect: snowflake\nTO_TIMESTAMP('2024-01-15 12:30:00');\nTIMESTAMP;\n\n# dialect: snowflake\nTO_TIMESTAMP('2024-01-15 12:30:00', 'AUTO');\nTIMESTAMP;\n\n# dialect: snowflake\nTO_TIMESTAMP_LTZ('2024-01-15 12:30:00');\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nTO_TIMESTAMP_LTZ('2024-01-15 12:30:00', 'AUTO');\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nTO_TIMESTAMP_NTZ('2024-01-15 12:30:00');\nTIMESTAMPNTZ;\n\n# dialect: snowflake\nTO_TIMESTAMP_NTZ('2024-01-15 12:30:00', 'AUTO');\nTIMESTAMPNTZ;\n\n# dialect: snowflake\nTO_TIMESTAMP_TZ('2024-01-15 12:30:00');\nTIMESTAMPTZ;\n\n# dialect: snowflake\nTO_TIMESTAMP_TZ('2024-01-15 12:30:00', 'AUTO');\nTIMESTAMPTZ;\n\n# dialect: snowflake\nTRY_TO_TIMESTAMP('2024-01-15 12:30:00');\nTIMESTAMP;\n\n# dialect: snowflake\nTRY_TO_TIMESTAMP('2024-01-15 12:30:00', 'AUTO');\nTIMESTAMP;\n\n# dialect: snowflake\nTRY_TO_TIMESTAMP_LTZ('2024-01-15 12:30:00');\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nTRY_TO_TIMESTAMP_LTZ('2024-01-15 12:30:00', 'AUTO');\nTIMESTAMPLTZ;\n\n# dialect: snowflake\nTRY_TO_TIMESTAMP_NTZ('2024-01-15 12:30:00');\nTIMESTAMPNTZ;\n\n# dialect: snowflake\nTRY_TO_TIMESTAMP_NTZ('2024-01-15 12:30:00', 'AUTO');\nTIMESTAMPNTZ;\n\n# dialect: snowflake\nTRY_TO_TIMESTAMP_TZ('2024-01-15 12:30:00');\nTIMESTAMPTZ;\n\n# dialect: snowflake\nTRY_TO_TIMESTAMP_TZ('2024-01-15 12:30:00', 'AUTO');\nTIMESTAMPTZ;\n\n# dialect: snowflake\nABS(CAST(-123.456 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nABS(CAST(-123.456 AS FLOAT));\nFLOAT;\n\n# dialect: snowflake\nMOD(CAST(10 AS DECFLOAT), 3);\nDECFLOAT;\n\n# dialect: snowflake\nMOD(CAST(10 AS FLOAT), 3);\nFLOAT;\n\n# dialect: snowflake\nGREATEST(CAST(1 AS FLOAT), CAST(2 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nGREATEST(CAST(2 AS DECFLOAT), CAST(2 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nGREATEST(CAST(1 AS FLOAT), CAST(2 AS FLOAT));\nFLOAT;\n\n# dialect: snowflake\nLEAST(CAST(1 AS FLOAT), CAST(2 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nLEAST(CAST(1 AS DECFLOAT), CAST(2 AS DECFLOAT));\nDECFLOAT;\n\n# dialect: snowflake\nLEAST(CAST(1 AS FLOAT), CAST(2 AS FLOAT));\nFLOAT;\n\n# dialect: snowflake\nSECOND(CAST('08:50:57' AS TIME));\nINT;\n\n# dialect: snowflake\nSQUARE(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nTANH(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nRIGHT('hello world', 5);\nVARCHAR;\n\n# dialect: snowflake\nRIGHT(tbl.str_col, 3);\nSTRING;\n\n# dialect: snowflake\nRIGHT(tbl.bin_col, 3);\nBINARY;\n\n# dialect: snowflake\nRIGHT(tbl.str_col, NULL);\nSTRING;\n\n# dialect: snowflake\nRLIKE('foo', 'bar');\nBOOLEAN;\n\n# dialect: snowflake\nRLIKE(NULL, 'bar');\nBOOLEAN;\n\n# dialect: snowflake\nRLIKE('foo', 'bar', NULL);\nBOOLEAN;\n\n# dialect: snowflake\nRTRIM('  hello world  ');\nVARCHAR;\n\n# dialect: snowflake\nRTRIM(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nRTRIM(NULL);\nVARCHAR;\n\n# dialect: snowflake\nRTRIMMED_LENGTH(' ABCD ');\nINT;\n\n# dialect: snowflake\nSHA1('foo');\nVARCHAR;\n\n# dialect: snowflake\nSHA1(null);\nVARCHAR;\n\n# dialect: snowflake\nSHA1_BINARY('foo');\nBINARY;\n\n# dialect: snowflake\nSHA1_BINARY(null);\nBINARY;\n\n# dialect: snowflake\nSHA1_HEX('foo');\nVARCHAR;\n\n# dialect: snowflake\nSHA1_HEX(null);\nVARCHAR;\n\n# dialect: snowflake\nSHA2('foo');\nVARCHAR;\n\n# dialect: snowflake\nSHA2(null);\nVARCHAR;\n\n# dialect: snowflake\nSHA2('foo', 256);\nVARCHAR;\n\n# dialect: snowflake\nSHA2('foo', null);\nVARCHAR;\n\n# dialect: snowflake\nSHA2_BINARY('foo');\nBINARY;\n\n# dialect: snowflake\nSHA2_BINARY(null);\nBINARY;\n\n# dialect: snowflake\nSHA2_BINARY('foo', 256);\nBINARY;\n\n# dialect: snowflake\nSHA2_BINARY('foo', null);\nBINARY;\n\n# dialect: snowflake\nSHA2_HEX('foo');\nVARCHAR;\n\n# dialect: snowflake\nSHA2_HEX(null);\nVARCHAR;\n\n# dialect: snowflake\nSHA2_HEX('foo', 256);\nVARCHAR;\n\n# dialect: snowflake\nSHA2_HEX('foo', null);\nVARCHAR;\n\n# dialect: snowflake\nSIN(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nSINH(1);\nDOUBLE;\n\n# dialect: snowflake\nSINH(1.5);\nDOUBLE;\n\n# dialect: snowflake\nSIGN(tbl.double_col);\nINT;\n\n# dialect: snowflake\nSKEW(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nSOUNDEX(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nSOUNDEX_P123('test');\nVARCHAR;\n\n# dialect: snowflake\nSPACE(5);\nVARCHAR;\n\n# dialect: snowflake\nSPACE(tbl.int_col);\nVARCHAR;\n\n# dialect: snowflake\nSPACE(NULL);\nVARCHAR;\n\n# dialect: snowflake\nSQRT(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nSPLIT('hello world', ' ');\nARRAY;\n\n# dialect: snowflake\nSPLIT(tbl.str_col, ',');\nARRAY;\n\n# dialect: snowflake\nSPLIT(NULL, ',');\nARRAY;\n\n# dialect: snowflake\nSPLIT_PART('11.22.33', '.', 1);\nVARCHAR;\n\n# dialect: snowflake\nSTRTOK('hello world');\nVARCHAR;\n\n# dialect: snowflake\nSTRTOK('hello world', ' ');\nVARCHAR;\n\n# dialect: snowflake\nSTRTOK('a.b.c', '.', 1);\nVARCHAR;\n\n# dialect: snowflake\nSTARTSWITH('hello world', 'hello');\nBOOLEAN;\n\n# dialect: snowflake\nSTARTSWITH(tbl.str_col, 'test');\nBOOLEAN;\n\n# dialect: snowflake\nSTARTSWITH(tbl.bin_col, tbl.bin_col);\nBOOLEAN;\n\n# dialect: snowflake\nSTARTSWITH(tbl.bin_col, NULL);\nBOOLEAN;\n\n# dialect: snowflake\nSEARCH(line, 'king');\nBOOLEAN;\n\n# dialect: snowflake\nSEARCH((play, line), 'dream');\nBOOLEAN;\n\n# dialect: snowflake\nSEARCH(line, 'king', ANALYZER => 'UNICODE_ANALYZER');\nBOOLEAN;\n\n# dialect: snowflake\nSEARCH(line, 'king', SEARCH_MODE => 'OR');\nBOOLEAN;\n\n# dialect: snowflake\nSEARCH(line, 'king', ANALYZER => 'UNICODE_ANALYZER', SEARCH_MODE => 'AND');\nBOOLEAN;\n\n# dialect: snowflake\nSEARCH_IP(col, '192.168.0.0');\nBOOLEAN;\n\n# dialect: snowflake\nSTDDEV(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nSTDDEV(tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nSTDDEV_POP(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nSTDDEV_POP(tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nSTDDEV_SAMP(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nSTDDEV_SAMP(tbl.double_col) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nSTRTOK_TO_ARRAY('a,b,c', ',');\nARRAY;\n\n# dialect: snowflake\nSUBSTR('hello world', 1, 5);\nVARCHAR;\n\n# dialect: snowflake\nSUBSTR(tbl.str_col, 1, 3);\nSTRING;\n\n# dialect: snowflake\nSUBSTR(tbl.bin_col, 1, 3);\nBINARY;\n\n# dialect: snowflake\nSUBSTR(tbl.str_col, NULL);\nSTRING;\n\n# dialect: snowflake\nTAN(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nTIMEADD(hour, 1, CAST('14:30:45' AS TIME));\nTIME;\n\n# dialect: snowflake\nTIMEADD(minute, 30, CAST('2024-05-09 14:30:45' AS TIMESTAMP));\nTIMESTAMP;\n\n# dialect: snowflake\nTIMEADD(day, 1, CAST('2024-05-09' AS DATE));\nDATE;\n\n# dialect: snowflake\nTIMEADD(hour, 1, CAST('2024-05-09' AS DATE));\nTIMESTAMPNTZ;\n\n# dialect: snowflake\nTIME_FROM_PARTS(14, 30, 45);\nTIME;\n\n# dialect: snowflake\nTIME_FROM_PARTS(14, 30, 45, 123);\nTIME;\n\n# dialect: snowflake\nTIMEFROMPARTS(14, 30, 45);\nTIME;\n\n# dialect: snowflake\nTIMEFROMPARTS(14, 30, 45, 123);\nTIME;\n\n# dialect: snowflake\nTIME_SLICE(tbl.timestamp_col, 15, 'minute');\nTIMESTAMP;\n\n# dialect: snowflake\nTIME_SLICE(tbl.date_col, 1, 'day', 'start');\nDATE;\n\n# dialect: snowflake\nTIMESTAMPADD(DAY, 5, CAST('2008-12-25' AS DATE));\nDATE;\n\n# dialect: snowflake\nTIMESTAMPADD(HOUR, 3, TO_TIME('05:00:00'));\nTIME;\n\n# dialect: snowflake\nTIMESTAMPADD(YEAR, 1, TO_TIMESTAMP('2022-05-08 14:30:00'));\nTIMESTAMP;\n\n# dialect: snowflake\nTRANSLATE('hello world', 'elo', 'XYZ');\nVARCHAR;\n\n# dialect: snowflake\nUNICODE('€');\nINT;\n\n# dialect: snowflake\nWIDTH_BUCKET(tbl.double_col, 0, 100, 10);\nINT;\n\n# dialect: snowflake\nZEROIFNULL(5);\nINT;\n\n# dialect: snowflake\nZEROIFNULL(5::BIGINT);\nBIGINT;\n\n# dialect: snowflake\nZEROIFNULL(5.5);\nDOUBLE;\n\n# dialect: snowflake\nZEROIFNULL(5.5::FLOAT);\nFLOAT;\n\n# dialect: snowflake\nZEROIFNULL(5.12::DECIMAL(10,2));\nDECIMAL(10, 2);\n\n# dialect: snowflake\nTRIM('hello world');\nVARCHAR;\n\n# dialect: snowflake\nTRIM('hello world', 'hello');\nVARCHAR;\n\n# dialect: snowflake\nTRIM(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nTRIM(tbl.str_col, tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nTRIM(NULL);\nVARCHAR;\n\n# dialect: snowflake\nTRY_BASE64_DECODE_BINARY('SGVsbG8=');\nBINARY;\n\n# dialect: snowflake\nTRY_BASE64_DECODE_BINARY('SGVsbG8=', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');\nBINARY;\n\n# dialect: snowflake\nTRY_BASE64_DECODE_STRING('SGVsbG8gV29ybGQ=');\nVARCHAR;\n\n# dialect: snowflake\nTRY_BASE64_DECODE_STRING('SGVsbG8gV29ybGQ=', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/');\nVARCHAR;\n\n# dialect: snowflake\nTRY_HEX_DECODE_BINARY('48656C6C6F');\nBINARY;\n\n# dialect: snowflake\nTRY_HEX_DECODE_STRING('48656C6C6F');\nVARCHAR;\n\n# dialect: snowflake\nUPPER('Hello, world!');\nVARCHAR;\n\n# dialect: snowflake\nUPPER(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nUUID_STRING();\nVARCHAR;\n\n# dialect: snowflake\nUUID_STRING('foo', 'bar');\nVARCHAR;\n\n# dialect: snowflake\nUUID_STRING(null, null);\nVARCHAR;\n\n# dialect: snowflake\nMD5(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nMD5_HEX(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nMD5_BINARY(tbl.str_col);\nBINARY;\n\n# dialect: snowflake\nMD5_NUMBER_LOWER64(tbl.str_col);\nBIGINT;\n\n# dialect: snowflake\nMD5_NUMBER_UPPER64(tbl.str_col);\nBIGINT;\n\n# dialect: snowflake\n'Hello' NOT ILIKE 'h%';\nBOOLEAN;\n\n# dialect: snowflake\n'Hello' ILIKE 'h_llo';\nBOOLEAN;\n\n# dialect: snowflake\ntbl.str_col NOT ILIKE '%x%';\nBOOLEAN;\n\n# dialect: snowflake\n'Hello' NOT LIKE 'H%';\nBOOLEAN;\n\n# dialect: snowflake\n'Hello' LIKE 'H_llo';\nBOOLEAN;\n\n# dialect: snowflake\ntbl.str_col NOT LIKE '%e%';\nBOOLEAN;\n\n# dialect: snowflake\ntbl.str_col LIKE ALL ('H%', '%o');\nBOOLEAN;\n\n# dialect: snowflake\ntbl.str_col LIKE ANY ('H%', '%o');\nBOOLEAN;\n\n# dialect: snowflake\ntbl.str_col ILIKE ANY ('h%', '%x');\nBOOLEAN;\n\n# dialect: snowflake\nLIKE(tbl.str_col, 'pattern');\nBOOLEAN;\n\n# dialect: snowflake\nILIKE(tbl.str_col, 'pattern');\nBOOLEAN;\n\n# dialect: snowflake\nOBJECT_AGG(tbl.str_col, tbl.variant_col);\nOBJECT;\n\n# dialect: snowflake\nPERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY tbl.int_col);\nINT;\n\n# dialect: snowflake\nPERCENTILE_DISC(0.25) WITHIN GROUP (ORDER BY tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nPERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY tbl.int_col);\nINT;\n\n# dialect: snowflake\nPERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nPERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY tbl.bigint_col) OVER (PARTITION BY 1);\nBIGINT;\n\n# dialect: snowflake\nPARSE_IP('192.168.1.1', 'INET');\nOBJECT;\n\n# dialect: snowflake\nMAX(tbl.bigint_col);\nBIGINT;\n\n# dialect: snowflake\nMAX(tbl.int_col);\nINT;\n\n# dialect: snowflake\nMAX(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nMAX(tbl.str_col);\nVARCHAR;\n\n# dialect: snowflake\nMAX(tbl.date_col);\nDATE;\n\n# dialect: snowflake\nMAX(tbl.timestamp_col);\nTIMESTAMP;\n\n# dialect: snowflake\nMAX_BY('foo', tbl.bigint_col);\nVARCHAR;\n\n# dialect: snowflake\nMAX_BY('foo', tbl.bigint_col, 3);\nARRAY;\n\n# dialect: snowflake\nMIN_BY('foo', tbl.bigint_col);\nVARCHAR;\n\n# dialect: snowflake\nMIN_BY('foo', tbl.bigint_col, 3);\nARRAY;\n\n# dialect: snowflake\nAPPROX_PERCENTILE(tbl.bigint_col, 0.5);\nDOUBLE;\n\n# dialect: snowflake\nAPPROX_PERCENTILE(tbl.double_col, 0.5);\nDOUBLE;\n\n# dialect: snowflake\nAPPROX_PERCENTILE(tbl.int_col, 0.9);\nDOUBLE;\n\n# dialect: snowflake\nAPPROX_PERCENTILE(tbl.bigint_col, 0.5) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nAPPROX_PERCENTILE(tbl.double_col, 0.5) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nAPPROX_PERCENTILE(tbl.int_col, 0.9) OVER (PARTITION BY 1);\nDOUBLE;\n\n# dialect: snowflake\nAPPROX_PERCENTILE_COMBINE(tbl.state_col);\nOBJECT;\n\n# dialect: snowflake\nAPPROX_PERCENTILE_ACCUMULATE(tbl.bigint_col);\nOBJECT;\n\n# dialect: snowflake\nAPPROX_PERCENTILE_ACCUMULATE(tbl.double_col);\nOBJECT;\n\n# dialect: snowflake\nAPPROX_PERCENTILE_ACCUMULATE(tbl.int_col);\nOBJECT;\n\n# dialect: snowflake\nAPPROX_PERCENTILE_ESTIMATE(tbl.state_col, 0.5);\nDOUBLE;\n\n# dialect: snowflake\nAPPROX_TOP_K_ACCUMULATE(tbl.str_col, 10);\nOBJECT;\n\n# dialect: snowflake\nAPPROX_TOP_K_COMBINE(tbl.state_col, 10);\nOBJECT;\n\n# dialect: snowflake\nAPPROX_TOP_K_COMBINE(tbl.state_col);\nOBJECT;\n\n# dialect: snowflake\nAPPROX_TOP_K_ESTIMATE(tbl.state_col, 4);\nARRAY;\n\n# dialect: snowflake\nAPPROX_TOP_K_ESTIMATE(tbl.state_col);\nARRAY;\n\n# dialect: snowflake\nAPPROX_COUNT_DISTINCT(tbl.str_col);\nBIGINT;\n\n# dialect: snowflake\nAPPROX_COUNT_DISTINCT(tbl.bigint_col);\nBIGINT;\n\n# dialect: snowflake\nAPPROX_COUNT_DISTINCT(tbl.double_col);\nBIGINT;\n\n# dialect: snowflake\nAPPROX_COUNT_DISTINCT(*);\nBIGINT;\n\n# dialect: snowflake\nAPPROX_COUNT_DISTINCT(DISTINCT tbl.str_col);\nBIGINT;\n\n# dialect: snowflake\nAPPROX_COUNT_DISTINCT(tbl.str_col) OVER (PARTITION BY 1);\nBIGINT;\n\n# dialect: snowflake\nAPPROX_COUNT_DISTINCT(tbl.bigint_col) OVER (PARTITION BY 1);\nBIGINT;\n\n# dialect: snowflake\nAPPROX_COUNT_DISTINCT(tbl.double_col) OVER (PARTITION BY 1);\nBIGINT;\n\n# dialect: snowflake\nAPPROX_TOP_K(tbl.bigint_col);\nARRAY;\n\n# dialect: snowflake\nAPPROX_TOP_K(tbl.str_col);\nARRAY;\n\n# dialect: snowflake\nAPPROX_TOP_K(tbl.str_col, 5);\nARRAY;\n\n# dialect: snowflake\nAPPROX_TOP_K(tbl.str_col, 5, 1000);\nARRAY;\n\n# dialect: snowflake\nMINHASH(5, tbl.int_col);\nVARIANT;\n\n# dialect: snowflake\nMINHASH(5, tbl.int_col, tbl.str_col);\nVARIANT;\n\n# dialect: snowflake\nMINHASH(5, *);\nVARIANT;\n\n# dialect: snowflake\nMINHASH_COMBINE(tbl.variant_col);\nVARIANT;\n\n# dialect: snowflake\nAPPROXIMATE_SIMILARITY(tbl.variant_col);\nDOUBLE;\n\n# dialect: snowflake\nAPPROXIMATE_JACCARD_INDEX(tbl.variant_col);\nDOUBLE;\n\n# dialect: snowflake\nMIN(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nMIN(tbl.int_col);\nINT;\n\n# dialect: snowflake\nMIN(tbl.bigint_col);\nBIGINT;\n\n# dialect: snowflake\nMIN(CAST(100 AS DECIMAL(10,2)));\nDECIMAL(10, 2);\n\n# dialect: snowflake\nMIN(tbl.bigint_col) OVER (PARTITION BY 1);\nBIGINT;\n\n# dialect: snowflake\nVECTOR_COSINE_SIMILARITY([1,2,3], [4,5,6]);\nDOUBLE;\n\n# dialect: snowflake\nVECTOR_INNER_PRODUCT([1,2,3], [4,5,6]);\nDOUBLE;\n\n# dialect: snowflake\nVECTOR_L1_DISTANCE([1,2,3], [4,5,6]);\nDOUBLE;\n\n# dialect: snowflake\nVECTOR_L2_DISTANCE([1,2,3], [4,5,6]);\nDOUBLE;\n\n# dialect: snowflake\nZIPF(1, 10, RANDOM());\nBIGINT;\n\n# dialect: snowflake\nZIPF(2, 100, 1234);\nBIGINT;\n\n# dialect: snowflake\nXMLGET(PARSE_XML('<root><level2>content</level2></root>'), 'level2');\nOBJECT;\n\n# dialect: snowflake\nXMLGET(PARSE_XML('<root><item>a</item><item>b</item></root>'), 'item', 1);\nOBJECT;\n\n# dialect: snowflake\nMODE(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nMODE(tbl.date_col);\nDATE;\n\n# dialect: snowflake\nMODE(tbl.timestamp_col);\nTIMESTAMP;\n\n# dialect: snowflake\nMODE(tbl.bool_col);\nBOOLEAN;\n\n# dialect: snowflake\nMODE(CAST(100 AS DECIMAL(10,2)));\nDECIMAL(10, 2);\n\n# dialect: snowflake\nMODE(tbl.bigint_col) OVER (PARTITION BY 1);\nBIGINT;\n\n# dialect: snowflake\nMODE(CAST(NULL AS INT));\nINT;\n\n# dialect: snowflake\nMODE(tbl.str_col) OVER (PARTITION BY tbl.int_col);\nVARCHAR;\n\n# dialect: snowflake\nVAR_SAMP(tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nVAR_SAMP(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nVAR_SAMP(tbl.int_col);\nNUMBER(38, 6);\n\n# dialect: snowflake\nVARIANCE_SAMP(tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nVARIANCE_SAMP(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nVARIANCE_SAMP(tbl.int_col);\nNUMBER(38, 6);\n\n# dialect: snowflake\nVARIANCE(tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nVARIANCE(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nVARIANCE(tbl.int_col);\nNUMBER(38, 6);\n\n# dialect: snowflake\nVAR_POP(tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nVAR_POP(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nVAR_POP(tbl.int_col);\nNUMBER(38, 6);\n\n# dialect: snowflake\nVARIANCE_POP(tbl.decfloat_col);\nDECFLOAT;\n\n# dialect: snowflake\nVARIANCE_POP(tbl.double_col);\nDOUBLE;\n\n# dialect: snowflake\nVARIANCE_POP(tbl.int_col);\nNUMBER(38, 6);\n\n# dialect: snowflake\nVARIANCE_POP(1::NUMBER(38, 6));\nNUMBER(38, 12);\n\n# dialect: snowflake\nVARIANCE_POP(1::NUMBER(38, 15));\nNUMBER(38, 15);\n\n# dialect: snowflake\nVARIANCE_POP(1::NUMBER(30, 5));\nNUMBER(38, 12);\n\n# dialect: snowflake\nENCRYPT(tbl.str_col, 'passphrase');\nBINARY;\n\n# dialect: snowflake\nENCRYPT(tbl.str_col, 'passphrase', 'aad');\nBINARY;\n\n# dialect: snowflake\nENCRYPT(tbl.str_col, 'passphrase', 'aad', 'AES-GCM');\nBINARY;\n\n# dialect: snowflake\nENCRYPT_RAW(tbl.str_col, tbl.key_col, tbl.iv_col);\nBINARY;\n\n# dialect: snowflake\nENCRYPT_RAW(tbl.str_col, tbl.key_col, tbl.iv_col, tbl.aad_col);\nBINARY;\n\n# dialect: snowflake\nENCRYPT_RAW(tbl.str_col, tbl.key_col, tbl.iv_col, tbl.aad_col, 'AES-GCM');\nBINARY;\n\n# dialect: snowflake\nDECRYPT(tbl.encrypted_col, 'passphrase');\nBINARY;\n\n# dialect: snowflake\nDECRYPT(tbl.encrypted_col, 'passphrase', 'aad');\nBINARY;\n\n# dialect: snowflake\nDECRYPT(tbl.encrypted_col, 'passphrase', 'aad', 'AES-GCM');\nBINARY;\n\n# dialect: snowflake\nDECRYPT_RAW(tbl.encrypted_col, tbl.key_col, tbl.iv_col);\nBINARY;\n\n# dialect: snowflake\nDECRYPT_RAW(tbl.encrypted_col, tbl.key_col, tbl.iv_col, tbl.aad_col);\nBINARY;\n\n# dialect: snowflake\nDECRYPT_RAW(tbl.encrypted_col, tbl.key_col, tbl.iv_col, tbl.aad_col, 'AES-GCM');\nBINARY;\n\n# dialect: snowflake\nDECRYPT_RAW(tbl.encrypted_col, tbl.key_col, tbl.iv_col, tbl.aad_col, 'AES-GCM', HEX_DECODE_BINARY('ff'));\nBINARY;\n\n# dialect: snowflake\nTRY_DECRYPT(tbl.encrypted_col, 'passphrase');\nBINARY;\n\n# dialect: snowflake\nTRY_DECRYPT(tbl.encrypted_col, 'passphrase', 'aad');\nBINARY;\n\n# dialect: snowflake\nTRY_DECRYPT(tbl.encrypted_col, 'passphrase', 'aad', 'AES-GCM');\nBINARY;\n\n# dialect: snowflake\nTRY_DECRYPT_RAW(tbl.encrypted_col, tbl.key_col, tbl.iv_col);\nBINARY;\n\n# dialect: snowflake\nTRY_DECRYPT_RAW(tbl.encrypted_col, tbl.key_col, tbl.iv_col, tbl.aad_col);\nBINARY;\n\n# dialect: snowflake\nTRY_DECRYPT_RAW(tbl.encrypted_col, tbl.key_col, tbl.iv_col, tbl.aad_col, 'AES-GCM');\nBINARY;\n\n# dialect: snowflake\nTRY_DECRYPT_RAW(tbl.encrypted_col, tbl.key_col, tbl.iv_col, tbl.aad_col, 'AES-GCM', HEX_DECODE_BINARY('ff'));\nBINARY;\n\n# dialect: snowflake\nSEQ1();\nINT;\n\n# dialect: snowflake\nSEQ1(1);\nINT;\n\n# dialect: snowflake\nSEQ2();\nINT;\n\n# dialect: snowflake\nSEQ2(1);\nINT;\n\n# dialect: snowflake\nSEQ4();\nINT;\n\n# dialect: snowflake\nSEQ4(1);\nINT;\n\n# dialect: snowflake\nSEQ8();\nBIGINT;\n\n# dialect: snowflake\nSEQ8(1);\nBIGINT;\n\n--------------------------------------\n-- T-SQL\n--------------------------------------\n\n# dialect: tsql\nSYSDATETIMEOFFSET();\nTIMESTAMPTZ;\n\n# dialect: tsql\nRADIANS(90);\nINT;\n\n# dialect: tsql\nSIN(tbl.int_col);\nFLOAT;\n\n# dialect: tsql\nSIN(tbl.float_col);\nFLOAT;\n\n# dialect: tsql\nCOS(tbl.int_col);\nFLOAT;\n\n# dialect: tsql\nCOS(tbl.float_col);\nFLOAT;\n\n# dialect: tsql\nTAN(tbl.int_col);\nFLOAT;\n\n# dialect: tsql\nTAN(tbl.float_col);\nFLOAT;\n\n# dialect: tsql\nCOT(tbl.int_col);\nFLOAT;\n\n# dialect: tsql\nCOT(tbl.float_col);\nFLOAT;\n\n# dialect: tsql\nATN2(tbl.int_col, tbl.int_col);\nFLOAT;\n\n# dialect: tsql\nATN2(tbl.int_col, tbl.float_col);\nFLOAT;\n\n# dialect: tsql\nATN2(tbl.float_col, tbl.int_col);\nFLOAT;\n\n# dialect: tsql\nATN2(tbl.float_col, tbl.float_col);\nFLOAT;\n\n# dialect: tsql \nASIN(tbl.int_col);\nFLOAT;\n\n# dialect: tsql \nASIN(tbl.float_col);\nFLOAT;\n\n# dialect: tsql \nACOS(tbl.int_col);\nFLOAT;\n\n# dialect: tsql \nACOS(tbl.float_col);\nFLOAT;\n\n# dialect: tsql \nATAN(tbl.int_col);\nFLOAT;\n\n# dialect: tsql \nATAN(tbl.float_col);\nFLOAT;\n\n# dialect: tsql\nCURRENT_TIMEZONE();\nNVARCHAR;\n\n# dialect: tsql\nSOUNDEX(tbl.str_col);\nVARCHAR;\n\n# dialect: tsql\nSTUFF(tbl.str_col, tbl.int_col, tbl.int_col, tbl.str_col);\nVARCHAR;\n\n# dialect: tsql\nDEGREES(tbl.int_col);\nINT;\n\n# dialect: tsql\nDEGREES(tbl.float_col);\nFLOAT;\n\n# dialect: tsql\nDEGREES(tbl.bigint_col);\nBIGINT;\n\n# dialect: tsql \nCURRENT_TIMESTAMP;\nDATETIME;\n\n--------------------------------------\n-- MySQL\n--------------------------------------\n\n# dialect: mysql\nDEGREES(tbl.double_col);\nDOUBLE;\n\n# dialect: mysql\nDEGREES(tbl.int_col);\nDOUBLE;\n\n# dialect: mysql\nLOCALTIME;\nDATETIME;\n\n# dialect: mysql\nELT(1, 'a', 'b');\nVARCHAR;\n\n# dialect: mysql\nDAYOFWEEK(tbl.date_col);\nINT;\n\n# dialect: mysql\nDAYOFMONTH(tbl.date_col);\nINT;\n\n# dialect: mysql\nDAYOFYEAR(tbl.date_col);\nINT;\n\n# dialect: mysql\nMONTH(tbl.date_col);\nINT;\n\n# dialect: mysql\nWEEK(tbl.date_col);\nINT;\n\n# dialect: mysql\nWEEK(tbl.date_col, int_col);\nINT;\n\n# dialect: mysql\nQUARTER(tbl.date_col);\nINT;\n\n# dialect: mysql\nHOUR(tbl.time_col);\nINT;\n\n# dialect: mysql\nSECOND(tbl.time_col);\nINT;\n\n# dialect: mysql\nSIN(tbl.int_col);\nDOUBLE;\n\n# dialect: mysql\nSIN(tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nCOS(tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nCOS(tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nTAN(tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nTAN(tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nCOT(tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nCOT(tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nASIN(tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nASIN(tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nACOS(tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nACOS(tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nATAN(tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nATAN(tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nATAN(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nATAN(tbl.int_col, tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nATAN(tbl.double_col, tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nATAN(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nATAN2(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nATAN2(tbl.int_col, tbl.double_col);\nDOUBLE;\n\n# dialect: mysql \nATAN2(tbl.double_col, tbl.int_col);\nDOUBLE;\n\n# dialect: mysql \nATAN2(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: mysql\nVERSION();\nVARCHAR;\n\n# dialect: mysql\nCURRENT_TIMESTAMP();\nDATETIME;\n\n--------------------------------------\n-- DuckDB\n--------------------------------------\n\n# dialect: duckdb\nSHA1(tbl.str_col);\nVARCHAR;\n\n# dialect: duckdb\nSHA256(tbl.str_col);\nVARCHAR;\n\n# dialect: duckdb \nGET_BIT(tbl.str_col, tbl.int_col);\nINT;\n\n# dialect: duckdb\nFACTORIAL(tbl.int_col);\nHUGEINT;\n\n# dialect: duckdb\nSIN(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nSIN(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nASIN(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nASIN(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nCOS(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nCOS(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nACOS(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nACOS(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nCOT(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nCOT(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nTAN(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nTAN(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nATAN(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nATAN(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nATAN2(tbl.int_col, tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nATAN2(tbl.int_col, tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nATAN2(tbl.double_col, tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nATAN2(tbl.double_col, tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nACOSH(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nACOSH(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nASINH(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nASINH(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nATANH(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nTANH(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nTANH(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nCOSH(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nCOSH(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nSINH(tbl.int_col);\nDOUBLE;\n\n# dialect: duckdb\nSINH(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nATANH(tbl.double_col);\nDOUBLE;\n\n# dialect: duckdb\nISINF(tbl.float_col);\nBOOLEAN;\n\n# dialect: duckdb\nREVERSE(tbl.str_col);\nVARCHAR;\n\n# dialect: duckdb\nRANDOM();\nDOUBLE;\n\n# dialect: duckdb\nFORMAT('Benchmark \"{}\" took {} seconds', 'CSV', 42);\nVARCHAR;\n\n# dialect: duckdb\nQUARTER(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb\nQUARTER(tbl.timestamp_col);\nBIGINT;\n\n# dialect: duckdb\nQUARTER(tbl.interval_col);\nBIGINT;\n\n# dialect: duckdb\nQUARTER(tbl.timestamp_tz_col);\nBIGINT;\n\n# dialect: duckdb\nMINUTE(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb \nMONTH(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb \nDAYOFWEEK(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb \nDAYOFYEAR(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb\nEPOCH(tbl.interval_col);\nDOUBLE;\n\n# dialect: duckdb\nDAYOFMONTH(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb\nDAY(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb\nHOUR(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb\nSECOND(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb\nTO_DAYS(tbl.int_col);\nINTERVAL;\n\n# dialect: duckdb\nISODOW(tbl.date_col);\nBIGINT;\n\n# dialect: duckdb\nBIT_LENGTH(tbl.str_col);\nBIGINT;\n\n# dialect: duckdb\nMAKE_TIME(tbl.bigint_col, tbl.bigint_col, tbl.double_col);\nTIME;\n\n# dialect: duckdb\nLENGTH(tbl.str_col);\nBIGINT;\n\n# dialect: duckdb\nTIME_BUCKET(tbl.interval_col, tbl.date_col, tbl.interval_col);\nDATE;\n\n# dialect: duckdb\nTIME_BUCKET(tbl.interval_col, tbl.date_col);\nDATE;\n\n# dialect: duckdb\nTIME_BUCKET(tbl.interval_col, tbl.timestamp_col, tbl.interval_col);\nTIMESTAMP;\n\n# dialect: duckdb\nTIME_BUCKET(tbl.interval_col, tbl.timestamp_col);\nTIMESTAMP;\n\n# dialect: duckdb\nTRANSLATE(tbl.str_col, tbl.str_col, tbl.str_col);\nVARCHAR;\n\n# dialect: duckdb\nCOUNTIF(tbl.int_col > tbl.int_col);\nHUGEINT;\n\n# dialect: duckdb\nDATE_DIFF('year', tbl.timestamp_col, tbl.timestamp_col);\nBIGINT;\n\n# dialect: duckdb\nEXTRACT('hour' FROM tbl.timestamp_col);\nBIGINT;\n\n# dialect: duckdb\nEXTRACT('month' FROM tbl.timestamp_col);\nBIGINT;\n\n--------------------------------------\n-- Presto / Trino\n--------------------------------------\n\n# dialect: presto, trino\nMD5(tbl.bin_col);\nVARBINARY;\n\n# dialect: presto, trino\nLEVENSHTEIN_DISTANCE(tbl.str_col, tbl.str_col);\nBIGINT;\n\n# dialect: presto, trino\nLENGTH(tbl.str_col);\nBIGINT;\n\n# dialect: presto, trino\nPOSITION(tbl.str_col IN tbl.str_col);\nBIGINT;\n\n# dialect: presto, trino\nSTRPOS(tbl.str_col, tbl.str_col);\nBIGINT;\n\n# dialect: presto, trino\nBITWISE_AND(tbl.bigint_col, tbl.bigint_col);\nBIGINT;\n\n# dialect: presto, trino\nBITWISE_NOT(tbl.bigint_col);\nBIGINT;\n\n# dialect: presto, trino\nBITWISE_OR(tbl.bigint_col, tbl.bigint_col);\nBIGINT;\n\n# dialect: presto, trino\nBITWISE_XOR(tbl.bigint_col, tbl.bigint_col);\nBIGINT;\n\n# dialect: presto, trino\nWIDTH_BUCKET(tbl.double_col, tbl.array_col);\nBIGINT;\n\n# dialect: trino\nARRAY_FIRST(ARRAY['a', 'b'], x -> x = 'b');\nVARCHAR;\n"
  },
  {
    "path": "tests/fixtures/optimizer/annotate_types.sql",
    "content": "5;\nINT;\n\n-5;\nINT;\n\n~5;\nINT;\n\n(5);\nINT;\n\n5.3;\nDOUBLE;\n\n'bla';\nVARCHAR;\n\ntrue;\nbool;\n\nnot true;\nbool;\n\nfalse;\nbool;\n\nx is null;\nbool;\n\nx is not null;\nbool;\n\nEXISTS(SELECT 1);\nbool;\n\nALL(SELECT 1);\nbool;\n\nANY(SELECT 1);\nbool;\n\nnull;\nUNKNOWN;\n\n# dialect: spark\nnull;\nNULL;\n\n# dialect: databricks\nnull;\nNULL;\n\nnull and false;\nbool;\n\nnull + 1;\nint;\n\nCASE WHEN x THEN NULL ELSE 1 END;\nINT;\n\nCASE WHEN x THEN 1 ELSE NULL END;\nINT;\n\nIF(true, 1, null);\nINT;\n\nIF(true, null, 1);\nINT;\n\nSTRUCT(1 AS col);\nSTRUCT<col INT>;\n\n# Note: ensure the struct is annotated as UNKNOWN when any of its arguments are UNKNOWN\nSTRUCT(1, f2);\nUNKNOWN;\n\nSTRUCT(1 AS col, 2.5 AS row);\nSTRUCT<col INT, row DOUBLE>;\n\nSTRUCT(1);\nSTRUCT<INT>;\n\nSTRUCT(1 AS col, 2.5 AS row, struct(3.5 AS inner_col, 4 AS inner_row) AS nested_struct);\nSTRUCT<col INT, row DOUBLE, nested_struct STRUCT<inner_col DOUBLE, inner_row INT>>;\n\nSTRUCT(1 AS col, 2.5, ARRAY[1, 2, 3] AS nested_array, 'foo');\nSTRUCT<col INT, DOUBLE, nested_array ARRAY<INT>, VARCHAR>;\n\nSTRUCT(1, 2.5, 'bar');\nSTRUCT<INT, DOUBLE, VARCHAR>;\n\nSTRUCT(1 AS \"CaseSensitive\");\nSTRUCT<\"CaseSensitive\" INT>;\n\n# dialect: duckdb\nSTRUCT_PACK(a := 1, b := 2.5);\nSTRUCT<a INT, b DOUBLE>;\n\n# dialect: presto\nROW(1, 2.5, 'foo');\nSTRUCT<INT, DOUBLE, VARCHAR>;\n\n# dialect: bigquery\nEXTRACT(date from x);\nDATE;\n\n# dialect: bigquery\nEXTRACT(time from x);\nTIME;\n\n# dialect: bigquery\nEXTRACT(day from x);\nINT;\n\nCASE WHEN x THEN CAST(y AS DECIMAL(18, 2)) ELSE NULL END;\nDECIMAL(18,2);\n\nCASE WHEN x THEN NULL ELSE CAST(y AS DECIMAL(18, 2)) END;\nDECIMAL(18,2);\n\n# dialect: bigquery\nCASE WHEN TRUE THEN '2010-01-01' ELSE DATE '2020-02-02' END;\nDATE;\n\n# dialect: bigquery\nCASE WHEN TRUE THEN '2010-01-01' WHEN FALSE THEN DATE '2020-02-02' ELSE '1990-01-01' END;\nDATE;\n\n# dialect: bigquery\nCASE WHEN TRUE THEN DATETIME '2020-02-02 00:00:00' ELSE '2010-01-01' END;\nDATETIME;\n\n# dialect: bigquery\nCASE WHEN TRUE THEN TIMESTAMP '2020-02-02 00:00:00' ELSE '2010-01-01' END;\nTIMESTAMP;\n\n# dialect: bigquery\nNULL;\nINT64;\n\n# dialect: bigquery\nARRAY(SELECT 'foo' UNION ALL SELECT 'bar');\nARRAY<STRING>;\n\n# dialect: bigquery\nARRAY(SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3);\nARRAY<INT64>;\n\n# dialect: bigquery\nARRAY(SELECT 1 UNION ALL SELECT 2.5);\nARRAY<FLOAT64>;\n\n1 + (SELECT 2.5 AS c);\nDOUBLE;\n\n# dialect: bigquery\nCASE WHEN TRUE THEN 2.5 ELSE CAST(3.5 AS BIGNUMERIC) END;\nBIGNUMERIC;\n\n# dialect: bigquery\nCASE WHEN TRUE THEN CAST(3.5 AS BIGNUMERIC) ELSE 2.5 END;\nBIGNUMERIC;\n\n# dialect: bigquery\nCASE WHEN TRUE THEN 3/10 ELSE CAST(3.5 AS BIGNUMERIC) END;\nFLOAT64;\n\n# dialect: bigquery\nCASE WHEN TRUE THEN CAST(3.5 AS BIGNUMERIC) ELSE 3/10 END;\nFLOAT64;\n\n# dialect: bigquery\nCASE WHEN TRUE THEN 2.4 ELSE 2.5 END;\nFLOAT64;\n\n# dialect: bigquery\nCASE WHEN x < y THEN 3/10 WHEN x > y THEN 2 ELSE CAST(3.5 AS BIGNUMERIC) END;\nFLOAT64;\n\n# dialect: bigquery\nCASE WHEN x < y THEN 2 WHEN x > y THEN 3/10 ELSE CAST(3.5 AS BIGNUMERIC) END;\nFLOAT64;\n\n# dialect: bigquery\nCASE WHEN x < y THEN CAST(3.5 AS BIGNUMERIC) WHEN x > y THEN 3/10 ELSE 2 END;\nFLOAT64;\n\n# dialect: snowflake\nBITSHIFTLEFT(255, 4);\nINT;\n\n# dialect: snowflake\nBITSHIFTRIGHT(1024, 2);\nINT;\n\n# dialect: snowflake\nBITSHIFTLEFT(X'FF', 4);\nBINARY;\n\n# dialect: snowflake\nBITSHIFTRIGHT(X'FF', 4);\nBINARY;\n\n# dialect: snowflake\nBITOR(BITSHIFTLEFT(5, 16), BITSHIFTLEFT(3, 8));\nINT;\n\n# dialect: snowflake\nBITAND(BITSHIFTLEFT(255, 4), BITSHIFTLEFT(15, 2));\nINT;\n\n# dialect: bigquery\nCAST(1 AS BIGNUMERIC) + 1.5;\nBIGNUMERIC;\n\n# dialect: bigquery\n1.5 + CAST(1 AS BIGNUMERIC);\nBIGNUMERIC;\n\n# dialect: bigquery\n1.5 + CAST(1 AS FLOAT64);\nFLOAT64;\n\n# dialect: bigquery\nCAST(1 AS FLOAT64) + 1.5;\nFLOAT64;\n\n# dialect: bigquery\nCAST(1 AS INT) + 1.5;\nFLOAT64;\n\n# dialect: bigquery\n1.5 + CAST(1 AS INT);\nFLOAT64;\n\n# dialect: bigquery\nIF(1 = 1, CAST(1 AS BIGNUMERIC) * 1.5, CAST(2 AS BIGNUMERIC));\nBIGNUMERIC;\n"
  },
  {
    "path": "tests/fixtures/optimizer/canonicalize.sql",
    "content": "SELECT w.d + w.e AS c FROM w AS w;\nSELECT CONCAT(\"w\".\"d\", \"w\".\"e\") AS \"c\" FROM \"w\" AS \"w\";\n\nSELECT CAST(w.d AS DATE) > w.e AS a FROM w AS w;\nSELECT CAST(\"w\".\"d\" AS DATE) > CAST(\"w\".\"e\" AS DATE) AS \"a\" FROM \"w\" AS \"w\";\n\nSELECT CAST(1 AS VARCHAR) AS a FROM w AS w;\nSELECT CAST(1 AS VARCHAR) AS \"a\" FROM \"w\" AS \"w\";\n\nSELECT CAST(1 + 3.2 AS DOUBLE) AS a FROM w AS w;\nSELECT 1 + 3.2 AS \"a\" FROM \"w\" AS \"w\";\n\nSELECT '1' + 1 AS \"col\";\nSELECT '1' + 1 AS \"col\";\n\nSELECT '1' + '1' AS \"col\";\nSELECT CONCAT('1', '1') AS \"col\";\n\nSELECT CAST('2022-01-01' AS DATE) + INTERVAL '1' DAY;\nSELECT CAST('2022-01-01' AS DATE) + INTERVAL '1' DAY AS \"_col_0\";\n\nSELECT CAST('2022-01-01' AS DATE) IS NULL AS \"a\";\nSELECT CAST('2022-01-01' AS DATE) IS NULL AS \"a\";\n\n--------------------------------------\n-- Ensure boolean predicates\n--------------------------------------\nSELECT a FROM x WHERE b;\nSELECT \"x\".\"a\" AS \"a\" FROM \"x\" AS \"x\" WHERE \"x\".\"b\" <> 0;\n\nSELECT NOT b FROM x;\nSELECT NOT \"x\".\"b\" <> 0 AS \"_col_0\" FROM \"x\" AS \"x\";\n\nSELECT a FROM x GROUP BY a HAVING SUM(b);\nSELECT \"x\".\"a\" AS \"a\" FROM \"x\" AS \"x\" GROUP BY \"x\".\"a\" HAVING SUM(\"x\".\"b\") <> 0;\n\nSELECT a FROM x GROUP BY a HAVING SUM(b) AND TRUE;\nSELECT \"x\".\"a\" AS \"a\" FROM \"x\" AS \"x\" GROUP BY \"x\".\"a\" HAVING SUM(\"x\".\"b\") <> 0 AND TRUE;\n\nSELECT a FROM x WHERE 1;\nSELECT \"x\".\"a\" AS \"a\" FROM \"x\" AS \"x\" WHERE 1 <> 0;\n\nSELECT a FROM x WHERE COALESCE(0, 1);\nSELECT \"x\".\"a\" AS \"a\" FROM \"x\" AS \"x\" WHERE COALESCE(0 <> 0, 1 <> 0);\n\nSELECT a FROM x WHERE CASE WHEN COALESCE(b, 1) THEN 1 ELSE 0 END;\nSELECT \"x\".\"a\" AS \"a\" FROM \"x\" AS \"x\" WHERE CASE WHEN COALESCE(\"x\".\"b\" <> 0, 1 <> 0) THEN 1 ELSE 0 END <> 0;\n\n--------------------------------------\n-- Replace date functions\n--------------------------------------\nDATE('2023-01-01');\nCAST('2023-01-01' AS DATE);\n\n-- Some dialects only allow dates\nDATE('2023-01-01 00:00:00');\nDATE('2023-01-01 00:00:00');\n\nTIMESTAMP('2023-01-01');\nCAST('2023-01-01' AS TIMESTAMP);\n\nTIMESTAMP('2023-01-01', '12:00:00');\nTIMESTAMP('2023-01-01', '12:00:00');\n\n--------------------------------------\n-- Coerce date function args\n--------------------------------------\n'2023-01-01' + INTERVAL '1' DAY;\nCAST('2023-01-01' AS DATE) + INTERVAL '1' DAY;\n\n'2023-01-01' + INTERVAL '1' HOUR;\nCAST('2023-01-01' AS DATETIME) + INTERVAL '1' HOUR;\n\n'2023-01-01 00:00:01' + INTERVAL '1' HOUR;\nCAST('2023-01-01 00:00:01' AS DATETIME) + INTERVAL '1' HOUR;\n\nCAST('2023-01-01' AS DATE) + INTERVAL '1' HOUR;\nCAST(CAST('2023-01-01' AS DATE) AS DATETIME) + INTERVAL '1' HOUR;\n\nSELECT t.d + INTERVAL '1' HOUR FROM temporal AS t;\nSELECT CAST(\"t\".\"d\" AS DATETIME) + INTERVAL '1' HOUR AS \"_col_0\" FROM \"temporal\" AS \"t\";\n\nDATE_ADD(CAST(\"x\" AS DATE), 1, 'YEAR');\nDATE_ADD(CAST(\"x\" AS DATE), 1, 'YEAR');\n\nDATE_ADD('2023-01-01', 1, 'YEAR');\nDATE_ADD(CAST('2023-01-01' AS DATE), 1, 'YEAR');\n\nDATE_ADD('2023-01-01 00:00:00', 1, 'DAY');\nDATE_ADD(CAST('2023-01-01 00:00:00' AS DATETIME), 1, 'DAY');\n\nSELECT DATE_ADD(t.d, 1, 'HOUR') FROM temporal AS t;\nSELECT DATE_ADD(CAST(\"t\".\"d\" AS DATETIME), 1, 'HOUR') AS \"_col_0\" FROM \"temporal\" AS \"t\";\n\nSELECT DATE_TRUNC('SECOND', t.d) FROM temporal AS t;\nSELECT DATE_TRUNC('SECOND', CAST(\"t\".\"d\" AS DATETIME)) AS \"_col_0\" FROM \"temporal\" AS \"t\";\n\nDATE_TRUNC('DAY', '2023-01-01');\nDATE_TRUNC('DAY', CAST('2023-01-01' AS DATE));\n\nDATEDIFF('2023-01-01', '2023-01-02', DAY);\nDATEDIFF(CAST('2023-01-01' AS DATETIME), CAST('2023-01-02' AS DATETIME), DAY);\n\nSELECT \"t\".\"d\" > '2023-01-01' AS \"d\" FROM \"temporal\" AS \"t\";\nSELECT \"t\".\"d\" > CAST('2023-01-01' AS DATE) AS \"d\" FROM \"temporal\" AS \"t\";\n\nSELECT \"t\".\"d\" > CAST('2023-01-01' AS DATETIME) AS \"d\" FROM \"temporal\" AS \"t\";\nSELECT \"t\".\"d\" > CAST('2023-01-01' AS DATETIME) AS \"d\" FROM \"temporal\" AS \"t\";\n\nSELECT \"t\".\"t\" > '2023-01-01 00:00:01' AS \"t\" FROM \"temporal\" AS \"t\";\nSELECT \"t\".\"t\" > CAST('2023-01-01 00:00:01' AS DATETIME) AS \"t\" FROM \"temporal\" AS \"t\";\n\nWITH \"t\" AS (SELECT CAST(\"ext\".\"created_at\" AS TIMESTAMP) AS \"created_at\" FROM \"ext\" AS \"ext\") SELECT \"t\".\"created_at\" > '2024-10-01 12:05:02' AS \"col\" FROM \"t\" AS \"t\";\nWITH \"t\" AS (SELECT CAST(\"ext\".\"created_at\" AS TIMESTAMP) AS \"created_at\" FROM \"ext\" AS \"ext\") SELECT \"t\".\"created_at\" > CAST('2024-10-01 12:05:02' AS TIMESTAMP) AS \"col\" FROM \"t\" AS \"t\";\n\n# dialect: mysql\nSELECT `t`.`d` < '2023-01-01 00:00:01' AS `col` FROM `temporal` AS `t`;\nSELECT CAST(`t`.`d` AS DATETIME) < CAST('2023-01-01 00:00:01' AS DATETIME) AS `col` FROM `temporal` AS `t`;\n\n# dialect: mysql\nSELECT CAST(`t`.`some_col` AS DATE) < CAST(`t`.`other_col` AS CHAR) AS `col` FROM `other_table` AS `t`;\nSELECT CAST(CAST(`t`.`some_col` AS DATE) AS DATETIME) < CAST(CAST(`t`.`other_col` AS CHAR) AS DATETIME) AS `col` FROM `other_table` AS `t`;\n\n--------------------------------------\n-- Remove redundant casts\n--------------------------------------\nCAST(CAST(\"foo\" AS DECIMAL(4, 2)) AS DECIMAL(8, 4)) AS \"x\";\nCAST(CAST(\"foo\" AS DECIMAL(4, 2)) AS DECIMAL(8, 4)) AS \"x\";\n\nCAST(CAST(\"foo\" AS DECIMAL(4, 2)) AS DECIMAL(4, 2)) AS \"x\";\nCAST(\"foo\" AS DECIMAL(4, 2)) AS \"x\";\n\nCAST(CAST('2023-01-01' AS DATE) AS DATE);\nCAST('2023-01-01' AS DATE);\n\nCAST(DATE_TRUNC('YEAR', CAST('2023-01-01' AS DATE)) AS DATE);\nDATE_TRUNC('YEAR', CAST('2023-01-01' AS DATE));\n\nDATE(DATE_TRUNC('YEAR', CAST(\"x\" AS DATE)));\nDATE_TRUNC('YEAR', CAST(\"x\" AS DATE));\n"
  },
  {
    "path": "tests/fixtures/optimizer/eliminate_ctes.sql",
    "content": "# title: CTE\nWITH q AS (\n  SELECT\n    a\n  FROM x\n)\nSELECT\n  a\nFROM x;\nSELECT\n  a\nFROM x;\n\n# title: Nested CTE\nSELECT\n  a\nFROM (\n  WITH q AS (\n    SELECT\n      a\n    FROM x\n  )\n  SELECT a FROM x\n);\nSELECT\n  a\nFROM (\n  SELECT\n    a\n  FROM x\n);\n\n# title: Chained CTE\nWITH q AS (\n  SELECT\n    a\n  FROM x\n), r AS (\n  SELECT\n    a\n  FROM q\n)\nSELECT\n  a\nFROM x;\nSELECT\n  a\nFROM x;\n\n# title: CTE reference in subquery where alias matches outer table name\nWITH q AS (\n  SELECT\n    a\n  FROM y\n)\nSELECT\n  a\nFROM x AS q\nWHERE\n  a IN (\n    SELECT\n      a\n    FROM q\n  );\nWITH q AS (\n  SELECT\n    a\n  FROM y\n)\nSELECT\n  a\nFROM x AS q\nWHERE\n  a IN (\n    SELECT\n      a\n    FROM q\n  );\n\n# title: CTE reference in subquery where alias matches outer table name and outer alias is also CTE\nWITH q AS (\n  SELECT\n    a\n  FROM y\n), q2 AS (\n  SELECT\n    a\n  FROM y\n)\nSELECT\n  a\nFROM q2 AS q\nWHERE\n  a IN (\n    SELECT\n      a\n    FROM q\n  );\nWITH q AS (\n  SELECT\n    a\n  FROM y\n), q2 AS (\n  SELECT\n    a\n  FROM y\n)\nSELECT\n  a\nFROM q2 AS q\nWHERE\n  a IN (\n    SELECT\n      a\n    FROM q\n  );\n\n\n# Title: Do not remove CTE if it is an RHS of a SEMI/ANTI join\nWITH t1 AS (\n  SELECT\n    1 AS foo\n), t2 AS (\n  SELECT\n    1 AS foo\n)\nSELECT\n  *\nFROM t1\nLEFT ANTI JOIN t2\n  ON t1.foo = t2.foo;\nWITH t1 AS (\n  SELECT\n    1 AS foo\n), t2 AS (\n  SELECT\n    1 AS foo\n)\nSELECT\n  *\nFROM t1\nLEFT ANTI JOIN t2\n  ON t1.foo = t2.foo\n"
  },
  {
    "path": "tests/fixtures/optimizer/eliminate_joins.sql",
    "content": "# title: Remove left join on distinct derived table\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y\n  ON x.b = y.b;\nSELECT\n  x.a\nFROM x;\n\n# title: Remove left join on grouped derived table\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT\n    y.b,\n    SUM(y.c)\n  FROM y\n  GROUP BY y.b\n) AS y\n  ON x.b = y.b;\nSELECT\n  x.a\nFROM x;\n\n# title: Remove left join on aggregate derived table\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT\n    SUM(y.b) AS b\n  FROM y\n) AS y\n  ON x.b = y.b;\nSELECT\n  x.a\nFROM x;\n\n# title: Noop - not all distinct columns in condition\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT DISTINCT\n    y.b,\n    y.c\n  FROM y\n) AS y\n  ON x.b = y.b;\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT DISTINCT\n    y.b,\n    y.c\n  FROM y\n) AS y\n  ON x.b = y.b;\n\n# title: Noop - not all grouped columns in condition\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT\n    y.b,\n    y.c\n  FROM y\n  GROUP BY\n    y.b,\n    y.c\n) AS y\n  ON x.b = y.b;\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT\n    y.b,\n    y.c\n  FROM y\n  GROUP BY\n    y.b,\n    y.c\n) AS y\n  ON x.b = y.b;\n\n# title: Noop - not left join\nSELECT\n  x.a\nFROM x\nJOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y\n  ON x.b = y.b;\nSELECT\n  x.a\nFROM x\nJOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y\n  ON x.b = y.b;\n\n# title: Noop - unqualified columns\nSELECT\n  a\nFROM x\nLEFT JOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y\n  ON x.b = y.b;\nSELECT\n  a\nFROM x\nLEFT JOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y\n  ON x.b = y.b;\n\n# title: Noop - cross join\nSELECT\n  a\nFROM x\nCROSS JOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y;\nSELECT\n  a\nFROM x\nCROSS JOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y;\n\n# title: Noop - column is used\nSELECT\n  x.a,\n  y.b\nFROM x\nLEFT JOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y\n  ON x.b = y.b;\nSELECT\n  x.a,\n  y.b\nFROM x\nLEFT JOIN (\n  SELECT DISTINCT\n    y.b\n  FROM y\n) AS y\n  ON x.b = y.b;\n\n# title: Multiple group by columns\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT\n    y.b AS b,\n    y.c + 1 AS d,\n    COUNT(1)\n  FROM y\n  GROUP BY y.b, y.c + 1\n) AS y\n  ON x.b = y.b\n  AND 1 = y.d;\nSELECT\n  x.a\nFROM x;\n\n# title: Chained left joins\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT\n    y.b AS b\n  FROM y\n  GROUP BY y.b\n) AS y\n  ON x.b = y.b\nLEFT JOIN (\n  SELECT\n    y.b AS c\n  FROM y\n  GROUP BY y.b\n) AS z\n  ON y.b = z.c;\nSELECT\n  x.a\nFROM x;\n\n# title: CTE\nWITH z AS (\n  SELECT DISTINCT\n    y.b\n  FROM y\n)\nSELECT\n  x.a\nFROM x\nLEFT JOIN z\n  ON x.b = z.b;\nWITH z AS (\n  SELECT DISTINCT\n    y.b\n  FROM y\n)\nSELECT\n  x.a\nFROM x;\n\n# title: Noop - Not all grouped expressions are in outputs\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT\n    y.b\n  FROM y\n  GROUP BY\n    y.b,\n    y.c\n) AS y\n  ON x.b = y.b;\nSELECT\n  x.a\nFROM x\nLEFT JOIN (\n  SELECT\n    y.b\n  FROM y\n  GROUP BY\n    y.b,\n    y.c\n) AS y\n  ON x.b = y.b;\n\n# title: Cross join on aggregate derived table\nSELECT\n  x.a\nFROM x\nCROSS JOIN (\n  SELECT\n    SUM(y.b) AS b\n  FROM y\n) AS y;\nSELECT\n  x.a\nFROM x;\n\n# title: Cross join on derived table with LIMIT 1\nSELECT\n  x.a\nFROM x\nCROSS JOIN (\n  SELECT\n    y.b AS b\n  FROM y\n  LIMIT 1\n) AS y;\nSELECT\n  x.a\nFROM x;\n\n# title: Cross join on derived table with no FROM clause\nSELECT\n  x.a\nFROM x\nCROSS JOIN (\n  SELECT\n    1 AS b,\n    2 AS c\n) AS y;\nSELECT\n  x.a\nFROM x;\n\n# title: Noop - cross join on non-aggregate subquery\nSELECT\n  x.a\nFROM x\nCROSS JOIN (\n  SELECT\n    y.b\n  FROM y\n) AS y;\nSELECT\n  x.a\nFROM x\nCROSS JOIN (\n  SELECT\n    y.b\n  FROM y\n) AS y;\n\n\n# title: Do not remove left anti join\nSELECT\n  x.b\nFROM x\nLEFT ANTI JOIN (\n  SELECT\n    1 AS b\n) AS sub\n  ON x.b = sub.b;\nSELECT\n  x.b\nFROM x\nLEFT ANTI JOIN (\n  SELECT\n    1 AS b\n) AS sub\n  ON x.b = sub.b;\n"
  },
  {
    "path": "tests/fixtures/optimizer/eliminate_subqueries.sql",
    "content": "-- No derived tables\nSELECT * FROM x;\nSELECT * FROM x;\n\n-- Unaliased derived tables\nSELECT a FROM (SELECT b FROM (SELECT c FROM x));\nWITH cte AS (SELECT c FROM x), cte_2 AS (SELECT b FROM cte AS cte) SELECT a FROM cte_2 AS cte_2;\n\n-- Joined derived table inside nested derived table\nSELECT b FROM (SELECT b FROM (SELECT b FROM x JOIN (SELECT b FROM y) AS y ON x.b = y.b));\nWITH y_2 AS (SELECT b FROM y), cte AS (SELECT b FROM x JOIN y_2 AS y ON x.b = y.b), cte_2 AS (SELECT b FROM cte AS cte) SELECT b FROM cte_2 AS cte_2;\n\n-- Aliased derived tables\nSELECT a FROM (SELECT b FROM (SELECT c FROM x) AS y) AS z;\nWITH y AS (SELECT c FROM x), z AS (SELECT b FROM y AS y) SELECT a FROM z AS z;\n\n-- Existing CTEs\nWITH q AS (SELECT c FROM x) SELECT a FROM (SELECT b FROM q AS y) AS z;\nWITH q AS (SELECT c FROM x), z AS (SELECT b FROM q AS y) SELECT a FROM z AS z;\n\n-- Derived table inside CTE\nWITH x AS (SELECT a FROM (SELECT a FROM x) AS y) SELECT a FROM x;\nWITH y AS (SELECT a FROM x), x AS (SELECT a FROM y AS y) SELECT a FROM x;\n\n-- Name conflicts with existing outer derived table\nSELECT a FROM (SELECT b FROM (SELECT c FROM x) AS y) AS y;\nWITH y AS (SELECT c FROM x), y_2 AS (SELECT b FROM y AS y) SELECT a FROM y_2 AS y;\n\n-- Name conflicts with outer join\nSELECT a, b FROM (SELECT c FROM (SELECT d FROM x) AS x) AS y JOIN x ON x.a = y.a;\nWITH x_2 AS (SELECT d FROM x), y AS (SELECT c FROM x_2 AS x) SELECT a, b FROM y AS y JOIN x ON x.a = y.a;\n\n-- Name conflicts with table name that is selected in another branch\nSELECT * FROM (SELECT * FROM (SELECT a FROM x) AS x) AS y JOIN (SELECT * FROM x) AS z ON x.a = y.a;\nWITH x_2 AS (SELECT a FROM x), y AS (SELECT * FROM x_2 AS x), z AS (SELECT * FROM x) SELECT * FROM y AS y JOIN z AS z ON x.a = y.a;\n\n-- Name conflicts with table alias\nSELECT a FROM (SELECT a FROM (SELECT a FROM x) AS y) AS z CROSS JOIN q AS y;\nWITH y AS (SELECT a FROM x), z AS (SELECT a FROM y AS y) SELECT a FROM z AS z CROSS JOIN q AS y;\n\n-- Name conflicts with existing CTE\nWITH y AS (SELECT a FROM (SELECT a FROM x) AS y) SELECT a FROM y;\nWITH y_2 AS (SELECT a FROM x), y AS (SELECT a FROM y_2 AS y) SELECT a FROM y;\n\n-- Union of selects with derived tables\n(SELECT a FROM (SELECT b FROM x)) UNION (SELECT a FROM (SELECT b FROM y));\nWITH cte AS (SELECT b FROM x), cte_2 AS (SELECT b FROM y) (SELECT a FROM cte AS cte) UNION (SELECT a FROM cte_2 AS cte_2);\n\n-- Subquery\nSELECT a FROM x WHERE b = (SELECT y.c FROM y);\nSELECT a FROM x WHERE b = (SELECT y.c FROM y);\n\n-- Correlated subquery\nSELECT a FROM x WHERE b = (SELECT c FROM y WHERE y.a = x.a);\nSELECT a FROM x WHERE b = (SELECT c FROM y WHERE y.a = x.a);\n\n-- Duplicate CTE\nSELECT a FROM (SELECT b FROM x) AS y CROSS JOIN (SELECT b FROM x) AS z;\nWITH y AS (SELECT b FROM x) SELECT a FROM y AS y CROSS JOIN y AS z;\n\n-- Doubly duplicate CTE\nSELECT * FROM (SELECT * FROM x JOIN (SELECT * FROM x) AS y) AS z JOIN (SELECT * FROM x JOIN (SELECT * FROM x) AS y) AS q;\nWITH y AS (SELECT * FROM x), z AS (SELECT * FROM x, y AS y) SELECT * FROM z AS z, z AS q;\n\n-- Another duplicate...\nSELECT x.id FROM (SELECT * FROM x AS x JOIN y AS y ON x.id = y.id) AS x JOIN (SELECT * FROM x AS x JOIN y AS y ON x.id = y.id) AS y ON x.id = y.id;\nWITH x_2 AS (SELECT * FROM x AS x JOIN y AS y ON x.id = y.id) SELECT x.id FROM x_2 AS x JOIN x_2 AS y ON x.id = y.id;\n\n-- Root subquery\n(SELECT * FROM (SELECT * FROM x)) LIMIT 1;\n(WITH cte AS (SELECT * FROM x) SELECT * FROM cte AS cte) LIMIT 1;\n\n-- Existing duplicate CTE\nWITH y AS (SELECT a FROM x) SELECT a FROM (SELECT a FROM x) AS y CROSS JOIN y AS z;\nWITH y AS (SELECT a FROM x) SELECT a FROM y AS y CROSS JOIN y AS z;\n\n-- Nested CTE\nWITH cte1 AS (SELECT a FROM x) SELECT a FROM (WITH cte2 AS (SELECT a FROM cte1) SELECT a FROM cte2);\nWITH cte1 AS (SELECT a FROM x), cte2 AS (SELECT a FROM cte1), cte AS (SELECT a FROM cte2 AS cte2) SELECT a FROM cte AS cte;\n\n-- Nested CTE inside CTE\nWITH cte1 AS (WITH cte2 AS (SELECT a FROM x) SELECT t.a FROM cte2 AS t) SELECT a FROM cte1;\nWITH cte2 AS (SELECT a FROM x), cte1 AS (SELECT t.a FROM cte2 AS t) SELECT a FROM cte1;\n\n-- Duplicate CTE nested in CTE\nWITH cte1 AS (SELECT a FROM x), cte2 AS (WITH cte3 AS (SELECT a FROM x) SELECT a FROM cte3) SELECT a FROM cte2;\nWITH cte1 AS (SELECT a FROM x), cte2 AS (SELECT a FROM cte1 AS cte3) SELECT a FROM cte2;\n\n-- Wrapped subquery joined with table\nSELECT * FROM ((SELECT c FROM t1) JOIN t2);\nWITH cte AS (SELECT c FROM t1) SELECT * FROM (cte AS cte, t2);\n\n-- Wrapped subquery with redundant parentheses\nSELECT * FROM (((SELECT * FROM tbl)));\nWITH cte AS (SELECT * FROM tbl) SELECT * FROM cte AS cte;\n"
  },
  {
    "path": "tests/fixtures/optimizer/isolate_table_selects.sql",
    "content": "SELECT * FROM x AS x, y AS y2;\nSELECT * FROM (SELECT * FROM x AS x) AS x, (SELECT * FROM y AS y2) AS y2;\n\nSELECT * FROM x AS x WHERE x = 1;\nSELECT * FROM x AS x WHERE x = 1;\n\nSELECT * FROM x AS x CROSS JOIN y AS y;\nSELECT * FROM (SELECT * FROM x AS x) AS x CROSS JOIN (SELECT * FROM y AS y) AS y;\n\nSELECT * FROM (SELECT 1) AS x CROSS JOIN y AS y;\nSELECT * FROM (SELECT 1) AS x CROSS JOIN (SELECT * FROM y AS y) AS y;\n\nSELECT * FROM x AS x JOIN (SELECT * FROM y) AS y;\nSELECT * FROM (SELECT * FROM x AS x) AS x, (SELECT * FROM y) AS y;\n\nWITH y AS (SELECT *) SELECT * FROM x AS x;\nWITH y AS (SELECT *) SELECT * FROM x AS x;\n\nWITH y AS (SELECT * FROM y AS y2 CROSS JOIN x AS z2) SELECT * FROM x AS x CROSS JOIN y as y;\nWITH y AS (SELECT * FROM (SELECT * FROM y AS y2) AS y2 CROSS JOIN (SELECT * FROM x AS z2) AS z2) SELECT * FROM (SELECT * FROM x AS x) AS x CROSS JOIN y AS y;\n\nSELECT * FROM x AS x CROSS JOIN xx AS y;\nSELECT * FROM (SELECT * FROM x AS x) AS x CROSS JOIN xx AS y;\n"
  },
  {
    "path": "tests/fixtures/optimizer/merge_subqueries.sql",
    "content": "# title: Simple\nSELECT a, b FROM (SELECT a, b FROM x);\nSELECT x.a AS a, x.b AS b FROM x AS x;\n\n# title: Wrap addition in a multiplication\nSELECT c * 2 AS d FROM (SELECT a + b AS c FROM x);\nSELECT (x.a + x.b) * 2 AS d FROM x AS x;\n\n# title: Wrap addition in an addition\n# note: The \"simplify\" rule will unwrap this\nSELECT c + d AS e FROM (SELECT a + b AS c, a AS d FROM x);\nSELECT (x.a + x.b) + x.a AS e FROM x AS x;\n\n# title: Wrap multiplication in an addition\n# note: The \"simplify\" rule will unwrap this\nWITH cte AS (SELECT a * b AS c, a AS d FROM x) SELECT c + d AS e FROM cte;\nSELECT (x.a * x.b) + x.a AS e FROM x AS x;\n\n# title: Don't wrap function\nSELECT 2 * foo AS bar FROM (SELECT CAST(b AS DOUBLE) AS foo FROM x);\nSELECT 2 * CAST(x.b AS DOUBLE) AS bar FROM x AS x;\n\n# title: Don't wrap a wrapped expression\nSELECT foo * 2 AS bar FROM (SELECT (1 + 2 + 3) AS foo FROM x);\nSELECT (1 + 2 + 3) * 2 AS bar FROM x AS x;\n\n# title: Inner table alias is merged\nSELECT a, b FROM (SELECT a, b FROM x AS q) AS r;\nSELECT q.a AS a, q.b AS b FROM x AS q;\n\n# title: Double nesting\nSELECT a, b FROM (SELECT a, b FROM (SELECT a, b FROM x));\nSELECT x.a AS a, x.b AS b FROM x AS x;\n\n# title: WHERE clause is merged\nSELECT a, SUM(b) AS b FROM (SELECT a, b FROM x WHERE a > 1) GROUP BY a;\nSELECT x.a AS a, SUM(x.b) AS b FROM x AS x WHERE x.a > 1 GROUP BY x.a;\n\n# title: Outer query has join\nSELECT a, c FROM (SELECT a, b FROM x WHERE a > 1) AS x JOIN y ON x.b = y.b;\nSELECT x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b WHERE x.a > 1;\n\n# title: Leave tables isolated\n# leave_tables_isolated: true\nSELECT a, c FROM (SELECT a, b FROM x WHERE a > 1) AS x JOIN y ON x.b = y.b;\nSELECT x.a AS a, y.c AS c FROM (SELECT x.a AS a, x.b AS b FROM x AS x WHERE x.a > 1) AS x JOIN y AS y ON x.b = y.b;\n\n# title: Join on derived table\nSELECT a, c FROM x JOIN (SELECT b, c FROM y) AS y ON x.b = y.b;\nSELECT x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\n# title: Inner query has a join\nSELECT a, c FROM (SELECT a, c FROM x JOIN y ON x.b = y.b);\nSELECT x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\n# title: Inner query has conflicting name in outer query\nSELECT a, c FROM (SELECT q.a, q.b FROM x AS q) AS x JOIN y AS q ON x.b = q.b;\nSELECT q_2.a AS a, q.c AS c FROM x AS q_2 JOIN y AS q ON q_2.b = q.b;\n\n# title: Inner query has conflicting name in joined source\nSELECT x.a, q.c FROM (SELECT a, x.b FROM x JOIN y AS q ON x.b = q.b) AS x JOIN y AS q ON x.b = q.b;\nSELECT x.a AS a, q.c AS c FROM x AS x JOIN y AS q_2 ON x.b = q_2.b JOIN y AS q ON x.b = q.b;\n\n# title: Inner query has multiple conflicting names\nSELECT x.a, q.c, r.c FROM (SELECT q.a, r.b FROM x AS q JOIN y AS r ON q.b = r.b) AS x JOIN y AS q ON x.b = q.b JOIN y AS r ON x.b = r.b ORDER BY x.a, q.c, r.c;\nSELECT q_2.a AS a, q.c AS c, r.c AS c FROM x AS q_2 JOIN y AS r_2 ON q_2.b = r_2.b JOIN y AS q ON r_2.b = q.b JOIN y AS r ON r_2.b = r.b ORDER BY q_2.a, q.c, r.c;\n\n# title: Inner queries have conflicting names with each other\nSELECT r.b FROM (SELECT b FROM x AS x) AS q JOIN (SELECT b FROM x) AS r ON q.b = r.b;\nSELECT x_2.b AS b FROM x AS x JOIN x AS x_2 ON x.b = x_2.b;\n\n# title: WHERE clause in joined derived table is merged to ON clause\nSELECT x.a, y.c FROM x JOIN (SELECT b, c FROM y WHERE c > 1) AS y ON x.b = y.b ORDER BY x.a;\nSELECT x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b AND y.c > 1 ORDER BY x.a;\n\n# title: Comma JOIN in outer query\nSELECT x.a, y.c FROM (SELECT a FROM x) AS x, (SELECT c FROM y) AS y;\nSELECT x.a AS a, y.c AS c FROM x AS x, y AS y;\n\n# title: Comma JOIN in inner query\nSELECT x.a, x.c FROM (SELECT x.a, z.c FROM x, y AS z) AS x;\nSELECT x.a AS a, z.c AS c FROM x AS x, y AS z;\n\n# title: (Regression) Column in ORDER BY\nSELECT * FROM (SELECT * FROM (SELECT * FROM x)) ORDER BY a LIMIT 1;\nSELECT x.a AS a, x.b AS b FROM x AS x ORDER BY x.a LIMIT 1;\n\n# title: CTE\nWITH x AS (SELECT a, b FROM main.x) SELECT a, b FROM x;\nSELECT x.a AS a, x.b AS b FROM main.x AS x;\n\n# title: CTE with outer table alias\nWITH y AS (SELECT a, b FROM x) SELECT a, b FROM y AS z;\nSELECT x.a AS a, x.b AS b FROM x AS x;\n\n# title: Nested CTE\nWITH x2 AS (SELECT a FROM main.x), x3 AS (SELECT a FROM x2) SELECT a FROM x3;\nSELECT x.a AS a FROM main.x AS x;\n\n# title: CTE WHERE clause is merged\nWITH x AS (SELECT a, b FROM main.x WHERE a > 1) SELECT a, SUM(b) AS b FROM x GROUP BY a;\nSELECT x.a AS a, SUM(x.b) AS b FROM main.x AS x WHERE x.a > 1 GROUP BY x.a;\n\n# title: CTE Outer query has join\nWITH x2 AS (SELECT a, b FROM x WHERE a > 1) SELECT a, c FROM x2 AS x JOIN y ON x.b = y.b;\nSELECT x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b WHERE x.a > 1;\n\n# title: CTE with inner table alias\nWITH y AS (SELECT a, b FROM x AS q) SELECT a, b FROM y AS z;\nSELECT q.a AS a, q.b AS b FROM x AS q;\n\n# title: Nested CTE\nSELECT * FROM (WITH x AS (SELECT a, b FROM main.x) SELECT a, b FROM x);\nSELECT x.a AS a, x.b AS b FROM main.x AS x;\n\n# title: Inner select is an expression\nSELECT a FROM (SELECT a FROM (SELECT COALESCE(a) AS a FROM x LEFT JOIN y ON x.a = y.b) AS x) AS x;\nSELECT COALESCE(x.a) AS a FROM x AS x LEFT JOIN y AS y ON x.a = y.b;\n\n# title: CTE select is an expression\nWITH x2 AS (SELECT COALESCE(a) AS a FROM x LEFT JOIN y ON x.a = y.b) SELECT a FROM (SELECT a FROM x2 AS x) AS x;\nSELECT COALESCE(x.a) AS a FROM x AS x LEFT JOIN y AS y ON x.a = y.b;\n\n# title: Full outer join\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x WHERE x.b = 1) AS x FULL OUTER JOIN (SELECT y.b AS b FROM y AS y WHERE y.b = 2) AS y ON x.b = y.b;\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x WHERE x.b = 1) AS x FULL OUTER JOIN (SELECT y.b AS b FROM y AS y WHERE y.b = 2) AS y ON x.b = y.b;\n\n# title: Full outer join, no predicates\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x) AS x FULL OUTER JOIN (SELECT y.b AS b FROM y AS y) AS y ON x.b = y.b;\nSELECT x.b AS b, y.b AS b2 FROM x AS x FULL OUTER JOIN y AS y ON x.b = y.b;\n\n# title: Left join\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x WHERE x.b = 1) AS x LEFT JOIN (SELECT y.b AS b FROM y AS y WHERE y.b = 2) AS y ON x.b = y.b;\nSELECT x.b AS b, y.b AS b2 FROM x AS x LEFT JOIN (SELECT y.b AS b FROM y AS y WHERE y.b = 2) AS y ON x.b = y.b WHERE x.b = 1;\n\n# title: Left join, no predicates\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x) AS x LEFT JOIN (SELECT y.b AS b FROM y AS y) AS y ON x.b = y.b;\nSELECT x.b AS b, y.b AS b2 FROM x AS x LEFT JOIN y AS y ON x.b = y.b;\n\n# title: Right join\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x WHERE x.b = 1) AS x RIGHT JOIN (SELECT y.b AS b FROM y AS y WHERE y.b = 2) AS y ON x.b = y.b;\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x WHERE x.b = 1) AS x RIGHT JOIN (SELECT y.b AS b FROM y AS y WHERE y.b = 2) AS y ON x.b = y.b;\n\n# title: Right join, no predicates\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x) AS x RIGHT JOIN (SELECT y.b AS b FROM y AS y) AS y ON x.b = y.b;\nSELECT x.b AS b, y.b AS b2 FROM x AS x RIGHT JOIN y AS y ON x.b = y.b;\n\n# title: Inner join\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x WHERE x.b = 1) AS x INNER JOIN (SELECT y.b AS b FROM y AS y WHERE y.b = 2) AS y ON x.b = y.b;\nSELECT x.b AS b, y.b AS b2 FROM x AS x INNER JOIN y AS y ON x.b = y.b AND y.b = 2 WHERE x.b = 1;\n\n# title: Inner join, no predicates\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x) AS x INNER JOIN (SELECT y.b AS b FROM y AS y) AS y ON x.b = y.b;\nSELECT x.b AS b, y.b AS b2 FROM x AS x INNER JOIN y AS y ON x.b = y.b;\n\n# title: Cross join\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x WHERE x.b = 1) AS x CROSS JOIN (SELECT y.b AS b FROM y AS y WHERE y.b = 2) AS y;\nSELECT x.b AS b, y.b AS b2 FROM x AS x JOIN y AS y ON y.b = 2 WHERE x.b = 1;\n\n# title: Cross join, no predicates\nSELECT x.b AS b, y.b AS b2 FROM (SELECT x.b AS b FROM x AS x) AS x CROSS JOIN (SELECT y.b AS b FROM y AS y) AS y;\nSELECT x.b AS b, y.b AS b2 FROM x AS x CROSS JOIN y AS y;\n\n# title: Broadcast hint\n# dialect: spark\nWITH m AS (SELECT x.a, x.b FROM x), n AS (SELECT y.b, y.c FROM y), joined as (SELECT /*+ BROADCAST(k) */ m.a, k.c FROM m JOIN n AS k ON m.b = k.b) SELECT joined.a, joined.c FROM joined;\nSELECT /*+ BROADCAST(y) */ x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\n# title: Broadcast hint multiple tables\n# dialect: spark\nWITH m AS (SELECT x.a, x.b FROM x), n AS (SELECT y.b, y.c FROM y), joined as (SELECT /*+ BROADCAST(m, n) */ m.a, n.c FROM m JOIN n ON m.b = n.b) SELECT joined.a, joined.c FROM joined;\nSELECT /*+ BROADCAST(x, y) */ x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\n# title: Multiple Table Hints\n# dialect: spark\nWITH m AS (SELECT x.a, x.b FROM x), n AS (SELECT y.b, y.c FROM y), joined as (SELECT /*+ BROADCAST(m), MERGE(m, n) */ m.a, n.c FROM m JOIN n ON m.b = n.b) SELECT joined.a, joined.c FROM joined;\nSELECT /*+ BROADCAST(x), MERGE(x, y) */ x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\n# title: Mix Table and Column Hints\n# dialect: spark\nWITH m AS (SELECT x.a, x.b FROM x), n AS (SELECT y.b, y.c FROM y), joined as (SELECT /*+ BROADCAST(m), MERGE(m, n) */ m.a, n.c FROM m JOIN n ON m.b = n.b) SELECT /*+ COALESCE(3) */ joined.a, joined.c FROM joined;\nSELECT /*+ COALESCE(3), BROADCAST(x), MERGE(x, y) */ x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\n# title: Hint Subquery\n# dialect: spark\nSELECT\n    subquery.a,\n    subquery.c\nFROM (\n    SELECT /*+ BROADCAST(m), MERGE(m, n) */ m.a, n.c FROM (SELECT x.a, x.b FROM x) AS m JOIN (SELECT y.b, y.c FROM y) AS n ON m.b = n.b\n) AS subquery;\nSELECT /*+ BROADCAST(x), MERGE(x, y) */ x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\n# title: Subquery Test\n# dialect: spark\nSELECT /*+ BROADCAST(x) */\n  x.a,\n  x.c\nFROM (\n  SELECT\n    x.a,\n    x.c\n  FROM (\n    SELECT\n      x.a,\n      COUNT(1) AS c\n    FROM x\n    GROUP BY x.a\n  ) AS x\n) AS x;\nSELECT /*+ BROADCAST(x) */ x.a AS a, x.c AS c FROM (SELECT x.a AS a, COUNT(1) AS c FROM x AS x GROUP BY x.a) AS x;\n\n# title: Test preventing merge of window expressions where clause\nwith t1 as (\n  SELECT\n    x.a,\n    x.b,\n    ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num\n  FROM\n    x\n  ORDER BY x.a, x.b, row_num\n)\nSELECT\n  t1.a,\n  t1.b\nFROM\n  t1\nWHERE\n  row_num = 1;\nWITH t1 AS (SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x ORDER BY x.a, x.b, row_num) SELECT t1.a AS a, t1.b AS b FROM t1 AS t1 WHERE t1.row_num = 1;\n\n# title: Test preventing merge of window expressions join clause\nwith t1 as (\n  SELECT\n    x.a,\n    x.b,\n    ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num\n  FROM\n    x\n)\nSELECT\n  t1.a,\n  t1.b\nFROM t1 JOIN y ON t1.a = y.c AND t1.row_num = 1;\nWITH t1 AS (SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x) SELECT t1.a AS a, t1.b AS b FROM t1 AS t1 JOIN y AS y ON t1.a = y.c AND t1.row_num = 1;\n\n# title: Test preventing merge of window expressions agg function\nwith t1 as (\n  SELECT\n    x.a,\n    x.b,\n    ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num\n  FROM\n    x\n)\nSELECT\n  SUM(t1.row_num) as total_rows\nFROM\n  t1;\nWITH t1 AS (SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x) SELECT SUM(t1.row_num) AS total_rows FROM t1 AS t1;\n\n# title: Test prevent merging of window if in group by\nwith t1 as (\n  SELECT\n    x.a,\n    x.b,\n    ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num\n  FROM\n    x\n)\nSELECT\n  t1.row_num AS row_num,\n  SUM(t1.a) AS total\nFROM\n  t1\nGROUP BY t1.row_num\nORDER BY t1.row_num;\nWITH t1 AS (SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x) SELECT t1.row_num AS row_num, SUM(t1.a) AS total FROM t1 AS t1 GROUP BY t1.row_num ORDER BY row_num;\n\n# title: Test prevent merging of window if in order by\nwith t1 as (\n  SELECT\n    x.a,\n    x.b,\n    ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num\n  FROM\n    x\n)\nSELECT\n  t1.row_num AS row_num,\n  t1.a AS a\nFROM\n  t1\nORDER BY t1.row_num, t1.a;\nWITH t1 AS (SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x) SELECT t1.row_num AS row_num, t1.a AS a FROM t1 AS t1 ORDER BY t1.row_num, t1.a;\n\n# title: Test preventing merging of window nested under complex projection if in order by\nWITH t1 AS (\n  SELECT\n    x.a,\n    x.b,\n    ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) - 1 AS row_num\n  FROM\n    x\n)\nSELECT\n  t1.row_num AS row_num,\n  t1.a AS a\nFROM\n  t1\nORDER BY t1.row_num, t1.a;\nWITH t1 AS (SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) - 1 AS row_num FROM x AS x) SELECT t1.row_num AS row_num, t1.a AS a FROM t1 AS t1 ORDER BY t1.row_num, t1.a;\n\n# title: Test allow merging of window function\nwith t1 as (\n  SELECT\n    x.a,\n    x.b,\n    ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num\n  FROM\n    x\n  ORDER BY x.a, x.b, row_num\n)\nSELECT\n  t1.a,\n  t1.b,\n  t1.row_num\nFROM\n  t1;\nSELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x ORDER BY x.a, x.b, row_num;\n\n# title: Keep ORDER BY\n# execute: false\nWITH t AS (SELECT t1.x AS x, t1.y AS y, t2.a AS a, t2.b AS b FROM t1 AS t1(x, y) CROSS JOIN t2 AS t2(a, b) ORDER BY t2.a) SELECT t.x AS x, t.y AS y, t.a AS a, t.b AS b FROM t AS t;\nSELECT t1.x AS x, t1.y AS y, t2.a AS a, t2.b AS b FROM t1 AS t1(x, y) CROSS JOIN t2 AS t2(a, b) ORDER BY t2.a;\n\n# title: Do not merge window functions, inner table is aliased in outer query\nwith t1 as (\n  SELECT\n    ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num\n  FROM\n    x\n)\nSELECT\n  t2.row_num\nFROM\n  t1 AS t2\nWHERE\n  t2.row_num = 2;\nWITH t1 AS (SELECT ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x AS x) SELECT t2.row_num AS row_num FROM t1 AS t2 WHERE t2.row_num = 2;\n\n# title: Values Test\n# dialect: spark\nWITH t1 AS (\n  SELECT\n    a1.cola\n  FROM\n    VALUES (1) AS a1(cola)\n), t2 AS (\n  SELECT\n    a2.cola\n  FROM\n    VALUES (1) AS a2(cola)\n)\nSELECT /*+ BROADCAST(t2) */\n  t1.cola,\n  t2.cola,\nFROM\n  t1\n  JOIN\n  t2\n  ON\n    t1.cola = t2.cola;\nSELECT /*+ BROADCAST(a2) */ a1.cola AS cola, a2.cola AS cola FROM VALUES (1) AS a1(cola) JOIN VALUES (1) AS a2(cola) ON a1.cola = a2.cola;\n\n# title: Nested subquery selects from same table as another subquery\nWITH i AS (\n  SELECT\n    x.a AS a\n  FROM x AS x\n), j AS (\n  SELECT\n    x.a,\n    x.b\n  FROM x AS x\n), k AS (\n  SELECT\n    j.a,\n    j.b\n  FROM j AS j\n)\nSELECT\n  i.a,\n  k.b\nFROM i AS i\nLEFT JOIN k AS k\nON  i.a = k.a;\nSELECT x.a AS a, x_2.b AS b FROM x AS x LEFT JOIN x AS x_2 ON x.a = x_2.a;\n\n# title: Outer select joins on inner select join\nWITH i AS (\n  SELECT\n    x.a AS a\n  FROM y AS y\n  JOIN x AS x\n    ON y.b = x.b\n)\nSELECT\n  x.a AS a\nFROM x AS x\nLEFT JOIN i AS i\n  ON x.a = i.a;\nWITH i AS (SELECT x.a AS a FROM y AS y JOIN x AS x ON y.b = x.b) SELECT x.a AS a FROM x AS x LEFT JOIN i AS i ON x.a = i.a;\n\n# title: Outer scope selects from wrapped table with a join (unknown schema)\n# execute: false\nWITH _q_0 AS (SELECT t1.c AS c FROM t1 AS t1) SELECT * FROM (_q_0 AS _q_0 CROSS JOIN t2 AS t2);\nWITH _q_0 AS (SELECT t1.c AS c FROM t1 AS t1) SELECT * FROM (_q_0 AS _q_0 CROSS JOIN t2 AS t2);\n\n# title: Outer scope selects single column from wrapped table with a join\nWITH _q_0 AS (\n  SELECT\n    x.a AS a\n  FROM x AS x\n), y_2 AS (\n  SELECT\n    y.b AS b\n  FROM y AS y\n)\nSELECT\n  y.b AS b\nFROM (\n  _q_0 AS _q_0\n    JOIN y_2 AS y\n      ON _q_0.a = y.b\n);\nSELECT y.b AS b FROM (x AS x JOIN y AS y ON x.a = y.b);\n\n# title: merge cte into subquery with overlapping alias\nWITH q AS (\n  SELECT\n    y.b AS a\n  FROM y AS y\n)\nSELECT\n  q.a AS a\nFROM x AS q\nWHERE\n  q.a IN (\n    SELECT\n      q.a AS a\n    FROM q AS q\n  );\nSELECT q.a AS a FROM x AS q WHERE q.a IN (SELECT y.b AS a FROM y AS y);\n\n# title: dont merge when inner query has ORDER BY and outer query is UNION\nWITH q AS (\n  SELECT\n    x.a AS a\n  FROM x\n  ORDER BY x.a\n)\nSELECT\n  q.a AS a\nFROM q\nUNION ALL\nSELECT\n  1 AS a;\nWITH q AS (SELECT x.a AS a FROM x AS x ORDER BY x.a) SELECT q.a AS a FROM q AS q UNION ALL SELECT 1 AS a;\n\n# title: Consecutive inner - outer conflicting names\nWITH tbl AS (select 1 as id)\nSELECT\n  id\nFROM (\n  SELECT OTBL.id\n  FROM (\n    SELECT OTBL.id\n    FROM (\n      SELECT OTBL.id\n      FROM tbl AS OTBL\n      LEFT OUTER JOIN tbl AS ITBL ON OTBL.id = ITBL.id\n    ) AS OTBL\n    LEFT OUTER JOIN tbl AS ITBL ON OTBL.id = ITBL.id\n  ) AS OTBL\n  LEFT OUTER JOIN tbl AS ITBL ON OTBL.id = ITBL.id\n) AS ITBL;\nWITH tbl AS (SELECT 1 AS id) SELECT OTBL.id AS id FROM tbl AS OTBL LEFT OUTER JOIN tbl AS ITBL_2 ON OTBL.id = ITBL_2.id LEFT OUTER JOIN tbl AS ITBL_3 ON OTBL.id = ITBL_3.id LEFT OUTER JOIN tbl AS ITBL ON OTBL.id = ITBL.id;\n\n# title: Inner query contains subquery with an alias that conflicts with outer query\nWITH i AS (\n  SELECT\n    a\n  FROM (\n    SELECT 1 a\n  ) AS conflict\n), j AS (\n  SELECT 1 AS a\n)\nSELECT\n  i.a,\n  conflict.a\nFROM i\nLEFT JOIN j AS conflict\n  ON i.a = conflict.a;\nWITH j AS (SELECT 1 AS a) SELECT conflict_2.a AS a, conflict.a AS a FROM (SELECT 1 AS a) AS conflict_2 LEFT JOIN j AS conflict ON conflict_2.a = conflict.a;\n\n# title: column name is not lost\nwith cte as (\n    select x.a * x.b as mult from x\n)\nselect cte.mult from cte;\nSELECT x.a * x.b AS mult FROM x AS x;\n\n# title: avoid merging subquery with JOIN\nWITH t0 AS (\n  SELECT\n    5 AS id\n), t1 AS (\n  SELECT\n    1 AS id,\n    'US' AS cid\n), t2 AS (\n  SELECT\n    1 AS id,\n    'US' AS cid\n)\nSELECT\n  t0.id,\n  t3.cid AS cid\nFROM t0\nINNER JOIN (\n  SELECT\n    t1.id,\n    t2.cid\n  FROM t1\n  RIGHT JOIN t2\n    ON t1.cid = t2.cid\n) AS t3\n  ON t0.id = t3.id;\nWITH t0 AS (SELECT 5 AS id), t1 AS (SELECT 1 AS id, 'US' AS cid), t2 AS (SELECT 1 AS id, 'US' AS cid) SELECT t0.id AS id, t3.cid AS cid FROM t0 AS t0 INNER JOIN (SELECT t1.id AS id, t2.cid AS cid FROM t1 AS t1 RIGHT JOIN t2 AS t2 ON t1.cid = t2.cid) AS t3 ON t0.id = t3.id;\n\n# title: Dont replace GROUP and ORDER BY if expression is literal\nWITH t1 AS (SELECT 1 AS col) SELECT a, SUM(b) AS b FROM (SELECT 6 AS a, col AS b FROM t1) AS t GROUP BY a ORDER BY a;\nWITH t1 AS (SELECT 1 AS col) SELECT 6 AS a, SUM(t1.col) AS b FROM t1 AS t1 GROUP BY a ORDER BY a;\n"
  },
  {
    "path": "tests/fixtures/optimizer/normalize.sql",
    "content": "(A OR B) AND (B OR C) AND (E OR F);\n(A OR B) AND (B OR C) AND (E OR F);\n\n(A AND B) OR (B AND C AND D);\n(A OR C) AND (A OR D) AND B;\n\n(A OR B) AND (A OR C) AND (A OR D) AND (B OR C) AND (B OR D) AND B;\n(A OR C) AND (A OR D) AND B;\n\n(A AND E) OR (B AND C) OR (D AND (E OR F));\n(A OR B OR D) AND (A OR C OR D) AND (B OR D OR E) AND (B OR E OR F) AND (C OR D OR E) AND (C OR E OR F);\n\n(A AND B AND C AND D AND E AND F AND G) OR (H AND I AND J AND K AND L AND M AND N) OR (O AND P AND Q);\n(A AND B AND C AND D AND E AND F AND G) OR (H AND I AND J AND K AND L AND M AND N) OR (O AND P AND Q);\n\nNOT NOT NOT (A OR B);\nNOT A AND NOT B;\n\nA OR B;\nA OR B;\n\nA AND (B AND C);\nA AND B AND C;\n\nA OR (B AND C);\n(A OR B) AND (A OR C);\n\n(A AND B) OR C;\n(A OR C) AND (B OR C);\n\nA OR (B OR (C AND D));\n(A OR B OR C) AND (A OR B OR D);\n\nA OR ((((B OR C) AND (B OR D)) OR C) AND (((B OR C) AND (B OR D)) OR D));\n(A OR B OR C) AND (A OR B OR D);\n\n(A AND B) OR (C AND D);\n(A OR C) AND (A OR D) AND (B OR C) AND (B OR D);\n\n(A AND B) OR (C OR (D AND E));\n(A OR C OR D) AND (A OR C OR E) AND (B OR C OR D) AND (B OR C OR E);\n\nSELECT * FROM x WHERE (A AND B) OR C;\nSELECT * FROM x WHERE (A OR C) AND (B OR C);\n\ndt2 between '2022-01-01 12:00:00' and '2022-12-31' and dt2 >= '2022-05-01 12:00:00' or dt2 = '2021-06-01 12:00:00';\n(dt2 <= '2022-12-31' OR dt2 = '2021-06-01 12:00:00') AND (dt2 = '2021-06-01 12:00:00' OR dt2 >= '2022-01-01 12:00:00') AND (dt2 = '2021-06-01 12:00:00' OR dt2 >= '2022-05-01 12:00:00');\n"
  },
  {
    "path": "tests/fixtures/optimizer/normalize_identifiers.sql",
    "content": "foo;\nfoo;\n\n# dialect: snowflake\nfoo + \"bar\".baz;\nFOO + \"bar\".BAZ;\n\nSELECT a FROM x;\nSELECT a FROM x;\n\n# dialect: snowflake\nSELECT A FROM X;\nSELECT A FROM X;\n\nSELECT \"A\" FROM \"X\";\nSELECT \"A\" FROM \"X\";\n\nSELECT a AS A FROM x;\nSELECT a AS a FROM x;\n\n# dialect: snowflake\nSELECT A AS a FROM X;\nSELECT A AS A FROM X;\n\nSELECT * FROM x;\nSELECT * FROM x;\n\nSELECT A FROM x;\nSELECT a FROM x;\n\n# dialect: snowflake\nSELECT a FROM X;\nSELECT A FROM X;\n\nSELECT a FROM X;\nSELECT a FROM x;\n\n# dialect: snowflake\nSELECT A FROM x;\nSELECT A FROM X;\n\nSELECT A AS A FROM (SELECT a AS A FROM x);\nSELECT a AS a FROM (SELECT a AS a FROM x);\n\nSELECT a AS B FROM x ORDER BY B;\nSELECT a AS b FROM x ORDER BY b;\n\nSELECT A FROM x ORDER BY A;\nSELECT a FROM x ORDER BY a;\n\nSELECT A AS B FROM X GROUP BY A HAVING SUM(B) > 0;\nSELECT a AS b FROM x GROUP BY a HAVING SUM(b) > 0;\n\nSELECT A AS B, SUM(B) AS C FROM X GROUP BY A HAVING C > 0;\nSELECT a AS b, SUM(b) AS c FROM x GROUP BY a HAVING c > 0;\n\nSELECT A FROM X UNION SELECT A FROM X;\nSELECT a FROM x UNION SELECT a FROM x;\n\nSELECT A AS A FROM X UNION SELECT A AS A FROM X;\nSELECT a AS a FROM x UNION SELECT a AS a FROM x;\n\n(SELECT A AS A FROM X);\n(SELECT a AS a FROM x);\n\n# dialect: snowflake\nSELECT a /* sqlglot.meta case_sensitive */, b FROM table /* sqlglot.meta case_sensitive */;\nSELECT a /* sqlglot.meta case_sensitive */, B FROM table /* sqlglot.meta case_sensitive */;\n\n# dialect: redshift\nSELECT COALESCE(json_val.a /* sqlglot.meta case_sensitive */, json_val.A /* sqlglot.meta case_sensitive */) FROM tbl;\nSELECT COALESCE(json_val.a /* sqlglot.meta case_sensitive */, json_val.A /* sqlglot.meta case_sensitive */) FROM tbl;\n\nSELECT @X;\nSELECT @X;\n\n# dialect: bigquery,normalization_strategy=case_insensitive_uppercase\nSELECT `foo`, `BaR` FROM baz CROSS JOIN `bla` CROSS JOIN bloo;\nSELECT `FOO`, `BAR` FROM BAZ CROSS JOIN `BLA` CROSS JOIN BLOO;\n"
  },
  {
    "path": "tests/fixtures/optimizer/optimize_joins.sql",
    "content": "SELECT * FROM x JOIN y ON y.a = 1 JOIN z ON x.a = z.a AND y.a = z.a;\nSELECT * FROM x JOIN z ON x.a = z.a AND TRUE JOIN y ON y.a = 1 AND y.a = z.a;\n\nSELECT * FROM x JOIN y ON y.a = 1 JOIN z ON x.a = z.a;\nSELECT * FROM x JOIN y ON y.a = 1 JOIN z ON x.a = z.a;\n\nSELECT * FROM x CROSS JOIN y JOIN z ON x.a = z.a AND y.a = z.a;\nSELECT * FROM x JOIN z ON x.a = z.a AND TRUE JOIN y ON y.a = z.a;\n\nSELECT * FROM x LEFT JOIN y ON y.a = 1 JOIN z ON x.a = z.a AND y.a = z.a;\nSELECT * FROM x LEFT JOIN y ON y.a = 1 JOIN z ON x.a = z.a AND y.a = z.a;\n\nSELECT * FROM x INNER JOIN z ON x.id = z.id;\nSELECT * FROM x JOIN z ON x.id = z.id;\n\nSELECT * FROM x LEFT OUTER JOIN z;\nSELECT * FROM x LEFT JOIN z ON TRUE;\n\nSELECT * FROM x CROSS JOIN z;\nSELECT * FROM x CROSS JOIN z;\n\nSELECT * FROM x JOIN z;\nSELECT * FROM x CROSS JOIN z;\n\nSELECT * FROM x FULL JOIN z;\nSELECT * FROM x FULL JOIN z ON TRUE;\n\nSELECT * FROM x NATURAL JOIN z;\nSELECT * FROM x NATURAL JOIN z ON TRUE;\n\nSELECT * FROM x RIGHT JOIN z;\nSELECT * FROM x RIGHT JOIN z ON TRUE;\n\nSELECT * FROM x JOIN z USING (id);\nSELECT * FROM x JOIN z USING (id);\n\nSELECT * FROM x CROSS JOIN z ON TRUE;\nSELECT * FROM x CROSS JOIN z;\n\nSELECT * FROM x LEFT ANTI JOIN y ON x.a = y.a; \nSELECT * FROM x LEFT ANTI JOIN y ON x.a = y.a;\n\nSELECT * FROM x LEFT SEMI JOIN y ON x.a = y.a; \nSELECT * FROM x LEFT SEMI JOIN y ON x.a = y.a;\n"
  },
  {
    "path": "tests/fixtures/optimizer/optimizer.sql",
    "content": "# title: lateral\n# execute: false\nSELECT a, m FROM z LATERAL VIEW EXPLODE([1, 2]) q AS m;\nSELECT\n  \"z\".\"a\" AS \"a\",\n  \"q\".\"m\" AS \"m\"\nFROM \"z\" AS \"z\"\nLATERAL VIEW\nEXPLODE(ARRAY(1, 2)) q AS \"m\";\n\n# title: unnest\n# execute: false\nSELECT x FROM UNNEST([1, 2]) AS q(x, y);\nSELECT\n  \"q\".\"x\" AS \"x\"\nFROM UNNEST(ARRAY(1, 2)) AS \"q\"(\"x\", \"y\");\n\n# title: explode_outer\n# dialect: spark\n# execute: false\nCREATE OR REPLACE TEMPORARY VIEW latest_boo AS\nSELECT\n    TRIM(split(points, ':')[0]) as points_type,\n    TRIM(split(points, ':')[1]) as points_value\nFROM (\n         SELECT\n             explode_outer(split(object_pointsText, ',')) as points\n         FROM (\n                  SELECT\n                      object_pointstext,\n                  FROM boo\n              )\n         WHERE object_pointstext IS NOT NULL\n     );\nCREATE OR REPLACE TEMPORARY VIEW `latest_boo` AS\nWITH `_1` AS (\n  SELECT\n    EXPLODE_OUTER(SPLIT(`boo`.`object_pointstext`, ',')) AS `points`\n  FROM `boo` AS `boo`\n  WHERE\n    NOT `boo`.`object_pointstext` IS NULL\n)\nSELECT\n  TRIM(SPLIT(`_1`.`points`, ':')[0]) AS `points_type`,\n  TRIM(SPLIT(`_1`.`points`, ':')[1]) AS `points_value`\nFROM `_1` AS `_1`;\n\n# title: Union in CTE\nWITH cte AS (\n    (\n        SELECT\n            a\n            FROM\n            x\n    )\n    UNION ALL\n    (\n        SELECT\n            b AS a\n        FROM\n            y\n    )\n)\nSELECT\n    *\nFROM\n    cte;\nWITH \"cte\" AS (\n  (\n    SELECT\n      \"x\".\"a\" AS \"a\"\n    FROM \"x\" AS \"x\"\n  )\n  UNION ALL\n  (\n    SELECT\n      \"y\".\"b\" AS \"a\"\n    FROM \"y\" AS \"y\"\n  )\n)\nSELECT\n  \"cte\".\"a\" AS \"a\"\nFROM \"cte\" AS \"cte\";\n\n# title: Chained CTEs\nWITH cte1 AS (\n    SELECT a\n    FROM x\n), cte2 AS (\n    SELECT a + 1 AS a\n    FROM cte1\n)\nSELECT\n    a\nFROM cte1\nUNION ALL\nSELECT\n    a\nFROM cte2;\nWITH \"cte1\" AS (\n  SELECT\n    \"x\".\"a\" AS \"a\"\n  FROM \"x\" AS \"x\"\n)\nSELECT\n  \"cte1\".\"a\" AS \"a\"\nFROM \"cte1\" AS \"cte1\"\nUNION ALL\nSELECT\n  \"cte1\".\"a\" + 1 AS \"a\"\nFROM \"cte1\" AS \"cte1\";\n\n# title: Correlated subquery\nSELECT a, SUM(b) AS sum_b\nFROM (\n    SELECT x.a, y.b\n    FROM x, y\n    WHERE (SELECT max(b) FROM y WHERE x.b = y.b) >= 0 AND x.b = y.b\n) d\nWHERE (TRUE AND TRUE OR 'a' = 'b') AND a > 1\nGROUP BY a;\nWITH \"_u_0\" AS (\n  SELECT\n    MAX(\"y\".\"b\") AS \"_col_0\",\n    \"y\".\"b\" AS \"_u_1\"\n  FROM \"y\" AS \"y\"\n  GROUP BY\n    \"y\".\"b\"\n)\nSELECT\n  \"x\".\"a\" AS \"a\",\n  SUM(\"y\".\"b\") AS \"sum_b\"\nFROM \"x\" AS \"x\"\nJOIN \"y\" AS \"y\"\n  ON \"x\".\"b\" = \"y\".\"b\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"x\".\"b\"\nWHERE\n  \"_u_0\".\"_col_0\" >= 0 AND \"x\".\"a\" > 1\nGROUP BY\n  \"x\".\"a\";\n\n# title: Root subquery\n(SELECT a FROM x) LIMIT 1;\n(\n  SELECT\n    \"x\".\"a\" AS \"a\"\n  FROM \"x\" AS \"x\"\n)\nLIMIT 1;\n\n# title: Root subquery is union\n(SELECT b FROM x UNION SELECT b FROM y ORDER BY b) LIMIT 1;\n(\n  SELECT\n    \"x\".\"b\" AS \"b\"\n  FROM \"x\" AS \"x\"\n  UNION\n  SELECT\n    \"y\".\"b\" AS \"b\"\n  FROM \"y\" AS \"y\"\n  ORDER BY\n    \"b\"\n)\nLIMIT 1;\n\n# title: broadcast\n# dialect: spark\nSELECT /*+ BROADCAST(y) */ x.b FROM x JOIN y ON x.b = y.b;\nSELECT /*+ BROADCAST(`y`) */\n  `x`.`b` AS `b`\nFROM `x` AS `x`\nJOIN `y` AS `y`\n  ON `x`.`b` = `y`.`b`;\n\n# title: aggregate\n# execute: false\nSELECT AGGREGATE(ARRAY(x.a, x.b), 0, (x, acc) -> x + acc + a) AS sum_agg FROM x;\nSELECT\n  AGGREGATE(ARRAY(\"x\".\"a\", \"x\".\"b\"), 0, (\"x\", \"acc\") -> \"x\" + \"acc\" + \"x\".\"a\") AS \"sum_agg\"\nFROM \"x\" AS \"x\";\n\n# title: values\nSELECT cola, colb FROM (VALUES (1, 'test'), (2, 'test2')) AS tab(cola, colb);\nSELECT\n  \"tab\".\"cola\" AS \"cola\",\n  \"tab\".\"colb\" AS \"colb\"\nFROM (VALUES\n  (1, 'test'),\n  (2, 'test2')) AS \"tab\"(\"cola\", \"colb\");\n\n# title: spark values\n# dialect: spark\nSELECT cola, colb FROM (VALUES (1, 'test'), (2, 'test2')) AS tab(cola, colb);\nSELECT\n  `tab`.`cola` AS `cola`,\n  `tab`.`colb` AS `colb`\nFROM VALUES\n  (1, 'test'),\n  (2, 'test2') AS `tab`(`cola`, `colb`);\n\n# title: complex CTE dependencies\nWITH m AS (\n  SELECT a, b FROM (VALUES (1, 2)) AS a1(a, b)\n), n AS (\n  SELECT a, b FROM m WHERE m.a = 1\n), o AS (\n  SELECT a, b FROM m WHERE m.a = 2\n) SELECT\n    n.a,\n    n.b,\n    o.b\nFROM n\nFULL OUTER JOIN o ON n.a = o.a\nCROSS JOIN n AS n2\nWHERE o.b > 0 AND n.a = n2.a;\nWITH \"m\" AS (\n  SELECT\n    \"a1\".\"a\" AS \"a\",\n    \"a1\".\"b\" AS \"b\"\n  FROM (VALUES\n    (1, 2)) AS \"a1\"(\"a\", \"b\")\n), \"n\" AS (\n  SELECT\n    \"m\".\"a\" AS \"a\",\n    \"m\".\"b\" AS \"b\"\n  FROM \"m\" AS \"m\"\n  WHERE\n    \"m\".\"a\" = 1\n), \"o\" AS (\n  SELECT\n    \"m\".\"a\" AS \"a\",\n    \"m\".\"b\" AS \"b\"\n  FROM \"m\" AS \"m\"\n  WHERE\n    \"m\".\"a\" = 2\n)\nSELECT\n  \"n\".\"a\" AS \"a\",\n  \"n\".\"b\" AS \"b\",\n  \"o\".\"b\" AS \"b\"\nFROM \"n\" AS \"n\"\nFULL JOIN \"o\" AS \"o\"\n  ON \"n\".\"a\" = \"o\".\"a\"\nJOIN \"n\" AS \"n2\"\n  ON \"n\".\"a\" = \"n2\".\"a\"\nWHERE\n  \"o\".\"b\" > 0;\n\n# title: Broadcast hint\n# dialect: spark\nWITH m AS (\n  SELECT\n    x.a,\n    x.b\n  FROM x\n), n AS (\n  SELECT\n    y.b,\n    y.c\n  FROM y\n), joined as (\n  SELECT /*+ BROADCAST(n) */\n    m.a,\n    n.c\n  FROM m JOIN n ON m.b = n.b\n)\nSELECT\n  joined.a,\n  joined.c\nFROM joined;\nSELECT /*+ BROADCAST(`y`) */\n  `x`.`a` AS `a`,\n  `y`.`c` AS `c`\nFROM `x` AS `x`\nJOIN `y` AS `y`\n  ON `x`.`b` = `y`.`b`;\n\n# title: Mix Table and Column Hints\n# dialect: spark\nWITH m AS (\n  SELECT\n    x.a,\n    x.b\n  FROM x\n), n AS (\n  SELECT\n    y.b,\n    y.c\n  FROM y\n), joined as (\n  SELECT /*+ BROADCAST(m), MERGE(m, n) */\n    m.a,\n    n.c\n  FROM m JOIN n ON m.b = n.b\n)\nSELECT\n  /*+ COALESCE(3) */\n  joined.a,\n  joined.c\nFROM joined;\nSELECT /*+ COALESCE(3),\n  BROADCAST(`x`),\n  MERGE(`x`, `y`) */\n  `x`.`a` AS `a`,\n  `y`.`c` AS `c`\nFROM `x` AS `x`\nJOIN `y` AS `y`\n  ON `x`.`b` = `y`.`b`;\n\nWITH cte1 AS (\n  WITH cte2 AS (\n    SELECT a, b FROM x\n  )\n  SELECT a1\n  FROM (\n    WITH cte3 AS (SELECT 1)\n    SELECT a AS a1, b AS b1 FROM cte2\n  )\n)\nSELECT a1 FROM cte1;\nSELECT\n  \"x\".\"a\" AS \"a1\"\nFROM \"x\" AS \"x\";\n\n# title: recursive cte\nWITH RECURSIVE cte1 AS (\n  SELECT *\n  FROM (\n      SELECT 1 AS a, 2 AS b\n  ) base\n  CROSS JOIN (SELECT 3 c) y\n  UNION ALL\n  SELECT *\n  FROM cte1\n  WHERE a < 1\n)\nSELECT *\nFROM cte1;\nWITH RECURSIVE \"base\" AS (\n  SELECT\n    1 AS \"a\",\n    2 AS \"b\"\n), \"y\" AS (\n  SELECT\n    3 AS \"c\"\n), \"cte1\" AS (\n  SELECT\n    \"base\".\"a\" AS \"a\",\n    \"base\".\"b\" AS \"b\",\n    \"y\".\"c\" AS \"c\"\n  FROM \"base\" AS \"base\"\n  CROSS JOIN \"y\" AS \"y\"\n  UNION ALL\n  SELECT\n    \"cte1\".\"a\" AS \"a\",\n    \"cte1\".\"b\" AS \"b\",\n    \"cte1\".\"c\" AS \"c\"\n  FROM \"cte1\" AS \"cte1\"\n  WHERE\n    \"cte1\".\"a\" < 1\n)\nSELECT\n  \"cte1\".\"a\" AS \"a\",\n  \"cte1\".\"b\" AS \"b\",\n  \"cte1\".\"c\" AS \"c\"\nFROM \"cte1\" AS \"cte1\";\n\n# title: right join should not push down to from\nSELECT x.a, y.b\nFROM x\nRIGHT JOIN y\nON x.a = y.b\nWHERE x.b = 1;\nSELECT\n  \"x\".\"a\" AS \"a\",\n  \"y\".\"b\" AS \"b\"\nFROM \"x\" AS \"x\"\nRIGHT JOIN \"y\" AS \"y\"\n  ON \"x\".\"a\" = \"y\".\"b\"\nWHERE\n  \"x\".\"b\" = 1;\n\n# title: right join can push down to itself\nSELECT x.a, y.b\nFROM x\nRIGHT JOIN y\nON x.a = y.b\nWHERE y.b = 1;\nWITH \"y_2\" AS (\n  SELECT\n    \"y\".\"b\" AS \"b\"\n  FROM \"y\" AS \"y\"\n  WHERE\n    \"y\".\"b\" = 1\n)\nSELECT\n  \"x\".\"a\" AS \"a\",\n  \"y\".\"b\" AS \"b\"\nFROM \"x\" AS \"x\"\nRIGHT JOIN \"y_2\" AS \"y\"\n  ON \"x\".\"a\" = \"y\".\"b\";\n\n\n# title: lateral column alias reference\nSELECT x.a + 1 AS c, c + 1 AS d FROM x;\nSELECT\n  \"x\".\"a\" + 1 AS \"c\",\n  \"x\".\"a\" + 2 AS \"d\"\nFROM \"x\" AS \"x\";\n\n# title: column reference takes priority over lateral column alias reference\nSELECT x.a + 1 AS b, b + 1 AS c FROM x;\nSELECT\n  \"x\".\"a\" + 1 AS \"b\",\n  \"x\".\"b\" + 1 AS \"c\"\nFROM \"x\" AS \"x\";\n\n# title: unqualified struct element is selected in the outer query\n# execute: false\nWITH \"cte\" AS (\n  SELECT\n    FROM_JSON(\"value\", 'STRUCT<f1: STRUCT<f2: STRUCT<f3: STRUCT<f4: STRING>>>>') AS \"struct\"\n  FROM \"tbl\"\n) SELECT \"struct\".\"f1\".\"f2\".\"f3\".\"f4\" AS \"f4\" FROM \"cte\";\nSELECT\n  FROM_JSON(\"tbl\".\"value\", 'STRUCT<f1: STRUCT<f2: STRUCT<f3: STRUCT<f4: STRING>>>>').\"f1\".\"f2\".\"f3\".\"f4\" AS \"f4\"\nFROM \"tbl\" AS \"tbl\";\n\n# title: qualified struct element is selected in the outer query\n# execute: false\nWITH \"cte\" AS (\n  SELECT\n    FROM_JSON(\"value\", 'STRUCT<f1: STRUCT<f2: INTEGER>, STRUCT<f3: STRING>>') AS \"struct\"\n  FROM \"tbl\"\n) SELECT \"cte\".\"struct\".\"f1\".\"f2\" AS \"f2\", \"cte\".\"struct\".\"f1\".\"f3\" AS \"f3\" FROM \"cte\";\nSELECT\n  FROM_JSON(\"tbl\".\"value\", 'STRUCT<f1: STRUCT<f2: INTEGER>, STRUCT<f3: STRING>>').\"f1\".\"f2\" AS \"f2\",\n  FROM_JSON(\"tbl\".\"value\", 'STRUCT<f1: STRUCT<f2: INTEGER>, STRUCT<f3: STRING>>').\"f1\".\"f3\" AS \"f3\"\nFROM \"tbl\" AS \"tbl\";\n\n# title: left join doesnt push down predicate to join in merge subqueries\n# execute: false\nSELECT\n  main_query.id,\n  main_query.score\nFROM (\n  SELECT\n    alias_1.id,\n    score\n  FROM (\n    SELECT\n      company_table.score AS score,\n      id\n    FROM company_table\n  ) AS alias_1\n  JOIN (\n    SELECT\n      id\n    FROM (\n      SELECT\n        company_table_2.id,\n        CASE WHEN unlocked.company_id IS NULL THEN 0 ELSE 1 END AS is_exported\n      FROM company_table AS company_table_2\n      LEFT JOIN unlocked AS unlocked\n        ON company_table_2.id = unlocked.company_id\n    )\n    WHERE\n      NOT id IS NULL AND is_exported = FALSE\n  ) AS alias_2\n    ON (\n      alias_1.id = alias_2.id\n    )\n) AS main_query;\nWITH \"alias_2\" AS (\n  SELECT\n    \"company_table_2\".\"id\" AS \"id\"\n  FROM \"company_table\" AS \"company_table_2\"\n  LEFT JOIN \"unlocked\" AS \"unlocked\"\n    ON \"company_table_2\".\"id\" = \"unlocked\".\"company_id\"\n  WHERE\n    CASE WHEN \"unlocked\".\"company_id\" IS NULL THEN 0 ELSE 1 END = FALSE\n    AND NOT \"company_table_2\".\"id\" IS NULL\n)\nSELECT\n  \"company_table\".\"id\" AS \"id\",\n  \"company_table\".\"score\" AS \"score\"\nFROM \"company_table\" AS \"company_table\"\nJOIN \"alias_2\" AS \"alias_2\"\n  ON \"alias_2\".\"id\" = \"company_table\".\"id\";\n\n# title: db.table alias clash\n# execute: false\nselect * from db1.tbl, db2.tbl;\nSELECT\n  *\nFROM \"db1\".\"tbl\" AS \"tbl\"\nCROSS JOIN \"db2\".\"tbl\" AS \"tbl_2\";\n\n# execute: false\nSELECT\n*,\nIFF(\n  IFF(\n\tuploaded_at >= '2022-06-16',\n\t'workday',\n\t'bamboohr'\n  ) = source_system,\n  1,\n  0\n) AS sort_order\nFROM\nunioned\nWHERE\n(\n  source_system = 'workday'\n  AND '9999-01-01' >= '2022-06-16'\n)\nOR (\n  source_system = 'bamboohr'\n  AND '0001-01-01' < '2022-06-16'\n) QUALIFY ROW_NUMBER() OVER (\n  PARTITION BY unique_filter_key\n  ORDER BY\n\tsort_order DESC,\n\t1\n) = 1;\nSELECT\n  *,\n  IFF(\n    \"unioned\".\"source_system\" = IFF(\"unioned\".\"uploaded_at\" >= '2022-06-16', 'workday', 'bamboohr'),\n    1,\n    0\n  ) AS \"sort_order\"\nFROM \"unioned\" AS \"unioned\"\nWHERE\n  \"unioned\".\"source_system\" = 'bamboohr' OR \"unioned\".\"source_system\" = 'workday'\nQUALIFY\n  ROW_NUMBER() OVER (\n    PARTITION BY \"unioned\".\"unique_filter_key\"\n    ORDER BY \"unioned\".\"sort_order\" DESC, 1\n  ) = 1;\n\n# title: pivoted source with explicit selections\n# execute: false\nSELECT * FROM (SELECT a, b, c FROM sc.tb) PIVOT (SUM(c) FOR b IN ('x','y','z'));\nSELECT\n  \"_1\".\"a\" AS \"a\",\n  \"_1\".\"x\" AS \"x\",\n  \"_1\".\"y\" AS \"y\",\n  \"_1\".\"z\" AS \"z\"\nFROM (\n  SELECT\n    \"tb\".\"a\" AS \"a\",\n    \"tb\".\"b\" AS \"b\",\n    \"tb\".\"c\" AS \"c\"\n  FROM \"sc\".\"tb\" AS \"tb\"\n) AS \"_0\"\nPIVOT(SUM(\"_0\".\"c\") FOR \"_0\".\"b\" IN ('x', 'y', 'z')) AS \"_1\";\n\n# title: pivoted source with explicit selections where one of them is excluded & selected at the same time\n# note: we need to respect the exclude when selecting * from pivoted source and not include the computed column twice\n# execute: false\nSELECT * EXCEPT (x), CAST(x AS TEXT) AS x FROM (SELECT a, b, c FROM sc.tb) PIVOT (SUM(c) FOR b IN ('x','y','z'));\nSELECT\n  \"_1\".\"a\" AS \"a\",\n  \"_1\".\"y\" AS \"y\",\n  \"_1\".\"z\" AS \"z\",\n  CAST(\"_1\".\"x\" AS TEXT) AS \"x\"\nFROM (\n  SELECT\n    \"tb\".\"a\" AS \"a\",\n    \"tb\".\"b\" AS \"b\",\n    \"tb\".\"c\" AS \"c\"\n  FROM \"sc\".\"tb\" AS \"tb\"\n) AS \"_0\"\nPIVOT(SUM(\"_0\".\"c\") FOR \"_0\".\"b\" IN ('x', 'y', 'z')) AS \"_1\";\n\n# title: pivoted source with implicit selections\n# execute: false\nSELECT * FROM (SELECT * FROM u) PIVOT (SUM(f) FOR h IN ('x', 'y'));\nSELECT\n  \"_1\".\"g\" AS \"g\",\n  \"_1\".\"x\" AS \"x\",\n  \"_1\".\"y\" AS \"y\"\nFROM (\n  SELECT\n    \"u\".\"f\" AS \"f\",\n    \"u\".\"g\" AS \"g\",\n    \"u\".\"h\" AS \"h\"\n  FROM \"u\" AS \"u\"\n) AS \"_0\"\nPIVOT(SUM(\"_0\".\"f\") FOR \"_0\".\"h\" IN ('x', 'y')) AS \"_1\";\n\n# title: selecting explicit qualified columns from pivoted source with explicit selections\n# execute: false\nSELECT piv.x, piv.y FROM (SELECT f, h FROM u) PIVOT (SUM(f) FOR h IN ('x', 'y')) AS piv;\nSELECT\n  \"piv\".\"x\" AS \"x\",\n  \"piv\".\"y\" AS \"y\"\nFROM (\n  SELECT\n    \"u\".\"f\" AS \"f\",\n    \"u\".\"h\" AS \"h\"\n  FROM \"u\" AS \"u\"\n) AS \"_0\"\nPIVOT(SUM(\"_0\".\"f\") FOR \"_0\".\"h\" IN ('x', 'y')) AS \"piv\";\n\n# title: selecting explicit unqualified columns from pivoted source with implicit selections\n# execute: false\nSELECT x, y FROM u PIVOT (SUM(f) FOR h IN ('x', 'y'));\nSELECT\n  \"_0\".\"x\" AS \"x\",\n  \"_0\".\"y\" AS \"y\"\nFROM \"u\" AS \"u\"\nPIVOT(SUM(\"u\".\"f\") FOR \"u\".\"h\" IN ('x', 'y')) AS \"_0\";\n\n# title: selecting all columns from a pivoted CTE source, using alias for the aggregation and generating bigquery\n# execute: false\n# dialect: bigquery\nWITH u_cte(f, g, h) AS (SELECT * FROM u) SELECT * FROM u_cte PIVOT(SUM(f) AS sum FOR h IN ('x', 'y'));\nWITH `u_cte` AS (\n  SELECT\n    `u`.`f` AS `f`,\n    `u`.`g` AS `g`,\n    `u`.`h` AS `h`\n  FROM `u` AS `u`\n)\nSELECT\n  `_0`.`g` AS `g`,\n  `_0`.`sum_x` AS `sum_x`,\n  `_0`.`sum_y` AS `sum_y`\nFROM `u_cte` AS `u_cte`\nPIVOT(SUM(`u_cte`.`f`) AS `sum` FOR `u_cte`.`h` IN ('x', 'y')) AS `_0`;\n\n# title: selecting all columns from a pivoted source and generating snowflake\n# execute: false\n# dialect: snowflake\nSELECT * FROM u PIVOT (SUM(f) FOR h IN ('x', 'y'));\nSELECT\n  \"_0\".\"G\" AS \"G\",\n  \"_0\".\"'x'\" AS \"'x'\",\n  \"_0\".\"'y'\" AS \"'y'\"\nFROM \"U\" AS \"U\"\nPIVOT(SUM(\"U\".\"F\") FOR \"U\".\"H\" IN ('x', 'y')) AS \"_0\";\n\n# title: selecting all columns from a pivoted source and generating spark\n# note: spark doesn't allow pivot aliases or qualified columns for the pivot's \"field\" (`h`)\n# execute: false\n# dialect: spark\nSELECT * FROM u PIVOT (SUM(f) FOR h IN ('x', 'y'));\nSELECT\n  `_0`.`g` AS `g`,\n  `_0`.`x` AS `x`,\n  `_0`.`y` AS `y`\nFROM (\n  SELECT\n    *\n  FROM `u` AS `u`\n  PIVOT(SUM(`u`.`f`) FOR `h` IN ('x', 'y'))\n) AS `_0`;\n\n# title: selecting all columns from a pivoted source, pivot has column aliases\n# execute: false\n# dialect: snowflake\nWITH source AS (\n  SELECT\n    id,\n    key,\n    value,\n    timestamp_1,\n    timestamp_2\n  FROM DB_NAME.SCHEMA_NAME.TABLE_NAME\n),\nenriched AS (\n  SELECT * FROM source\n  PIVOT(MAX(value) FOR key IN ('a', 'b', 'c'))\n  AS final (id, timestamp_1, timestamp_2, col_1, col_2, col_3)\n)\nSELECT id, timestamp_1 FROM enriched;\nWITH \"SOURCE\" AS (\n  SELECT\n    \"TABLE_NAME\".\"ID\" AS \"ID\",\n    \"TABLE_NAME\".\"KEY\" AS \"KEY\",\n    \"TABLE_NAME\".\"VALUE\" AS \"VALUE\",\n    \"TABLE_NAME\".\"TIMESTAMP_1\" AS \"TIMESTAMP_1\",\n    \"TABLE_NAME\".\"TIMESTAMP_2\" AS \"TIMESTAMP_2\"\n  FROM \"DB_NAME\".\"SCHEMA_NAME\".\"TABLE_NAME\" AS \"TABLE_NAME\"\n)\nSELECT\n  \"FINAL\".\"ID\" AS \"ID\",\n  \"FINAL\".\"TIMESTAMP_1\" AS \"TIMESTAMP_1\"\nFROM \"SOURCE\" AS \"SOURCE\"\nPIVOT(MAX(\"SOURCE\".\"VALUE\") FOR \"SOURCE\".\"KEY\" IN ('a', 'b', 'c')) AS \"FINAL\"(\"ID\", \"TIMESTAMP_1\", \"TIMESTAMP_2\", \"COL_1\", \"COL_2\", \"COL_3\");\n\n# title: unpivoted table source with a single value column, unpivot columns can't be qualified\n# execute: false\n# dialect: snowflake\nSELECT * FROM m_sales AS m_sales(empid, dept, jan, feb) UNPIVOT(sales FOR month IN (jan, feb)) ORDER BY empid;\nSELECT\n  \"M_SALES\".\"EMPID\" AS \"EMPID\",\n  \"M_SALES\".\"DEPT\" AS \"DEPT\",\n  \"M_SALES\".\"MONTH\" AS \"MONTH\",\n  \"M_SALES\".\"SALES\" AS \"SALES\"\nFROM \"M_SALES\" AS \"M_SALES\"(\"EMPID\", \"DEPT\", \"JAN\", \"FEB\")\nUNPIVOT(\"SALES\" FOR \"MONTH\" IN (\"JAN\", \"FEB\")) AS \"M_SALES\"\nORDER BY\n  \"M_SALES\".\"EMPID\";\n\n# title: unpivoted table source, unpivot has column aliases\n# execute: false\nSELECT * FROM (SELECT * FROM m_sales) AS m_sales(empid, dept, jan, feb) UNPIVOT(sales FOR month IN (jan, feb)) AS unpiv(a, b, c, d);\nSELECT\n  \"unpiv\".\"a\" AS \"a\",\n  \"unpiv\".\"b\" AS \"b\",\n  \"unpiv\".\"c\" AS \"c\",\n  \"unpiv\".\"d\" AS \"d\"\nFROM (\n  SELECT\n    \"m_sales\".\"empid\" AS \"empid\",\n    \"m_sales\".\"dept\" AS \"dept\",\n    \"m_sales\".\"jan\" AS \"jan\",\n    \"m_sales\".\"feb\" AS \"feb\"\n  FROM \"m_sales\" AS \"m_sales\"\n) AS \"m_sales\"\nUNPIVOT(\"sales\" FOR \"month\" IN (\"m_sales\".\"jan\", \"m_sales\".\"feb\")) AS \"unpiv\"(\"a\", \"b\", \"c\", \"d\");\n\n# title: unpivoted derived table source with a single value column\n# execute: false\n# dialect: snowflake\nSELECT * FROM (SELECT * FROM m_sales) AS m_sales(empid, dept, jan, feb) UNPIVOT(sales FOR month IN (jan, feb)) ORDER BY empid;\nSELECT\n  \"_0\".\"EMPID\" AS \"EMPID\",\n  \"_0\".\"DEPT\" AS \"DEPT\",\n  \"_0\".\"MONTH\" AS \"MONTH\",\n  \"_0\".\"SALES\" AS \"SALES\"\nFROM (\n  SELECT\n    \"M_SALES\".\"EMPID\" AS \"EMPID\",\n    \"M_SALES\".\"DEPT\" AS \"DEPT\",\n    \"M_SALES\".\"JAN\" AS \"JAN\",\n    \"M_SALES\".\"FEB\" AS \"FEB\"\n  FROM \"M_SALES\" AS \"M_SALES\"\n) AS \"M_SALES\"\nUNPIVOT(\"SALES\" FOR \"MONTH\" IN (\"JAN\", \"FEB\")) AS \"_0\"\nORDER BY\n  \"_0\".\"EMPID\";\n\n# title: unpivoted table source with a single value column, unpivot columns can be qualified\n# execute: false\n# dialect: bigquery\n# note: the named columns aren not supported by BQ but we add them here to avoid defining a schema\nSELECT * FROM produce AS produce(product, q1, q2, q3, q4) UNPIVOT(sales FOR quarter IN (q1, q2, q3, q4));\nSELECT\n  `produce`.`product` AS `product`,\n  `produce`.`quarter` AS `quarter`,\n  `produce`.`sales` AS `sales`\nFROM `produce` AS `produce`\nUNPIVOT(`sales` FOR `quarter` IN (`produce`.`q1`, `produce`.`q2`, `produce`.`q3`, `produce`.`q4`)) AS `produce`;\n\n# title: unpivoted table source with multiple value columns\n# execute: false\n# dialect: bigquery\nSELECT * FROM produce AS produce(product, q1, q2, q3, q4) UNPIVOT((first_half_sales, second_half_sales) FOR semesters IN ((Q1, Q2) AS 'semester_1', (Q3, Q4) AS 'semester_2'));\nSELECT\n  `produce`.`product` AS `product`,\n  `produce`.`semesters` AS `semesters`,\n  `produce`.`first_half_sales` AS `first_half_sales`,\n  `produce`.`second_half_sales` AS `second_half_sales`\nFROM `produce` AS `produce`\nUNPIVOT((`first_half_sales`, `second_half_sales`) FOR \n  `semesters` IN (\n    (`produce`.`q1`, `produce`.`q2`) AS 'semester_1',\n    (`produce`.`q3`, `produce`.`q4`) AS 'semester_2'\n  )\n) AS `produce`;\n\n# title: quoting is preserved\n# dialect: snowflake\nwith cte1(\"id\", foo) as (select 1, 2) select \"id\" from cte1;\nWITH \"CTE1\" AS (\n  SELECT\n    1 AS \"id\"\n)\nSELECT\n  \"CTE1\".\"id\" AS \"id\"\nFROM \"CTE1\" AS \"CTE1\";\n\n# title: ensures proper quoting happens after all optimizations\n# execute: false\nSELECT \"foO\".x FROM (SELECT 1 AS x) AS \"foO\";\nWITH \"foO\" AS (\n  SELECT\n    1 AS \"x\"\n)\nSELECT\n  \"foO\".\"x\" AS \"x\"\nFROM \"foO\" AS \"foO\";\n\n# title: lateral subquery\n# execute: false\n# dialect: postgres\nSELECT u.user_id, l.log_date\nFROM   users u\nCROSS JOIN LATERAL (\n    SELECT l.log_date\n    FROM   logs l\n    WHERE  l.user_id = u.user_id AND l.log_date <= 100\n    ORDER  BY l.log_date DESC NULLS LAST\n    LIMIT  1\n) l;\nSELECT\n  \"u\".\"user_id\" AS \"user_id\",\n  \"l\".\"log_date\" AS \"log_date\"\nFROM \"users\" AS \"u\"\nCROSS JOIN LATERAL (\n  SELECT\n    \"l\".\"log_date\" AS \"log_date\"\n  FROM \"logs\" AS \"l\"\n  WHERE\n    \"l\".\"log_date\" <= 100 AND \"l\".\"user_id\" = \"u\".\"user_id\"\n  ORDER BY\n    \"l\".\"log_date\" DESC NULLS LAST\n  LIMIT 1\n) AS \"l\";\n\n# title: bigquery column identifiers are case-insensitive\n# execute: false\n# dialect: bigquery\nWITH cte AS (\n    SELECT\n        refresh_date AS `reFREsh_date`,\n        term AS `TeRm`,\n        `rank`\n    FROM `bigquery-public-data.GooGle_tReNDs.TOp_TeRmS`\n)\nSELECT\n   refresh_date AS `Day`,\n   term AS Top_Term,\n   rank,\nFROM cte\nWHERE\n   rank = 1\n   AND refresh_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 2 WEEK)\nGROUP BY `dAy`, `top_term`, rank\nORDER BY `DaY` DESC;\nSELECT\n  `top_terms`.`refresh_date` AS `day`,\n  `top_terms`.`term` AS `top_term`,\n  `top_terms`.`rank` AS `rank`\nFROM `bigquery-public-data.GooGle_tReNDs.TOp_TeRmS` AS `top_terms`\nWHERE\n  `top_terms`.`rank` = 1\n  AND `top_terms`.`refresh_date` >= DATE_SUB(CURRENT_DATE, INTERVAL '2' WEEK)\nGROUP BY\n  `day`,\n  `top_term`,\n  `rank`\nORDER BY\n  `day` DESC;\n\n\n# title: group by keys cannot be simplified\nSELECT a + 1 + 1 + 1 + 1 AS b, 2 + 1 AS c FROM x GROUP BY a + 1 + 1 HAVING a + 1 + 1 + 1 + 1 > 1;\nSELECT\n  \"x\".\"a\" + 1 + 1 + 1 + 1 AS \"b\",\n  3 AS \"c\"\nFROM \"x\" AS \"x\"\nGROUP BY\n  \"x\".\"a\" + 1 + 1\nHAVING\n  \"x\".\"a\" + 1 + 1 + 1 + 1 > 1;\n\n# title: replace alias with mult expression without wrapping it\nWITH cte AS (SELECT a * b AS c, a AS d, b as e FROM x) SELECT c + d - (c - e) AS f FROM cte;\nSELECT\n  \"x\".\"a\" * \"x\".\"b\" + \"x\".\"a\" - (\n    \"x\".\"a\" * \"x\".\"b\" - \"x\".\"b\"\n  ) AS \"f\"\nFROM \"x\" AS \"x\";\n\n# title: wrapped table without alias\n# execute: false\nSELECT * FROM (tbl);\nSELECT\n  *\nFROM (\n  \"tbl\" AS \"tbl\"\n);\n\n# title: wrapped table with alias\n# execute: false\nSELECT * FROM (tbl AS tbl);\nSELECT\n  *\nFROM (\n  \"tbl\" AS \"tbl\"\n);\n\n# title: wrapped join of tables without alias\nSELECT a, c FROM (x LEFT JOIN y ON a = c);\nSELECT\n  \"x\".\"a\" AS \"a\",\n  \"y\".\"c\" AS \"c\"\nFROM (\n  \"x\" AS \"x\"\n    LEFT JOIN \"y\" AS \"y\"\n      ON \"x\".\"a\" = \"y\".\"c\"\n);\n\n# title: wrapped join of tables with alias\n# execute: false\nSELECT a, c FROM (x LEFT JOIN y ON a = c) AS t;\nSELECT\n  \"x\".\"a\" AS \"a\",\n  \"y\".\"c\" AS \"c\"\nFROM \"x\" AS \"x\"\nLEFT JOIN \"y\" AS \"y\"\n  ON \"x\".\"a\" = \"y\".\"c\";\n\n# title: chained wrapped joins without aliases\n# execute: false\nSELECT * FROM ((a CROSS JOIN ((b CROSS JOIN c) CROSS JOIN (d CROSS JOIN e))));\nSELECT\n  *\nFROM (\n  (\n    \"a\" AS \"a\"\n      CROSS JOIN (\n        (\n          \"b\" AS \"b\"\n            CROSS JOIN \"c\" AS \"c\"\n        )\n        CROSS JOIN (\n          \"d\" AS \"d\"\n            CROSS JOIN \"e\" AS \"e\"\n        )\n      )\n  )\n);\n\n# title: chained wrapped joins with aliases\n# execute: false\nSELECT * FROM ((a AS foo CROSS JOIN b AS bar) CROSS JOIN c AS baz);\nSELECT\n  *\nFROM (\n  (\n    \"a\" AS \"foo\"\n      CROSS JOIN \"b\" AS \"bar\"\n  )\n  CROSS JOIN \"c\" AS \"baz\"\n);\n\n# title: table joined with join construct\nSELECT x.a, y.b, z.c FROM x LEFT JOIN (y INNER JOIN z ON y.c = z.c) ON x.b = y.b;\nSELECT\n  \"x\".\"a\" AS \"a\",\n  \"y\".\"b\" AS \"b\",\n  \"z\".\"c\" AS \"c\"\nFROM \"x\" AS \"x\"\nLEFT JOIN (\n  \"y\" AS \"y\"\n    JOIN \"z\" AS \"z\"\n      ON \"y\".\"c\" = \"z\".\"c\"\n)\n  ON \"x\".\"b\" = \"y\".\"b\";\n\n# title: select * from table joined with join construct\n# execute: false\nSELECT * FROM x LEFT JOIN (y INNER JOIN z ON y.c = z.c) ON x.b = y.b;\nSELECT\n  \"y\".\"b\" AS \"b\",\n  \"y\".\"c\" AS \"c\",\n  \"z\".\"a\" AS \"a\",\n  \"z\".\"c\" AS \"c\",\n  \"x\".\"a\" AS \"a\",\n  \"x\".\"b\" AS \"b\"\nFROM \"x\" AS \"x\"\nLEFT JOIN (\n  \"y\" AS \"y\"\n    JOIN \"z\" AS \"z\"\n      ON \"y\".\"c\" = \"z\".\"c\"\n)\n  ON \"x\".\"b\" = \"y\".\"b\";\n\n# title: select * from wrapped subquery\n# execute: false\nSELECT * FROM ((SELECT * FROM tbl));\nWITH \"_0\" AS (\n  SELECT\n    *\n  FROM \"tbl\" AS \"tbl\"\n)\nSELECT\n  *\nFROM (\n  \"_0\" AS \"_0\"\n);\n\n# title: select * from wrapped subquery joined to a table (known schema)\nSELECT * FROM ((SELECT * FROM x) INNER JOIN y ON a = c);\nSELECT\n  \"x\".\"a\" AS \"a\",\n  \"x\".\"b\" AS \"b\",\n  \"y\".\"b\" AS \"b\",\n  \"y\".\"c\" AS \"c\"\nFROM (\n  \"x\" AS \"x\"\n    JOIN \"y\" AS \"y\"\n      ON \"x\".\"a\" = \"y\".\"c\"\n);\n\n# title: select * from wrapped subquery joined to a table (unknown schema)\n# execute: false\nSELECT * FROM ((SELECT c FROM t1) JOIN t2);\nWITH \"_0\" AS (\n  SELECT\n    \"t1\".\"c\" AS \"c\"\n  FROM \"t1\" AS \"t1\"\n)\nSELECT\n  *\nFROM (\n  \"_0\" AS \"_0\"\n    CROSS JOIN \"t2\" AS \"t2\"\n);\n\n# title: select specific columns from wrapped subquery joined to a table\nSELECT b FROM ((SELECT a FROM x) INNER JOIN y ON a = b);\nSELECT\n  \"y\".\"b\" AS \"b\"\nFROM (\n  \"x\" AS \"x\"\n    JOIN \"y\" AS \"y\"\n      ON \"x\".\"a\" = \"y\".\"b\"\n);\n\n# title: select * from wrapped join of subqueries (unknown schema)\n# execute: false\nSELECT * FROM ((SELECT * FROM t1) JOIN (SELECT * FROM t2));\nWITH \"_0\" AS (\n  SELECT\n    *\n  FROM \"t1\" AS \"t1\"\n), \"_1\" AS (\n  SELECT\n    *\n  FROM \"t2\" AS \"t2\"\n)\nSELECT\n  *\nFROM (\n  \"_0\" AS \"_0\"\n    CROSS JOIN \"_1\" AS \"_1\"\n);\n\n# title: select * from wrapped join of subqueries (known schema)\nSELECT * FROM ((SELECT * FROM x) INNER JOIN (SELECT * FROM y) ON a = c);\nSELECT\n  \"x\".\"a\" AS \"a\",\n  \"x\".\"b\" AS \"b\",\n  \"y\".\"b\" AS \"b\",\n  \"y\".\"c\" AS \"c\"\nFROM (\n  \"x\" AS \"x\"\n    JOIN \"y\" AS \"y\"\n      ON \"x\".\"a\" = \"y\".\"c\"\n);\n\n# title: replace scalar subquery, wrap resulting column in a MAX\nSELECT a, SUM(c) / (SELECT SUM(c) FROM y) * 100 AS foo FROM y INNER JOIN x ON y.b = x.b GROUP BY a;\nWITH \"_u_0\" AS (\n  SELECT\n    SUM(\"y\".\"c\") AS \"_col_0\"\n  FROM \"y\" AS \"y\"\n)\nSELECT\n  \"x\".\"a\" AS \"a\",\n  SUM(\"y\".\"c\") / MAX(\"_u_0\".\"_col_0\") * 100 AS \"foo\"\nFROM \"y\" AS \"y\"\nCROSS JOIN \"_u_0\" AS \"_u_0\"\nJOIN \"x\" AS \"x\"\n  ON \"x\".\"b\" = \"y\".\"b\"\nGROUP BY\n  \"x\".\"a\";\n\n# title: select * from a cte, which had one of its two columns aliased\nWITH cte(x, y) AS (SELECT 1, 2) SELECT * FROM cte AS cte(a);\nWITH \"cte\" AS (\n  SELECT\n    1 AS \"x\",\n    2 AS \"y\"\n)\nSELECT\n  \"cte\".\"a\" AS \"a\",\n  \"cte\".\"y\" AS \"y\"\nFROM \"cte\" AS \"cte\"(\"a\");\n\n# title: select single column from a cte using its alias\nWITH cte(x) AS (SELECT 1) SELECT a FROM cte AS cte(a);\nWITH \"cte\" AS (\n  SELECT\n    1 AS \"x\"\n)\nSELECT\n  \"cte\".\"a\" AS \"a\"\nFROM \"cte\" AS \"cte\"(\"a\");\n\n# title: joined ctes with a \"using\" clause, one of which has had its column aliased\nWITH m(a) AS (SELECT 1), n(b) AS (SELECT 1) SELECT * FROM m JOIN n AS foo(a) USING (a);\nWITH \"m\" AS (\n  SELECT\n    1 AS \"a\"\n), \"n\" AS (\n  SELECT\n    1 AS \"b\"\n)\nSELECT\n  COALESCE(\"m\".\"a\", \"foo\".\"a\") AS \"a\"\nFROM \"m\" AS \"m\"\nJOIN \"n\" AS \"foo\"(\"a\")\n  ON \"foo\".\"a\" = \"m\".\"a\";\n\n# title: reduction of string concatenation that uses CONCAT(..), || and +\n# execute: false\nSELECT CONCAT('a', 'b') || CONCAT(CONCAT('c', 'd'), CONCAT('e', 'f')) + ('g' || 'h' || 'i');\nSELECT\n  'abcdefghi' AS \"_col_0\";\n\n# title: complex query with derived tables and redundant parentheses\n# execute: false\n# dialect: snowflake\nSELECT\n  (\"SUBQUERY_0\".\"KEY\") AS \"SUBQUERY_1_COL_0\"\nFROM\n  (\n    SELECT\n      *\n    FROM\n      (((\n          SELECT\n            *\n          FROM\n            (\n              SELECT\n                event_name AS key,\n                insert_ts\n              FROM\n                (\n                  SELECT\n                    insert_ts,\n                    event_name\n                  FROM\n                    sales\n                  WHERE\n                    insert_ts > '2023-08-07 21:03:35.590 -0700'\n                )\n            )\n      ))) AS \"SF_CONNECTOR_QUERY_ALIAS\"\n  ) AS \"SUBQUERY_0\";\nSELECT\n  \"SALES\".\"EVENT_NAME\" AS \"SUBQUERY_1_COL_0\"\nFROM \"SALES\" AS \"SALES\"\nWHERE\n  \"SALES\".\"INSERT_TS\" > '2023-08-07 21:03:35.590 -0700';\n\n# title: using join without select *\n# execute: false\nwith\n    alias1 as (select * from table1),\n    alias2 as (select * from table2),\n    alias3 as (\n        select\n            cid,\n            min(od) as m_od,\n            count(odi) as c_od,\n        from alias2\n        group by 1\n    )\nselect\n    alias1.cid,\n    alias3.m_od,\n    coalesce(alias3.c_od, 0) as c_od,\nfrom alias1\nleft join alias3 using (cid);\nWITH \"alias3\" AS (\n  SELECT\n    \"table2\".\"cid\" AS \"cid\",\n    MIN(\"table2\".\"od\") AS \"m_od\",\n    COUNT(\"table2\".\"odi\") AS \"c_od\"\n  FROM \"table2\" AS \"table2\"\n  GROUP BY\n    \"table2\".\"cid\"\n)\nSELECT\n  \"table1\".\"cid\" AS \"cid\",\n  \"alias3\".\"m_od\" AS \"m_od\",\n  COALESCE(\"alias3\".\"c_od\", 0) AS \"c_od\"\nFROM \"table1\" AS \"table1\"\nLEFT JOIN \"alias3\" AS \"alias3\"\n  ON \"alias3\".\"cid\" = \"table1\".\"cid\";\n\n# title: CTE with EXPLODE cannot be merged\n# dialect: spark\n# execute: false\nSELECT Name,\n       FruitStruct.`$id`,\n       FruitStruct.value\n  FROM\n       (SELECT Name,\n               explode(Fruits) as FruitStruct\n          FROM fruits_table);\nWITH `_0` AS (\n  SELECT\n    `fruits_table`.`name` AS `name`,\n    EXPLODE(`fruits_table`.`fruits`) AS `fruitstruct`\n  FROM `fruits_table` AS `fruits_table`\n)\nSELECT\n  `_0`.`name` AS `name`,\n  `_0`.`fruitstruct`.`$id` AS `$id`,\n  `_0`.`fruitstruct`.`value` AS `value`\nFROM `_0` AS `_0`;\n\n# title: mysql is case-sensitive by default\n# dialect: mysql\n# execute: false\nWITH T AS (SELECT 1 AS CoL) SELECT * FROM `T`;\nWITH `T` AS (\n  SELECT\n    1 AS `CoL`\n)\nSELECT\n  `T`.`CoL` AS `CoL`\nFROM `T` AS `T`;\n\n# title: override mysql's settings so it normalizes to lowercase\n# dialect: mysql, normalization_strategy = lowercase\n# execute: false\nWITH T AS (SELECT 1 AS `CoL`) SELECT * FROM T;\nWITH `t` AS (\n  SELECT\n    1 AS `CoL`\n)\nSELECT\n  `t`.`CoL` AS `CoL`\nFROM `t` AS `t`;\n\n# title: top-level query is parenthesized\n# execute: false\nWITH x AS (\n  SELECT a FROM t\n)\n(\n  SELECT * FROM x\n  UNION ALL\n  SELECT * FROM x\n  LIMIT 10\n)\nLIMIT 10;\nWITH \"x\" AS (\n  SELECT\n    \"t\".\"a\" AS \"a\"\n  FROM \"t\" AS \"t\"\n)\n(\n  SELECT\n    \"x\".\"a\" AS \"a\"\n  FROM \"x\" AS \"x\"\n  UNION ALL\n  SELECT\n    \"x\".\"a\" AS \"a\"\n  FROM \"x\" AS \"x\"\n  LIMIT 10\n)\nLIMIT 10;\n\n# title: avoid producing DAG cycle when pushing down predicate to join\n# execute: false\nSELECT\n  a.company,\n  b.num\nFROM route AS a(num, company, pos, stop)\nJOIN route AS b(num, company, pos, stop) ON (a.num = b.num)\nJOIN stops AS c(id, name) ON (c.id = b.stop)\nJOIN stops AS d(id, name) ON (d.id = c.id)\nWHERE\n  c.name = 'Craiglockhart'\n  OR d.name = 'Tollcross';\nSELECT\n  \"a\".\"company\" AS \"company\",\n  \"b\".\"num\" AS \"num\"\nFROM \"route\" AS \"a\"(\"num\", \"company\", \"pos\", \"stop\")\nJOIN \"route\" AS \"b\"(\"num\", \"company\", \"pos\", \"stop\")\n  ON \"a\".\"num\" = \"b\".\"num\"\nJOIN \"stops\" AS \"c\"(\"id\", \"name\")\n  ON \"b\".\"stop\" = \"c\".\"id\"\nJOIN \"stops\" AS \"d\"(\"id\", \"name\")\n  ON \"c\".\"id\" = \"d\".\"id\"\n  AND (\n    \"c\".\"name\" = 'Craiglockhart' OR \"d\".\"name\" = 'Tollcross'\n  );\n\n# title: avoid dag cycles with unnesting subqueries\n# execute: false\n# dialect: snowflake\nSELECT\n  A.ACCOUNT_ID,\n  A.NAME,\n  C.EMAIL_DOMAIN\nFROM ACCOUNTS AS A\nLEFT JOIN CONTACTS AS C\n  ON C.ACCOUNT_ID = A.ACCOUNT_ID\n  AND C.EMAIL_DOMAIN IN (\n    SELECT\n      D.DOMAIN\n    FROM DOMAINS D\n    WHERE\n      TYPE = 'education'\n  );\nWITH \"_u_0\" AS (\n  SELECT\n    \"D\".\"DOMAIN\" AS \"DOMAIN\"\n  FROM \"DOMAINS\" AS \"D\"\n  WHERE\n    \"D\".\"TYPE\" = 'education'\n  GROUP BY\n    \"D\".\"DOMAIN\"\n)\nSELECT\n  \"A\".\"ACCOUNT_ID\" AS \"ACCOUNT_ID\",\n  \"A\".\"NAME\" AS \"NAME\",\n  \"C\".\"EMAIL_DOMAIN\" AS \"EMAIL_DOMAIN\"\nFROM \"ACCOUNTS\" AS \"A\"\nLEFT JOIN \"CONTACTS\" AS \"C\"\n  ON \"A\".\"ACCOUNT_ID\" = \"C\".\"ACCOUNT_ID\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"C\".\"EMAIL_DOMAIN\" = \"_u_0\".\"DOMAIN\"\nWHERE\n  NOT \"_u_0\".\"DOMAIN\" IS NULL;\n\n# title: decorrelate subquery and transpile ArrayAny correctly when generating spark\n# execute: false\n# dialect: spark\nSELECT\n  COUNT(DISTINCT cs1.cs_order_number) AS `order count`,\n  SUM(cs1.cs_ext_ship_cost) AS `total shipping cost`,\n  SUM(cs1.cs_net_profit) AS `total net profit`\nFROM catalog_sales cs1, date_dim, customer_address, call_center\nWHERE\n  date_dim.d_date BETWEEN '2002-02-01' AND (CAST('2002-02-01' AS DATE) + INTERVAL 60 days)\n  AND cs1.cs_ship_date_sk = date_dim.d_date_sk\n  AND cs1.cs_ship_addr_sk = customer_address.ca_address_sk\n  AND customer_address.ca_state = 'GA'\n  AND cs1.cs_call_center_sk = call_center.cc_call_center_sk\n  AND call_center.cc_county IN (\n    'Williamson County', 'Williamson County', 'Williamson County', 'Williamson County', 'Williamson County'\n  )\n  AND EXISTS(\n    SELECT *\n    FROM catalog_sales cs2\n    WHERE cs1.cs_order_number = cs2.cs_order_number\n      AND cs1.cs_warehouse_sk <> cs2.cs_warehouse_sk)\n      AND NOT EXISTS(\n        SELECT *\n        FROM catalog_returns cr1\n        WHERE cs1.cs_order_number = cr1.cr_order_number\n      )\n    ORDER BY COUNT(DISTINCT cs1.cs_order_number\n  )\nLIMIT 100;\nWITH `_u_0` AS (\n  SELECT\n    `cs2`.`cs_order_number` AS `_u_1`,\n    COLLECT_LIST(`cs2`.`cs_warehouse_sk`) AS `_u_2`\n  FROM `catalog_sales` AS `cs2`\n  GROUP BY\n    `cs2`.`cs_order_number`\n), `_u_3` AS (\n  SELECT\n    `cr1`.`cr_order_number` AS `_u_4`\n  FROM `catalog_returns` AS `cr1`\n  GROUP BY\n    `cr1`.`cr_order_number`\n)\nSELECT\n  COUNT(DISTINCT `cs1`.`cs_order_number`) AS `order count`,\n  SUM(`cs1`.`cs_ext_ship_cost`) AS `total shipping cost`,\n  SUM(`cs1`.`cs_net_profit`) AS `total net profit`\nFROM `catalog_sales` AS `cs1`\nJOIN `date_dim` AS `date_dim`\n  ON `cs1`.`cs_ship_date_sk` = `date_dim`.`d_date_sk`\n  AND `date_dim`.`d_date` <= (\n    CAST(CAST('2002-02-01' AS DATE) AS TIMESTAMP) + INTERVAL '60' DAYS\n  )\n  AND `date_dim`.`d_date` >= '2002-02-01'\nJOIN `customer_address` AS `customer_address`\n  ON `cs1`.`cs_ship_addr_sk` = `customer_address`.`ca_address_sk`\n  AND `customer_address`.`ca_state` = 'GA'\nJOIN `call_center` AS `call_center`\n  ON `call_center`.`cc_call_center_sk` = `cs1`.`cs_call_center_sk`\n  AND `call_center`.`cc_county` IN (\n    'Williamson County',\n    'Williamson County',\n    'Williamson County',\n    'Williamson County',\n    'Williamson County'\n  )\nLEFT JOIN `_u_0` AS `_u_0`\n  ON `_u_0`.`_u_1` = `cs1`.`cs_order_number`\nLEFT JOIN `_u_3` AS `_u_3`\n  ON `_u_3`.`_u_4` = `cs1`.`cs_order_number`\nWHERE\n  `_u_3`.`_u_4` IS NULL\n  AND (\n    SIZE(`_u_0`.`_u_2`) = 0\n    OR SIZE(FILTER(`_u_0`.`_u_2`, `_x` -> `cs1`.`cs_warehouse_sk` <> `_x`)) <> 0\n  )\n  AND NOT `_u_0`.`_u_1` IS NULL\nORDER BY\n  COUNT(DISTINCT `cs1`.`cs_order_number`)\nLIMIT 100;\n\n# execute: false\nSELECT\n  *\nFROM event\nWHERE priority = 'High' AND tagname IN (\n  SELECT\n    tag_input AS tagname\n  FROM cascade\n  WHERE tag_input = 'XXX' OR tag_output = 'XXX'\n  UNION\n  SELECT\n    tag_output AS tagname\n  FROM cascade\n  WHERE tag_input = 'XXX' OR tag_output = 'XXX'\n);\nWITH \"_u_0\" AS (\n  SELECT\n    \"cascade\".\"tag_input\" AS \"tagname\"\n  FROM \"cascade\" AS \"cascade\"\n  WHERE\n    \"cascade\".\"tag_input\" = 'XXX' OR \"cascade\".\"tag_output\" = 'XXX'\n  UNION\n  SELECT\n    \"cascade\".\"tag_output\" AS \"tagname\"\n  FROM \"cascade\" AS \"cascade\"\n  WHERE\n    \"cascade\".\"tag_input\" = 'XXX' OR \"cascade\".\"tag_output\" = 'XXX'\n), \"_u_1\" AS (\n  SELECT\n    \"cascade\".\"tag_input\" AS \"tagname\"\n  FROM \"_u_0\" AS \"_u_0\"\n  GROUP BY\n    \"cascade\".\"tag_input\"\n)\nSELECT\n  *\nFROM \"event\" AS \"event\"\nLEFT JOIN \"_u_1\" AS \"_u_1\"\n  ON \"_u_1\".\"tagname\" = \"event\".\"tagname\"\nWHERE\n  \"event\".\"priority\" = 'High' AND NOT \"_u_1\".\"tagname\" IS NULL;\n\n# title: SELECT TRANSFORM ... Spark clause when schema is provided\n# execute: false\n# dialect: spark\nWITH a AS (SELECT 'v' AS x) SELECT * FROM (SELECT TRANSFORM(x) USING 'cat' AS (y STRING) FROM a);\nWITH `a` AS (\n  SELECT\n    'v' AS `x`\n), `_0` AS (\n  SELECT\n    TRANSFORM(`a`.`x`) USING 'cat' AS (\n      `y` STRING\n    )\n  FROM `a` AS `a`\n)\nSELECT\n  `_0`.`y` AS `y`\nFROM `_0` AS `_0`;\n\n# title: SELECT TRANSFORM ... Spark clause when schema is not provided\n# execute: false\n# dialect: spark\nWITH a AS (SELECT 'v' AS x) SELECT * FROM (SELECT TRANSFORM(x) USING 'cat'  FROM a);\nWITH `a` AS (\n  SELECT\n    'v' AS `x`\n), `_0` AS (\n  SELECT\n    TRANSFORM(`a`.`x`) USING 'cat'\n  FROM `a` AS `a`\n)\nSELECT\n  `_0`.`key` AS `key`,\n  `_0`.`value` AS `value`\nFROM `_0` AS `_0`;\n\n# title: avoid reordering of non inner joins\n# execute: true\nWITH t1 AS (\n    SELECT NULL AS id1\n),\nt2 AS (\n    SELECT 1 AS id2\n),\nt3 AS (\n    SELECT 'info' AS info\n)\nSELECT \n  t1.id1 AS id1,\n  t2.id2 AS id2,\n  t3.info AS info\nFROM t1\nRIGHT JOIN t2 AS t2\n  ON t1.id1 = t2.id2\nRIGHT JOIN t3 ON TRUE;\nWITH \"t1\" AS (\n  SELECT\n    NULL AS \"id1\"\n), \"t2\" AS (\n  SELECT\n    1 AS \"id2\"\n), \"t3\" AS (\n  SELECT\n    'info' AS \"info\"\n)\nSELECT\n  \"t1\".\"id1\" AS \"id1\",\n  \"t2\".\"id2\" AS \"id2\",\n  \"t3\".\"info\" AS \"info\"\nFROM \"t1\" AS \"t1\"\nRIGHT JOIN \"t2\" AS \"t2\"\n  ON \"t1\".\"id1\" = \"t2\".\"id2\"\nCROSS JOIN \"t3\" AS \"t3\";\n\n# title: subquery in GENERATE_SERIES stays untouched\n# execute: false\nWITH t3 AS (SELECT t1.c1::bigint AS ref1 FROM (SELECT MAX(x.a) as c1 FROM x) t1 JOIN GENERATE_SERIES((SELECT MAX(a) FROM x), 10, 1) AS t2(c1) on t2.c1 > t1.c1) SELECT * FROM t3;\nWITH \"t1\" AS (\n  SELECT\n    MAX(\"x\".\"a\") AS \"c1\"\n  FROM \"x\" AS \"x\"\n)\nSELECT\n  CAST(\"t1\".\"c1\" AS BIGINT) AS \"ref1\"\nFROM \"t1\" AS \"t1\"\nJOIN GENERATE_SERIES((\n  SELECT\n    MAX(\"x\".\"a\") AS \"_col_0\"\n  FROM \"x\" AS \"x\"\n), 10, 1) AS \"t2\"(\"c1\")\n  ON \"t1\".\"c1\" < \"t2\".\"c1\";\n\n# title: empty table right join GENERATE_SERIES should have data\nSELECT t1.c1 FROM z AS z RIGHT JOIN GENERATE_SERIES((SELECT MIN(x.a) FROM x), 10, 1) AS t1(c1) ON t1.c1 > z.c;\nSELECT\n  \"t1\".\"c1\" AS \"c1\"\nFROM \"z\" AS \"z\"\nRIGHT JOIN GENERATE_SERIES((\n  SELECT\n    MIN(\"x\".\"a\") AS \"_col_0\"\n  FROM \"x\" AS \"x\"\n), 10, 1) AS \"t1\"(\"c1\")\n  ON \"t1\".\"c1\" > \"z\".\"c\"\n;"
  },
  {
    "path": "tests/fixtures/optimizer/pushdown_cte_alias_columns.sql",
    "content": "WITH y(c) AS (SELECT SUM(a) FROM (SELECT 1 a) AS x HAVING c > 0) SELECT c FROM y;\nWITH y(c) AS (SELECT SUM(a) AS c FROM (SELECT 1 AS a) AS x HAVING c > 0) SELECT c FROM y;\n\nWITH y(c) AS (SELECT SUM(a) as d FROM (SELECT 1 a) AS x HAVING c > 0) SELECT c FROM y;\nWITH y(c) AS (SELECT SUM(a) AS c FROM (SELECT 1 AS a) AS x HAVING c > 0) SELECT c FROM y;\n\nWITH x(c) AS (SELECT SUM(1) a HAVING c > 0 LIMIT 1) SELECT * FROM x;\nWITH x(c) AS (SELECT SUM(1) AS c HAVING c > 0 LIMIT 1) SELECT * FROM x;\n\n-- Invalid statement in Snowflake but checking more complex structures\nWITH x(c) AS ((SELECT 1 a) HAVING c > 0) SELECT * FROM x;\nWITH x(c) AS ((SELECT 1 AS a) HAVING c > 0) SELECT * FROM x;\n\n-- Invalid statement in Snowflake but checking more complex structures\nWITH x(c) AS ((SELECT SUM(1) a) HAVING c > 0 LIMIT 1) SELECT * FROM x;\nWITH x(c) AS ((SELECT SUM(1) AS a) HAVING c > 0 LIMIT 1) SELECT * FROM x;\n\n-- Invalid statement in Snowflake but checking that we don't fail\nWITH x(c) AS (SELECT SUM(a) FROM x HAVING c > 0 UNION ALL SELECT SUM(a) FROM y HAVING c > 0) SELECT * FROM x;\nWITH x(c) AS (SELECT SUM(a) FROM x HAVING c > 0 UNION ALL SELECT SUM(a) FROM y HAVING c > 0) SELECT * FROM x;\n"
  },
  {
    "path": "tests/fixtures/optimizer/pushdown_predicates.sql",
    "content": "SELECT x.a AS a FROM (SELECT x.a FROM x AS x) AS x JOIN y WHERE x.a = 1 AND x.b = 1 AND y.a = 1;\nSELECT x.a AS a FROM (SELECT x.a FROM x AS x WHERE x.a = 1 AND x.b = 1) AS x JOIN y ON y.a = 1 WHERE TRUE AND TRUE AND TRUE;\n\nWITH x AS (SELECT y.a FROM y) SELECT * FROM x WHERE x.a = 1;\nWITH x AS (SELECT y.a FROM y WHERE y.a = 1) SELECT * FROM x WHERE TRUE;\n\nSELECT x.a FROM (SELECT * FROM x) AS x CROSS JOIN y WHERE y.a = 1 OR (x.a = 1 AND x.b = 1);\nSELECT x.a FROM (SELECT * FROM x) AS x CROSS JOIN y WHERE (x.a = 1 AND x.b = 1) OR y.a = 1;\n\nSELECT x.a FROM (SELECT * FROM x) AS x JOIN y WHERE (x.a = y.a AND x.a = 1 AND x.b = 1) OR x.a = y.a;\nSELECT x.a FROM (SELECT * FROM x) AS x JOIN y ON x.a = y.a WHERE TRUE;\n\nSELECT x.a FROM (SELECT * FROM x) AS x JOIN y WHERE (x.a = y.a AND x.a = 1 AND x.b = 1) OR x.a = y.b;\nSELECT x.a FROM (SELECT * FROM x) AS x JOIN y ON (x.a = 1 AND x.a = y.a AND x.b = 1) OR x.a = y.b WHERE (x.a = 1 AND x.a = y.a AND x.b = 1) OR x.a = y.b;\n\nSELECT x.a FROM (SELECT x.a AS a, x.b * 1 AS c FROM x) AS x WHERE x.c = 1;\nSELECT x.a FROM (SELECT x.a AS a, x.b * 1 AS c FROM x WHERE x.b * 1 = 1) AS x WHERE TRUE;\n\nSELECT x.a FROM (SELECT x.a AS a, x.b * 1 AS c FROM x) AS x WHERE x.c = 1 or x.c = 2;\nSELECT x.a FROM (SELECT x.a AS a, x.b * 1 AS c FROM x WHERE x.b * 1 = 1 OR x.b * 1 = 2) AS x WHERE TRUE;\n\nSELECT x.a AS a FROM (SELECT x.a FROM x AS x) AS x JOIN y WHERE x.a = 1 AND x.b = 1 AND (x.c = 1 OR y.c = 1);\nSELECT x.a AS a FROM (SELECT x.a FROM x AS x WHERE x.a = 1 AND x.b = 1) AS x JOIN y ON x.c = 1 OR y.c = 1 WHERE TRUE AND TRUE AND (TRUE);\n\nSELECT x.a FROM x AS x JOIN (SELECT y.a FROM y AS y) AS y ON y.a = 1 AND x.a = y.a;\nSELECT x.a FROM x AS x JOIN (SELECT y.a FROM y AS y WHERE y.a = 1) AS y ON x.a = y.a AND TRUE;\n\nSELECT x.a AS a FROM x AS x JOIN (SELECT * FROM y AS y) AS y ON y.a = 1 WHERE x.a = 1 AND x.b = 1 AND y.a = x.a;\nSELECT x.a AS a FROM x AS x JOIN (SELECT * FROM y AS y WHERE y.a = 1) AS y ON x.a = y.a AND TRUE WHERE x.a = 1 AND TRUE AND x.b = 1;\n\nSELECT x.a AS a FROM x AS x CROSS JOIN (SELECT * FROM y AS y) AS y WHERE x.a = 1 AND x.b = 1 AND y.a = x.a AND y.a = 1;\nSELECT x.a AS a FROM x AS x JOIN (SELECT * FROM y AS y WHERE y.a = 1) AS y ON x.a = y.a AND TRUE WHERE x.a = 1 AND TRUE AND x.b = 1 AND TRUE;\n\nwith t1 as (SELECT x.a, x.b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) as row_num FROM x) SELECT t1.a, t1.b FROM t1 WHERE row_num = 1;\nWITH t1 AS (SELECT x.a, x.b, ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.a) AS row_num FROM x) SELECT t1.a, t1.b FROM t1 WHERE row_num = 1;\n\nWITH m AS (SELECT a, b FROM (VALUES (1, 2)) AS a1(a, b)), n AS (SELECT a, b FROM m WHERE m.a = 1), o AS (SELECT a, b FROM m WHERE m.a = 2) SELECT n.a, n.b, n.a, o.b FROM n FULL OUTER JOIN o ON n.a = o.a;\nWITH m AS (SELECT a, b FROM (VALUES (1, 2)) AS a1(a, b)), n AS (SELECT a, b FROM m WHERE m.a = 1), o AS (SELECT a, b FROM m WHERE m.a = 2) SELECT n.a, n.b, n.a, o.b FROM n FULL OUTER JOIN o ON n.a = o.a;\n\n-- Pushdown predicate to HAVING (CNF)\nSELECT x.cnt AS cnt FROM (SELECT COUNT(1) AS cnt FROM x AS x) AS x WHERE x.cnt > 0;\nSELECT x.cnt AS cnt FROM (SELECT COUNT(1) AS cnt FROM x AS x HAVING COUNT(1) > 0) AS x WHERE TRUE;\n\n-- Pushdown predicate to HAVING (DNF)\nSELECT x.cnt AS cnt FROM (SELECT COUNT(1) AS cnt, COUNT(x.a) AS cnt_a, COUNT(x.b) AS cnt_b FROM x AS x) AS x WHERE (x.cnt_a > 0 AND x.cnt_b > 0) OR x.cnt > 0;\nSELECT x.cnt AS cnt FROM (SELECT COUNT(1) AS cnt, COUNT(x.a) AS cnt_a, COUNT(x.b) AS cnt_b FROM x AS x HAVING COUNT(1) > 0 OR (COUNT(x.a) > 0 AND COUNT(x.b) > 0)) AS x WHERE x.cnt > 0 OR (x.cnt_a > 0 AND x.cnt_b > 0);\n\nSELECT x.a, u.val FROM x AS x CROSS JOIN UNNEST(ARRAY[0, 1]) AS u(\"val\") WHERE x.a > u.val;\nSELECT x.a, u.val FROM x AS x JOIN UNNEST(ARRAY(0, 1)) AS u(\"val\") ON u.val < x.a WHERE TRUE;\n\n# dialect: presto\nSELECT x.a, u.val FROM x AS x CROSS JOIN UNNEST(ARRAY[0, 1]) AS u(\"val\") WHERE x.a > u.val;\nSELECT x.a, u.val FROM x AS x CROSS JOIN UNNEST(ARRAY[0, 1]) AS u(\"val\") WHERE x.a > u.val;\n\n# dialect: trino\nSELECT x.a, u.val FROM x AS x CROSS JOIN UNNEST(ARRAY[0, 1]) AS u(\"val\") WHERE x.a > u.val;\nSELECT x.a, u.val FROM x AS x CROSS JOIN UNNEST(ARRAY[0, 1]) AS u(\"val\") WHERE x.a > u.val;\n\n# dialect: athena\nSELECT x.a, u.val FROM x AS x CROSS JOIN UNNEST(ARRAY[0, 1]) AS u(\"val\") WHERE x.a > u.val;\nSELECT x.a, u.val FROM x AS x CROSS JOIN UNNEST(ARRAY[0, 1]) AS u(\"val\") WHERE x.a > u.val;\n\n# dialect: presto\nSELECT x.a, u.val FROM UNNEST(ARRAY[0, 1]) AS u(\"val\") CROSS JOIN x AS x WHERE x.a > u.val;\nSELECT x.a, u.val FROM UNNEST(ARRAY[0, 1]) AS u(\"val\") JOIN x AS x ON u.val < x.a WHERE TRUE;\n\n# dialect: trino\nSELECT x.a, u.val FROM UNNEST(ARRAY[0, 1]) AS u(\"val\") CROSS JOIN x AS x WHERE x.a > u.val;\nSELECT x.a, u.val FROM UNNEST(ARRAY[0, 1]) AS u(\"val\") JOIN x AS x ON u.val < x.a WHERE TRUE;\n\n# dialect: athena\nSELECT x.a, u.val FROM UNNEST(ARRAY[0, 1]) AS u(\"val\") CROSS JOIN x AS x WHERE x.a > u.val;\nSELECT x.a, u.val FROM UNNEST(ARRAY[0, 1]) AS u(\"val\") JOIN x AS x ON u.val < x.a WHERE TRUE;\n\n-- DNF: cross-table predicate is only pushed to the last eligible JOIN (not to an earlier JOIN that doesn't yet have all referenced tables in scope)\nSELECT a.id, b.val, c.name FROM t_a AS a INNER JOIN t_b AS b ON b.a_id = a.id INNER JOIN t_c AS c ON c.b_id = b.id WHERE (b.flag = 1 AND c.active = 1) OR (b.flag = 2 AND c.active = 0);\nSELECT a.id, b.val, c.name FROM t_a AS a INNER JOIN t_b AS b ON a.id = b.a_id INNER JOIN t_c AS c ON ((b.flag = 1 AND c.active = 1) OR (b.flag = 2 AND c.active = 0)) AND b.id = c.b_id WHERE (b.flag = 1 AND c.active = 1) OR (b.flag = 2 AND c.active = 0);\n\n-- DNF: single-table predicate is pushed to its own JOIN regardless of join order\nSELECT a.id, b.val FROM t_a AS a INNER JOIN t_b AS b ON b.a_id = a.id WHERE (b.flag = 1 AND b.active = 1) OR (b.flag = 2 AND b.active = 0);\nSELECT a.id, b.val FROM t_a AS a INNER JOIN t_b AS b ON ((b.active = 0 AND b.flag = 2) OR (b.active = 1 AND b.flag = 1)) AND a.id = b.a_id WHERE (b.active = 0 AND b.flag = 2) OR (b.active = 1 AND b.flag = 1);\n"
  },
  {
    "path": "tests/fixtures/optimizer/pushdown_projections.sql",
    "content": "SELECT a FROM (SELECT * FROM x);\nSELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _0;\n\nSELECT 1 FROM (SELECT * FROM x) WHERE b = 2;\nSELECT 1 AS \"1\" FROM (SELECT x.b AS b FROM x AS x) AS _0 WHERE _0.b = 2;\n\nSELECT a, b, a from x;\nSELECT x.a AS a, x.b AS b, x.a AS a FROM x AS x;\n\nSELECT (SELECT c FROM y WHERE q.b = y.b) FROM (SELECT * FROM x) AS q;\nSELECT (SELECT y.c AS c FROM y AS y WHERE q.b = y.b) AS _col_0 FROM (SELECT x.b AS b FROM x AS x) AS q;\n\nSELECT a FROM x JOIN (SELECT b, c FROM y) AS z ON x.b = z.b;\nSELECT x.a AS a FROM x AS x JOIN (SELECT y.b AS b FROM y AS y) AS z ON x.b = z.b;\n\nSELECT x1.a FROM (SELECT * FROM x) AS x1, (SELECT * FROM x) AS x2;\nSELECT x1.a AS a FROM (SELECT x.a AS a FROM x AS x) AS x1, (SELECT 1 AS _ FROM x AS x) AS x2;\n\nSELECT a FROM (SELECT DISTINCT a, b FROM x);\nSELECT _0.a AS a FROM (SELECT DISTINCT x.a AS a, x.b AS b FROM x AS x) AS _0;\n\nSELECT a FROM (SELECT a, b FROM x UNION ALL SELECT a, b FROM x);\nSELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x UNION ALL SELECT x.a AS a FROM x AS x) AS _0;\n\nWITH t1 AS (SELECT x.a AS a, x.b AS b FROM x UNION ALL SELECT z.b AS b, z.c AS c FROM z) SELECT a, b FROM t1;\nWITH t1 AS (SELECT x.a AS a, x.b AS b FROM x AS x UNION ALL SELECT z.b AS b, z.c AS c FROM z AS z) SELECT t1.a AS a, t1.b AS b FROM t1 AS t1;\n\nSELECT a FROM (SELECT a, b FROM x UNION SELECT a, b FROM x);\nSELECT _0.a AS a FROM (SELECT x.a AS a, x.b AS b FROM x AS x UNION SELECT x.a AS a, x.b AS b FROM x AS x) AS _0;\n\nWITH y AS (SELECT * FROM x) SELECT a FROM y;\nWITH y AS (SELECT x.a AS a FROM x AS x) SELECT y.a AS a FROM y AS y;\n\nWITH z AS (SELECT * FROM x), q AS (SELECT b FROM z) SELECT b FROM q;\nWITH z AS (SELECT x.b AS b FROM x AS x), q AS (SELECT z.b AS b FROM z AS z) SELECT q.b AS b FROM q AS q;\n\nWITH z AS (SELECT * FROM x) SELECT a FROM z UNION SELECT a FROM z;\nWITH z AS (SELECT x.a AS a FROM x AS x) SELECT z.a AS a FROM z AS z UNION SELECT z.a AS a FROM z AS z;\n\nSELECT b FROM (SELECT a, SUM(b) AS b FROM x GROUP BY a);\nSELECT _0.b AS b FROM (SELECT SUM(x.b) AS b FROM x AS x GROUP BY x.a) AS _0;\n\nSELECT b FROM (SELECT a, SUM(b) AS b FROM x ORDER BY a);\nSELECT _0.b AS b FROM (SELECT x.a AS a, SUM(x.b) AS b FROM x AS x ORDER BY a) AS _0;\n\nSELECT x FROM (VALUES(1, 2)) AS q(x, y);\nSELECT q.x AS x FROM (VALUES (1, 2)) AS q(x, y);\n\nSELECT x FROM UNNEST([1, 2]) AS q(x, y);\nSELECT q.x AS x FROM UNNEST(ARRAY(1, 2)) AS q(x, y);\n\nWITH t1 AS (SELECT cola, colb FROM UNNEST([STRUCT(1 AS cola, 'test' AS colb)]) AS \"q\"(\"cola\", \"colb\")) SELECT cola FROM t1;\nWITH t1 AS (SELECT \"q\".cola AS cola FROM UNNEST(ARRAY(STRUCT(1 AS cola, 'test' AS colb))) AS \"q\"(\"cola\", \"colb\")) SELECT t1.cola AS cola FROM t1 AS t1;\n\nSELECT x FROM VALUES(1, 2) AS q(x, y);\nSELECT q.x AS x FROM (VALUES (1, 2)) AS q(x, y);\n\nSELECT i.a FROM x AS i LEFT JOIN (SELECT a, b FROM (SELECT a, b FROM x)) AS j ON i.a = j.a;\nSELECT i.a AS a FROM x AS i LEFT JOIN (SELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _0) AS j ON i.a = j.a;\n\nWITH cte AS (SELECT source.a AS a, ROW_NUMBER() OVER (PARTITION BY source.id, source.timestamp ORDER BY source.a DESC) AS index FROM source AS source QUALIFY index) SELECT cte.a AS a FROM cte;\nWITH cte AS (SELECT source.a AS a FROM source AS source QUALIFY ROW_NUMBER() OVER (PARTITION BY source.id, source.timestamp ORDER BY source.a DESC)) SELECT cte.a AS a FROM cte AS cte;\n\nWITH cte AS (SELECT 1 AS x, 2 AS y, 3 AS z) SELECT cte.a FROM cte AS cte(a);\nWITH cte AS (SELECT 1 AS x) SELECT cte.a AS a FROM cte AS cte(a);\n\nWITH cte(x, y, z) AS (SELECT 1, 2, 3) SELECT a, z FROM cte AS cte(a);\nWITH cte AS (SELECT 1 AS x, 3 AS z) SELECT cte.a AS a, cte.z AS z FROM cte AS cte(a);\n\nWITH cte(x, y, z) AS (SELECT 1, 2, 3) SELECT a, z FROM (SELECT * FROM cte AS cte(b)) AS cte(a);\nWITH cte AS (SELECT 1 AS x, 3 AS z) SELECT cte.a AS a, cte.z AS z FROM (SELECT cte.b AS a, cte.z AS z FROM cte AS cte(b)) AS cte;\n\nWITH y AS (SELECT a FROM x) SELECT 1 FROM y;\nWITH y AS (SELECT 1 AS _ FROM x AS x) SELECT 1 AS \"1\" FROM y AS y;\n\nWITH y AS (SELECT SUM(a) FROM x) SELECT 1 FROM y;\nWITH y AS (SELECT MAX(1) AS _ FROM x AS x) SELECT 1 AS \"1\" FROM y AS y;\n\nWITH y AS (SELECT a FROM x GROUP BY a) SELECT 1 FROM y;\nWITH y AS (SELECT 1 AS _ FROM x AS x GROUP BY x.a) SELECT 1 AS \"1\" FROM y AS y;\n\nWITH cte AS (SELECT col FROM t) SELECT IF(1 IN UNNEST(col), 1, 0) AS col FROM cte;\nWITH cte AS (SELECT t.col AS col FROM t AS t) SELECT CASE WHEN 1 IN (SELECT UNNEST(cte.col)) THEN 1 ELSE 0 END AS col FROM cte AS cte;\n\n--------------------------------------\n-- Unknown Star Expansion\n--------------------------------------\n\nSELECT a FROM (SELECT * FROM zz) WHERE b = 1;\nSELECT _0.a AS a FROM (SELECT zz.a AS a, zz.b AS b FROM zz AS zz) AS _0 WHERE _0.b = 1;\n\nSELECT a FROM (SELECT * FROM aa UNION ALL SELECT * FROM bb UNION ALL SELECT * from cc);\nSELECT _0.a AS a FROM (SELECT aa.a AS a FROM aa AS aa UNION ALL SELECT bb.a AS a FROM bb AS bb UNION ALL SELECT cc.a AS a FROM cc AS cc) AS _0;\n\nSELECT a FROM (SELECT a FROM aa UNION ALL SELECT * FROM bb UNION ALL SELECT * from cc);\nSELECT _0.a AS a FROM (SELECT aa.a AS a FROM aa AS aa UNION ALL SELECT bb.a AS a FROM bb AS bb UNION ALL SELECT cc.a AS a FROM cc AS cc) AS _0;\n\nSELECT a FROM (SELECT * FROM aa CROSS JOIN bb);\nSELECT _0.a AS a FROM (SELECT a AS a FROM aa AS aa CROSS JOIN bb AS bb) AS _0;\n\nSELECT a FROM (SELECT aa.* FROM aa);\nSELECT _0.a AS a FROM (SELECT aa.a AS a FROM aa AS aa) AS _0;\n\nSELECT a FROM (SELECT * FROM (SELECT * FROM aa));\nSELECT _1.a AS a FROM (SELECT _0.a AS a FROM (SELECT aa.a AS a FROM aa AS aa) AS _0) AS _1;\n\nwith cte1 as (SELECT cola, colb FROM tb UNION ALL SELECT colc, cold FROM tb2) SELECT cola FROM cte1;\nWITH cte1 AS (SELECT tb.cola AS cola FROM tb AS tb UNION ALL SELECT tb2.colc AS colc FROM tb2 AS tb2) SELECT cte1.cola AS cola FROM cte1 AS cte1;\n\nSELECT * FROM ((SELECT c FROM t1) JOIN t2);\nSELECT * FROM ((SELECT t1.c AS c FROM t1 AS t1) AS _0, t2 AS t2);\n\nSELECT a, d FROM (SELECT 1 a, 2 c, 3 d, 4 e UNION ALL BY NAME SELECT 6 c, 7 d, 8 a, 9 e);\nSELECT _0.a AS a, _0.d AS d FROM (SELECT 1 AS a, 3 AS d UNION ALL BY NAME SELECT 7 AS d, 8 AS a) AS _0;\n\nSELECT a, b FROM (WITH cte1 AS (SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d) (SELECT a, b, c FROM cte1));\nSELECT _0.a AS a, _0.b AS b FROM (WITH cte1 AS (SELECT 1 AS a, 2 AS b) SELECT cte1.a AS a, cte1.b AS b FROM cte1 AS cte1) AS _0;\n"
  },
  {
    "path": "tests/fixtures/optimizer/qualify_columns.sql",
    "content": "--------------------------------------\n-- Qualify columns\n--------------------------------------\nSELECT a FROM x;\nSELECT x.a AS a FROM x AS x;\n\nSELECT \"a\" FROM x;\nSELECT x.\"a\" AS a FROM x AS x;\n\n# execute: false\nSELECT a FROM zz GROUP BY a ORDER BY a;\nSELECT zz.a AS a FROM zz AS zz GROUP BY zz.a ORDER BY a;\n\n# execute: false\nSELECT x, p FROM (SELECT x from xx) xx CROSS JOIN yy;\nSELECT xx.x AS x, yy.p AS p FROM (SELECT xx.x AS x FROM xx AS xx) AS xx CROSS JOIN yy AS yy;\n\nSELECT a FROM x AS z;\nSELECT z.a AS a FROM x AS z;\n\nSELECT a AS a FROM x;\nSELECT x.a AS a FROM x AS x;\n\nSELECT x.a FROM x;\nSELECT x.a AS a FROM x AS x;\n\nSELECT x.a AS a FROM x;\nSELECT x.a AS a FROM x AS x;\n\nSELECT a AS b FROM x;\nSELECT x.a AS b FROM x AS x;\n\n# execute: false\nSELECT 1, 2 + 3 FROM x;\nSELECT 1 AS \"1\", 2 + 3 AS _col_1 FROM x AS x;\n\n# execute: false\nSELECT a + b FROM x;\nSELECT x.a + x.b AS _col_0 FROM x AS x;\n\nSELECT l.a FROM x l WHERE a IN (select a FROM x ORDER by a);\nSELECT l.a AS a FROM x AS l WHERE l.a IN (SELECT x.a AS a FROM x AS x ORDER BY a);\n\n# execute: false\nSELECT a, SUM(b) FROM x WHERE a > 1 AND b > 1 GROUP BY a;\nSELECT x.a AS a, SUM(x.b) AS _col_1 FROM x AS x WHERE x.a > 1 AND x.b > 1 GROUP BY x.a;\n\nSELECT SUM(a) AS c FROM x HAVING SUM(a) > 3;\nSELECT SUM(x.a) AS c FROM x AS x HAVING SUM(x.a) > 3;\n\nSELECT SUM(a) AS a FROM x HAVING SUM(a) > 3;\nSELECT SUM(x.a) AS a FROM x AS x HAVING SUM(x.a) > 3;\n\nSELECT SUM(a) AS c FROM x HAVING c > 3;\nSELECT SUM(x.a) AS c FROM x AS x HAVING SUM(x.a) > 3;\n\n# execute: false\nSELECT SUM(a) AS a FROM x HAVING a > 3;\nSELECT SUM(x.a) AS a FROM x AS x HAVING SUM(x.a) > 3;\n\nSELECT SUM(a) AS c FROM x HAVING SUM(b) > 3;\nSELECT SUM(x.a) AS c FROM x AS x HAVING SUM(x.b) > 3;\n\nSELECT a AS j, b FROM x ORDER BY j;\nSELECT x.a AS j, x.b AS b FROM x AS x ORDER BY j;\n\nSELECT a AS j, b AS a FROM x ORDER BY 1;\nSELECT x.a AS j, x.b AS a FROM x AS x ORDER BY j;\n\nSELECT SUM(a) AS c, SUM(b) AS d FROM x ORDER BY 1, 2;\nSELECT SUM(x.a) AS c, SUM(x.b) AS d FROM x AS x ORDER BY c, d;\n\n# execute: false\nSELECT CAST(a AS INT) FROM x ORDER BY a;\nSELECT CAST(x.a AS INT) AS a FROM x AS x ORDER BY a;\n\n# execute: false\nSELECT SUM(a), SUM(b) AS c FROM x ORDER BY 1, 2;\nSELECT SUM(x.a) AS _col_0, SUM(x.b) AS c FROM x AS x ORDER BY _col_0, c;\n\nSELECT a AS j, b FROM x GROUP BY j, b;\nSELECT x.a AS j, x.b AS b FROM x AS x GROUP BY x.a, x.b;\n\nSELECT a, b FROM x GROUP BY 1, 2;\nSELECT x.a AS a, x.b AS b FROM x AS x GROUP BY x.a, x.b;\n\nSELECT a, b FROM x ORDER BY 1, 2;\nSELECT x.a AS a, x.b AS b FROM x AS x ORDER BY a, b;\n\nSELECT DISTINCT a AS c, b AS d FROM x ORDER BY 1;\nSELECT DISTINCT x.a AS c, x.b AS d FROM x AS x ORDER BY c;\n\nSELECT 2 FROM x GROUP BY 1;\nSELECT 2 AS \"2\" FROM x AS x GROUP BY 1;\n\nSELECT 'a' AS a FROM x GROUP BY 1;\nSELECT 'a' AS a FROM x AS x GROUP BY 1;\n\nSELECT NULL AS a FROM x GROUP BY 1;\nSELECT NULL AS a FROM x AS x GROUP BY 1;\n\nSELECT TRUE AS a FROM x GROUP BY 1;\nSELECT TRUE AS a FROM x AS x GROUP BY 1;\n\n# execute: false\n# dialect: oracle\nSELECT t.\"col\" FROM tbl t;\nSELECT T.\"col\" AS \"col\" FROM TBL T;\n\n# execute: false\n# dialect: oracle\nWITH base AS (SELECT x.dummy AS COL_1 FROM dual x) SELECT b.\"COL_1\" FROM base b;\nWITH BASE AS (SELECT X.DUMMY AS COL_1 FROM DUAL X) SELECT B.\"COL_1\" AS COL_1 FROM BASE B;\n\n# execute: false\n-- this query seems to be invalid in postgres and duckdb but valid in bigquery\nSELECT 2 a FROM x GROUP BY 1 HAVING a > 1;\nSELECT 2 AS a FROM x AS x GROUP BY 1 HAVING a > 1;\n\nSELECT 2 d FROM x GROUP BY d HAVING d > 1;\nSELECT 2 AS d FROM x AS x GROUP BY 1 HAVING d > 1;\n\nSELECT 2 d FROM x GROUP BY 1 ORDER BY 1;\nSELECT 2 AS d FROM x AS x GROUP BY 1 ORDER BY d;\n\n# execute: false\nSELECT DATE(a), DATE(b) AS c FROM x GROUP BY 1, 2;\nSELECT DATE(x.a) AS _col_0, DATE(x.b) AS c FROM x AS x GROUP BY DATE(x.a), DATE(x.b);\n\n# execute: false\nSELECT (SELECT MIN(a) FROM UNNEST([1, 2])) AS f FROM x GROUP BY 1;\nSELECT (SELECT MIN(_0.a) AS _col_0 FROM UNNEST(ARRAY(1, 2)) AS _0) AS f FROM x AS x GROUP BY 1;\n\n# dialect: bigquery\nWITH x AS (select 'a' as a, 1 as b) SELECT x.a AS c, y.a as d, SUM(x.b) AS y, FROM x join x as y on x.a = y.a group by 1, 2;\nWITH x AS (SELECT 'a' AS a, 1 AS b) SELECT x.a AS c, y.a AS d, SUM(x.b) AS y FROM x AS x JOIN x AS y ON x.a = y.a GROUP BY x.a, 2;\n\nSELECT SUM(x.a) AS c FROM x JOIN y ON x.b = y.b GROUP BY c;\nSELECT SUM(x.a) AS c FROM x AS x JOIN y AS y ON x.b = y.b GROUP BY y.c;\n\nSELECT COALESCE(x.a) AS d FROM x JOIN y ON x.b = y.b GROUP BY d;\nSELECT COALESCE(x.a) AS d FROM x AS x JOIN y AS y ON x.b = y.b GROUP BY COALESCE(x.a);\n\nSELECT a + 1 AS d FROM x WHERE d > 1;\nSELECT x.a + 1 AS d FROM x AS x WHERE (x.a + 1) > 1;\n\n# execute: false\nSELECT a + 1 AS d, d + 2 FROM x;\nSELECT x.a + 1 AS d, x.a + 1 + 2 AS _col_1 FROM x AS x;\n\nSELECT a AS a, b FROM x ORDER BY a;\nSELECT x.a AS a, x.b AS b FROM x AS x ORDER BY a;\n\nSELECT a, b FROM x ORDER BY a;\nSELECT x.a AS a, x.b AS b FROM x AS x ORDER BY a;\n\nSELECT a FROM x ORDER BY b;\nSELECT x.a AS a FROM x AS x ORDER BY x.b;\n\nSELECT SUM(a) AS a FROM x ORDER BY SUM(a);\nSELECT SUM(x.a) AS a FROM x AS x ORDER BY SUM(x.a);\n\n# execute: false\nSELECT AGGREGATE(ARRAY(a, x.b), 0, (x, acc) -> x + acc + a) AS sum_agg FROM x;\nSELECT AGGREGATE(ARRAY(x.a, x.b), 0, (x, acc) -> x + acc + x.a) AS sum_agg FROM x AS x;\n\n# dialect: starrocks\n# execute: false\nSELECT DATE_TRUNC('week', a) AS a FROM x;\nSELECT DATE_TRUNC('WEEK', x.a) AS a FROM x AS x;\n\n# dialect: bigquery\n# execute: false\nSELECT DATE_TRUNC(a, MONTH) AS a FROM x;\nSELECT DATE_TRUNC(x.a, MONTH) AS a FROM x AS x;\n\n# execute: false\nSELECT x FROM READ_PARQUET('path.parquet', hive_partition=1);\nSELECT _0.x AS x FROM READ_PARQUET('path.parquet', hive_partition = 1) AS _0;\n\n# execute: false\nselect * from (values (1, 2));\nSELECT _0._col_0 AS _col_0, _0._col_1 AS _col_1 FROM (VALUES (1, 2)) AS _0(_col_0, _col_1);\n\n# execute: false\nselect * from (values (1, 2)) x;\nSELECT x._col_0 AS _col_0, x._col_1 AS _col_1 FROM (VALUES (1, 2)) AS x(_col_0, _col_1);\n\n# execute: false\nSELECT SOME_UDF(data).* FROM t;\nSELECT SOME_UDF(t.data).* FROM t AS t;\n\n# execute: false\nSELECT p.* FROM p UNION ALL SELECT p2.* FROM p2;\nSELECT p.* FROM p AS p UNION ALL SELECT p2.* FROM p2 AS p2;\n\n# execute: false\n# allow_partial_qualification: true\n# validate_qualify_columns: false\nSELECT a + 1 AS i, missing_column FROM x;\nSELECT x.a + 1 AS i, missing_column AS missing_column FROM x AS x;\n\n# execute: false\n# dialect: clickhouse\nSELECT s, arr1, arr2 FROM arrays_test LEFT ARRAY JOIN arr1, arrays_test.arr2;\nSELECT arrays_test.s AS s, arrays_test.arr1 AS arr1, arrays_test.arr2 AS arr2 FROM arrays_test AS arrays_test LEFT ARRAY JOIN arrays_test.arr1, arrays_test.arr2;\n\n# execute: false\n# dialect: snowflake\nWITH employees AS (\n    SELECT *\n    FROM (VALUES ('President', 1, NULL),\n                 ('Vice President Engineering', 10, 1),\n                 ('Programmer', 100, 10),\n                 ('QA Engineer', 101, 10),\n                 ('Vice President HR', 20, 1),\n                 ('Health Insurance Analyst', 200, 20)\n    ) AS t(title, employee_ID, manager_ID)\n)\nSELECT\n  employee_ID,\n  manager_ID,\n  title,\n  level\nFROM employees\nSTART WITH title = 'President'\nCONNECT BY manager_ID = PRIOR employee_id\nORDER BY\n  employee_ID NULLS LAST;\nWITH EMPLOYEES AS (SELECT T.TITLE AS TITLE, T.EMPLOYEE_ID AS EMPLOYEE_ID, T.MANAGER_ID AS MANAGER_ID FROM (VALUES ('President', 1, NULL), ('Vice President Engineering', 10, 1), ('Programmer', 100, 10), ('QA Engineer', 101, 10), ('Vice President HR', 20, 1), ('Health Insurance Analyst', 200, 20)) AS T(TITLE, EMPLOYEE_ID, MANAGER_ID)) SELECT EMPLOYEES.EMPLOYEE_ID AS EMPLOYEE_ID, EMPLOYEES.MANAGER_ID AS MANAGER_ID, EMPLOYEES.TITLE AS TITLE, LEVEL AS LEVEL FROM EMPLOYEES AS EMPLOYEES START WITH EMPLOYEES.TITLE = 'President' CONNECT BY EMPLOYEES.MANAGER_ID = PRIOR EMPLOYEES.EMPLOYEE_ID ORDER BY EMPLOYEE_ID;\n\n# execute: false\n# dialect: oracle\nWITH\nt1 AS (\n  SELECT\n    1 AS c1,\n    1 AS c2,\n    'Y' AS TOP_PARENT_INDICATOR,\n    1 AS id\n  FROM DUAL\n),\nt2 AS (\n  SELECT\n    1 AS c2,\n    2 AS id\n  FROM DUAL\n)\nSELECT t1.c1\nFROM t1\nLEFT JOIN t2 ON t1.c2 = t2.c2\nWHERE (t1.TOP_PARENT_INDICATOR = 'Y' OR LEVEL = 1)\nSTART WITH (t1.id IS NOT NULL)\nCONNECT BY PRIOR t1.id = t2.id;\nWITH T1 AS (SELECT 1 AS C1, 1 AS C2, 'Y' AS TOP_PARENT_INDICATOR, 1 AS ID FROM DUAL DUAL), T2 AS (SELECT 1 AS C2, 2 AS ID FROM DUAL DUAL) SELECT T1.C1 AS C1 FROM T1 T1 LEFT JOIN T2 T2 ON T1.C2 = T2.C2 WHERE (T1.TOP_PARENT_INDICATOR = 'Y' OR LEVEL = 1) START WITH (NOT T1.ID IS NULL) CONNECT BY PRIOR T1.ID = T2.ID;\n\n# execute: false\n# dialect: postgres\nSELECT * FROM ROWS FROM (GENERATE_SERIES(1, 3), GENERATE_SERIES(10, 12)) AS t(a, b);\nSELECT t.a AS a, t.b AS b FROM ROWS FROM (GENERATE_SERIES(1, 3), GENERATE_SERIES(10, 12)) AS t(a, b);\n\n# execute: false\n# dialect: clickhouse\nSELECT generate_series FROM generate_series(0, 10) AS g;\nSELECT g.generate_series AS generate_series FROM generate_series(0, 10) AS g(generate_series);\n\n# execute: false\n# dialect: snowflake\nSELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY ORDER BY quarter)) ORDER BY empid;\nSELECT * FROM QUARTERLY_SALES AS QUARTERLY_SALES PIVOT(SUM(QUARTERLY_SALES.AMOUNT) FOR QUARTERLY_SALES.QUARTER IN (ANY ORDER BY QUARTER)) AS _0 ORDER BY _0.EMPID;\n\n# execute: false\nSELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x) AS x FROM t;\nSELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY t.x) AS x FROM t AS t;\n\n# execute: false\n# dialect: bigquery\nWITH t AS (SELECT 1 AS c) SELECT TO_JSON_STRING(t) FROM t;\nWITH t AS (SELECT 1 AS c) SELECT TO_JSON_STRING(t) AS _col_0 FROM t AS t;\n\n# execute: false\n# dialect: bigquery\nSELECT DATE_TRUNC(col1, WEEK(MONDAY)), col2 FROM t;\nSELECT DATE_TRUNC(t.col1, WEEK(MONDAY)) AS _col_0, t.col2 AS col2 FROM t AS t;\n\n# execute: false\nSELECT first, second FROM (SELECT 'val' AS col, STACK(2, 1, 2, 3) AS (first, second)) AS tbl;\nSELECT tbl.first AS first, tbl.second AS second FROM (SELECT 'val' AS col, STACK(2, 1, 2, 3) AS (first, second)) AS tbl;\n\n# execute: false\n# dialect: postgres\nWITH t AS (SELECT 1 AS c) SELECT t FROM t;\nWITH t AS (SELECT 1 AS c) SELECT t AS _col_0 FROM t AS t;\n\n--------------------------------------\n-- Derived tables\n--------------------------------------\nSELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y;\nSELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y;\n\nSELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y(a);\nSELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y;\n\nSELECT y.c AS c FROM (SELECT x.a AS a, x.b AS b FROM x AS x) AS y(c);\nSELECT y.c AS c FROM (SELECT x.a AS c, x.b AS b FROM x AS x) AS y;\n\nSELECT a FROM (SELECT a FROM x AS x) y;\nSELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y;\n\nSELECT a FROM (SELECT a AS a FROM x);\nSELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _0;\n\nSELECT a FROM (SELECT a FROM (SELECT a FROM x));\nSELECT _1.a AS a FROM (SELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _0) AS _1;\n\nSELECT x.a FROM x AS x JOIN (SELECT * FROM x) AS y ON x.a = y.a;\nSELECT x.a AS a FROM x AS x JOIN (SELECT x.a AS a, x.b AS b FROM x AS x) AS y ON x.a = y.a;\n\nSELECT a FROM x as t1 /* there is comment */;\nSELECT t1.a AS a FROM x AS t1 /* there is comment */;\n\n--------------------------------------\n-- Joins\n--------------------------------------\nSELECT a, c FROM x JOIN y ON x.b = y.b;\nSELECT x.a AS a, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\nSELECT a, c FROM x, y;\nSELECT x.a AS a, y.c AS c FROM x AS x, y AS y;\n\n--------------------------------------\n-- Unions\n--------------------------------------\nSELECT a FROM x UNION SELECT a FROM x ORDER BY a;\nSELECT x.a AS a FROM x AS x UNION SELECT x.a AS a FROM x AS x ORDER BY a;\n\nSELECT a FROM x UNION SELECT a FROM x UNION SELECT a FROM x ORDER BY a;\nSELECT x.a AS a FROM x AS x UNION SELECT x.a AS a FROM x AS x UNION SELECT x.a AS a FROM x AS x ORDER BY a;\n\nSELECT a FROM (SELECT a FROM x UNION SELECT a FROM x) ORDER BY a;\nSELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x UNION SELECT x.a AS a FROM x AS x) AS _0 ORDER BY a;\n\n# title: nested subqueries in union\n((select a from x where a < 1)) UNION ((select a from x where a > 2));\n(SELECT x.a AS a FROM x AS x WHERE x.a < 1) UNION (SELECT x.a AS a FROM x AS x WHERE x.a > 2);\n\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (SELECT 1 AS foo, 2 AS bar INNER UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz);\nSELECT _0.bar AS bar FROM (SELECT 1 AS foo, 2 AS bar INNER UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) AS _0;\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (SELECT 1 AS foo, 2 AS bar UNION ALL CORRESPONDING SELECT 3 AS bar, 4 AS baz);\nSELECT _0.bar AS bar FROM (SELECT 1 AS foo, 2 AS bar INNER UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) AS _0;\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz);\nSELECT _0.foo AS foo, _0.bar AS bar FROM (SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) AS _0;\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (SELECT 1 AS foo, 2 AS bar FULL UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz);\nSELECT _0.foo AS foo, _0.bar AS bar, _0.baz AS baz FROM (SELECT 1 AS foo, 2 AS bar FULL UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) AS _0;\n\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (SELECT 1 AS foo, 2 AS bar LEFT UNION ALL CORRESPONDING SELECT 3 AS bar, 4 AS baz);\nSELECT _0.foo AS foo, _0.bar AS bar FROM (SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) AS _0;\n\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (SELECT 1 AS foo, 2 AS bar FULL UNION ALL CORRESPONDING SELECT 3 AS bar, 4 AS baz);\nSELECT _0.foo AS foo, _0.bar AS bar, _0.baz AS baz FROM (SELECT 1 AS foo, 2 AS bar FULL UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) AS _0;\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (SELECT 1 AS foo, 2 AS bar FULL UNION ALL CORRESPONDING BY (foo, bar) SELECT 3 AS bar, 4 AS baz);\nSELECT _0.foo AS foo, _0.bar AS bar FROM (SELECT 1 AS foo, 2 AS bar FULL UNION ALL BY NAME ON (foo, bar) SELECT 3 AS bar, 4 AS baz) AS _0;\n\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (SELECT 1 AS foo, 2 AS bar FULL UNION ALL BY NAME ON (foo, bar) SELECT 3 AS bar, 4 AS baz);\nSELECT _0.foo AS foo, _0.bar AS bar FROM (SELECT 1 AS foo, 2 AS bar FULL UNION ALL BY NAME ON (foo, bar) SELECT 3 AS bar, 4 AS baz) AS _0;\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM ((SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) LEFT UNION ALL BY NAME ON (bar) SELECT 3 AS foo, 4 AS bar);\nSELECT _0.bar AS bar FROM ((SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) LEFT UNION ALL BY NAME ON (bar) SELECT 3 AS foo, 4 AS bar) AS _0;\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM ((SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) FULL UNION ALL BY NAME ON (foo, qux) SELECT 3 AS qux, 4 AS bar);\nSELECT _0.foo AS foo, _0.qux AS qux FROM ((SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) FULL UNION ALL BY NAME ON (foo, qux) SELECT 3 AS qux, 4 AS bar) AS _0;\n\n# dialect: bigquery\n# execute: false\nSELECT * FROM (((SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) FULL UNION ALL BY NAME ON (foo, qux) SELECT 3 AS qux, 4 AS bar) INNER UNION ALL BY NAME ON (foo) SELECT 6 AS foo);\nSELECT _0.foo AS foo FROM (((SELECT 1 AS foo, 2 AS bar LEFT UNION ALL BY NAME SELECT 3 AS bar, 4 AS baz) FULL UNION ALL BY NAME ON (foo, qux) SELECT 3 AS qux, 4 AS bar) INNER UNION ALL BY NAME ON (foo) SELECT 6 AS foo) AS _0;\n\n# Title: Nested set operations with modifiers\n# dialect: bigquery\n# execute: false\nWITH t1 AS (SELECT 1 AS a, 2 AS b), t2 AS (SELECT 2 AS b, 3 AS c), t3 AS (SELECT 2 AS c, 3 AS d), t4 AS (SELECT 2 AS e, 3 AS f) SELECT * FROM ((SELECT * FROM t1 FULL OUTER UNION ALL BY NAME (SELECT * FROM t2 FULL OUTER UNION ALL BY NAME (SELECT * FROM t3 FULL OUTER UNION ALL BY NAME SELECT * FROM t4))));\nWITH t1 AS (SELECT 1 AS a, 2 AS b), t2 AS (SELECT 2 AS b, 3 AS c), t3 AS (SELECT 2 AS c, 3 AS d), t4 AS (SELECT 2 AS e, 3 AS f) SELECT _0.a AS a, _0.b AS b, _0.c AS c, _0.d AS d, _0.e AS e, _0.f AS f FROM ((SELECT t1.a AS a, t1.b AS b FROM t1 AS t1 FULL OUTER UNION ALL BY NAME (SELECT t2.b AS b, t2.c AS c FROM t2 AS t2 FULL OUTER UNION ALL BY NAME (SELECT t3.c AS c, t3.d AS d FROM t3 AS t3 FULL OUTER UNION ALL BY NAME SELECT t4.e AS e, t4.f AS f FROM t4 AS t4))) AS _0);\n\n\n# Title: Nested set operations with different modifiers (FULL + INNER)\n# dialect: bigquery\n# execute: false\nWITH t1 AS (SELECT 1 AS a, 2 AS b), t2 AS (SELECT 2 AS b, 3 AS c), t3 AS (SELECT 2 AS c, 3 AS d), t4 AS (SELECT 2 AS e, 3 AS f) SELECT * FROM ((SELECT * FROM t1 FULL OUTER UNION ALL BY NAME (SELECT * FROM t2 INNER UNION ALL BY NAME (SELECT * FROM t3 FULL OUTER UNION ALL BY NAME SELECT * FROM t4))));\nWITH t1 AS (SELECT 1 AS a, 2 AS b), t2 AS (SELECT 2 AS b, 3 AS c), t3 AS (SELECT 2 AS c, 3 AS d), t4 AS (SELECT 2 AS e, 3 AS f) SELECT _0.a AS a, _0.b AS b, _0.c AS c FROM ((SELECT t1.a AS a, t1.b AS b FROM t1 AS t1 FULL OUTER UNION ALL BY NAME (SELECT t2.b AS b, t2.c AS c FROM t2 AS t2 INNER UNION ALL BY NAME (SELECT t3.c AS c, t3.d AS d FROM t3 AS t3 FULL OUTER UNION ALL BY NAME SELECT t4.e AS e, t4.f AS f FROM t4 AS t4))) AS _0);\n\n# Title: Nested set operations with different modifiers (FULL + LEFT)\n# dialect: bigquery\n# execute: false\nWITH t1 AS (SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d), t2 AS (SELECT 2 AS b, 3 AS c), t3 AS (SELECT 2 AS c, 3 AS d), t4 AS (SELECT 2 AS d, 3 AS e) SELECT * FROM ((SELECT * FROM t1 FULL OUTER UNION ALL BY NAME (SELECT * FROM t2 FULL UNION ALL BY NAME (SELECT * FROM t3 LEFT UNION ALL BY NAME SELECT * FROM t4))));\nWITH t1 AS (SELECT 1 AS a, 2 AS b, 3 AS c, 4 AS d), t2 AS (SELECT 2 AS b, 3 AS c), t3 AS (SELECT 2 AS c, 3 AS d), t4 AS (SELECT 2 AS d, 3 AS e) SELECT _0.a AS a, _0.b AS b, _0.c AS c, _0.d AS d FROM ((SELECT t1.a AS a, t1.b AS b, t1.c AS c, t1.d AS d FROM t1 AS t1 FULL OUTER UNION ALL BY NAME (SELECT t2.b AS b, t2.c AS c FROM t2 AS t2 FULL UNION ALL BY NAME (SELECT t3.c AS c, t3.d AS d FROM t3 AS t3 LEFT UNION ALL BY NAME SELECT t4.d AS d, t4.e AS e FROM t4 AS t4))) AS _0);\n\n--------------------------------------\n-- Subqueries\n--------------------------------------\nSELECT a FROM x WHERE b IN (SELECT c FROM y);\nSELECT x.a AS a FROM x AS x WHERE x.b IN (SELECT y.c AS c FROM y AS y);\n\n# execute: false\nSELECT (SELECT c FROM y) FROM x;\nSELECT (SELECT y.c AS c FROM y AS y) AS _col_0 FROM x AS x;\n\n# execute: false\nWITH t(c) AS (SELECT 1) SELECT (SELECT c) FROM t;\nWITH t AS (SELECT 1 AS c) SELECT (SELECT t.c AS c) AS _col_0 FROM t AS t;\n\n# execute: false\nWITH t1(c1) AS (SELECT 1), t2(c2) AS (SELECT 2) SELECT (SELECT c1 FROM t2) FROM t1;\nWITH t1 AS (SELECT 1 AS c1), t2 AS (SELECT 2 AS c2) SELECT (SELECT t1.c1 AS c1 FROM t2 AS t2) AS _col_0 FROM t1 AS t1;\n\nSELECT a FROM (SELECT a FROM x) WHERE a IN (SELECT b FROM (SELECT b FROM y));\nSELECT _1.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _1 WHERE _1.a IN (SELECT _0.b AS b FROM (SELECT y.b AS b FROM y AS y) AS _0);\n\n# dialect: mysql\n# execute: false\nSELECT * FROM table_a as A WHERE A.col1 IN (SELECT MAX(B.col2) FROM table_b as B UNION ALL SELECT MAX(C.col2) FROM table_b as C);\nSELECT * FROM table_a AS `A` WHERE `A`.col1 IN (SELECT MAX(`B`.col2) AS _col_0 FROM table_b AS `B` UNION ALL SELECT MAX(`C`.col2) AS _col_0 FROM table_b AS `C`);\n\n# Title: Unnest deep subquery\nselect * from x where b in ((((select b from y))));\nSELECT x.a AS a, x.b AS b FROM x AS x WHERE x.b IN (SELECT y.b AS b FROM y AS y);\n\n--------------------------------------\n-- Correlated subqueries\n--------------------------------------\nSELECT a FROM x WHERE b IN (SELECT c FROM y WHERE y.b = x.a);\nSELECT x.a AS a FROM x AS x WHERE x.b IN (SELECT y.c AS c FROM y AS y WHERE y.b = x.a);\n\nSELECT a FROM x WHERE b IN (SELECT c FROM y WHERE y.b = a);\nSELECT x.a AS a FROM x AS x WHERE x.b IN (SELECT y.c AS c FROM y AS y WHERE y.b = x.a);\n\nSELECT a FROM x WHERE b IN (SELECT b FROM y AS x);\nSELECT x.a AS a FROM x AS x WHERE x.b IN (SELECT x.b AS b FROM y AS x);\n\nSELECT a FROM x AS i WHERE b IN (SELECT b FROM y AS j WHERE j.b IN (SELECT c FROM y AS k WHERE k.b = j.b));\nSELECT i.a AS a FROM x AS i WHERE i.b IN (SELECT j.b AS b FROM y AS j WHERE j.b IN (SELECT k.c AS c FROM y AS k WHERE k.b = j.b));\n\n# execute: false\nSELECT (SELECT n.a FROM n WHERE n.id = m.id) FROM m AS m;\nSELECT (SELECT n.a AS a FROM n AS n WHERE n.id = m.id) AS _col_0 FROM m AS m;\n\n--------------------------------------\n-- Expand *\n--------------------------------------\nSELECT * FROM x;\nSELECT x.a AS a, x.b AS b FROM x AS x;\n\nSELECT x.* FROM x;\nSELECT x.a AS a, x.b AS b FROM x AS x;\n\nSELECT * FROM x JOIN y ON x.b = y.b;\nSELECT x.a AS a, x.b AS b, y.b AS b, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\nSELECT x.* FROM x JOIN y ON x.b = y.b;\nSELECT x.a AS a, x.b AS b FROM x AS x JOIN y AS y ON x.b = y.b;\n\nSELECT x.*, y.* FROM x JOIN y ON x.b = y.b;\nSELECT x.a AS a, x.b AS b, y.b AS b, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\nSELECT a FROM (SELECT * FROM x);\nSELECT _0.a AS a FROM (SELECT x.a AS a, x.b AS b FROM x AS x) AS _0;\n\nSELECT * FROM (SELECT a FROM x);\nSELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _0;\n\nSELECT * FROM x GROUP BY 1, 2;\nSELECT x.a AS a, x.b AS b FROM x AS x GROUP BY x.a, x.b;\n\nSELECT * FROM (SELECT * FROM x) AS s(a, b);\nSELECT s.a AS a, s.b AS b FROM (SELECT x.a AS a, x.b AS b FROM x AS x) AS s;\n\n# execute: false\nSELECT * FROM (SELECT * FROM t) AS s(a, b);\nSELECT s.a AS a, s.b AS b FROM (SELECT t.a AS a, t.b AS b FROM t AS t) AS s;\n\n# execute: false\nSELECT * FROM (SELECT * FROM t1 UNION ALL SELECT * FROM t2) AS s(b);\nSELECT s.b AS b FROM (SELECT t1.b AS b FROM t1 AS t1 UNION ALL SELECT t2.b AS b FROM t2 AS t2) AS s;\n\n# dialect: bigquery\n# execute: false\nWITH tbl1 AS (SELECT STRUCT(1 AS col1, 2 AS col2, Struct(\"test\" AS col1, Struct(3 AS col2) AS lvl2) AS lvl1) AS col), tbl2 AS (SELECT STRUCT(1 AS col1, 2 AS col2, Struct(\"test\" AS col1, Struct(3 AS col2) AS lvl2) AS lvl1) AS col) SELECT tbl1.col.*, tbl2.col.* FROM tbl1, tbl2;\nWITH tbl1 AS (SELECT STRUCT(1 AS col1, 2 AS col2, Struct('test' AS col1, Struct(3 AS col2) AS lvl2) AS lvl1) AS col), tbl2 AS (SELECT STRUCT(1 AS col1, 2 AS col2, Struct('test' AS col1, Struct(3 AS col2) AS lvl2) AS lvl1) AS col) SELECT tbl1.col.col1 AS col1, tbl1.col.col2 AS col2, tbl1.col.lvl1 AS lvl1, tbl2.col.col1 AS col1, tbl2.col.col2 AS col2, tbl2.col.lvl1 AS lvl1 FROM tbl1 AS tbl1 CROSS JOIN tbl2 AS tbl2;\n\n# dialect: bigquery\n# execute: false\nWITH tbl1 AS (SELECT STRUCT(1 AS col1, 2 AS col2, Struct(\"test\" AS col1, Struct(3 AS col2) AS lvl2) AS lvl1, 3 AS col3) AS col) SELECT tbl1.col.lvl1.* FROM tbl1;\nWITH tbl1 AS (SELECT STRUCT(1 AS col1, 2 AS col2, Struct('test' AS col1, Struct(3 AS col2) AS lvl2) AS lvl1, 3 AS col3) AS col) SELECT tbl1.col.lvl1.col1 AS col1, tbl1.col.lvl1.lvl2 AS lvl2 FROM tbl1 AS tbl1;\n\n# dialect: bigquery\n# execute: false\n# title: Cannot expand struct star with unnamed fields\nWITH tbl1 AS (SELECT STRUCT(1 AS col1, Struct(5 AS col1)) AS col) SELECT tbl1.col.* FROM tbl1;\nWITH tbl1 AS (SELECT STRUCT(1 AS col1, Struct(5 AS col1)) AS col) SELECT tbl1.col.* FROM tbl1 AS tbl1;\n\n# dialect: bigquery\n# execute: false\n# title: Cannot expand struct star with ambiguous fields\nWITH tbl1 AS (SELECT STRUCT(1 AS col1, 2 AS col1) AS col) SELECT tbl1.col.* FROM tbl1;\nWITH tbl1 AS (SELECT STRUCT(1 AS col1, 2 AS col1) AS col) SELECT tbl1.col.* FROM tbl1 AS tbl1;\n\n# dialect: bigquery\n# execute: false\n# title: BigQuery - Expand struct literal\nWITH tbl1 AS (SELECT STRUCT(1 AS f0, 2 as f1) AS col) SELECT tbl1.col.* from tbl1;\nWITH tbl1 AS (SELECT STRUCT(1 AS f0, 2 AS f1) AS col) SELECT tbl1.col.f0 AS f0, tbl1.col.f1 AS f1 FROM tbl1 AS tbl1;\n\n# dialect: bigquery\n# execute: false\n# title: BigQuery - Expand top level nested struct\nSELECT one.* FROM structs;\nSELECT structs.one.a_1 AS a_1, structs.one.b_1 AS b_1 FROM structs AS structs;\n\n# dialect: risingwave\n# execute: false\n# title: RisingWave - Expand top level nested struct\nSELECT (one).* FROM structs;\nSELECT (structs.one).a_1 AS a_1, (structs.one).b_1 AS b_1 FROM structs AS structs;\n\n# dialect: risingwave\n# execute: false\n# title: RisingWave - Preserve struct field identifier quotes\nSELECT (quoted).* FROM structs;\nSELECT (structs.quoted).\"foo bar\" AS \"foo bar\" FROM structs AS structs;\n\n# dialect: bigquery\n# execute: false\n# title: BigQuery - Expand midlevel struct\nSELECT nested_0.nested_1.* FROM structs;\nSELECT structs.nested_0.nested_1.a_2 AS a_2, structs.nested_0.nested_1.nested_2 AS nested_2 FROM structs AS structs;\n\n# dialect: risingwave\n# execute: false\n# title: RisingWave - Expand midlevel struct\nSELECT ((nested_0).nested_1).* FROM structs;\nSELECT ((structs.nested_0).nested_1).a_2 AS a_2, ((structs.nested_0).nested_1).nested_2 AS nested_2 FROM structs AS structs;\n\n# title: CSV files are not scanned by default\n# execute: false\nSELECT * FROM READ_CSV('file.csv');\nSELECT * FROM READ_CSV('file.csv') AS _0;\n\n# dialect: clickhouse\n# Title: Expand tuples in VALUES using the structure provided\n# execute: false\nSELECT * FROM VALUES ('person String, place String', ('Noah', 'Paris'));\nSELECT _0.person AS person, _0.place AS place FROM VALUES ('person String, place String', ('Noah', 'Paris')) AS _0(person, place);\n\n# dialect: clickhouse\n# Title: Expand tuples in VALUES using the default naming scheme in CH\n# execute: false\nSELECT * FROM VALUES ((1, 1), (2, 2));\nSELECT _0.c1 AS c1, _0.c2 AS c2 FROM VALUES ((1, 1), (2, 2)) AS _0(c1, c2);\n\n# dialect: clickhouse\n# Title: Expand fields in VALUES using the default naming scheme in CH\n# execute: false\nSELECT * FROM VALUES (1, 2, 3);\nSELECT _0.c1 AS c1 FROM VALUES ((1), (2), (3)) AS _0(c1);\n\n# title: Expand PIVOT column combinations\n# dialect: duckdb\nWITH cities AS (SELECT * FROM (VALUES ('nl', 'amsterdam', 2000, 1005)) AS t(country, name, year, population)) SELECT * FROM cities PIVOT(SUM(population) AS total, COUNT(population) AS count FOR country IN ('nl', 'us') year IN (2000, 2010) name IN ('amsterdam', 'seattle'));\nWITH cities AS (SELECT t.country AS country, t.name AS name, t.year AS year, t.population AS population FROM (VALUES ('nl', 'amsterdam', 2000, 1005)) AS t(country, name, year, population)) SELECT _0.nl_2000_amsterdam_total AS nl_2000_amsterdam_total, _0.nl_2000_amsterdam_count AS nl_2000_amsterdam_count, _0.nl_2000_seattle_total AS nl_2000_seattle_total, _0.nl_2000_seattle_count AS nl_2000_seattle_count, _0.nl_2010_amsterdam_total AS nl_2010_amsterdam_total, _0.nl_2010_amsterdam_count AS nl_2010_amsterdam_count, _0.nl_2010_seattle_total AS nl_2010_seattle_total, _0.nl_2010_seattle_count AS nl_2010_seattle_count, _0.us_2000_amsterdam_total AS us_2000_amsterdam_total, _0.us_2000_amsterdam_count AS us_2000_amsterdam_count, _0.us_2000_seattle_total AS us_2000_seattle_total, _0.us_2000_seattle_count AS us_2000_seattle_count, _0.us_2010_amsterdam_total AS us_2010_amsterdam_total, _0.us_2010_amsterdam_count AS us_2010_amsterdam_count, _0.us_2010_seattle_total AS us_2010_seattle_total, _0.us_2010_seattle_count AS us_2010_seattle_count FROM cities AS cities PIVOT(SUM(population) AS total, COUNT(population) AS count FOR country IN ('nl', 'us') year IN (2000, 2010) name IN ('amsterdam', 'seattle')) AS _0;\n\n--------------------------------------\n-- CTEs\n--------------------------------------\nWITH z AS (SELECT x.a AS a FROM x) SELECT z.a AS a FROM z;\nWITH z AS (SELECT x.a AS a FROM x AS x) SELECT z.a AS a FROM z AS z;\n\nWITH z(a) AS (SELECT a FROM x) SELECT * FROM z;\nWITH z AS (SELECT x.a AS a FROM x AS x) SELECT z.a AS a FROM z AS z;\n\nWITH z AS (SELECT a FROM x) SELECT * FROM z as q;\nWITH z AS (SELECT x.a AS a FROM x AS x) SELECT q.a AS a FROM z AS q;\n\nWITH z AS (SELECT a FROM x) SELECT * FROM z;\nWITH z AS (SELECT x.a AS a FROM x AS x) SELECT z.a AS a FROM z AS z;\n\nWITH z AS (SELECT a FROM x), q AS (SELECT * FROM z) SELECT * FROM q;\nWITH z AS (SELECT x.a AS a FROM x AS x), q AS (SELECT z.a AS a FROM z AS z) SELECT q.a AS a FROM q AS q;\n\nWITH z AS (SELECT * FROM x) SELECT * FROM z UNION SELECT * FROM z ORDER BY a, b;\nWITH z AS (SELECT x.a AS a, x.b AS b FROM x AS x) SELECT z.a AS a, z.b AS b FROM z AS z UNION SELECT z.a AS a, z.b AS b FROM z AS z ORDER BY a, b;\n\nWITH z AS (SELECT * FROM x), q AS (SELECT b FROM z) SELECT b FROM q;\nWITH z AS (SELECT x.a AS a, x.b AS b FROM x AS x), q AS (SELECT z.b AS b FROM z AS z) SELECT q.b AS b FROM q AS q;\n\nWITH z AS ((SELECT b FROM x UNION ALL SELECT b FROM y) ORDER BY b) SELECT * FROM z;\nWITH z AS ((SELECT x.b AS b FROM x AS x UNION ALL SELECT y.b AS b FROM y AS y) ORDER BY b) SELECT z.b AS b FROM z AS z;\n\nWITH cte(x) AS (SELECT 1) SELECT * FROM cte AS cte(a);\nWITH cte AS (SELECT 1 AS x) SELECT cte.a AS a FROM cte AS cte(a);\n\nWITH cte(x, y) AS (SELECT 1, 2) SELECT cte.* FROM cte AS cte(a);\nWITH cte AS (SELECT 1 AS x, 2 AS y) SELECT cte.a AS a, cte.y AS y FROM cte AS cte(a);\n\n-- Cannot pop table column aliases for recursive ctes (redshift).\nWITH RECURSIVE cte(x) AS (SELECT 1), cte2(y) AS (SELECT 2) SELECT * FROM cte, cte2;\nWITH RECURSIVE cte(x) AS (SELECT 1 AS x), cte2(y) AS (SELECT 2 AS y) SELECT cte.x AS x, cte2.y AS y FROM cte AS cte, cte2 AS cte2;\n\n# execute: false\nWITH player AS (SELECT player.name, player.asset.info FROM players) SELECT * FROM player;\nWITH player AS (SELECT players.player.name AS name, players.player.asset.info AS info FROM players AS players) SELECT player.name AS name, player.info AS info FROM player AS player;\n\n# execute: false\nWITH tesT AS (SELECT c1 FROM t1) SELECT c1 FROM test;\nWITH test AS (SELECT t1.c1 AS c1 FROM t1 AS t1) SELECT test.c1 AS c1 FROM test AS test;\n\n--------------------------------------\n-- Except, Replace, Rename\n--------------------------------------\n# execute: false\nSELECT * RENAME(a AS d) FROM x;\nSELECT x.a AS d, x.b AS b FROM x AS x;\n\n# execute: false\nSELECT * EXCEPT(b) RENAME(a AS d) FROM x;\nSELECT x.a AS d FROM x AS x;\n\nSELECT x.* EXCEPT(a), y.* FROM x, y;\nSELECT x.b AS b, y.b AS b, y.c AS c FROM x AS x, y AS y;\n\nSELECT * EXCEPT(a) FROM x;\nSELECT x.b AS b FROM x AS x;\n\n# execute: false\nSELECT * EXCEPT(x.a) FROM x AS x;\nSELECT x.b AS b FROM x AS x;\n\n# execute: false\n# note: this query would fail in the engine level because there are 0 selected columns\nSELECT * EXCEPT (a, b) FROM x;\nSELECT * EXCEPT (a, b) FROM x AS x;\n\nSELECT x.a, * EXCEPT (a) FROM x AS x LEFT JOIN x AS y USING (a);\nSELECT x.a AS a, x.b AS b, y.b AS b FROM x AS x LEFT JOIN x AS y ON x.a = y.a;\n\nSELECT COALESCE(CAST(t1.a AS VARCHAR), '') AS a, t2.* EXCEPT (a) FROM x AS t1, x AS t2;\nSELECT COALESCE(CAST(t1.a AS VARCHAR), '') AS a, t2.b AS b FROM x AS t1, x AS t2;\n\n# execute: false\nSELECT * REPLACE(2 AS a) FROM x;\nSELECT 2 AS a, x.b AS b FROM x AS x;\n\n# execute: false\nSELECT * EXCEPT (a, b) REPLACE (a AS a) FROM x;\nSELECT * EXCEPT (a, b) REPLACE (x.a AS a) FROM x AS x;\n\n# execute: false\nSELECT * REPLACE(COALESCE(b, a) AS a, a as b) FROM x;\nSELECT COALESCE(x.b, x.a) AS a, x.a AS b FROM x AS x;\n\n# execute: false\nSELECT * REPLACE(1 AS a) RENAME(b as alias_b) FROM x;\nSELECT 1 AS a, x.b AS alias_b FROM x AS x;\n\n# execute: false\nSELECT * EXCEPT(a) REPLACE(COALESCE(a, b) AS b) RENAME(b AS new_b) FROM x;\nSELECT COALESCE(x.a, x.b) AS new_b FROM x AS x;\n\n# execute: false\nSELECT * REPLACE(1 AS a, a AS b) RENAME(b AS new_b) FROM x;\nSELECT 1 AS a, x.a AS new_b FROM x AS x;\n\n--------------------------------------\n-- Using\n--------------------------------------\nSELECT x.b FROM x JOIN y USING (b);\nSELECT x.b AS b FROM x AS x JOIN y AS y ON x.b = y.b;\n\n# execute: false\nWITH cte AS (SELECT a.b.c.d.f.g FROM tbl1) SELECT g FROM (SELECT g FROM tbl2) tbl2 JOIN cte USING(g);\nWITH cte AS (SELECT tbl1.a.b.c.d.f.g AS g FROM tbl1 AS tbl1) SELECT COALESCE(tbl2.g, cte.g) AS g FROM (SELECT tbl2.g AS g FROM tbl2 AS tbl2) AS tbl2 JOIN cte AS cte ON tbl2.g = cte.g;\n\nSELECT x.b FROM x JOIN y USING (b) JOIN z USING (b);\nSELECT x.b AS b FROM x AS x JOIN y AS y ON x.b = y.b JOIN z AS z ON x.b = z.b;\n\nSELECT b FROM x AS x2 JOIN y AS y2 USING (b);\nSELECT COALESCE(x2.b, y2.b) AS b FROM x AS x2 JOIN y AS y2 ON x2.b = y2.b;\n\nSELECT b FROM x JOIN y USING (b) WHERE b = 1 and y.b = 2;\nSELECT COALESCE(x.b, y.b) AS b FROM x AS x JOIN y AS y ON x.b = y.b WHERE COALESCE(x.b, y.b) = 1 AND y.b = 2;\n\nSELECT b FROM x JOIN y USING (b) JOIN z USING (b);\nSELECT COALESCE(x.b, y.b, z.b) AS b FROM x AS x JOIN y AS y ON x.b = y.b JOIN z AS z ON x.b = z.b;\n\nSELECT * FROM x JOIN y USING(b);\nSELECT x.a AS a, COALESCE(x.b, y.b) AS b, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\nSELECT x.* FROM x JOIN y USING(b);\nSELECT x.a AS a, COALESCE(x.b, y.b) AS b FROM x AS x JOIN y AS y ON x.b = y.b;\n\nSELECT * FROM x LEFT JOIN y USING(b);\nSELECT x.a AS a, COALESCE(x.b, y.b) AS b, y.c AS c FROM x AS x LEFT JOIN y AS y ON x.b = y.b;\n\nSELECT b FROM x JOIN y USING(b);\nSELECT COALESCE(x.b, y.b) AS b FROM x AS x JOIN y AS y ON x.b = y.b;\n\nSELECT b, c FROM x JOIN y USING(b);\nSELECT COALESCE(x.b, y.b) AS b, y.c AS c FROM x AS x JOIN y AS y ON x.b = y.b;\n\nSELECT b, c FROM y JOIN z USING(b, c);\nSELECT COALESCE(y.b, z.b) AS b, COALESCE(y.c, z.c) AS c FROM y AS y JOIN z AS z ON y.b = z.b AND y.c = z.c;\n\nSELECT * FROM y JOIN z USING(b, c);\nSELECT COALESCE(y.b, z.b) AS b, COALESCE(y.c, z.c) AS c FROM y AS y JOIN z AS z ON y.b = z.b AND y.c = z.c;\n\nSELECT * FROM y JOIN z USING(b, c) WHERE b = 2 AND c = 3;\nSELECT COALESCE(y.b, z.b) AS b, COALESCE(y.c, z.c) AS c FROM y AS y JOIN z AS z ON y.b = z.b AND y.c = z.c WHERE COALESCE(y.b, z.b) = 2 AND COALESCE(y.c, z.c) = 3;\n\n-- We can safely convert `b` to `x.b` in the following two queries, because the original queries\n-- would be invalid if `b` also existed in `t`'s schema (which we don't know), due to ambiguity.\n\n# execute: false\nSELECT b FROM x JOIN t USING(a);\nSELECT x.b AS b FROM x AS x JOIN t AS t ON x.a = t.a;\n\n# execute: false\nSELECT b FROM t JOIN x USING(a);\nSELECT x.b AS b FROM t AS t JOIN x AS x ON t.a = x.a;\n\n# execute: false\nSELECT a FROM t1 JOIN t2 USING(a);\nSELECT COALESCE(t1.a, t2.a) AS a FROM t1 AS t1 JOIN t2 AS t2 ON t1.a = t2.a;\n\nWITH m(a) AS (SELECT 1), n(b) AS (SELECT 1) SELECT * FROM m JOIN n AS foo(a) USING (a);\nWITH m AS (SELECT 1 AS a), n AS (SELECT 1 AS b) SELECT COALESCE(m.a, foo.a) AS a FROM m AS m JOIN n AS foo(a) ON m.a = foo.a;\n\n# title: coalesce the USING clause's columns (3 joins, 2 join columns)\nWITH t1 AS (SELECT 'x' AS id, DATE '2024-01-01' AS foo, 000 AS value), t2 AS (SELECT 'x' AS id, DATE '2024-02-02' AS foo, 123 AS value), t3 AS (SELECT 'x' AS id, DATE '2024-02-02' AS foo, 456 AS value) SELECT * FROM t1 FULL OUTER JOIN t2 USING(id, foo) FULL OUTER JOIN t3 USING(id, foo);\nWITH t1 AS (SELECT 'x' AS id, CAST('2024-01-01' AS DATE) AS foo, 000 AS value), t2 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 123 AS value), t3 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 456 AS value) SELECT COALESCE(t1.id, t2.id, t3.id) AS id, COALESCE(t1.foo, t2.foo, t3.foo) AS foo, t1.value AS value, t2.value AS value, t3.value AS value FROM t1 AS t1 FULL OUTER JOIN t2 AS t2 ON t1.id = t2.id AND t1.foo = t2.foo FULL OUTER JOIN t3 AS t3 ON COALESCE(t1.id, t2.id) = t3.id AND COALESCE(t1.foo, t2.foo) = t3.foo;\n\n# title: coalesce the USING clause's columns (3 joins, 3 join columns)\nWITH t1 AS (SELECT 'x' AS id, CAST('2024-01-01' AS DATE) AS foo, 000 AS value), t2 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 123 AS value), t3 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 456 AS value) SELECT * FROM t1 FULL OUTER JOIN t2 USING (id, foo, value) FULL OUTER JOIN t3 USING (id, foo, value);\nWITH t1 AS (SELECT 'x' AS id, CAST('2024-01-01' AS DATE) AS foo, 000 AS value), t2 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 123 AS value), t3 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 456 AS value) SELECT COALESCE(t1.id, t2.id, t3.id) AS id, COALESCE(t1.foo, t2.foo, t3.foo) AS foo, COALESCE(t1.value, t2.value, t3.value) AS value FROM t1 AS t1 FULL OUTER JOIN t2 AS t2 ON t1.id = t2.id AND t1.foo = t2.foo AND t1.value = t2.value FULL OUTER JOIN t3 AS t3 ON COALESCE(t1.id, t2.id) = t3.id AND COALESCE(t1.foo, t2.foo) = t3.foo AND COALESCE(t1.value, t2.value) = t3.value;\n\n# title: coalesce the USING clause's columns (4 joins, 2 join columns)\nWITH t1 AS (SELECT 'x' AS id, CAST('2024-01-01' AS DATE) AS foo, 000 AS value), t2 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 123 AS value), t3 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 456 AS value), t4 AS (SELECT 'x' AS id, CAST('2024-03-03' AS DATE) AS foo, 789 AS value) SELECT * FROM t1 FULL OUTER JOIN t2 USING (id, foo) FULL OUTER JOIN t3 USING (id, foo) FULL OUTER JOIN t4 USING (id, foo);\nWITH t1 AS (SELECT 'x' AS id, CAST('2024-01-01' AS DATE) AS foo, 000 AS value), t2 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 123 AS value), t3 AS (SELECT 'x' AS id, CAST('2024-02-02' AS DATE) AS foo, 456 AS value), t4 AS (SELECT 'x' AS id, CAST('2024-03-03' AS DATE) AS foo, 789 AS value) SELECT COALESCE(t1.id, t2.id, t3.id, t4.id) AS id, COALESCE(t1.foo, t2.foo, t3.foo, t4.foo) AS foo, t1.value AS value, t2.value AS value, t3.value AS value, t4.value AS value FROM t1 AS t1 FULL OUTER JOIN t2 AS t2 ON t1.id = t2.id AND t1.foo = t2.foo FULL OUTER JOIN t3 AS t3 ON COALESCE(t1.id, t2.id) = t3.id AND COALESCE(t1.foo, t2.foo) = t3.foo FULL OUTER JOIN t4 AS t4 ON COALESCE(t1.id, t2.id, t3.id) = t4.id AND COALESCE(t1.foo, t2.foo, t3.foo) = t4.foo;\n\n# title: Name anonymous STRUCT fields if replacing USING columns\nWITH t1 AS (SELECT 1 AS id), t2 AS (SELECT 2 AS id) SELECT STRUCT(id) AS my_field FROM t1 JOIN t2 USING (id);\nWITH t1 AS (SELECT 1 AS id), t2 AS (SELECT 2 AS id) SELECT STRUCT(COALESCE(t1.id, t2.id) AS id) AS my_field FROM t1 AS t1 JOIN t2 AS t2 ON t1.id = t2.id;\n\n# title: Do not rename aliased STRUCT fields if replacing USING columns\nWITH t1 AS (SELECT 1 AS id), t2 AS (SELECT 2 AS id) SELECT STRUCT(id AS col) AS my_field FROM t1 JOIN t2 USING (id);\nWITH t1 AS (SELECT 1 AS id), t2 AS (SELECT 2 AS id) SELECT STRUCT(COALESCE(t1.id, t2.id) AS col) AS my_field FROM t1 AS t1 JOIN t2 AS t2 ON t1.id = t2.id;\n\n--------------------------------------\n-- Hint with table reference\n--------------------------------------\n# dialect: spark\nSELECT /*+ BROADCAST(y) */ x.b FROM x JOIN y ON x.b = y.b;\nSELECT /*+ BROADCAST(y) */ x.b AS b FROM x AS x JOIN y AS y ON x.b = y.b;\n\n--------------------------------------\n-- UDTF\n--------------------------------------\n# execute: false\nSELECT c FROM x LATERAL VIEW EXPLODE (a) AS c;\nSELECT _0.c AS c FROM x AS x LATERAL VIEW EXPLODE(x.a) _0 AS c;\n\n# execute: false\nSELECT c FROM xx LATERAL VIEW EXPLODE (a) AS c;\nSELECT _0.c AS c FROM xx AS xx LATERAL VIEW EXPLODE(xx.a) _0 AS c;\n\n# execute: false\nSELECT c FROM x LATERAL VIEW EXPLODE (a) t AS c;\nSELECT t.c AS c FROM x AS x LATERAL VIEW EXPLODE(x.a) t AS c;\n\n# execute: false\nSELECT aa FROM x, UNNEST(a) AS t(aa);\nSELECT t.aa AS aa FROM x AS x, UNNEST(x.a) AS t(aa);\n\n# dialect: bigquery\n# execute: false\nSELECT aa FROM x, UNNEST(a) AS aa;\nSELECT aa AS aa FROM x AS x CROSS JOIN UNNEST(x.a) AS aa;\n\n# dialect: bigquery\n# execute: false\nselect * from unnest ([1, 2]) as x with offset;\nSELECT x AS x, offset AS offset FROM UNNEST([1, 2]) AS x WITH OFFSET AS offset;\n\n# dialect: bigquery\n# execute: false\nselect * from unnest ([1, 2]) as x with offset as y;\nSELECT x AS x, y AS y FROM UNNEST([1, 2]) AS x WITH OFFSET AS y;\n\n# dialect: bigquery\n# execute: false\nselect x, a, x.a from unnest([STRUCT(1 AS a)]) as x CROSS JOIN m;\nSELECT x AS x, a AS a, x.a AS a FROM UNNEST([STRUCT(1 AS a)]) AS x CROSS JOIN m AS m;\n\n# dialect: bigquery\n# execute: false\nWITH cte AS (SELECT [STRUCT(1 AS a)] AS x) select a, x, m.a from cte, UNNEST(x) AS m CROSS JOIN n;\nWITH cte AS (SELECT [STRUCT(1 AS a)] AS x) SELECT a AS a, cte.x AS x, m.a AS a FROM cte AS cte CROSS JOIN UNNEST(cte.x) AS m CROSS JOIN n AS n;\n\n# dialect: presto\nSELECT x.a, i.b FROM x CROSS JOIN UNNEST(SPLIT(CAST(b AS VARCHAR), ',')) AS i(b);\nSELECT x.a AS a, i.b AS b FROM x AS x CROSS JOIN UNNEST(SPLIT(CAST(x.b AS VARCHAR), ',')) AS i(b);\n\n# execute: false\nSELECT c FROM (SELECT 1 a) AS x LATERAL VIEW EXPLODE(a) AS c;\nSELECT _0.c AS c FROM (SELECT 1 AS a) AS x LATERAL VIEW EXPLODE(x.a) _0 AS c;\n\n# execute: false\nSELECT * FROM foo(bar) AS t(c1, c2, c3);\nSELECT t.c1 AS c1, t.c2 AS c2, t.c3 AS c3 FROM FOO(bar) AS t(c1, c2, c3);\n\n# execute: false\nSELECT c1, c3 FROM foo(bar) AS t(c1, c2, c3);\nSELECT t.c1 AS c1, t.c3 AS c3 FROM FOO(bar) AS t(c1, c2, c3);\n\n# dialect: redshift\n# execute: false\nSELECT c.f::VARCHAR(MAX) AS f, e AS e FROM a.b AS c, c.d AS e;\nSELECT CAST(c.f AS VARCHAR(MAX)) AS f, e AS e FROM a.b AS c, c.d AS e;\n\n# dialect: bigquery\nWITH cte AS (SELECT 1 AS col) SELECT * FROM cte LEFT JOIN UNNEST((SELECT ARRAY_AGG(DISTINCT x) AS agg FROM UNNEST([1]) AS x WHERE col = 1));\nWITH cte AS (SELECT 1 AS col) SELECT * FROM cte AS cte LEFT JOIN UNNEST((SELECT ARRAY_AGG(DISTINCT x) AS agg FROM UNNEST([1]) AS x WHERE cte.col = 1));\n\n# dialect: bigquery\nSELECT * FROM UNNEST(ARRAY<STRUCT<percentile STRING, value INT64, score FLOAT64>>[(\"p10\", 1, 0.0)]);\nSELECT percentile AS percentile, value AS value, score AS score FROM UNNEST(ARRAY<STRUCT<percentile STRING, value INT64, score FLOAT64>>[('p10', 1, 0.0)]);\n\n# dialect: bigquery\n# execute: false\nWITH scores AS (SELECT * FROM UNNEST((SELECT ARRAY<STRUCT<percentile STRING, value INT64, score FLOAT64>>[(\"p10\", 1, 0.0)]))) SELECT percentile FROM scores;\nWITH scores AS (SELECT percentile AS percentile, value AS value, score AS score FROM UNNEST((SELECT ARRAY<STRUCT<percentile STRING, value INT64, score FLOAT64>>[('p10', 1, 0.0)] AS _col_0))) SELECT scores.percentile AS percentile FROM scores AS scores;\n\n--------------------------------------\n-- Window functions\n--------------------------------------\n# title: ORDER BY in window function\nSELECT a + 1 AS a, ROW_NUMBER() OVER (PARTITION BY b ORDER BY a) AS row_num FROM x ORDER BY a, row_num;\nSELECT x.a + 1 AS a, ROW_NUMBER() OVER (PARTITION BY x.b ORDER BY x.a) AS row_num FROM x AS x ORDER BY a, row_num;\n\n# dialect: bigquery\nSELECT ROW_NUMBER() OVER (PARTITION BY a ORDER BY b) AS row_num FROM x QUALIFY row_num = 1;\nSELECT ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.b) AS row_num FROM x AS x QUALIFY ROW_NUMBER() OVER (PARTITION BY x.a ORDER BY x.b) = 1;\n\n# dialect: bigquery\nSELECT x.b, x.a FROM x LEFT JOIN y ON x.b = y.b QUALIFY ROW_NUMBER() OVER(PARTITION BY x.b ORDER BY x.a DESC) = 1 ORDER BY x.b, x.a;\nSELECT x.b AS b, x.a AS a FROM x AS x LEFT JOIN y AS y ON x.b = y.b QUALIFY ROW_NUMBER() OVER (PARTITION BY x.b ORDER BY x.a DESC) = 1 ORDER BY x.b, x.a;\n\nSELECT * FROM x QUALIFY COUNT(a) OVER (PARTITION BY b) > 1;\nSELECT x.a AS a, x.b AS b FROM x AS x QUALIFY COUNT(x.a) OVER (PARTITION BY x.b) > 1;\n\n--------------------------------------\n-- Expand laterals\n--------------------------------------\n# execute: false\nSELECT 2 AS d, d + 1 FROM x WHERE d = 2 GROUP BY d;\nSELECT 2 AS d, 2 + 1 AS _col_1 FROM x AS x WHERE 2 = 2 GROUP BY 1;\n\n# title: expand alias reference\nSELECT\n  x.a + 1 AS i,\n  i + 1 AS j,\n  j + 1 AS k\nFROM x;\nSELECT x.a + 1 AS i, x.a + 1 + 1 AS j, x.a + 1 + 1 + 1 AS k FROM x AS x;\n\n# title: noop - reference comes before alias\n# execute: false\n# validate_qualify_columns: false\nSELECT i + 1 AS j, x.a + 1 AS i FROM x;\nSELECT i + 1 AS j, x.a + 1 AS i FROM x AS x;\n\n# title: subquery\nSELECT\n  *\nFROM (\n  SELECT\n    x.a + 1 AS i,\n    i + 1 AS j\n  FROM x\n);\nSELECT _0.i AS i, _0.j AS j FROM (SELECT x.a + 1 AS i, x.a + 1 + 1 AS j FROM x AS x) AS _0;\n\n# title: wrap expanded alias to ensure operator precedence isnt broken\n# execute: false\nSELECT x.a + x.b AS f, f * x.b FROM x;\nSELECT x.a + x.b AS f, (x.a + x.b) * x.b AS _col_1 FROM x AS x;\n\n# title: no need to wrap expanded alias\n# execute: false\nSELECT x.a + x.b AS f, f, f + 5 FROM x;\nSELECT x.a + x.b AS f, x.a + x.b AS _col_1, x.a + x.b + 5 AS _col_2 FROM x AS x;\n\n# title: expand double agg if window func\nSELECT a, SUM(b) AS c, SUM(c) OVER(PARTITION BY a) AS d from x group by 1 ORDER BY a;\nSELECT x.a AS a, SUM(x.b) AS c, SUM(SUM(x.b)) OVER (PARTITION BY x.a) AS d FROM x AS x GROUP BY x.a ORDER BY a;\n\n# title: we can't expand aliases corresponding to recursive CTE columns (CTE names output columns)\n# execute: false\nWITH RECURSIVE t(c) AS (SELECT 1 AS c UNION ALL SELECT c + 1 AS c FROM t WHERE c <= 10) SELECT c FROM t;\nWITH RECURSIVE t(c) AS (SELECT 1 AS c UNION ALL SELECT t.c + 1 AS c FROM t AS t WHERE t.c <= 10) SELECT t.c AS c FROM t AS t;\n\n# title: we can't expand aliases corresponding to recursive CTE columns (CTE doesn't name output columns)\n# execute: false\nWITH RECURSIVE t AS (SELECT 1 AS c UNION ALL SELECT c + 1 AS c FROM t WHERE c <= 10) SELECT c FROM t;\nWITH RECURSIVE t AS (SELECT 1 AS c UNION ALL SELECT t.c + 1 AS c FROM t AS t WHERE t.c <= 10) SELECT t.c AS c FROM t AS t;\n\n# title: expand DISTINCT ON ordinals / projection names\nSELECT DISTINCT ON (new_col, b + 1, 1) t1.a AS new_col FROM x AS t1 ORDER BY new_col;\nSELECT DISTINCT ON (new_col, t1.b + 1, new_col) t1.a AS new_col FROM x AS t1 ORDER BY new_col;\n\n# title: qualify columns for Aggregate Functions and DISTINCT\nSELECT COALESCE(COUNT(DISTINCT a)) AS a FROM x;\nSELECT COALESCE(COUNT(DISTINCT x.a)) AS a FROM x AS x;\n\n# title: Oracle does not support lateral alias expansion\n# dialect: oracle\n# execute: false\nSELECT a AS b, b AS a FROM c;\nSELECT C.A AS B, C.B AS A FROM C C;\n\n# title: enable aliases expansion for the base case of recursive CTE\nWITH RECURSIVE rec AS (SELECT id, parent_id AS parent, 1 AS level FROM (SELECT 1 AS id, 0 AS parent_id) AS t WHERE parent = 0 UNION ALL SELECT rec.id + 10 AS id, rec.id AS parent, rec.level + 1 AS level FROM rec WHERE level < 3) SELECT * FROM rec;\nWITH RECURSIVE rec AS (SELECT t.id AS id, t.parent_id AS parent, 1 AS level FROM (SELECT 1 AS id, 0 AS parent_id) AS t WHERE t.parent_id = 0 UNION ALL SELECT rec.id + 10 AS id, rec.id AS parent, rec.level + 1 AS level FROM rec AS rec WHERE rec.level < 3) SELECT rec.id AS id, rec.parent AS parent, rec.level AS level FROM rec AS rec;\n\nWITH RECURSIVE rec AS (SELECT id, parent_id AS parent, 1 AS level FROM (SELECT 1 AS id, 0 AS parent_id) AS t WHERE parent = 0 UNION ALL SELECT num, val AS x, 2 AS level FROM (SELECT 2 AS num, 1 AS val) AS s WHERE x = 1 UNION ALL SELECT rec.id + 10 AS id, rec.id AS parent, rec.level + 1 AS level FROM rec WHERE rec.level < 3) SELECT * FROM rec ORDER BY rec.id;\nWITH RECURSIVE rec AS (SELECT t.id AS id, t.parent_id AS parent, 1 AS level FROM (SELECT 1 AS id, 0 AS parent_id) AS t WHERE t.parent_id = 0 UNION ALL SELECT s.num AS num, s.val AS x, 2 AS level FROM (SELECT 2 AS num, 1 AS val) AS s WHERE s.val = 1 UNION ALL SELECT rec.id + 10 AS id, rec.id AS parent, rec.level + 1 AS level FROM rec AS rec WHERE rec.level < 3) SELECT rec.id AS id, rec.parent AS parent, rec.level AS level FROM rec AS rec ORDER BY rec.id;\n\nWITH RECURSIVE t(c) AS (SELECT 1 AS c UNION ALL SELECT * FROM (SELECT c + 1 AS c FROM t WHERE c <= 3 UNION ALL SELECT c + 2 AS c FROM t WHERE c <= 3)) SELECT c FROM t ORDER BY c;\nWITH RECURSIVE t(c) AS (SELECT 1 AS c UNION ALL SELECT _0.c AS c FROM (SELECT t.c + 1 AS c FROM t AS t WHERE t.c <= 3 UNION ALL SELECT t.c + 2 AS c FROM t AS t WHERE t.c <= 3) AS _0) SELECT t.c AS c FROM t AS t ORDER BY c;\n\n--------------------------------------\n-- Wrapped tables / join constructs\n--------------------------------------\n# execute: false\nSELECT * FROM ((tbl));\nSELECT * FROM ((tbl AS tbl));\n\nSELECT a, c FROM (x LEFT JOIN y ON a = c);\nSELECT x.a AS a, y.c AS c FROM (x AS x LEFT JOIN y AS y ON x.a = y.c);\n\n# execute: false\nSELECT * FROM ((a CROSS JOIN ((b CROSS JOIN c) CROSS JOIN (d CROSS JOIN e))));\nSELECT * FROM ((a AS a CROSS JOIN ((b AS b CROSS JOIN c AS c) CROSS JOIN (d AS d CROSS JOIN e AS e))));\n\n# execute: false\nSELECT * FROM ((SELECT * FROM tbl));\nSELECT * FROM ((SELECT * FROM tbl AS tbl) AS _0);\n\n# execute: false\nSELECT * FROM ((SELECT c FROM t1) CROSS JOIN t2);\nSELECT * FROM ((SELECT t1.c AS c FROM t1 AS t1) AS _0 CROSS JOIN t2 AS t2);\n\n# execute: false\nSELECT * FROM ((SELECT * FROM x) INNER JOIN y ON a = c);\nSELECT y.b AS b, y.c AS c, _0.a AS a, _0.b AS b FROM ((SELECT x.a AS a, x.b AS b FROM x AS x) AS _0 INNER JOIN y AS y ON _0.a = y.c);\n\nSELECT x.a, y.b, z.c FROM x LEFT JOIN (y INNER JOIN z ON y.c = z.c) ON x.b = y.b;\nSELECT x.a AS a, y.b AS b, z.c AS c FROM x AS x LEFT JOIN (y AS y INNER JOIN z AS z ON y.c = z.c) ON x.b = y.b;\n\nSELECT * FROM ((SELECT * FROM x) INNER JOIN (SELECT * FROM y) ON a = c);\nSELECT _0.a AS a, _0.b AS b, _1.b AS b, _1.c AS c FROM ((SELECT x.a AS a, x.b AS b FROM x AS x) AS _0 INNER JOIN (SELECT y.b AS b, y.c AS c FROM y AS y) AS _1 ON _0.a = _1.c);\n\nSELECT b FROM ((SELECT a FROM x) INNER JOIN y ON a = b);\nSELECT y.b AS b FROM ((SELECT x.a AS a FROM x AS x) AS _0 INNER JOIN y AS y ON _0.a = y.b);\n\nSELECT a, c FROM x TABLESAMPLE SYSTEM (10 ROWS) CROSS JOIN y TABLESAMPLE SYSTEM (10 ROWS);\nSELECT x.a AS a, y.c AS c FROM x AS x TABLESAMPLE SYSTEM (10 ROWS) CROSS JOIN y AS y TABLESAMPLE SYSTEM (10 ROWS);\n\nSELECT x.a FROM x INNER JOIN y ON x.a = c INNER JOIN z ON x.a = z.c;\nSELECT x.a AS a FROM x AS x INNER JOIN y AS y ON x.a = y.c INNER JOIN z AS z ON x.a = z.c;\n\n--------------------------------------\n-- Snowflake allows column alias to be used in almost all clauses\n--------------------------------------\n# title: Snowflake column alias in JOIN\n# dialect: snowflake\n# execute: false\nSELECT x.a AS foo FROM x JOIN y ON foo = y.b;\nSELECT X.A AS FOO FROM X AS X JOIN Y AS Y ON X.A = Y.B;\n\n# title: Snowflake column alias in QUALIFY\n# dialect: snowflake\n# execute: false\nSELECT x.a AS foo FROM x QUALIFY foo = 1;\nSELECT X.A AS FOO FROM X AS X QUALIFY X.A = 1;\n\n# title: Snowflake column alias in GROUP BY\n# dialect: snowflake\n# execute: false\nSELECT x.a AS foo FROM x GROUP BY foo = 1;\nSELECT X.A AS FOO FROM X AS X GROUP BY X.A = 1;\n\n# title: Snowflake column alias in WHERE\n# dialect: snowflake\n# execute: false\nSELECT x.a AS foo FROM x WHERE foo = 1;\nSELECT X.A AS FOO FROM X AS X WHERE X.A = 1;\n\n\n--------------------------------------\n-- SEMI / ANTI Joins\n--------------------------------------\n\n# title: SEMI JOIN table is excluded from the scope\nSELECT * FROM x SEMI JOIN y USING (b);\nSELECT x.a AS a, x.b AS b FROM x AS x SEMI JOIN y AS y ON x.b = y.b;\n\n# title: ANTI JOIN table is excluded from the scope\nSELECT * FROM x ANTI JOIN y USING (b);\nSELECT x.a AS a, x.b AS b FROM x AS x ANTI JOIN y AS y ON x.b = y.b;\n\n# title: SEMI + normal joins reinclude the table on scope\nSELECT * FROM x SEMI JOIN y USING (b) JOIN y USING (b);\nSELECT x.a AS a, COALESCE(x.b, y_2.b) AS b, y_2.c AS c FROM x AS x SEMI JOIN y AS y ON x.b = y.b JOIN y AS y_2 ON x.b = y_2.b;\n\n# title: ANTI + normal joins reinclude the table on scope\nSELECT * FROM x ANTI JOIN y USING (b) JOIN y USING (b);\nSELECT x.a AS a, COALESCE(x.b, y_2.b) AS b, y_2.c AS c FROM x AS x ANTI JOIN y AS y ON x.b = y.b JOIN y AS y_2 ON x.b = y_2.b;\n"
  },
  {
    "path": "tests/fixtures/optimizer/qualify_columns__invalid.sql",
    "content": "SELECT z.a FROM x;\nSELECT z.* FROM x;\nSELECT x FROM x;\nSELECT x FROM VALUES (1, 2);\nSELECT a FROM x AS z JOIN y AS z;\nSELECT a FROM x JOIN (SELECT b FROM y WHERE y.b = x.c);\nSELECT a FROM x AS y JOIN (SELECT a FROM y) AS q ON y.a = q.a;\nSELECT q.a FROM (SELECT x.b FROM x) AS z JOIN (SELECT a FROM z) AS q ON z.b = q.a;\nSELECT b FROM x AS a CROSS JOIN y AS b CROSS JOIN y AS c;\nSELECT x.a FROM x JOIN y USING (a);\nSELECT a, SUM(b) FROM x GROUP BY 3;\nSELECT p FROM (SELECT x from xx) y CROSS JOIN yy CROSS JOIN zz\nSELECT a FROM (SELECT * FROM x CROSS JOIN y);\nSELECT x FROM tbl AS tbl(a);\nSELECT a JOIN b USING (a);\nSELECT x.a FROM x INNER JOIN y ON x.a = c INNER JOIN z ON x.a = c;\nSELECT b FROM x INNER JOIN y ON x.a = y.c INNER JOIN z ON x.a = z.c;\n"
  },
  {
    "path": "tests/fixtures/optimizer/qualify_columns__with_invisible.sql",
    "content": "--------------------------------------\n-- Qualify columns\n--------------------------------------\nSELECT a FROM x;\nSELECT x.a AS a FROM x AS x;\n\nSELECT b FROM x;\nSELECT x.b AS b FROM x AS x;\n\n--------------------------------------\n-- Derived tables\n--------------------------------------\nSELECT x.a FROM x AS x CROSS JOIN (SELECT * FROM x);\nSELECT x.a AS a FROM x AS x CROSS JOIN (SELECT x.a AS a FROM x AS x) AS _0;\n\nSELECT x.b FROM x AS x CROSS JOIN (SELECT b FROM x);\nSELECT x.b AS b FROM x AS x CROSS JOIN (SELECT x.b AS b FROM x AS x) AS _0;\n\n--------------------------------------\n-- Expand *\n--------------------------------------\nSELECT * FROM x;\nSELECT x.a AS a FROM x AS x;\n\nSELECT * FROM y CROSS JOIN z ON y.b = z.b;\nSELECT y.b AS b, z.b AS b FROM y AS y CROSS JOIN z AS z ON y.b = z.b;\n\nSELECT * FROM y CROSS JOIN z ON y.c = z.c;\nSELECT y.b AS b, z.b AS b FROM y AS y CROSS JOIN z AS z ON y.c = z.c;\n\nSELECT a FROM (SELECT * FROM x);\nSELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _0;\n\nSELECT * FROM (SELECT a FROM x);\nSELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _0;\n"
  },
  {
    "path": "tests/fixtures/optimizer/qualify_columns_ddl.sql",
    "content": "# title: Create with CTE\nWITH cte AS (SELECT b FROM y) CREATE TABLE s AS SELECT * FROM cte;\nWITH cte AS (SELECT y.b AS b FROM y AS y) CREATE TABLE s AS SELECT cte.b AS b FROM cte AS cte;\n\n# title: Create with CTE, query also has CTE\nWITH cte1 AS (SELECT b FROM y) CREATE TABLE s AS WITH cte2 AS (SELECT b FROM cte1) SELECT * FROM cte2;\nWITH cte1 AS (SELECT y.b AS b FROM y AS y) CREATE TABLE s AS WITH cte2 AS (SELECT cte1.b AS b FROM cte1 AS cte1) SELECT cte2.b AS b FROM cte2 AS cte2;\n\n# title: Create without CTE\nCREATE TABLE foo AS SELECT a FROM tbl;\nCREATE TABLE foo AS SELECT tbl.a AS a FROM tbl AS tbl;\n\n# title: Create with complex CTE with derived table\nWITH cte AS (SELECT a FROM (SELECT a FROM x)) CREATE TABLE s AS SELECT * FROM cte;\nWITH cte AS (SELECT _0.a AS a FROM (SELECT x.a AS a FROM x AS x) AS _0) CREATE TABLE s AS SELECT cte.a AS a FROM cte AS cte;\n\n# title: Create wtih multiple CTEs\nWITH cte1 AS (SELECT b FROM y), cte2 AS (SELECT b FROM cte1) CREATE TABLE s AS SELECT * FROM cte2;\nWITH cte1 AS (SELECT y.b AS b FROM y AS y), cte2 AS (SELECT cte1.b AS b FROM cte1 AS cte1) CREATE TABLE s AS SELECT cte2.b AS b FROM cte2 AS cte2;\n\n# title: Create with multiple CTEs, selecting only from the first CTE (unnecessary code)\nWITH cte1 AS (SELECT b FROM y), cte2 AS (SELECT b FROM cte1) CREATE TABLE s AS SELECT * FROM cte1;\nWITH cte1 AS (SELECT y.b AS b FROM y AS y), cte2 AS (SELECT cte1.b AS b FROM cte1 AS cte1) CREATE TABLE s AS SELECT cte1.b AS b FROM cte1 AS cte1;\n\n# title: Create with multiple derived tables\nCREATE TABLE s AS SELECT * FROM (SELECT b FROM (SELECT b FROM y));\nCREATE TABLE s AS SELECT _1.b AS b FROM (SELECT _0.b AS b FROM (SELECT y.b AS b FROM y AS y) AS _0) AS _1;\n\n# title: Create with a CTE and a derived table\nWITH cte AS (SELECT b FROM y) CREATE TABLE s AS SELECT * FROM (SELECT b FROM (SELECT b FROM cte));\nWITH cte AS (SELECT y.b AS b FROM y AS y) CREATE TABLE s AS SELECT _1.b AS b FROM (SELECT _0.b AS b FROM (SELECT cte.b AS b FROM cte AS cte) AS _0) AS _1;\n\n# title: Insert with CTE\n# dialect: spark\nWITH cte AS (SELECT b FROM y) INSERT INTO s SELECT * FROM cte;\nWITH cte AS (SELECT y.b AS b FROM y AS y) INSERT INTO s SELECT cte.b AS b FROM cte AS cte;\n\n# title: Insert without CTE\nINSERT INTO foo SELECT a FROM tbl;\nINSERT INTO foo SELECT tbl.a AS a FROM tbl AS tbl;\n"
  },
  {
    "path": "tests/fixtures/optimizer/qualify_tables.sql",
    "content": "# title: single table\nSELECT 1 FROM z;\nSELECT 1 FROM c.db.z AS z;\n\n# title: single table with db\nSELECT 1 FROM y.z;\nSELECT 1 FROM c.y.z AS z;\n\n# title: single table with db, catalog\nSELECT 1 FROM x.y.z;\nSELECT 1 FROM x.y.z AS z;\n\n# title: single table with db, catalog, alias\nSELECT 1 FROM x.y.z AS z;\nSELECT 1 FROM x.y.z AS z;\n\n# title: only information schema\n# dialect: bigquery\nSELECT * FROM information_schema.tables;\nSELECT * FROM c.db.`information_schema.tables` AS tables;\n\n# title: information schema with db\n# dialect: bigquery\nSELECT * FROM y.information_schema.tables;\nSELECT * FROM c.y.`information_schema.tables` AS tables;\n\n# title: information schema with db, catalog\n# dialect: bigquery\nSELECT * FROM x.y.information_schema.tables;\nSELECT * FROM x.y.`information_schema.tables` AS tables;\n\n# title: information schema with db, catalog, alias\n# dialect: bigquery\nSELECT * FROM x.y.information_schema.tables AS z;\nSELECT * FROM x.y.`information_schema.tables` AS z;\n\n# title: redshift unnest syntax, z.a should be a column, not a table\n# dialect: redshift\nSELECT 1 FROM y.z AS z, z.a;\nSELECT 1 FROM c.y.z AS z, z.a;\n\n# title: bigquery implicit unnest syntax, coordinates.position should be a column, not a table\n# dialect: bigquery\nSELECT results FROM Coordinates, coordinates.position AS results;\nSELECT results FROM c.db.Coordinates AS coordinates CROSS JOIN UNNEST(coordinates.position) AS results;\n\n# title: bigquery implicit unnest syntax, table is already qualified\n# dialect: bigquery\nSELECT results FROM db.coordinates, Coordinates.position AS results;\nSELECT results FROM c.db.coordinates AS coordinates CROSS JOIN UNNEST(Coordinates.position) AS results;\n\n# title: bigquery schema name clashes with CTE name - this is a join, not an implicit unnest\n# dialect: bigquery\nWITH Coordinates AS (SELECT [1, 2] AS position) SELECT results FROM Coordinates, `Coordinates.position` AS results;\nWITH Coordinates AS (SELECT [1, 2] AS position) SELECT results FROM Coordinates AS Coordinates CROSS JOIN `c.Coordinates.position` AS results;\n\n# title: single cte\nWITH a AS (SELECT 1 FROM z) SELECT 1 FROM a;\nWITH a AS (SELECT 1 FROM c.db.z AS z) SELECT 1 FROM a AS a;\n\n# title: two ctes that are self-joined\nWITH a AS (SELECT 1 FROM z) SELECT 1 FROM a CROSS JOIN a;\nWITH a AS (SELECT 1 FROM c.db.z AS z) SELECT 1 FROM a AS a CROSS JOIN a AS a;\n\n# title: query that yields a single column as projection\nSELECT (SELECT y.c FROM y AS y) FROM x;\nSELECT (SELECT y.c FROM c.db.y AS y) FROM c.db.x AS x;\n\n# title: pivoted table\nSELECT * FROM x PIVOT (SUM(a) FOR b IN ('a', 'b'));\nSELECT * FROM c.db.x AS x PIVOT(SUM(a) FOR b IN ('a', 'b')) AS _0;\n\n# title: pivoted table, pivot has alias\nSELECT * FROM x PIVOT (SUM(a) FOR b IN ('a', 'b')) AS piv;\nSELECT * FROM c.db.x AS x PIVOT(SUM(a) FOR b IN ('a', 'b')) AS piv;\n\n# title: wrapped table without alias\nSELECT * FROM (tbl);\nSELECT * FROM (c.db.tbl AS tbl);\n\n# title: wrapped table with alias\nSELECT * FROM (tbl AS tbl);\nSELECT * FROM (c.db.tbl AS tbl);\n\n# title: wrapped table with alias using multiple (redundant) parentheses\nSELECT * FROM ((((tbl AS tbl))));\nSELECT * FROM ((((c.db.tbl AS tbl))));\n\n# title: wrapped join of tables without alias\nSELECT * FROM (t1 CROSS JOIN t2);\nSELECT * FROM (c.db.t1 AS t1 CROSS JOIN c.db.t2 AS t2);\n\n# title: wrapped join of tables with alias, expansion of join construct\nSELECT * FROM (t1 CROSS JOIN t2) AS t;\nSELECT * FROM (SELECT * FROM c.db.t1 AS t1 CROSS JOIN c.db.t2 AS t2) AS t;\n\n# title: chained wrapped joins without aliases (1)\nSELECT * FROM ((a CROSS JOIN b) CROSS JOIN c);\nSELECT * FROM ((c.db.a AS a CROSS JOIN c.db.b AS b) CROSS JOIN c.db.c AS c);\n\n# title: chained wrapped joins without aliases (2)\nSELECT * FROM (a CROSS JOIN (b CROSS JOIN c));\nSELECT * FROM (c.db.a AS a CROSS JOIN (c.db.b AS b CROSS JOIN c.db.c AS c));\n\n# title: chained wrapped joins without aliases (3)\nSELECT * FROM ((a CROSS JOIN ((b CROSS JOIN c) CROSS JOIN d)));\nSELECT * FROM ((c.db.a AS a CROSS JOIN ((c.db.b AS b CROSS JOIN c.db.c AS c) CROSS JOIN c.db.d AS d)));\n\n# title: chained wrapped joins without aliases (4)\nSELECT * FROM ((a CROSS JOIN ((b CROSS JOIN c) CROSS JOIN (d CROSS JOIN e))));\nSELECT * FROM ((c.db.a AS a CROSS JOIN ((c.db.b AS b CROSS JOIN c.db.c AS c) CROSS JOIN (c.db.d AS d CROSS JOIN c.db.e AS e))));\n\n# title: chained wrapped joins with aliases\nSELECT * FROM ((a AS foo CROSS JOIN b AS bar) CROSS JOIN c AS baz);\nSELECT * FROM ((c.db.a AS foo CROSS JOIN c.db.b AS bar) CROSS JOIN c.db.c AS baz);\n\n# title: wrapped join with subquery without alias\nSELECT * FROM (tbl1 CROSS JOIN (SELECT * FROM tbl2) AS t1);\nSELECT * FROM (c.db.tbl1 AS tbl1 CROSS JOIN (SELECT * FROM c.db.tbl2 AS tbl2) AS t1);\n\n# title: wrapped join with subquery with alias, parentheses cant be omitted because of alias\nSELECT * FROM (tbl1 CROSS JOIN (SELECT * FROM tbl2) AS t1) AS t2;\nSELECT * FROM (SELECT * FROM c.db.tbl1 AS tbl1 CROSS JOIN (SELECT * FROM c.db.tbl2 AS tbl2) AS t1) AS t2;\n\n# title: join construct as the right operand of a left join\nSELECT * FROM a LEFT JOIN (b INNER JOIN c ON c.id = b.id) ON b.id = a.id;\nSELECT * FROM c.db.a AS a LEFT JOIN (c.db.b AS b INNER JOIN c.db.c AS c ON c.id = b.id) ON b.id = a.id;\n\n# title: nested joins\nSELECT * FROM a LEFT JOIN b INNER JOIN c ON c.id = b.id ON b.id = a.id;\nSELECT * FROM c.db.a AS a LEFT JOIN c.db.b AS b INNER JOIN c.db.c AS c ON c.id = b.id ON b.id = a.id;\n\n# title: parentheses cant be omitted because alias shadows inner table names\nSELECT t.a FROM (tbl AS tbl) AS t;\nSELECT t.a FROM (SELECT * FROM c.db.tbl AS tbl) AS t;\n\n# title: wrapped aliased table with outer alias\nSELECT * FROM ((((tbl AS tbl)))) AS _0;\nSELECT * FROM (SELECT * FROM c.db.tbl AS tbl) AS _0;\n\n# title: join construct with three tables\nSELECT * FROM (tbl1 AS tbl1 JOIN tbl2 AS tbl2 ON id1 = id2 JOIN tbl3 AS tbl3 ON id1 = id3) AS _0;\nSELECT * FROM (SELECT * FROM c.db.tbl1 AS tbl1 JOIN c.db.tbl2 AS tbl2 ON id1 = id2 JOIN c.db.tbl3 AS tbl3 ON id1 = id3) AS _0;\n\n# title: join construct with three tables and redundant set of parentheses\nSELECT * FROM ((tbl1 AS tbl1 JOIN tbl2 AS tbl2 ON id1 = id2 JOIN tbl3 AS tbl3 ON id1 = id3)) AS _0;\nSELECT * FROM (SELECT * FROM c.db.tbl1 AS tbl1 JOIN c.db.tbl2 AS tbl2 ON id1 = id2 JOIN c.db.tbl3 AS tbl3 ON id1 = id3) AS _0;\n\n# title: join construct within join construct\nSELECT * FROM (tbl1 AS tbl1 JOIN (tbl2 AS tbl2 JOIN tbl3 AS tbl3 ON id2 = id3) AS _0 ON id1 = id3) AS _1;\nSELECT * FROM (SELECT * FROM c.db.tbl1 AS tbl1 JOIN (SELECT * FROM c.db.tbl2 AS tbl2 JOIN c.db.tbl3 AS tbl3 ON id2 = id3) AS _0 ON id1 = id3) AS _1;\n\n# title: wrapped subquery without alias\nSELECT * FROM ((SELECT * FROM t));\nSELECT * FROM ((SELECT * FROM c.db.t AS t) AS _0);\n\n# title: wrapped subquery without alias joined with a table\nSELECT * FROM ((SELECT * FROM t1) INNER JOIN t2 ON a = b);\nSELECT * FROM ((SELECT * FROM c.db.t1 AS t1) AS _0 INNER JOIN c.db.t2 AS t2 ON a = b);\n\n# title: lateral unnest with alias\nSELECT x FROM t, LATERAL UNNEST(t.xs) AS x;\nSELECT x FROM c.db.t AS t, LATERAL UNNEST(t.xs) AS x;\n\n# title: lateral unnest without alias\nSELECT x FROM t, LATERAL UNNEST(t.xs);\nSELECT x FROM c.db.t AS t, LATERAL UNNEST(t.xs) AS _0;\n\n# title: table with ordinality\nSELECT * FROM t CROSS JOIN JSON_ARRAY_ELEMENTS(t.response) WITH ORDINALITY AS kv_json;\nSELECT * FROM c.db.t AS t CROSS JOIN JSON_ARRAY_ELEMENTS(t.response) WITH ORDINALITY AS kv_json;\n\n# title: alter table\nALTER TABLE t ADD PRIMARY KEY (id) NOT ENFORCED;\nALTER TABLE c.db.t ADD PRIMARY KEY (id) NOT ENFORCED;\n\n# title: create statement with cte\nCREATE TABLE t1 AS (WITH cte AS (SELECT x FROM t2) SELECT * FROM cte);\nCREATE TABLE c.db.t1 AS (WITH cte AS (SELECT x FROM c.db.t2 AS t2) SELECT * FROM cte AS cte);\n\n# title: delete statement\nDELETE FROM t1 WHERE NOT c IN (SELECT c FROM t2);\nDELETE FROM c.db.t1 WHERE NOT c IN (SELECT c FROM c.db.t2 AS t2);\n\n# title: insert statement with cte\n# dialect: spark\nWITH cte AS (SELECT b FROM y) INSERT INTO s SELECT * FROM cte;\nWITH cte AS (SELECT b FROM c.db.y AS y) INSERT INTO c.db.s SELECT * FROM cte AS cte;\n\n# title: qualify wrapped query\n(SELECT x FROM t);\n(SELECT x FROM c.db.t AS t);\n\n# title: replace columns with db/catalog refs\nSELECT db1.a.id, db2.a.id FROM db1.a JOIN db2.a ON db1.a.id = db2.a.id;\nSELECT a.id, a_2.id FROM c.db1.a AS a JOIN c.db2.a AS a_2 ON a.id = a_2.id;\n\nSELECT cat.db1.a.id, db2.a.id FROM cat.db1.a JOIN db2.a ON cat.db1.a.id = db2.a.id;\nSELECT a.id, a_2.id FROM cat.db1.a AS a JOIN c.db2.a AS a_2 ON a.id = a_2.id;\n\nCOPY INTO (SELECT * FROM x) TO 'data' WITH (FORMAT 'CSV');\nCOPY INTO (SELECT * FROM c.db.x AS x) TO 'data' WITH (FORMAT 'CSV');\n\n# title: tablesample\nSELECT 1 FROM x TABLESAMPLE SYSTEM (10 PERCENT) CROSS JOIN y TABLESAMPLE SYSTEM (10 PERCENT);\nSELECT 1 FROM c.db.x AS x TABLESAMPLE SYSTEM (10 PERCENT) CROSS JOIN c.db.y AS y TABLESAMPLE SYSTEM (10 PERCENT);\n\nWITH cte_tbl AS (SELECT 1 AS col2) UPDATE y SET col1 = (SELECT * FROM x) WHERE EXISTS(SELECT 1 FROM cte_tbl);\nWITH cte_tbl AS (SELECT 1 AS col2) UPDATE c.db.y SET col1 = (SELECT * FROM c.db.x AS x) WHERE EXISTS(SELECT 1 FROM cte_tbl AS cte_tbl);\n\n# title: avoid qualifying CTE with UPDATE\nWITH cte AS (SELECT 1 AS c, 'name' AS name) UPDATE t SET name = cte.name FROM cte WHERE cte.c = 1;\nWITH cte AS (SELECT 1 AS c, 'name' AS name) UPDATE c.db.t SET name = cte.name FROM cte WHERE cte.c = 1;\n\n# title: avoid qualifying CTE with DELETE\nWITH cte AS (SELECT 1 AS c, 'name' AS name) DELETE t FROM t AS t INNER JOIN cte ON t.id = cte.c;\nWITH cte AS (SELECT 1 AS c, 'name' AS name) DELETE c.db.t FROM c.db.t AS t INNER JOIN cte ON t.id = cte.c;\n\n# title: canonicalize single table alias\n# canonicalize_table_aliases: true\nSELECT * FROM t;\nSELECT * FROM c.db.t AS _0;\n\n# title: canonicalize join table aliases\n# canonicalize_table_aliases: true\nSELECT * FROM t1 JOIN t2 ON t1.id = t2.id;\nSELECT * FROM c.db.t1 AS _0 JOIN c.db.t2 AS _1 ON _0.id = _1.id;\n\n# title: canonicalize join with different databases\n# canonicalize_table_aliases: true\nSELECT * FROM db1.users JOIN db2.users ON db1.users.id = db2.users.id;\nSELECT * FROM c.db1.users AS _0 JOIN c.db2.users AS _1 ON _0.id = _1.id;\n\n# title: canonicalize CTE alias\n# canonicalize_table_aliases: true\nWITH cte AS (SELECT * FROM t) SELECT * FROM cte;\nWITH cte AS (SELECT * FROM c.db.t AS _0) SELECT * FROM cte AS _1;\n\n# title: canonicalize subquery alias\n# canonicalize_table_aliases: true\nSELECT * FROM (SELECT * FROM t);\nSELECT * FROM (SELECT * FROM c.db.t AS _0) AS _1;\n\n# title: canonicalize multiple tables with subquery\n# canonicalize_table_aliases: true\nSELECT * FROM t1, (SELECT * FROM t2) AS sub, t3;\nSELECT * FROM c.db.t1 AS _2, (SELECT * FROM c.db.t2 AS _0) AS _1, c.db.t3 AS _3;\n\n# title: canonicalize CTE with PIVOT\n# canonicalize_table_aliases: true\nWITH cte AS (SELECT * FROM t) SELECT * FROM cte PIVOT(SUM(c) FOR v IN ('x', 'y'));\nWITH cte AS (SELECT * FROM c.db.t AS _0) SELECT * FROM cte AS _1 PIVOT(SUM(c) FOR v IN ('x', 'y')) AS _2;\n\n# title: canonicalize sources that reference external columns\n# canonicalize_table_aliases: true\nSELECT * FROM x WHERE x.a = (SELECT SUM(y.c) AS c FROM y WHERE y.a = x.a LIMIT 10);\nSELECT * FROM c.db.x AS _1 WHERE _1.a = (SELECT SUM(_0.c) AS c FROM c.db.y AS _0 WHERE _0.a = _1.a LIMIT 10);\n\n# title: canonicalize sources that have colliding aliases\n# canonicalize_table_aliases: true\nSELECT t.foo FROM t AS t, (SELECT t.bar FROM t AS t);\nSELECT _2.foo FROM c.db.t AS _2, (SELECT _0.bar FROM c.db.t AS _0) AS _1;\n\n# title: Qualify GENERATE_SERIES with its default column generate_series\n# dialect: postgres\nSELECT generate_series FROM GENERATE_SERIES(1,2);\nSELECT generate_series FROM GENERATE_SERIES(1, 2) AS _0(generate_series);\n\n# title: Qualify GENERATE_SERIES with alias by wrapping it\n# dialect: postgres\nSELECT g FROM GENERATE_SERIES(1,2) AS g;\nSELECT g FROM GENERATE_SERIES(1, 2) AS _0(g);\n\n# title: Qualify GENERATE_SERIES with alias on table and columns\n# dialect: postgres\nSELECT g FROM GENERATE_SERIES(1,2) AS t(g);\nSELECT g FROM GENERATE_SERIES(1, 2) AS t(g);\n\n# title: Qualify GENERATE_SERIES with explicit column and canonicalize_table_aliases\n# dialect: postgres\n# canonicalize_table_aliases: true\nSELECT g FROM GENERATE_SERIES(1,2) AS t(g);\nSELECT g FROM GENERATE_SERIES(1, 2) AS _0(g);\n"
  },
  {
    "path": "tests/fixtures/optimizer/quote_identifiers.sql",
    "content": "SELECT a FROM x;\nSELECT \"a\" FROM \"x\";\n\nSELECT \"a\" FROM \"x\";\nSELECT \"a\" FROM \"x\";\n\nSELECT x.a AS a FROM db.x;\nSELECT \"x\".\"a\" AS \"a\" FROM \"db\".\"x\";\n\nSELECT @x;\nSELECT @x;\n\n# dialect: snowflake\nSELECT * FROM DUAL;\nSELECT * FROM DUAL;\n\n# dialect: snowflake\nSELECT * FROM \"DUAL\";\nSELECT * FROM \"DUAL\";\n\n# dialect: snowflake\nSELECT * FROM \"dual\";\nSELECT * FROM \"dual\";\n\n# dialect: snowflake\nSELECT dual FROM t;\nSELECT \"dual\" FROM \"t\";\n\n# dialect: snowflake\nSELECT * FROM t AS dual;\nSELECT * FROM \"t\" AS \"dual\";\n\n# dialect: bigquery\nSELECT `p.d.udf`(data).* FROM `p.d.t`;\nSELECT `p.d.udf`(`data`).* FROM `p.d.t`;\n"
  },
  {
    "path": "tests/fixtures/optimizer/simplify.sql",
    "content": "--------------------------------------\n-- Conditions\n--------------------------------------\nx AND x;\nx AND TRUE;\n\ny OR y;\ny AND TRUE;\n\nx AND NOT x;\nNOT x AND x;\n\nx OR NOT x;\nNOT x OR x;\n\n1 AND TRUE;\nTRUE;\n\nTRUE AND TRUE;\nTRUE;\n\n1 AND TRUE AND 1 AND 1;\nTRUE;\n\nTRUE AND FALSE;\nFALSE;\n\nFALSE AND FALSE;\nFALSE;\n\nFALSE AND TRUE AND TRUE;\nFALSE;\n\nx > y OR FALSE;\nx > y;\n\nFALSE OR x = y;\nx = y;\n\n1 = 1;\nTRUE;\n\n1.0 = 1;\nTRUE;\n\nCAST('2023-01-01' AS DATE) = CAST('2023-01-01' AS DATE);\nTRUE;\n\n'x' = 'y';\nFALSE;\n\n'x' = 'x';\nTRUE;\n\nSTRUCT(NULL AS a);\nSTRUCT(NULL AS a);\n\nNULL AND TRUE;\nNULL AND TRUE;\n\nNULL AND FALSE;\nFALSE;\n\nNULL AND NULL;\nNULL AND TRUE;\n\nNULL OR TRUE;\nTRUE;\n\nNULL OR NULL;\nNULL AND TRUE;\n\nFALSE OR NULL;\nNULL AND TRUE;\n\nNOT TRUE;\nFALSE;\n\nNOT FALSE;\nTRUE;\n\nNOT NULL;\nNULL AND TRUE;\n\nNULL = NULL;\nNULL = NULL;\n\nSELECT (EXISTS(SELECT 1 WHERE FALSE)) AND NULL;\nSELECT EXISTS(SELECT 1 WHERE FALSE) AND NULL;\n\nSELECT NULL AND (EXISTS(SELECT 1 WHERE FALSE));\nSELECT EXISTS(SELECT 1 WHERE FALSE) AND NULL;\n\n1 AND 0;\nFALSE;\n\n0 AND 1;\nFALSE;\n\n0 OR 1;\nTRUE;\n\n0 OR NULL;\nNULL AND TRUE;\n\nNULL OR 0;\nNULL AND TRUE;\n\n0 AND NULL;\nFALSE;\n\nNULL AND 0;\nFALSE;\n\n-- Can't optimize this because different engines do different things\n-- mysql converts to 0 and 1 but tsql does true and false\nNULL <=> NULL;\nNULL IS NOT DISTINCT FROM NULL;\n\na IS NOT DISTINCT FROM a;\na IS NOT DISTINCT FROM a;\n\nNULL IS DISTINCT FROM NULL;\nNULL IS DISTINCT FROM NULL;\n\nNOT (NOT TRUE);\nTRUE;\n\na AND (b OR b);\na AND b;\n\na AND (b AND b);\na AND b;\n\n-- bigquery doesn't allow unparenthesis comparisons\n(x is not null) != (y is null);\n(NOT x IS NULL) <> (y IS NULL);\n\n# dialect: mysql\nA XOR A;\nA XOR A;\n\n# dialect: mysql\nSELECT DISTINCT GREATEST(EXISTS(SELECT 1 WHERE FALSE), (EXISTS(SELECT 1 WHERE FALSE)) XOR ((0.08) IN ((t1.c0) XOR (t1.c0)))) AS ref0 FROM (SELECT NULL AS c0 UNION ALL SELECT 1 AS c0) AS t1, (SELECT 0.01 AS c1) AS t0;\nSELECT DISTINCT GREATEST(EXISTS(SELECT 1 WHERE FALSE), 0.08 IN (t1.c0 XOR t1.c0) XOR EXISTS(SELECT 1 WHERE FALSE)) AS ref0 FROM (SELECT NULL AS c0 UNION ALL SELECT 1 AS c0) AS t1, (SELECT 0.01 AS c1) AS t0;\n\nTRUE AND TRUE OR TRUE AND FALSE;\nTRUE;\n\nCOALESCE(x, y) <> ALL (SELECT z FROM w);\nCOALESCE(x, y) <> ALL (SELECT z FROM w);\n\nSELECT NOT (2 <> ALL (SELECT 2 UNION ALL SELECT 3));\nSELECT 2 = ANY(SELECT 2 UNION ALL SELECT 3);\n\nSELECT t_bool.a AND TRUE FROM t_bool;\nSELECT t_bool.a FROM t_bool;\n\nSELECT TRUE AND t_bool.a FROM t_bool;\nSELECT t_bool.a FROM t_bool;\n\nSELECT t_bool.a OR FALSE FROM t_bool;\nSELECT t_bool.a FROM t_bool;\n\nSELECT FALSE OR t_bool.a FROM t_bool;\nSELECT t_bool.a FROM t_bool;\n\n--------------------------------------\n-- Absorption\n--------------------------------------\n(A OR B) AND (C OR NOT A);\n(A OR B) AND (C OR NOT A);\n\nA AND (A OR B);\nA AND TRUE;\n\nA AND D AND E AND (B OR A);\nA AND D AND E;\n\nD AND A AND E AND (B OR A);\nA AND D AND E;\n\n(A OR B) AND A;\nA AND TRUE;\n\nC AND D AND (A OR B) AND E AND F AND A;\nA AND C AND D AND E AND F;\n\nA OR (A AND B);\nA AND TRUE;\n\n(A AND B) OR A;\nA AND TRUE;\n\nA AND (NOT A OR B);\nA AND B;\n\n(NOT A OR B) AND A;\nA AND B;\n\nA OR (NOT A AND B);\nA OR B;\n\nA OR ((((NOT A AND B))));\nA OR B;\n\n(A OR C) AND ((A OR C) OR B);\nA OR C;\n\n(A OR C) AND (A OR B OR C);\nA OR C;\n\nA AND (B AND C) AND (D AND E);\nA AND B AND C AND D AND E;\n\nA AND (A OR B) AND (A OR B OR C);\nA AND TRUE;\n\n(A OR B) AND (A OR C) AND (A OR B OR C);\n(A OR B) AND (A OR C);\n\n--------------------------------------\n-- Elimination\n--------------------------------------\n(A AND B) OR (A AND NOT B);\nA AND TRUE;\n\n(A AND B) OR (NOT A AND B);\nB AND TRUE;\n\n(A AND NOT B) OR (A AND B);\nA AND TRUE;\n\n(NOT A AND B) OR (A AND B);\nB AND TRUE;\n\n(A OR B) AND (A OR NOT B);\nA AND TRUE;\n\n(A OR B) AND (NOT A OR B);\nB AND TRUE;\n\n(A OR NOT B) AND (A OR B);\nA AND TRUE;\n\n(NOT A OR B) AND (A OR B);\nB AND TRUE;\n\n(NOT A OR NOT B) AND (NOT A OR B);\nNOT A;\n\n(NOT A OR NOT B) AND (NOT A OR NOT NOT B);\nNOT A;\n\nE OR (A AND B) OR C OR D OR (A AND NOT B);\nA OR C OR D OR E;\n\n(A AND B) OR (A AND NOT B) OR (A AND NOT B);\nA AND TRUE;\n\n(A AND B) OR (A AND B) OR (A AND NOT B);\nA AND TRUE;\n\n(A AND B) OR (A AND NOT B) OR (A AND B) OR (A AND NOT B);\nA AND TRUE;\n\nSELECT t_bool.a OR t_bool.a FROM t_bool;\nSELECT t_bool.a FROM t_bool;\n\nSELECT t_bool.a AND t_bool.a FROM t_bool;\nSELECT t_bool.a FROM t_bool;\n\nSELECT SUM(t.x OR t.x) FROM t;\nSELECT SUM(t.x AND TRUE) FROM t;\n\nSELECT SUM(t.x AND t.x) FROM t;\nSELECT SUM(t.x AND TRUE) FROM t;\n\n--------------------------------------\n-- Associativity\n--------------------------------------\n(A AND B) AND C;\nA AND B AND C;\n\nA AND (B AND C);\nA AND B AND C;\n\n(A OR B) OR C;\nA OR B OR C;\n\nA OR (B OR C);\nA OR B OR C;\n\n((A AND B) AND C) AND D;\nA AND B AND C AND D;\n\n(((((A) AND B)) AND C)) AND D;\nA AND B AND C AND D;\n\n(x + 1) + 2;\nx + 3;\n\nx + (1 + 2);\nx + 3;\n\n(x * 2) * 4 + (1 + 3) + 5;\nx * 8 + 9;\n\n(x - 1) - 2;\n(x - 1) - 2;\n\nx - (3 - 2);\nx - 1;\n\n--------------------------------------\n-- Comparison and Pruning\n--------------------------------------\nA AND D AND B AND E AND F AND G AND E AND A;\nA AND B AND D AND E AND F AND G;\n\nA OR D OR B OR E OR F OR G OR E OR A;\nA OR B OR D OR E OR F OR G;\n\n# dialect: mysql\nA XOR D XOR B XOR E XOR F XOR G XOR C;\nA XOR B XOR C XOR D XOR E XOR F XOR G;\n\nA AND NOT B AND C AND B;\nA AND B AND C AND NOT B;\n\n(a AND b AND c AND d) AND (d AND c AND b AND a);\na AND b AND c AND d;\n\n(c AND (a AND b)) AND ((b AND a) AND c);\na AND b AND c;\n\n(A AND B AND C) OR (C AND B AND A);\nA AND B AND C;\n\n--------------------------------------\n-- Where removal\n--------------------------------------\nSELECT x WHERE TRUE;\nSELECT x;\n\nSELECT x FROM y JOIN z ON TRUE;\nSELECT x FROM y CROSS JOIN z;\n\nSELECT x FROM y RIGHT JOIN z ON TRUE;\nSELECT x FROM y CROSS JOIN z;\n\nSELECT x FROM y LEFT JOIN z ON TRUE;\nSELECT x FROM y LEFT JOIN z ON TRUE;\n\nSELECT x FROM y FULL OUTER JOIN z ON TRUE;\nSELECT x FROM y FULL OUTER JOIN z ON TRUE;\n\nSELECT x FROM y JOIN z USING (x);\nSELECT x FROM y JOIN z USING (x);\n\n--------------------------------------\n-- Parenthesis removal\n--------------------------------------\n(TRUE);\nTRUE;\n\n(FALSE);\nFALSE;\n\n((TRUE));\nTRUE;\n\n(FALSE OR TRUE);\nTRUE;\n\nTRUE OR (((FALSE) OR (TRUE)) OR FALSE);\nTRUE;\n\n(NOT FALSE) AND (NOT TRUE);\nFALSE;\n\n((NOT FALSE) AND (x = x)) AND (TRUE OR 1 <> 3);\nx = x;\n\n((NOT FALSE) AND (x = x)) AND (FALSE OR 1 <> 2);\nx = x;\n\n(('a' = 'a') AND TRUE and NOT FALSE);\nTRUE;\n\n(x = y) and z;\nx = y AND z;\n\nx * (1 - y);\nx * (1 - y);\n\n(((x % 20) = 0) = TRUE);\n((x % 20) = 0) = TRUE;\n\nANY(t.value);\nANY(t.value);\n\nSELECT (ARRAY_AGG(foo))[1];\nSELECT (ARRAY_AGG(foo))[1];\n\nSELECT -(x.a > x.b) FROM x;\nSELECT -(x.a > x.b) FROM x;\n\nSELECT (-((x.a) IS NULL)) FROM x;\nSELECT -(x.a IS NULL) FROM x;\n\nSELECT * FROM A WHERE a - (b < c) < 0 AND a + (b > c) >= 0;\nSELECT * FROM A WHERE a + (b > c) >= 0 AND a - (b < c) < 0;\n\n\n--------------------------------------\n-- Literals\n--------------------------------------\n1 + 1;\n2;\n\n0.06 + 0.01;\n0.07;\n\n0.06 + 1;\n1.06;\n\n1.2E+1 + 15E-3;\n12.015;\n\n1.2E1 + 15E-3;\n12.015;\n\n1 - 2;\n-1;\n\n-1 + 3;\n2;\n\n1 - 2 - 4;\n-5;\n\n-(-1);\n1;\n\n- -+1;\n1;\n\n+-1;\n-1;\n\n++1;\n1;\n\n0.06 - 0.01;\n0.05;\n\n3 * 4;\n12;\n\n3.0 * 9;\n27.0;\n\n0.03 * 0.73;\n0.0219;\n\n1 / 3;\n1 / 3;\n\n1 / 3.0;\n0.3333333333333333333333333333;\n\n20.0 / 6;\n3.333333333333333333333333333;\n\n10 / 5;\n10 / 5;\n\n(1.0 * 3) * 4 - 2 * (5 / 2);\n12.0 - 2 * (5 / 2);\n\na * 0.5 / 10 / (2.0 + 3);\na * 0.5 / 10 / 5.0;\n\na * 0.5 - 10 - (2.0 + 3);\na * 0.5 - 10 - 5.0;\n\nx * (10 - 5);\nx * 5;\n\n6 - 2 + 4 * 2 + a;\n12 + a;\n\na + 1 + 1 + 2;\na + 4;\n\na + (1 + 1) + (10);\na + 12;\n\na + (1 * 1) + (1 - (1 * 1));\na + 1;\n\na + (b * c) + (d - (e * f));\na + b * c + (d - e * f);\n\n5 + 4 * 3;\n17;\n\n1 < 2;\nTRUE;\n\n2 <= 2;\nTRUE;\n\n2 >= 2;\nTRUE;\n\n2 > 1;\nTRUE;\n\n2 > 2.5;\nFALSE;\n\n3 > 2.5;\nTRUE;\n\n1 > NULL;\n1 > NULL;\n\n1 <= NULL;\n1 <= NULL;\n\n1 IS NULL;\nFALSE;\n\nNULL IS NULL;\nTRUE;\n\nNULL IS NOT NULL;\nFALSE;\n\n1 IS NOT NULL;\nTRUE;\n\ndate '1998-12-01' - interval x day;\nCAST('1998-12-01' AS DATE) - INTERVAL x DAY;\n\ndate '1998-12-01' - interval '90' day;\nCAST('1998-09-02' AS DATE);\n\ndate '1998-12-01' + interval '1' week;\nCAST('1998-12-08' AS DATE);\n\ninterval '1' year + date '1998-01-01';\nCAST('1999-01-01' AS DATE);\n\ninterval '1' year + date '1998-01-01' + 3 * 7 * 4;\nCAST('1999-01-01' AS DATE) + 84;\n\ndate '1998-12-01' - interval '90' foo;\nCAST('1998-12-01' AS DATE) - INTERVAL '90' FOO;\n\ndate '1998-12-01' + interval '90' foo;\nCAST('1998-12-01' AS DATE) + INTERVAL '90' FOO;\n\nCAST(x AS DATE) + interval '1' week;\nCAST(x AS DATE) + INTERVAL '1' WEEK;\n\nCAST('2008-11-11' AS DATETIME) + INTERVAL '5' MONTH;\nCAST('2009-04-11 00:00:00' AS DATETIME);\n\ndatetime '1998-12-01' - interval '90' day;\nCAST('1998-09-02 00:00:00' AS DATETIME);\n\nCAST(x AS DATETIME) + interval '1' WEEK;\nCAST(x AS DATETIME) + INTERVAL '1' WEEK;\n\n# dialect: bigquery\nCAST('2023-01-01' AS TIMESTAMP) + INTERVAL 1 DAY;\nCAST('2023-01-02 00:00:00' AS TIMESTAMP);\n\n# dialect: bigquery\nINTERVAL 1 DAY + CAST('2023-01-01' AS TIMESTAMP);\nCAST('2023-01-02 00:00:00' AS TIMESTAMP);\n\n# dialect: bigquery\nCAST('2023-01-02' AS TIMESTAMP) - INTERVAL 1 DAY;\nCAST('2023-01-01 00:00:00' AS TIMESTAMP);\n\nTS_OR_DS_TO_DATE('1998-12-01 00:00:01') - interval '90' day;\nCAST('1998-09-02' AS DATE);\n\nDATE_ADD(CAST('2023-01-02' AS DATE), -2, 'MONTH');\nCAST('2022-11-02' AS DATE);\n\nDATE_SUB(CAST('2023-01-02' AS DATE), 1 + 1, 'DAY');\nCAST('2022-12-31' AS DATE);\n\nDATE_ADD(CAST('2023-01-02' AS DATETIME), -2, 'HOUR');\nCAST('2023-01-01 22:00:00' AS DATETIME);\n\nDATETIME_ADD(CAST('2023-01-02' AS DATETIME), -2, 'HOUR');\nCAST('2023-01-01 22:00:00' AS DATETIME);\n\nDATETIME_SUB(CAST('2023-01-02' AS DATETIME), 1 + 1, 'HOUR');\nCAST('2023-01-01 22:00:00' AS DATETIME);\n\nDATE_ADD(x, 1, 'MONTH');\nDATE_ADD(x, 1, 'MONTH');\n\nDATE_ADD(x, 1);\nDATE_ADD(x, 1, 'DAY');\n\nSELECT 1 WHERE 'foo';\nSELECT 1 WHERE 'foo';\n\nSELECT 1 WHERE NOT 'foo';\nSELECT 1 WHERE NOT 'foo';\n\n--------------------------------------\n-- Comparisons\n--------------------------------------\nx < 0 OR x > 1;\nx < 0 OR x > 1;\n\nx < 0 OR x > 0;\nx < 0 OR x > 0;\n\nx < 1 OR x > 0;\nx < 1 OR x > 0;\n\nx < 1 OR x >= 0;\nx < 1 OR x >= 0;\n\nx <= 1 OR x > 0;\nx <= 1 OR x > 0;\n\nx <= 1 OR x >= 0;\nx <= 1 OR x >= 0;\n\nx <= 1 AND x <= 0;\nx <= 0;\n\nx <= 1 AND x > 0;\nx <= 1 AND x > 0;\n\nx <= 1 OR x > 0;\nx <= 1 OR x > 0;\n\nx <= 0 OR x < 0;\nx <= 0;\n\nx >= 0 OR x > 0;\nx >= 0;\n\nx >= 0 OR x > 1;\nx >= 0;\n\nx <= 0 OR x >= 0;\nx <= 0 OR x >= 0;\n\nx <= 0 AND x >= 0;\nx <= 0 AND x >= 0;\n\nx < 1 AND x < 2;\nx < 1;\n\nx < 1 OR x < 2;\nx < 2;\n\nx < 2 AND x < 1;\nx < 1;\n\nx < 2 OR x < 1;\nx < 2;\n\nx < 1 AND x < 1;\nx < 1;\n\nx < 1 OR x < 1;\nx < 1;\n\nx <= 1 AND x < 1;\nx < 1;\n\nx <= 1 OR x < 1;\nx <= 1;\n\nx < 1 AND x <= 1;\nx < 1;\n\nx < 1 OR x <= 1;\nx <= 1;\n\nx > 1 AND x > 2;\nx > 2;\n\nx > 1 OR x > 2;\nx > 1;\n\nx > 2 AND x > 1;\nx > 2;\n\nx > 2 OR x > 1;\nx > 1;\n\nx > 1 AND x > 1;\nx > 1;\n\nx > 1 OR x > 1;\nx > 1;\n\nx >= 1 AND x > 1;\nx > 1;\n\nx >= 1 OR x > 1;\nx >= 1;\n\nx > 1 AND x >= 1;\nx > 1;\n\nx > 1 OR x >= 1;\nx >= 1;\n\nx > 1 AND x >= 2;\nx >= 2;\n\nx > 1 OR x >= 2;\nx > 1;\n\nx > 1 AND x >= 2 AND x > 3 AND x > 0;\nx > 3;\n\n(x > 1 AND x >= 2 AND x > 3 AND x > 0) OR x > 0;\nx > 0;\n\nx > 1 AND x < 2 AND x > 3;\nFALSE;\n\nx > 1 AND x < 1;\nFALSE;\n\nx < 2 AND x > 1;\nx < 2 AND x > 1;\n\nx = 1 AND x < 1;\nFALSE;\n\nx = 1 AND x < 1.1;\nx = 1;\n\nx = 1 AND x <= 1;\nx = 1;\n\nx = 1 AND x <= 0.9;\nFALSE;\n\nx = 1 AND x > 0.9;\nx = 1;\n\nx = 1 AND x > 1;\nFALSE;\n\nx = 1 AND x >= 1;\nx = 1;\n\nx = 1 AND x >= 2;\nFALSE;\n\nx = 1 AND x <> 2;\nx = 1;\n\nx <> 1 AND x = 1;\nFALSE;\n\nx BETWEEN 0 AND 5 AND x > 3;\nx <= 5 AND x > 3;\n\nx > 3 AND 5 > x AND x BETWEEN 0 AND 10;\nx < 5 AND x > 3;\n\nx > 3 AND 5 < x AND x BETWEEN 9 AND 10;\nx <= 10 AND x >= 9;\n\nNOT x BETWEEN 0 AND 1;\nx < 0 OR x > 1;\n\n1 < x AND 3 < x;\nx > 3;\n\n'a' < 'b';\nTRUE;\n\nx = 2018 OR x <> 2018;\nx <> 2018 OR x = 2018;\n\nt0.x = t1.x AND t0.y < t1.y AND t0.y <= t1.y;\nt0.x = t1.x AND t0.y < t1.y AND t0.y <= t1.y;\n\n1 < x;\nx > 1;\n\n1 <= x;\nx >= 1;\n\n1 > x;\nx < 1;\n\n1 >= x;\nx <= 1;\n\n1 = x;\nx = 1;\n\n1 <> x;\nx <> 1;\n\nNOT 1 < x;\nx <= 1;\n\nNOT 1 <= x;\nx < 1;\n\nNOT 1 > x;\nx >= 1;\n\nNOT 1 >= x;\nx > 1;\n\nNOT 1 = x;\nx <> 1;\n\nNOT 1 <> x;\nx = 1;\n\nx > CAST('2024-01-01' AS DATE) OR x > CAST('2023-12-31' AS DATE);\nx > CAST('2023-12-31' AS DATE);\n\nCAST(x AS DATE) > CAST('2024-01-01' AS DATE) OR CAST(x AS DATE) > CAST('2023-12-31' AS DATE);\nCAST(x AS DATE) > CAST('2023-12-31' AS DATE);\n\nFUN() > 0 OR FUN() > 1;\nFUN() > 0;\n\nRAND() > 0 OR RAND() > 1;\nRAND() > 0 OR RAND() > 1;\n\nCAST(1 AS UINT) >= 0;\nTRUE;\n\nCAST(-1 AS TINYINT) <= 0;\nTRUE;\n\nCAST(1 AS INT) = CAST(1 AS UINT);\nTRUE;\n\nCASE WHEN CAST(1 AS TINYINT) = 1 THEN FALSE ELSE TRUE END;\nFALSE;\n\nCAST(1 AS INT) + 1;\nCAST(1 AS INT) + 1;\n\nCAST(CAST(CAST(-1 AS INT) AS INT) AS INT) = -1;\nTRUE;\n\nCAST(-1 AS UINT) <= 0;\nCAST(-1 AS UINT) <= 0;\n\nCAST(-129 AS TINYINT) <= 0;\nCAST(-129 AS TINYINT) <= 0;\n\nCAST(256 AS UINT) >= 0;\nCAST(256 AS UINT) >= 0;\n\nCAST(CAST(CAST(-1 AS INT) AS UINT) AS INT) = 1;\nCAST(CAST(CAST(-1 AS INT) AS UINT) AS INT) = 1;\n\nCAST(x AS TINYINT) = 1;\nCAST(x AS TINYINT) = 1;\n\nCAST(CAST(1 AS INT) AS BOOLEAN) = 1;\nCAST(CAST(1 AS INT) AS BOOLEAN) = 1;\n\nCAST(CAST(CAST(1 AS INT) AS BOOLEAN) AS INT) = 1;\nCAST(CAST(CAST(1 AS INT) AS BOOLEAN) AS INT) = 1;\n\nx > CAST('2023-01-01' AS DATE) AND x < CAST('2023-01-01' AS DATETIME);\nFALSE;\n\n--------------------------------------\n-- COALESCE\n--------------------------------------\nCOALESCE(x);\nx;\n\nCOALESCE(x, 1) = 2;\nNOT x IS NULL AND x = 2;\n\n# dialect: redshift\nCOALESCE(x, 1) = 2;\nCOALESCE(x, 1) = 2;\n\n2 = COALESCE(x, 1);\nNOT x IS NULL AND x = 2;\n\nCOALESCE(x, 1, 1) = 1 + 1;\nNOT x IS NULL AND x = 2;\n\nCOALESCE(x, 1, 2) = 2;\nNOT x IS NULL AND x = 2;\n\nCOALESCE(x, 3) <= 2;\nNOT x IS NULL AND x <= 2;\n\nCOALESCE(x, 1) <> 2;\nx <> 2 OR x IS NULL;\n\nCOALESCE(x, 1) <= 2;\nx <= 2 OR x IS NULL;\n\nCOALESCE(x, 1) = 1;\nx = 1 OR x IS NULL;\n\nCOALESCE(x, 1) IS NULL;\nFALSE;\n\nCOALESCE(ROW() OVER (), 1) = 1;\nROW() OVER () = 1 OR ROW() OVER () IS NULL;\n\na AND b AND COALESCE(ROW() OVER (), 1) = 1;\n(ROW() OVER () = 1 OR ROW() OVER () IS NULL) AND a AND b;\n\nCOALESCE(1, 2);\n1;\n\nCOALESCE(CAST(CAST('2023-01-01' AS TIMESTAMP) AS DATE), x);\nCAST(CAST('2023-01-01' AS TIMESTAMP) AS DATE);\n\nCOALESCE(CAST(NULL AS DATE), x);\nCOALESCE(CAST(NULL AS DATE), x);\n\nNOT COALESCE(x, 1) = 2 AND y = 3;\n(x <> 2 OR x IS NULL) AND y = 3;\n\n--------------------------------------\n-- CONCAT\n--------------------------------------\nCONCAT(x, y);\nCONCAT(x, y);\n\nCONCAT_WS(sep, x, y);\nCONCAT_WS(sep, x, y);\n\nCONCAT(x);\nCONCAT(x);\n\nCONCAT('a', 'b', 'c');\n'abc';\n\nCONCAT('a', NULL);\nCONCAT('a', NULL);\n\nCONCAT_WS('-', 'a', 'b', 'c');\n'a-b-c';\n\nCONCAT('a', x, y, 'b', 'c');\nCONCAT('a', x, y, 'bc');\n\nCONCAT_WS('-', 'a', x, y, 'b', 'c');\nCONCAT_WS('-', 'a', x, y, 'b-c');\n\n'a' || 'b';\n'ab';\n\nCONCAT_WS('-', 'a');\n'a';\n\nCONCAT_WS('-', x, y);\nCONCAT_WS('-', x, y);\n\nCONCAT_WS('', x, y);\nCONCAT_WS('', x, y);\n\nCONCAT_WS('-', x);\nCONCAT_WS('-', x);\n\nCONCAT_WS(sep, 'a', 'b');\nCONCAT_WS(sep, 'a', 'b');\n\n'a' || 'b' || x;\n'ab' || x;\n\nCONCAT(a, b) IN (SELECT * FROM foo WHERE cond);\nCONCAT(a, b) IN (SELECT * FROM foo WHERE cond);\n\n--------------------------------------\n-- DATE_TRUNC\n--------------------------------------\nDATE_TRUNC('week', CAST('2023-12-15' AS DATE));\nCAST('2023-12-11' AS DATE);\n\nDATE_TRUNC('week', CAST('2023-12-16' AS DATE));\nCAST('2023-12-11' AS DATE);\n\n# dialect: bigquery\nDATE_TRUNC(CAST('2023-12-15' AS DATE), WEEK);\nCAST('2023-12-10' AS DATE);\n\n# dialect: bigquery\nDATE_TRUNC(CAST('2023-10-01' AS TIMESTAMP), QUARTER);\nCAST('2023-10-01 00:00:00' AS TIMESTAMP);\n\n# dialect: bigquery\nDATE_TRUNC(CAST('2023-12-16' AS DATE), WEEK);\nCAST('2023-12-10' AS DATE);\n\nDATE_TRUNC('year', x) = CAST('2021-01-01' AS DATE);\nx < CAST('2022-01-01' AS DATE) AND x >= CAST('2021-01-01' AS DATE);\n\n# dialect: bigquery\nDATE_TRUNC(x, year) = CAST('2021-01-01' AS TIMESTAMP);\nx < CAST('2022-01-01 00:00:00' AS TIMESTAMP) AND x >= CAST('2021-01-01 00:00:00' AS TIMESTAMP);\n\nDATE_TRUNC('quarter', x) = CAST('2021-01-01' AS DATE);\nx < CAST('2021-04-01' AS DATE) AND x >= CAST('2021-01-01' AS DATE);\n\n# dialect: bigquery\nDATE_TRUNC(x, quarter) = CAST('2021-01-01' AS TIMESTAMP);\nx < CAST('2021-04-01 00:00:00' AS TIMESTAMP) AND x >= CAST('2021-01-01 00:00:00' AS TIMESTAMP);\n\nDATE_TRUNC('month', x) = CAST('2021-01-01' AS DATE);\nx < CAST('2021-02-01' AS DATE) AND x >= CAST('2021-01-01' AS DATE);\n\n# dialect: bigquery\nDATE_TRUNC(x, month) = CAST('2021-01-01' AS TIMESTAMP);\nx < CAST('2021-02-01 00:00:00' AS TIMESTAMP) AND x >= CAST('2021-01-01 00:00:00' AS TIMESTAMP);\n\nDATE_TRUNC('week', x) = CAST('2021-01-04' AS DATE);\nx < CAST('2021-01-11' AS DATE) AND x >= CAST('2021-01-04' AS DATE);\n\nDATE_TRUNC('day', x) = CAST('2021-01-01' AS DATE);\nx < CAST('2021-01-02' AS DATE) AND x >= CAST('2021-01-01' AS DATE);\n\n# dialect: bigquery\nDATE_TRUNC(x, DAY) = CAST('2021-01-01' AS TIMESTAMP);\nx < CAST('2021-01-02 00:00:00' AS TIMESTAMP) AND x >= CAST('2021-01-01 00:00:00' AS TIMESTAMP);\n\nCAST('2021-01-01' AS DATE) = DATE_TRUNC('year', x);\nx < CAST('2022-01-01' AS DATE) AND x >= CAST('2021-01-01' AS DATE);\n\n# dialect: bigquery\nCAST('2021-01-01' AS TIMESTAMP) = DATE_TRUNC(x, year);\nx < CAST('2022-01-01 00:00:00' AS TIMESTAMP) AND x >= CAST('2021-01-01 00:00:00' AS TIMESTAMP);\n\n-- Always false, except for nulls\nDATE_TRUNC('quarter', x) = CAST('2021-01-02' AS DATE);\nDATE_TRUNC('QUARTER', x) = CAST('2021-01-02' AS DATE);\n\nDATE_TRUNC('year', x) <> CAST('2021-01-01' AS DATE);\nFALSE;\n\n-- Always true, except for nulls\nDATE_TRUNC('year', x) <> CAST('2021-01-02' AS DATE);\nDATE_TRUNC('YEAR', x) <> CAST('2021-01-02' AS DATE);\n\nDATE_TRUNC('year', x) <= CAST('2021-01-01' AS DATE);\nx < CAST('2022-01-01' AS DATE);\n\n# dialect: bigquery\nDATE_TRUNC(x, year) <= CAST('2021-01-01' AS TIMESTAMP);\nx < CAST('2022-01-01 00:00:00' AS TIMESTAMP);\n\nDATE_TRUNC('year', x) <= CAST('2021-01-02' AS DATE);\nx < CAST('2022-01-01' AS DATE);\n\nCAST('2021-01-01' AS DATE) >= DATE_TRUNC('year', x);\nx < CAST('2022-01-01' AS DATE);\n\n# dialect: bigquery\nCAST('2021-01-01' AS TIMESTAMP) >= DATE_TRUNC(x, year);\nx < CAST('2022-01-01 00:00:00' AS TIMESTAMP);\n\nDATE_TRUNC('year', x) < CAST('2021-01-01' AS DATE);\nx < CAST('2021-01-01' AS DATE);\n\nDATE_TRUNC('year', x) < CAST('2021-01-02' AS DATE);\nx < CAST('2022-01-01' AS DATE);\n\nDATE_TRUNC('year', x) >= CAST('2021-01-01' AS DATE);\nx >= CAST('2021-01-01' AS DATE);\n\nDATE_TRUNC('year', x) >= CAST('2021-01-02' AS DATE);\nx >= CAST('2022-01-01' AS DATE);\n\nDATE_TRUNC('year', x) > CAST('2021-01-01' AS DATE);\nx >= CAST('2022-01-01' AS DATE);\n\nDATE_TRUNC('year', x) > CAST('2021-01-02' AS DATE);\nx >= CAST('2022-01-01' AS DATE);\n\nDATE_TRUNC('year', x) > TS_OR_DS_TO_DATE(TS_OR_DS_TO_DATE('2021-01-02'));\nx >= CAST('2022-01-01' AS DATE);\n\nDATE_TRUNC('year', x) > TS_OR_DS_TO_DATE(TS_OR_DS_TO_DATE('2021-01-02', '%Y'));\nDATE_TRUNC('YEAR', x) > CAST(STR_TO_TIME('2021-01-02', '%Y') AS DATE);\n\n-- right is not a date\nDATE_TRUNC('year', x) <> '2021-01-02';\nDATE_TRUNC('YEAR', x) <> '2021-01-02';\n\nDATE_TRUNC('year', x) IN (CAST('2021-01-01' AS DATE), CAST('2023-01-01' AS DATE));\n(x < CAST('2022-01-01' AS DATE) AND x >= CAST('2021-01-01' AS DATE)) OR (x < CAST('2024-01-01' AS DATE) AND x >= CAST('2023-01-01' AS DATE));\n\n# dialect: bigquery\nDATE_TRUNC(x, year) IN (CAST('2021-01-01' AS TIMESTAMP), CAST('2023-01-01' AS TIMESTAMP));\n(x < CAST('2022-01-01 00:00:00' AS TIMESTAMP) AND x >= CAST('2021-01-01 00:00:00' AS TIMESTAMP)) OR (x < CAST('2024-01-01 00:00:00' AS TIMESTAMP) AND x >= CAST('2023-01-01 00:00:00' AS TIMESTAMP));\n\n-- merge ranges\nDATE_TRUNC('year', x) IN (CAST('2021-01-01' AS DATE), CAST('2022-01-01' AS DATE));\nx < CAST('2023-01-01' AS DATE) AND x >= CAST('2021-01-01' AS DATE);\n\n-- one of the values will always be false\nDATE_TRUNC('year', x) IN (CAST('2021-01-01' AS DATE), CAST('2022-01-02' AS DATE));\nx < CAST('2022-01-01' AS DATE) AND x >= CAST('2021-01-01' AS DATE);\n\nTIMESTAMP_TRUNC(x, YEAR) = CAST('2021-01-01' AS DATETIME);\nx < CAST('2022-01-01 00:00:00' AS DATETIME) AND x >= CAST('2021-01-01 00:00:00' AS DATETIME);\n\n-- right side is not a date literal\nDATE_TRUNC('day', x) = CAST(y AS DATE);\nCAST(y AS DATE) = DATE_TRUNC('DAY', x);\n\n-- nested cast\nDATE_TRUNC('day', x) = CAST(CAST('2021-01-01 01:02:03' AS DATETIME) AS DATE);\nx < CAST('2021-01-02' AS DATE) AND x >= CAST('2021-01-01' AS DATE);\n\nTIMESTAMP_TRUNC(x, YEAR) = CAST(CAST('2021-01-01 01:02:03' AS DATE) AS DATETIME);\nx < CAST('2022-01-01 00:00:00' AS DATETIME) AND x >= CAST('2021-01-01 00:00:00' AS DATETIME);\n\nDATE_TRUNC('day', CAST(x AS DATE)) <= CAST('2021-01-01 01:02:03' AS TIMESTAMP);\nCAST(x AS DATE) < CAST('2021-01-02 01:02:03' AS TIMESTAMP);\n\n--------------------------------------\n-- EQUALITY\n--------------------------------------\nx + 1 = 3;\nx = 2;\n\n1 + x = 3;\nx = 2;\n\n3 = x + 1;\nx = 2;\n\nx - 1 = 3;\nx = 4;\n\nx + 1 > 3;\nx > 2;\n\nx + 1 >= 3;\nx >= 2;\n\nx + 1 <= 3;\nx <= 2;\n\nx + 1 <= 3;\nx <= 2;\n\nx + 1 <> 3;\nx <> 2;\n\n1 + x + 1 = 3 + 1;\nx = 2;\n\nx - INTERVAL 1 DAY = CAST('2021-01-01' AS DATE);\nx = CAST('2021-01-02' AS DATE);\n\nx - INTERVAL 1 DAY = TS_OR_DS_TO_DATE('2021-01-01 00:00:01');\nx = CAST('2021-01-02' AS DATE);\n\nx - INTERVAL 1 HOUR > CAST('2021-01-01' AS DATETIME);\nx > CAST('2021-01-01 01:00:00' AS DATETIME);\n\nDATETIME_ADD(x, 1, HOUR) < CAST('2021-01-01' AS DATETIME);\nx < CAST('2020-12-31 23:00:00' AS DATETIME);\n\nDATETIME_SUB(x, 1, DAY) >= CAST('2021-01-01' AS DATETIME);\nx >= CAST('2021-01-02 00:00:00' AS DATETIME);\n\nDATE_ADD(x, 1, DAY) <= CAST('2021-01-01' AS DATE);\nx <= CAST('2020-12-31' AS DATE);\n\nDATE_SUB(x, 1, DAY) <> CAST('2021-01-01' AS DATE);\nx <> CAST('2021-01-02' AS DATE);\n\nDATE_ADD(DATE_ADD(DATE_TRUNC('week', DATE_SUB(x, 1, DAY)), 1, DAY), 1, YEAR) < CAST('2021-01-08' AS DATE);\nx < CAST('2020-01-14' AS DATE);\n\nx - INTERVAL '1' day = CAST(y AS DATE);\nCAST(y AS DATE) = x - INTERVAL '1' DAY;\n\n--------------------------------------\n-- Constant Propagation\n--------------------------------------\nx = 5 AND y = x;\nx = 5 AND y = 5;\n\n5 = x AND y = x;\nx = 5 AND y = 5;\n\nx = 5 OR y = x;\nx = 5 OR x = y;\n\n(x = 5 AND y = x) OR y = 1;\n(x = 5 AND y = 5) OR y = 1;\n\nt.x = 5 AND y = x;\nt.x = 5 AND x = y;\n\nt.x = 'a' AND y = CONCAT_WS('-', t.x, 'b');\nt.x = 'a' AND y = 'a-b';\n\nx = 5 AND y = x AND y + 1 < 5;\nFALSE;\n\nx = 5 AND x = 6;\nFALSE;\n\nx = 5 AND (y = x OR z = 1);\nx = 5 AND (x = y OR z = 1);\n\nx = 5 AND x + 3 = 8;\nx = 5;\n\nx = 5 AND (SELECT x FROM t WHERE y = 1);\n(SELECT x FROM t WHERE y = 1) AND x = 5;\n\nx = 1 AND y > 0 AND (SELECT z = 5 FROM t WHERE y = 1);\n(SELECT z = 5 FROM t WHERE y = 1) AND x = 1 AND y > 0;\n\nx = 1 AND x = y AND (SELECT z FROM t WHERE a AND (b OR c));\n(SELECT z FROM t WHERE a AND (b OR c)) AND x = 1 AND y = 1;\n\nt1.a = 39 AND t2.b = t1.a AND t3.c = t2.b;\nt1.a = 39 AND t2.b = 39 AND t3.c = 39;\n\nx = 1 AND CASE WHEN x = 5 THEN FALSE ELSE TRUE END;\nx = 1;\n\nx = 1 AND IF(x = 5, FALSE, TRUE);\nx = 1;\n\nx = 1 AND CASE x WHEN 5 THEN FALSE ELSE TRUE END;\nx = 1;\n\nx = y AND CASE WHEN x = 5 THEN FALSE ELSE TRUE END;\nCASE WHEN x = 5 THEN FALSE ELSE TRUE END AND x = y;\n\nx = 1 AND CASE WHEN y = 5 THEN x = z END;\nCASE WHEN y = 5 THEN z = 1 END AND x = 1;\n\n--------------------------------------\n-- Simplify Conditionals\n--------------------------------------\nIF(TRUE, x, y);\nx;\n\nIF(FALSE, x, y);\ny;\n\nIF(FALSE, x);\nNULL;\n\nIF(NULL, x, y);\ny;\n\nIF(cond, x, y);\nCASE WHEN cond THEN x ELSE y END;\n\nCASE WHEN TRUE THEN x ELSE y END;\nx;\n\nCASE WHEN FALSE THEN x ELSE y END;\ny;\n\nCASE WHEN FALSE THEN x WHEN FALSE THEN y WHEN TRUE THEN z END;\nz;\n\nCASE NULL WHEN NULL THEN x ELSE y END;\ny;\n\nCASE 4 WHEN 1 THEN x WHEN 2 THEN y WHEN 3 THEN z ELSE w END;\nw;\n\nCASE 4 WHEN 1 THEN x WHEN 2 THEN y WHEN 3 THEN z WHEN 4 THEN w END;\nw;\n\nCASE WHEN value = 1 THEN x ELSE y END;\nCASE WHEN value = 1 THEN x ELSE y END;\n\nCASE WHEN FALSE THEN x END;\nNULL;\n\nCASE 1 WHEN 1 + 1 THEN x END;\nNULL;\n\nCASE WHEN cond THEN x ELSE y END;\nCASE WHEN cond THEN x ELSE y END;\n\nCASE WHEN cond THEN x END;\nCASE WHEN cond THEN x END;\n\nCASE x WHEN y THEN z ELSE w END;\nCASE WHEN x = y THEN z ELSE w END;\n\nCASE x WHEN y THEN z END;\nCASE WHEN x = y THEN z END;\n\nCASE x1 + x2 WHEN x3 THEN x4 WHEN x5 + x6 THEN x7 ELSE x8 END;\nCASE WHEN x3 = (x1 + x2) THEN x4 WHEN (x1 + x2) = (x5 + x6) THEN x7 ELSE x8 END;\n\n--------------------------------------\n-- Simplify STARTSWITH\n--------------------------------------\nSTARTS_WITH('foo', 'f');\nTRUE;\n\nSTARTS_WITH('foo', 'g');\nFALSE;\n\nSTARTS_WITH('', 'f');\nFALSE;\n\nSTARTS_WITH('', '');\nTRUE;\n\nSTARTS_WITH('foo', '');\nTRUE;\n\nSTARTS_WITH(NULL, y);\nSTARTS_WITH(NULL, y);\n\nSTARTS_WITH(x, y);\nSTARTS_WITH(x, y);\n\nSTARTS_WITH('x', y);\nSTARTS_WITH('x', y);\n\nSTARTS_WITH(x, 'y');\nSTARTS_WITH(x, 'y');\n\n--------------------------------------\n-- Simplify NOT\n--------------------------------------\nSELECT NOT(NOT(a)) FROM x;\nSELECT NOT NOT a FROM x;\n\nSELECT NOT(NOT(NOT(NOT t_bool.a))) FROM t_bool;\nSELECT t_bool.a FROM t_bool;\n\n# dialect: mysql\nSELECT NOT(NOT(NOT(NOT t_bool.a))) FROM t_bool;\nSELECT NOT NOT NOT NOT t_bool.a FROM t_bool;\n\n# dialect: sqlite\nSELECT NOT(NOT(NOT(NOT t_bool.a))) FROM t_bool;\nSELECT NOT NOT NOT NOT t_bool.a FROM t_bool;\n\n# dialect: mysql\nWITH t0 AS (SELECT 1 AS a, 'foo' AS p) SELECT NOT(NOT(CASE WHEN t0.a > 1 THEN t0.a ELSE t0.p END)) AS res FROM t0;\nWITH t0 AS (SELECT 1 AS a, 'foo' AS p) SELECT NOT NOT CASE WHEN t0.a > 1 THEN t0.a ELSE t0.p END AS res FROM t0;\n\n# dialect: sqlite\nWITH t0 AS (SELECT 1 AS a, 'foo' AS p) SELECT NOT (NOT(CASE WHEN t0.a > 1 THEN t0.a ELSE t0.p END)) AS res FROM t0;\nWITH t0 AS (SELECT 1 AS a, 'foo' AS p) SELECT NOT NOT CASE WHEN t0.a > 1 THEN t0.a ELSE t0.p END AS res FROM t0;\n\n--------------------------------------\n-- Simplify complements\n--------------------------------------\nTRUE OR NOT TRUE;\nTRUE;\n\nTRUE AND NOT TRUE;\nFALSE;\n\n'a' OR NOT 'a';\nTRUE;\n\n'a' AND NOT 'a';\nFALSE;\n\n100 OR NOT 100;\nTRUE;\n\n100 AND NOT 100;\nFALSE;\n\nNULL OR NOT NULL;\nNULL AND TRUE;\n\nNULL AND NOT NULL;\nNULL AND TRUE;\n\nNULL OR (NULL AND TRUE);\nNULL AND TRUE;\n\nSELECT IF(NULL = NULL, 1, 100);\nSELECT 100;\n\n# dialect: snowflake\nSELECT * FROM o ASOF JOIN e MATCH_CONDITION (o.observed_date >= e.metric_date) ON o.id = e.id;\nSELECT * FROM o ASOF JOIN e MATCH_CONDITION (o.observed_date >= e.metric_date) ON e.id = o.id;\n\n"
  },
  {
    "path": "tests/fixtures/optimizer/tpc-ds/tpc-ds.sql",
    "content": "--------------------------------------\n-- TPC-DS 1\n--------------------------------------\n# execute: true\nWITH customer_total_return\n     AS (SELECT sr_customer_sk     AS ctr_customer_sk,\n                sr_store_sk        AS ctr_store_sk,\n                Sum(sr_return_amt) AS ctr_total_return\n         FROM   store_returns,\n                date_dim\n         WHERE  sr_returned_date_sk = d_date_sk\n                AND d_year = 2001\n         GROUP  BY sr_customer_sk,\n                   sr_store_sk)\nSELECT c_customer_id\nFROM   customer_total_return ctr1,\n       store,\n       customer\nWHERE  ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2\n                                FROM   customer_total_return ctr2\n                                WHERE  ctr1.ctr_store_sk = ctr2.ctr_store_sk)\n       AND s_store_sk = ctr1.ctr_store_sk\n       AND s_state = 'TN'\n       AND ctr1.ctr_customer_sk = c_customer_sk\nORDER  BY c_customer_id\nLIMIT 100;\nWITH \"customer_total_return\" AS (\n  SELECT\n    \"store_returns\".\"sr_customer_sk\" AS \"ctr_customer_sk\",\n    \"store_returns\".\"sr_store_sk\" AS \"ctr_store_sk\",\n    SUM(\"store_returns\".\"sr_return_amt\") AS \"ctr_total_return\"\n  FROM \"store_returns\" AS \"store_returns\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_returns\".\"sr_returned_date_sk\"\n    AND \"date_dim\".\"d_year\" = 2001\n  GROUP BY\n    \"store_returns\".\"sr_customer_sk\",\n    \"store_returns\".\"sr_store_sk\"\n), \"_u_0\" AS (\n  SELECT\n    AVG(\"ctr2\".\"ctr_total_return\") * 1.2 AS \"_col_0\",\n    \"ctr2\".\"ctr_store_sk\" AS \"_u_1\"\n  FROM \"customer_total_return\" AS \"ctr2\"\n  GROUP BY\n    \"ctr2\".\"ctr_store_sk\"\n)\nSELECT\n  \"customer\".\"c_customer_id\" AS \"c_customer_id\"\nFROM \"customer_total_return\" AS \"ctr1\"\nJOIN \"store\" AS \"store\"\n  ON \"ctr1\".\"ctr_store_sk\" = \"store\".\"s_store_sk\" AND \"store\".\"s_state\" = 'TN'\nJOIN \"customer\" AS \"customer\"\n  ON \"ctr1\".\"ctr_customer_sk\" = \"customer\".\"c_customer_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"ctr1\".\"ctr_store_sk\"\nWHERE\n  \"_u_0\".\"_col_0\" < \"ctr1\".\"ctr_total_return\"\nORDER BY\n  \"c_customer_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 2\n--------------------------------------\n# execute: true\nWITH wscs\n     AS (SELECT sold_date_sk,\n                sales_price\n         FROM   (SELECT ws_sold_date_sk    sold_date_sk,\n                        ws_ext_sales_price sales_price\n                 FROM   web_sales)\n         UNION ALL\n         (SELECT cs_sold_date_sk    sold_date_sk,\n                 cs_ext_sales_price sales_price\n          FROM   catalog_sales)),\n     wswscs\n     AS (SELECT d_week_seq,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Sunday' ) THEN sales_price\n                      ELSE NULL\n                    END) sun_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Monday' ) THEN sales_price\n                      ELSE NULL\n                    END) mon_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Tuesday' ) THEN sales_price\n                      ELSE NULL\n                    END) tue_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Wednesday' ) THEN sales_price\n                      ELSE NULL\n                    END) wed_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Thursday' ) THEN sales_price\n                      ELSE NULL\n                    END) thu_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Friday' ) THEN sales_price\n                      ELSE NULL\n                    END) fri_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Saturday' ) THEN sales_price\n                      ELSE NULL\n                    END) sat_sales\n         FROM   wscs,\n                date_dim\n         WHERE  d_date_sk = sold_date_sk\n         GROUP  BY d_week_seq)\nSELECT d_week_seq1,\n       Round(sun_sales1 / sun_sales2, 2) AS \"_col_1\",\n       Round(mon_sales1 / mon_sales2, 2) AS \"_col_2\",\n       Round(tue_sales1 / tue_sales2, 2) AS \"_col_3\",\n       Round(wed_sales1 / wed_sales2, 2) AS \"_col_4\",\n       Round(thu_sales1 / thu_sales2, 2) AS \"_col_5\",\n       Round(fri_sales1 / fri_sales2, 2) AS \"_col_6\",\n       Round(sat_sales1 / sat_sales2, 2) AS \"_col_7\"\nFROM   (SELECT wswscs.d_week_seq d_week_seq1,\n               sun_sales         sun_sales1,\n               mon_sales         mon_sales1,\n               tue_sales         tue_sales1,\n               wed_sales         wed_sales1,\n               thu_sales         thu_sales1,\n               fri_sales         fri_sales1,\n               sat_sales         sat_sales1\n        FROM   wswscs,\n               date_dim\n        WHERE  date_dim.d_week_seq = wswscs.d_week_seq\n               AND d_year = 1998) y,\n       (SELECT wswscs.d_week_seq d_week_seq2,\n               sun_sales         sun_sales2,\n               mon_sales         mon_sales2,\n               tue_sales         tue_sales2,\n               wed_sales         wed_sales2,\n               thu_sales         thu_sales2,\n               fri_sales         fri_sales2,\n               sat_sales         sat_sales2\n        FROM   wswscs,\n               date_dim\n        WHERE  date_dim.d_week_seq = wswscs.d_week_seq\n               AND d_year = 1998 + 1) z\nWHERE  d_week_seq1 = d_week_seq2 - 53\nORDER  BY d_week_seq1;\nWITH \"wscs\" AS (\n  SELECT\n    \"web_sales\".\"ws_sold_date_sk\" AS \"sold_date_sk\",\n    \"web_sales\".\"ws_ext_sales_price\" AS \"sales_price\"\n  FROM \"web_sales\" AS \"web_sales\"\n  UNION ALL\n  (\n    SELECT\n      \"catalog_sales\".\"cs_sold_date_sk\" AS \"sold_date_sk\",\n      \"catalog_sales\".\"cs_ext_sales_price\" AS \"sales_price\"\n    FROM \"catalog_sales\" AS \"catalog_sales\"\n  )\n), \"wswscs\" AS (\n  SELECT\n    \"date_dim\".\"d_week_seq\" AS \"d_week_seq\",\n    SUM(\n      CASE WHEN \"date_dim\".\"d_day_name\" = 'Sunday' THEN \"wscs\".\"sales_price\" ELSE NULL END\n    ) AS \"sun_sales\",\n    SUM(\n      CASE WHEN \"date_dim\".\"d_day_name\" = 'Monday' THEN \"wscs\".\"sales_price\" ELSE NULL END\n    ) AS \"mon_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Tuesday'\n        THEN \"wscs\".\"sales_price\"\n        ELSE NULL\n      END\n    ) AS \"tue_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Wednesday'\n        THEN \"wscs\".\"sales_price\"\n        ELSE NULL\n      END\n    ) AS \"wed_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Thursday'\n        THEN \"wscs\".\"sales_price\"\n        ELSE NULL\n      END\n    ) AS \"thu_sales\",\n    SUM(\n      CASE WHEN \"date_dim\".\"d_day_name\" = 'Friday' THEN \"wscs\".\"sales_price\" ELSE NULL END\n    ) AS \"fri_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Saturday'\n        THEN \"wscs\".\"sales_price\"\n        ELSE NULL\n      END\n    ) AS \"sat_sales\"\n  FROM \"wscs\" AS \"wscs\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"wscs\".\"sold_date_sk\"\n  GROUP BY\n    \"date_dim\".\"d_week_seq\"\n), \"z\" AS (\n  SELECT\n    \"wswscs\".\"d_week_seq\" AS \"d_week_seq2\",\n    \"wswscs\".\"sun_sales\" AS \"sun_sales2\",\n    \"wswscs\".\"mon_sales\" AS \"mon_sales2\",\n    \"wswscs\".\"tue_sales\" AS \"tue_sales2\",\n    \"wswscs\".\"wed_sales\" AS \"wed_sales2\",\n    \"wswscs\".\"thu_sales\" AS \"thu_sales2\",\n    \"wswscs\".\"fri_sales\" AS \"fri_sales2\",\n    \"wswscs\".\"sat_sales\" AS \"sat_sales2\"\n  FROM \"wswscs\" AS \"wswscs\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_week_seq\" = \"wswscs\".\"d_week_seq\" AND \"date_dim\".\"d_year\" = 1999\n)\nSELECT\n  \"wswscs\".\"d_week_seq\" AS \"d_week_seq1\",\n  ROUND(\"wswscs\".\"sun_sales\" / \"z\".\"sun_sales2\", 2) AS \"_col_1\",\n  ROUND(\"wswscs\".\"mon_sales\" / \"z\".\"mon_sales2\", 2) AS \"_col_2\",\n  ROUND(\"wswscs\".\"tue_sales\" / \"z\".\"tue_sales2\", 2) AS \"_col_3\",\n  ROUND(\"wswscs\".\"wed_sales\" / \"z\".\"wed_sales2\", 2) AS \"_col_4\",\n  ROUND(\"wswscs\".\"thu_sales\" / \"z\".\"thu_sales2\", 2) AS \"_col_5\",\n  ROUND(\"wswscs\".\"fri_sales\" / \"z\".\"fri_sales2\", 2) AS \"_col_6\",\n  ROUND(\"wswscs\".\"sat_sales\" / \"z\".\"sat_sales2\", 2) AS \"_col_7\"\nFROM \"wswscs\" AS \"wswscs\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_week_seq\" = \"wswscs\".\"d_week_seq\" AND \"date_dim\".\"d_year\" = 1998\nJOIN \"z\" AS \"z\"\n  ON \"wswscs\".\"d_week_seq\" = \"z\".\"d_week_seq2\" - 53\nORDER BY\n  \"d_week_seq1\";\n\n--------------------------------------\n-- TPC-DS 3\n--------------------------------------\n# execute: true\nSELECT dt.d_year,\n               item.i_brand_id          brand_id,\n               item.i_brand             brand,\n               Sum(ss_ext_discount_amt) sum_agg\nFROM   date_dim dt,\n       store_sales,\n       item\nWHERE  dt.d_date_sk = store_sales.ss_sold_date_sk\n       AND store_sales.ss_item_sk = item.i_item_sk\n       AND item.i_manufact_id = 427\n       AND dt.d_moy = 11\nGROUP  BY dt.d_year,\n          item.i_brand,\n          item.i_brand_id\nORDER  BY dt.d_year,\n          sum_agg DESC,\n          brand_id\nLIMIT 100;\nSELECT\n  \"dt\".\"d_year\" AS \"d_year\",\n  \"item\".\"i_brand_id\" AS \"brand_id\",\n  \"item\".\"i_brand\" AS \"brand\",\n  SUM(\"store_sales\".\"ss_ext_discount_amt\") AS \"sum_agg\"\nFROM \"date_dim\" AS \"dt\"\nJOIN \"store_sales\" AS \"store_sales\"\n  ON \"dt\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\" AND \"item\".\"i_manufact_id\" = 427\nWHERE\n  \"dt\".\"d_moy\" = 11\nGROUP BY\n  \"dt\".\"d_year\",\n  \"item\".\"i_brand\",\n  \"item\".\"i_brand_id\"\nORDER BY\n  \"d_year\",\n  \"sum_agg\" DESC,\n  \"brand_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 4\n--------------------------------------\n# execute: true\nWITH year_total\n     AS (SELECT c_customer_id                       customer_id,\n                c_first_name                        customer_first_name,\n                c_last_name                         customer_last_name,\n                c_preferred_cust_flag               customer_preferred_cust_flag\n                ,\n                c_birth_country\n                customer_birth_country,\n                c_login                             customer_login,\n                c_email_address                     customer_email_address,\n                d_year                              dyear,\n                Sum(( ( ss_ext_list_price - ss_ext_wholesale_cost\n                        - ss_ext_discount_amt\n                      )\n                      +\n                          ss_ext_sales_price ) / 2) year_total,\n                's'                                 sale_type\n         FROM   customer,\n                store_sales,\n                date_dim\n         WHERE  c_customer_sk = ss_customer_sk\n                AND ss_sold_date_sk = d_date_sk\n         GROUP  BY c_customer_id,\n                   c_first_name,\n                   c_last_name,\n                   c_preferred_cust_flag,\n                   c_birth_country,\n                   c_login,\n                   c_email_address,\n                   d_year\n         UNION ALL\n         SELECT c_customer_id                             customer_id,\n                c_first_name                              customer_first_name,\n                c_last_name                               customer_last_name,\n                c_preferred_cust_flag\n                customer_preferred_cust_flag,\n                c_birth_country                           customer_birth_country\n                ,\n                c_login\n                customer_login,\n                c_email_address                           customer_email_address\n                ,\n                d_year                                    dyear\n                ,\n                Sum(( ( ( cs_ext_list_price\n                          - cs_ext_wholesale_cost\n                          - cs_ext_discount_amt\n                        ) +\n                              cs_ext_sales_price ) / 2 )) year_total,\n                'c'                                       sale_type\n         FROM   customer,\n                catalog_sales,\n                date_dim\n         WHERE  c_customer_sk = cs_bill_customer_sk\n                AND cs_sold_date_sk = d_date_sk\n         GROUP  BY c_customer_id,\n                   c_first_name,\n                   c_last_name,\n                   c_preferred_cust_flag,\n                   c_birth_country,\n                   c_login,\n                   c_email_address,\n                   d_year\n         UNION ALL\n         SELECT c_customer_id                             customer_id,\n                c_first_name                              customer_first_name,\n                c_last_name                               customer_last_name,\n                c_preferred_cust_flag\n                customer_preferred_cust_flag,\n                c_birth_country                           customer_birth_country\n                ,\n                c_login\n                customer_login,\n                c_email_address                           customer_email_address\n                ,\n                d_year                                    dyear\n                ,\n                Sum(( ( ( ws_ext_list_price\n                          - ws_ext_wholesale_cost\n                          - ws_ext_discount_amt\n                        ) +\n                              ws_ext_sales_price ) / 2 )) year_total,\n                'w'                                       sale_type\n         FROM   customer,\n                web_sales,\n                date_dim\n         WHERE  c_customer_sk = ws_bill_customer_sk\n                AND ws_sold_date_sk = d_date_sk\n         GROUP  BY c_customer_id,\n                   c_first_name,\n                   c_last_name,\n                   c_preferred_cust_flag,\n                   c_birth_country,\n                   c_login,\n                   c_email_address,\n                   d_year)\nSELECT t_s_secyear.customer_id,\n               t_s_secyear.customer_first_name,\n               t_s_secyear.customer_last_name,\n               t_s_secyear.customer_preferred_cust_flag\nFROM   year_total t_s_firstyear,\n       year_total t_s_secyear,\n       year_total t_c_firstyear,\n       year_total t_c_secyear,\n       year_total t_w_firstyear,\n       year_total t_w_secyear\nWHERE  t_s_secyear.customer_id = t_s_firstyear.customer_id\n       AND t_s_firstyear.customer_id = t_c_secyear.customer_id\n       AND t_s_firstyear.customer_id = t_c_firstyear.customer_id\n       AND t_s_firstyear.customer_id = t_w_firstyear.customer_id\n       AND t_s_firstyear.customer_id = t_w_secyear.customer_id\n       AND t_s_firstyear.sale_type = 's'\n       AND t_c_firstyear.sale_type = 'c'\n       AND t_w_firstyear.sale_type = 'w'\n       AND t_s_secyear.sale_type = 's'\n       AND t_c_secyear.sale_type = 'c'\n       AND t_w_secyear.sale_type = 'w'\n       AND t_s_firstyear.dyear = 2001\n       AND t_s_secyear.dyear = 2001 + 1\n       AND t_c_firstyear.dyear = 2001\n       AND t_c_secyear.dyear = 2001 + 1\n       AND t_w_firstyear.dyear = 2001\n       AND t_w_secyear.dyear = 2001 + 1\n       AND t_s_firstyear.year_total > 0\n       AND t_c_firstyear.year_total > 0\n       AND t_w_firstyear.year_total > 0\n       AND CASE\n             WHEN t_c_firstyear.year_total > 0 THEN t_c_secyear.year_total /\n                                                    t_c_firstyear.year_total\n             ELSE NULL\n           END > CASE\n                   WHEN t_s_firstyear.year_total > 0 THEN\n                   t_s_secyear.year_total /\n                   t_s_firstyear.year_total\n                   ELSE NULL\n                 END\n       AND CASE\n             WHEN t_c_firstyear.year_total > 0 THEN t_c_secyear.year_total /\n                                                    t_c_firstyear.year_total\n             ELSE NULL\n           END > CASE\n                   WHEN t_w_firstyear.year_total > 0 THEN\n                   t_w_secyear.year_total /\n                   t_w_firstyear.year_total\n                   ELSE NULL\n                 END\nORDER  BY t_s_secyear.customer_id,\n          t_s_secyear.customer_first_name,\n          t_s_secyear.customer_last_name,\n          t_s_secyear.customer_preferred_cust_flag\nLIMIT 100;\nWITH \"customer_2\" AS (\n  SELECT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\",\n    \"customer\".\"c_customer_id\" AS \"c_customer_id\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"customer\".\"c_last_name\" AS \"c_last_name\",\n    \"customer\".\"c_preferred_cust_flag\" AS \"c_preferred_cust_flag\",\n    \"customer\".\"c_birth_country\" AS \"c_birth_country\",\n    \"customer\".\"c_login\" AS \"c_login\",\n    \"customer\".\"c_email_address\" AS \"c_email_address\"\n  FROM \"customer\" AS \"customer\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n), \"year_total\" AS (\n  SELECT\n    \"customer\".\"c_customer_id\" AS \"customer_id\",\n    \"customer\".\"c_first_name\" AS \"customer_first_name\",\n    \"customer\".\"c_last_name\" AS \"customer_last_name\",\n    \"customer\".\"c_preferred_cust_flag\" AS \"customer_preferred_cust_flag\",\n    \"date_dim\".\"d_year\" AS \"dyear\",\n    SUM(\n      (\n        (\n          \"store_sales\".\"ss_ext_list_price\" - \"store_sales\".\"ss_ext_wholesale_cost\" - \"store_sales\".\"ss_ext_discount_amt\"\n        ) + \"store_sales\".\"ss_ext_sales_price\"\n      ) / 2\n    ) AS \"year_total\",\n    's' AS \"sale_type\"\n  FROM \"customer_2\" AS \"customer\"\n  JOIN \"store_sales\" AS \"store_sales\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"customer\".\"c_customer_id\",\n    \"customer\".\"c_first_name\",\n    \"customer\".\"c_last_name\",\n    \"customer\".\"c_preferred_cust_flag\",\n    \"customer\".\"c_birth_country\",\n    \"customer\".\"c_login\",\n    \"customer\".\"c_email_address\",\n    \"date_dim\".\"d_year\"\n  UNION ALL\n  SELECT\n    \"customer\".\"c_customer_id\" AS \"customer_id\",\n    \"customer\".\"c_first_name\" AS \"customer_first_name\",\n    \"customer\".\"c_last_name\" AS \"customer_last_name\",\n    \"customer\".\"c_preferred_cust_flag\" AS \"customer_preferred_cust_flag\",\n    \"date_dim\".\"d_year\" AS \"dyear\",\n    SUM(\n      (\n        (\n          (\n            \"catalog_sales\".\"cs_ext_list_price\" - \"catalog_sales\".\"cs_ext_wholesale_cost\" - \"catalog_sales\".\"cs_ext_discount_amt\"\n          ) + \"catalog_sales\".\"cs_ext_sales_price\"\n        ) / 2\n      )\n    ) AS \"year_total\",\n    'c' AS \"sale_type\"\n  FROM \"customer_2\" AS \"customer\"\n  JOIN \"catalog_sales\" AS \"catalog_sales\"\n    ON \"catalog_sales\".\"cs_bill_customer_sk\" = \"customer\".\"c_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  GROUP BY\n    \"customer\".\"c_customer_id\",\n    \"customer\".\"c_first_name\",\n    \"customer\".\"c_last_name\",\n    \"customer\".\"c_preferred_cust_flag\",\n    \"customer\".\"c_birth_country\",\n    \"customer\".\"c_login\",\n    \"customer\".\"c_email_address\",\n    \"date_dim\".\"d_year\"\n  UNION ALL\n  SELECT\n    \"customer\".\"c_customer_id\" AS \"customer_id\",\n    \"customer\".\"c_first_name\" AS \"customer_first_name\",\n    \"customer\".\"c_last_name\" AS \"customer_last_name\",\n    \"customer\".\"c_preferred_cust_flag\" AS \"customer_preferred_cust_flag\",\n    \"date_dim\".\"d_year\" AS \"dyear\",\n    SUM(\n      (\n        (\n          (\n            \"web_sales\".\"ws_ext_list_price\" - \"web_sales\".\"ws_ext_wholesale_cost\" - \"web_sales\".\"ws_ext_discount_amt\"\n          ) + \"web_sales\".\"ws_ext_sales_price\"\n        ) / 2\n      )\n    ) AS \"year_total\",\n    'w' AS \"sale_type\"\n  FROM \"customer_2\" AS \"customer\"\n  JOIN \"web_sales\" AS \"web_sales\"\n    ON \"customer\".\"c_customer_sk\" = \"web_sales\".\"ws_bill_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  GROUP BY\n    \"customer\".\"c_customer_id\",\n    \"customer\".\"c_first_name\",\n    \"customer\".\"c_last_name\",\n    \"customer\".\"c_preferred_cust_flag\",\n    \"customer\".\"c_birth_country\",\n    \"customer\".\"c_login\",\n    \"customer\".\"c_email_address\",\n    \"date_dim\".\"d_year\"\n)\nSELECT\n  \"t_s_secyear\".\"customer_id\" AS \"customer_id\",\n  \"t_s_secyear\".\"customer_first_name\" AS \"customer_first_name\",\n  \"t_s_secyear\".\"customer_last_name\" AS \"customer_last_name\",\n  \"t_s_secyear\".\"customer_preferred_cust_flag\" AS \"customer_preferred_cust_flag\"\nFROM \"year_total\" AS \"t_s_firstyear\"\nJOIN \"year_total\" AS \"t_c_firstyear\"\n  ON \"t_c_firstyear\".\"customer_id\" = \"t_s_firstyear\".\"customer_id\"\n  AND \"t_c_firstyear\".\"dyear\" = 2001\n  AND \"t_c_firstyear\".\"sale_type\" = 'c'\n  AND \"t_c_firstyear\".\"year_total\" > 0\nJOIN \"year_total\" AS \"t_s_secyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_s_secyear\".\"customer_id\"\n  AND \"t_s_secyear\".\"dyear\" = 2002\n  AND \"t_s_secyear\".\"sale_type\" = 's'\nJOIN \"year_total\" AS \"t_w_firstyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_w_firstyear\".\"customer_id\"\n  AND \"t_w_firstyear\".\"dyear\" = 2001\n  AND \"t_w_firstyear\".\"sale_type\" = 'w'\n  AND \"t_w_firstyear\".\"year_total\" > 0\nJOIN \"year_total\" AS \"t_c_secyear\"\n  ON \"t_c_secyear\".\"customer_id\" = \"t_s_firstyear\".\"customer_id\"\n  AND \"t_c_secyear\".\"dyear\" = 2002\n  AND \"t_c_secyear\".\"sale_type\" = 'c'\n  AND CASE\n    WHEN \"t_c_firstyear\".\"year_total\" > 0\n    THEN \"t_c_secyear\".\"year_total\" / \"t_c_firstyear\".\"year_total\"\n    ELSE NULL\n  END > CASE\n    WHEN \"t_s_firstyear\".\"year_total\" > 0\n    THEN \"t_s_secyear\".\"year_total\" / \"t_s_firstyear\".\"year_total\"\n    ELSE NULL\n  END\nJOIN \"year_total\" AS \"t_w_secyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_w_secyear\".\"customer_id\"\n  AND \"t_w_secyear\".\"dyear\" = 2002\n  AND \"t_w_secyear\".\"sale_type\" = 'w'\n  AND CASE\n    WHEN \"t_c_firstyear\".\"year_total\" > 0\n    THEN \"t_c_secyear\".\"year_total\" / \"t_c_firstyear\".\"year_total\"\n    ELSE NULL\n  END > CASE\n    WHEN \"t_w_firstyear\".\"year_total\" > 0\n    THEN \"t_w_secyear\".\"year_total\" / \"t_w_firstyear\".\"year_total\"\n    ELSE NULL\n  END\nWHERE\n  \"t_s_firstyear\".\"dyear\" = 2001\n  AND \"t_s_firstyear\".\"sale_type\" = 's'\n  AND \"t_s_firstyear\".\"year_total\" > 0\nORDER BY\n  \"t_s_secyear\".\"customer_id\",\n  \"t_s_secyear\".\"customer_first_name\",\n  \"t_s_secyear\".\"customer_last_name\",\n  \"t_s_secyear\".\"customer_preferred_cust_flag\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 5\n--------------------------------------\nWITH ssr AS\n(\n         SELECT   s_store_id,\n                  Sum(sales_price) AS sales,\n                  Sum(profit)      AS profit,\n                  Sum(return_amt)  AS returns1,\n                  Sum(net_loss)    AS profit_loss\n         FROM     (\n                         SELECT ss_store_sk             AS store_sk,\n                                ss_sold_date_sk         AS date_sk,\n                                ss_ext_sales_price      AS sales_price,\n                                ss_net_profit           AS profit,\n                                Cast(0 AS DECIMAL(7,2)) AS return_amt,\n                                Cast(0 AS DECIMAL(7,2)) AS net_loss\n                         FROM   store_sales\n                         UNION ALL\n                         SELECT sr_store_sk             AS store_sk,\n                                sr_returned_date_sk     AS date_sk,\n                                Cast(0 AS DECIMAL(7,2)) AS sales_price,\n                                Cast(0 AS DECIMAL(7,2)) AS profit,\n                                sr_return_amt           AS return_amt,\n                                sr_net_loss             AS net_loss\n                         FROM   store_returns ) salesreturns,\n                  date_dim,\n                  store\n         WHERE    date_sk = d_date_sk\n         AND      d_date BETWEEN Cast('2002-08-22' AS DATE) AND      (\n                           Cast('2002-08-22' AS DATE) + INTERVAL '14' day)\n         AND      store_sk = s_store_sk\n         GROUP BY s_store_id) , csr AS\n(\n         SELECT   cp_catalog_page_id,\n                  sum(sales_price) AS sales,\n                  sum(profit)      AS profit,\n                  sum(return_amt)  AS returns1,\n                  sum(net_loss)    AS profit_loss\n         FROM     (\n                         SELECT cs_catalog_page_sk      AS page_sk,\n                                cs_sold_date_sk         AS date_sk,\n                                cs_ext_sales_price      AS sales_price,\n                                cs_net_profit           AS profit,\n                                cast(0 AS decimal(7,2)) AS return_amt,\n                                cast(0 AS decimal(7,2)) AS net_loss\n                         FROM   catalog_sales\n                         UNION ALL\n                         SELECT cr_catalog_page_sk      AS page_sk,\n                                cr_returned_date_sk     AS date_sk,\n                                cast(0 AS decimal(7,2)) AS sales_price,\n                                cast(0 AS decimal(7,2)) AS profit,\n                                cr_return_amount        AS return_amt,\n                                cr_net_loss             AS net_loss\n                         FROM   catalog_returns ) salesreturns,\n                  date_dim,\n                  catalog_page\n         WHERE    date_sk = d_date_sk\n         AND      d_date BETWEEN cast('2002-08-22' AS date) AND      (\n                           cast('2002-08-22' AS date) + INTERVAL '14' day)\n         AND      page_sk = cp_catalog_page_sk\n         GROUP BY cp_catalog_page_id) , wsr AS\n(\n         SELECT   web_site_id,\n                  sum(sales_price) AS sales,\n                  sum(profit)      AS profit,\n                  sum(return_amt)  AS returns1,\n                  sum(net_loss)    AS profit_loss\n         FROM     (\n                         SELECT ws_web_site_sk          AS wsr_web_site_sk,\n                                ws_sold_date_sk         AS date_sk,\n                                ws_ext_sales_price      AS sales_price,\n                                ws_net_profit           AS profit,\n                                cast(0 AS decimal(7,2)) AS return_amt,\n                                cast(0 AS decimal(7,2)) AS net_loss\n                         FROM   web_sales\n                         UNION ALL\n                         SELECT          ws_web_site_sk          AS wsr_web_site_sk,\n                                         wr_returned_date_sk     AS date_sk,\n                                         cast(0 AS decimal(7,2)) AS sales_price,\n                                         cast(0 AS decimal(7,2)) AS profit,\n                                         wr_return_amt           AS return_amt,\n                                         wr_net_loss             AS net_loss\n                         FROM            web_returns\n                         LEFT OUTER JOIN web_sales\n                         ON              (\n                                                         wr_item_sk = ws_item_sk\n                                         AND             wr_order_number = ws_order_number) ) salesreturns,\n                  date_dim,\n                  web_site\n         WHERE    date_sk = d_date_sk\n         AND      d_date BETWEEN cast('2002-08-22' AS date) AND      (\n                           cast('2002-08-22' AS date) + INTERVAL '14' day)\n         AND      wsr_web_site_sk = web_site_sk\n         GROUP BY web_site_id)\nSELECT\n         channel ,\n         id ,\n         sum(sales)   AS sales ,\n         sum(returns1) AS returns1 ,\n         sum(profit)  AS profit\nFROM     (\n                SELECT 'store channel' AS channel ,\n                       'store'\n                              || s_store_id AS id ,\n                       sales ,\n                       returns1 ,\n                       (profit - profit_loss) AS profit\n                FROM   ssr\n                UNION ALL\n                SELECT 'catalog channel' AS channel ,\n                       'catalog_page'\n                              || cp_catalog_page_id AS id ,\n                       sales ,\n                       returns1 ,\n                       (profit - profit_loss) AS profit\n                FROM   csr\n                UNION ALL\n                SELECT 'web channel' AS channel ,\n                       'web_site'\n                              || web_site_id AS id ,\n                       sales ,\n                       returns1 ,\n                       (profit - profit_loss) AS profit\n                FROM   wsr ) x\nGROUP BY rollup (channel, id)\nORDER BY channel ,\n         id\nLIMIT 100;\nWITH \"salesreturns\" AS (\n  SELECT\n    \"store_sales\".\"ss_store_sk\" AS \"store_sk\",\n    \"store_sales\".\"ss_sold_date_sk\" AS \"date_sk\",\n    \"store_sales\".\"ss_ext_sales_price\" AS \"sales_price\",\n    \"store_sales\".\"ss_net_profit\" AS \"profit\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"return_amt\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"net_loss\"\n  FROM \"store_sales\" AS \"store_sales\"\n  UNION ALL\n  SELECT\n    \"store_returns\".\"sr_store_sk\" AS \"store_sk\",\n    \"store_returns\".\"sr_returned_date_sk\" AS \"date_sk\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"sales_price\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"profit\",\n    \"store_returns\".\"sr_return_amt\" AS \"return_amt\",\n    \"store_returns\".\"sr_net_loss\" AS \"net_loss\"\n  FROM \"store_returns\" AS \"store_returns\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2002-09-05' AS DATE)\n    AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2002-08-22' AS DATE)\n), \"ssr\" AS (\n  SELECT\n    \"store\".\"s_store_id\" AS \"s_store_id\",\n    SUM(\"salesreturns\".\"sales_price\") AS \"sales\",\n    SUM(\"salesreturns\".\"profit\") AS \"profit\",\n    SUM(\"salesreturns\".\"return_amt\") AS \"returns1\",\n    SUM(\"salesreturns\".\"net_loss\") AS \"profit_loss\"\n  FROM \"salesreturns\" AS \"salesreturns\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"salesreturns\".\"date_sk\"\n  JOIN \"store\" AS \"store\"\n    ON \"salesreturns\".\"store_sk\" = \"store\".\"s_store_sk\"\n  GROUP BY\n    \"store\".\"s_store_id\"\n), \"salesreturns_2\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_catalog_page_sk\" AS \"page_sk\",\n    \"catalog_sales\".\"cs_sold_date_sk\" AS \"date_sk\",\n    \"catalog_sales\".\"cs_ext_sales_price\" AS \"sales_price\",\n    \"catalog_sales\".\"cs_net_profit\" AS \"profit\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"return_amt\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"net_loss\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  UNION ALL\n  SELECT\n    \"catalog_returns\".\"cr_catalog_page_sk\" AS \"page_sk\",\n    \"catalog_returns\".\"cr_returned_date_sk\" AS \"date_sk\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"sales_price\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"profit\",\n    \"catalog_returns\".\"cr_return_amount\" AS \"return_amt\",\n    \"catalog_returns\".\"cr_net_loss\" AS \"net_loss\"\n  FROM \"catalog_returns\" AS \"catalog_returns\"\n), \"csr\" AS (\n  SELECT\n    \"catalog_page\".\"cp_catalog_page_id\" AS \"cp_catalog_page_id\",\n    SUM(\"salesreturns\".\"sales_price\") AS \"sales\",\n    SUM(\"salesreturns\".\"profit\") AS \"profit\",\n    SUM(\"salesreturns\".\"return_amt\") AS \"returns1\",\n    SUM(\"salesreturns\".\"net_loss\") AS \"profit_loss\"\n  FROM \"salesreturns_2\" AS \"salesreturns\"\n  JOIN \"catalog_page\" AS \"catalog_page\"\n    ON \"catalog_page\".\"cp_catalog_page_sk\" = \"salesreturns\".\"page_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"salesreturns\".\"date_sk\"\n  GROUP BY\n    \"catalog_page\".\"cp_catalog_page_id\"\n), \"salesreturns_3\" AS (\n  SELECT\n    \"web_sales\".\"ws_web_site_sk\" AS \"wsr_web_site_sk\",\n    \"web_sales\".\"ws_sold_date_sk\" AS \"date_sk\",\n    \"web_sales\".\"ws_ext_sales_price\" AS \"sales_price\",\n    \"web_sales\".\"ws_net_profit\" AS \"profit\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"return_amt\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"net_loss\"\n  FROM \"web_sales\" AS \"web_sales\"\n  UNION ALL\n  SELECT\n    \"web_sales\".\"ws_web_site_sk\" AS \"wsr_web_site_sk\",\n    \"web_returns\".\"wr_returned_date_sk\" AS \"date_sk\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"sales_price\",\n    CAST(0 AS DECIMAL(7, 2)) AS \"profit\",\n    \"web_returns\".\"wr_return_amt\" AS \"return_amt\",\n    \"web_returns\".\"wr_net_loss\" AS \"net_loss\"\n  FROM \"web_returns\" AS \"web_returns\"\n  LEFT JOIN \"web_sales\" AS \"web_sales\"\n    ON \"web_returns\".\"wr_item_sk\" = \"web_sales\".\"ws_item_sk\"\n    AND \"web_returns\".\"wr_order_number\" = \"web_sales\".\"ws_order_number\"\n), \"wsr\" AS (\n  SELECT\n    \"web_site\".\"web_site_id\" AS \"web_site_id\",\n    SUM(\"salesreturns\".\"sales_price\") AS \"sales\",\n    SUM(\"salesreturns\".\"profit\") AS \"profit\",\n    SUM(\"salesreturns\".\"return_amt\") AS \"returns1\",\n    SUM(\"salesreturns\".\"net_loss\") AS \"profit_loss\"\n  FROM \"salesreturns_3\" AS \"salesreturns\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"salesreturns\".\"date_sk\"\n  JOIN \"web_site\" AS \"web_site\"\n    ON \"salesreturns\".\"wsr_web_site_sk\" = \"web_site\".\"web_site_sk\"\n  GROUP BY\n    \"web_site\".\"web_site_id\"\n), \"x\" AS (\n  SELECT\n    'store channel' AS \"channel\",\n    'store' || \"ssr\".\"s_store_id\" AS \"id\",\n    \"ssr\".\"sales\" AS \"sales\",\n    \"ssr\".\"returns1\" AS \"returns1\",\n    \"ssr\".\"profit\" - \"ssr\".\"profit_loss\" AS \"profit\"\n  FROM \"ssr\" AS \"ssr\"\n  UNION ALL\n  SELECT\n    'catalog channel' AS \"channel\",\n    'catalog_page' || \"csr\".\"cp_catalog_page_id\" AS \"id\",\n    \"csr\".\"sales\" AS \"sales\",\n    \"csr\".\"returns1\" AS \"returns1\",\n    \"csr\".\"profit\" - \"csr\".\"profit_loss\" AS \"profit\"\n  FROM \"csr\" AS \"csr\"\n  UNION ALL\n  SELECT\n    'web channel' AS \"channel\",\n    'web_site' || \"wsr\".\"web_site_id\" AS \"id\",\n    \"wsr\".\"sales\" AS \"sales\",\n    \"wsr\".\"returns1\" AS \"returns1\",\n    \"wsr\".\"profit\" - \"wsr\".\"profit_loss\" AS \"profit\"\n  FROM \"wsr\" AS \"wsr\"\n)\nSELECT\n  \"x\".\"channel\" AS \"channel\",\n  \"x\".\"id\" AS \"id\",\n  SUM(\"x\".\"sales\") AS \"sales\",\n  SUM(\"x\".\"returns1\") AS \"returns1\",\n  SUM(\"x\".\"profit\") AS \"profit\"\nFROM \"x\" AS \"x\"\nGROUP BY\n  ROLLUP (\n    \"x\".\"channel\",\n    \"x\".\"id\"\n  )\nORDER BY\n  \"channel\",\n  \"id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 6\n--------------------------------------\n# execute: true\nSELECT a.ca_state state,\n               Count(*)   cnt\nFROM   customer_address a,\n       customer c,\n       store_sales s,\n       date_dim d,\n       item i\nWHERE  a.ca_address_sk = c.c_current_addr_sk\n       AND c.c_customer_sk = s.ss_customer_sk\n       AND s.ss_sold_date_sk = d.d_date_sk\n       AND s.ss_item_sk = i.i_item_sk\n       AND d.d_month_seq = (SELECT DISTINCT ( d_month_seq )\n                            FROM   date_dim\n                            WHERE  d_year = 1998\n                                   AND d_moy = 7)\n       AND i.i_current_price > 1.2 * (SELECT Avg(j.i_current_price)\n                                      FROM   item j\n                                      WHERE  j.i_category = i.i_category)\nGROUP  BY a.ca_state\nHAVING Count(*) >= 10\nORDER  BY cnt\nLIMIT 100;\nWITH \"_u_0\" AS (\n  SELECT DISTINCT\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 7 AND \"date_dim\".\"d_year\" = 1998\n), \"_u_1\" AS (\n  SELECT\n    AVG(\"j\".\"i_current_price\") AS \"_col_0\",\n    \"j\".\"i_category\" AS \"_u_2\"\n  FROM \"item\" AS \"j\"\n  GROUP BY\n    \"j\".\"i_category\"\n)\nSELECT\n  \"a\".\"ca_state\" AS \"state\",\n  COUNT(*) AS \"cnt\"\nFROM \"customer_address\" AS \"a\"\nJOIN \"customer\" AS \"c\"\n  ON \"a\".\"ca_address_sk\" = \"c\".\"c_current_addr_sk\"\nJOIN \"store_sales\" AS \"s\"\n  ON \"c\".\"c_customer_sk\" = \"s\".\"ss_customer_sk\"\nJOIN \"date_dim\" AS \"d\"\n  ON \"d\".\"d_date_sk\" = \"s\".\"ss_sold_date_sk\"\nJOIN \"item\" AS \"i\"\n  ON \"i\".\"i_item_sk\" = \"s\".\"ss_item_sk\"\nJOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"d_month_seq\" = \"d\".\"d_month_seq\"\nLEFT JOIN \"_u_1\" AS \"_u_1\"\n  ON \"_u_1\".\"_u_2\" = \"i\".\"i_category\"\nWHERE\n  \"i\".\"i_current_price\" > 1.2 * \"_u_1\".\"_col_0\"\nGROUP BY\n  \"a\".\"ca_state\"\nHAVING\n  COUNT(*) >= 10\nORDER BY\n  \"cnt\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 7\n--------------------------------------\n# execute: true\nSELECT i_item_id,\n               Avg(ss_quantity)    agg1,\n               Avg(ss_list_price)  agg2,\n               Avg(ss_coupon_amt)  agg3,\n               Avg(ss_sales_price) agg4\nFROM   store_sales,\n       customer_demographics,\n       date_dim,\n       item,\n       promotion\nWHERE  ss_sold_date_sk = d_date_sk\n       AND ss_item_sk = i_item_sk\n       AND ss_cdemo_sk = cd_demo_sk\n       AND ss_promo_sk = p_promo_sk\n       AND cd_gender = 'F'\n       AND cd_marital_status = 'W'\n       AND cd_education_status = '2 yr Degree'\n       AND ( p_channel_email = 'N'\n              OR p_channel_event = 'N' )\n       AND d_year = 1998\nGROUP  BY i_item_id\nORDER  BY i_item_id\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  AVG(\"store_sales\".\"ss_quantity\") AS \"agg1\",\n  AVG(\"store_sales\".\"ss_list_price\") AS \"agg2\",\n  AVG(\"store_sales\".\"ss_coupon_amt\") AS \"agg3\",\n  AVG(\"store_sales\".\"ss_sales_price\") AS \"agg4\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"customer_demographics\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n  AND \"customer_demographics\".\"cd_education_status\" = '2 yr Degree'\n  AND \"customer_demographics\".\"cd_gender\" = 'F'\n  AND \"customer_demographics\".\"cd_marital_status\" = 'W'\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"date_dim\".\"d_year\" = 1998\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\nJOIN \"promotion\" AS \"promotion\"\n  ON (\n    \"promotion\".\"p_channel_email\" = 'N' OR \"promotion\".\"p_channel_event\" = 'N'\n  )\n  AND \"promotion\".\"p_promo_sk\" = \"store_sales\".\"ss_promo_sk\"\nGROUP BY\n  \"item\".\"i_item_id\"\nORDER BY\n  \"i_item_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 8\n--------------------------------------\nSELECT s_store_name,\n               Sum(ss_net_profit)\nFROM   store_sales,\n       date_dim,\n       store,\n       (SELECT ca_zip\n        FROM   (SELECT SUBSTRING(ca_zip, 1, 5) ca_zip\n                FROM   customer_address\n                WHERE  SUBSTRING(ca_zip, 1, 5) IN ( '67436', '26121', '38443',\n                                                 '63157',\n                                                 '68856', '19485', '86425',\n                                                 '26741',\n                                                 '70991', '60899', '63573',\n                                                 '47556',\n                                                 '56193', '93314', '87827',\n                                                 '62017',\n                                                 '85067', '95390', '48091',\n                                                 '10261',\n                                                 '81845', '41790', '42853',\n                                                 '24675',\n                                                 '12840', '60065', '84430',\n                                                 '57451',\n                                                 '24021', '91735', '75335',\n                                                 '71935',\n                                                 '34482', '56943', '70695',\n                                                 '52147',\n                                                 '56251', '28411', '86653',\n                                                 '23005',\n                                                 '22478', '29031', '34398',\n                                                 '15365',\n                                                 '42460', '33337', '59433',\n                                                 '73943',\n                                                 '72477', '74081', '74430',\n                                                 '64605',\n                                                 '39006', '11226', '49057',\n                                                 '97308',\n                                                 '42663', '18187', '19768',\n                                                 '43454',\n                                                 '32147', '76637', '51975',\n                                                 '11181',\n                                                 '45630', '33129', '45995',\n                                                 '64386',\n                                                 '55522', '26697', '20963',\n                                                 '35154',\n                                                 '64587', '49752', '66386',\n                                                 '30586',\n                                                 '59286', '13177', '66646',\n                                                 '84195',\n                                                 '74316', '36853', '32927',\n                                                 '12469',\n                                                 '11904', '36269', '17724',\n                                                 '55346',\n                                                 '12595', '53988', '65439',\n                                                 '28015',\n                                                 '63268', '73590', '29216',\n                                                 '82575',\n                                                 '69267', '13805', '91678',\n                                                 '79460',\n                                                 '94152', '14961', '15419',\n                                                 '48277',\n                                                 '62588', '55493', '28360',\n                                                 '14152',\n                                                 '55225', '18007', '53705',\n                                                 '56573',\n                                                 '80245', '71769', '57348',\n                                                 '36845',\n                                                 '13039', '17270', '22363',\n                                                 '83474',\n                                                 '25294', '43269', '77666',\n                                                 '15488',\n                                                 '99146', '64441', '43338',\n                                                 '38736',\n                                                 '62754', '48556', '86057',\n                                                 '23090',\n                                                 '38114', '66061', '18910',\n                                                 '84385',\n                                                 '23600', '19975', '27883',\n                                                 '65719',\n                                                 '19933', '32085', '49731',\n                                                 '40473',\n                                                 '27190', '46192', '23949',\n                                                 '44738',\n                                                 '12436', '64794', '68741',\n                                                 '15333',\n                                                 '24282', '49085', '31844',\n                                                 '71156',\n                                                 '48441', '17100', '98207',\n                                                 '44982',\n                                                 '20277', '71496', '96299',\n                                                 '37583',\n                                                 '22206', '89174', '30589',\n                                                 '61924',\n                                                 '53079', '10976', '13104',\n                                                 '42794',\n                                                 '54772', '15809', '56434',\n                                                 '39975',\n                                                 '13874', '30753', '77598',\n                                                 '78229',\n                                                 '59478', '12345', '55547',\n                                                 '57422',\n                                                 '42600', '79444', '29074',\n                                                 '29752',\n                                                 '21676', '32096', '43044',\n                                                 '39383',\n                                                 '37296', '36295', '63077',\n                                                 '16572',\n                                                 '31275', '18701', '40197',\n                                                 '48242',\n                                                 '27219', '49865', '84175',\n                                                 '30446',\n                                                 '25165', '13807', '72142',\n                                                 '70499',\n                                                 '70464', '71429', '18111',\n                                                 '70857',\n                                                 '29545', '36425', '52706',\n                                                 '36194',\n                                                 '42963', '75068', '47921',\n                                                 '74763',\n                                                 '90990', '89456', '62073',\n                                                 '88397',\n                                                 '73963', '75885', '62657',\n                                                 '12530',\n                                                 '81146', '57434', '25099',\n                                                 '41429',\n                                                 '98441', '48713', '52552',\n                                                 '31667',\n                                                 '14072', '13903', '44709',\n                                                 '85429',\n                                                 '58017', '38295', '44875',\n                                                 '73541',\n                                                 '30091', '12707', '23762',\n                                                 '62258',\n                                                 '33247', '78722', '77431',\n                                                 '14510',\n                                                 '35656', '72428', '92082',\n                                                 '35267',\n                                                 '43759', '24354', '90952',\n                                                 '11512',\n                                                 '21242', '22579', '56114',\n                                                 '32339',\n                                                 '52282', '41791', '24484',\n                                                 '95020',\n                                                 '28408', '99710', '11899',\n                                                 '43344',\n                                                 '72915', '27644', '62708',\n                                                 '74479',\n                                                 '17177', '32619', '12351',\n                                                 '91339',\n                                                 '31169', '57081', '53522',\n                                                 '16712',\n                                                 '34419', '71779', '44187',\n                                                 '46206',\n                                                 '96099', '61910', '53664',\n                                                 '12295',\n                                                 '31837', '33096', '10813',\n                                                 '63048',\n                                                 '31732', '79118', '73084',\n                                                 '72783',\n                                                 '84952', '46965', '77956',\n                                                 '39815',\n                                                 '32311', '75329', '48156',\n                                                 '30826',\n                                                 '49661', '13736', '92076',\n                                                 '74865',\n                                                 '88149', '92397', '52777',\n                                                 '68453',\n                                                 '32012', '21222', '52721',\n                                                 '24626',\n                                                 '18210', '42177', '91791',\n                                                 '75251',\n                                                 '82075', '44372', '45542',\n                                                 '20609',\n                                                 '60115', '17362', '22750',\n                                                 '90434',\n                                                 '31852', '54071', '33762',\n                                                 '14705',\n                                                 '40718', '56433', '30996',\n                                                 '40657',\n                                                 '49056', '23585', '66455',\n                                                 '41021',\n                                                 '74736', '72151', '37007',\n                                                 '21729',\n                                                 '60177', '84558', '59027',\n                                                 '93855',\n                                                 '60022', '86443', '19541',\n                                                 '86886',\n                                                 '30532', '39062', '48532',\n                                                 '34713',\n                                                 '52077', '22564', '64638',\n                                                 '15273',\n                                                 '31677', '36138', '62367',\n                                                 '60261',\n                                                 '80213', '42818', '25113',\n                                                 '72378',\n                                                 '69802', '69096', '55443',\n                                                 '28820',\n                                                 '13848', '78258', '37490',\n                                                 '30556',\n                                                 '77380', '28447', '44550',\n                                                 '26791',\n                                                 '70609', '82182', '33306',\n                                                 '43224',\n                                                 '22322', '86959', '68519',\n                                                 '14308',\n                                                 '46501', '81131', '34056',\n                                                 '61991',\n                                                 '19896', '87804', '65774',\n                                                 '92564' )\n                INTERSECT\n                SELECT ca_zip\n                FROM   (SELECT SUBSTRING(ca_zip, 1, 5) ca_zip,\n                               Count(*)             cnt\n                        FROM   customer_address,\n                               customer\n                        WHERE  ca_address_sk = c_current_addr_sk\n                               AND c_preferred_cust_flag = 'Y'\n                        GROUP  BY ca_zip\n                        HAVING Count(*) > 10)A1)A2) V1\nWHERE  ss_store_sk = s_store_sk\n       AND ss_sold_date_sk = d_date_sk\n       AND d_qoy = 2\n       AND d_year = 2000\n       AND ( SUBSTRING(s_zip, 1, 2) = SUBSTRING(V1.ca_zip, 1, 2) )\nGROUP  BY s_store_name\nORDER  BY s_store_name\nLIMIT 100;\nWITH \"a1\" AS (\n  SELECT\n    SUBSTRING(\"customer_address\".\"ca_zip\", 1, 5) AS \"ca_zip\"\n  FROM \"customer_address\" AS \"customer_address\"\n  JOIN \"customer\" AS \"customer\"\n    ON \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n    AND \"customer\".\"c_preferred_cust_flag\" = 'Y'\n  GROUP BY\n    \"customer_address\".\"ca_zip\"\n  HAVING\n    COUNT(*) > 10\n), \"a2\" AS (\n  SELECT\n    SUBSTRING(\"customer_address\".\"ca_zip\", 1, 5) AS \"ca_zip\"\n  FROM \"customer_address\" AS \"customer_address\"\n  WHERE\n    SUBSTRING(\"customer_address\".\"ca_zip\", 1, 5) IN (\n      '67436',\n      '26121',\n      '38443',\n      '63157',\n      '68856',\n      '19485',\n      '86425',\n      '26741',\n      '70991',\n      '60899',\n      '63573',\n      '47556',\n      '56193',\n      '93314',\n      '87827',\n      '62017',\n      '85067',\n      '95390',\n      '48091',\n      '10261',\n      '81845',\n      '41790',\n      '42853',\n      '24675',\n      '12840',\n      '60065',\n      '84430',\n      '57451',\n      '24021',\n      '91735',\n      '75335',\n      '71935',\n      '34482',\n      '56943',\n      '70695',\n      '52147',\n      '56251',\n      '28411',\n      '86653',\n      '23005',\n      '22478',\n      '29031',\n      '34398',\n      '15365',\n      '42460',\n      '33337',\n      '59433',\n      '73943',\n      '72477',\n      '74081',\n      '74430',\n      '64605',\n      '39006',\n      '11226',\n      '49057',\n      '97308',\n      '42663',\n      '18187',\n      '19768',\n      '43454',\n      '32147',\n      '76637',\n      '51975',\n      '11181',\n      '45630',\n      '33129',\n      '45995',\n      '64386',\n      '55522',\n      '26697',\n      '20963',\n      '35154',\n      '64587',\n      '49752',\n      '66386',\n      '30586',\n      '59286',\n      '13177',\n      '66646',\n      '84195',\n      '74316',\n      '36853',\n      '32927',\n      '12469',\n      '11904',\n      '36269',\n      '17724',\n      '55346',\n      '12595',\n      '53988',\n      '65439',\n      '28015',\n      '63268',\n      '73590',\n      '29216',\n      '82575',\n      '69267',\n      '13805',\n      '91678',\n      '79460',\n      '94152',\n      '14961',\n      '15419',\n      '48277',\n      '62588',\n      '55493',\n      '28360',\n      '14152',\n      '55225',\n      '18007',\n      '53705',\n      '56573',\n      '80245',\n      '71769',\n      '57348',\n      '36845',\n      '13039',\n      '17270',\n      '22363',\n      '83474',\n      '25294',\n      '43269',\n      '77666',\n      '15488',\n      '99146',\n      '64441',\n      '43338',\n      '38736',\n      '62754',\n      '48556',\n      '86057',\n      '23090',\n      '38114',\n      '66061',\n      '18910',\n      '84385',\n      '23600',\n      '19975',\n      '27883',\n      '65719',\n      '19933',\n      '32085',\n      '49731',\n      '40473',\n      '27190',\n      '46192',\n      '23949',\n      '44738',\n      '12436',\n      '64794',\n      '68741',\n      '15333',\n      '24282',\n      '49085',\n      '31844',\n      '71156',\n      '48441',\n      '17100',\n      '98207',\n      '44982',\n      '20277',\n      '71496',\n      '96299',\n      '37583',\n      '22206',\n      '89174',\n      '30589',\n      '61924',\n      '53079',\n      '10976',\n      '13104',\n      '42794',\n      '54772',\n      '15809',\n      '56434',\n      '39975',\n      '13874',\n      '30753',\n      '77598',\n      '78229',\n      '59478',\n      '12345',\n      '55547',\n      '57422',\n      '42600',\n      '79444',\n      '29074',\n      '29752',\n      '21676',\n      '32096',\n      '43044',\n      '39383',\n      '37296',\n      '36295',\n      '63077',\n      '16572',\n      '31275',\n      '18701',\n      '40197',\n      '48242',\n      '27219',\n      '49865',\n      '84175',\n      '30446',\n      '25165',\n      '13807',\n      '72142',\n      '70499',\n      '70464',\n      '71429',\n      '18111',\n      '70857',\n      '29545',\n      '36425',\n      '52706',\n      '36194',\n      '42963',\n      '75068',\n      '47921',\n      '74763',\n      '90990',\n      '89456',\n      '62073',\n      '88397',\n      '73963',\n      '75885',\n      '62657',\n      '12530',\n      '81146',\n      '57434',\n      '25099',\n      '41429',\n      '98441',\n      '48713',\n      '52552',\n      '31667',\n      '14072',\n      '13903',\n      '44709',\n      '85429',\n      '58017',\n      '38295',\n      '44875',\n      '73541',\n      '30091',\n      '12707',\n      '23762',\n      '62258',\n      '33247',\n      '78722',\n      '77431',\n      '14510',\n      '35656',\n      '72428',\n      '92082',\n      '35267',\n      '43759',\n      '24354',\n      '90952',\n      '11512',\n      '21242',\n      '22579',\n      '56114',\n      '32339',\n      '52282',\n      '41791',\n      '24484',\n      '95020',\n      '28408',\n      '99710',\n      '11899',\n      '43344',\n      '72915',\n      '27644',\n      '62708',\n      '74479',\n      '17177',\n      '32619',\n      '12351',\n      '91339',\n      '31169',\n      '57081',\n      '53522',\n      '16712',\n      '34419',\n      '71779',\n      '44187',\n      '46206',\n      '96099',\n      '61910',\n      '53664',\n      '12295',\n      '31837',\n      '33096',\n      '10813',\n      '63048',\n      '31732',\n      '79118',\n      '73084',\n      '72783',\n      '84952',\n      '46965',\n      '77956',\n      '39815',\n      '32311',\n      '75329',\n      '48156',\n      '30826',\n      '49661',\n      '13736',\n      '92076',\n      '74865',\n      '88149',\n      '92397',\n      '52777',\n      '68453',\n      '32012',\n      '21222',\n      '52721',\n      '24626',\n      '18210',\n      '42177',\n      '91791',\n      '75251',\n      '82075',\n      '44372',\n      '45542',\n      '20609',\n      '60115',\n      '17362',\n      '22750',\n      '90434',\n      '31852',\n      '54071',\n      '33762',\n      '14705',\n      '40718',\n      '56433',\n      '30996',\n      '40657',\n      '49056',\n      '23585',\n      '66455',\n      '41021',\n      '74736',\n      '72151',\n      '37007',\n      '21729',\n      '60177',\n      '84558',\n      '59027',\n      '93855',\n      '60022',\n      '86443',\n      '19541',\n      '86886',\n      '30532',\n      '39062',\n      '48532',\n      '34713',\n      '52077',\n      '22564',\n      '64638',\n      '15273',\n      '31677',\n      '36138',\n      '62367',\n      '60261',\n      '80213',\n      '42818',\n      '25113',\n      '72378',\n      '69802',\n      '69096',\n      '55443',\n      '28820',\n      '13848',\n      '78258',\n      '37490',\n      '30556',\n      '77380',\n      '28447',\n      '44550',\n      '26791',\n      '70609',\n      '82182',\n      '33306',\n      '43224',\n      '22322',\n      '86959',\n      '68519',\n      '14308',\n      '46501',\n      '81131',\n      '34056',\n      '61991',\n      '19896',\n      '87804',\n      '65774',\n      '92564'\n    )\n  INTERSECT\n  SELECT\n    \"a1\".\"ca_zip\" AS \"ca_zip\"\n  FROM \"a1\" AS \"a1\"\n)\nSELECT\n  \"store\".\"s_store_name\" AS \"s_store_name\",\n  SUM(\"store_sales\".\"ss_net_profit\") AS \"_col_1\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"date_dim\".\"d_qoy\" = 2\n  AND \"date_dim\".\"d_year\" = 2000\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nJOIN \"a2\" AS \"a2\"\n  ON SUBSTRING(\"a2\".\"ca_zip\", 1, 2) = SUBSTRING(\"store\".\"s_zip\", 1, 2)\nGROUP BY\n  \"store\".\"s_store_name\"\nORDER BY\n  \"s_store_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 9\n--------------------------------------\n# execute: true\nSELECT CASE\n         WHEN (SELECT Count(*)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 1 AND 20) > 3672 THEN\n         (SELECT Avg(ss_ext_list_price)\n          FROM   store_sales\n          WHERE\n         ss_quantity BETWEEN 1 AND 20)\n         ELSE (SELECT Avg(ss_net_profit)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 1 AND 20)\n       END bucket1,\n       CASE\n         WHEN (SELECT Count(*)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 21 AND 40) > 3392 THEN\n         (SELECT Avg(ss_ext_list_price)\n          FROM   store_sales\n          WHERE\n         ss_quantity BETWEEN 21 AND 40)\n         ELSE (SELECT Avg(ss_net_profit)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 21 AND 40)\n       END bucket2,\n       CASE\n         WHEN (SELECT Count(*)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 41 AND 60) > 32784 THEN\n         (SELECT Avg(ss_ext_list_price)\n          FROM   store_sales\n          WHERE\n         ss_quantity BETWEEN 41 AND 60)\n         ELSE (SELECT Avg(ss_net_profit)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 41 AND 60)\n       END bucket3,\n       CASE\n         WHEN (SELECT Count(*)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 61 AND 80) > 26032 THEN\n         (SELECT Avg(ss_ext_list_price)\n          FROM   store_sales\n          WHERE\n         ss_quantity BETWEEN 61 AND 80)\n         ELSE (SELECT Avg(ss_net_profit)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 61 AND 80)\n       END bucket4,\n       CASE\n         WHEN (SELECT Count(*)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 81 AND 100) > 23982 THEN\n         (SELECT Avg(ss_ext_list_price)\n          FROM   store_sales\n          WHERE\n         ss_quantity BETWEEN 81 AND 100)\n         ELSE (SELECT Avg(ss_net_profit)\n               FROM   store_sales\n               WHERE  ss_quantity BETWEEN 81 AND 100)\n       END bucket5\nFROM   reason\nWHERE  r_reason_sk = 1;\nWITH \"_u_0\" AS (\n  SELECT\n    COUNT(*) AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 20 AND \"store_sales\".\"ss_quantity\" >= 1\n), \"_u_1\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_ext_list_price\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 20 AND \"store_sales\".\"ss_quantity\" >= 1\n), \"_u_10\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_ext_list_price\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 80 AND \"store_sales\".\"ss_quantity\" >= 61\n), \"_u_11\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_net_profit\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 80 AND \"store_sales\".\"ss_quantity\" >= 61\n), \"_u_12\" AS (\n  SELECT\n    COUNT(*) AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 100 AND \"store_sales\".\"ss_quantity\" >= 81\n), \"_u_13\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_ext_list_price\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 100 AND \"store_sales\".\"ss_quantity\" >= 81\n), \"_u_14\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_net_profit\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 100 AND \"store_sales\".\"ss_quantity\" >= 81\n), \"_u_2\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_net_profit\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 20 AND \"store_sales\".\"ss_quantity\" >= 1\n), \"_u_3\" AS (\n  SELECT\n    COUNT(*) AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 40 AND \"store_sales\".\"ss_quantity\" >= 21\n), \"_u_4\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_ext_list_price\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 40 AND \"store_sales\".\"ss_quantity\" >= 21\n), \"_u_5\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_net_profit\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 40 AND \"store_sales\".\"ss_quantity\" >= 21\n), \"_u_6\" AS (\n  SELECT\n    COUNT(*) AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 60 AND \"store_sales\".\"ss_quantity\" >= 41\n), \"_u_7\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_ext_list_price\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 60 AND \"store_sales\".\"ss_quantity\" >= 41\n), \"_u_8\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_net_profit\") AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 60 AND \"store_sales\".\"ss_quantity\" >= 41\n), \"_u_9\" AS (\n  SELECT\n    COUNT(*) AS \"_col_0\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_quantity\" <= 80 AND \"store_sales\".\"ss_quantity\" >= 61\n)\nSELECT\n  CASE WHEN \"_u_0\".\"_col_0\" > 3672 THEN \"_u_1\".\"_col_0\" ELSE \"_u_2\".\"_col_0\" END AS \"bucket1\",\n  CASE WHEN \"_u_3\".\"_col_0\" > 3392 THEN \"_u_4\".\"_col_0\" ELSE \"_u_5\".\"_col_0\" END AS \"bucket2\",\n  CASE WHEN \"_u_6\".\"_col_0\" > 32784 THEN \"_u_7\".\"_col_0\" ELSE \"_u_8\".\"_col_0\" END AS \"bucket3\",\n  CASE WHEN \"_u_9\".\"_col_0\" > 26032 THEN \"_u_10\".\"_col_0\" ELSE \"_u_11\".\"_col_0\" END AS \"bucket4\",\n  CASE WHEN \"_u_12\".\"_col_0\" > 23982 THEN \"_u_13\".\"_col_0\" ELSE \"_u_14\".\"_col_0\" END AS \"bucket5\"\nFROM \"reason\" AS \"reason\"\nCROSS JOIN \"_u_0\" AS \"_u_0\"\nCROSS JOIN \"_u_1\" AS \"_u_1\"\nCROSS JOIN \"_u_10\" AS \"_u_10\"\nCROSS JOIN \"_u_11\" AS \"_u_11\"\nCROSS JOIN \"_u_12\" AS \"_u_12\"\nCROSS JOIN \"_u_13\" AS \"_u_13\"\nCROSS JOIN \"_u_14\" AS \"_u_14\"\nCROSS JOIN \"_u_2\" AS \"_u_2\"\nCROSS JOIN \"_u_3\" AS \"_u_3\"\nCROSS JOIN \"_u_4\" AS \"_u_4\"\nCROSS JOIN \"_u_5\" AS \"_u_5\"\nCROSS JOIN \"_u_6\" AS \"_u_6\"\nCROSS JOIN \"_u_7\" AS \"_u_7\"\nCROSS JOIN \"_u_8\" AS \"_u_8\"\nCROSS JOIN \"_u_9\" AS \"_u_9\"\nWHERE\n  \"reason\".\"r_reason_sk\" = 1;\n\n--------------------------------------\n-- TPC-DS 10\n--------------------------------------\n# execute: true\nSELECT cd_gender,\n               cd_marital_status,\n               cd_education_status,\n               Count(*) cnt1,\n               cd_purchase_estimate,\n               Count(*) cnt2,\n               cd_credit_rating,\n               Count(*) cnt3,\n               cd_dep_count,\n               Count(*) cnt4,\n               cd_dep_employed_count,\n               Count(*) cnt5,\n               cd_dep_college_count,\n               Count(*) cnt6\nFROM   customer c,\n       customer_address ca,\n       customer_demographics\nWHERE  c.c_current_addr_sk = ca.ca_address_sk\n       AND ca_county IN ( 'Lycoming County', 'Sheridan County',\n                          'Kandiyohi County',\n                          'Pike County',\n                                           'Greene County' )\n       AND cd_demo_sk = c.c_current_cdemo_sk\n       AND EXISTS (SELECT *\n                   FROM   store_sales,\n                          date_dim\n                   WHERE  c.c_customer_sk = ss_customer_sk\n                          AND ss_sold_date_sk = d_date_sk\n                          AND d_year = 2002\n                          AND d_moy BETWEEN 4 AND 4 + 3)\n       AND ( EXISTS (SELECT *\n                     FROM   web_sales,\n                            date_dim\n                     WHERE  c.c_customer_sk = ws_bill_customer_sk\n                            AND ws_sold_date_sk = d_date_sk\n                            AND d_year = 2002\n                            AND d_moy BETWEEN 4 AND 4 + 3)\n              OR EXISTS (SELECT *\n                         FROM   catalog_sales,\n                                date_dim\n                         WHERE  c.c_customer_sk = cs_ship_customer_sk\n                                AND cs_sold_date_sk = d_date_sk\n                                AND d_year = 2002\n                                AND d_moy BETWEEN 4 AND 4 + 3) )\nGROUP  BY cd_gender,\n          cd_marital_status,\n          cd_education_status,\n          cd_purchase_estimate,\n          cd_credit_rating,\n          cd_dep_count,\n          cd_dep_employed_count,\n          cd_dep_college_count\nORDER  BY cd_gender,\n          cd_marital_status,\n          cd_education_status,\n          cd_purchase_estimate,\n          cd_credit_rating,\n          cd_dep_count,\n          cd_dep_employed_count,\n          cd_dep_college_count\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date_id\" AS \"d_date_id\",\n    \"date_dim\".\"d_date\" AS \"d_date\",\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\",\n    \"date_dim\".\"d_week_seq\" AS \"d_week_seq\",\n    \"date_dim\".\"d_quarter_seq\" AS \"d_quarter_seq\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_dow\" AS \"d_dow\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\",\n    \"date_dim\".\"d_dom\" AS \"d_dom\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"date_dim\".\"d_fy_year\" AS \"d_fy_year\",\n    \"date_dim\".\"d_fy_quarter_seq\" AS \"d_fy_quarter_seq\",\n    \"date_dim\".\"d_fy_week_seq\" AS \"d_fy_week_seq\",\n    \"date_dim\".\"d_day_name\" AS \"d_day_name\",\n    \"date_dim\".\"d_quarter_name\" AS \"d_quarter_name\",\n    \"date_dim\".\"d_holiday\" AS \"d_holiday\",\n    \"date_dim\".\"d_weekend\" AS \"d_weekend\",\n    \"date_dim\".\"d_following_holiday\" AS \"d_following_holiday\",\n    \"date_dim\".\"d_first_dom\" AS \"d_first_dom\",\n    \"date_dim\".\"d_last_dom\" AS \"d_last_dom\",\n    \"date_dim\".\"d_same_day_ly\" AS \"d_same_day_ly\",\n    \"date_dim\".\"d_same_day_lq\" AS \"d_same_day_lq\",\n    \"date_dim\".\"d_current_day\" AS \"d_current_day\",\n    \"date_dim\".\"d_current_week\" AS \"d_current_week\",\n    \"date_dim\".\"d_current_month\" AS \"d_current_month\",\n    \"date_dim\".\"d_current_quarter\" AS \"d_current_quarter\",\n    \"date_dim\".\"d_current_year\" AS \"d_current_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" <= 7 AND \"date_dim\".\"d_moy\" >= 4 AND \"date_dim\".\"d_year\" = 2002\n), \"_u_0\" AS (\n  SELECT\n    \"store_sales\".\"ss_customer_sk\" AS \"_u_1\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_customer_sk\"\n), \"_u_2\" AS (\n  SELECT\n    \"web_sales\".\"ws_bill_customer_sk\" AS \"_u_3\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  GROUP BY\n    \"web_sales\".\"ws_bill_customer_sk\"\n), \"_u_4\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_ship_customer_sk\" AS \"_u_5\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  GROUP BY\n    \"catalog_sales\".\"cs_ship_customer_sk\"\n)\nSELECT\n  \"customer_demographics\".\"cd_gender\" AS \"cd_gender\",\n  \"customer_demographics\".\"cd_marital_status\" AS \"cd_marital_status\",\n  \"customer_demographics\".\"cd_education_status\" AS \"cd_education_status\",\n  COUNT(*) AS \"cnt1\",\n  \"customer_demographics\".\"cd_purchase_estimate\" AS \"cd_purchase_estimate\",\n  COUNT(*) AS \"cnt2\",\n  \"customer_demographics\".\"cd_credit_rating\" AS \"cd_credit_rating\",\n  COUNT(*) AS \"cnt3\",\n  \"customer_demographics\".\"cd_dep_count\" AS \"cd_dep_count\",\n  COUNT(*) AS \"cnt4\",\n  \"customer_demographics\".\"cd_dep_employed_count\" AS \"cd_dep_employed_count\",\n  COUNT(*) AS \"cnt5\",\n  \"customer_demographics\".\"cd_dep_college_count\" AS \"cd_dep_college_count\",\n  COUNT(*) AS \"cnt6\"\nFROM \"customer\" AS \"c\"\nJOIN \"customer_address\" AS \"ca\"\n  ON \"c\".\"c_current_addr_sk\" = \"ca\".\"ca_address_sk\"\n  AND \"ca\".\"ca_county\" IN (\n    'Lycoming County',\n    'Sheridan County',\n    'Kandiyohi County',\n    'Pike County',\n    'Greene County'\n  )\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"c\".\"c_current_cdemo_sk\" = \"customer_demographics\".\"cd_demo_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"c\".\"c_customer_sk\"\nLEFT JOIN \"_u_2\" AS \"_u_2\"\n  ON \"_u_2\".\"_u_3\" = \"c\".\"c_customer_sk\"\nLEFT JOIN \"_u_4\" AS \"_u_4\"\n  ON \"_u_4\".\"_u_5\" = \"c\".\"c_customer_sk\"\nWHERE\n  NOT \"_u_0\".\"_u_1\" IS NULL\n  AND (\n    NOT \"_u_2\".\"_u_3\" IS NULL OR NOT \"_u_4\".\"_u_5\" IS NULL\n  )\nGROUP BY\n  \"customer_demographics\".\"cd_gender\",\n  \"customer_demographics\".\"cd_marital_status\",\n  \"customer_demographics\".\"cd_education_status\",\n  \"customer_demographics\".\"cd_purchase_estimate\",\n  \"customer_demographics\".\"cd_credit_rating\",\n  \"customer_demographics\".\"cd_dep_count\",\n  \"customer_demographics\".\"cd_dep_employed_count\",\n  \"customer_demographics\".\"cd_dep_college_count\"\nORDER BY\n  \"cd_gender\",\n  \"cd_marital_status\",\n  \"cd_education_status\",\n  \"cd_purchase_estimate\",\n  \"cd_credit_rating\",\n  \"cd_dep_count\",\n  \"cd_dep_employed_count\",\n  \"cd_dep_college_count\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 11\n--------------------------------------\n# execute: true\nWITH year_total\n     AS (SELECT c_customer_id                                customer_id,\n                c_first_name                                 customer_first_name\n                ,\n                c_last_name\n                customer_last_name,\n                c_preferred_cust_flag\n                   customer_preferred_cust_flag\n                    ,\n                c_birth_country\n                    customer_birth_country,\n                c_login                                      customer_login,\n                c_email_address\n                customer_email_address,\n                d_year                                       dyear,\n                Sum(ss_ext_list_price - ss_ext_discount_amt) year_total,\n                's'                                          sale_type\n         FROM   customer,\n                store_sales,\n                date_dim\n         WHERE  c_customer_sk = ss_customer_sk\n                AND ss_sold_date_sk = d_date_sk\n         GROUP  BY c_customer_id,\n                   c_first_name,\n                   c_last_name,\n                   c_preferred_cust_flag,\n                   c_birth_country,\n                   c_login,\n                   c_email_address,\n                   d_year\n         UNION ALL\n         SELECT c_customer_id                                customer_id,\n                c_first_name                                 customer_first_name\n                ,\n                c_last_name\n                customer_last_name,\n                c_preferred_cust_flag\n                customer_preferred_cust_flag\n                ,\n                c_birth_country\n                customer_birth_country,\n                c_login                                      customer_login,\n                c_email_address\n                customer_email_address,\n                d_year                                       dyear,\n                Sum(ws_ext_list_price - ws_ext_discount_amt) year_total,\n                'w'                                          sale_type\n         FROM   customer,\n                web_sales,\n                date_dim\n         WHERE  c_customer_sk = ws_bill_customer_sk\n                AND ws_sold_date_sk = d_date_sk\n         GROUP  BY c_customer_id,\n                   c_first_name,\n                   c_last_name,\n                   c_preferred_cust_flag,\n                   c_birth_country,\n                   c_login,\n                   c_email_address,\n                   d_year)\nSELECT t_s_secyear.customer_id,\n               t_s_secyear.customer_first_name,\n               t_s_secyear.customer_last_name,\n               t_s_secyear.customer_birth_country\nFROM   year_total t_s_firstyear,\n       year_total t_s_secyear,\n       year_total t_w_firstyear,\n       year_total t_w_secyear\nWHERE  t_s_secyear.customer_id = t_s_firstyear.customer_id\n       AND t_s_firstyear.customer_id = t_w_secyear.customer_id\n       AND t_s_firstyear.customer_id = t_w_firstyear.customer_id\n       AND t_s_firstyear.sale_type = 's'\n       AND t_w_firstyear.sale_type = 'w'\n       AND t_s_secyear.sale_type = 's'\n       AND t_w_secyear.sale_type = 'w'\n       AND t_s_firstyear.dyear = 2001\n       AND t_s_secyear.dyear = 2001 + 1\n       AND t_w_firstyear.dyear = 2001\n       AND t_w_secyear.dyear = 2001 + 1\n       AND t_s_firstyear.year_total > 0\n       AND t_w_firstyear.year_total > 0\n       AND CASE\n             WHEN t_w_firstyear.year_total > 0 THEN t_w_secyear.year_total /\n                                                    t_w_firstyear.year_total\n             ELSE 0.0\n           END > CASE\n                   WHEN t_s_firstyear.year_total > 0 THEN\n                   t_s_secyear.year_total /\n                   t_s_firstyear.year_total\n                   ELSE 0.0\n                 END\nORDER  BY t_s_secyear.customer_id,\n          t_s_secyear.customer_first_name,\n          t_s_secyear.customer_last_name,\n          t_s_secyear.customer_birth_country\nLIMIT 100;\nWITH \"customer_2\" AS (\n  SELECT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\",\n    \"customer\".\"c_customer_id\" AS \"c_customer_id\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"customer\".\"c_last_name\" AS \"c_last_name\",\n    \"customer\".\"c_preferred_cust_flag\" AS \"c_preferred_cust_flag\",\n    \"customer\".\"c_birth_country\" AS \"c_birth_country\",\n    \"customer\".\"c_login\" AS \"c_login\",\n    \"customer\".\"c_email_address\" AS \"c_email_address\"\n  FROM \"customer\" AS \"customer\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n), \"year_total\" AS (\n  SELECT\n    \"customer\".\"c_customer_id\" AS \"customer_id\",\n    \"customer\".\"c_first_name\" AS \"customer_first_name\",\n    \"customer\".\"c_last_name\" AS \"customer_last_name\",\n    \"customer\".\"c_birth_country\" AS \"customer_birth_country\",\n    \"date_dim\".\"d_year\" AS \"dyear\",\n    SUM(\"store_sales\".\"ss_ext_list_price\" - \"store_sales\".\"ss_ext_discount_amt\") AS \"year_total\",\n    's' AS \"sale_type\"\n  FROM \"customer_2\" AS \"customer\"\n  JOIN \"store_sales\" AS \"store_sales\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"customer\".\"c_customer_id\",\n    \"customer\".\"c_first_name\",\n    \"customer\".\"c_last_name\",\n    \"customer\".\"c_preferred_cust_flag\",\n    \"customer\".\"c_birth_country\",\n    \"customer\".\"c_login\",\n    \"customer\".\"c_email_address\",\n    \"date_dim\".\"d_year\"\n  UNION ALL\n  SELECT\n    \"customer\".\"c_customer_id\" AS \"customer_id\",\n    \"customer\".\"c_first_name\" AS \"customer_first_name\",\n    \"customer\".\"c_last_name\" AS \"customer_last_name\",\n    \"customer\".\"c_birth_country\" AS \"customer_birth_country\",\n    \"date_dim\".\"d_year\" AS \"dyear\",\n    SUM(\"web_sales\".\"ws_ext_list_price\" - \"web_sales\".\"ws_ext_discount_amt\") AS \"year_total\",\n    'w' AS \"sale_type\"\n  FROM \"customer_2\" AS \"customer\"\n  JOIN \"web_sales\" AS \"web_sales\"\n    ON \"customer\".\"c_customer_sk\" = \"web_sales\".\"ws_bill_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  GROUP BY\n    \"customer\".\"c_customer_id\",\n    \"customer\".\"c_first_name\",\n    \"customer\".\"c_last_name\",\n    \"customer\".\"c_preferred_cust_flag\",\n    \"customer\".\"c_birth_country\",\n    \"customer\".\"c_login\",\n    \"customer\".\"c_email_address\",\n    \"date_dim\".\"d_year\"\n)\nSELECT\n  \"t_s_secyear\".\"customer_id\" AS \"customer_id\",\n  \"t_s_secyear\".\"customer_first_name\" AS \"customer_first_name\",\n  \"t_s_secyear\".\"customer_last_name\" AS \"customer_last_name\",\n  \"t_s_secyear\".\"customer_birth_country\" AS \"customer_birth_country\"\nFROM \"year_total\" AS \"t_s_firstyear\"\nJOIN \"year_total\" AS \"t_s_secyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_s_secyear\".\"customer_id\"\n  AND \"t_s_secyear\".\"dyear\" = 2002\n  AND \"t_s_secyear\".\"sale_type\" = 's'\nJOIN \"year_total\" AS \"t_w_firstyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_w_firstyear\".\"customer_id\"\n  AND \"t_w_firstyear\".\"dyear\" = 2001\n  AND \"t_w_firstyear\".\"sale_type\" = 'w'\n  AND \"t_w_firstyear\".\"year_total\" > 0\nJOIN \"year_total\" AS \"t_w_secyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_w_secyear\".\"customer_id\"\n  AND \"t_w_secyear\".\"dyear\" = 2002\n  AND \"t_w_secyear\".\"sale_type\" = 'w'\n  AND CASE\n    WHEN \"t_s_firstyear\".\"year_total\" > 0\n    THEN \"t_s_secyear\".\"year_total\" / \"t_s_firstyear\".\"year_total\"\n    ELSE 0.0\n  END < CASE\n    WHEN \"t_w_firstyear\".\"year_total\" > 0\n    THEN \"t_w_secyear\".\"year_total\" / \"t_w_firstyear\".\"year_total\"\n    ELSE 0.0\n  END\nWHERE\n  \"t_s_firstyear\".\"dyear\" = 2001\n  AND \"t_s_firstyear\".\"sale_type\" = 's'\n  AND \"t_s_firstyear\".\"year_total\" > 0\nORDER BY\n  \"t_s_secyear\".\"customer_id\",\n  \"t_s_secyear\".\"customer_first_name\",\n  \"t_s_secyear\".\"customer_last_name\",\n  \"t_s_secyear\".\"customer_birth_country\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 12\n--------------------------------------\nSELECT\n         i_item_id ,\n         i_item_desc ,\n         i_category ,\n         i_class ,\n         i_current_price ,\n         Sum(ws_ext_sales_price)                                                              AS itemrevenue ,\n         Sum(ws_ext_sales_price)*100/Sum(Sum(ws_ext_sales_price)) OVER (partition BY i_class) AS revenueratio\nFROM     web_sales ,\n         item ,\n         date_dim\nWHERE    ws_item_sk = i_item_sk\nAND      i_category IN ('Home',\n                        'Men',\n                        'Women')\nAND      ws_sold_date_sk = d_date_sk\nAND      d_date BETWEEN Cast('2000-05-11' AS DATE) AND      (\n                  Cast('2000-05-11' AS DATE) + INTERVAL '30' day)\nGROUP BY i_item_id ,\n         i_item_desc ,\n         i_category ,\n         i_class ,\n         i_current_price\nORDER BY i_category ,\n         i_class ,\n         i_item_id ,\n         i_item_desc ,\n         revenueratio\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"item\".\"i_category\" AS \"i_category\",\n  \"item\".\"i_class\" AS \"i_class\",\n  \"item\".\"i_current_price\" AS \"i_current_price\",\n  SUM(\"web_sales\".\"ws_ext_sales_price\") AS \"itemrevenue\",\n  SUM(\"web_sales\".\"ws_ext_sales_price\") * 100 / SUM(SUM(\"web_sales\".\"ws_ext_sales_price\")) OVER (PARTITION BY \"item\".\"i_class\") AS \"revenueratio\"\nFROM \"web_sales\" AS \"web_sales\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2000-06-10' AS DATE)\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2000-05-11' AS DATE)\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_category\" IN ('Home', 'Men', 'Women')\n  AND \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\nGROUP BY\n  \"item\".\"i_item_id\",\n  \"item\".\"i_item_desc\",\n  \"item\".\"i_category\",\n  \"item\".\"i_class\",\n  \"item\".\"i_current_price\"\nORDER BY\n  \"i_category\",\n  \"i_class\",\n  \"i_item_id\",\n  \"i_item_desc\",\n  \"revenueratio\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 13\n--------------------------------------\nSELECT Avg(ss_quantity),\n       Avg(ss_ext_sales_price),\n       Avg(ss_ext_wholesale_cost),\n       Sum(ss_ext_wholesale_cost)\nFROM   store_sales,\n       store,\n       customer_demographics,\n       household_demographics,\n       customer_address,\n       date_dim\nWHERE  s_store_sk = ss_store_sk\n       AND ss_sold_date_sk = d_date_sk\n       AND d_year = 2001\n       AND ( ( ss_hdemo_sk = hd_demo_sk\n               AND cd_demo_sk = ss_cdemo_sk\n               AND cd_marital_status = 'U'\n               AND cd_education_status = 'Advanced Degree'\n               AND ss_sales_price BETWEEN 100.00 AND 150.00\n               AND hd_dep_count = 3 )\n              OR ( ss_hdemo_sk = hd_demo_sk\n                   AND cd_demo_sk = ss_cdemo_sk\n                   AND cd_marital_status = 'M'\n                   AND cd_education_status = 'Primary'\n                   AND ss_sales_price BETWEEN 50.00 AND 100.00\n                   AND hd_dep_count = 1 )\n              OR ( ss_hdemo_sk = hd_demo_sk\n                   AND cd_demo_sk = ss_cdemo_sk\n                   AND cd_marital_status = 'D'\n                   AND cd_education_status = 'Secondary'\n                   AND ss_sales_price BETWEEN 150.00 AND 200.00\n                   AND hd_dep_count = 1 ) )\n       AND ( ( ss_addr_sk = ca_address_sk\n               AND ca_country = 'United States'\n               AND ca_state IN ( 'AZ', 'NE', 'IA' )\n               AND ss_net_profit BETWEEN 100 AND 200 )\n              OR ( ss_addr_sk = ca_address_sk\n                   AND ca_country = 'United States'\n                   AND ca_state IN ( 'MS', 'CA', 'NV' )\n                   AND ss_net_profit BETWEEN 150 AND 300 )\n              OR ( ss_addr_sk = ca_address_sk\n                   AND ca_country = 'United States'\n                   AND ca_state IN ( 'GA', 'TX', 'NJ' )\n                   AND ss_net_profit BETWEEN 50 AND 250 ) );\nSELECT\n  AVG(\"store_sales\".\"ss_quantity\") AS \"_col_0\",\n  AVG(\"store_sales\".\"ss_ext_sales_price\") AS \"_col_1\",\n  AVG(\"store_sales\".\"ss_ext_wholesale_cost\") AS \"_col_2\",\n  SUM(\"store_sales\".\"ss_ext_wholesale_cost\") AS \"_col_3\"\nFROM \"store_sales\" AS \"store_sales\"\nCROSS JOIN \"household_demographics\" AS \"household_demographics\"\nJOIN \"customer_address\" AS \"customer_address\"\n  ON (\n    \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n    AND \"customer_address\".\"ca_country\" = 'United States'\n    AND \"customer_address\".\"ca_state\" IN ('AZ', 'NE', 'IA')\n    AND \"store_sales\".\"ss_net_profit\" <= 200\n    AND \"store_sales\".\"ss_net_profit\" >= 100\n  )\n  OR (\n    \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n    AND \"customer_address\".\"ca_country\" = 'United States'\n    AND \"customer_address\".\"ca_state\" IN ('GA', 'TX', 'NJ')\n    AND \"store_sales\".\"ss_net_profit\" <= 250\n    AND \"store_sales\".\"ss_net_profit\" >= 50\n  )\n  OR (\n    \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n    AND \"customer_address\".\"ca_country\" = 'United States'\n    AND \"customer_address\".\"ca_state\" IN ('MS', 'CA', 'NV')\n    AND \"store_sales\".\"ss_net_profit\" <= 300\n    AND \"store_sales\".\"ss_net_profit\" >= 150\n  )\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON (\n    \"customer_demographics\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n    AND \"customer_demographics\".\"cd_education_status\" = 'Advanced Degree'\n    AND \"customer_demographics\".\"cd_marital_status\" = 'U'\n    AND \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n    AND \"household_demographics\".\"hd_dep_count\" = 3\n    AND \"store_sales\".\"ss_sales_price\" <= 150.00\n    AND \"store_sales\".\"ss_sales_price\" >= 100.00\n  )\n  OR (\n    \"customer_demographics\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n    AND \"customer_demographics\".\"cd_education_status\" = 'Primary'\n    AND \"customer_demographics\".\"cd_marital_status\" = 'M'\n    AND \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n    AND \"household_demographics\".\"hd_dep_count\" = 1\n    AND \"store_sales\".\"ss_sales_price\" <= 100.00\n    AND \"store_sales\".\"ss_sales_price\" >= 50.00\n  )\n  OR (\n    \"customer_demographics\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n    AND \"customer_demographics\".\"cd_education_status\" = 'Secondary'\n    AND \"customer_demographics\".\"cd_marital_status\" = 'D'\n    AND \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n    AND \"household_demographics\".\"hd_dep_count\" = 1\n    AND \"store_sales\".\"ss_sales_price\" <= 200.00\n    AND \"store_sales\".\"ss_sales_price\" >= 150.00\n  )\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"date_dim\".\"d_year\" = 2001\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\";\n\n--------------------------------------\n-- TPC-DS 14\n--------------------------------------\nWITH cross_items\n     AS (SELECT i_item_sk ss_item_sk\n         FROM   item,\n                (SELECT iss.i_brand_id    brand_id,\n                        iss.i_class_id    class_id,\n                        iss.i_category_id category_id\n                 FROM   store_sales,\n                        item iss,\n                        date_dim d1\n                 WHERE  ss_item_sk = iss.i_item_sk\n                        AND ss_sold_date_sk = d1.d_date_sk\n                        AND d1.d_year BETWEEN 1999 AND 1999 + 2\n                 INTERSECT\n                 SELECT ics.i_brand_id,\n                        ics.i_class_id,\n                        ics.i_category_id\n                 FROM   catalog_sales,\n                        item ics,\n                        date_dim d2\n                 WHERE  cs_item_sk = ics.i_item_sk\n                        AND cs_sold_date_sk = d2.d_date_sk\n                        AND d2.d_year BETWEEN 1999 AND 1999 + 2\n                 INTERSECT\n                 SELECT iws.i_brand_id,\n                        iws.i_class_id,\n                        iws.i_category_id\n                 FROM   web_sales,\n                        item iws,\n                        date_dim d3\n                 WHERE  ws_item_sk = iws.i_item_sk\n                        AND ws_sold_date_sk = d3.d_date_sk\n                        AND d3.d_year BETWEEN 1999 AND 1999 + 2)\n         WHERE  i_brand_id = brand_id\n                AND i_class_id = class_id\n                AND i_category_id = category_id),\n     avg_sales\n     AS (SELECT Avg(quantity * list_price) average_sales\n         FROM   (SELECT ss_quantity   quantity,\n                        ss_list_price list_price\n                 FROM   store_sales,\n                        date_dim\n                 WHERE  ss_sold_date_sk = d_date_sk\n                        AND d_year BETWEEN 1999 AND 1999 + 2\n                 UNION ALL\n                 SELECT cs_quantity   quantity,\n                        cs_list_price list_price\n                 FROM   catalog_sales,\n                        date_dim\n                 WHERE  cs_sold_date_sk = d_date_sk\n                        AND d_year BETWEEN 1999 AND 1999 + 2\n                 UNION ALL\n                 SELECT ws_quantity   quantity,\n                        ws_list_price list_price\n                 FROM   web_sales,\n                        date_dim\n                 WHERE  ws_sold_date_sk = d_date_sk\n                        AND d_year BETWEEN 1999 AND 1999 + 2) x)\nSELECT channel,\n               i_brand_id,\n               i_class_id,\n               i_category_id,\n               Sum(sales),\n               Sum(number_sales)\nFROM  (SELECT 'store'                          channel,\n              i_brand_id,\n              i_class_id,\n              i_category_id,\n              Sum(ss_quantity * ss_list_price) sales,\n              Count(*)                         number_sales\n       FROM   store_sales,\n              item,\n              date_dim\n       WHERE  ss_item_sk IN (SELECT ss_item_sk\n                             FROM   cross_items)\n              AND ss_item_sk = i_item_sk\n              AND ss_sold_date_sk = d_date_sk\n              AND d_year = 1999 + 2\n              AND d_moy = 11\n       GROUP  BY i_brand_id,\n                 i_class_id,\n                 i_category_id\n       HAVING Sum(ss_quantity * ss_list_price) > (SELECT average_sales\n                                                  FROM   avg_sales)\n       UNION ALL\n       SELECT 'catalog'                        channel,\n              i_brand_id,\n              i_class_id,\n              i_category_id,\n              Sum(cs_quantity * cs_list_price) sales,\n              Count(*)                         number_sales\n       FROM   catalog_sales,\n              item,\n              date_dim\n       WHERE  cs_item_sk IN (SELECT ss_item_sk\n                             FROM   cross_items)\n              AND cs_item_sk = i_item_sk\n              AND cs_sold_date_sk = d_date_sk\n              AND d_year = 1999 + 2\n              AND d_moy = 11\n       GROUP  BY i_brand_id,\n                 i_class_id,\n                 i_category_id\n       HAVING Sum(cs_quantity * cs_list_price) > (SELECT average_sales\n                                                  FROM   avg_sales)\n       UNION ALL\n       SELECT 'web'                            channel,\n              i_brand_id,\n              i_class_id,\n              i_category_id,\n              Sum(ws_quantity * ws_list_price) sales,\n              Count(*)                         number_sales\n       FROM   web_sales,\n              item,\n              date_dim\n       WHERE  ws_item_sk IN (SELECT ss_item_sk\n                             FROM   cross_items)\n              AND ws_item_sk = i_item_sk\n              AND ws_sold_date_sk = d_date_sk\n              AND d_year = 1999 + 2\n              AND d_moy = 11\n       GROUP  BY i_brand_id,\n                 i_class_id,\n                 i_category_id\n       HAVING Sum(ws_quantity * ws_list_price) > (SELECT average_sales\n                                                  FROM   avg_sales)) y\nGROUP  BY rollup ( channel, i_brand_id, i_class_id, i_category_id )\nORDER  BY channel,\n          i_brand_id,\n          i_class_id,\n          i_category_id\nLIMIT 100;\nWITH \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_brand_id\" AS \"i_brand_id\",\n    \"item\".\"i_class_id\" AS \"i_class_id\",\n    \"item\".\"i_category_id\" AS \"i_category_id\"\n  FROM \"item\" AS \"item\"\n), \"_0\" AS (\n  SELECT\n    \"iss\".\"i_brand_id\" AS \"brand_id\",\n    \"iss\".\"i_class_id\" AS \"class_id\",\n    \"iss\".\"i_category_id\" AS \"category_id\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim\" AS \"d1\"\n    ON \"d1\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"d1\".\"d_year\" <= 2001\n    AND \"d1\".\"d_year\" >= 1999\n  JOIN \"item\" AS \"iss\"\n    ON \"iss\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  INTERSECT\n  SELECT\n    \"ics\".\"i_brand_id\" AS \"i_brand_id\",\n    \"ics\".\"i_class_id\" AS \"i_class_id\",\n    \"ics\".\"i_category_id\" AS \"i_category_id\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim\" AS \"d2\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"d2\".\"d_date_sk\"\n    AND \"d2\".\"d_year\" <= 2001\n    AND \"d2\".\"d_year\" >= 1999\n  JOIN \"item\" AS \"ics\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"ics\".\"i_item_sk\"\n  INTERSECT\n  SELECT\n    \"iws\".\"i_brand_id\" AS \"i_brand_id\",\n    \"iws\".\"i_class_id\" AS \"i_class_id\",\n    \"iws\".\"i_category_id\" AS \"i_category_id\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim\" AS \"d3\"\n    ON \"d3\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n    AND \"d3\".\"d_year\" <= 2001\n    AND \"d3\".\"d_year\" >= 1999\n  JOIN \"item\" AS \"iws\"\n    ON \"iws\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_year\" <= 2001 AND \"date_dim\".\"d_year\" >= 1999\n), \"x\" AS (\n  SELECT\n    \"store_sales\".\"ss_quantity\" AS \"quantity\",\n    \"store_sales\".\"ss_list_price\" AS \"list_price\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  UNION ALL\n  SELECT\n    \"catalog_sales\".\"cs_quantity\" AS \"quantity\",\n    \"catalog_sales\".\"cs_list_price\" AS \"list_price\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  UNION ALL\n  SELECT\n    \"web_sales\".\"ws_quantity\" AS \"quantity\",\n    \"web_sales\".\"ws_list_price\" AS \"list_price\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n), \"avg_sales\" AS (\n  SELECT\n    AVG(\"x\".\"quantity\" * \"x\".\"list_price\") AS \"average_sales\"\n  FROM \"x\" AS \"x\"\n), \"date_dim_3\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 11 AND \"date_dim\".\"d_year\" = 2001\n), \"_u_0\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"ss_item_sk\"\n  FROM \"item_2\" AS \"item\"\n  JOIN \"_0\" AS \"_0\"\n    ON \"_0\".\"brand_id\" = \"item\".\"i_brand_id\"\n    AND \"_0\".\"category_id\" = \"item\".\"i_category_id\"\n    AND \"_0\".\"class_id\" = \"item\".\"i_class_id\"\n  GROUP BY\n    \"item\".\"i_item_sk\"\n), \"_u_1\" AS (\n  SELECT\n    \"avg_sales\".\"average_sales\" AS \"average_sales\"\n  FROM \"avg_sales\" AS \"avg_sales\"\n), \"y\" AS (\n  SELECT\n    'store' AS \"channel\",\n    \"item\".\"i_brand_id\" AS \"i_brand_id\",\n    \"item\".\"i_class_id\" AS \"i_class_id\",\n    \"item\".\"i_category_id\" AS \"i_category_id\",\n    SUM(\"store_sales\".\"ss_quantity\" * \"store_sales\".\"ss_list_price\") AS \"sales\",\n    COUNT(*) AS \"number_sales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"date_dim_3\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_0\"\n    ON \"_u_0\".\"ss_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  CROSS JOIN \"_u_1\" AS \"_u_1\"\n  WHERE\n    NOT \"_u_0\".\"ss_item_sk\" IS NULL\n  GROUP BY\n    \"item\".\"i_brand_id\",\n    \"item\".\"i_class_id\",\n    \"item\".\"i_category_id\"\n  HAVING\n    MAX(\"_u_1\".\"average_sales\") < SUM(\"store_sales\".\"ss_quantity\" * \"store_sales\".\"ss_list_price\")\n  UNION ALL\n  SELECT\n    'catalog' AS \"channel\",\n    \"item\".\"i_brand_id\" AS \"i_brand_id\",\n    \"item\".\"i_class_id\" AS \"i_class_id\",\n    \"item\".\"i_category_id\" AS \"i_category_id\",\n    SUM(\"catalog_sales\".\"cs_quantity\" * \"catalog_sales\".\"cs_list_price\") AS \"sales\",\n    COUNT(*) AS \"number_sales\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  JOIN \"date_dim_3\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_2\"\n    ON \"_u_2\".\"ss_item_sk\" = \"catalog_sales\".\"cs_item_sk\"\n  CROSS JOIN \"_u_1\" AS \"_u_3\"\n  WHERE\n    NOT \"_u_2\".\"ss_item_sk\" IS NULL\n  GROUP BY\n    \"item\".\"i_brand_id\",\n    \"item\".\"i_class_id\",\n    \"item\".\"i_category_id\"\n  HAVING\n    MAX(\"_u_3\".\"average_sales\") < SUM(\"catalog_sales\".\"cs_quantity\" * \"catalog_sales\".\"cs_list_price\")\n  UNION ALL\n  SELECT\n    'web' AS \"channel\",\n    \"item\".\"i_brand_id\" AS \"i_brand_id\",\n    \"item\".\"i_class_id\" AS \"i_class_id\",\n    \"item\".\"i_category_id\" AS \"i_category_id\",\n    SUM(\"web_sales\".\"ws_quantity\" * \"web_sales\".\"ws_list_price\") AS \"sales\",\n    COUNT(*) AS \"number_sales\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  JOIN \"date_dim_3\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_4\"\n    ON \"_u_4\".\"ss_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  CROSS JOIN \"_u_1\" AS \"_u_5\"\n  WHERE\n    NOT \"_u_4\".\"ss_item_sk\" IS NULL\n  GROUP BY\n    \"item\".\"i_brand_id\",\n    \"item\".\"i_class_id\",\n    \"item\".\"i_category_id\"\n  HAVING\n    MAX(\"_u_5\".\"average_sales\") < SUM(\"web_sales\".\"ws_quantity\" * \"web_sales\".\"ws_list_price\")\n)\nSELECT\n  \"y\".\"channel\" AS \"channel\",\n  \"y\".\"i_brand_id\" AS \"i_brand_id\",\n  \"y\".\"i_class_id\" AS \"i_class_id\",\n  \"y\".\"i_category_id\" AS \"i_category_id\",\n  SUM(\"y\".\"sales\") AS \"_col_4\",\n  SUM(\"y\".\"number_sales\") AS \"_col_5\"\nFROM \"y\" AS \"y\"\nGROUP BY\n  ROLLUP (\n    \"y\".\"channel\",\n    \"y\".\"i_brand_id\",\n    \"y\".\"i_class_id\",\n    \"y\".\"i_category_id\"\n  )\nORDER BY\n  \"channel\",\n  \"i_brand_id\",\n  \"i_class_id\",\n  \"i_category_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 15\n--------------------------------------\n# execute: true\nSELECT ca_zip,\n               Sum(cs_sales_price) AS \"_col_1\"\nFROM   catalog_sales,\n       customer,\n       customer_address,\n       date_dim\nWHERE  cs_bill_customer_sk = c_customer_sk\n       AND c_current_addr_sk = ca_address_sk\n       AND ( SUBSTRING(ca_zip, 1, 5) IN ( '85669', '86197', '88274', '83405',\n                                       '86475', '85392', '85460', '80348',\n                                       '81792' )\n              OR ca_state IN ( 'CA', 'WA', 'GA' )\n              OR cs_sales_price > 500 )\n       AND cs_sold_date_sk = d_date_sk\n       AND d_qoy = 1\n       AND d_year = 1998\nGROUP  BY ca_zip\nORDER  BY ca_zip\nLIMIT 100;\nSELECT\n  \"customer_address\".\"ca_zip\" AS \"ca_zip\",\n  SUM(\"catalog_sales\".\"cs_sales_price\") AS \"_col_1\"\nFROM \"catalog_sales\" AS \"catalog_sales\"\nJOIN \"customer\" AS \"customer\"\n  ON \"catalog_sales\".\"cs_bill_customer_sk\" = \"customer\".\"c_customer_sk\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  AND \"date_dim\".\"d_qoy\" = 1\n  AND \"date_dim\".\"d_year\" = 1998\nJOIN \"customer_address\" AS \"customer_address\"\n  ON (\n    \"catalog_sales\".\"cs_sales_price\" > 500\n    OR \"customer_address\".\"ca_state\" IN ('CA', 'WA', 'GA')\n    OR SUBSTRING(\"customer_address\".\"ca_zip\", 1, 5) IN ('85669', '86197', '88274', '83405', '86475', '85392', '85460', '80348', '81792')\n  )\n  AND \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\nGROUP BY\n  \"customer_address\".\"ca_zip\"\nORDER BY\n  \"ca_zip\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 16\n--------------------------------------\nSELECT\n         Count(DISTINCT cs_order_number) AS \"order count\" ,\n         Sum(cs_ext_ship_cost)           AS \"total shipping cost\" ,\n         Sum(cs_net_profit)              AS \"total net profit\"\nFROM     catalog_sales cs1 ,\n         date_dim ,\n         customer_address ,\n         call_center\nWHERE    d_date BETWEEN '2002-3-01' AND      (\n                  Cast('2002-3-01' AS DATE) + INTERVAL '60' day)\nAND      cs1.cs_ship_date_sk = d_date_sk\nAND      cs1.cs_ship_addr_sk = ca_address_sk\nAND      ca_state = 'IA'\nAND      cs1.cs_call_center_sk = cc_call_center_sk\nAND      cc_county IN ('Williamson County',\n                       'Williamson County',\n                       'Williamson County',\n                       'Williamson County',\n                       'Williamson County' )\nAND      EXISTS\n         (\n                SELECT *\n                FROM   catalog_sales cs2\n                WHERE  cs1.cs_order_number = cs2.cs_order_number\n                AND    cs1.cs_warehouse_sk <> cs2.cs_warehouse_sk)\nAND      NOT EXISTS\n         (\n                SELECT *\n                FROM   catalog_returns cr1\n                WHERE  cs1.cs_order_number = cr1.cr_order_number)\nORDER BY count(DISTINCT cs_order_number)\nLIMIT 100;\nWITH \"_u_0\" AS (\n  SELECT\n    \"cs2\".\"cs_order_number\" AS \"_u_1\",\n    ARRAY_AGG(\"cs2\".\"cs_warehouse_sk\") AS \"_u_2\"\n  FROM \"catalog_sales\" AS \"cs2\"\n  GROUP BY\n    \"cs2\".\"cs_order_number\"\n), \"_u_3\" AS (\n  SELECT\n    \"cr1\".\"cr_order_number\" AS \"_u_4\"\n  FROM \"catalog_returns\" AS \"cr1\"\n  GROUP BY\n    \"cr1\".\"cr_order_number\"\n)\nSELECT\n  COUNT(DISTINCT \"cs1\".\"cs_order_number\") AS \"order count\",\n  SUM(\"cs1\".\"cs_ext_ship_cost\") AS \"total shipping cost\",\n  SUM(\"cs1\".\"cs_net_profit\") AS \"total net profit\"\nFROM \"catalog_sales\" AS \"cs1\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"cs1\".\"cs_ship_date_sk\" = \"date_dim\".\"d_date_sk\"\n  AND \"date_dim\".\"d_date\" >= '2002-3-01'\n  AND (\n    CAST('2002-3-01' AS DATE) + INTERVAL '60' DAY\n  ) >= CAST(\"date_dim\".\"d_date\" AS DATE)\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"cs1\".\"cs_ship_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n  AND \"customer_address\".\"ca_state\" = 'IA'\nJOIN \"call_center\" AS \"call_center\"\n  ON \"call_center\".\"cc_call_center_sk\" = \"cs1\".\"cs_call_center_sk\"\n  AND \"call_center\".\"cc_county\" IN (\n    'Williamson County',\n    'Williamson County',\n    'Williamson County',\n    'Williamson County',\n    'Williamson County'\n  )\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"cs1\".\"cs_order_number\"\nLEFT JOIN \"_u_3\" AS \"_u_3\"\n  ON \"_u_3\".\"_u_4\" = \"cs1\".\"cs_order_number\"\nWHERE\n  \"_u_3\".\"_u_4\" IS NULL\n  AND ARRAY_ANY(\"_u_0\".\"_u_2\", \"_x\" -> \"cs1\".\"cs_warehouse_sk\" <> \"_x\")\n  AND NOT \"_u_0\".\"_u_1\" IS NULL\nORDER BY\n  COUNT(DISTINCT \"cs1\".\"cs_order_number\")\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 17\n--------------------------------------\n# execute: true\nSELECT i_item_id,\n               i_item_desc,\n               s_state,\n               Count(ss_quantity)                                        AS\n               store_sales_quantitycount,\n               Avg(ss_quantity)                                          AS\n               store_sales_quantityave,\n               Stddev_samp(ss_quantity)                                  AS\n               store_sales_quantitystdev,\n               Stddev_samp(ss_quantity) / Avg(ss_quantity)               AS\n               store_sales_quantitycov,\n               Count(sr_return_quantity)                                 AS\n               store_returns_quantitycount,\n               Avg(sr_return_quantity)                                   AS\n               store_returns_quantityave,\n               Stddev_samp(sr_return_quantity)                           AS\n               store_returns_quantitystdev,\n               Stddev_samp(sr_return_quantity) / Avg(sr_return_quantity) AS\n               store_returns_quantitycov,\n               Count(cs_quantity)                                        AS\n               catalog_sales_quantitycount,\n               Avg(cs_quantity)                                          AS\n               catalog_sales_quantityave,\n               Stddev_samp(cs_quantity) / Avg(cs_quantity)               AS\n               catalog_sales_quantitystdev,\n               Stddev_samp(cs_quantity) / Avg(cs_quantity)               AS\n               catalog_sales_quantitycov\nFROM   store_sales,\n       store_returns,\n       catalog_sales,\n       date_dim d1,\n       date_dim d2,\n       date_dim d3,\n       store,\n       item\nWHERE  d1.d_quarter_name = '1999Q1'\n       AND d1.d_date_sk = ss_sold_date_sk\n       AND i_item_sk = ss_item_sk\n       AND s_store_sk = ss_store_sk\n       AND ss_customer_sk = sr_customer_sk\n       AND ss_item_sk = sr_item_sk\n       AND ss_ticket_number = sr_ticket_number\n       AND sr_returned_date_sk = d2.d_date_sk\n       AND d2.d_quarter_name IN ( '1999Q1', '1999Q2', '1999Q3' )\n       AND sr_customer_sk = cs_bill_customer_sk\n       AND sr_item_sk = cs_item_sk\n       AND cs_sold_date_sk = d3.d_date_sk\n       AND d3.d_quarter_name IN ( '1999Q1', '1999Q2', '1999Q3' )\nGROUP  BY i_item_id,\n          i_item_desc,\n          s_state\nORDER  BY i_item_id,\n          i_item_desc,\n          s_state\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"store\".\"s_state\" AS \"s_state\",\n  COUNT(\"store_sales\".\"ss_quantity\") AS \"store_sales_quantitycount\",\n  AVG(\"store_sales\".\"ss_quantity\") AS \"store_sales_quantityave\",\n  STDDEV_SAMP(\"store_sales\".\"ss_quantity\") AS \"store_sales_quantitystdev\",\n  STDDEV_SAMP(\"store_sales\".\"ss_quantity\") / AVG(\"store_sales\".\"ss_quantity\") AS \"store_sales_quantitycov\",\n  COUNT(\"store_returns\".\"sr_return_quantity\") AS \"store_returns_quantitycount\",\n  AVG(\"store_returns\".\"sr_return_quantity\") AS \"store_returns_quantityave\",\n  STDDEV_SAMP(\"store_returns\".\"sr_return_quantity\") AS \"store_returns_quantitystdev\",\n  STDDEV_SAMP(\"store_returns\".\"sr_return_quantity\") / AVG(\"store_returns\".\"sr_return_quantity\") AS \"store_returns_quantitycov\",\n  COUNT(\"catalog_sales\".\"cs_quantity\") AS \"catalog_sales_quantitycount\",\n  AVG(\"catalog_sales\".\"cs_quantity\") AS \"catalog_sales_quantityave\",\n  STDDEV_SAMP(\"catalog_sales\".\"cs_quantity\") / AVG(\"catalog_sales\".\"cs_quantity\") AS \"catalog_sales_quantitystdev\",\n  STDDEV_SAMP(\"catalog_sales\".\"cs_quantity\") / AVG(\"catalog_sales\".\"cs_quantity\") AS \"catalog_sales_quantitycov\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"date_dim\" AS \"d1\"\n  ON \"d1\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"d1\".\"d_quarter_name\" = '1999Q1'\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nJOIN \"store_returns\" AS \"store_returns\"\n  ON \"store_returns\".\"sr_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  AND \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\nJOIN \"catalog_sales\" AS \"catalog_sales\"\n  ON \"catalog_sales\".\"cs_bill_customer_sk\" = \"store_returns\".\"sr_customer_sk\"\n  AND \"catalog_sales\".\"cs_item_sk\" = \"store_returns\".\"sr_item_sk\"\nJOIN \"date_dim\" AS \"d2\"\n  ON \"d2\".\"d_date_sk\" = \"store_returns\".\"sr_returned_date_sk\"\n  AND \"d2\".\"d_quarter_name\" IN ('1999Q1', '1999Q2', '1999Q3')\nJOIN \"date_dim\" AS \"d3\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"d3\".\"d_date_sk\"\n  AND \"d3\".\"d_quarter_name\" IN ('1999Q1', '1999Q2', '1999Q3')\nGROUP BY\n  \"item\".\"i_item_id\",\n  \"item\".\"i_item_desc\",\n  \"store\".\"s_state\"\nORDER BY\n  \"i_item_id\",\n  \"i_item_desc\",\n  \"s_state\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 18\n--------------------------------------\nSELECT i_item_id,\n               ca_country,\n               ca_state,\n               ca_county,\n               Avg(Cast(cs_quantity AS NUMERIC(12, 2)))      agg1,\n               Avg(Cast(cs_list_price AS NUMERIC(12, 2)))    agg2,\n               Avg(Cast(cs_coupon_amt AS NUMERIC(12, 2)))    agg3,\n               Avg(Cast(cs_sales_price AS NUMERIC(12, 2)))   agg4,\n               Avg(Cast(cs_net_profit AS NUMERIC(12, 2)))    agg5,\n               Avg(Cast(c_birth_year AS NUMERIC(12, 2)))     agg6,\n               Avg(Cast(cd1.cd_dep_count AS NUMERIC(12, 2))) agg7\nFROM   catalog_sales,\n       customer_demographics cd1,\n       customer_demographics cd2,\n       customer,\n       customer_address,\n       date_dim,\n       item\nWHERE  cs_sold_date_sk = d_date_sk\n       AND cs_item_sk = i_item_sk\n       AND cs_bill_cdemo_sk = cd1.cd_demo_sk\n       AND cs_bill_customer_sk = c_customer_sk\n       AND cd1.cd_gender = 'F'\n       AND cd1.cd_education_status = 'Secondary'\n       AND c_current_cdemo_sk = cd2.cd_demo_sk\n       AND c_current_addr_sk = ca_address_sk\n       AND c_birth_month IN ( 8, 4, 2, 5,\n                              11, 9 )\n       AND d_year = 2001\n       AND ca_state IN ( 'KS', 'IA', 'AL', 'UT',\n                         'VA', 'NC', 'TX' )\nGROUP  BY rollup ( i_item_id, ca_country, ca_state, ca_county )\nORDER  BY ca_country,\n          ca_state,\n          ca_county,\n          i_item_id\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"customer_address\".\"ca_country\" AS \"ca_country\",\n  \"customer_address\".\"ca_state\" AS \"ca_state\",\n  \"customer_address\".\"ca_county\" AS \"ca_county\",\n  AVG(CAST(\"catalog_sales\".\"cs_quantity\" AS DECIMAL(12, 2))) AS \"agg1\",\n  AVG(CAST(\"catalog_sales\".\"cs_list_price\" AS DECIMAL(12, 2))) AS \"agg2\",\n  AVG(CAST(\"catalog_sales\".\"cs_coupon_amt\" AS DECIMAL(12, 2))) AS \"agg3\",\n  AVG(CAST(\"catalog_sales\".\"cs_sales_price\" AS DECIMAL(12, 2))) AS \"agg4\",\n  AVG(CAST(\"catalog_sales\".\"cs_net_profit\" AS DECIMAL(12, 2))) AS \"agg5\",\n  AVG(CAST(\"customer\".\"c_birth_year\" AS DECIMAL(12, 2))) AS \"agg6\",\n  AVG(CAST(\"cd1\".\"cd_dep_count\" AS DECIMAL(12, 2))) AS \"agg7\"\nFROM \"catalog_sales\" AS \"catalog_sales\"\nJOIN \"customer_demographics\" AS \"cd1\"\n  ON \"catalog_sales\".\"cs_bill_cdemo_sk\" = \"cd1\".\"cd_demo_sk\"\n  AND \"cd1\".\"cd_education_status\" = 'Secondary'\n  AND \"cd1\".\"cd_gender\" = 'F'\nJOIN \"customer\" AS \"customer\"\n  ON \"catalog_sales\".\"cs_bill_customer_sk\" = \"customer\".\"c_customer_sk\"\n  AND \"customer\".\"c_birth_month\" IN (8, 4, 2, 5, 11, 9)\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  AND \"date_dim\".\"d_year\" = 2001\nJOIN \"item\" AS \"item\"\n  ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\nJOIN \"customer_demographics\" AS \"cd2\"\n  ON \"cd2\".\"cd_demo_sk\" = \"customer\".\"c_current_cdemo_sk\"\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n  AND \"customer_address\".\"ca_state\" IN ('KS', 'IA', 'AL', 'UT', 'VA', 'NC', 'TX')\nGROUP BY\n  ROLLUP (\n    \"item\".\"i_item_id\",\n    \"customer_address\".\"ca_country\",\n    \"customer_address\".\"ca_state\",\n    \"customer_address\".\"ca_county\"\n  )\nORDER BY\n  \"ca_country\",\n  \"ca_state\",\n  \"ca_county\",\n  \"i_item_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 19\n--------------------------------------\n# execute: true\nSELECT i_brand_id              brand_id,\n               i_brand                 brand,\n               i_manufact_id,\n               i_manufact,\n               Sum(ss_ext_sales_price) ext_price\nFROM   date_dim,\n       store_sales,\n       item,\n       customer,\n       customer_address,\n       store\nWHERE  d_date_sk = ss_sold_date_sk\n       AND ss_item_sk = i_item_sk\n       AND i_manager_id = 38\n       AND d_moy = 12\n       AND d_year = 1998\n       AND ss_customer_sk = c_customer_sk\n       AND c_current_addr_sk = ca_address_sk\n       AND SUBSTRING(ca_zip, 1, 5) <> SUBSTRING(s_zip, 1, 5)\n       AND ss_store_sk = s_store_sk\nGROUP  BY i_brand,\n          i_brand_id,\n          i_manufact_id,\n          i_manufact\nORDER  BY ext_price DESC,\n          i_brand,\n          i_brand_id,\n          i_manufact_id,\n          i_manufact\nLIMIT 100;\nSELECT\n  \"item\".\"i_brand_id\" AS \"brand_id\",\n  \"item\".\"i_brand\" AS \"brand\",\n  \"item\".\"i_manufact_id\" AS \"i_manufact_id\",\n  \"item\".\"i_manufact\" AS \"i_manufact\",\n  SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"ext_price\"\nFROM \"date_dim\" AS \"date_dim\"\nJOIN \"store_sales\" AS \"store_sales\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\" AND \"item\".\"i_manager_id\" = 38\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  AND SUBSTRING(\"customer_address\".\"ca_zip\", 1, 5) <> SUBSTRING(\"store\".\"s_zip\", 1, 5)\nWHERE\n  \"date_dim\".\"d_moy\" = 12 AND \"date_dim\".\"d_year\" = 1998\nGROUP BY\n  \"item\".\"i_brand\",\n  \"item\".\"i_brand_id\",\n  \"item\".\"i_manufact_id\",\n  \"item\".\"i_manufact\"\nORDER BY\n  \"ext_price\" DESC,\n  \"brand\",\n  \"brand_id\",\n  \"i_manufact_id\",\n  \"i_manufact\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 20\n--------------------------------------\nSELECT\n         i_item_id ,\n         i_item_desc ,\n         i_category ,\n         i_class ,\n         i_current_price ,\n         Sum(cs_ext_sales_price)                                                              AS itemrevenue ,\n         Sum(cs_ext_sales_price)*100/Sum(Sum(cs_ext_sales_price)) OVER (partition BY i_class) AS revenueratio\nFROM     catalog_sales ,\n         item ,\n         date_dim\nWHERE    cs_item_sk = i_item_sk\nAND      i_category IN ('Children',\n                        'Women',\n                        'Electronics')\nAND      cs_sold_date_sk = d_date_sk\nAND      d_date BETWEEN Cast('2001-02-03' AS DATE) AND      (\n                  Cast('2001-02-03' AS DATE) + INTERVAL '30' day)\nGROUP BY i_item_id ,\n         i_item_desc ,\n         i_category ,\n         i_class ,\n         i_current_price\nORDER BY i_category ,\n         i_class ,\n         i_item_id ,\n         i_item_desc ,\n         revenueratio\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"item\".\"i_category\" AS \"i_category\",\n  \"item\".\"i_class\" AS \"i_class\",\n  \"item\".\"i_current_price\" AS \"i_current_price\",\n  SUM(\"catalog_sales\".\"cs_ext_sales_price\") AS \"itemrevenue\",\n  SUM(\"catalog_sales\".\"cs_ext_sales_price\") * 100 / SUM(SUM(\"catalog_sales\".\"cs_ext_sales_price\")) OVER (PARTITION BY \"item\".\"i_class\") AS \"revenueratio\"\nFROM \"catalog_sales\" AS \"catalog_sales\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2001-03-05' AS DATE)\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2001-02-03' AS DATE)\nJOIN \"item\" AS \"item\"\n  ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  AND \"item\".\"i_category\" IN ('Children', 'Women', 'Electronics')\nGROUP BY\n  \"item\".\"i_item_id\",\n  \"item\".\"i_item_desc\",\n  \"item\".\"i_category\",\n  \"item\".\"i_class\",\n  \"item\".\"i_current_price\"\nORDER BY\n  \"i_category\",\n  \"i_class\",\n  \"i_item_id\",\n  \"i_item_desc\",\n  \"revenueratio\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 21\n--------------------------------------\nSELECT\n         *\nFROM    (\n                  SELECT   w_warehouse_name ,\n                           i_item_id ,\n                           Sum(\n                           CASE\n                                    WHEN (\n                                                      Cast(d_date AS DATE) < Cast ('2000-05-13' AS DATE)) THEN inv_quantity_on_hand\n                                    ELSE 0\n                           END) AS inv_before ,\n                           Sum(\n                           CASE\n                                    WHEN (\n                                                      Cast(d_date AS DATE) >= Cast ('2000-05-13' AS DATE)) THEN inv_quantity_on_hand\n                                    ELSE 0\n                           END) AS inv_after\n                  FROM     inventory ,\n                           warehouse ,\n                           item ,\n                           date_dim\n                  WHERE    i_current_price BETWEEN 0.99 AND      1.49\n                  AND      i_item_sk = inv_item_sk\n                  AND      inv_warehouse_sk = w_warehouse_sk\n                  AND      inv_date_sk = d_date_sk\n                  AND      d_date BETWEEN (Cast ('2000-05-13' AS DATE) - INTERVAL '30' day) AND      (\n                                    cast ('2000-05-13' AS        date) + INTERVAL '30' day)\n                  GROUP BY w_warehouse_name,\n                           i_item_id) x\nWHERE    (\n                  CASE\n                           WHEN inv_before > 0 THEN inv_after / inv_before\n                           ELSE NULL\n                  END) BETWEEN 2.0/3.0 AND      3.0/2.0\nORDER BY w_warehouse_name ,\n         i_item_id\nLIMIT 100;\nWITH \"x\" AS (\n  SELECT\n    \"warehouse\".\"w_warehouse_name\" AS \"w_warehouse_name\",\n    \"item\".\"i_item_id\" AS \"i_item_id\",\n    SUM(\n      CASE\n        WHEN CAST(\"date_dim\".\"d_date\" AS DATE) < CAST('2000-05-13' AS DATE)\n        THEN \"inventory\".\"inv_quantity_on_hand\"\n        ELSE 0\n      END\n    ) AS \"inv_before\",\n    SUM(\n      CASE\n        WHEN CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2000-05-13' AS DATE)\n        THEN \"inventory\".\"inv_quantity_on_hand\"\n        ELSE 0\n      END\n    ) AS \"inv_after\"\n  FROM \"inventory\" AS \"inventory\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"inventory\".\"inv_date_sk\"\n    AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2000-06-12' AS DATE)\n    AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2000-04-13' AS DATE)\n  JOIN \"item\" AS \"item\"\n    ON \"inventory\".\"inv_item_sk\" = \"item\".\"i_item_sk\"\n    AND \"item\".\"i_current_price\" <= 1.49\n    AND \"item\".\"i_current_price\" >= 0.99\n  JOIN \"warehouse\" AS \"warehouse\"\n    ON \"inventory\".\"inv_warehouse_sk\" = \"warehouse\".\"w_warehouse_sk\"\n  GROUP BY\n    \"warehouse\".\"w_warehouse_name\",\n    \"item\".\"i_item_id\"\n)\nSELECT\n  \"x\".\"w_warehouse_name\" AS \"w_warehouse_name\",\n  \"x\".\"i_item_id\" AS \"i_item_id\",\n  \"x\".\"inv_before\" AS \"inv_before\",\n  \"x\".\"inv_after\" AS \"inv_after\"\nFROM \"x\" AS \"x\"\nWHERE\n  CASE WHEN \"x\".\"inv_before\" > 0 THEN \"x\".\"inv_after\" / \"x\".\"inv_before\" ELSE NULL END <= 1.5\n  AND CASE WHEN \"x\".\"inv_before\" > 0 THEN \"x\".\"inv_after\" / \"x\".\"inv_before\" ELSE NULL END >= 0.6666666666666666666666666667\nORDER BY\n  \"x\".\"w_warehouse_name\",\n  \"x\".\"i_item_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 22\n--------------------------------------\nSELECT i_product_name,\n               i_brand,\n               i_class,\n               i_category,\n               Avg(inv_quantity_on_hand) qoh\nFROM   inventory,\n       date_dim,\n       item,\n       warehouse\nWHERE  inv_date_sk = d_date_sk\n       AND inv_item_sk = i_item_sk\n       AND inv_warehouse_sk = w_warehouse_sk\n       AND d_month_seq BETWEEN 1205 AND 1205 + 11\nGROUP  BY rollup( i_product_name, i_brand, i_class, i_category )\nORDER  BY qoh,\n          i_product_name,\n          i_brand,\n          i_class,\n          i_category\nLIMIT 100;\nSELECT\n  \"item\".\"i_product_name\" AS \"i_product_name\",\n  \"item\".\"i_brand\" AS \"i_brand\",\n  \"item\".\"i_class\" AS \"i_class\",\n  \"item\".\"i_category\" AS \"i_category\",\n  AVG(\"inventory\".\"inv_quantity_on_hand\") AS \"qoh\"\nFROM \"inventory\" AS \"inventory\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"inventory\".\"inv_date_sk\"\n  AND \"date_dim\".\"d_month_seq\" <= 1216\n  AND \"date_dim\".\"d_month_seq\" >= 1205\nJOIN \"item\" AS \"item\"\n  ON \"inventory\".\"inv_item_sk\" = \"item\".\"i_item_sk\"\nJOIN \"warehouse\" AS \"warehouse\"\n  ON \"inventory\".\"inv_warehouse_sk\" = \"warehouse\".\"w_warehouse_sk\"\nGROUP BY\n  ROLLUP (\n    \"item\".\"i_product_name\",\n    \"item\".\"i_brand\",\n    \"item\".\"i_class\",\n    \"item\".\"i_category\"\n  )\nORDER BY\n  \"qoh\",\n  \"i_product_name\",\n  \"i_brand\",\n  \"i_class\",\n  \"i_category\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 23\n--------------------------------------\n# execute: true\nWITH frequent_ss_items\n     AS (SELECT SUBSTRING(i_item_desc, 1, 30) itemdesc,\n                i_item_sk                  item_sk,\n                d_date                     solddate,\n                Count(*)                   cnt\n         FROM   store_sales,\n                date_dim,\n                item\n         WHERE  ss_sold_date_sk = d_date_sk\n                AND ss_item_sk = i_item_sk\n                AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 )\n         GROUP  BY SUBSTRING(i_item_desc, 1, 30),\n                   i_item_sk,\n                   d_date\n         HAVING Count(*) > 4),\n     max_store_sales\n     AS (SELECT Max(csales) tpcds_cmax\n         FROM   (SELECT c_customer_sk,\n                        Sum(ss_quantity * ss_sales_price) csales\n                 FROM   store_sales,\n                        customer,\n                        date_dim\n                 WHERE  ss_customer_sk = c_customer_sk\n                        AND ss_sold_date_sk = d_date_sk\n                        AND d_year IN ( 1998, 1998 + 1, 1998 + 2, 1998 + 3 )\n                 GROUP  BY c_customer_sk)),\n     best_ss_customer\n     AS (SELECT c_customer_sk,\n                Sum(ss_quantity * ss_sales_price) ssales\n         FROM   store_sales,\n                customer\n         WHERE  ss_customer_sk = c_customer_sk\n         GROUP  BY c_customer_sk\n         HAVING Sum(ss_quantity * ss_sales_price) >\n                ( 95 / 100.0 ) * (SELECT *\n                                  FROM   max_store_sales))\nSELECT Sum(sales) AS \"_col_0\"\nFROM   (SELECT cs_quantity * cs_list_price sales\n        FROM   catalog_sales,\n               date_dim\n        WHERE  d_year = 1998\n               AND d_moy = 6\n               AND cs_sold_date_sk = d_date_sk\n               AND cs_item_sk IN (SELECT item_sk\n                                  FROM   frequent_ss_items)\n               AND cs_bill_customer_sk IN (SELECT c_customer_sk\n                                           FROM   best_ss_customer)\n        UNION ALL\n        SELECT ws_quantity * ws_list_price sales\n        FROM   web_sales,\n               date_dim\n        WHERE  d_year = 1998\n               AND d_moy = 6\n               AND ws_sold_date_sk = d_date_sk\n               AND ws_item_sk IN (SELECT item_sk\n                                  FROM   frequent_ss_items)\n               AND ws_bill_customer_sk IN (SELECT c_customer_sk\n                                           FROM   best_ss_customer)) LIMIT 100;\nWITH \"frequent_ss_items\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"item_sk\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_year\" IN (1998, 1999, 2000, 2001)\n  JOIN \"item\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  GROUP BY\n    SUBSTRING(\"item\".\"i_item_desc\", 1, 30),\n    \"item\".\"i_item_sk\",\n    \"date_dim\".\"d_date\"\n  HAVING\n    COUNT(*) > 4\n), \"customer_2\" AS (\n  SELECT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\"\n  FROM \"customer\" AS \"customer\"\n), \"_0\" AS (\n  SELECT\n    SUM(\"store_sales\".\"ss_quantity\" * \"store_sales\".\"ss_sales_price\") AS \"csales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer_2\" AS \"customer\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_year\" IN (1998, 1999, 2000, 2001)\n  GROUP BY\n    \"customer\".\"c_customer_sk\"\n), \"max_store_sales\" AS (\n  SELECT\n    MAX(\"_0\".\"csales\") AS \"tpcds_cmax\"\n  FROM \"_0\" AS \"_0\"\n), \"best_ss_customer\" AS (\n  SELECT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\"\n  FROM \"store_sales\" AS \"store_sales\"\n  CROSS JOIN \"max_store_sales\" AS \"max_store_sales\"\n  JOIN \"customer_2\" AS \"customer\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  GROUP BY\n    \"customer\".\"c_customer_sk\"\n  HAVING\n    0.95 * MAX(\"max_store_sales\".\"tpcds_cmax\") < SUM(\"store_sales\".\"ss_quantity\" * \"store_sales\".\"ss_sales_price\")\n), \"date_dim_4\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 6 AND \"date_dim\".\"d_year\" = 1998\n), \"_u_1\" AS (\n  SELECT\n    \"frequent_ss_items\".\"item_sk\" AS \"item_sk\"\n  FROM \"frequent_ss_items\" AS \"frequent_ss_items\"\n  GROUP BY\n    \"frequent_ss_items\".\"item_sk\"\n), \"_u_2\" AS (\n  SELECT\n    \"best_ss_customer\".\"c_customer_sk\" AS \"c_customer_sk\"\n  FROM \"best_ss_customer\" AS \"best_ss_customer\"\n  GROUP BY\n    \"best_ss_customer\".\"c_customer_sk\"\n), \"_1\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_quantity\" * \"catalog_sales\".\"cs_list_price\" AS \"sales\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_4\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  LEFT JOIN \"_u_1\" AS \"_u_1\"\n    ON \"_u_1\".\"item_sk\" = \"catalog_sales\".\"cs_item_sk\"\n  LEFT JOIN \"_u_2\" AS \"_u_2\"\n    ON \"_u_2\".\"c_customer_sk\" = \"catalog_sales\".\"cs_bill_customer_sk\"\n  WHERE\n    NOT \"_u_1\".\"item_sk\" IS NULL AND NOT \"_u_2\".\"c_customer_sk\" IS NULL\n  UNION ALL\n  SELECT\n    \"web_sales\".\"ws_quantity\" * \"web_sales\".\"ws_list_price\" AS \"sales\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_4\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  LEFT JOIN \"_u_1\" AS \"_u_3\"\n    ON \"_u_3\".\"item_sk\" = \"web_sales\".\"ws_item_sk\"\n  LEFT JOIN \"_u_2\" AS \"_u_4\"\n    ON \"_u_4\".\"c_customer_sk\" = \"web_sales\".\"ws_bill_customer_sk\"\n  WHERE\n    NOT \"_u_3\".\"item_sk\" IS NULL AND NOT \"_u_4\".\"c_customer_sk\" IS NULL\n)\nSELECT\n  SUM(\"_1\".\"sales\") AS \"_col_0\"\nFROM \"_1\" AS \"_1\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 24\n--------------------------------------\n# execute: true\nWITH ssales\n     AS (SELECT c_last_name,\n                c_first_name,\n                s_store_name,\n                ca_state,\n                s_state,\n                i_color,\n                i_current_price,\n                i_manager_id,\n                i_units,\n                i_size,\n                Sum(ss_net_profit) netpaid\n         FROM   store_sales,\n                store_returns,\n                store,\n                item,\n                customer,\n                customer_address\n         WHERE  ss_ticket_number = sr_ticket_number\n                AND ss_item_sk = sr_item_sk\n                AND ss_customer_sk = c_customer_sk\n                AND ss_item_sk = i_item_sk\n                AND ss_store_sk = s_store_sk\n                AND c_birth_country = Upper(ca_country)\n                AND s_zip = ca_zip\n                AND s_market_id = 6\n         GROUP  BY c_last_name,\n                   c_first_name,\n                   s_store_name,\n                   ca_state,\n                   s_state,\n                   i_color,\n                   i_current_price,\n                   i_manager_id,\n                   i_units,\n                   i_size)\nSELECT c_last_name,\n       c_first_name,\n       s_store_name,\n       Sum(netpaid) paid\nFROM   ssales\nWHERE  i_color = 'papaya'\nGROUP  BY c_last_name,\n          c_first_name,\n          s_store_name\nHAVING Sum(netpaid) > (SELECT 0.05 * Avg(netpaid)\n                       FROM   ssales);\nWITH \"ssales\" AS (\n  SELECT\n    \"customer\".\"c_last_name\" AS \"c_last_name\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"store\".\"s_store_name\" AS \"s_store_name\",\n    \"item\".\"i_color\" AS \"i_color\",\n    SUM(\"store_sales\".\"ss_net_profit\") AS \"netpaid\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer\" AS \"customer\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"item\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_market_id\" = 6 AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"store_returns\" AS \"store_returns\"\n    ON \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n    AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\n  JOIN \"customer_address\" AS \"customer_address\"\n    ON \"customer\".\"c_birth_country\" = UPPER(\"customer_address\".\"ca_country\")\n    AND \"customer_address\".\"ca_zip\" = \"store\".\"s_zip\"\n  GROUP BY\n    \"customer\".\"c_last_name\",\n    \"customer\".\"c_first_name\",\n    \"store\".\"s_store_name\",\n    \"customer_address\".\"ca_state\",\n    \"store\".\"s_state\",\n    \"item\".\"i_color\",\n    \"item\".\"i_current_price\",\n    \"item\".\"i_manager_id\",\n    \"item\".\"i_units\",\n    \"item\".\"i_size\"\n), \"_u_0\" AS (\n  SELECT\n    0.05 * AVG(\"ssales\".\"netpaid\") AS \"_col_0\"\n  FROM \"ssales\" AS \"ssales\"\n)\nSELECT\n  \"ssales\".\"c_last_name\" AS \"c_last_name\",\n  \"ssales\".\"c_first_name\" AS \"c_first_name\",\n  \"ssales\".\"s_store_name\" AS \"s_store_name\",\n  SUM(\"ssales\".\"netpaid\") AS \"paid\"\nFROM \"ssales\" AS \"ssales\"\nCROSS JOIN \"_u_0\" AS \"_u_0\"\nWHERE\n  \"ssales\".\"i_color\" = 'papaya'\nGROUP BY\n  \"ssales\".\"c_last_name\",\n  \"ssales\".\"c_first_name\",\n  \"ssales\".\"s_store_name\"\nHAVING\n  MAX(\"_u_0\".\"_col_0\") < SUM(\"ssales\".\"netpaid\");\n\n--------------------------------------\n-- TPC-DS 25\n--------------------------------------\n# execute: true\nSELECT i_item_id,\n               i_item_desc,\n               s_store_id,\n               s_store_name,\n               Max(ss_net_profit) AS store_sales_profit,\n               Max(sr_net_loss)   AS store_returns_loss,\n               Max(cs_net_profit) AS catalog_sales_profit\nFROM   store_sales,\n       store_returns,\n       catalog_sales,\n       date_dim d1,\n       date_dim d2,\n       date_dim d3,\n       store,\n       item\nWHERE  d1.d_moy = 4\n       AND d1.d_year = 2001\n       AND d1.d_date_sk = ss_sold_date_sk\n       AND i_item_sk = ss_item_sk\n       AND s_store_sk = ss_store_sk\n       AND ss_customer_sk = sr_customer_sk\n       AND ss_item_sk = sr_item_sk\n       AND ss_ticket_number = sr_ticket_number\n       AND sr_returned_date_sk = d2.d_date_sk\n       AND d2.d_moy BETWEEN 4 AND 10\n       AND d2.d_year = 2001\n       AND sr_customer_sk = cs_bill_customer_sk\n       AND sr_item_sk = cs_item_sk\n       AND cs_sold_date_sk = d3.d_date_sk\n       AND d3.d_moy BETWEEN 4 AND 10\n       AND d3.d_year = 2001\nGROUP  BY i_item_id,\n          i_item_desc,\n          s_store_id,\n          s_store_name\nORDER  BY i_item_id,\n          i_item_desc,\n          s_store_id,\n          s_store_name\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"store\".\"s_store_id\" AS \"s_store_id\",\n  \"store\".\"s_store_name\" AS \"s_store_name\",\n  MAX(\"store_sales\".\"ss_net_profit\") AS \"store_sales_profit\",\n  MAX(\"store_returns\".\"sr_net_loss\") AS \"store_returns_loss\",\n  MAX(\"catalog_sales\".\"cs_net_profit\") AS \"catalog_sales_profit\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"date_dim\" AS \"d1\"\n  ON \"d1\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"d1\".\"d_moy\" = 4\n  AND \"d1\".\"d_year\" = 2001\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nJOIN \"store_returns\" AS \"store_returns\"\n  ON \"store_returns\".\"sr_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  AND \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\nJOIN \"catalog_sales\" AS \"catalog_sales\"\n  ON \"catalog_sales\".\"cs_bill_customer_sk\" = \"store_returns\".\"sr_customer_sk\"\n  AND \"catalog_sales\".\"cs_item_sk\" = \"store_returns\".\"sr_item_sk\"\nJOIN \"date_dim\" AS \"d2\"\n  ON \"d2\".\"d_date_sk\" = \"store_returns\".\"sr_returned_date_sk\"\n  AND \"d2\".\"d_moy\" <= 10\n  AND \"d2\".\"d_moy\" >= 4\n  AND \"d2\".\"d_year\" = 2001\nJOIN \"date_dim\" AS \"d3\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"d3\".\"d_date_sk\"\n  AND \"d3\".\"d_moy\" <= 10\n  AND \"d3\".\"d_moy\" >= 4\n  AND \"d3\".\"d_year\" = 2001\nGROUP BY\n  \"item\".\"i_item_id\",\n  \"item\".\"i_item_desc\",\n  \"store\".\"s_store_id\",\n  \"store\".\"s_store_name\"\nORDER BY\n  \"i_item_id\",\n  \"i_item_desc\",\n  \"s_store_id\",\n  \"s_store_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 26\n--------------------------------------\n# execute: true\nSELECT i_item_id,\n               Avg(cs_quantity)    agg1,\n               Avg(cs_list_price)  agg2,\n               Avg(cs_coupon_amt)  agg3,\n               Avg(cs_sales_price) agg4\nFROM   catalog_sales,\n       customer_demographics,\n       date_dim,\n       item,\n       promotion\nWHERE  cs_sold_date_sk = d_date_sk\n       AND cs_item_sk = i_item_sk\n       AND cs_bill_cdemo_sk = cd_demo_sk\n       AND cs_promo_sk = p_promo_sk\n       AND cd_gender = 'F'\n       AND cd_marital_status = 'W'\n       AND cd_education_status = 'Secondary'\n       AND ( p_channel_email = 'N'\n              OR p_channel_event = 'N' )\n       AND d_year = 2000\nGROUP  BY i_item_id\nORDER  BY i_item_id\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  AVG(\"catalog_sales\".\"cs_quantity\") AS \"agg1\",\n  AVG(\"catalog_sales\".\"cs_list_price\") AS \"agg2\",\n  AVG(\"catalog_sales\".\"cs_coupon_amt\") AS \"agg3\",\n  AVG(\"catalog_sales\".\"cs_sales_price\") AS \"agg4\"\nFROM \"catalog_sales\" AS \"catalog_sales\"\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"catalog_sales\".\"cs_bill_cdemo_sk\" = \"customer_demographics\".\"cd_demo_sk\"\n  AND \"customer_demographics\".\"cd_education_status\" = 'Secondary'\n  AND \"customer_demographics\".\"cd_gender\" = 'F'\n  AND \"customer_demographics\".\"cd_marital_status\" = 'W'\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  AND \"date_dim\".\"d_year\" = 2000\nJOIN \"item\" AS \"item\"\n  ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\nJOIN \"promotion\" AS \"promotion\"\n  ON \"catalog_sales\".\"cs_promo_sk\" = \"promotion\".\"p_promo_sk\"\n  AND (\n    \"promotion\".\"p_channel_email\" = 'N' OR \"promotion\".\"p_channel_event\" = 'N'\n  )\nGROUP BY\n  \"item\".\"i_item_id\"\nORDER BY\n  \"i_item_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 27\n--------------------------------------\nSELECT i_item_id,\n               s_state,\n               Grouping(s_state)   g_state,\n               Avg(ss_quantity)    agg1,\n               Avg(ss_list_price)  agg2,\n               Avg(ss_coupon_amt)  agg3,\n               Avg(ss_sales_price) agg4\nFROM   store_sales,\n       customer_demographics,\n       date_dim,\n       store,\n       item\nWHERE  ss_sold_date_sk = d_date_sk\n       AND ss_item_sk = i_item_sk\n       AND ss_store_sk = s_store_sk\n       AND ss_cdemo_sk = cd_demo_sk\n       AND cd_gender = 'M'\n       AND cd_marital_status = 'D'\n       AND cd_education_status = 'College'\n       AND d_year = 2000\n       AND s_state IN ( 'TN', 'TN', 'TN', 'TN',\n                        'TN', 'TN' )\nGROUP  BY rollup ( i_item_id, s_state )\nORDER  BY i_item_id,\n          s_state\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"store\".\"s_state\" AS \"s_state\",\n  GROUPING(\"store\".\"s_state\") AS \"g_state\",\n  AVG(\"store_sales\".\"ss_quantity\") AS \"agg1\",\n  AVG(\"store_sales\".\"ss_list_price\") AS \"agg2\",\n  AVG(\"store_sales\".\"ss_coupon_amt\") AS \"agg3\",\n  AVG(\"store_sales\".\"ss_sales_price\") AS \"agg4\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"customer_demographics\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n  AND \"customer_demographics\".\"cd_education_status\" = 'College'\n  AND \"customer_demographics\".\"cd_gender\" = 'M'\n  AND \"customer_demographics\".\"cd_marital_status\" = 'D'\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"date_dim\".\"d_year\" = 2000\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_state\" IN ('TN', 'TN', 'TN', 'TN', 'TN', 'TN')\n  AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nGROUP BY\n  ROLLUP (\n    \"item\".\"i_item_id\",\n    \"store\".\"s_state\"\n  )\nORDER BY\n  \"i_item_id\",\n  \"s_state\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 28\n--------------------------------------\nSELECT *\nFROM   (SELECT Avg(ss_list_price)            b1_lp,\n               Count(ss_list_price)          b1_cnt,\n               Count(DISTINCT ss_list_price) b1_cntd\n        FROM   store_sales\n        WHERE  ss_quantity BETWEEN 0 AND 5\n               AND ( ss_list_price BETWEEN 18 AND 18 + 10\n                      OR ss_coupon_amt BETWEEN 1939 AND 1939 + 1000\n                      OR ss_wholesale_cost BETWEEN 34 AND 34 + 20 )) B1,\n       (SELECT Avg(ss_list_price)            b2_lp,\n               Count(ss_list_price)          b2_cnt,\n               Count(DISTINCT ss_list_price) b2_cntd\n        FROM   store_sales\n        WHERE  ss_quantity BETWEEN 6 AND 10\n               AND ( ss_list_price BETWEEN 1 AND 1 + 10\n                      OR ss_coupon_amt BETWEEN 35 AND 35 + 1000\n                      OR ss_wholesale_cost BETWEEN 50 AND 50 + 20 )) B2,\n       (SELECT Avg(ss_list_price)            b3_lp,\n               Count(ss_list_price)          b3_cnt,\n               Count(DISTINCT ss_list_price) b3_cntd\n        FROM   store_sales\n        WHERE  ss_quantity BETWEEN 11 AND 15\n               AND ( ss_list_price BETWEEN 91 AND 91 + 10\n                      OR ss_coupon_amt BETWEEN 1412 AND 1412 + 1000\n                      OR ss_wholesale_cost BETWEEN 17 AND 17 + 20 )) B3,\n       (SELECT Avg(ss_list_price)            b4_lp,\n               Count(ss_list_price)          b4_cnt,\n               Count(DISTINCT ss_list_price) b4_cntd\n        FROM   store_sales\n        WHERE  ss_quantity BETWEEN 16 AND 20\n               AND ( ss_list_price BETWEEN 9 AND 9 + 10\n                      OR ss_coupon_amt BETWEEN 5270 AND 5270 + 1000\n                      OR ss_wholesale_cost BETWEEN 29 AND 29 + 20 )) B4,\n       (SELECT Avg(ss_list_price)            b5_lp,\n               Count(ss_list_price)          b5_cnt,\n               Count(DISTINCT ss_list_price) b5_cntd\n        FROM   store_sales\n        WHERE  ss_quantity BETWEEN 21 AND 25\n               AND ( ss_list_price BETWEEN 45 AND 45 + 10\n                      OR ss_coupon_amt BETWEEN 826 AND 826 + 1000\n                      OR ss_wholesale_cost BETWEEN 5 AND 5 + 20 )) B5,\n       (SELECT Avg(ss_list_price)            b6_lp,\n               Count(ss_list_price)          b6_cnt,\n               Count(DISTINCT ss_list_price) b6_cntd\n        FROM   store_sales\n        WHERE  ss_quantity BETWEEN 26 AND 30\n               AND ( ss_list_price BETWEEN 174 AND 174 + 10\n                      OR ss_coupon_amt BETWEEN 5548 AND 5548 + 1000\n                      OR ss_wholesale_cost BETWEEN 42 AND 42 + 20 )) B6\nLIMIT 100;\nWITH \"b1\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_list_price\") AS \"b1_lp\",\n    COUNT(\"store_sales\".\"ss_list_price\") AS \"b1_cnt\",\n    COUNT(DISTINCT \"store_sales\".\"ss_list_price\") AS \"b1_cntd\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    (\n      \"store_sales\".\"ss_coupon_amt\" <= 2939\n      AND \"store_sales\".\"ss_coupon_amt\" >= 1939\n      OR \"store_sales\".\"ss_list_price\" <= 28\n      AND \"store_sales\".\"ss_list_price\" >= 18\n      OR \"store_sales\".\"ss_wholesale_cost\" <= 54\n      AND \"store_sales\".\"ss_wholesale_cost\" >= 34\n    )\n    AND \"store_sales\".\"ss_quantity\" <= 5\n    AND \"store_sales\".\"ss_quantity\" >= 0\n), \"b2\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_list_price\") AS \"b2_lp\",\n    COUNT(\"store_sales\".\"ss_list_price\") AS \"b2_cnt\",\n    COUNT(DISTINCT \"store_sales\".\"ss_list_price\") AS \"b2_cntd\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    (\n      \"store_sales\".\"ss_coupon_amt\" <= 1035\n      AND \"store_sales\".\"ss_coupon_amt\" >= 35\n      OR \"store_sales\".\"ss_list_price\" <= 11\n      AND \"store_sales\".\"ss_list_price\" >= 1\n      OR \"store_sales\".\"ss_wholesale_cost\" <= 70\n      AND \"store_sales\".\"ss_wholesale_cost\" >= 50\n    )\n    AND \"store_sales\".\"ss_quantity\" <= 10\n    AND \"store_sales\".\"ss_quantity\" >= 6\n), \"b3\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_list_price\") AS \"b3_lp\",\n    COUNT(\"store_sales\".\"ss_list_price\") AS \"b3_cnt\",\n    COUNT(DISTINCT \"store_sales\".\"ss_list_price\") AS \"b3_cntd\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    (\n      \"store_sales\".\"ss_coupon_amt\" <= 2412\n      AND \"store_sales\".\"ss_coupon_amt\" >= 1412\n      OR \"store_sales\".\"ss_list_price\" <= 101\n      AND \"store_sales\".\"ss_list_price\" >= 91\n      OR \"store_sales\".\"ss_wholesale_cost\" <= 37\n      AND \"store_sales\".\"ss_wholesale_cost\" >= 17\n    )\n    AND \"store_sales\".\"ss_quantity\" <= 15\n    AND \"store_sales\".\"ss_quantity\" >= 11\n), \"b4\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_list_price\") AS \"b4_lp\",\n    COUNT(\"store_sales\".\"ss_list_price\") AS \"b4_cnt\",\n    COUNT(DISTINCT \"store_sales\".\"ss_list_price\") AS \"b4_cntd\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    (\n      \"store_sales\".\"ss_coupon_amt\" <= 6270\n      AND \"store_sales\".\"ss_coupon_amt\" >= 5270\n      OR \"store_sales\".\"ss_list_price\" <= 19\n      AND \"store_sales\".\"ss_list_price\" >= 9\n      OR \"store_sales\".\"ss_wholesale_cost\" <= 49\n      AND \"store_sales\".\"ss_wholesale_cost\" >= 29\n    )\n    AND \"store_sales\".\"ss_quantity\" <= 20\n    AND \"store_sales\".\"ss_quantity\" >= 16\n), \"b5\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_list_price\") AS \"b5_lp\",\n    COUNT(\"store_sales\".\"ss_list_price\") AS \"b5_cnt\",\n    COUNT(DISTINCT \"store_sales\".\"ss_list_price\") AS \"b5_cntd\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    (\n      \"store_sales\".\"ss_coupon_amt\" <= 1826\n      AND \"store_sales\".\"ss_coupon_amt\" >= 826\n      OR \"store_sales\".\"ss_list_price\" <= 55\n      AND \"store_sales\".\"ss_list_price\" >= 45\n      OR \"store_sales\".\"ss_wholesale_cost\" <= 25\n      AND \"store_sales\".\"ss_wholesale_cost\" >= 5\n    )\n    AND \"store_sales\".\"ss_quantity\" <= 25\n    AND \"store_sales\".\"ss_quantity\" >= 21\n), \"b6\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_list_price\") AS \"b6_lp\",\n    COUNT(\"store_sales\".\"ss_list_price\") AS \"b6_cnt\",\n    COUNT(DISTINCT \"store_sales\".\"ss_list_price\") AS \"b6_cntd\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    (\n      \"store_sales\".\"ss_coupon_amt\" <= 6548\n      AND \"store_sales\".\"ss_coupon_amt\" >= 5548\n      OR \"store_sales\".\"ss_list_price\" <= 184\n      AND \"store_sales\".\"ss_list_price\" >= 174\n      OR \"store_sales\".\"ss_wholesale_cost\" <= 62\n      AND \"store_sales\".\"ss_wholesale_cost\" >= 42\n    )\n    AND \"store_sales\".\"ss_quantity\" <= 30\n    AND \"store_sales\".\"ss_quantity\" >= 26\n)\nSELECT\n  \"b1\".\"b1_lp\" AS \"b1_lp\",\n  \"b1\".\"b1_cnt\" AS \"b1_cnt\",\n  \"b1\".\"b1_cntd\" AS \"b1_cntd\",\n  \"b2\".\"b2_lp\" AS \"b2_lp\",\n  \"b2\".\"b2_cnt\" AS \"b2_cnt\",\n  \"b2\".\"b2_cntd\" AS \"b2_cntd\",\n  \"b3\".\"b3_lp\" AS \"b3_lp\",\n  \"b3\".\"b3_cnt\" AS \"b3_cnt\",\n  \"b3\".\"b3_cntd\" AS \"b3_cntd\",\n  \"b4\".\"b4_lp\" AS \"b4_lp\",\n  \"b4\".\"b4_cnt\" AS \"b4_cnt\",\n  \"b4\".\"b4_cntd\" AS \"b4_cntd\",\n  \"b5\".\"b5_lp\" AS \"b5_lp\",\n  \"b5\".\"b5_cnt\" AS \"b5_cnt\",\n  \"b5\".\"b5_cntd\" AS \"b5_cntd\",\n  \"b6\".\"b6_lp\" AS \"b6_lp\",\n  \"b6\".\"b6_cnt\" AS \"b6_cnt\",\n  \"b6\".\"b6_cntd\" AS \"b6_cntd\"\nFROM \"b1\" AS \"b1\"\nCROSS JOIN \"b2\" AS \"b2\"\nCROSS JOIN \"b3\" AS \"b3\"\nCROSS JOIN \"b4\" AS \"b4\"\nCROSS JOIN \"b5\" AS \"b5\"\nCROSS JOIN \"b6\" AS \"b6\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 29\n--------------------------------------\n# execute: true\nSELECT i_item_id,\n               i_item_desc,\n               s_store_id,\n               s_store_name,\n               Avg(ss_quantity)        AS store_sales_quantity,\n               Avg(sr_return_quantity) AS store_returns_quantity,\n               Avg(cs_quantity)        AS catalog_sales_quantity\nFROM   store_sales,\n       store_returns,\n       catalog_sales,\n       date_dim d1,\n       date_dim d2,\n       date_dim d3,\n       store,\n       item\nWHERE  d1.d_moy = 4\n       AND d1.d_year = 1998\n       AND d1.d_date_sk = ss_sold_date_sk\n       AND i_item_sk = ss_item_sk\n       AND s_store_sk = ss_store_sk\n       AND ss_customer_sk = sr_customer_sk\n       AND ss_item_sk = sr_item_sk\n       AND ss_ticket_number = sr_ticket_number\n       AND sr_returned_date_sk = d2.d_date_sk\n       AND d2.d_moy BETWEEN 4 AND 4 + 3\n       AND d2.d_year = 1998\n       AND sr_customer_sk = cs_bill_customer_sk\n       AND sr_item_sk = cs_item_sk\n       AND cs_sold_date_sk = d3.d_date_sk\n       AND d3.d_year IN ( 1998, 1998 + 1, 1998 + 2 )\nGROUP  BY i_item_id,\n          i_item_desc,\n          s_store_id,\n          s_store_name\nORDER  BY i_item_id,\n          i_item_desc,\n          s_store_id,\n          s_store_name\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"store\".\"s_store_id\" AS \"s_store_id\",\n  \"store\".\"s_store_name\" AS \"s_store_name\",\n  AVG(\"store_sales\".\"ss_quantity\") AS \"store_sales_quantity\",\n  AVG(\"store_returns\".\"sr_return_quantity\") AS \"store_returns_quantity\",\n  AVG(\"catalog_sales\".\"cs_quantity\") AS \"catalog_sales_quantity\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"date_dim\" AS \"d1\"\n  ON \"d1\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"d1\".\"d_moy\" = 4\n  AND \"d1\".\"d_year\" = 1998\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nJOIN \"store_returns\" AS \"store_returns\"\n  ON \"store_returns\".\"sr_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  AND \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\nJOIN \"catalog_sales\" AS \"catalog_sales\"\n  ON \"catalog_sales\".\"cs_bill_customer_sk\" = \"store_returns\".\"sr_customer_sk\"\n  AND \"catalog_sales\".\"cs_item_sk\" = \"store_returns\".\"sr_item_sk\"\nJOIN \"date_dim\" AS \"d2\"\n  ON \"d2\".\"d_date_sk\" = \"store_returns\".\"sr_returned_date_sk\"\n  AND \"d2\".\"d_moy\" <= 7\n  AND \"d2\".\"d_moy\" >= 4\n  AND \"d2\".\"d_year\" = 1998\nJOIN \"date_dim\" AS \"d3\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"d3\".\"d_date_sk\"\n  AND \"d3\".\"d_year\" IN (1998, 1999, 2000)\nGROUP BY\n  \"item\".\"i_item_id\",\n  \"item\".\"i_item_desc\",\n  \"store\".\"s_store_id\",\n  \"store\".\"s_store_name\"\nORDER BY\n  \"i_item_id\",\n  \"i_item_desc\",\n  \"s_store_id\",\n  \"s_store_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 30\n--------------------------------------\nWITH customer_total_return\n     AS (SELECT wr_returning_customer_sk AS ctr_customer_sk,\n                ca_state                 AS ctr_state,\n                Sum(wr_return_amt)       AS ctr_total_return\n         FROM   web_returns,\n                date_dim,\n                customer_address\n         WHERE  wr_returned_date_sk = d_date_sk\n                AND d_year = 2000\n                AND wr_returning_addr_sk = ca_address_sk\n         GROUP  BY wr_returning_customer_sk,\n                   ca_state)\nSELECT c_customer_id,\n               c_salutation,\n               c_first_name,\n               c_last_name,\n               c_preferred_cust_flag,\n               c_birth_day,\n               c_birth_month,\n               c_birth_year,\n               c_birth_country,\n               c_login,\n               c_email_address,\n               c_last_review_date,\n               ctr_total_return\nFROM   customer_total_return ctr1,\n       customer_address,\n       customer\nWHERE  ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2\n                                FROM   customer_total_return ctr2\n                                WHERE  ctr1.ctr_state = ctr2.ctr_state)\n       AND ca_address_sk = c_current_addr_sk\n       AND ca_state = 'IN'\n       AND ctr1.ctr_customer_sk = c_customer_sk\nORDER  BY c_customer_id,\n          c_salutation,\n          c_first_name,\n          c_last_name,\n          c_preferred_cust_flag,\n          c_birth_day,\n          c_birth_month,\n          c_birth_year,\n          c_birth_country,\n          c_login,\n          c_email_address,\n          c_last_review_date,\n          ctr_total_return\nLIMIT 100;\nWITH \"customer_total_return\" AS (\n  SELECT\n    \"web_returns\".\"wr_returning_customer_sk\" AS \"ctr_customer_sk\",\n    \"customer_address\".\"ca_state\" AS \"ctr_state\",\n    SUM(\"web_returns\".\"wr_return_amt\") AS \"ctr_total_return\"\n  FROM \"web_returns\" AS \"web_returns\"\n  JOIN \"customer_address\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"web_returns\".\"wr_returning_addr_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_returns\".\"wr_returned_date_sk\"\n    AND \"date_dim\".\"d_year\" = 2000\n  GROUP BY\n    \"web_returns\".\"wr_returning_customer_sk\",\n    \"customer_address\".\"ca_state\"\n), \"_u_0\" AS (\n  SELECT\n    AVG(\"ctr2\".\"ctr_total_return\") * 1.2 AS \"_col_0\",\n    \"ctr2\".\"ctr_state\" AS \"_u_1\"\n  FROM \"customer_total_return\" AS \"ctr2\"\n  GROUP BY\n    \"ctr2\".\"ctr_state\"\n)\nSELECT\n  \"customer\".\"c_customer_id\" AS \"c_customer_id\",\n  \"customer\".\"c_salutation\" AS \"c_salutation\",\n  \"customer\".\"c_first_name\" AS \"c_first_name\",\n  \"customer\".\"c_last_name\" AS \"c_last_name\",\n  \"customer\".\"c_preferred_cust_flag\" AS \"c_preferred_cust_flag\",\n  \"customer\".\"c_birth_day\" AS \"c_birth_day\",\n  \"customer\".\"c_birth_month\" AS \"c_birth_month\",\n  \"customer\".\"c_birth_year\" AS \"c_birth_year\",\n  \"customer\".\"c_birth_country\" AS \"c_birth_country\",\n  \"customer\".\"c_login\" AS \"c_login\",\n  \"customer\".\"c_email_address\" AS \"c_email_address\",\n  \"customer\".\"c_last_review_date\" AS \"c_last_review_date\",\n  \"ctr1\".\"ctr_total_return\" AS \"ctr_total_return\"\nFROM \"customer_total_return\" AS \"ctr1\"\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer_address\".\"ca_state\" = 'IN'\nJOIN \"customer\" AS \"customer\"\n  ON \"ctr1\".\"ctr_customer_sk\" = \"customer\".\"c_customer_sk\"\n  AND \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"ctr1\".\"ctr_state\"\nWHERE\n  \"_u_0\".\"_col_0\" < \"ctr1\".\"ctr_total_return\"\nORDER BY\n  \"c_customer_id\",\n  \"c_salutation\",\n  \"c_first_name\",\n  \"c_last_name\",\n  \"c_preferred_cust_flag\",\n  \"c_birth_day\",\n  \"c_birth_month\",\n  \"c_birth_year\",\n  \"c_birth_country\",\n  \"c_login\",\n  \"c_email_address\",\n  \"c_last_review_date\",\n  \"ctr_total_return\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 31\n--------------------------------------\n# execute: true\nWITH ss\n     AS (SELECT ca_county,\n                d_qoy,\n                d_year,\n                Sum(ss_ext_sales_price) AS store_sales\n         FROM   store_sales,\n                date_dim,\n                customer_address\n         WHERE  ss_sold_date_sk = d_date_sk\n                AND ss_addr_sk = ca_address_sk\n         GROUP  BY ca_county,\n                   d_qoy,\n                   d_year),\n     ws\n     AS (SELECT ca_county,\n                d_qoy,\n                d_year,\n                Sum(ws_ext_sales_price) AS web_sales\n         FROM   web_sales,\n                date_dim,\n                customer_address\n         WHERE  ws_sold_date_sk = d_date_sk\n                AND ws_bill_addr_sk = ca_address_sk\n         GROUP  BY ca_county,\n                   d_qoy,\n                   d_year)\nSELECT ss1.ca_county,\n       ss1.d_year,\n       ws2.web_sales / ws1.web_sales     web_q1_q2_increase,\n       ss2.store_sales / ss1.store_sales store_q1_q2_increase,\n       ws3.web_sales / ws2.web_sales     web_q2_q3_increase,\n       ss3.store_sales / ss2.store_sales store_q2_q3_increase\nFROM   ss ss1,\n       ss ss2,\n       ss ss3,\n       ws ws1,\n       ws ws2,\n       ws ws3\nWHERE  ss1.d_qoy = 1\n       AND ss1.d_year = 2001\n       AND ss1.ca_county = ss2.ca_county\n       AND ss2.d_qoy = 2\n       AND ss2.d_year = 2001\n       AND ss2.ca_county = ss3.ca_county\n       AND ss3.d_qoy = 3\n       AND ss3.d_year = 2001\n       AND ss1.ca_county = ws1.ca_county\n       AND ws1.d_qoy = 1\n       AND ws1.d_year = 2001\n       AND ws1.ca_county = ws2.ca_county\n       AND ws2.d_qoy = 2\n       AND ws2.d_year = 2001\n       AND ws1.ca_county = ws3.ca_county\n       AND ws3.d_qoy = 3\n       AND ws3.d_year = 2001\n       AND CASE\n             WHEN ws1.web_sales > 0 THEN ws2.web_sales / ws1.web_sales\n             ELSE NULL\n           END > CASE\n                   WHEN ss1.store_sales > 0 THEN\n                   ss2.store_sales / ss1.store_sales\n                   ELSE NULL\n                 END\n       AND CASE\n             WHEN ws2.web_sales > 0 THEN ws3.web_sales / ws2.web_sales\n             ELSE NULL\n           END > CASE\n                   WHEN ss2.store_sales > 0 THEN\n                   ss3.store_sales / ss2.store_sales\n                   ELSE NULL\n                 END\nORDER  BY ss1.d_year;\nWITH \"customer_address_2\" AS (\n  SELECT\n    \"customer_address\".\"ca_address_sk\" AS \"ca_address_sk\",\n    \"customer_address\".\"ca_county\" AS \"ca_county\"\n  FROM \"customer_address\" AS \"customer_address\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\"\n  FROM \"date_dim\" AS \"date_dim\"\n), \"ss\" AS (\n  SELECT\n    \"customer_address\".\"ca_county\" AS \"ca_county\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"store_sales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"customer_address\".\"ca_county\",\n    \"date_dim\".\"d_qoy\",\n    \"date_dim\".\"d_year\"\n), \"ws\" AS (\n  SELECT\n    \"customer_address\".\"ca_county\" AS \"ca_county\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    SUM(\"web_sales\".\"ws_ext_sales_price\") AS \"web_sales\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"web_sales\".\"ws_bill_addr_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  GROUP BY\n    \"customer_address\".\"ca_county\",\n    \"date_dim\".\"d_qoy\",\n    \"date_dim\".\"d_year\"\n)\nSELECT\n  \"ss1\".\"ca_county\" AS \"ca_county\",\n  \"ss1\".\"d_year\" AS \"d_year\",\n  \"ws2\".\"web_sales\" / \"ws1\".\"web_sales\" AS \"web_q1_q2_increase\",\n  \"ss2\".\"store_sales\" / \"ss1\".\"store_sales\" AS \"store_q1_q2_increase\",\n  \"ws3\".\"web_sales\" / \"ws2\".\"web_sales\" AS \"web_q2_q3_increase\",\n  \"ss3\".\"store_sales\" / \"ss2\".\"store_sales\" AS \"store_q2_q3_increase\"\nFROM \"ss\" AS \"ss1\"\nJOIN \"ss\" AS \"ss2\"\n  ON \"ss1\".\"ca_county\" = \"ss2\".\"ca_county\"\n  AND \"ss2\".\"d_qoy\" = 2\n  AND \"ss2\".\"d_year\" = 2001\nJOIN \"ws\" AS \"ws1\"\n  ON \"ss1\".\"ca_county\" = \"ws1\".\"ca_county\"\n  AND \"ws1\".\"d_qoy\" = 1\n  AND \"ws1\".\"d_year\" = 2001\nJOIN \"ss\" AS \"ss3\"\n  ON \"ss2\".\"ca_county\" = \"ss3\".\"ca_county\"\n  AND \"ss3\".\"d_qoy\" = 3\n  AND \"ss3\".\"d_year\" = 2001\nJOIN \"ws\" AS \"ws2\"\n  ON \"ws1\".\"ca_county\" = \"ws2\".\"ca_county\"\n  AND \"ws2\".\"d_qoy\" = 2\n  AND \"ws2\".\"d_year\" = 2001\n  AND CASE\n    WHEN \"ss1\".\"store_sales\" > 0\n    THEN \"ss2\".\"store_sales\" / \"ss1\".\"store_sales\"\n    ELSE NULL\n  END < CASE\n    WHEN \"ws1\".\"web_sales\" > 0\n    THEN \"ws2\".\"web_sales\" / \"ws1\".\"web_sales\"\n    ELSE NULL\n  END\nJOIN \"ws\" AS \"ws3\"\n  ON \"ws1\".\"ca_county\" = \"ws3\".\"ca_county\"\n  AND \"ws3\".\"d_qoy\" = 3\n  AND \"ws3\".\"d_year\" = 2001\n  AND CASE\n    WHEN \"ss2\".\"store_sales\" > 0\n    THEN \"ss3\".\"store_sales\" / \"ss2\".\"store_sales\"\n    ELSE NULL\n  END < CASE\n    WHEN \"ws2\".\"web_sales\" > 0\n    THEN \"ws3\".\"web_sales\" / \"ws2\".\"web_sales\"\n    ELSE NULL\n  END\nWHERE\n  \"ss1\".\"d_qoy\" = 1 AND \"ss1\".\"d_year\" = 2001\nORDER BY\n  \"ss1\".\"d_year\";\n\n--------------------------------------\n-- TPC-DS 32\n--------------------------------------\nSELECT\n       Sum(cs_ext_discount_amt) AS \"excess discount amount\"\nFROM   catalog_sales ,\n       item ,\n       date_dim\nWHERE  i_manufact_id = 610\nAND    i_item_sk = cs_item_sk\nAND    d_date BETWEEN '2001-03-04' AND    (\n              Cast('2001-03-04' AS DATE) + INTERVAL '90' day)\nAND    d_date_sk = cs_sold_date_sk\nAND    cs_ext_discount_amt >\n       (\n              SELECT 1.3 * avg(cs_ext_discount_amt)\n              FROM   catalog_sales ,\n                     date_dim\n              WHERE  cs_item_sk = i_item_sk\n              AND    d_date BETWEEN '2001-03-04' AND    (\n                            cast('2001-03-04' AS date) + INTERVAL '90' day)\n              AND    d_date_sk = cs_sold_date_sk )\nLIMIT 100;\nWITH \"catalog_sales_2\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_sold_date_sk\" AS \"cs_sold_date_sk\",\n    \"catalog_sales\".\"cs_item_sk\" AS \"cs_item_sk\",\n    \"catalog_sales\".\"cs_ext_discount_amt\" AS \"cs_ext_discount_amt\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_date\" >= '2001-03-04'\n    AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2001-06-02' AS DATE)\n), \"_u_0\" AS (\n  SELECT\n    1.3 * AVG(\"catalog_sales\".\"cs_ext_discount_amt\") AS \"_col_0\",\n    \"catalog_sales\".\"cs_item_sk\" AS \"_u_1\"\n  FROM \"catalog_sales_2\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  GROUP BY\n    \"catalog_sales\".\"cs_item_sk\"\n)\nSELECT\n  SUM(\"catalog_sales\".\"cs_ext_discount_amt\") AS \"excess discount amount\"\nFROM \"catalog_sales_2\" AS \"catalog_sales\"\nJOIN \"item\" AS \"item\"\n  ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  AND \"item\".\"i_manufact_id\" = 610\nJOIN \"date_dim_2\" AS \"date_dim\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"item\".\"i_item_sk\"\nWHERE\n  \"_u_0\".\"_col_0\" < \"catalog_sales\".\"cs_ext_discount_amt\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 33\n--------------------------------------\n# execute: true\nWITH ss\n     AS (SELECT i_manufact_id,\n                Sum(ss_ext_sales_price) total_sales\n         FROM   store_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_manufact_id IN (SELECT i_manufact_id\n                                  FROM   item\n                                  WHERE  i_category IN ( 'Books' ))\n                AND ss_item_sk = i_item_sk\n                AND ss_sold_date_sk = d_date_sk\n                AND d_year = 1999\n                AND d_moy = 3\n                AND ss_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -5\n         GROUP  BY i_manufact_id),\n     cs\n     AS (SELECT i_manufact_id,\n                Sum(cs_ext_sales_price) total_sales\n         FROM   catalog_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_manufact_id IN (SELECT i_manufact_id\n                                  FROM   item\n                                  WHERE  i_category IN ( 'Books' ))\n                AND cs_item_sk = i_item_sk\n                AND cs_sold_date_sk = d_date_sk\n                AND d_year = 1999\n                AND d_moy = 3\n                AND cs_bill_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -5\n         GROUP  BY i_manufact_id),\n     ws\n     AS (SELECT i_manufact_id,\n                Sum(ws_ext_sales_price) total_sales\n         FROM   web_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_manufact_id IN (SELECT i_manufact_id\n                                  FROM   item\n                                  WHERE  i_category IN ( 'Books' ))\n                AND ws_item_sk = i_item_sk\n                AND ws_sold_date_sk = d_date_sk\n                AND d_year = 1999\n                AND d_moy = 3\n                AND ws_bill_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -5\n         GROUP  BY i_manufact_id)\nSELECT i_manufact_id,\n               Sum(total_sales) total_sales\nFROM   (SELECT *\n        FROM   ss\n        UNION ALL\n        SELECT *\n        FROM   cs\n        UNION ALL\n        SELECT *\n        FROM   ws) tmp1\nGROUP  BY i_manufact_id\nORDER  BY total_sales\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 3 AND \"date_dim\".\"d_year\" = 1999\n), \"customer_address_2\" AS (\n  SELECT\n    \"customer_address\".\"ca_address_sk\" AS \"ca_address_sk\",\n    \"customer_address\".\"ca_gmt_offset\" AS \"ca_gmt_offset\"\n  FROM \"customer_address\" AS \"customer_address\"\n  WHERE\n    \"customer_address\".\"ca_gmt_offset\" = -5\n), \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\"\n  FROM \"item\" AS \"item\"\n), \"_u_0\" AS (\n  SELECT\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\"\n  FROM \"item\" AS \"item\"\n  WHERE\n    \"item\".\"i_category\" IN ('Books')\n  GROUP BY\n    \"item\".\"i_manufact_id\"\n), \"ss\" AS (\n  SELECT\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\",\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"total_sales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_0\"\n    ON \"_u_0\".\"i_manufact_id\" = \"item\".\"i_manufact_id\"\n  WHERE\n    NOT \"_u_0\".\"i_manufact_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_manufact_id\"\n), \"cs\" AS (\n  SELECT\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\",\n    SUM(\"catalog_sales\".\"cs_ext_sales_price\") AS \"total_sales\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"catalog_sales\".\"cs_bill_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_1\"\n    ON \"_u_1\".\"i_manufact_id\" = \"item\".\"i_manufact_id\"\n  WHERE\n    NOT \"_u_1\".\"i_manufact_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_manufact_id\"\n), \"ws\" AS (\n  SELECT\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\",\n    SUM(\"web_sales\".\"ws_ext_sales_price\") AS \"total_sales\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"web_sales\".\"ws_bill_addr_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_2\"\n    ON \"_u_2\".\"i_manufact_id\" = \"item\".\"i_manufact_id\"\n  WHERE\n    NOT \"_u_2\".\"i_manufact_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_manufact_id\"\n), \"tmp1\" AS (\n  SELECT\n    \"ss\".\"i_manufact_id\" AS \"i_manufact_id\",\n    \"ss\".\"total_sales\" AS \"total_sales\"\n  FROM \"ss\" AS \"ss\"\n  UNION ALL\n  SELECT\n    \"cs\".\"i_manufact_id\" AS \"i_manufact_id\",\n    \"cs\".\"total_sales\" AS \"total_sales\"\n  FROM \"cs\" AS \"cs\"\n  UNION ALL\n  SELECT\n    \"ws\".\"i_manufact_id\" AS \"i_manufact_id\",\n    \"ws\".\"total_sales\" AS \"total_sales\"\n  FROM \"ws\" AS \"ws\"\n)\nSELECT\n  \"tmp1\".\"i_manufact_id\" AS \"i_manufact_id\",\n  SUM(\"tmp1\".\"total_sales\") AS \"total_sales\"\nFROM \"tmp1\" AS \"tmp1\"\nGROUP BY\n  \"tmp1\".\"i_manufact_id\"\nORDER BY\n  \"total_sales\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 34\n--------------------------------------\n# execute: true\nSELECT c_last_name,\n       c_first_name,\n       c_salutation,\n       c_preferred_cust_flag,\n       ss_ticket_number,\n       cnt\nFROM   (SELECT ss_ticket_number,\n               ss_customer_sk,\n               Count(*) cnt\n        FROM   store_sales,\n               date_dim,\n               store,\n               household_demographics\n        WHERE  store_sales.ss_sold_date_sk = date_dim.d_date_sk\n               AND store_sales.ss_store_sk = store.s_store_sk\n               AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk\n               AND ( date_dim.d_dom BETWEEN 1 AND 3\n                      OR date_dim.d_dom BETWEEN 25 AND 28 )\n               AND ( household_demographics.hd_buy_potential = '>10000'\n                      OR household_demographics.hd_buy_potential = 'unknown' )\n               AND household_demographics.hd_vehicle_count > 0\n               AND ( CASE\n                       WHEN household_demographics.hd_vehicle_count > 0 THEN\n                       household_demographics.hd_dep_count /\n                       household_demographics.hd_vehicle_count\n                       ELSE NULL\n                     END ) > 1.2\n               AND date_dim.d_year IN ( 1999, 1999 + 1, 1999 + 2 )\n               AND store.s_county IN ( 'Williamson County', 'Williamson County',\n                                       'Williamson County',\n                                                             'Williamson County'\n                                       ,\n                                       'Williamson County', 'Williamson County',\n                                           'Williamson County',\n                                                             'Williamson County'\n                                     )\n        GROUP  BY ss_ticket_number,\n                  ss_customer_sk) dn,\n       customer\nWHERE  ss_customer_sk = c_customer_sk\n       AND cnt BETWEEN 15 AND 20\nORDER  BY c_last_name,\n          c_first_name,\n          c_salutation,\n          c_preferred_cust_flag DESC;\nWITH \"dn\" AS (\n  SELECT\n    \"store_sales\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\" AS \"ss_customer_sk\",\n    COUNT(*) AS \"cnt\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_year\" IN (1999, 2000, 2001)\n    AND (\n      (\n        \"date_dim\".\"d_dom\" <= 28 AND \"date_dim\".\"d_dom\" >= 25\n      )\n      OR (\n        \"date_dim\".\"d_dom\" <= 3 AND \"date_dim\".\"d_dom\" >= 1\n      )\n    )\n  JOIN \"household_demographics\" AS \"household_demographics\"\n    ON (\n      \"household_demographics\".\"hd_buy_potential\" = '>10000'\n      OR \"household_demographics\".\"hd_buy_potential\" = 'unknown'\n    )\n    AND \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n    AND \"household_demographics\".\"hd_vehicle_count\" > 0\n    AND CASE\n      WHEN \"household_demographics\".\"hd_vehicle_count\" > 0\n      THEN \"household_demographics\".\"hd_dep_count\" / \"household_demographics\".\"hd_vehicle_count\"\n      ELSE NULL\n    END > 1.2\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_county\" IN (\n      'Williamson County',\n      'Williamson County',\n      'Williamson County',\n      'Williamson County',\n      'Williamson County',\n      'Williamson County',\n      'Williamson County',\n      'Williamson County'\n    )\n    AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\"\n)\nSELECT\n  \"customer\".\"c_last_name\" AS \"c_last_name\",\n  \"customer\".\"c_first_name\" AS \"c_first_name\",\n  \"customer\".\"c_salutation\" AS \"c_salutation\",\n  \"customer\".\"c_preferred_cust_flag\" AS \"c_preferred_cust_flag\",\n  \"dn\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n  \"dn\".\"cnt\" AS \"cnt\"\nFROM \"dn\" AS \"dn\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_customer_sk\" = \"dn\".\"ss_customer_sk\"\nWHERE\n  \"dn\".\"cnt\" <= 20 AND \"dn\".\"cnt\" >= 15\nORDER BY\n  \"c_last_name\",\n  \"c_first_name\",\n  \"c_salutation\",\n  \"c_preferred_cust_flag\" DESC;\n\n--------------------------------------\n-- TPC-DS 35\n--------------------------------------\n# execute: true\nSELECT ca_state,\n               cd_gender,\n               cd_marital_status,\n               cd_dep_count,\n               Count(*) cnt1,\n               Stddev_samp(cd_dep_count) AS \"_col_5\",\n               Avg(cd_dep_count) AS \"_col_6\",\n               Max(cd_dep_count) AS \"_col_7\",\n               cd_dep_employed_count,\n               Count(*) cnt2,\n               Stddev_samp(cd_dep_employed_count) AS \"_col_10\",\n               Avg(cd_dep_employed_count) AS \"_col_11\",\n               Max(cd_dep_employed_count) AS \"_col_12\",\n               cd_dep_college_count,\n               Count(*) cnt3,\n               Stddev_samp(cd_dep_college_count) AS \"_col_15\",\n               Avg(cd_dep_college_count) AS \"_col_16\",\n               Max(cd_dep_college_count) AS \"_col_17\"\nFROM   customer c,\n       customer_address ca,\n       customer_demographics\nWHERE  c.c_current_addr_sk = ca.ca_address_sk\n       AND cd_demo_sk = c.c_current_cdemo_sk\n       AND EXISTS (SELECT *\n                   FROM   store_sales,\n                          date_dim\n                   WHERE  c.c_customer_sk = ss_customer_sk\n                          AND ss_sold_date_sk = d_date_sk\n                          AND d_year = 2001\n                          AND d_qoy < 4)\n       AND ( EXISTS (SELECT *\n                     FROM   web_sales,\n                            date_dim\n                     WHERE  c.c_customer_sk = ws_bill_customer_sk\n                            AND ws_sold_date_sk = d_date_sk\n                            AND d_year = 2001\n                            AND d_qoy < 4)\n              OR EXISTS (SELECT *\n                         FROM   catalog_sales,\n                                date_dim\n                         WHERE  c.c_customer_sk = cs_ship_customer_sk\n                                AND cs_sold_date_sk = d_date_sk\n                                AND d_year = 2001\n                                AND d_qoy < 4) )\nGROUP  BY ca_state,\n          cd_gender,\n          cd_marital_status,\n          cd_dep_count,\n          cd_dep_employed_count,\n          cd_dep_college_count\nORDER  BY ca_state,\n          cd_gender,\n          cd_marital_status,\n          cd_dep_count,\n          cd_dep_employed_count,\n          cd_dep_college_count\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date_id\" AS \"d_date_id\",\n    \"date_dim\".\"d_date\" AS \"d_date\",\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\",\n    \"date_dim\".\"d_week_seq\" AS \"d_week_seq\",\n    \"date_dim\".\"d_quarter_seq\" AS \"d_quarter_seq\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_dow\" AS \"d_dow\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\",\n    \"date_dim\".\"d_dom\" AS \"d_dom\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"date_dim\".\"d_fy_year\" AS \"d_fy_year\",\n    \"date_dim\".\"d_fy_quarter_seq\" AS \"d_fy_quarter_seq\",\n    \"date_dim\".\"d_fy_week_seq\" AS \"d_fy_week_seq\",\n    \"date_dim\".\"d_day_name\" AS \"d_day_name\",\n    \"date_dim\".\"d_quarter_name\" AS \"d_quarter_name\",\n    \"date_dim\".\"d_holiday\" AS \"d_holiday\",\n    \"date_dim\".\"d_weekend\" AS \"d_weekend\",\n    \"date_dim\".\"d_following_holiday\" AS \"d_following_holiday\",\n    \"date_dim\".\"d_first_dom\" AS \"d_first_dom\",\n    \"date_dim\".\"d_last_dom\" AS \"d_last_dom\",\n    \"date_dim\".\"d_same_day_ly\" AS \"d_same_day_ly\",\n    \"date_dim\".\"d_same_day_lq\" AS \"d_same_day_lq\",\n    \"date_dim\".\"d_current_day\" AS \"d_current_day\",\n    \"date_dim\".\"d_current_week\" AS \"d_current_week\",\n    \"date_dim\".\"d_current_month\" AS \"d_current_month\",\n    \"date_dim\".\"d_current_quarter\" AS \"d_current_quarter\",\n    \"date_dim\".\"d_current_year\" AS \"d_current_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_qoy\" < 4 AND \"date_dim\".\"d_year\" = 2001\n), \"_u_0\" AS (\n  SELECT\n    \"store_sales\".\"ss_customer_sk\" AS \"_u_1\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_customer_sk\"\n), \"_u_2\" AS (\n  SELECT\n    \"web_sales\".\"ws_bill_customer_sk\" AS \"_u_3\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  GROUP BY\n    \"web_sales\".\"ws_bill_customer_sk\"\n), \"_u_4\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_ship_customer_sk\" AS \"_u_5\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  GROUP BY\n    \"catalog_sales\".\"cs_ship_customer_sk\"\n)\nSELECT\n  \"ca\".\"ca_state\" AS \"ca_state\",\n  \"customer_demographics\".\"cd_gender\" AS \"cd_gender\",\n  \"customer_demographics\".\"cd_marital_status\" AS \"cd_marital_status\",\n  \"customer_demographics\".\"cd_dep_count\" AS \"cd_dep_count\",\n  COUNT(*) AS \"cnt1\",\n  STDDEV_SAMP(\"customer_demographics\".\"cd_dep_count\") AS \"_col_5\",\n  AVG(\"customer_demographics\".\"cd_dep_count\") AS \"_col_6\",\n  MAX(\"customer_demographics\".\"cd_dep_count\") AS \"_col_7\",\n  \"customer_demographics\".\"cd_dep_employed_count\" AS \"cd_dep_employed_count\",\n  COUNT(*) AS \"cnt2\",\n  STDDEV_SAMP(\"customer_demographics\".\"cd_dep_employed_count\") AS \"_col_10\",\n  AVG(\"customer_demographics\".\"cd_dep_employed_count\") AS \"_col_11\",\n  MAX(\"customer_demographics\".\"cd_dep_employed_count\") AS \"_col_12\",\n  \"customer_demographics\".\"cd_dep_college_count\" AS \"cd_dep_college_count\",\n  COUNT(*) AS \"cnt3\",\n  STDDEV_SAMP(\"customer_demographics\".\"cd_dep_college_count\") AS \"_col_15\",\n  AVG(\"customer_demographics\".\"cd_dep_college_count\") AS \"_col_16\",\n  MAX(\"customer_demographics\".\"cd_dep_college_count\") AS \"_col_17\"\nFROM \"customer\" AS \"c\"\nJOIN \"customer_address\" AS \"ca\"\n  ON \"c\".\"c_current_addr_sk\" = \"ca\".\"ca_address_sk\"\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"c\".\"c_current_cdemo_sk\" = \"customer_demographics\".\"cd_demo_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"c\".\"c_customer_sk\"\nLEFT JOIN \"_u_2\" AS \"_u_2\"\n  ON \"_u_2\".\"_u_3\" = \"c\".\"c_customer_sk\"\nLEFT JOIN \"_u_4\" AS \"_u_4\"\n  ON \"_u_4\".\"_u_5\" = \"c\".\"c_customer_sk\"\nWHERE\n  NOT \"_u_0\".\"_u_1\" IS NULL\n  AND (\n    NOT \"_u_2\".\"_u_3\" IS NULL OR NOT \"_u_4\".\"_u_5\" IS NULL\n  )\nGROUP BY\n  \"ca\".\"ca_state\",\n  \"customer_demographics\".\"cd_gender\",\n  \"customer_demographics\".\"cd_marital_status\",\n  \"customer_demographics\".\"cd_dep_count\",\n  \"customer_demographics\".\"cd_dep_employed_count\",\n  \"customer_demographics\".\"cd_dep_college_count\"\nORDER BY\n  \"ca_state\",\n  \"cd_gender\",\n  \"cd_marital_status\",\n  \"cd_dep_count\",\n  \"cd_dep_employed_count\",\n  \"cd_dep_college_count\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 36\n--------------------------------------\nSELECT Sum(ss_net_profit) / Sum(ss_ext_sales_price)                 AS\n               gross_margin,\n               i_category,\n               i_class,\n               Grouping(i_category) + Grouping(i_class)                     AS\n               lochierarchy,\n               Rank()\n                 OVER (\n                   partition BY Grouping(i_category)+Grouping(i_class), CASE\n                 WHEN Grouping(\n                 i_class) = 0 THEN i_category END\n                   ORDER BY Sum(ss_net_profit)/Sum(ss_ext_sales_price) ASC) AS\n               rank_within_parent\nFROM   store_sales,\n       date_dim d1,\n       item,\n       store\nWHERE  d1.d_year = 2000\n       AND d1.d_date_sk = ss_sold_date_sk\n       AND i_item_sk = ss_item_sk\n       AND s_store_sk = ss_store_sk\n       AND s_state IN ( 'TN', 'TN', 'TN', 'TN',\n                        'TN', 'TN', 'TN', 'TN' )\nGROUP  BY rollup( i_category, i_class )\nORDER  BY lochierarchy DESC,\n          CASE\n            WHEN lochierarchy = 0 THEN i_category\n          END,\n          rank_within_parent\nLIMIT 100;\nSELECT\n  SUM(\"store_sales\".\"ss_net_profit\") / SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"gross_margin\",\n  \"item\".\"i_category\" AS \"i_category\",\n  \"item\".\"i_class\" AS \"i_class\",\n  GROUPING(\"item\".\"i_category\") + GROUPING(\"item\".\"i_class\") AS \"lochierarchy\",\n  RANK() OVER (\n    PARTITION BY GROUPING(\"item\".\"i_category\") + GROUPING(\"item\".\"i_class\"), CASE WHEN GROUPING(\"item\".\"i_class\") = 0 THEN \"item\".\"i_category\" END\n    ORDER BY SUM(\"store_sales\".\"ss_net_profit\") / SUM(\"store_sales\".\"ss_ext_sales_price\")\n  ) AS \"rank_within_parent\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"date_dim\" AS \"d1\"\n  ON \"d1\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\" AND \"d1\".\"d_year\" = 2000\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_state\" IN ('TN', 'TN', 'TN', 'TN', 'TN', 'TN', 'TN', 'TN')\n  AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nGROUP BY\n  ROLLUP (\n    \"item\".\"i_category\",\n    \"item\".\"i_class\"\n  )\nORDER BY\n  \"lochierarchy\" DESC,\n  CASE WHEN \"lochierarchy\" = 0 THEN \"i_category\" END,\n  \"rank_within_parent\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 37\n--------------------------------------\nSELECT\n         i_item_id ,\n         i_item_desc ,\n         i_current_price\nFROM     item,\n         inventory,\n         date_dim,\n         catalog_sales\nWHERE    i_current_price BETWEEN 20 AND      20 + 30\nAND      inv_item_sk = i_item_sk\nAND      d_date_sk=inv_date_sk\nAND      d_date BETWEEN Cast('1999-03-06' AS DATE) AND      (\n                  Cast('1999-03-06' AS DATE) + INTERVAL '60' day)\nAND      i_manufact_id IN (843,815,850,840)\nAND      inv_quantity_on_hand BETWEEN 100 AND      500\nAND      cs_item_sk = i_item_sk\nGROUP BY i_item_id,\n         i_item_desc,\n         i_current_price\nORDER BY i_item_id\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"item\".\"i_current_price\" AS \"i_current_price\"\nFROM \"item\" AS \"item\"\nJOIN \"catalog_sales\" AS \"catalog_sales\"\n  ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\nJOIN \"inventory\" AS \"inventory\"\n  ON \"inventory\".\"inv_item_sk\" = \"item\".\"i_item_sk\"\n  AND \"inventory\".\"inv_quantity_on_hand\" <= 500\n  AND \"inventory\".\"inv_quantity_on_hand\" >= 100\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"inventory\".\"inv_date_sk\"\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('1999-05-05' AS DATE)\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('1999-03-06' AS DATE)\nWHERE\n  \"item\".\"i_current_price\" <= 50\n  AND \"item\".\"i_current_price\" >= 20\n  AND \"item\".\"i_manufact_id\" IN (843, 815, 850, 840)\nGROUP BY\n  \"item\".\"i_item_id\",\n  \"item\".\"i_item_desc\",\n  \"item\".\"i_current_price\"\nORDER BY\n  \"i_item_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 38\n--------------------------------------\n# execute: true\nSELECT Count(*) AS \"_col_0\"\nFROM   (SELECT DISTINCT c_last_name,\n                        c_first_name,\n                        d_date\n        FROM   store_sales,\n               date_dim,\n               customer\n        WHERE  store_sales.ss_sold_date_sk = date_dim.d_date_sk\n               AND store_sales.ss_customer_sk = customer.c_customer_sk\n               AND d_month_seq BETWEEN 1188 AND 1188 + 11\n        INTERSECT\n        SELECT DISTINCT c_last_name,\n                        c_first_name,\n                        d_date\n        FROM   catalog_sales,\n               date_dim,\n               customer\n        WHERE  catalog_sales.cs_sold_date_sk = date_dim.d_date_sk\n               AND catalog_sales.cs_bill_customer_sk = customer.c_customer_sk\n               AND d_month_seq BETWEEN 1188 AND 1188 + 11\n        INTERSECT\n        SELECT DISTINCT c_last_name,\n                        c_first_name,\n                        d_date\n        FROM   web_sales,\n               date_dim,\n               customer\n        WHERE  web_sales.ws_sold_date_sk = date_dim.d_date_sk\n               AND web_sales.ws_bill_customer_sk = customer.c_customer_sk\n               AND d_month_seq BETWEEN 1188 AND 1188 + 11) hot_cust\nLIMIT 100;\nWITH \"customer_2\" AS (\n  SELECT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"customer\".\"c_last_name\" AS \"c_last_name\"\n  FROM \"customer\" AS \"customer\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\",\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_month_seq\" <= 1199 AND \"date_dim\".\"d_month_seq\" >= 1188\n), \"hot_cust\" AS (\n  SELECT DISTINCT\n    \"customer\".\"c_last_name\" AS \"c_last_name\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer_2\" AS \"customer\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  INTERSECT\n  SELECT DISTINCT\n    \"customer\".\"c_last_name\" AS \"c_last_name\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"customer_2\" AS \"customer\"\n    ON \"catalog_sales\".\"cs_bill_customer_sk\" = \"customer\".\"c_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  INTERSECT\n  SELECT DISTINCT\n    \"customer\".\"c_last_name\" AS \"c_last_name\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"customer_2\" AS \"customer\"\n    ON \"customer\".\"c_customer_sk\" = \"web_sales\".\"ws_bill_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n)\nSELECT\n  COUNT(*) AS \"_col_0\"\nFROM \"hot_cust\" AS \"hot_cust\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 39\n--------------------------------------\nWITH inv\n     AS (SELECT w_warehouse_name,\n                w_warehouse_sk,\n                i_item_sk,\n                d_moy,\n                stdev,\n                mean,\n                CASE mean\n                  WHEN 0 THEN NULL\n                  ELSE stdev / mean\n                END cov\n         FROM  (SELECT w_warehouse_name,\n                       w_warehouse_sk,\n                       i_item_sk,\n                       d_moy,\n                       Stddev_samp(inv_quantity_on_hand) stdev,\n                       Avg(inv_quantity_on_hand)         mean\n                FROM   inventory,\n                       item,\n                       warehouse,\n                       date_dim\n                WHERE  inv_item_sk = i_item_sk\n                       AND inv_warehouse_sk = w_warehouse_sk\n                       AND inv_date_sk = d_date_sk\n                       AND d_year = 2002\n                GROUP  BY w_warehouse_name,\n                          w_warehouse_sk,\n                          i_item_sk,\n                          d_moy) foo\n         WHERE  CASE mean\n                  WHEN 0 THEN 0\n                  ELSE stdev / mean\n                END > 1)\nSELECT inv1.w_warehouse_sk,\n       inv1.i_item_sk,\n       inv1.d_moy,\n       inv1.mean,\n       inv1.cov,\n       inv2.w_warehouse_sk,\n       inv2.i_item_sk,\n       inv2.d_moy,\n       inv2.mean,\n       inv2.cov\nFROM   inv inv1,\n       inv inv2\nWHERE  inv1.i_item_sk = inv2.i_item_sk\n       AND inv1.w_warehouse_sk = inv2.w_warehouse_sk\n       AND inv1.d_moy = 1\n       AND inv2.d_moy = 1 + 1\nORDER  BY inv1.w_warehouse_sk,\n          inv1.i_item_sk,\n          inv1.d_moy,\n          inv1.mean,\n          inv1.cov,\n          inv2.d_moy,\n          inv2.mean,\n          inv2.cov;\nWITH \"foo\" AS (\n  SELECT\n    \"warehouse\".\"w_warehouse_sk\" AS \"w_warehouse_sk\",\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\",\n    STDDEV_SAMP(\"inventory\".\"inv_quantity_on_hand\") AS \"stdev\",\n    AVG(\"inventory\".\"inv_quantity_on_hand\") AS \"mean\"\n  FROM \"inventory\" AS \"inventory\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"inventory\".\"inv_date_sk\" AND \"date_dim\".\"d_year\" = 2002\n  JOIN \"item\" AS \"item\"\n    ON \"inventory\".\"inv_item_sk\" = \"item\".\"i_item_sk\"\n  JOIN \"warehouse\" AS \"warehouse\"\n    ON \"inventory\".\"inv_warehouse_sk\" = \"warehouse\".\"w_warehouse_sk\"\n  GROUP BY\n    \"warehouse\".\"w_warehouse_name\",\n    \"warehouse\".\"w_warehouse_sk\",\n    \"item\".\"i_item_sk\",\n    \"date_dim\".\"d_moy\"\n), \"inv\" AS (\n  SELECT\n    \"foo\".\"w_warehouse_sk\" AS \"w_warehouse_sk\",\n    \"foo\".\"i_item_sk\" AS \"i_item_sk\",\n    \"foo\".\"d_moy\" AS \"d_moy\",\n    \"foo\".\"mean\" AS \"mean\",\n    CASE WHEN \"foo\".\"mean\" = 0 THEN NULL ELSE \"foo\".\"stdev\" / \"foo\".\"mean\" END AS \"cov\"\n  FROM \"foo\" AS \"foo\"\n  WHERE\n    CASE WHEN \"foo\".\"mean\" = 0 THEN 0 ELSE \"foo\".\"stdev\" / \"foo\".\"mean\" END > 1\n)\nSELECT\n  \"inv1\".\"w_warehouse_sk\" AS \"w_warehouse_sk\",\n  \"inv1\".\"i_item_sk\" AS \"i_item_sk\",\n  \"inv1\".\"d_moy\" AS \"d_moy\",\n  \"inv1\".\"mean\" AS \"mean\",\n  \"inv1\".\"cov\" AS \"cov\",\n  \"inv2\".\"w_warehouse_sk\" AS \"w_warehouse_sk\",\n  \"inv2\".\"i_item_sk\" AS \"i_item_sk\",\n  \"inv2\".\"d_moy\" AS \"d_moy\",\n  \"inv2\".\"mean\" AS \"mean\",\n  \"inv2\".\"cov\" AS \"cov\"\nFROM \"inv\" AS \"inv1\"\nJOIN \"inv\" AS \"inv2\"\n  ON \"inv1\".\"i_item_sk\" = \"inv2\".\"i_item_sk\"\n  AND \"inv1\".\"w_warehouse_sk\" = \"inv2\".\"w_warehouse_sk\"\n  AND \"inv2\".\"d_moy\" = 2\nWHERE\n  \"inv1\".\"d_moy\" = 1\nORDER BY\n  \"inv1\".\"w_warehouse_sk\",\n  \"inv1\".\"i_item_sk\",\n  \"inv1\".\"d_moy\",\n  \"inv1\".\"mean\",\n  \"inv1\".\"cov\",\n  \"inv2\".\"d_moy\",\n  \"inv2\".\"mean\",\n  \"inv2\".\"cov\";\n\n--------------------------------------\n-- TPC-DS 40\n--------------------------------------\nSELECT\n                w_state ,\n                i_item_id ,\n                Sum(\n                CASE\n                                WHEN (\n                                                                Cast(d_date AS DATE) < Cast ('2002-06-01' AS DATE)) THEN cs_sales_price - COALESCE(cr_refunded_cash,0)\n                                ELSE 0\n                END) AS sales_before ,\n                Sum(\n                CASE\n                                WHEN (\n                                                                Cast(d_date AS DATE) >= Cast ('2002-06-01' AS DATE)) THEN cs_sales_price - COALESCE(cr_refunded_cash,0)\n                                ELSE 0\n                END) AS sales_after\nFROM            catalog_sales\nLEFT OUTER JOIN catalog_returns\nON              (\n                                cs_order_number = cr_order_number\n                AND             cs_item_sk = cr_item_sk) ,\n                warehouse ,\n                item ,\n                date_dim\nWHERE           i_current_price BETWEEN 0.99 AND             1.49\nAND             i_item_sk = cs_item_sk\nAND             cs_warehouse_sk = w_warehouse_sk\nAND             cs_sold_date_sk = d_date_sk\nAND             d_date BETWEEN (Cast ('2002-06-01' AS DATE) - INTERVAL '30' day) AND             (\n                                cast ('2002-06-01' AS date) + INTERVAL '30' day)\nGROUP BY        w_state,\n                i_item_id\nORDER BY        w_state,\n                i_item_id\nLIMIT 100;\nSELECT\n  \"warehouse\".\"w_state\" AS \"w_state\",\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  SUM(\n    CASE\n      WHEN CAST(\"date_dim\".\"d_date\" AS DATE) < CAST('2002-06-01' AS DATE)\n      THEN \"catalog_sales\".\"cs_sales_price\" - COALESCE(\"catalog_returns\".\"cr_refunded_cash\", 0)\n      ELSE 0\n    END\n  ) AS \"sales_before\",\n  SUM(\n    CASE\n      WHEN CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2002-06-01' AS DATE)\n      THEN \"catalog_sales\".\"cs_sales_price\" - COALESCE(\"catalog_returns\".\"cr_refunded_cash\", 0)\n      ELSE 0\n    END\n  ) AS \"sales_after\"\nFROM \"catalog_sales\" AS \"catalog_sales\"\nLEFT JOIN \"catalog_returns\" AS \"catalog_returns\"\n  ON \"catalog_returns\".\"cr_item_sk\" = \"catalog_sales\".\"cs_item_sk\"\n  AND \"catalog_returns\".\"cr_order_number\" = \"catalog_sales\".\"cs_order_number\"\nJOIN \"warehouse\" AS \"warehouse\"\n  ON \"catalog_sales\".\"cs_warehouse_sk\" = \"warehouse\".\"w_warehouse_sk\"\nJOIN \"item\" AS \"item\"\n  ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  AND \"item\".\"i_current_price\" <= 1.49\n  AND \"item\".\"i_current_price\" >= 0.99\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2002-07-01' AS DATE)\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2002-05-02' AS DATE)\nGROUP BY\n  \"warehouse\".\"w_state\",\n  \"item\".\"i_item_id\"\nORDER BY\n  \"w_state\",\n  \"i_item_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 41\n--------------------------------------\nSELECT Distinct(i_product_name)\nFROM   item i1\nWHERE  i_manufact_id BETWEEN 765 AND 765 + 40\n       AND (SELECT Count(*) AS item_cnt\n            FROM   item\n            WHERE  ( i_manufact = i1.i_manufact\n                     AND ( ( i_category = 'Women'\n                             AND ( i_color = 'dim'\n                                    OR i_color = 'green' )\n                             AND ( i_units = 'Gross'\n                                    OR i_units = 'Dozen' )\n                             AND ( i_size = 'economy'\n                                    OR i_size = 'petite' ) )\n                            OR ( i_category = 'Women'\n                                 AND ( i_color = 'navajo'\n                                        OR i_color = 'aquamarine' )\n                                 AND ( i_units = 'Case'\n                                        OR i_units = 'Unknown' )\n                                 AND ( i_size = 'large'\n                                        OR i_size = 'N/A' ) )\n                            OR ( i_category = 'Men'\n                                 AND ( i_color = 'indian'\n                                        OR i_color = 'dark' )\n                                 AND ( i_units = 'Oz'\n                                        OR i_units = 'Lb' )\n                                 AND ( i_size = 'extra large'\n                                        OR i_size = 'small' ) )\n                            OR ( i_category = 'Men'\n                                 AND ( i_color = 'peach'\n                                        OR i_color = 'purple' )\n                                 AND ( i_units = 'Tbl'\n                                        OR i_units = 'Bunch' )\n                                 AND ( i_size = 'economy'\n                                        OR i_size = 'petite' ) ) ) )\n                    OR ( i_manufact = i1.i_manufact\n                         AND ( ( i_category = 'Women'\n                                 AND ( i_color = 'orchid'\n                                        OR i_color = 'peru' )\n                                 AND ( i_units = 'Carton'\n                                        OR i_units = 'Cup' )\n                                 AND ( i_size = 'economy'\n                                        OR i_size = 'petite' ) )\n                                OR ( i_category = 'Women'\n                                     AND ( i_color = 'violet'\n                                            OR i_color = 'papaya' )\n                                     AND ( i_units = 'Ounce'\n                                            OR i_units = 'Box' )\n                                     AND ( i_size = 'large'\n                                            OR i_size = 'N/A' ) )\n                                OR ( i_category = 'Men'\n                                     AND ( i_color = 'drab'\n                                            OR i_color = 'grey' )\n                                     AND ( i_units = 'Each'\n                                            OR i_units = 'N/A' )\n                                     AND ( i_size = 'extra large'\n                                            OR i_size = 'small' ) )\n                                OR ( i_category = 'Men'\n                                     AND ( i_color = 'chocolate'\n                                            OR i_color = 'antique' )\n                                     AND ( i_units = 'Dram'\n                                            OR i_units = 'Gram' )\n                                     AND ( i_size = 'economy'\n                                            OR i_size = 'petite' ) ) ) )) > 0\nORDER  BY i_product_name\nLIMIT 100;\nSELECT DISTINCT\n  \"i1\".\"i_product_name\" AS \"i_product_name\"\nFROM \"item\" AS \"i1\"\nWHERE\n  \"i1\".\"i_manufact_id\" <= 805\n  AND \"i1\".\"i_manufact_id\" >= 765\n  AND (\n    SELECT\n      COUNT(*) AS \"item_cnt\"\n    FROM \"item\" AS \"item\"\n    WHERE\n      (\n        \"i1\".\"i_manufact\" = \"item\".\"i_manufact\"\n        AND (\n          (\n            \"item\".\"i_category\" = 'Men'\n            AND (\n              \"item\".\"i_color\" = 'antique' OR \"item\".\"i_color\" = 'chocolate'\n            )\n            AND (\n              \"item\".\"i_size\" = 'economy' OR \"item\".\"i_size\" = 'petite'\n            )\n            AND (\n              \"item\".\"i_units\" = 'Dram' OR \"item\".\"i_units\" = 'Gram'\n            )\n          )\n          OR (\n            \"item\".\"i_category\" = 'Men'\n            AND (\n              \"item\".\"i_color\" = 'drab' OR \"item\".\"i_color\" = 'grey'\n            )\n            AND (\n              \"item\".\"i_size\" = 'extra large' OR \"item\".\"i_size\" = 'small'\n            )\n            AND (\n              \"item\".\"i_units\" = 'Each' OR \"item\".\"i_units\" = 'N/A'\n            )\n          )\n          OR (\n            \"item\".\"i_category\" = 'Women'\n            AND (\n              \"item\".\"i_color\" = 'orchid' OR \"item\".\"i_color\" = 'peru'\n            )\n            AND (\n              \"item\".\"i_size\" = 'economy' OR \"item\".\"i_size\" = 'petite'\n            )\n            AND (\n              \"item\".\"i_units\" = 'Carton' OR \"item\".\"i_units\" = 'Cup'\n            )\n          )\n          OR (\n            \"item\".\"i_category\" = 'Women'\n            AND (\n              \"item\".\"i_color\" = 'papaya' OR \"item\".\"i_color\" = 'violet'\n            )\n            AND (\n              \"item\".\"i_size\" = 'N/A' OR \"item\".\"i_size\" = 'large'\n            )\n            AND (\n              \"item\".\"i_units\" = 'Box' OR \"item\".\"i_units\" = 'Ounce'\n            )\n          )\n        )\n      )\n      OR (\n        \"i1\".\"i_manufact\" = \"item\".\"i_manufact\"\n        AND (\n          (\n            \"item\".\"i_category\" = 'Men'\n            AND (\n              \"item\".\"i_color\" = 'dark' OR \"item\".\"i_color\" = 'indian'\n            )\n            AND (\n              \"item\".\"i_size\" = 'extra large' OR \"item\".\"i_size\" = 'small'\n            )\n            AND (\n              \"item\".\"i_units\" = 'Lb' OR \"item\".\"i_units\" = 'Oz'\n            )\n          )\n          OR (\n            \"item\".\"i_category\" = 'Men'\n            AND (\n              \"item\".\"i_color\" = 'peach' OR \"item\".\"i_color\" = 'purple'\n            )\n            AND (\n              \"item\".\"i_size\" = 'economy' OR \"item\".\"i_size\" = 'petite'\n            )\n            AND (\n              \"item\".\"i_units\" = 'Bunch' OR \"item\".\"i_units\" = 'Tbl'\n            )\n          )\n          OR (\n            \"item\".\"i_category\" = 'Women'\n            AND (\n              \"item\".\"i_color\" = 'aquamarine' OR \"item\".\"i_color\" = 'navajo'\n            )\n            AND (\n              \"item\".\"i_size\" = 'N/A' OR \"item\".\"i_size\" = 'large'\n            )\n            AND (\n              \"item\".\"i_units\" = 'Case' OR \"item\".\"i_units\" = 'Unknown'\n            )\n          )\n          OR (\n            \"item\".\"i_category\" = 'Women'\n            AND (\n              \"item\".\"i_color\" = 'dim' OR \"item\".\"i_color\" = 'green'\n            )\n            AND (\n              \"item\".\"i_size\" = 'economy' OR \"item\".\"i_size\" = 'petite'\n            )\n            AND (\n              \"item\".\"i_units\" = 'Dozen' OR \"item\".\"i_units\" = 'Gross'\n            )\n          )\n        )\n      )\n  ) > 0\nORDER BY\n  \"i1\".\"i_product_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 42\n--------------------------------------\n# execute: true\nSELECT dt.d_year,\n               item.i_category_id,\n               item.i_category,\n               Sum(ss_ext_sales_price) AS \"_col_3\"\nFROM   date_dim dt,\n       store_sales,\n       item\nWHERE  dt.d_date_sk = store_sales.ss_sold_date_sk\n       AND store_sales.ss_item_sk = item.i_item_sk\n       AND item.i_manager_id = 1\n       AND dt.d_moy = 12\n       AND dt.d_year = 2000\nGROUP  BY dt.d_year,\n          item.i_category_id,\n          item.i_category\nORDER  BY Sum(ss_ext_sales_price) DESC,\n          dt.d_year,\n          item.i_category_id,\n          item.i_category\nLIMIT 100;\nSELECT\n  \"dt\".\"d_year\" AS \"d_year\",\n  \"item\".\"i_category_id\" AS \"i_category_id\",\n  \"item\".\"i_category\" AS \"i_category\",\n  SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"_col_3\"\nFROM \"date_dim\" AS \"dt\"\nJOIN \"store_sales\" AS \"store_sales\"\n  ON \"dt\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\" AND \"item\".\"i_manager_id\" = 1\nWHERE\n  \"dt\".\"d_moy\" = 12 AND \"dt\".\"d_year\" = 2000\nGROUP BY\n  \"dt\".\"d_year\",\n  \"item\".\"i_category_id\",\n  \"item\".\"i_category\"\nORDER BY\n  \"_col_3\" DESC,\n  \"d_year\",\n  \"i_category_id\",\n  \"i_category\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 43\n--------------------------------------\n# execute: true\nSELECT s_store_name,\n               s_store_id,\n               Sum(CASE\n                     WHEN ( d_day_name = 'Sunday' ) THEN ss_sales_price\n                     ELSE NULL\n                   END) sun_sales,\n               Sum(CASE\n                     WHEN ( d_day_name = 'Monday' ) THEN ss_sales_price\n                     ELSE NULL\n                   END) mon_sales,\n               Sum(CASE\n                     WHEN ( d_day_name = 'Tuesday' ) THEN ss_sales_price\n                     ELSE NULL\n                   END) tue_sales,\n               Sum(CASE\n                     WHEN ( d_day_name = 'Wednesday' ) THEN ss_sales_price\n                     ELSE NULL\n                   END) wed_sales,\n               Sum(CASE\n                     WHEN ( d_day_name = 'Thursday' ) THEN ss_sales_price\n                     ELSE NULL\n                   END) thu_sales,\n               Sum(CASE\n                     WHEN ( d_day_name = 'Friday' ) THEN ss_sales_price\n                     ELSE NULL\n                   END) fri_sales,\n               Sum(CASE\n                     WHEN ( d_day_name = 'Saturday' ) THEN ss_sales_price\n                     ELSE NULL\n                   END) sat_sales\nFROM   date_dim,\n       store_sales,\n       store\nWHERE  d_date_sk = ss_sold_date_sk\n       AND s_store_sk = ss_store_sk\n       AND s_gmt_offset = -5\n       AND d_year = 2002\nGROUP  BY s_store_name,\n          s_store_id\nORDER  BY s_store_name,\n          s_store_id,\n          sun_sales,\n          mon_sales,\n          tue_sales,\n          wed_sales,\n          thu_sales,\n          fri_sales,\n          sat_sales\nLIMIT 100;\nSELECT\n  \"store\".\"s_store_name\" AS \"s_store_name\",\n  \"store\".\"s_store_id\" AS \"s_store_id\",\n  SUM(\n    CASE\n      WHEN \"date_dim\".\"d_day_name\" = 'Sunday'\n      THEN \"store_sales\".\"ss_sales_price\"\n      ELSE NULL\n    END\n  ) AS \"sun_sales\",\n  SUM(\n    CASE\n      WHEN \"date_dim\".\"d_day_name\" = 'Monday'\n      THEN \"store_sales\".\"ss_sales_price\"\n      ELSE NULL\n    END\n  ) AS \"mon_sales\",\n  SUM(\n    CASE\n      WHEN \"date_dim\".\"d_day_name\" = 'Tuesday'\n      THEN \"store_sales\".\"ss_sales_price\"\n      ELSE NULL\n    END\n  ) AS \"tue_sales\",\n  SUM(\n    CASE\n      WHEN \"date_dim\".\"d_day_name\" = 'Wednesday'\n      THEN \"store_sales\".\"ss_sales_price\"\n      ELSE NULL\n    END\n  ) AS \"wed_sales\",\n  SUM(\n    CASE\n      WHEN \"date_dim\".\"d_day_name\" = 'Thursday'\n      THEN \"store_sales\".\"ss_sales_price\"\n      ELSE NULL\n    END\n  ) AS \"thu_sales\",\n  SUM(\n    CASE\n      WHEN \"date_dim\".\"d_day_name\" = 'Friday'\n      THEN \"store_sales\".\"ss_sales_price\"\n      ELSE NULL\n    END\n  ) AS \"fri_sales\",\n  SUM(\n    CASE\n      WHEN \"date_dim\".\"d_day_name\" = 'Saturday'\n      THEN \"store_sales\".\"ss_sales_price\"\n      ELSE NULL\n    END\n  ) AS \"sat_sales\"\nFROM \"date_dim\" AS \"date_dim\"\nJOIN \"store_sales\" AS \"store_sales\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_gmt_offset\" = -5\n  AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nWHERE\n  \"date_dim\".\"d_year\" = 2002\nGROUP BY\n  \"store\".\"s_store_name\",\n  \"store\".\"s_store_id\"\nORDER BY\n  \"s_store_name\",\n  \"s_store_id\",\n  \"sun_sales\",\n  \"mon_sales\",\n  \"tue_sales\",\n  \"wed_sales\",\n  \"thu_sales\",\n  \"fri_sales\",\n  \"sat_sales\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 44\n--------------------------------------\nSELECT asceding.rnk,\n               i1.i_product_name best_performing,\n               i2.i_product_name worst_performing\nFROM  (SELECT *\n       FROM   (SELECT item_sk,\n                      Rank()\n                        OVER (\n                          ORDER BY rank_col ASC) rnk\n               FROM   (SELECT ss_item_sk         item_sk,\n                              Avg(ss_net_profit) rank_col\n                       FROM   store_sales ss1\n                       WHERE  ss_store_sk = 4\n                       GROUP  BY ss_item_sk\n                       HAVING Avg(ss_net_profit) > 0.9 *\n                              (SELECT Avg(ss_net_profit)\n                                      rank_col\n                               FROM   store_sales\n                               WHERE  ss_store_sk = 4\n                                      AND ss_cdemo_sk IS\n                                          NULL\n                               GROUP  BY ss_store_sk))V1)\n              V11\n       WHERE  rnk < 11) asceding,\n      (SELECT *\n       FROM   (SELECT item_sk,\n                      Rank()\n                        OVER (\n                          ORDER BY rank_col DESC) rnk\n               FROM   (SELECT ss_item_sk         item_sk,\n                              Avg(ss_net_profit) rank_col\n                       FROM   store_sales ss1\n                       WHERE  ss_store_sk = 4\n                       GROUP  BY ss_item_sk\n                       HAVING Avg(ss_net_profit) > 0.9 *\n                              (SELECT Avg(ss_net_profit)\n                                      rank_col\n                               FROM   store_sales\n                               WHERE  ss_store_sk = 4\n                                      AND ss_cdemo_sk IS\n                                          NULL\n                               GROUP  BY ss_store_sk))V2)\n              V21\n       WHERE  rnk < 11) descending,\n      item i1,\n      item i2\nWHERE  asceding.rnk = descending.rnk\n       AND i1.i_item_sk = asceding.item_sk\n       AND i2.i_item_sk = descending.item_sk\nORDER  BY asceding.rnk\nLIMIT 100;\nWITH \"_u_0\" AS (\n  SELECT\n    AVG(\"store_sales\".\"ss_net_profit\") AS \"rank_col\"\n  FROM \"store_sales\" AS \"store_sales\"\n  WHERE\n    \"store_sales\".\"ss_cdemo_sk\" IS NULL AND \"store_sales\".\"ss_store_sk\" = 4\n  GROUP BY\n    \"store_sales\".\"ss_store_sk\"\n), \"v1\" AS (\n  SELECT\n    \"ss1\".\"ss_item_sk\" AS \"item_sk\",\n    AVG(\"ss1\".\"ss_net_profit\") AS \"rank_col\"\n  FROM \"store_sales\" AS \"ss1\"\n  CROSS JOIN \"_u_0\" AS \"_u_0\"\n  WHERE\n    \"ss1\".\"ss_store_sk\" = 4\n  GROUP BY\n    \"ss1\".\"ss_item_sk\"\n  HAVING\n    0.9 * MAX(\"_u_0\".\"rank_col\") < AVG(\"ss1\".\"ss_net_profit\")\n), \"v11\" AS (\n  SELECT\n    \"v1\".\"item_sk\" AS \"item_sk\",\n    RANK() OVER (ORDER BY \"v1\".\"rank_col\") AS \"rnk\"\n  FROM \"v1\" AS \"v1\"\n), \"v2\" AS (\n  SELECT\n    \"ss1\".\"ss_item_sk\" AS \"item_sk\",\n    AVG(\"ss1\".\"ss_net_profit\") AS \"rank_col\"\n  FROM \"store_sales\" AS \"ss1\"\n  CROSS JOIN \"_u_0\" AS \"_u_1\"\n  WHERE\n    \"ss1\".\"ss_store_sk\" = 4\n  GROUP BY\n    \"ss1\".\"ss_item_sk\"\n  HAVING\n    0.9 * MAX(\"_u_1\".\"rank_col\") < AVG(\"ss1\".\"ss_net_profit\")\n), \"v21\" AS (\n  SELECT\n    \"v2\".\"item_sk\" AS \"item_sk\",\n    RANK() OVER (ORDER BY \"v2\".\"rank_col\" DESC) AS \"rnk\"\n  FROM \"v2\" AS \"v2\"\n)\nSELECT\n  \"v11\".\"rnk\" AS \"rnk\",\n  \"i1\".\"i_product_name\" AS \"best_performing\",\n  \"i2\".\"i_product_name\" AS \"worst_performing\"\nFROM \"v11\" AS \"v11\"\nJOIN \"v21\" AS \"v21\"\n  ON \"v11\".\"rnk\" = \"v21\".\"rnk\" AND \"v21\".\"rnk\" < 11\nJOIN \"item\" AS \"i1\"\n  ON \"i1\".\"i_item_sk\" = \"v11\".\"item_sk\"\nJOIN \"item\" AS \"i2\"\n  ON \"i2\".\"i_item_sk\" = \"v21\".\"item_sk\"\nWHERE\n  \"v11\".\"rnk\" < 11\nORDER BY\n  \"v11\".\"rnk\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 45\n--------------------------------------\n# execute: true\nSELECT ca_zip,\n               ca_state,\n               Sum(ws_sales_price) AS \"_col_2\"\nFROM   web_sales,\n       customer,\n       customer_address,\n       date_dim,\n       item\nWHERE  ws_bill_customer_sk = c_customer_sk\n       AND c_current_addr_sk = ca_address_sk\n       AND ws_item_sk = i_item_sk\n       AND ( SUBSTRING(ca_zip, 1, 5) IN ( '85669', '86197', '88274', '83405',\n                                       '86475', '85392', '85460', '80348',\n                                       '81792' )\n              OR i_item_id IN (SELECT i_item_id\n                               FROM   item\n                               WHERE  i_item_sk IN ( 2, 3, 5, 7,\n                                                     11, 13, 17, 19,\n                                                     23, 29 )) )\n       AND ws_sold_date_sk = d_date_sk\n       AND d_qoy = 1\n       AND d_year = 2000\nGROUP  BY ca_zip,\n          ca_state\nORDER  BY ca_zip,\n          ca_state\nLIMIT 100;\nWITH \"_u_0\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\"\n  FROM \"item\" AS \"item\"\n  WHERE\n    \"item\".\"i_item_sk\" IN (2, 3, 5, 7, 11, 13, 17, 19, 23, 29)\n  GROUP BY\n    \"item\".\"i_item_id\"\n)\nSELECT\n  \"customer_address\".\"ca_zip\" AS \"ca_zip\",\n  \"customer_address\".\"ca_state\" AS \"ca_state\",\n  SUM(\"web_sales\".\"ws_sales_price\") AS \"_col_2\"\nFROM \"web_sales\" AS \"web_sales\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_customer_sk\" = \"web_sales\".\"ws_bill_customer_sk\"\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  AND \"date_dim\".\"d_qoy\" = 1\n  AND \"date_dim\".\"d_year\" = 2000\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"i_item_id\" = \"item\".\"i_item_id\"\nWHERE\n  NOT \"_u_0\".\"i_item_id\" IS NULL\n  OR SUBSTRING(\"customer_address\".\"ca_zip\", 1, 5) IN ('85669', '86197', '88274', '83405', '86475', '85392', '85460', '80348', '81792')\nGROUP BY\n  \"customer_address\".\"ca_zip\",\n  \"customer_address\".\"ca_state\"\nORDER BY\n  \"ca_zip\",\n  \"ca_state\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 46\n--------------------------------------\n# execute: true\nSELECT c_last_name,\n               c_first_name,\n               ca_city,\n               bought_city,\n               ss_ticket_number,\n               amt,\n               profit\nFROM   (SELECT ss_ticket_number,\n               ss_customer_sk,\n               ca_city            bought_city,\n               Sum(ss_coupon_amt) amt,\n               Sum(ss_net_profit) profit\n        FROM   store_sales,\n               date_dim,\n               store,\n               household_demographics,\n               customer_address\n        WHERE  store_sales.ss_sold_date_sk = date_dim.d_date_sk\n               AND store_sales.ss_store_sk = store.s_store_sk\n               AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk\n               AND store_sales.ss_addr_sk = customer_address.ca_address_sk\n               AND ( household_demographics.hd_dep_count = 6\n                      OR household_demographics.hd_vehicle_count = 0 )\n               AND date_dim.d_dow IN ( 6, 0 )\n               AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 )\n               AND store.s_city IN ( 'Midway', 'Fairview', 'Fairview',\n                                     'Fairview',\n                                     'Fairview' )\n        GROUP  BY ss_ticket_number,\n                  ss_customer_sk,\n                  ss_addr_sk,\n                  ca_city) dn,\n       customer,\n       customer_address current_addr\nWHERE  ss_customer_sk = c_customer_sk\n       AND customer.c_current_addr_sk = current_addr.ca_address_sk\n       AND current_addr.ca_city <> bought_city\nORDER  BY c_last_name,\n          c_first_name,\n          ca_city,\n          bought_city,\n          ss_ticket_number\nLIMIT 100;\nWITH \"dn\" AS (\n  SELECT\n    \"store_sales\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\" AS \"ss_customer_sk\",\n    \"customer_address\".\"ca_city\" AS \"bought_city\",\n    SUM(\"store_sales\".\"ss_coupon_amt\") AS \"amt\",\n    SUM(\"store_sales\".\"ss_net_profit\") AS \"profit\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer_address\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_dow\" IN (6, 0)\n    AND \"date_dim\".\"d_year\" IN (2000, 2001, 2002)\n  JOIN \"household_demographics\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n    AND (\n      \"household_demographics\".\"hd_dep_count\" = 6\n      OR \"household_demographics\".\"hd_vehicle_count\" = 0\n    )\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_city\" IN ('Midway', 'Fairview', 'Fairview', 'Fairview', 'Fairview')\n    AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\",\n    \"store_sales\".\"ss_addr_sk\",\n    \"customer_address\".\"ca_city\"\n)\nSELECT\n  \"customer\".\"c_last_name\" AS \"c_last_name\",\n  \"customer\".\"c_first_name\" AS \"c_first_name\",\n  \"current_addr\".\"ca_city\" AS \"ca_city\",\n  \"dn\".\"bought_city\" AS \"bought_city\",\n  \"dn\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n  \"dn\".\"amt\" AS \"amt\",\n  \"dn\".\"profit\" AS \"profit\"\nFROM \"dn\" AS \"dn\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_customer_sk\" = \"dn\".\"ss_customer_sk\"\nJOIN \"customer_address\" AS \"current_addr\"\n  ON \"current_addr\".\"ca_address_sk\" = \"customer\".\"c_current_addr_sk\"\n  AND \"current_addr\".\"ca_city\" <> \"dn\".\"bought_city\"\nORDER BY\n  \"c_last_name\",\n  \"c_first_name\",\n  \"ca_city\",\n  \"bought_city\",\n  \"ss_ticket_number\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 47\n--------------------------------------\nWITH v1\n     AS (SELECT i_category,\n                i_brand,\n                s_store_name,\n                s_company_name,\n                d_year,\n                d_moy,\n                Sum(ss_sales_price)         sum_sales,\n                Avg(Sum(ss_sales_price))\n                  OVER (\n                    partition BY i_category, i_brand, s_store_name,\n                  s_company_name,\n                  d_year)\n                                            avg_monthly_sales,\n                Rank()\n                  OVER (\n                    partition BY i_category, i_brand, s_store_name,\n                  s_company_name\n                    ORDER BY d_year, d_moy) rn\n         FROM   item,\n                store_sales,\n                date_dim,\n                store\n         WHERE  ss_item_sk = i_item_sk\n                AND ss_sold_date_sk = d_date_sk\n                AND ss_store_sk = s_store_sk\n                AND ( d_year = 1999\n                       OR ( d_year = 1999 - 1\n                            AND d_moy = 12 )\n                       OR ( d_year = 1999 + 1\n                            AND d_moy = 1 ) )\n         GROUP  BY i_category,\n                   i_brand,\n                   s_store_name,\n                   s_company_name,\n                   d_year,\n                   d_moy),\n     v2\n     AS (SELECT v1.i_category,\n                v1.d_year,\n                v1.d_moy,\n                v1.avg_monthly_sales,\n                v1.sum_sales,\n                v1_lag.sum_sales  psum,\n                v1_lead.sum_sales nsum\n         FROM   v1,\n                v1 v1_lag,\n                v1 v1_lead\n         WHERE  v1.i_category = v1_lag.i_category\n                AND v1.i_category = v1_lead.i_category\n                AND v1.i_brand = v1_lag.i_brand\n                AND v1.i_brand = v1_lead.i_brand\n                AND v1.s_store_name = v1_lag.s_store_name\n                AND v1.s_store_name = v1_lead.s_store_name\n                AND v1.s_company_name = v1_lag.s_company_name\n                AND v1.s_company_name = v1_lead.s_company_name\n                AND v1.rn = v1_lag.rn + 1\n                AND v1.rn = v1_lead.rn - 1)\nSELECT *\nFROM   v2\nWHERE  d_year = 1999\n       AND avg_monthly_sales > 0\n       AND CASE\n             WHEN avg_monthly_sales > 0 THEN Abs(sum_sales - avg_monthly_sales)\n                                             /\n                                             avg_monthly_sales\n             ELSE NULL\n           END > 0.1\nORDER  BY sum_sales - avg_monthly_sales,\n          3\nLIMIT 100;\nWITH \"v1\" AS (\n  SELECT\n    \"item\".\"i_category\" AS \"i_category\",\n    \"item\".\"i_brand\" AS \"i_brand\",\n    \"store\".\"s_store_name\" AS \"s_store_name\",\n    \"store\".\"s_company_name\" AS \"s_company_name\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\",\n    SUM(\"store_sales\".\"ss_sales_price\") AS \"sum_sales\",\n    AVG(SUM(\"store_sales\".\"ss_sales_price\")) OVER (\n      PARTITION BY \"item\".\"i_category\", \"item\".\"i_brand\", \"store\".\"s_store_name\", \"store\".\"s_company_name\", \"date_dim\".\"d_year\"\n    ) AS \"avg_monthly_sales\",\n    RANK() OVER (\n      PARTITION BY \"item\".\"i_category\", \"item\".\"i_brand\", \"store\".\"s_store_name\", \"store\".\"s_company_name\"\n      ORDER BY \"date_dim\".\"d_year\", \"date_dim\".\"d_moy\"\n    ) AS \"rn\"\n  FROM \"item\" AS \"item\"\n  JOIN \"store_sales\" AS \"store_sales\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND (\n      \"date_dim\".\"d_moy\" = 1 OR \"date_dim\".\"d_moy\" = 12 OR \"date_dim\".\"d_year\" = 1999\n    )\n    AND (\n      \"date_dim\".\"d_moy\" = 1 OR \"date_dim\".\"d_year\" = 1998 OR \"date_dim\".\"d_year\" = 1999\n    )\n    AND (\n      \"date_dim\".\"d_moy\" = 12\n      OR \"date_dim\".\"d_year\" = 1999\n      OR \"date_dim\".\"d_year\" = 2000\n    )\n    AND (\n      \"date_dim\".\"d_year\" = 1998\n      OR \"date_dim\".\"d_year\" = 1999\n      OR \"date_dim\".\"d_year\" = 2000\n    )\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    \"item\".\"i_category\",\n    \"item\".\"i_brand\",\n    \"store\".\"s_store_name\",\n    \"store\".\"s_company_name\",\n    \"date_dim\".\"d_year\",\n    \"date_dim\".\"d_moy\"\n)\nSELECT\n  \"v1\".\"i_category\" AS \"i_category\",\n  \"v1\".\"d_year\" AS \"d_year\",\n  \"v1\".\"d_moy\" AS \"d_moy\",\n  \"v1\".\"avg_monthly_sales\" AS \"avg_monthly_sales\",\n  \"v1\".\"sum_sales\" AS \"sum_sales\",\n  \"v1_lag\".\"sum_sales\" AS \"psum\",\n  \"v1_lead\".\"sum_sales\" AS \"nsum\"\nFROM \"v1\" AS \"v1\"\nJOIN \"v1\" AS \"v1_lag\"\n  ON \"v1\".\"i_brand\" = \"v1_lag\".\"i_brand\"\n  AND \"v1\".\"i_category\" = \"v1_lag\".\"i_category\"\n  AND \"v1\".\"rn\" = \"v1_lag\".\"rn\" + 1\n  AND \"v1\".\"s_company_name\" = \"v1_lag\".\"s_company_name\"\n  AND \"v1\".\"s_store_name\" = \"v1_lag\".\"s_store_name\"\nJOIN \"v1\" AS \"v1_lead\"\n  ON \"v1\".\"i_brand\" = \"v1_lead\".\"i_brand\"\n  AND \"v1\".\"i_category\" = \"v1_lead\".\"i_category\"\n  AND \"v1\".\"rn\" = \"v1_lead\".\"rn\" - 1\n  AND \"v1\".\"s_company_name\" = \"v1_lead\".\"s_company_name\"\n  AND \"v1\".\"s_store_name\" = \"v1_lead\".\"s_store_name\"\nWHERE\n  \"v1\".\"avg_monthly_sales\" > 0\n  AND \"v1\".\"d_year\" = 1999\n  AND CASE\n    WHEN \"v1\".\"avg_monthly_sales\" > 0\n    THEN ABS(\"v1\".\"sum_sales\" - \"v1\".\"avg_monthly_sales\") / \"v1\".\"avg_monthly_sales\"\n    ELSE NULL\n  END > 0.1\nORDER BY\n  \"v1\".\"sum_sales\" - \"v1\".\"avg_monthly_sales\",\n  \"d_moy\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 48\n--------------------------------------\n# execute: true\nSELECT Sum (ss_quantity) AS \"_col_0\"\nFROM   store_sales,\n       store,\n       customer_demographics,\n       customer_address,\n       date_dim\nWHERE  s_store_sk = ss_store_sk\n       AND ss_sold_date_sk = d_date_sk\n       AND d_year = 1999\n       AND ( ( cd_demo_sk = ss_cdemo_sk\n               AND cd_marital_status = 'W'\n               AND cd_education_status = 'Secondary'\n               AND ss_sales_price BETWEEN 100.00 AND 150.00 )\n              OR ( cd_demo_sk = ss_cdemo_sk\n                   AND cd_marital_status = 'M'\n                   AND cd_education_status = 'Advanced Degree'\n                   AND ss_sales_price BETWEEN 50.00 AND 100.00 )\n              OR ( cd_demo_sk = ss_cdemo_sk\n                   AND cd_marital_status = 'D'\n                   AND cd_education_status = '2 yr Degree'\n                   AND ss_sales_price BETWEEN 150.00 AND 200.00 ) )\n       AND ( ( ss_addr_sk = ca_address_sk\n               AND ca_country = 'United States'\n               AND ca_state IN ( 'TX', 'NE', 'MO' )\n               AND ss_net_profit BETWEEN 0 AND 2000 )\n              OR ( ss_addr_sk = ca_address_sk\n                   AND ca_country = 'United States'\n                   AND ca_state IN ( 'CO', 'TN', 'ND' )\n                   AND ss_net_profit BETWEEN 150 AND 3000 )\n              OR ( ss_addr_sk = ca_address_sk\n                   AND ca_country = 'United States'\n                   AND ca_state IN ( 'OK', 'PA', 'CA' )\n                   AND ss_net_profit BETWEEN 50 AND 25000 ) );\nSELECT\n  SUM(\"store_sales\".\"ss_quantity\") AS \"_col_0\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"customer_address\" AS \"customer_address\"\n  ON (\n    \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n    AND \"customer_address\".\"ca_country\" = 'United States'\n    AND \"customer_address\".\"ca_state\" IN ('CO', 'TN', 'ND')\n    AND \"store_sales\".\"ss_net_profit\" <= 3000\n    AND \"store_sales\".\"ss_net_profit\" >= 150\n  )\n  OR (\n    \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n    AND \"customer_address\".\"ca_country\" = 'United States'\n    AND \"customer_address\".\"ca_state\" IN ('OK', 'PA', 'CA')\n    AND \"store_sales\".\"ss_net_profit\" <= 25000\n    AND \"store_sales\".\"ss_net_profit\" >= 50\n  )\n  OR (\n    \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n    AND \"customer_address\".\"ca_country\" = 'United States'\n    AND \"customer_address\".\"ca_state\" IN ('TX', 'NE', 'MO')\n    AND \"store_sales\".\"ss_net_profit\" <= 2000\n    AND \"store_sales\".\"ss_net_profit\" >= 0\n  )\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON (\n    \"customer_demographics\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n    AND \"customer_demographics\".\"cd_education_status\" = '2 yr Degree'\n    AND \"customer_demographics\".\"cd_marital_status\" = 'D'\n    AND \"store_sales\".\"ss_sales_price\" <= 200.00\n    AND \"store_sales\".\"ss_sales_price\" >= 150.00\n  )\n  OR (\n    \"customer_demographics\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n    AND \"customer_demographics\".\"cd_education_status\" = 'Advanced Degree'\n    AND \"customer_demographics\".\"cd_marital_status\" = 'M'\n    AND \"store_sales\".\"ss_sales_price\" <= 100.00\n    AND \"store_sales\".\"ss_sales_price\" >= 50.00\n  )\n  OR (\n    \"customer_demographics\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n    AND \"customer_demographics\".\"cd_education_status\" = 'Secondary'\n    AND \"customer_demographics\".\"cd_marital_status\" = 'W'\n    AND \"store_sales\".\"ss_sales_price\" <= 150.00\n    AND \"store_sales\".\"ss_sales_price\" >= 100.00\n  )\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"date_dim\".\"d_year\" = 1999\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\";\n\n--------------------------------------\n-- TPC-DS 49\n--------------------------------------\nSELECT 'web' AS channel,\n               web.item,\n               web.return_ratio,\n               web.return_rank,\n               web.currency_rank\nFROM   (SELECT item,\n               return_ratio,\n               currency_ratio,\n               Rank()\n                 OVER (\n                   ORDER BY return_ratio)   AS return_rank,\n               Rank()\n                 OVER (\n                   ORDER BY currency_ratio) AS currency_rank\n        FROM   (SELECT ws.ws_item_sk                                       AS\n                       item,\n                       ( Cast(Sum(COALESCE(wr.wr_return_quantity, 0)) AS DEC(15,\n                              4)) /\n                         Cast(\n                         Sum(COALESCE(ws.ws_quantity, 0)) AS DEC(15, 4)) ) AS\n                       return_ratio,\n                       ( Cast(Sum(COALESCE(wr.wr_return_amt, 0)) AS DEC(15, 4))\n                         / Cast(\n                         Sum(\n                         COALESCE(ws.ws_net_paid, 0)) AS DEC(15,\n                         4)) )                                             AS\n                       currency_ratio\n                FROM   web_sales ws\n                       LEFT OUTER JOIN web_returns wr\n                                    ON ( ws.ws_order_number = wr.wr_order_number\n                                         AND ws.ws_item_sk = wr.wr_item_sk ),\n                       date_dim\n                WHERE  wr.wr_return_amt > 10000\n                       AND ws.ws_net_profit > 1\n                       AND ws.ws_net_paid > 0\n                       AND ws.ws_quantity > 0\n                       AND ws_sold_date_sk = d_date_sk\n                       AND d_year = 1999\n                       AND d_moy = 12\n                GROUP  BY ws.ws_item_sk) in_web) web\nWHERE  ( web.return_rank <= 10\n          OR web.currency_rank <= 10 )\nUNION\nSELECT 'catalog' AS channel,\n       catalog.item,\n       catalog.return_ratio,\n       catalog.return_rank,\n       catalog.currency_rank\nFROM   (SELECT item,\n               return_ratio,\n               currency_ratio,\n               Rank()\n                 OVER (\n                   ORDER BY return_ratio)   AS return_rank,\n               Rank()\n                 OVER (\n                   ORDER BY currency_ratio) AS currency_rank\n        FROM   (SELECT cs.cs_item_sk                                       AS\n                       item,\n                       ( Cast(Sum(COALESCE(cr.cr_return_quantity, 0)) AS DEC(15,\n                              4)) /\n                         Cast(\n                         Sum(COALESCE(cs.cs_quantity, 0)) AS DEC(15, 4)) ) AS\n                       return_ratio,\n                       ( Cast(Sum(COALESCE(cr.cr_return_amount, 0)) AS DEC(15, 4\n                              )) /\n                         Cast(Sum(\n                         COALESCE(cs.cs_net_paid, 0)) AS DEC(\n                         15, 4)) )                                         AS\n                       currency_ratio\n                FROM   catalog_sales cs\n                       LEFT OUTER JOIN catalog_returns cr\n                                    ON ( cs.cs_order_number = cr.cr_order_number\n                                         AND cs.cs_item_sk = cr.cr_item_sk ),\n                       date_dim\n                WHERE  cr.cr_return_amount > 10000\n                       AND cs.cs_net_profit > 1\n                       AND cs.cs_net_paid > 0\n                       AND cs.cs_quantity > 0\n                       AND cs_sold_date_sk = d_date_sk\n                       AND d_year = 1999\n                       AND d_moy = 12\n                GROUP  BY cs.cs_item_sk) in_cat) catalog\nWHERE  ( catalog.return_rank <= 10\n          OR catalog.currency_rank <= 10 )\nUNION\nSELECT 'store' AS channel,\n       store.item,\n       store.return_ratio,\n       store.return_rank,\n       store.currency_rank\nFROM   (SELECT item,\n               return_ratio,\n               currency_ratio,\n               Rank()\n                 OVER (\n                   ORDER BY return_ratio)   AS return_rank,\n               Rank()\n                 OVER (\n                   ORDER BY currency_ratio) AS currency_rank\n        FROM   (SELECT sts.ss_item_sk                                       AS\n                       item,\n                       ( Cast(Sum(COALESCE(sr.sr_return_quantity, 0)) AS DEC(15,\n                              4)) /\n                         Cast(\n                         Sum(COALESCE(sts.ss_quantity, 0)) AS DEC(15, 4)) ) AS\n                       return_ratio,\n                       ( Cast(Sum(COALESCE(sr.sr_return_amt, 0)) AS DEC(15, 4))\n                         / Cast(\n                         Sum(\n                         COALESCE(sts.ss_net_paid, 0)) AS DEC(15, 4)) )     AS\n                       currency_ratio\n                FROM   store_sales sts\n                       LEFT OUTER JOIN store_returns sr\n                                    ON ( sts.ss_ticket_number =\n                                         sr.sr_ticket_number\n                                         AND sts.ss_item_sk = sr.sr_item_sk ),\n                       date_dim\n                WHERE  sr.sr_return_amt > 10000\n                       AND sts.ss_net_profit > 1\n                       AND sts.ss_net_paid > 0\n                       AND sts.ss_quantity > 0\n                       AND ss_sold_date_sk = d_date_sk\n                       AND d_year = 1999\n                       AND d_moy = 12\n                GROUP  BY sts.ss_item_sk) in_store) store\nWHERE  ( store.return_rank <= 10\n          OR store.currency_rank <= 10 )\nORDER  BY 1,\n          4,\n          5\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 12 AND \"date_dim\".\"d_year\" = 1999\n), \"in_web\" AS (\n  SELECT\n    \"ws\".\"ws_item_sk\" AS \"item\",\n    CAST(SUM(COALESCE(\"wr\".\"wr_return_quantity\", 0)) AS DECIMAL(15, 4)) / CAST(SUM(COALESCE(\"ws\".\"ws_quantity\", 0)) AS DECIMAL(15, 4)) AS \"return_ratio\",\n    CAST(SUM(COALESCE(\"wr\".\"wr_return_amt\", 0)) AS DECIMAL(15, 4)) / CAST(SUM(COALESCE(\"ws\".\"ws_net_paid\", 0)) AS DECIMAL(15, 4)) AS \"currency_ratio\"\n  FROM \"web_sales\" AS \"ws\"\n  LEFT JOIN \"web_returns\" AS \"wr\"\n    ON \"wr\".\"wr_item_sk\" = \"ws\".\"ws_item_sk\"\n    AND \"wr\".\"wr_order_number\" = \"ws\".\"ws_order_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"ws\".\"ws_sold_date_sk\"\n  WHERE\n    \"wr\".\"wr_return_amt\" > 10000\n    AND \"ws\".\"ws_net_paid\" > 0\n    AND \"ws\".\"ws_net_profit\" > 1\n    AND \"ws\".\"ws_quantity\" > 0\n  GROUP BY\n    \"ws\".\"ws_item_sk\"\n), \"web\" AS (\n  SELECT\n    \"in_web\".\"item\" AS \"item\",\n    \"in_web\".\"return_ratio\" AS \"return_ratio\",\n    RANK() OVER (ORDER BY \"in_web\".\"return_ratio\") AS \"return_rank\",\n    RANK() OVER (ORDER BY \"in_web\".\"currency_ratio\") AS \"currency_rank\"\n  FROM \"in_web\" AS \"in_web\"\n), \"in_cat\" AS (\n  SELECT\n    \"cs\".\"cs_item_sk\" AS \"item\",\n    CAST(SUM(COALESCE(\"cr\".\"cr_return_quantity\", 0)) AS DECIMAL(15, 4)) / CAST(SUM(COALESCE(\"cs\".\"cs_quantity\", 0)) AS DECIMAL(15, 4)) AS \"return_ratio\",\n    CAST(SUM(COALESCE(\"cr\".\"cr_return_amount\", 0)) AS DECIMAL(15, 4)) / CAST(SUM(COALESCE(\"cs\".\"cs_net_paid\", 0)) AS DECIMAL(15, 4)) AS \"currency_ratio\"\n  FROM \"catalog_sales\" AS \"cs\"\n  LEFT JOIN \"catalog_returns\" AS \"cr\"\n    ON \"cr\".\"cr_item_sk\" = \"cs\".\"cs_item_sk\"\n    AND \"cr\".\"cr_order_number\" = \"cs\".\"cs_order_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"cs\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  WHERE\n    \"cr\".\"cr_return_amount\" > 10000\n    AND \"cs\".\"cs_net_paid\" > 0\n    AND \"cs\".\"cs_net_profit\" > 1\n    AND \"cs\".\"cs_quantity\" > 0\n  GROUP BY\n    \"cs\".\"cs_item_sk\"\n), \"catalog\" AS (\n  SELECT\n    \"in_cat\".\"item\" AS \"item\",\n    \"in_cat\".\"return_ratio\" AS \"return_ratio\",\n    RANK() OVER (ORDER BY \"in_cat\".\"return_ratio\") AS \"return_rank\",\n    RANK() OVER (ORDER BY \"in_cat\".\"currency_ratio\") AS \"currency_rank\"\n  FROM \"in_cat\" AS \"in_cat\"\n), \"in_store\" AS (\n  SELECT\n    \"sts\".\"ss_item_sk\" AS \"item\",\n    CAST(SUM(COALESCE(\"sr\".\"sr_return_quantity\", 0)) AS DECIMAL(15, 4)) / CAST(SUM(COALESCE(\"sts\".\"ss_quantity\", 0)) AS DECIMAL(15, 4)) AS \"return_ratio\",\n    CAST(SUM(COALESCE(\"sr\".\"sr_return_amt\", 0)) AS DECIMAL(15, 4)) / CAST(SUM(COALESCE(\"sts\".\"ss_net_paid\", 0)) AS DECIMAL(15, 4)) AS \"currency_ratio\"\n  FROM \"store_sales\" AS \"sts\"\n  LEFT JOIN \"store_returns\" AS \"sr\"\n    ON \"sr\".\"sr_item_sk\" = \"sts\".\"ss_item_sk\"\n    AND \"sr\".\"sr_ticket_number\" = \"sts\".\"ss_ticket_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"sts\".\"ss_sold_date_sk\"\n  WHERE\n    \"sr\".\"sr_return_amt\" > 10000\n    AND \"sts\".\"ss_net_paid\" > 0\n    AND \"sts\".\"ss_net_profit\" > 1\n    AND \"sts\".\"ss_quantity\" > 0\n  GROUP BY\n    \"sts\".\"ss_item_sk\"\n), \"store\" AS (\n  SELECT\n    \"in_store\".\"item\" AS \"item\",\n    \"in_store\".\"return_ratio\" AS \"return_ratio\",\n    RANK() OVER (ORDER BY \"in_store\".\"return_ratio\") AS \"return_rank\",\n    RANK() OVER (ORDER BY \"in_store\".\"currency_ratio\") AS \"currency_rank\"\n  FROM \"in_store\" AS \"in_store\"\n)\nSELECT\n  'web' AS \"channel\",\n  \"web\".\"item\" AS \"item\",\n  \"web\".\"return_ratio\" AS \"return_ratio\",\n  \"web\".\"return_rank\" AS \"return_rank\",\n  \"web\".\"currency_rank\" AS \"currency_rank\"\nFROM \"web\" AS \"web\"\nWHERE\n  \"web\".\"currency_rank\" <= 10 OR \"web\".\"return_rank\" <= 10\nUNION\nSELECT\n  'catalog' AS \"channel\",\n  \"catalog\".\"item\" AS \"item\",\n  \"catalog\".\"return_ratio\" AS \"return_ratio\",\n  \"catalog\".\"return_rank\" AS \"return_rank\",\n  \"catalog\".\"currency_rank\" AS \"currency_rank\"\nFROM \"catalog\" AS \"catalog\"\nWHERE\n  \"catalog\".\"currency_rank\" <= 10 OR \"catalog\".\"return_rank\" <= 10\nUNION\nSELECT\n  'store' AS \"channel\",\n  \"store\".\"item\" AS \"item\",\n  \"store\".\"return_ratio\" AS \"return_ratio\",\n  \"store\".\"return_rank\" AS \"return_rank\",\n  \"store\".\"currency_rank\" AS \"currency_rank\"\nFROM \"store\" AS \"store\"\nWHERE\n  \"store\".\"currency_rank\" <= 10 OR \"store\".\"return_rank\" <= 10\nORDER BY\n  \"channel\",\n  \"return_rank\",\n  \"currency_rank\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 50\n--------------------------------------\n# execute: true\nSELECT s_store_name,\n               s_company_id,\n               s_street_number,\n               s_street_name,\n               s_street_type,\n               s_suite_number,\n               s_city,\n               s_county,\n               s_state,\n               s_zip,\n               Sum(CASE\n                     WHEN ( sr_returned_date_sk - ss_sold_date_sk <= 30 ) THEN 1\n                     ELSE 0\n                   END) AS \"30 days\",\n               Sum(CASE\n                     WHEN ( sr_returned_date_sk - ss_sold_date_sk > 30 )\n                          AND ( sr_returned_date_sk - ss_sold_date_sk <= 60 )\n                   THEN 1\n                     ELSE 0\n                   END) AS \"31-60 days\",\n               Sum(CASE\n                     WHEN ( sr_returned_date_sk - ss_sold_date_sk > 60 )\n                          AND ( sr_returned_date_sk - ss_sold_date_sk <= 90 )\n                   THEN 1\n                     ELSE 0\n                   END) AS \"61-90 days\",\n               Sum(CASE\n                     WHEN ( sr_returned_date_sk - ss_sold_date_sk > 90 )\n                          AND ( sr_returned_date_sk - ss_sold_date_sk <= 120 )\n                   THEN 1\n                     ELSE 0\n                   END) AS \"91-120 days\",\n               Sum(CASE\n                     WHEN ( sr_returned_date_sk - ss_sold_date_sk > 120 ) THEN 1\n                     ELSE 0\n                   END) AS \">120 days\"\nFROM   store_sales,\n       store_returns,\n       store,\n       date_dim d1,\n       date_dim d2\nWHERE  d2.d_year = 2002\n       AND d2.d_moy = 9\n       AND ss_ticket_number = sr_ticket_number\n       AND ss_item_sk = sr_item_sk\n       AND ss_sold_date_sk = d1.d_date_sk\n       AND sr_returned_date_sk = d2.d_date_sk\n       AND ss_customer_sk = sr_customer_sk\n       AND ss_store_sk = s_store_sk\nGROUP  BY s_store_name,\n          s_company_id,\n          s_street_number,\n          s_street_name,\n          s_street_type,\n          s_suite_number,\n          s_city,\n          s_county,\n          s_state,\n          s_zip\nORDER  BY s_store_name,\n          s_company_id,\n          s_street_number,\n          s_street_name,\n          s_street_type,\n          s_suite_number,\n          s_city,\n          s_county,\n          s_state,\n          s_zip\nLIMIT 100;\nSELECT\n  \"store\".\"s_store_name\" AS \"s_store_name\",\n  \"store\".\"s_company_id\" AS \"s_company_id\",\n  \"store\".\"s_street_number\" AS \"s_street_number\",\n  \"store\".\"s_street_name\" AS \"s_street_name\",\n  \"store\".\"s_street_type\" AS \"s_street_type\",\n  \"store\".\"s_suite_number\" AS \"s_suite_number\",\n  \"store\".\"s_city\" AS \"s_city\",\n  \"store\".\"s_county\" AS \"s_county\",\n  \"store\".\"s_state\" AS \"s_state\",\n  \"store\".\"s_zip\" AS \"s_zip\",\n  SUM(\n    CASE\n      WHEN \"store_returns\".\"sr_returned_date_sk\" - \"store_sales\".\"ss_sold_date_sk\" <= 30\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"30 days\",\n  SUM(\n    CASE\n      WHEN \"store_returns\".\"sr_returned_date_sk\" - \"store_sales\".\"ss_sold_date_sk\" <= 60\n      AND \"store_returns\".\"sr_returned_date_sk\" - \"store_sales\".\"ss_sold_date_sk\" > 30\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"31-60 days\",\n  SUM(\n    CASE\n      WHEN \"store_returns\".\"sr_returned_date_sk\" - \"store_sales\".\"ss_sold_date_sk\" <= 90\n      AND \"store_returns\".\"sr_returned_date_sk\" - \"store_sales\".\"ss_sold_date_sk\" > 60\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"61-90 days\",\n  SUM(\n    CASE\n      WHEN \"store_returns\".\"sr_returned_date_sk\" - \"store_sales\".\"ss_sold_date_sk\" <= 120\n      AND \"store_returns\".\"sr_returned_date_sk\" - \"store_sales\".\"ss_sold_date_sk\" > 90\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"91-120 days\",\n  SUM(\n    CASE\n      WHEN \"store_returns\".\"sr_returned_date_sk\" - \"store_sales\".\"ss_sold_date_sk\" > 120\n      THEN 1\n      ELSE 0\n    END\n  ) AS \">120 days\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"date_dim\" AS \"d1\"\n  ON \"d1\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nJOIN \"store_returns\" AS \"store_returns\"\n  ON \"store_returns\".\"sr_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  AND \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\nJOIN \"date_dim\" AS \"d2\"\n  ON \"d2\".\"d_date_sk\" = \"store_returns\".\"sr_returned_date_sk\"\n  AND \"d2\".\"d_moy\" = 9\n  AND \"d2\".\"d_year\" = 2002\nGROUP BY\n  \"store\".\"s_store_name\",\n  \"store\".\"s_company_id\",\n  \"store\".\"s_street_number\",\n  \"store\".\"s_street_name\",\n  \"store\".\"s_street_type\",\n  \"store\".\"s_suite_number\",\n  \"store\".\"s_city\",\n  \"store\".\"s_county\",\n  \"store\".\"s_state\",\n  \"store\".\"s_zip\"\nORDER BY\n  \"s_store_name\",\n  \"s_company_id\",\n  \"s_street_number\",\n  \"s_street_name\",\n  \"s_street_type\",\n  \"s_suite_number\",\n  \"s_city\",\n  \"s_county\",\n  \"s_state\",\n  \"s_zip\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 51\n--------------------------------------\nWITH web_v1 AS\n(\n         SELECT   ws_item_sk item_sk,\n                  d_date,\n                  sum(Sum(ws_sales_price)) OVER (partition BY ws_item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND      CURRENT row) cume_sales\n         FROM     web_sales ,\n                  date_dim\n         WHERE    ws_sold_date_sk=d_date_sk\n         AND      d_month_seq BETWEEN 1192 AND      1192+11\n         AND      ws_item_sk IS NOT NULL\n         GROUP BY ws_item_sk,\n                  d_date), store_v1 AS\n(\n         SELECT   ss_item_sk item_sk,\n                  d_date,\n                  sum(sum(ss_sales_price)) OVER (partition BY ss_item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND      CURRENT row) cume_sales\n         FROM     store_sales ,\n                  date_dim\n         WHERE    ss_sold_date_sk=d_date_sk\n         AND      d_month_seq BETWEEN 1192 AND      1192+11\n         AND      ss_item_sk IS NOT NULL\n         GROUP BY ss_item_sk,\n                  d_date)\nSELECT\n         *\nFROM     (\n                  SELECT   item_sk ,\n                           d_date ,\n                           web_sales ,\n                           store_sales ,\n                           max(web_sales) OVER (partition BY item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND      CURRENT row)   web_cumulative ,\n                           max(store_sales) OVER (partition BY item_sk ORDER BY d_date rows BETWEEN UNBOUNDED PRECEDING AND      CURRENT row) store_cumulative\n                  FROM     (\n                                           SELECT\n                                                           CASE\n                                                                           WHEN web.item_sk IS NOT NULL THEN web.item_sk\n                                                                           ELSE store.item_sk\n                                                           END item_sk ,\n                                                           CASE\n                                                                           WHEN web.d_date IS NOT NULL THEN web.d_date\n                                                                           ELSE store.d_date\n                                                           END              d_date ,\n                                                           web.cume_sales   web_sales ,\n                                                           store.cume_sales store_sales\n                                           FROM            web_v1 web\n                                           FULL OUTER JOIN store_v1 store\n                                           ON              (\n                                                                           web.item_sk = store.item_sk\n                                                           AND             web.d_date = store.d_date) )x )y\nWHERE    web_cumulative > store_cumulative\nORDER BY item_sk ,\n         d_date\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\",\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_month_seq\" <= 1203 AND \"date_dim\".\"d_month_seq\" >= 1192\n), \"web_v1\" AS (\n  SELECT\n    \"web_sales\".\"ws_item_sk\" AS \"item_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\",\n    SUM(SUM(\"web_sales\".\"ws_sales_price\")) OVER (\n      PARTITION BY \"web_sales\".\"ws_item_sk\"\n      ORDER BY \"date_dim\".\"d_date\"\n      rows BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n    ) AS \"cume_sales\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  WHERE\n    NOT \"web_sales\".\"ws_item_sk\" IS NULL\n  GROUP BY\n    \"web_sales\".\"ws_item_sk\",\n    \"date_dim\".\"d_date\"\n), \"store_v1\" AS (\n  SELECT\n    \"store_sales\".\"ss_item_sk\" AS \"item_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\",\n    SUM(SUM(\"store_sales\".\"ss_sales_price\")) OVER (\n      PARTITION BY \"store_sales\".\"ss_item_sk\"\n      ORDER BY \"date_dim\".\"d_date\"\n      rows BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n    ) AS \"cume_sales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  WHERE\n    NOT \"store_sales\".\"ss_item_sk\" IS NULL\n  GROUP BY\n    \"store_sales\".\"ss_item_sk\",\n    \"date_dim\".\"d_date\"\n), \"y\" AS (\n  SELECT\n    CASE\n      WHEN NOT \"web\".\"item_sk\" IS NULL\n      THEN \"web\".\"item_sk\"\n      ELSE \"store\".\"item_sk\"\n    END AS \"item_sk\",\n    CASE WHEN NOT \"web\".\"d_date\" IS NULL THEN \"web\".\"d_date\" ELSE \"store\".\"d_date\" END AS \"d_date\",\n    \"web\".\"cume_sales\" AS \"web_sales\",\n    \"store\".\"cume_sales\" AS \"store_sales\",\n    MAX(\"web\".\"cume_sales\") OVER (\n      PARTITION BY CASE\n        WHEN NOT \"web\".\"item_sk\" IS NULL\n        THEN \"web\".\"item_sk\"\n        ELSE \"store\".\"item_sk\"\n      END\n      ORDER BY CASE WHEN NOT \"web\".\"d_date\" IS NULL THEN \"web\".\"d_date\" ELSE \"store\".\"d_date\" END\n      rows BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n    ) AS \"web_cumulative\",\n    MAX(\"store\".\"cume_sales\") OVER (\n      PARTITION BY CASE\n        WHEN NOT \"web\".\"item_sk\" IS NULL\n        THEN \"web\".\"item_sk\"\n        ELSE \"store\".\"item_sk\"\n      END\n      ORDER BY CASE WHEN NOT \"web\".\"d_date\" IS NULL THEN \"web\".\"d_date\" ELSE \"store\".\"d_date\" END\n      rows BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n    ) AS \"store_cumulative\"\n  FROM \"web_v1\" AS \"web\"\n  FULL JOIN \"store_v1\" AS \"store\"\n    ON \"store\".\"d_date\" = \"web\".\"d_date\" AND \"store\".\"item_sk\" = \"web\".\"item_sk\"\n)\nSELECT\n  \"y\".\"item_sk\" AS \"item_sk\",\n  \"y\".\"d_date\" AS \"d_date\",\n  \"y\".\"web_sales\" AS \"web_sales\",\n  \"y\".\"store_sales\" AS \"store_sales\",\n  \"y\".\"web_cumulative\" AS \"web_cumulative\",\n  \"y\".\"store_cumulative\" AS \"store_cumulative\"\nFROM \"y\" AS \"y\"\nWHERE\n  \"y\".\"store_cumulative\" < \"y\".\"web_cumulative\"\nORDER BY\n  \"y\".\"item_sk\",\n  \"y\".\"d_date\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 52\n--------------------------------------\n# execute: true\nSELECT dt.d_year,\n               item.i_brand_id         brand_id,\n               item.i_brand            brand,\n               Sum(ss_ext_sales_price) ext_price\nFROM   date_dim dt,\n       store_sales,\n       item\nWHERE  dt.d_date_sk = store_sales.ss_sold_date_sk\n       AND store_sales.ss_item_sk = item.i_item_sk\n       AND item.i_manager_id = 1\n       AND dt.d_moy = 11\n       AND dt.d_year = 1999\nGROUP  BY dt.d_year,\n          item.i_brand,\n          item.i_brand_id\nORDER  BY dt.d_year,\n          ext_price DESC,\n          brand_id\nLIMIT 100;\nSELECT\n  \"dt\".\"d_year\" AS \"d_year\",\n  \"item\".\"i_brand_id\" AS \"brand_id\",\n  \"item\".\"i_brand\" AS \"brand\",\n  SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"ext_price\"\nFROM \"date_dim\" AS \"dt\"\nJOIN \"store_sales\" AS \"store_sales\"\n  ON \"dt\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\" AND \"item\".\"i_manager_id\" = 1\nWHERE\n  \"dt\".\"d_moy\" = 11 AND \"dt\".\"d_year\" = 1999\nGROUP BY\n  \"dt\".\"d_year\",\n  \"item\".\"i_brand\",\n  \"item\".\"i_brand_id\"\nORDER BY\n  \"d_year\",\n  \"ext_price\" DESC,\n  \"brand_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 53\n--------------------------------------\nSELECT *\nFROM   (SELECT i_manufact_id,\n               Sum(ss_sales_price)             sum_sales,\n               Avg(Sum(ss_sales_price))\n                 OVER (\n                   partition BY i_manufact_id) avg_quarterly_sales\n        FROM   item,\n               store_sales,\n               date_dim,\n               store\n        WHERE  ss_item_sk = i_item_sk\n               AND ss_sold_date_sk = d_date_sk\n               AND ss_store_sk = s_store_sk\n               AND d_month_seq IN ( 1199, 1199 + 1, 1199 + 2, 1199 + 3,\n                                    1199 + 4, 1199 + 5, 1199 + 6, 1199 + 7,\n                                    1199 + 8, 1199 + 9, 1199 + 10, 1199 + 11 )\n               AND ( ( i_category IN ( 'Books', 'Children', 'Electronics' )\n                       AND i_class IN ( 'personal', 'portable', 'reference',\n                                        'self-help' )\n                       AND i_brand IN ( 'scholaramalgamalg #14',\n                                        'scholaramalgamalg #7'\n                                        ,\n                                        'exportiunivamalg #9',\n                                                       'scholaramalgamalg #9' )\n                     )\n                      OR ( i_category IN ( 'Women', 'Music', 'Men' )\n                           AND i_class IN ( 'accessories', 'classical',\n                                            'fragrances',\n                                            'pants' )\n                           AND i_brand IN ( 'amalgimporto #1',\n                                            'edu packscholar #1',\n                                            'exportiimporto #1',\n                                                'importoamalg #1' ) ) )\n        GROUP  BY i_manufact_id,\n                  d_qoy) tmp1\nWHERE  CASE\n         WHEN avg_quarterly_sales > 0 THEN Abs (sum_sales - avg_quarterly_sales)\n                                           /\n                                           avg_quarterly_sales\n         ELSE NULL\n       END > 0.1\nORDER  BY avg_quarterly_sales,\n          sum_sales,\n          i_manufact_id\nLIMIT 100;\nWITH \"tmp1\" AS (\n  SELECT\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\",\n    SUM(\"store_sales\".\"ss_sales_price\") AS \"sum_sales\",\n    AVG(SUM(\"store_sales\".\"ss_sales_price\")) OVER (PARTITION BY \"item\".\"i_manufact_id\") AS \"avg_quarterly_sales\"\n  FROM \"item\" AS \"item\"\n  JOIN \"store_sales\" AS \"store_sales\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_month_seq\" IN (1199, 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210)\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  WHERE\n    (\n      \"item\".\"i_brand\" IN ('amalgimporto #1', 'edu packscholar #1', 'exportiimporto #1', 'importoamalg #1')\n      OR \"item\".\"i_brand\" IN (\n        'scholaramalgamalg #14',\n        'scholaramalgamalg #7',\n        'exportiunivamalg #9',\n        'scholaramalgamalg #9'\n      )\n    )\n    AND (\n      \"item\".\"i_brand\" IN ('amalgimporto #1', 'edu packscholar #1', 'exportiimporto #1', 'importoamalg #1')\n      OR \"item\".\"i_category\" IN ('Books', 'Children', 'Electronics')\n    )\n    AND (\n      \"item\".\"i_brand\" IN ('amalgimporto #1', 'edu packscholar #1', 'exportiimporto #1', 'importoamalg #1')\n      OR \"item\".\"i_class\" IN ('personal', 'portable', 'reference', 'self-help')\n    )\n    AND (\n      \"item\".\"i_brand\" IN (\n        'scholaramalgamalg #14',\n        'scholaramalgamalg #7',\n        'exportiunivamalg #9',\n        'scholaramalgamalg #9'\n      )\n      OR \"item\".\"i_category\" IN ('Women', 'Music', 'Men')\n    )\n    AND (\n      \"item\".\"i_brand\" IN (\n        'scholaramalgamalg #14',\n        'scholaramalgamalg #7',\n        'exportiunivamalg #9',\n        'scholaramalgamalg #9'\n      )\n      OR \"item\".\"i_class\" IN ('accessories', 'classical', 'fragrances', 'pants')\n    )\n    AND (\n      \"item\".\"i_category\" IN ('Books', 'Children', 'Electronics')\n      OR \"item\".\"i_category\" IN ('Women', 'Music', 'Men')\n    )\n    AND (\n      \"item\".\"i_category\" IN ('Books', 'Children', 'Electronics')\n      OR \"item\".\"i_class\" IN ('accessories', 'classical', 'fragrances', 'pants')\n    )\n    AND (\n      \"item\".\"i_category\" IN ('Women', 'Music', 'Men')\n      OR \"item\".\"i_class\" IN ('personal', 'portable', 'reference', 'self-help')\n    )\n    AND (\n      \"item\".\"i_class\" IN ('accessories', 'classical', 'fragrances', 'pants')\n      OR \"item\".\"i_class\" IN ('personal', 'portable', 'reference', 'self-help')\n    )\n  GROUP BY\n    \"item\".\"i_manufact_id\",\n    \"date_dim\".\"d_qoy\"\n)\nSELECT\n  \"tmp1\".\"i_manufact_id\" AS \"i_manufact_id\",\n  \"tmp1\".\"sum_sales\" AS \"sum_sales\",\n  \"tmp1\".\"avg_quarterly_sales\" AS \"avg_quarterly_sales\"\nFROM \"tmp1\" AS \"tmp1\"\nWHERE\n  CASE\n    WHEN \"tmp1\".\"avg_quarterly_sales\" > 0\n    THEN ABS(\"tmp1\".\"sum_sales\" - \"tmp1\".\"avg_quarterly_sales\") / \"tmp1\".\"avg_quarterly_sales\"\n    ELSE NULL\n  END > 0.1\nORDER BY\n  \"tmp1\".\"avg_quarterly_sales\",\n  \"tmp1\".\"sum_sales\",\n  \"tmp1\".\"i_manufact_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 54\n--------------------------------------\n# execute: true\nWITH my_customers\n     AS (SELECT DISTINCT c_customer_sk,\n                         c_current_addr_sk\n         FROM   (SELECT cs_sold_date_sk     sold_date_sk,\n                        cs_bill_customer_sk customer_sk,\n                        cs_item_sk          item_sk\n                 FROM   catalog_sales\n                 UNION ALL\n                 SELECT ws_sold_date_sk     sold_date_sk,\n                        ws_bill_customer_sk customer_sk,\n                        ws_item_sk          item_sk\n                 FROM   web_sales) cs_or_ws_sales,\n                item,\n                date_dim,\n                customer\n         WHERE  sold_date_sk = d_date_sk\n                AND item_sk = i_item_sk\n                AND i_category = 'Sports'\n                AND i_class = 'fitness'\n                AND c_customer_sk = cs_or_ws_sales.customer_sk\n                AND d_moy = 5\n                AND d_year = 2000),\n     my_revenue\n     AS (SELECT c_customer_sk,\n                Sum(ss_ext_sales_price) AS revenue\n         FROM   my_customers,\n                store_sales,\n                customer_address,\n                store,\n                date_dim\n         WHERE  c_current_addr_sk = ca_address_sk\n                AND ca_county = s_county\n                AND ca_state = s_state\n                AND ss_sold_date_sk = d_date_sk\n                AND c_customer_sk = ss_customer_sk\n                AND d_month_seq BETWEEN (SELECT DISTINCT d_month_seq + 1\n                                         FROM   date_dim\n                                         WHERE  d_year = 2000\n                                                AND d_moy = 5) AND\n                                        (SELECT DISTINCT\n                                        d_month_seq + 3\n                                         FROM   date_dim\n                                         WHERE  d_year = 2000\n                                                AND d_moy = 5)\n         GROUP  BY c_customer_sk),\n     segments\n     AS (SELECT Cast(( revenue / 50 ) AS INT) AS segment\n         FROM   my_revenue)\nSELECT segment,\n               Count(*)     AS num_customers,\n               segment * 50 AS segment_base\nFROM   segments\nGROUP  BY segment\nORDER  BY segment,\n          num_customers\nLIMIT 100;\nWITH \"cs_or_ws_sales\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_sold_date_sk\" AS \"sold_date_sk\",\n    \"catalog_sales\".\"cs_bill_customer_sk\" AS \"customer_sk\",\n    \"catalog_sales\".\"cs_item_sk\" AS \"item_sk\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  UNION ALL\n  SELECT\n    \"web_sales\".\"ws_sold_date_sk\" AS \"sold_date_sk\",\n    \"web_sales\".\"ws_bill_customer_sk\" AS \"customer_sk\",\n    \"web_sales\".\"ws_item_sk\" AS \"item_sk\"\n  FROM \"web_sales\" AS \"web_sales\"\n), \"my_customers\" AS (\n  SELECT DISTINCT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\",\n    \"customer\".\"c_current_addr_sk\" AS \"c_current_addr_sk\"\n  FROM \"cs_or_ws_sales\" AS \"cs_or_ws_sales\"\n  JOIN \"customer\" AS \"customer\"\n    ON \"cs_or_ws_sales\".\"customer_sk\" = \"customer\".\"c_customer_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"cs_or_ws_sales\".\"sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n    AND \"date_dim\".\"d_moy\" = 5\n    AND \"date_dim\".\"d_year\" = 2000\n  JOIN \"item\" AS \"item\"\n    ON \"cs_or_ws_sales\".\"item_sk\" = \"item\".\"i_item_sk\"\n    AND \"item\".\"i_category\" = 'Sports'\n    AND \"item\".\"i_class\" = 'fitness'\n), \"_u_0\" AS (\n  SELECT DISTINCT\n    \"date_dim\".\"d_month_seq\" + 1 AS \"_col_0\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 5 AND \"date_dim\".\"d_year\" = 2000\n), \"_u_1\" AS (\n  SELECT DISTINCT\n    \"date_dim\".\"d_month_seq\" + 3 AS \"_col_0\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 5 AND \"date_dim\".\"d_year\" = 2000\n), \"my_revenue\" AS (\n  SELECT\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"revenue\"\n  FROM \"my_customers\" AS \"my_customers\"\n  JOIN \"customer_address\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"my_customers\".\"c_current_addr_sk\"\n  JOIN \"store_sales\" AS \"store_sales\"\n    ON \"my_customers\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"store\" AS \"store\"\n    ON \"customer_address\".\"ca_county\" = \"store\".\"s_county\"\n    AND \"customer_address\".\"ca_state\" = \"store\".\"s_state\"\n  JOIN \"_u_0\" AS \"_u_0\"\n    ON \"_u_0\".\"_col_0\" <= \"date_dim\".\"d_month_seq\"\n  JOIN \"_u_1\" AS \"_u_1\"\n    ON \"_u_1\".\"_col_0\" >= \"date_dim\".\"d_month_seq\"\n  GROUP BY\n    \"my_customers\".\"c_customer_sk\"\n)\nSELECT\n  CAST((\n    \"my_revenue\".\"revenue\" / 50\n  ) AS INT) AS \"segment\",\n  COUNT(*) AS \"num_customers\",\n  CAST((\n    \"my_revenue\".\"revenue\" / 50\n  ) AS INT) * 50 AS \"segment_base\"\nFROM \"my_revenue\" AS \"my_revenue\"\nGROUP BY\n  CAST((\n    \"my_revenue\".\"revenue\" / 50\n  ) AS INT)\nORDER BY\n  \"segment\",\n  \"num_customers\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 55\n--------------------------------------\n# execute: true\nSELECT i_brand_id              brand_id,\n               i_brand                 brand,\n               Sum(ss_ext_sales_price) ext_price\nFROM   date_dim,\n       store_sales,\n       item\nWHERE  d_date_sk = ss_sold_date_sk\n       AND ss_item_sk = i_item_sk\n       AND i_manager_id = 33\n       AND d_moy = 12\n       AND d_year = 1998\nGROUP  BY i_brand,\n          i_brand_id\nORDER  BY ext_price DESC,\n          i_brand_id\nLIMIT 100;\nSELECT\n  \"item\".\"i_brand_id\" AS \"brand_id\",\n  \"item\".\"i_brand\" AS \"brand\",\n  SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"ext_price\"\nFROM \"date_dim\" AS \"date_dim\"\nJOIN \"store_sales\" AS \"store_sales\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\" AND \"item\".\"i_manager_id\" = 33\nWHERE\n  \"date_dim\".\"d_moy\" = 12 AND \"date_dim\".\"d_year\" = 1998\nGROUP BY\n  \"item\".\"i_brand\",\n  \"item\".\"i_brand_id\"\nORDER BY\n  \"ext_price\" DESC,\n  \"brand_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 56\n--------------------------------------\n# execute: true\nWITH ss\n     AS (SELECT i_item_id,\n                Sum(ss_ext_sales_price) total_sales\n         FROM   store_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_item_id IN (SELECT i_item_id\n                              FROM   item\n                              WHERE  i_color IN ( 'firebrick', 'rosy', 'white' )\n                             )\n                AND ss_item_sk = i_item_sk\n                AND ss_sold_date_sk = d_date_sk\n                AND d_year = 1998\n                AND d_moy = 3\n                AND ss_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -6\n         GROUP  BY i_item_id),\n     cs\n     AS (SELECT i_item_id,\n                Sum(cs_ext_sales_price) total_sales\n         FROM   catalog_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_item_id IN (SELECT i_item_id\n                              FROM   item\n                              WHERE  i_color IN ( 'firebrick', 'rosy', 'white' )\n                             )\n                AND cs_item_sk = i_item_sk\n                AND cs_sold_date_sk = d_date_sk\n                AND d_year = 1998\n                AND d_moy = 3\n                AND cs_bill_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -6\n         GROUP  BY i_item_id),\n     ws\n     AS (SELECT i_item_id,\n                Sum(ws_ext_sales_price) total_sales\n         FROM   web_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_item_id IN (SELECT i_item_id\n                              FROM   item\n                              WHERE  i_color IN ( 'firebrick', 'rosy', 'white' )\n                             )\n                AND ws_item_sk = i_item_sk\n                AND ws_sold_date_sk = d_date_sk\n                AND d_year = 1998\n                AND d_moy = 3\n                AND ws_bill_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -6\n         GROUP  BY i_item_id)\nSELECT i_item_id,\n               Sum(total_sales) total_sales\nFROM   (SELECT *\n        FROM   ss\n        UNION ALL\n        SELECT *\n        FROM   cs\n        UNION ALL\n        SELECT *\n        FROM   ws) tmp1\nGROUP  BY i_item_id\nORDER  BY total_sales\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 3 AND \"date_dim\".\"d_year\" = 1998\n), \"customer_address_2\" AS (\n  SELECT\n    \"customer_address\".\"ca_address_sk\" AS \"ca_address_sk\",\n    \"customer_address\".\"ca_gmt_offset\" AS \"ca_gmt_offset\"\n  FROM \"customer_address\" AS \"customer_address\"\n  WHERE\n    \"customer_address\".\"ca_gmt_offset\" = -6\n), \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_item_id\" AS \"i_item_id\"\n  FROM \"item\" AS \"item\"\n), \"_u_0\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\"\n  FROM \"item\" AS \"item\"\n  WHERE\n    \"item\".\"i_color\" IN ('firebrick', 'rosy', 'white')\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"ss\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\",\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"total_sales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_0\"\n    ON \"_u_0\".\"i_item_id\" = \"item\".\"i_item_id\"\n  WHERE\n    NOT \"_u_0\".\"i_item_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"cs\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\",\n    SUM(\"catalog_sales\".\"cs_ext_sales_price\") AS \"total_sales\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"catalog_sales\".\"cs_bill_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_1\"\n    ON \"_u_1\".\"i_item_id\" = \"item\".\"i_item_id\"\n  WHERE\n    NOT \"_u_1\".\"i_item_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"ws\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\",\n    SUM(\"web_sales\".\"ws_ext_sales_price\") AS \"total_sales\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"web_sales\".\"ws_bill_addr_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_2\"\n    ON \"_u_2\".\"i_item_id\" = \"item\".\"i_item_id\"\n  WHERE\n    NOT \"_u_2\".\"i_item_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"tmp1\" AS (\n  SELECT\n    \"ss\".\"i_item_id\" AS \"i_item_id\",\n    \"ss\".\"total_sales\" AS \"total_sales\"\n  FROM \"ss\" AS \"ss\"\n  UNION ALL\n  SELECT\n    \"cs\".\"i_item_id\" AS \"i_item_id\",\n    \"cs\".\"total_sales\" AS \"total_sales\"\n  FROM \"cs\" AS \"cs\"\n  UNION ALL\n  SELECT\n    \"ws\".\"i_item_id\" AS \"i_item_id\",\n    \"ws\".\"total_sales\" AS \"total_sales\"\n  FROM \"ws\" AS \"ws\"\n)\nSELECT\n  \"tmp1\".\"i_item_id\" AS \"i_item_id\",\n  SUM(\"tmp1\".\"total_sales\") AS \"total_sales\"\nFROM \"tmp1\" AS \"tmp1\"\nGROUP BY\n  \"tmp1\".\"i_item_id\"\nORDER BY\n  \"total_sales\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 57\n--------------------------------------\nWITH v1\n     AS (SELECT i_category,\n                i_brand,\n                cc_name,\n                d_year,\n                d_moy,\n                Sum(cs_sales_price)                                    sum_sales\n                ,\n                Avg(Sum(cs_sales_price))\n                  OVER (\n                    partition BY i_category, i_brand, cc_name, d_year)\n                avg_monthly_sales\n                   ,\n                Rank()\n                  OVER (\n                    partition BY i_category, i_brand, cc_name\n                    ORDER BY d_year, d_moy)                            rn\n         FROM   item,\n                catalog_sales,\n                date_dim,\n                call_center\n         WHERE  cs_item_sk = i_item_sk\n                AND cs_sold_date_sk = d_date_sk\n                AND cc_call_center_sk = cs_call_center_sk\n                AND ( d_year = 2000\n                       OR ( d_year = 2000 - 1\n                            AND d_moy = 12 )\n                       OR ( d_year = 2000 + 1\n                            AND d_moy = 1 ) )\n         GROUP  BY i_category,\n                   i_brand,\n                   cc_name,\n                   d_year,\n                   d_moy),\n     v2\n     AS (SELECT v1.i_brand,\n                v1.d_year,\n                v1.avg_monthly_sales,\n                v1.sum_sales,\n                v1_lag.sum_sales  psum,\n                v1_lead.sum_sales nsum\n         FROM   v1,\n                v1 v1_lag,\n                v1 v1_lead\n         WHERE  v1.i_category = v1_lag.i_category\n                AND v1.i_category = v1_lead.i_category\n                AND v1.i_brand = v1_lag.i_brand\n                AND v1.i_brand = v1_lead.i_brand\n                AND v1. cc_name = v1_lag. cc_name\n                AND v1. cc_name = v1_lead. cc_name\n                AND v1.rn = v1_lag.rn + 1\n                AND v1.rn = v1_lead.rn - 1)\nSELECT *\nFROM   v2\nWHERE  d_year = 2000\n       AND avg_monthly_sales > 0\n       AND CASE\n             WHEN avg_monthly_sales > 0 THEN Abs(sum_sales - avg_monthly_sales)\n                                             /\n                                             avg_monthly_sales\n             ELSE NULL\n           END > 0.1\nORDER  BY sum_sales - avg_monthly_sales,\n          3\nLIMIT 100;\nWITH \"v1\" AS (\n  SELECT\n    \"item\".\"i_category\" AS \"i_category\",\n    \"item\".\"i_brand\" AS \"i_brand\",\n    \"call_center\".\"cc_name\" AS \"cc_name\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    SUM(\"catalog_sales\".\"cs_sales_price\") AS \"sum_sales\",\n    AVG(SUM(\"catalog_sales\".\"cs_sales_price\")) OVER (\n      PARTITION BY \"item\".\"i_category\", \"item\".\"i_brand\", \"call_center\".\"cc_name\", \"date_dim\".\"d_year\"\n    ) AS \"avg_monthly_sales\",\n    RANK() OVER (\n      PARTITION BY \"item\".\"i_category\", \"item\".\"i_brand\", \"call_center\".\"cc_name\"\n      ORDER BY \"date_dim\".\"d_year\", \"date_dim\".\"d_moy\"\n    ) AS \"rn\"\n  FROM \"item\" AS \"item\"\n  JOIN \"catalog_sales\" AS \"catalog_sales\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  JOIN \"call_center\" AS \"call_center\"\n    ON \"call_center\".\"cc_call_center_sk\" = \"catalog_sales\".\"cs_call_center_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n    AND (\n      \"date_dim\".\"d_moy\" = 1 OR \"date_dim\".\"d_moy\" = 12 OR \"date_dim\".\"d_year\" = 2000\n    )\n    AND (\n      \"date_dim\".\"d_moy\" = 1 OR \"date_dim\".\"d_year\" = 1999 OR \"date_dim\".\"d_year\" = 2000\n    )\n    AND (\n      \"date_dim\".\"d_moy\" = 12\n      OR \"date_dim\".\"d_year\" = 2000\n      OR \"date_dim\".\"d_year\" = 2001\n    )\n    AND (\n      \"date_dim\".\"d_year\" = 1999\n      OR \"date_dim\".\"d_year\" = 2000\n      OR \"date_dim\".\"d_year\" = 2001\n    )\n  GROUP BY\n    \"item\".\"i_category\",\n    \"item\".\"i_brand\",\n    \"call_center\".\"cc_name\",\n    \"date_dim\".\"d_year\",\n    \"date_dim\".\"d_moy\"\n)\nSELECT\n  \"v1\".\"i_brand\" AS \"i_brand\",\n  \"v1\".\"d_year\" AS \"d_year\",\n  \"v1\".\"avg_monthly_sales\" AS \"avg_monthly_sales\",\n  \"v1\".\"sum_sales\" AS \"sum_sales\",\n  \"v1_lag\".\"sum_sales\" AS \"psum\",\n  \"v1_lead\".\"sum_sales\" AS \"nsum\"\nFROM \"v1\" AS \"v1\"\nJOIN \"v1\" AS \"v1_lag\"\n  ON \"v1\".\"cc_name\" = \"v1_lag\".\"cc_name\"\n  AND \"v1\".\"i_brand\" = \"v1_lag\".\"i_brand\"\n  AND \"v1\".\"i_category\" = \"v1_lag\".\"i_category\"\n  AND \"v1\".\"rn\" = \"v1_lag\".\"rn\" + 1\nJOIN \"v1\" AS \"v1_lead\"\n  ON \"v1\".\"cc_name\" = \"v1_lead\".\"cc_name\"\n  AND \"v1\".\"i_brand\" = \"v1_lead\".\"i_brand\"\n  AND \"v1\".\"i_category\" = \"v1_lead\".\"i_category\"\n  AND \"v1\".\"rn\" = \"v1_lead\".\"rn\" - 1\nWHERE\n  \"v1\".\"avg_monthly_sales\" > 0\n  AND \"v1\".\"d_year\" = 2000\n  AND CASE\n    WHEN \"v1\".\"avg_monthly_sales\" > 0\n    THEN ABS(\"v1\".\"sum_sales\" - \"v1\".\"avg_monthly_sales\") / \"v1\".\"avg_monthly_sales\"\n    ELSE NULL\n  END > 0.1\nORDER BY\n  \"v1\".\"sum_sales\" - \"v1\".\"avg_monthly_sales\",\n  \"avg_monthly_sales\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 58\n--------------------------------------\nWITH ss_items\n     AS (SELECT i_item_id               item_id,\n                Sum(ss_ext_sales_price) ss_item_rev\n         FROM   store_sales,\n                item,\n                date_dim\n         WHERE  ss_item_sk = i_item_sk\n                AND d_date IN (SELECT d_date\n                               FROM   date_dim\n                               WHERE  d_week_seq = (SELECT d_week_seq\n                                                    FROM   date_dim\n                                                    WHERE  d_date = '2002-02-25'\n                                                   ))\n                AND ss_sold_date_sk = d_date_sk\n         GROUP  BY i_item_id),\n     cs_items\n     AS (SELECT i_item_id               item_id,\n                Sum(cs_ext_sales_price) cs_item_rev\n         FROM   catalog_sales,\n                item,\n                date_dim\n         WHERE  cs_item_sk = i_item_sk\n                AND d_date IN (SELECT d_date\n                               FROM   date_dim\n                               WHERE  d_week_seq = (SELECT d_week_seq\n                                                    FROM   date_dim\n                                                    WHERE  d_date = '2002-02-25'\n                                                   ))\n                AND cs_sold_date_sk = d_date_sk\n         GROUP  BY i_item_id),\n     ws_items\n     AS (SELECT i_item_id               item_id,\n                Sum(ws_ext_sales_price) ws_item_rev\n         FROM   web_sales,\n                item,\n                date_dim\n         WHERE  ws_item_sk = i_item_sk\n                AND d_date IN (SELECT d_date\n                               FROM   date_dim\n                               WHERE  d_week_seq = (SELECT d_week_seq\n                                                    FROM   date_dim\n                                                    WHERE  d_date = '2002-02-25'\n                                                   ))\n                AND ws_sold_date_sk = d_date_sk\n         GROUP  BY i_item_id)\nSELECT ss_items.item_id,\n               ss_item_rev,\n               ss_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 *\n               100 ss_dev,\n               cs_item_rev,\n               cs_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 *\n               100 cs_dev,\n               ws_item_rev,\n               ws_item_rev / ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3 *\n               100 ws_dev,\n               ( ss_item_rev + cs_item_rev + ws_item_rev ) / 3\n               average\nFROM   ss_items,\n       cs_items,\n       ws_items\nWHERE  ss_items.item_id = cs_items.item_id\n       AND ss_items.item_id = ws_items.item_id\n       AND ss_item_rev BETWEEN 0.9 * cs_item_rev AND 1.1 * cs_item_rev\n       AND ss_item_rev BETWEEN 0.9 * ws_item_rev AND 1.1 * ws_item_rev\n       AND cs_item_rev BETWEEN 0.9 * ss_item_rev AND 1.1 * ss_item_rev\n       AND cs_item_rev BETWEEN 0.9 * ws_item_rev AND 1.1 * ws_item_rev\n       AND ws_item_rev BETWEEN 0.9 * ss_item_rev AND 1.1 * ss_item_rev\n       AND ws_item_rev BETWEEN 0.9 * cs_item_rev AND 1.1 * cs_item_rev\nORDER  BY item_id,\n          ss_item_rev\nLIMIT 100;\nWITH \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_item_id\" AS \"i_item_id\"\n  FROM \"item\" AS \"item\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n), \"_u_0\" AS (\n  SELECT\n    \"date_dim\".\"d_week_seq\" AS \"d_week_seq\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_date\" = '2002-02-25'\n), \"_u_1\" AS (\n  SELECT\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  JOIN \"_u_0\" AS \"_u_0\"\n    ON \"_u_0\".\"d_week_seq\" = \"date_dim\".\"d_week_seq\"\n  GROUP BY\n    \"date_dim\".\"d_date\"\n), \"ss_items\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"item_id\",\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"ss_item_rev\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  LEFT JOIN \"_u_1\" AS \"_u_1\"\n    ON \"_u_1\".\"d_date\" = \"date_dim\".\"d_date\"\n  WHERE\n    NOT \"_u_1\".\"d_date\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"_u_3\" AS (\n  SELECT\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  JOIN \"_u_0\" AS \"_u_2\"\n    ON \"_u_2\".\"d_week_seq\" = \"date_dim\".\"d_week_seq\"\n  GROUP BY\n    \"date_dim\".\"d_date\"\n), \"cs_items\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"item_id\",\n    SUM(\"catalog_sales\".\"cs_ext_sales_price\") AS \"cs_item_rev\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  LEFT JOIN \"_u_3\" AS \"_u_3\"\n    ON \"_u_3\".\"d_date\" = \"date_dim\".\"d_date\"\n  WHERE\n    NOT \"_u_3\".\"d_date\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"_u_5\" AS (\n  SELECT\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  JOIN \"_u_0\" AS \"_u_4\"\n    ON \"_u_4\".\"d_week_seq\" = \"date_dim\".\"d_week_seq\"\n  GROUP BY\n    \"date_dim\".\"d_date\"\n), \"ws_items\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"item_id\",\n    SUM(\"web_sales\".\"ws_ext_sales_price\") AS \"ws_item_rev\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  LEFT JOIN \"_u_5\" AS \"_u_5\"\n    ON \"_u_5\".\"d_date\" = \"date_dim\".\"d_date\"\n  WHERE\n    NOT \"_u_5\".\"d_date\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n)\nSELECT\n  \"ss_items\".\"item_id\" AS \"item_id\",\n  \"ss_items\".\"ss_item_rev\" AS \"ss_item_rev\",\n  \"ss_items\".\"ss_item_rev\" / (\n    \"ss_items\".\"ss_item_rev\" + \"cs_items\".\"cs_item_rev\" + \"ws_items\".\"ws_item_rev\"\n  ) / 3 * 100 AS \"ss_dev\",\n  \"cs_items\".\"cs_item_rev\" AS \"cs_item_rev\",\n  \"cs_items\".\"cs_item_rev\" / (\n    \"ss_items\".\"ss_item_rev\" + \"cs_items\".\"cs_item_rev\" + \"ws_items\".\"ws_item_rev\"\n  ) / 3 * 100 AS \"cs_dev\",\n  \"ws_items\".\"ws_item_rev\" AS \"ws_item_rev\",\n  \"ws_items\".\"ws_item_rev\" / (\n    \"ss_items\".\"ss_item_rev\" + \"cs_items\".\"cs_item_rev\" + \"ws_items\".\"ws_item_rev\"\n  ) / 3 * 100 AS \"ws_dev\",\n  (\n    \"ss_items\".\"ss_item_rev\" + \"cs_items\".\"cs_item_rev\" + \"ws_items\".\"ws_item_rev\"\n  ) / 3 AS \"average\"\nFROM \"ss_items\" AS \"ss_items\"\nJOIN \"cs_items\" AS \"cs_items\"\n  ON \"cs_items\".\"cs_item_rev\" <= 1.1 * \"ss_items\".\"ss_item_rev\"\n  AND \"cs_items\".\"cs_item_rev\" >= 0.9 * \"ss_items\".\"ss_item_rev\"\n  AND \"cs_items\".\"item_id\" = \"ss_items\".\"item_id\"\n  AND \"ss_items\".\"ss_item_rev\" <= 1.1 * \"cs_items\".\"cs_item_rev\"\n  AND \"ss_items\".\"ss_item_rev\" >= 0.9 * \"cs_items\".\"cs_item_rev\"\nJOIN \"ws_items\" AS \"ws_items\"\n  ON \"cs_items\".\"cs_item_rev\" <= 1.1 * \"ws_items\".\"ws_item_rev\"\n  AND \"cs_items\".\"cs_item_rev\" >= 0.9 * \"ws_items\".\"ws_item_rev\"\n  AND \"ss_items\".\"item_id\" = \"ws_items\".\"item_id\"\n  AND \"ss_items\".\"ss_item_rev\" <= 1.1 * \"ws_items\".\"ws_item_rev\"\n  AND \"ss_items\".\"ss_item_rev\" >= 0.9 * \"ws_items\".\"ws_item_rev\"\n  AND \"ws_items\".\"ws_item_rev\" <= 1.1 * \"cs_items\".\"cs_item_rev\"\n  AND \"ws_items\".\"ws_item_rev\" <= 1.1 * \"ss_items\".\"ss_item_rev\"\n  AND \"ws_items\".\"ws_item_rev\" >= 0.9 * \"cs_items\".\"cs_item_rev\"\n  AND \"ws_items\".\"ws_item_rev\" >= 0.9 * \"ss_items\".\"ss_item_rev\"\nORDER BY\n  \"item_id\",\n  \"ss_item_rev\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 59\n--------------------------------------\n# execute: true\nWITH wss\n     AS (SELECT d_week_seq,\n                ss_store_sk,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Sunday' ) THEN ss_sales_price\n                      ELSE NULL\n                    END) sun_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Monday' ) THEN ss_sales_price\n                      ELSE NULL\n                    END) mon_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Tuesday' ) THEN ss_sales_price\n                      ELSE NULL\n                    END) tue_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Wednesday' ) THEN ss_sales_price\n                      ELSE NULL\n                    END) wed_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Thursday' ) THEN ss_sales_price\n                      ELSE NULL\n                    END) thu_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Friday' ) THEN ss_sales_price\n                      ELSE NULL\n                    END) fri_sales,\n                Sum(CASE\n                      WHEN ( d_day_name = 'Saturday' ) THEN ss_sales_price\n                      ELSE NULL\n                    END) sat_sales\n         FROM   store_sales,\n                date_dim\n         WHERE  d_date_sk = ss_sold_date_sk\n         GROUP  BY d_week_seq,\n                   ss_store_sk)\nSELECT s_store_name1,\n               s_store_id1,\n               d_week_seq1,\n               sun_sales1 / sun_sales2 AS \"_col_3\",\n               mon_sales1 / mon_sales2 AS \"_col_4\",\n               tue_sales1 / tue_sales2 AS \"_col_5\",\n               wed_sales1 / wed_sales2 AS \"_col_6\",\n               thu_sales1 / thu_sales2 AS \"_col_7\",\n               fri_sales1 / fri_sales2 AS \"_col_8\",\n               sat_sales1 / sat_sales2 AS \"_col_9\"\nFROM   (SELECT s_store_name   s_store_name1,\n               wss.d_week_seq d_week_seq1,\n               s_store_id     s_store_id1,\n               sun_sales      sun_sales1,\n               mon_sales      mon_sales1,\n               tue_sales      tue_sales1,\n               wed_sales      wed_sales1,\n               thu_sales      thu_sales1,\n               fri_sales      fri_sales1,\n               sat_sales      sat_sales1\n        FROM   wss,\n               store,\n               date_dim d\n        WHERE  d.d_week_seq = wss.d_week_seq\n               AND ss_store_sk = s_store_sk\n               AND d_month_seq BETWEEN 1196 AND 1196 + 11) y,\n       (SELECT s_store_name   s_store_name2,\n               wss.d_week_seq d_week_seq2,\n               s_store_id     s_store_id2,\n               sun_sales      sun_sales2,\n               mon_sales      mon_sales2,\n               tue_sales      tue_sales2,\n               wed_sales      wed_sales2,\n               thu_sales      thu_sales2,\n               fri_sales      fri_sales2,\n               sat_sales      sat_sales2\n        FROM   wss,\n               store,\n               date_dim d\n        WHERE  d.d_week_seq = wss.d_week_seq\n               AND ss_store_sk = s_store_sk\n               AND d_month_seq BETWEEN 1196 + 12 AND 1196 + 23) x\nWHERE  s_store_id1 = s_store_id2\n       AND d_week_seq1 = d_week_seq2 - 52\nORDER  BY s_store_name1,\n          s_store_id1,\n          d_week_seq1\nLIMIT 100;\nWITH \"wss\" AS (\n  SELECT\n    \"date_dim\".\"d_week_seq\" AS \"d_week_seq\",\n    \"store_sales\".\"ss_store_sk\" AS \"ss_store_sk\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Sunday'\n        THEN \"store_sales\".\"ss_sales_price\"\n        ELSE NULL\n      END\n    ) AS \"sun_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Monday'\n        THEN \"store_sales\".\"ss_sales_price\"\n        ELSE NULL\n      END\n    ) AS \"mon_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Tuesday'\n        THEN \"store_sales\".\"ss_sales_price\"\n        ELSE NULL\n      END\n    ) AS \"tue_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Wednesday'\n        THEN \"store_sales\".\"ss_sales_price\"\n        ELSE NULL\n      END\n    ) AS \"wed_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Thursday'\n        THEN \"store_sales\".\"ss_sales_price\"\n        ELSE NULL\n      END\n    ) AS \"thu_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Friday'\n        THEN \"store_sales\".\"ss_sales_price\"\n        ELSE NULL\n      END\n    ) AS \"fri_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_day_name\" = 'Saturday'\n        THEN \"store_sales\".\"ss_sales_price\"\n        ELSE NULL\n      END\n    ) AS \"sat_sales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"date_dim\".\"d_week_seq\",\n    \"store_sales\".\"ss_store_sk\"\n), \"x\" AS (\n  SELECT\n    \"wss\".\"d_week_seq\" AS \"d_week_seq2\",\n    \"store\".\"s_store_id\" AS \"s_store_id2\",\n    \"wss\".\"sun_sales\" AS \"sun_sales2\",\n    \"wss\".\"mon_sales\" AS \"mon_sales2\",\n    \"wss\".\"tue_sales\" AS \"tue_sales2\",\n    \"wss\".\"wed_sales\" AS \"wed_sales2\",\n    \"wss\".\"thu_sales\" AS \"thu_sales2\",\n    \"wss\".\"fri_sales\" AS \"fri_sales2\",\n    \"wss\".\"sat_sales\" AS \"sat_sales2\"\n  FROM \"wss\" AS \"wss\"\n  JOIN \"date_dim\" AS \"d\"\n    ON \"d\".\"d_month_seq\" <= 1219\n    AND \"d\".\"d_month_seq\" >= 1208\n    AND \"d\".\"d_week_seq\" = \"wss\".\"d_week_seq\"\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"wss\".\"ss_store_sk\"\n)\nSELECT\n  \"store\".\"s_store_name\" AS \"s_store_name1\",\n  \"store\".\"s_store_id\" AS \"s_store_id1\",\n  \"wss\".\"d_week_seq\" AS \"d_week_seq1\",\n  \"wss\".\"sun_sales\" / \"x\".\"sun_sales2\" AS \"_col_3\",\n  \"wss\".\"mon_sales\" / \"x\".\"mon_sales2\" AS \"_col_4\",\n  \"wss\".\"tue_sales\" / \"x\".\"tue_sales2\" AS \"_col_5\",\n  \"wss\".\"wed_sales\" / \"x\".\"wed_sales2\" AS \"_col_6\",\n  \"wss\".\"thu_sales\" / \"x\".\"thu_sales2\" AS \"_col_7\",\n  \"wss\".\"fri_sales\" / \"x\".\"fri_sales2\" AS \"_col_8\",\n  \"wss\".\"sat_sales\" / \"x\".\"sat_sales2\" AS \"_col_9\"\nFROM \"wss\" AS \"wss\"\nJOIN \"date_dim\" AS \"d\"\n  ON \"d\".\"d_month_seq\" <= 1207\n  AND \"d\".\"d_month_seq\" >= 1196\n  AND \"d\".\"d_week_seq\" = \"wss\".\"d_week_seq\"\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"wss\".\"ss_store_sk\"\nJOIN \"x\" AS \"x\"\n  ON \"store\".\"s_store_id\" = \"x\".\"s_store_id2\"\n  AND \"wss\".\"d_week_seq\" = \"x\".\"d_week_seq2\" - 52\nORDER BY\n  \"s_store_name1\",\n  \"s_store_id1\",\n  \"d_week_seq1\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 60\n--------------------------------------\n# execute: true\nWITH ss\n     AS (SELECT i_item_id,\n                Sum(ss_ext_sales_price) total_sales\n         FROM   store_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_item_id IN (SELECT i_item_id\n                              FROM   item\n                              WHERE  i_category IN ( 'Jewelry' ))\n                AND ss_item_sk = i_item_sk\n                AND ss_sold_date_sk = d_date_sk\n                AND d_year = 1999\n                AND d_moy = 8\n                AND ss_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -6\n         GROUP  BY i_item_id),\n     cs\n     AS (SELECT i_item_id,\n                Sum(cs_ext_sales_price) total_sales\n         FROM   catalog_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_item_id IN (SELECT i_item_id\n                              FROM   item\n                              WHERE  i_category IN ( 'Jewelry' ))\n                AND cs_item_sk = i_item_sk\n                AND cs_sold_date_sk = d_date_sk\n                AND d_year = 1999\n                AND d_moy = 8\n                AND cs_bill_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -6\n         GROUP  BY i_item_id),\n     ws\n     AS (SELECT i_item_id,\n                Sum(ws_ext_sales_price) total_sales\n         FROM   web_sales,\n                date_dim,\n                customer_address,\n                item\n         WHERE  i_item_id IN (SELECT i_item_id\n                              FROM   item\n                              WHERE  i_category IN ( 'Jewelry' ))\n                AND ws_item_sk = i_item_sk\n                AND ws_sold_date_sk = d_date_sk\n                AND d_year = 1999\n                AND d_moy = 8\n                AND ws_bill_addr_sk = ca_address_sk\n                AND ca_gmt_offset = -6\n         GROUP  BY i_item_id)\nSELECT i_item_id,\n               Sum(total_sales) total_sales\nFROM   (SELECT *\n        FROM   ss\n        UNION ALL\n        SELECT *\n        FROM   cs\n        UNION ALL\n        SELECT *\n        FROM   ws) tmp1\nGROUP  BY i_item_id\nORDER  BY i_item_id,\n          total_sales\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 8 AND \"date_dim\".\"d_year\" = 1999\n), \"customer_address_2\" AS (\n  SELECT\n    \"customer_address\".\"ca_address_sk\" AS \"ca_address_sk\",\n    \"customer_address\".\"ca_gmt_offset\" AS \"ca_gmt_offset\"\n  FROM \"customer_address\" AS \"customer_address\"\n  WHERE\n    \"customer_address\".\"ca_gmt_offset\" = -6\n), \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_item_id\" AS \"i_item_id\"\n  FROM \"item\" AS \"item\"\n), \"_u_0\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\"\n  FROM \"item\" AS \"item\"\n  WHERE\n    \"item\".\"i_category\" IN ('Jewelry')\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"ss\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\",\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"total_sales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_0\"\n    ON \"_u_0\".\"i_item_id\" = \"item\".\"i_item_id\"\n  WHERE\n    NOT \"_u_0\".\"i_item_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"cs\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\",\n    SUM(\"catalog_sales\".\"cs_ext_sales_price\") AS \"total_sales\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"catalog_sales\".\"cs_bill_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_1\"\n    ON \"_u_1\".\"i_item_id\" = \"item\".\"i_item_id\"\n  WHERE\n    NOT \"_u_1\".\"i_item_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"ws\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"i_item_id\",\n    SUM(\"web_sales\".\"ws_ext_sales_price\") AS \"total_sales\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"web_sales\".\"ws_bill_addr_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  LEFT JOIN \"_u_0\" AS \"_u_2\"\n    ON \"_u_2\".\"i_item_id\" = \"item\".\"i_item_id\"\n  WHERE\n    NOT \"_u_2\".\"i_item_id\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"tmp1\" AS (\n  SELECT\n    \"ss\".\"i_item_id\" AS \"i_item_id\",\n    \"ss\".\"total_sales\" AS \"total_sales\"\n  FROM \"ss\" AS \"ss\"\n  UNION ALL\n  SELECT\n    \"cs\".\"i_item_id\" AS \"i_item_id\",\n    \"cs\".\"total_sales\" AS \"total_sales\"\n  FROM \"cs\" AS \"cs\"\n  UNION ALL\n  SELECT\n    \"ws\".\"i_item_id\" AS \"i_item_id\",\n    \"ws\".\"total_sales\" AS \"total_sales\"\n  FROM \"ws\" AS \"ws\"\n)\nSELECT\n  \"tmp1\".\"i_item_id\" AS \"i_item_id\",\n  SUM(\"tmp1\".\"total_sales\") AS \"total_sales\"\nFROM \"tmp1\" AS \"tmp1\"\nGROUP BY\n  \"tmp1\".\"i_item_id\"\nORDER BY\n  \"i_item_id\",\n  \"total_sales\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 61\n--------------------------------------\nSELECT promotions,\n               total,\n               Cast(promotions AS DECIMAL(15, 4)) /\n               Cast(total AS DECIMAL(15, 4)) * 100\nFROM   (SELECT Sum(ss_ext_sales_price) promotions\n        FROM   store_sales,\n               store,\n               promotion,\n               date_dim,\n               customer,\n               customer_address,\n               item\n        WHERE  ss_sold_date_sk = d_date_sk\n               AND ss_store_sk = s_store_sk\n               AND ss_promo_sk = p_promo_sk\n               AND ss_customer_sk = c_customer_sk\n               AND ca_address_sk = c_current_addr_sk\n               AND ss_item_sk = i_item_sk\n               AND ca_gmt_offset = -7\n               AND i_category = 'Books'\n               AND ( p_channel_dmail = 'Y'\n                      OR p_channel_email = 'Y'\n                      OR p_channel_tv = 'Y' )\n               AND s_gmt_offset = -7\n               AND d_year = 2001\n               AND d_moy = 12) promotional_sales,\n       (SELECT Sum(ss_ext_sales_price) total\n        FROM   store_sales,\n               store,\n               date_dim,\n               customer,\n               customer_address,\n               item\n        WHERE  ss_sold_date_sk = d_date_sk\n               AND ss_store_sk = s_store_sk\n               AND ss_customer_sk = c_customer_sk\n               AND ca_address_sk = c_current_addr_sk\n               AND ss_item_sk = i_item_sk\n               AND ca_gmt_offset = -7\n               AND i_category = 'Books'\n               AND s_gmt_offset = -7\n               AND d_year = 2001\n               AND d_moy = 12) all_sales\nORDER  BY promotions,\n          total\nLIMIT 100;\nWITH \"customer_2\" AS (\n  SELECT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\",\n    \"customer\".\"c_current_addr_sk\" AS \"c_current_addr_sk\"\n  FROM \"customer\" AS \"customer\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 12 AND \"date_dim\".\"d_year\" = 2001\n), \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_category\" AS \"i_category\"\n  FROM \"item\" AS \"item\"\n  WHERE\n    \"item\".\"i_category\" = 'Books'\n), \"store_2\" AS (\n  SELECT\n    \"store\".\"s_store_sk\" AS \"s_store_sk\",\n    \"store\".\"s_gmt_offset\" AS \"s_gmt_offset\"\n  FROM \"store\" AS \"store\"\n  WHERE\n    \"store\".\"s_gmt_offset\" = -7\n), \"customer_address_2\" AS (\n  SELECT\n    \"customer_address\".\"ca_address_sk\" AS \"ca_address_sk\",\n    \"customer_address\".\"ca_gmt_offset\" AS \"ca_gmt_offset\"\n  FROM \"customer_address\" AS \"customer_address\"\n  WHERE\n    \"customer_address\".\"ca_gmt_offset\" = -7\n), \"promotional_sales\" AS (\n  SELECT\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"promotions\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer_2\" AS \"customer\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"promotion\" AS \"promotion\"\n    ON (\n      \"promotion\".\"p_channel_dmail\" = 'Y'\n      OR \"promotion\".\"p_channel_email\" = 'Y'\n      OR \"promotion\".\"p_channel_tv\" = 'Y'\n    )\n    AND \"promotion\".\"p_promo_sk\" = \"store_sales\".\"ss_promo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n), \"all_sales\" AS (\n  SELECT\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"total\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer_2\" AS \"customer\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"customer_address_2\" AS \"customer_address\"\n    ON \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n)\nSELECT\n  \"promotional_sales\".\"promotions\" AS \"promotions\",\n  \"all_sales\".\"total\" AS \"total\",\n  CAST(\"promotional_sales\".\"promotions\" AS DECIMAL(15, 4)) / CAST(\"all_sales\".\"total\" AS DECIMAL(15, 4)) * 100 AS \"_col_2\"\nFROM \"promotional_sales\" AS \"promotional_sales\"\nCROSS JOIN \"all_sales\" AS \"all_sales\"\nORDER BY\n  \"promotions\",\n  \"total\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 62\n--------------------------------------\n# execute: true\nSELECT SUBSTRING(w_warehouse_name, 1, 20) AS \"_col_0\",\n               sm_type,\n               web_name,\n               Sum(CASE\n                     WHEN ( ws_ship_date_sk - ws_sold_date_sk <= 30 ) THEN 1\n                     ELSE 0\n                   END) AS \"30 days\",\n               Sum(CASE\n                     WHEN ( ws_ship_date_sk - ws_sold_date_sk > 30 )\n                          AND ( ws_ship_date_sk - ws_sold_date_sk <= 60 ) THEN 1\n                     ELSE 0\n                   END) AS \"31-60 days\",\n               Sum(CASE\n                     WHEN ( ws_ship_date_sk - ws_sold_date_sk > 60 )\n                          AND ( ws_ship_date_sk - ws_sold_date_sk <= 90 ) THEN 1\n                     ELSE 0\n                   END) AS \"61-90 days\",\n               Sum(CASE\n                     WHEN ( ws_ship_date_sk - ws_sold_date_sk > 90 )\n                          AND ( ws_ship_date_sk - ws_sold_date_sk <= 120 ) THEN\n                     1\n                     ELSE 0\n                   END) AS \"91-120 days\",\n               Sum(CASE\n                     WHEN ( ws_ship_date_sk - ws_sold_date_sk > 120 ) THEN 1\n                     ELSE 0\n                   END) AS \">120 days\"\nFROM   web_sales,\n       warehouse,\n       ship_mode,\n       web_site,\n       date_dim\nWHERE  d_month_seq BETWEEN 1222 AND 1222 + 11\n       AND ws_ship_date_sk = d_date_sk\n       AND ws_warehouse_sk = w_warehouse_sk\n       AND ws_ship_mode_sk = sm_ship_mode_sk\n       AND ws_web_site_sk = web_site_sk\nGROUP  BY SUBSTRING(w_warehouse_name, 1, 20),\n          sm_type,\n          web_name\nORDER  BY SUBSTRING(w_warehouse_name, 1, 20),\n          sm_type,\n          web_name\nLIMIT 100;\nSELECT\n  SUBSTRING(\"warehouse\".\"w_warehouse_name\", 1, 20) AS \"_col_0\",\n  \"ship_mode\".\"sm_type\" AS \"sm_type\",\n  \"web_site\".\"web_name\" AS \"web_name\",\n  SUM(\n    CASE\n      WHEN \"web_sales\".\"ws_ship_date_sk\" - \"web_sales\".\"ws_sold_date_sk\" <= 30\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"30 days\",\n  SUM(\n    CASE\n      WHEN \"web_sales\".\"ws_ship_date_sk\" - \"web_sales\".\"ws_sold_date_sk\" <= 60\n      AND \"web_sales\".\"ws_ship_date_sk\" - \"web_sales\".\"ws_sold_date_sk\" > 30\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"31-60 days\",\n  SUM(\n    CASE\n      WHEN \"web_sales\".\"ws_ship_date_sk\" - \"web_sales\".\"ws_sold_date_sk\" <= 90\n      AND \"web_sales\".\"ws_ship_date_sk\" - \"web_sales\".\"ws_sold_date_sk\" > 60\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"61-90 days\",\n  SUM(\n    CASE\n      WHEN \"web_sales\".\"ws_ship_date_sk\" - \"web_sales\".\"ws_sold_date_sk\" <= 120\n      AND \"web_sales\".\"ws_ship_date_sk\" - \"web_sales\".\"ws_sold_date_sk\" > 90\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"91-120 days\",\n  SUM(\n    CASE\n      WHEN \"web_sales\".\"ws_ship_date_sk\" - \"web_sales\".\"ws_sold_date_sk\" > 120\n      THEN 1\n      ELSE 0\n    END\n  ) AS \">120 days\"\nFROM \"web_sales\" AS \"web_sales\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_ship_date_sk\"\n  AND \"date_dim\".\"d_month_seq\" <= 1233\n  AND \"date_dim\".\"d_month_seq\" >= 1222\nJOIN \"ship_mode\" AS \"ship_mode\"\n  ON \"ship_mode\".\"sm_ship_mode_sk\" = \"web_sales\".\"ws_ship_mode_sk\"\nJOIN \"warehouse\" AS \"warehouse\"\n  ON \"warehouse\".\"w_warehouse_sk\" = \"web_sales\".\"ws_warehouse_sk\"\nJOIN \"web_site\" AS \"web_site\"\n  ON \"web_sales\".\"ws_web_site_sk\" = \"web_site\".\"web_site_sk\"\nGROUP BY\n  SUBSTRING(\"warehouse\".\"w_warehouse_name\", 1, 20),\n  \"ship_mode\".\"sm_type\",\n  \"web_site\".\"web_name\"\nORDER BY\n  \"_col_0\",\n  \"sm_type\",\n  \"web_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 63\n--------------------------------------\nSELECT *\nFROM   (SELECT i_manager_id,\n               Sum(ss_sales_price)            sum_sales,\n               Avg(Sum(ss_sales_price))\n                 OVER (\n                   partition BY i_manager_id) avg_monthly_sales\n        FROM   item,\n               store_sales,\n               date_dim,\n               store\n        WHERE  ss_item_sk = i_item_sk\n               AND ss_sold_date_sk = d_date_sk\n               AND ss_store_sk = s_store_sk\n               AND d_month_seq IN ( 1200, 1200 + 1, 1200 + 2, 1200 + 3,\n                                    1200 + 4, 1200 + 5, 1200 + 6, 1200 + 7,\n                                    1200 + 8, 1200 + 9, 1200 + 10, 1200 + 11 )\n               AND ( ( i_category IN ( 'Books', 'Children', 'Electronics' )\n                       AND i_class IN ( 'personal', 'portable', 'reference',\n                                        'self-help' )\n                       AND i_brand IN ( 'scholaramalgamalg #14',\n                                        'scholaramalgamalg #7'\n                                        ,\n                                        'exportiunivamalg #9',\n                                                       'scholaramalgamalg #9' )\n                     )\n                      OR ( i_category IN ( 'Women', 'Music', 'Men' )\n                           AND i_class IN ( 'accessories', 'classical',\n                                            'fragrances',\n                                            'pants' )\n                           AND i_brand IN ( 'amalgimporto #1',\n                                            'edu packscholar #1',\n                                            'exportiimporto #1',\n                                                'importoamalg #1' ) ) )\n        GROUP  BY i_manager_id,\n                  d_moy) tmp1\nWHERE  CASE\n         WHEN avg_monthly_sales > 0 THEN Abs (sum_sales - avg_monthly_sales) /\n                                         avg_monthly_sales\n         ELSE NULL\n       END > 0.1\nORDER  BY i_manager_id,\n          avg_monthly_sales,\n          sum_sales\nLIMIT 100;\nWITH \"tmp1\" AS (\n  SELECT\n    \"item\".\"i_manager_id\" AS \"i_manager_id\",\n    SUM(\"store_sales\".\"ss_sales_price\") AS \"sum_sales\",\n    AVG(SUM(\"store_sales\".\"ss_sales_price\")) OVER (PARTITION BY \"item\".\"i_manager_id\") AS \"avg_monthly_sales\"\n  FROM \"item\" AS \"item\"\n  JOIN \"store_sales\" AS \"store_sales\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_month_seq\" IN (1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211)\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  WHERE\n    (\n      \"item\".\"i_brand\" IN ('amalgimporto #1', 'edu packscholar #1', 'exportiimporto #1', 'importoamalg #1')\n      OR \"item\".\"i_brand\" IN (\n        'scholaramalgamalg #14',\n        'scholaramalgamalg #7',\n        'exportiunivamalg #9',\n        'scholaramalgamalg #9'\n      )\n    )\n    AND (\n      \"item\".\"i_brand\" IN ('amalgimporto #1', 'edu packscholar #1', 'exportiimporto #1', 'importoamalg #1')\n      OR \"item\".\"i_category\" IN ('Books', 'Children', 'Electronics')\n    )\n    AND (\n      \"item\".\"i_brand\" IN ('amalgimporto #1', 'edu packscholar #1', 'exportiimporto #1', 'importoamalg #1')\n      OR \"item\".\"i_class\" IN ('personal', 'portable', 'reference', 'self-help')\n    )\n    AND (\n      \"item\".\"i_brand\" IN (\n        'scholaramalgamalg #14',\n        'scholaramalgamalg #7',\n        'exportiunivamalg #9',\n        'scholaramalgamalg #9'\n      )\n      OR \"item\".\"i_category\" IN ('Women', 'Music', 'Men')\n    )\n    AND (\n      \"item\".\"i_brand\" IN (\n        'scholaramalgamalg #14',\n        'scholaramalgamalg #7',\n        'exportiunivamalg #9',\n        'scholaramalgamalg #9'\n      )\n      OR \"item\".\"i_class\" IN ('accessories', 'classical', 'fragrances', 'pants')\n    )\n    AND (\n      \"item\".\"i_category\" IN ('Books', 'Children', 'Electronics')\n      OR \"item\".\"i_category\" IN ('Women', 'Music', 'Men')\n    )\n    AND (\n      \"item\".\"i_category\" IN ('Books', 'Children', 'Electronics')\n      OR \"item\".\"i_class\" IN ('accessories', 'classical', 'fragrances', 'pants')\n    )\n    AND (\n      \"item\".\"i_category\" IN ('Women', 'Music', 'Men')\n      OR \"item\".\"i_class\" IN ('personal', 'portable', 'reference', 'self-help')\n    )\n    AND (\n      \"item\".\"i_class\" IN ('accessories', 'classical', 'fragrances', 'pants')\n      OR \"item\".\"i_class\" IN ('personal', 'portable', 'reference', 'self-help')\n    )\n  GROUP BY\n    \"item\".\"i_manager_id\",\n    \"date_dim\".\"d_moy\"\n)\nSELECT\n  \"tmp1\".\"i_manager_id\" AS \"i_manager_id\",\n  \"tmp1\".\"sum_sales\" AS \"sum_sales\",\n  \"tmp1\".\"avg_monthly_sales\" AS \"avg_monthly_sales\"\nFROM \"tmp1\" AS \"tmp1\"\nWHERE\n  CASE\n    WHEN \"tmp1\".\"avg_monthly_sales\" > 0\n    THEN ABS(\"tmp1\".\"sum_sales\" - \"tmp1\".\"avg_monthly_sales\") / \"tmp1\".\"avg_monthly_sales\"\n    ELSE NULL\n  END > 0.1\nORDER BY\n  \"tmp1\".\"i_manager_id\",\n  \"tmp1\".\"avg_monthly_sales\",\n  \"tmp1\".\"sum_sales\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 64\n--------------------------------------\nWITH cs_ui\n     AS (SELECT cs_item_sk,\n                Sum(cs_ext_list_price) AS sale,\n                Sum(cr_refunded_cash + cr_reversed_charge\n                    + cr_store_credit) AS refund\n         FROM   catalog_sales,\n                catalog_returns\n         WHERE  cs_item_sk = cr_item_sk\n                AND cs_order_number = cr_order_number\n         GROUP  BY cs_item_sk\n         HAVING Sum(cs_ext_list_price) > 2 * Sum(\n                cr_refunded_cash + cr_reversed_charge\n                + cr_store_credit)),\n     cross_sales\n     AS (SELECT i_product_name         product_name,\n                i_item_sk              item_sk,\n                s_store_name           store_name,\n                s_zip                  store_zip,\n                ad1.ca_street_number   b_street_number,\n                ad1.ca_street_name     b_streen_name,\n                ad1.ca_city            b_city,\n                ad1.ca_zip             b_zip,\n                ad2.ca_street_number   c_street_number,\n                ad2.ca_street_name     c_street_name,\n                ad2.ca_city            c_city,\n                ad2.ca_zip             c_zip,\n                d1.d_year              AS syear,\n                d2.d_year              AS fsyear,\n                d3.d_year              s2year,\n                Count(*)               cnt,\n                Sum(ss_wholesale_cost) s1,\n                Sum(ss_list_price)     s2,\n                Sum(ss_coupon_amt)     s3\n         FROM   store_sales,\n                store_returns,\n                cs_ui,\n                date_dim d1,\n                date_dim d2,\n                date_dim d3,\n                store,\n                customer,\n                customer_demographics cd1,\n                customer_demographics cd2,\n                promotion,\n                household_demographics hd1,\n                household_demographics hd2,\n                customer_address ad1,\n                customer_address ad2,\n                income_band ib1,\n                income_band ib2,\n                item\n         WHERE  ss_store_sk = s_store_sk\n                AND ss_sold_date_sk = d1.d_date_sk\n                AND ss_customer_sk = c_customer_sk\n                AND ss_cdemo_sk = cd1.cd_demo_sk\n                AND ss_hdemo_sk = hd1.hd_demo_sk\n                AND ss_addr_sk = ad1.ca_address_sk\n                AND ss_item_sk = i_item_sk\n                AND ss_item_sk = sr_item_sk\n                AND ss_ticket_number = sr_ticket_number\n                AND ss_item_sk = cs_ui.cs_item_sk\n                AND c_current_cdemo_sk = cd2.cd_demo_sk\n                AND c_current_hdemo_sk = hd2.hd_demo_sk\n                AND c_current_addr_sk = ad2.ca_address_sk\n                AND c_first_sales_date_sk = d2.d_date_sk\n                AND c_first_shipto_date_sk = d3.d_date_sk\n                AND ss_promo_sk = p_promo_sk\n                AND hd1.hd_income_band_sk = ib1.ib_income_band_sk\n                AND hd2.hd_income_band_sk = ib2.ib_income_band_sk\n                AND cd1.cd_marital_status <> cd2.cd_marital_status\n                AND i_color IN ( 'cyan', 'peach', 'blush', 'frosted',\n                                 'powder', 'orange' )\n                AND i_current_price BETWEEN 58 AND 58 + 10\n                AND i_current_price BETWEEN 58 + 1 AND 58 + 15\n         GROUP  BY i_product_name,\n                   i_item_sk,\n                   s_store_name,\n                   s_zip,\n                   ad1.ca_street_number,\n                   ad1.ca_street_name,\n                   ad1.ca_city,\n                   ad1.ca_zip,\n                   ad2.ca_street_number,\n                   ad2.ca_street_name,\n                   ad2.ca_city,\n                   ad2.ca_zip,\n                   d1.d_year,\n                   d2.d_year,\n                   d3.d_year)\nSELECT cs1.product_name,\n       cs1.store_name,\n       cs1.store_zip,\n       cs1.b_street_number,\n       cs1.b_streen_name,\n       cs1.b_city,\n       cs1.b_zip,\n       cs1.c_street_number,\n       cs1.c_street_name,\n       cs1.c_city,\n       cs1.c_zip,\n       cs1.syear,\n       cs1.cnt,\n       cs1.s1,\n       cs1.s2,\n       cs1.s3,\n       cs2.s1,\n       cs2.s2,\n       cs2.s3,\n       cs2.syear,\n       cs2.cnt\nFROM   cross_sales cs1,\n       cross_sales cs2\nWHERE  cs1.item_sk = cs2.item_sk\n       AND cs1.syear = 2001\n       AND cs2.syear = 2001 + 1\n       AND cs2.cnt <= cs1.cnt\n       AND cs1.store_name = cs2.store_name\n       AND cs1.store_zip = cs2.store_zip\nORDER  BY cs1.product_name,\n          cs1.store_name,\n          cs2.cnt;\nWITH \"cs_ui\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_item_sk\" AS \"cs_item_sk\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"catalog_returns\" AS \"catalog_returns\"\n    ON \"catalog_returns\".\"cr_item_sk\" = \"catalog_sales\".\"cs_item_sk\"\n    AND \"catalog_returns\".\"cr_order_number\" = \"catalog_sales\".\"cs_order_number\"\n  GROUP BY\n    \"catalog_sales\".\"cs_item_sk\"\n  HAVING\n    2 * SUM(\n      \"catalog_returns\".\"cr_refunded_cash\" + \"catalog_returns\".\"cr_reversed_charge\" + \"catalog_returns\".\"cr_store_credit\"\n    ) < SUM(\"catalog_sales\".\"cs_ext_list_price\")\n), \"cross_sales\" AS (\n  SELECT\n    \"item\".\"i_product_name\" AS \"product_name\",\n    \"item\".\"i_item_sk\" AS \"item_sk\",\n    \"store\".\"s_store_name\" AS \"store_name\",\n    \"store\".\"s_zip\" AS \"store_zip\",\n    \"ad1\".\"ca_street_number\" AS \"b_street_number\",\n    \"ad1\".\"ca_street_name\" AS \"b_streen_name\",\n    \"ad1\".\"ca_city\" AS \"b_city\",\n    \"ad1\".\"ca_zip\" AS \"b_zip\",\n    \"ad2\".\"ca_street_number\" AS \"c_street_number\",\n    \"ad2\".\"ca_street_name\" AS \"c_street_name\",\n    \"ad2\".\"ca_city\" AS \"c_city\",\n    \"ad2\".\"ca_zip\" AS \"c_zip\",\n    \"d1\".\"d_year\" AS \"syear\",\n    COUNT(*) AS \"cnt\",\n    SUM(\"store_sales\".\"ss_wholesale_cost\") AS \"s1\",\n    SUM(\"store_sales\".\"ss_list_price\") AS \"s2\",\n    SUM(\"store_sales\".\"ss_coupon_amt\") AS \"s3\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer_address\" AS \"ad1\"\n    ON \"ad1\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n  JOIN \"customer_demographics\" AS \"cd1\"\n    ON \"cd1\".\"cd_demo_sk\" = \"store_sales\".\"ss_cdemo_sk\"\n  JOIN \"cs_ui\" AS \"cs_ui\"\n    ON \"cs_ui\".\"cs_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"customer\" AS \"customer\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim\" AS \"d1\"\n    ON \"d1\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"household_demographics\" AS \"hd1\"\n    ON \"hd1\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"item\" AS \"item\"\n    ON \"item\".\"i_color\" IN ('cyan', 'peach', 'blush', 'frosted', 'powder', 'orange')\n    AND \"item\".\"i_current_price\" <= 68\n    AND \"item\".\"i_current_price\" >= 59\n    AND \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"promotion\" AS \"promotion\"\n    ON \"promotion\".\"p_promo_sk\" = \"store_sales\".\"ss_promo_sk\"\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"store_returns\" AS \"store_returns\"\n    ON \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n    AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\n  JOIN \"customer_address\" AS \"ad2\"\n    ON \"ad2\".\"ca_address_sk\" = \"customer\".\"c_current_addr_sk\"\n  JOIN \"customer_demographics\" AS \"cd2\"\n    ON \"cd1\".\"cd_marital_status\" <> \"cd2\".\"cd_marital_status\"\n    AND \"cd2\".\"cd_demo_sk\" = \"customer\".\"c_current_cdemo_sk\"\n  JOIN \"date_dim\" AS \"d2\"\n    ON \"customer\".\"c_first_sales_date_sk\" = \"d2\".\"d_date_sk\"\n  JOIN \"date_dim\" AS \"d3\"\n    ON \"customer\".\"c_first_shipto_date_sk\" = \"d3\".\"d_date_sk\"\n  JOIN \"household_demographics\" AS \"hd2\"\n    ON \"customer\".\"c_current_hdemo_sk\" = \"hd2\".\"hd_demo_sk\"\n  JOIN \"income_band\" AS \"ib1\"\n    ON \"hd1\".\"hd_income_band_sk\" = \"ib1\".\"ib_income_band_sk\"\n  JOIN \"income_band\" AS \"ib2\"\n    ON \"hd2\".\"hd_income_band_sk\" = \"ib2\".\"ib_income_band_sk\"\n  GROUP BY\n    \"item\".\"i_product_name\",\n    \"item\".\"i_item_sk\",\n    \"store\".\"s_store_name\",\n    \"store\".\"s_zip\",\n    \"ad1\".\"ca_street_number\",\n    \"ad1\".\"ca_street_name\",\n    \"ad1\".\"ca_city\",\n    \"ad1\".\"ca_zip\",\n    \"ad2\".\"ca_street_number\",\n    \"ad2\".\"ca_street_name\",\n    \"ad2\".\"ca_city\",\n    \"ad2\".\"ca_zip\",\n    \"d1\".\"d_year\",\n    \"d2\".\"d_year\",\n    \"d3\".\"d_year\"\n)\nSELECT\n  \"cs1\".\"product_name\" AS \"product_name\",\n  \"cs1\".\"store_name\" AS \"store_name\",\n  \"cs1\".\"store_zip\" AS \"store_zip\",\n  \"cs1\".\"b_street_number\" AS \"b_street_number\",\n  \"cs1\".\"b_streen_name\" AS \"b_streen_name\",\n  \"cs1\".\"b_city\" AS \"b_city\",\n  \"cs1\".\"b_zip\" AS \"b_zip\",\n  \"cs1\".\"c_street_number\" AS \"c_street_number\",\n  \"cs1\".\"c_street_name\" AS \"c_street_name\",\n  \"cs1\".\"c_city\" AS \"c_city\",\n  \"cs1\".\"c_zip\" AS \"c_zip\",\n  \"cs1\".\"syear\" AS \"syear\",\n  \"cs1\".\"cnt\" AS \"cnt\",\n  \"cs1\".\"s1\" AS \"s1\",\n  \"cs1\".\"s2\" AS \"s2\",\n  \"cs1\".\"s3\" AS \"s3\",\n  \"cs2\".\"s1\" AS \"s1\",\n  \"cs2\".\"s2\" AS \"s2\",\n  \"cs2\".\"s3\" AS \"s3\",\n  \"cs2\".\"syear\" AS \"syear\",\n  \"cs2\".\"cnt\" AS \"cnt\"\nFROM \"cross_sales\" AS \"cs1\"\nJOIN \"cross_sales\" AS \"cs2\"\n  ON \"cs1\".\"cnt\" >= \"cs2\".\"cnt\"\n  AND \"cs1\".\"item_sk\" = \"cs2\".\"item_sk\"\n  AND \"cs1\".\"store_name\" = \"cs2\".\"store_name\"\n  AND \"cs1\".\"store_zip\" = \"cs2\".\"store_zip\"\n  AND \"cs2\".\"syear\" = 2002\nWHERE\n  \"cs1\".\"syear\" = 2001\nORDER BY\n  \"cs1\".\"product_name\",\n  \"cs1\".\"store_name\",\n  \"cs2\".\"cnt\";\n\n--------------------------------------\n-- TPC-DS 65\n--------------------------------------\n# execute: true\nSELECT s_store_name,\n               i_item_desc,\n               sc.revenue,\n               i_current_price,\n               i_wholesale_cost,\n               i_brand\nFROM   store,\n       item,\n       (SELECT ss_store_sk,\n               Avg(revenue) AS ave\n        FROM   (SELECT ss_store_sk,\n                       ss_item_sk,\n                       Sum(ss_sales_price) AS revenue\n                FROM   store_sales,\n                       date_dim\n                WHERE  ss_sold_date_sk = d_date_sk\n                       AND d_month_seq BETWEEN 1199 AND 1199 + 11\n                GROUP  BY ss_store_sk,\n                          ss_item_sk) sa\n        GROUP  BY ss_store_sk) sb,\n       (SELECT ss_store_sk,\n               ss_item_sk,\n               Sum(ss_sales_price) AS revenue\n        FROM   store_sales,\n               date_dim\n        WHERE  ss_sold_date_sk = d_date_sk\n               AND d_month_seq BETWEEN 1199 AND 1199 + 11\n        GROUP  BY ss_store_sk,\n                  ss_item_sk) sc\nWHERE  sb.ss_store_sk = sc.ss_store_sk\n       AND sc.revenue <= 0.1 * sb.ave\n       AND s_store_sk = sc.ss_store_sk\n       AND i_item_sk = sc.ss_item_sk\nORDER  BY s_store_name,\n          i_item_desc\nLIMIT 100;\nWITH \"store_sales_2\" AS (\n  SELECT\n    \"store_sales\".\"ss_sold_date_sk\" AS \"ss_sold_date_sk\",\n    \"store_sales\".\"ss_item_sk\" AS \"ss_item_sk\",\n    \"store_sales\".\"ss_store_sk\" AS \"ss_store_sk\",\n    \"store_sales\".\"ss_sales_price\" AS \"ss_sales_price\"\n  FROM \"store_sales\" AS \"store_sales\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_month_seq\" <= 1210 AND \"date_dim\".\"d_month_seq\" >= 1199\n), \"sc\" AS (\n  SELECT\n    \"store_sales\".\"ss_store_sk\" AS \"ss_store_sk\",\n    \"store_sales\".\"ss_item_sk\" AS \"ss_item_sk\",\n    SUM(\"store_sales\".\"ss_sales_price\") AS \"revenue\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_store_sk\",\n    \"store_sales\".\"ss_item_sk\"\n), \"sa\" AS (\n  SELECT\n    \"store_sales\".\"ss_store_sk\" AS \"ss_store_sk\",\n    SUM(\"store_sales\".\"ss_sales_price\") AS \"revenue\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_store_sk\",\n    \"store_sales\".\"ss_item_sk\"\n), \"sb\" AS (\n  SELECT\n    \"sa\".\"ss_store_sk\" AS \"ss_store_sk\",\n    AVG(\"sa\".\"revenue\") AS \"ave\"\n  FROM \"sa\" AS \"sa\"\n  GROUP BY\n    \"sa\".\"ss_store_sk\"\n)\nSELECT\n  \"store\".\"s_store_name\" AS \"s_store_name\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"sc\".\"revenue\" AS \"revenue\",\n  \"item\".\"i_current_price\" AS \"i_current_price\",\n  \"item\".\"i_wholesale_cost\" AS \"i_wholesale_cost\",\n  \"item\".\"i_brand\" AS \"i_brand\"\nFROM \"store\" AS \"store\"\nJOIN \"sc\" AS \"sc\"\n  ON \"sc\".\"ss_store_sk\" = \"store\".\"s_store_sk\"\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"sc\".\"ss_item_sk\"\nJOIN \"sb\" AS \"sb\"\n  ON \"sb\".\"ss_store_sk\" = \"sc\".\"ss_store_sk\" AND \"sc\".\"revenue\" <= 0.1 * \"sb\".\"ave\"\nORDER BY\n  \"s_store_name\",\n  \"i_item_desc\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 66\n--------------------------------------\n# execute: true\nSELECT w_warehouse_name,\n               w_warehouse_sq_ft,\n               w_city,\n               w_county,\n               w_state,\n               w_country,\n               ship_carriers,\n               year1,\n               Sum(jan_sales)                     AS jan_sales,\n               Sum(feb_sales)                     AS feb_sales,\n               Sum(mar_sales)                     AS mar_sales,\n               Sum(apr_sales)                     AS apr_sales,\n               Sum(may_sales)                     AS may_sales,\n               Sum(jun_sales)                     AS jun_sales,\n               Sum(jul_sales)                     AS jul_sales,\n               Sum(aug_sales)                     AS aug_sales,\n               Sum(sep_sales)                     AS sep_sales,\n               Sum(oct_sales)                     AS oct_sales,\n               Sum(nov_sales)                     AS nov_sales,\n               Sum(dec_sales)                     AS dec_sales,\n               Sum(jan_sales / w_warehouse_sq_ft) AS jan_sales_per_sq_foot,\n               Sum(feb_sales / w_warehouse_sq_ft) AS feb_sales_per_sq_foot,\n               Sum(mar_sales / w_warehouse_sq_ft) AS mar_sales_per_sq_foot,\n               Sum(apr_sales / w_warehouse_sq_ft) AS apr_sales_per_sq_foot,\n               Sum(may_sales / w_warehouse_sq_ft) AS may_sales_per_sq_foot,\n               Sum(jun_sales / w_warehouse_sq_ft) AS jun_sales_per_sq_foot,\n               Sum(jul_sales / w_warehouse_sq_ft) AS jul_sales_per_sq_foot,\n               Sum(aug_sales / w_warehouse_sq_ft) AS aug_sales_per_sq_foot,\n               Sum(sep_sales / w_warehouse_sq_ft) AS sep_sales_per_sq_foot,\n               Sum(oct_sales / w_warehouse_sq_ft) AS oct_sales_per_sq_foot,\n               Sum(nov_sales / w_warehouse_sq_ft) AS nov_sales_per_sq_foot,\n               Sum(dec_sales / w_warehouse_sq_ft) AS dec_sales_per_sq_foot,\n               Sum(jan_net)                       AS jan_net,\n               Sum(feb_net)                       AS feb_net,\n               Sum(mar_net)                       AS mar_net,\n               Sum(apr_net)                       AS apr_net,\n               Sum(may_net)                       AS may_net,\n               Sum(jun_net)                       AS jun_net,\n               Sum(jul_net)                       AS jul_net,\n               Sum(aug_net)                       AS aug_net,\n               Sum(sep_net)                       AS sep_net,\n               Sum(oct_net)                       AS oct_net,\n               Sum(nov_net)                       AS nov_net,\n               Sum(dec_net)                       AS dec_net\nFROM   (SELECT w_warehouse_name,\n               w_warehouse_sq_ft,\n               w_city,\n               w_county,\n               w_state,\n               w_country,\n               'ZOUROS'\n               || ','\n               || 'ZHOU' AS ship_carriers,\n               d_year    AS year1,\n               Sum(CASE\n                     WHEN d_moy = 1 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS jan_sales,\n               Sum(CASE\n                     WHEN d_moy = 2 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS feb_sales,\n               Sum(CASE\n                     WHEN d_moy = 3 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS mar_sales,\n               Sum(CASE\n                     WHEN d_moy = 4 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS apr_sales,\n               Sum(CASE\n                     WHEN d_moy = 5 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS may_sales,\n               Sum(CASE\n                     WHEN d_moy = 6 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS jun_sales,\n               Sum(CASE\n                     WHEN d_moy = 7 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS jul_sales,\n               Sum(CASE\n                     WHEN d_moy = 8 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS aug_sales,\n               Sum(CASE\n                     WHEN d_moy = 9 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS sep_sales,\n               Sum(CASE\n                     WHEN d_moy = 10 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS oct_sales,\n               Sum(CASE\n                     WHEN d_moy = 11 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS nov_sales,\n               Sum(CASE\n                     WHEN d_moy = 12 THEN ws_ext_sales_price * ws_quantity\n                     ELSE 0\n                   END)  AS dec_sales,\n               Sum(CASE\n                     WHEN d_moy = 1 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS jan_net,\n               Sum(CASE\n                     WHEN d_moy = 2 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS feb_net,\n               Sum(CASE\n                     WHEN d_moy = 3 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS mar_net,\n               Sum(CASE\n                     WHEN d_moy = 4 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS apr_net,\n               Sum(CASE\n                     WHEN d_moy = 5 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS may_net,\n               Sum(CASE\n                     WHEN d_moy = 6 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS jun_net,\n               Sum(CASE\n                     WHEN d_moy = 7 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS jul_net,\n               Sum(CASE\n                     WHEN d_moy = 8 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS aug_net,\n               Sum(CASE\n                     WHEN d_moy = 9 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS sep_net,\n               Sum(CASE\n                     WHEN d_moy = 10 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS oct_net,\n               Sum(CASE\n                     WHEN d_moy = 11 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS nov_net,\n               Sum(CASE\n                     WHEN d_moy = 12 THEN ws_net_paid_inc_ship * ws_quantity\n                     ELSE 0\n                   END)  AS dec_net\n        FROM   web_sales,\n               warehouse,\n               date_dim,\n               time_dim,\n               ship_mode\n        WHERE  ws_warehouse_sk = w_warehouse_sk\n               AND ws_sold_date_sk = d_date_sk\n               AND ws_sold_time_sk = t_time_sk\n               AND ws_ship_mode_sk = sm_ship_mode_sk\n               AND d_year = 1998\n               AND t_time BETWEEN 7249 AND 7249 + 28800\n               AND sm_carrier IN ( 'ZOUROS', 'ZHOU' )\n        GROUP  BY w_warehouse_name,\n                  w_warehouse_sq_ft,\n                  w_city,\n                  w_county,\n                  w_state,\n                  w_country,\n                  d_year\n        UNION ALL\n        SELECT w_warehouse_name,\n               w_warehouse_sq_ft,\n               w_city,\n               w_county,\n               w_state,\n               w_country,\n               'ZOUROS'\n               || ','\n               || 'ZHOU' AS ship_carriers,\n               d_year    AS year1,\n               Sum(CASE\n                     WHEN d_moy = 1 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS jan_sales,\n               Sum(CASE\n                     WHEN d_moy = 2 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS feb_sales,\n               Sum(CASE\n                     WHEN d_moy = 3 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS mar_sales,\n               Sum(CASE\n                     WHEN d_moy = 4 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS apr_sales,\n               Sum(CASE\n                     WHEN d_moy = 5 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS may_sales,\n               Sum(CASE\n                     WHEN d_moy = 6 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS jun_sales,\n               Sum(CASE\n                     WHEN d_moy = 7 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS jul_sales,\n               Sum(CASE\n                     WHEN d_moy = 8 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS aug_sales,\n               Sum(CASE\n                     WHEN d_moy = 9 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS sep_sales,\n               Sum(CASE\n                     WHEN d_moy = 10 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS oct_sales,\n               Sum(CASE\n                     WHEN d_moy = 11 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS nov_sales,\n               Sum(CASE\n                     WHEN d_moy = 12 THEN cs_ext_sales_price * cs_quantity\n                     ELSE 0\n                   END)  AS dec_sales,\n               Sum(CASE\n                     WHEN d_moy = 1 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS jan_net,\n               Sum(CASE\n                     WHEN d_moy = 2 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS feb_net,\n               Sum(CASE\n                     WHEN d_moy = 3 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS mar_net,\n               Sum(CASE\n                     WHEN d_moy = 4 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS apr_net,\n               Sum(CASE\n                     WHEN d_moy = 5 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS may_net,\n               Sum(CASE\n                     WHEN d_moy = 6 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS jun_net,\n               Sum(CASE\n                     WHEN d_moy = 7 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS jul_net,\n               Sum(CASE\n                     WHEN d_moy = 8 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS aug_net,\n               Sum(CASE\n                     WHEN d_moy = 9 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS sep_net,\n               Sum(CASE\n                     WHEN d_moy = 10 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS oct_net,\n               Sum(CASE\n                     WHEN d_moy = 11 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS nov_net,\n               Sum(CASE\n                     WHEN d_moy = 12 THEN cs_net_paid * cs_quantity\n                     ELSE 0\n                   END)  AS dec_net\n        FROM   catalog_sales,\n               warehouse,\n               date_dim,\n               time_dim,\n               ship_mode\n        WHERE  cs_warehouse_sk = w_warehouse_sk\n               AND cs_sold_date_sk = d_date_sk\n               AND cs_sold_time_sk = t_time_sk\n               AND cs_ship_mode_sk = sm_ship_mode_sk\n               AND d_year = 1998\n               AND t_time BETWEEN 7249 AND 7249 + 28800\n               AND sm_carrier IN ( 'ZOUROS', 'ZHOU' )\n        GROUP  BY w_warehouse_name,\n                  w_warehouse_sq_ft,\n                  w_city,\n                  w_county,\n                  w_state,\n                  w_country,\n                  d_year) x\nGROUP  BY w_warehouse_name,\n          w_warehouse_sq_ft,\n          w_city,\n          w_county,\n          w_state,\n          w_country,\n          ship_carriers,\n          year1\nORDER  BY w_warehouse_name\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_year\" = 1998\n), \"ship_mode_2\" AS (\n  SELECT\n    \"ship_mode\".\"sm_ship_mode_sk\" AS \"sm_ship_mode_sk\",\n    \"ship_mode\".\"sm_carrier\" AS \"sm_carrier\"\n  FROM \"ship_mode\" AS \"ship_mode\"\n  WHERE\n    \"ship_mode\".\"sm_carrier\" IN ('ZOUROS', 'ZHOU')\n), \"time_dim_2\" AS (\n  SELECT\n    \"time_dim\".\"t_time_sk\" AS \"t_time_sk\",\n    \"time_dim\".\"t_time\" AS \"t_time\"\n  FROM \"time_dim\" AS \"time_dim\"\n  WHERE\n    \"time_dim\".\"t_time\" <= 36049 AND \"time_dim\".\"t_time\" >= 7249\n), \"warehouse_2\" AS (\n  SELECT\n    \"warehouse\".\"w_warehouse_sk\" AS \"w_warehouse_sk\",\n    \"warehouse\".\"w_warehouse_name\" AS \"w_warehouse_name\",\n    \"warehouse\".\"w_warehouse_sq_ft\" AS \"w_warehouse_sq_ft\",\n    \"warehouse\".\"w_city\" AS \"w_city\",\n    \"warehouse\".\"w_county\" AS \"w_county\",\n    \"warehouse\".\"w_state\" AS \"w_state\",\n    \"warehouse\".\"w_country\" AS \"w_country\"\n  FROM \"warehouse\" AS \"warehouse\"\n), \"x\" AS (\n  SELECT\n    \"warehouse\".\"w_warehouse_name\" AS \"w_warehouse_name\",\n    \"warehouse\".\"w_warehouse_sq_ft\" AS \"w_warehouse_sq_ft\",\n    \"warehouse\".\"w_city\" AS \"w_city\",\n    \"warehouse\".\"w_county\" AS \"w_county\",\n    \"warehouse\".\"w_state\" AS \"w_state\",\n    \"warehouse\".\"w_country\" AS \"w_country\",\n    'ZOUROS,ZHOU' AS \"ship_carriers\",\n    \"date_dim\".\"d_year\" AS \"year1\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 1\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"jan_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 2\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"feb_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 3\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"mar_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 4\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"apr_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 5\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"may_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 6\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"jun_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 7\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"jul_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 8\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"aug_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 9\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"sep_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 10\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"oct_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 11\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"nov_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 12\n        THEN \"web_sales\".\"ws_ext_sales_price\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"dec_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 1\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"jan_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 2\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"feb_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 3\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"mar_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 4\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"apr_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 5\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"may_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 6\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"jun_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 7\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"jul_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 8\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"aug_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 9\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"sep_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 10\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"oct_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 11\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"nov_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 12\n        THEN \"web_sales\".\"ws_net_paid_inc_ship\" * \"web_sales\".\"ws_quantity\"\n        ELSE 0\n      END\n    ) AS \"dec_net\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  JOIN \"ship_mode_2\" AS \"ship_mode\"\n    ON \"ship_mode\".\"sm_ship_mode_sk\" = \"web_sales\".\"ws_ship_mode_sk\"\n  JOIN \"time_dim_2\" AS \"time_dim\"\n    ON \"time_dim\".\"t_time_sk\" = \"web_sales\".\"ws_sold_time_sk\"\n  JOIN \"warehouse_2\" AS \"warehouse\"\n    ON \"warehouse\".\"w_warehouse_sk\" = \"web_sales\".\"ws_warehouse_sk\"\n  GROUP BY\n    \"warehouse\".\"w_warehouse_name\",\n    \"warehouse\".\"w_warehouse_sq_ft\",\n    \"warehouse\".\"w_city\",\n    \"warehouse\".\"w_county\",\n    \"warehouse\".\"w_state\",\n    \"warehouse\".\"w_country\",\n    \"date_dim\".\"d_year\"\n  UNION ALL\n  SELECT\n    \"warehouse\".\"w_warehouse_name\" AS \"w_warehouse_name\",\n    \"warehouse\".\"w_warehouse_sq_ft\" AS \"w_warehouse_sq_ft\",\n    \"warehouse\".\"w_city\" AS \"w_city\",\n    \"warehouse\".\"w_county\" AS \"w_county\",\n    \"warehouse\".\"w_state\" AS \"w_state\",\n    \"warehouse\".\"w_country\" AS \"w_country\",\n    'ZOUROS,ZHOU' AS \"ship_carriers\",\n    \"date_dim\".\"d_year\" AS \"year1\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 1\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"jan_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 2\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"feb_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 3\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"mar_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 4\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"apr_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 5\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"may_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 6\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"jun_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 7\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"jul_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 8\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"aug_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 9\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"sep_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 10\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"oct_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 11\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"nov_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 12\n        THEN \"catalog_sales\".\"cs_ext_sales_price\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"dec_sales\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 1\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"jan_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 2\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"feb_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 3\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"mar_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 4\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"apr_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 5\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"may_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 6\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"jun_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 7\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"jul_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 8\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"aug_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 9\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"sep_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 10\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"oct_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 11\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"nov_net\",\n    SUM(\n      CASE\n        WHEN \"date_dim\".\"d_moy\" = 12\n        THEN \"catalog_sales\".\"cs_net_paid\" * \"catalog_sales\".\"cs_quantity\"\n        ELSE 0\n      END\n    ) AS \"dec_net\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  JOIN \"ship_mode_2\" AS \"ship_mode\"\n    ON \"catalog_sales\".\"cs_ship_mode_sk\" = \"ship_mode\".\"sm_ship_mode_sk\"\n  JOIN \"time_dim_2\" AS \"time_dim\"\n    ON \"catalog_sales\".\"cs_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n  JOIN \"warehouse_2\" AS \"warehouse\"\n    ON \"catalog_sales\".\"cs_warehouse_sk\" = \"warehouse\".\"w_warehouse_sk\"\n  GROUP BY\n    \"warehouse\".\"w_warehouse_name\",\n    \"warehouse\".\"w_warehouse_sq_ft\",\n    \"warehouse\".\"w_city\",\n    \"warehouse\".\"w_county\",\n    \"warehouse\".\"w_state\",\n    \"warehouse\".\"w_country\",\n    \"date_dim\".\"d_year\"\n)\nSELECT\n  \"x\".\"w_warehouse_name\" AS \"w_warehouse_name\",\n  \"x\".\"w_warehouse_sq_ft\" AS \"w_warehouse_sq_ft\",\n  \"x\".\"w_city\" AS \"w_city\",\n  \"x\".\"w_county\" AS \"w_county\",\n  \"x\".\"w_state\" AS \"w_state\",\n  \"x\".\"w_country\" AS \"w_country\",\n  \"x\".\"ship_carriers\" AS \"ship_carriers\",\n  \"x\".\"year1\" AS \"year1\",\n  SUM(\"x\".\"jan_sales\") AS \"jan_sales\",\n  SUM(\"x\".\"feb_sales\") AS \"feb_sales\",\n  SUM(\"x\".\"mar_sales\") AS \"mar_sales\",\n  SUM(\"x\".\"apr_sales\") AS \"apr_sales\",\n  SUM(\"x\".\"may_sales\") AS \"may_sales\",\n  SUM(\"x\".\"jun_sales\") AS \"jun_sales\",\n  SUM(\"x\".\"jul_sales\") AS \"jul_sales\",\n  SUM(\"x\".\"aug_sales\") AS \"aug_sales\",\n  SUM(\"x\".\"sep_sales\") AS \"sep_sales\",\n  SUM(\"x\".\"oct_sales\") AS \"oct_sales\",\n  SUM(\"x\".\"nov_sales\") AS \"nov_sales\",\n  SUM(\"x\".\"dec_sales\") AS \"dec_sales\",\n  SUM(\"x\".\"jan_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"jan_sales_per_sq_foot\",\n  SUM(\"x\".\"feb_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"feb_sales_per_sq_foot\",\n  SUM(\"x\".\"mar_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"mar_sales_per_sq_foot\",\n  SUM(\"x\".\"apr_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"apr_sales_per_sq_foot\",\n  SUM(\"x\".\"may_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"may_sales_per_sq_foot\",\n  SUM(\"x\".\"jun_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"jun_sales_per_sq_foot\",\n  SUM(\"x\".\"jul_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"jul_sales_per_sq_foot\",\n  SUM(\"x\".\"aug_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"aug_sales_per_sq_foot\",\n  SUM(\"x\".\"sep_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"sep_sales_per_sq_foot\",\n  SUM(\"x\".\"oct_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"oct_sales_per_sq_foot\",\n  SUM(\"x\".\"nov_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"nov_sales_per_sq_foot\",\n  SUM(\"x\".\"dec_sales\" / \"x\".\"w_warehouse_sq_ft\") AS \"dec_sales_per_sq_foot\",\n  SUM(\"x\".\"jan_net\") AS \"jan_net\",\n  SUM(\"x\".\"feb_net\") AS \"feb_net\",\n  SUM(\"x\".\"mar_net\") AS \"mar_net\",\n  SUM(\"x\".\"apr_net\") AS \"apr_net\",\n  SUM(\"x\".\"may_net\") AS \"may_net\",\n  SUM(\"x\".\"jun_net\") AS \"jun_net\",\n  SUM(\"x\".\"jul_net\") AS \"jul_net\",\n  SUM(\"x\".\"aug_net\") AS \"aug_net\",\n  SUM(\"x\".\"sep_net\") AS \"sep_net\",\n  SUM(\"x\".\"oct_net\") AS \"oct_net\",\n  SUM(\"x\".\"nov_net\") AS \"nov_net\",\n  SUM(\"x\".\"dec_net\") AS \"dec_net\"\nFROM \"x\" AS \"x\"\nGROUP BY\n  \"x\".\"w_warehouse_name\",\n  \"x\".\"w_warehouse_sq_ft\",\n  \"x\".\"w_city\",\n  \"x\".\"w_county\",\n  \"x\".\"w_state\",\n  \"x\".\"w_country\",\n  \"x\".\"ship_carriers\",\n  \"x\".\"year1\"\nORDER BY\n  \"w_warehouse_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 67\n--------------------------------------\nselect *\nfrom (select i_category\n            ,i_class\n            ,i_brand\n            ,i_product_name\n            ,d_year\n            ,d_qoy\n            ,d_moy\n            ,s_store_id\n            ,sumsales\n            ,rank() over (partition by i_category order by sumsales desc) rk\n      from (select i_category\n                  ,i_class\n                  ,i_brand\n                  ,i_product_name\n                  ,d_year\n                  ,d_qoy\n                  ,d_moy\n                  ,s_store_id\n                  ,sum(coalesce(ss_sales_price*ss_quantity,0)) sumsales\n            from store_sales\n                ,date_dim\n                ,store\n                ,item\n       where  ss_sold_date_sk=d_date_sk\n          and ss_item_sk=i_item_sk\n          and ss_store_sk = s_store_sk\n          and d_month_seq between 1181 and 1181+11\n       group by  rollup(i_category, i_class, i_brand, i_product_name, d_year, d_qoy, d_moy,s_store_id))dw1) dw2\nwhere rk <= 100\norder by i_category\n        ,i_class\n        ,i_brand\n        ,i_product_name\n        ,d_year\n        ,d_qoy\n        ,d_moy\n        ,s_store_id\n        ,sumsales\n        ,rk\nlimit 100;\nWITH \"dw1\" AS (\n  SELECT\n    \"item\".\"i_category\" AS \"i_category\",\n    \"item\".\"i_class\" AS \"i_class\",\n    \"item\".\"i_brand\" AS \"i_brand\",\n    \"item\".\"i_product_name\" AS \"i_product_name\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\",\n    \"store\".\"s_store_id\" AS \"s_store_id\",\n    SUM(COALESCE(\"store_sales\".\"ss_sales_price\" * \"store_sales\".\"ss_quantity\", 0)) AS \"sumsales\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_month_seq\" <= 1192\n    AND \"date_dim\".\"d_month_seq\" >= 1181\n  JOIN \"item\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    ROLLUP (\n      \"item\".\"i_category\",\n      \"item\".\"i_class\",\n      \"item\".\"i_brand\",\n      \"item\".\"i_product_name\",\n      \"date_dim\".\"d_year\",\n      \"date_dim\".\"d_qoy\",\n      \"date_dim\".\"d_moy\",\n      \"store\".\"s_store_id\"\n    )\n), \"dw2\" AS (\n  SELECT\n    \"dw1\".\"i_category\" AS \"i_category\",\n    \"dw1\".\"i_class\" AS \"i_class\",\n    \"dw1\".\"i_brand\" AS \"i_brand\",\n    \"dw1\".\"i_product_name\" AS \"i_product_name\",\n    \"dw1\".\"d_year\" AS \"d_year\",\n    \"dw1\".\"d_qoy\" AS \"d_qoy\",\n    \"dw1\".\"d_moy\" AS \"d_moy\",\n    \"dw1\".\"s_store_id\" AS \"s_store_id\",\n    \"dw1\".\"sumsales\" AS \"sumsales\",\n    RANK() OVER (PARTITION BY \"dw1\".\"i_category\" ORDER BY \"dw1\".\"sumsales\" DESC) AS \"rk\"\n  FROM \"dw1\" AS \"dw1\"\n)\nSELECT\n  \"dw2\".\"i_category\" AS \"i_category\",\n  \"dw2\".\"i_class\" AS \"i_class\",\n  \"dw2\".\"i_brand\" AS \"i_brand\",\n  \"dw2\".\"i_product_name\" AS \"i_product_name\",\n  \"dw2\".\"d_year\" AS \"d_year\",\n  \"dw2\".\"d_qoy\" AS \"d_qoy\",\n  \"dw2\".\"d_moy\" AS \"d_moy\",\n  \"dw2\".\"s_store_id\" AS \"s_store_id\",\n  \"dw2\".\"sumsales\" AS \"sumsales\",\n  \"dw2\".\"rk\" AS \"rk\"\nFROM \"dw2\" AS \"dw2\"\nWHERE\n  \"dw2\".\"rk\" <= 100\nORDER BY\n  \"dw2\".\"i_category\",\n  \"dw2\".\"i_class\",\n  \"dw2\".\"i_brand\",\n  \"dw2\".\"i_product_name\",\n  \"dw2\".\"d_year\",\n  \"dw2\".\"d_qoy\",\n  \"dw2\".\"d_moy\",\n  \"dw2\".\"s_store_id\",\n  \"dw2\".\"sumsales\",\n  \"dw2\".\"rk\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 68\n--------------------------------------\n# execute: true\nSELECT c_last_name,\n               c_first_name,\n               ca_city,\n               bought_city,\n               ss_ticket_number,\n               extended_price,\n               extended_tax,\n               list_price\nFROM   (SELECT ss_ticket_number,\n               ss_customer_sk,\n               ca_city                 bought_city,\n               Sum(ss_ext_sales_price) extended_price,\n               Sum(ss_ext_list_price)  list_price,\n               Sum(ss_ext_tax)         extended_tax\n        FROM   store_sales,\n               date_dim,\n               store,\n               household_demographics,\n               customer_address\n        WHERE  store_sales.ss_sold_date_sk = date_dim.d_date_sk\n               AND store_sales.ss_store_sk = store.s_store_sk\n               AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk\n               AND store_sales.ss_addr_sk = customer_address.ca_address_sk\n               AND date_dim.d_dom BETWEEN 1 AND 2\n               AND ( household_demographics.hd_dep_count = 8\n                      OR household_demographics.hd_vehicle_count = 3 )\n               AND date_dim.d_year IN ( 1998, 1998 + 1, 1998 + 2 )\n               AND store.s_city IN ( 'Fairview', 'Midway' )\n        GROUP  BY ss_ticket_number,\n                  ss_customer_sk,\n                  ss_addr_sk,\n                  ca_city) dn,\n       customer,\n       customer_address current_addr\nWHERE  ss_customer_sk = c_customer_sk\n       AND customer.c_current_addr_sk = current_addr.ca_address_sk\n       AND current_addr.ca_city <> bought_city\nORDER  BY c_last_name,\n          ss_ticket_number\nLIMIT 100;\nWITH \"dn\" AS (\n  SELECT\n    \"store_sales\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\" AS \"ss_customer_sk\",\n    \"customer_address\".\"ca_city\" AS \"bought_city\",\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"extended_price\",\n    SUM(\"store_sales\".\"ss_ext_list_price\") AS \"list_price\",\n    SUM(\"store_sales\".\"ss_ext_tax\") AS \"extended_tax\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"customer_address\" AS \"customer_address\"\n    ON \"customer_address\".\"ca_address_sk\" = \"store_sales\".\"ss_addr_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_dom\" <= 2\n    AND \"date_dim\".\"d_dom\" >= 1\n    AND \"date_dim\".\"d_year\" IN (1998, 1999, 2000)\n  JOIN \"household_demographics\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n    AND (\n      \"household_demographics\".\"hd_dep_count\" = 8\n      OR \"household_demographics\".\"hd_vehicle_count\" = 3\n    )\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_city\" IN ('Fairview', 'Midway')\n    AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\",\n    \"store_sales\".\"ss_addr_sk\",\n    \"customer_address\".\"ca_city\"\n)\nSELECT\n  \"customer\".\"c_last_name\" AS \"c_last_name\",\n  \"customer\".\"c_first_name\" AS \"c_first_name\",\n  \"current_addr\".\"ca_city\" AS \"ca_city\",\n  \"dn\".\"bought_city\" AS \"bought_city\",\n  \"dn\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n  \"dn\".\"extended_price\" AS \"extended_price\",\n  \"dn\".\"extended_tax\" AS \"extended_tax\",\n  \"dn\".\"list_price\" AS \"list_price\"\nFROM \"dn\" AS \"dn\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_customer_sk\" = \"dn\".\"ss_customer_sk\"\nJOIN \"customer_address\" AS \"current_addr\"\n  ON \"current_addr\".\"ca_address_sk\" = \"customer\".\"c_current_addr_sk\"\n  AND \"current_addr\".\"ca_city\" <> \"dn\".\"bought_city\"\nORDER BY\n  \"c_last_name\",\n  \"ss_ticket_number\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 69\n--------------------------------------\n# execute: true\nSELECT cd_gender,\n               cd_marital_status,\n               cd_education_status,\n               Count(*) cnt1,\n               cd_purchase_estimate,\n               Count(*) cnt2,\n               cd_credit_rating,\n               Count(*) cnt3\nFROM   customer c,\n       customer_address ca,\n       customer_demographics\nWHERE  c.c_current_addr_sk = ca.ca_address_sk\n       AND ca_state IN ( 'KS', 'AZ', 'NE' )\n       AND cd_demo_sk = c.c_current_cdemo_sk\n       AND EXISTS (SELECT *\n                   FROM   store_sales,\n                          date_dim\n                   WHERE  c.c_customer_sk = ss_customer_sk\n                          AND ss_sold_date_sk = d_date_sk\n                          AND d_year = 2004\n                          AND d_moy BETWEEN 3 AND 3 + 2)\n       AND ( NOT EXISTS (SELECT *\n                         FROM   web_sales,\n                                date_dim\n                         WHERE  c.c_customer_sk = ws_bill_customer_sk\n                                AND ws_sold_date_sk = d_date_sk\n                                AND d_year = 2004\n                                AND d_moy BETWEEN 3 AND 3 + 2)\n             AND NOT EXISTS (SELECT *\n                             FROM   catalog_sales,\n                                    date_dim\n                             WHERE  c.c_customer_sk = cs_ship_customer_sk\n                                    AND cs_sold_date_sk = d_date_sk\n                                    AND d_year = 2004\n                                    AND d_moy BETWEEN 3 AND 3 + 2) )\nGROUP  BY cd_gender,\n          cd_marital_status,\n          cd_education_status,\n          cd_purchase_estimate,\n          cd_credit_rating\nORDER  BY cd_gender,\n          cd_marital_status,\n          cd_education_status,\n          cd_purchase_estimate,\n          cd_credit_rating\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date_id\" AS \"d_date_id\",\n    \"date_dim\".\"d_date\" AS \"d_date\",\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\",\n    \"date_dim\".\"d_week_seq\" AS \"d_week_seq\",\n    \"date_dim\".\"d_quarter_seq\" AS \"d_quarter_seq\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_dow\" AS \"d_dow\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\",\n    \"date_dim\".\"d_dom\" AS \"d_dom\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"date_dim\".\"d_fy_year\" AS \"d_fy_year\",\n    \"date_dim\".\"d_fy_quarter_seq\" AS \"d_fy_quarter_seq\",\n    \"date_dim\".\"d_fy_week_seq\" AS \"d_fy_week_seq\",\n    \"date_dim\".\"d_day_name\" AS \"d_day_name\",\n    \"date_dim\".\"d_quarter_name\" AS \"d_quarter_name\",\n    \"date_dim\".\"d_holiday\" AS \"d_holiday\",\n    \"date_dim\".\"d_weekend\" AS \"d_weekend\",\n    \"date_dim\".\"d_following_holiday\" AS \"d_following_holiday\",\n    \"date_dim\".\"d_first_dom\" AS \"d_first_dom\",\n    \"date_dim\".\"d_last_dom\" AS \"d_last_dom\",\n    \"date_dim\".\"d_same_day_ly\" AS \"d_same_day_ly\",\n    \"date_dim\".\"d_same_day_lq\" AS \"d_same_day_lq\",\n    \"date_dim\".\"d_current_day\" AS \"d_current_day\",\n    \"date_dim\".\"d_current_week\" AS \"d_current_week\",\n    \"date_dim\".\"d_current_month\" AS \"d_current_month\",\n    \"date_dim\".\"d_current_quarter\" AS \"d_current_quarter\",\n    \"date_dim\".\"d_current_year\" AS \"d_current_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" <= 5 AND \"date_dim\".\"d_moy\" >= 3 AND \"date_dim\".\"d_year\" = 2004\n), \"_u_0\" AS (\n  SELECT\n    \"store_sales\".\"ss_customer_sk\" AS \"_u_1\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_customer_sk\"\n), \"_u_2\" AS (\n  SELECT\n    \"web_sales\".\"ws_bill_customer_sk\" AS \"_u_3\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  GROUP BY\n    \"web_sales\".\"ws_bill_customer_sk\"\n), \"_u_4\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_ship_customer_sk\" AS \"_u_5\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  GROUP BY\n    \"catalog_sales\".\"cs_ship_customer_sk\"\n)\nSELECT\n  \"customer_demographics\".\"cd_gender\" AS \"cd_gender\",\n  \"customer_demographics\".\"cd_marital_status\" AS \"cd_marital_status\",\n  \"customer_demographics\".\"cd_education_status\" AS \"cd_education_status\",\n  COUNT(*) AS \"cnt1\",\n  \"customer_demographics\".\"cd_purchase_estimate\" AS \"cd_purchase_estimate\",\n  COUNT(*) AS \"cnt2\",\n  \"customer_demographics\".\"cd_credit_rating\" AS \"cd_credit_rating\",\n  COUNT(*) AS \"cnt3\"\nFROM \"customer\" AS \"c\"\nJOIN \"customer_address\" AS \"ca\"\n  ON \"c\".\"c_current_addr_sk\" = \"ca\".\"ca_address_sk\"\n  AND \"ca\".\"ca_state\" IN ('KS', 'AZ', 'NE')\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"c\".\"c_current_cdemo_sk\" = \"customer_demographics\".\"cd_demo_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"c\".\"c_customer_sk\"\nLEFT JOIN \"_u_2\" AS \"_u_2\"\n  ON \"_u_2\".\"_u_3\" = \"c\".\"c_customer_sk\"\nLEFT JOIN \"_u_4\" AS \"_u_4\"\n  ON \"_u_4\".\"_u_5\" = \"c\".\"c_customer_sk\"\nWHERE\n  \"_u_2\".\"_u_3\" IS NULL AND \"_u_4\".\"_u_5\" IS NULL AND NOT \"_u_0\".\"_u_1\" IS NULL\nGROUP BY\n  \"customer_demographics\".\"cd_gender\",\n  \"customer_demographics\".\"cd_marital_status\",\n  \"customer_demographics\".\"cd_education_status\",\n  \"customer_demographics\".\"cd_purchase_estimate\",\n  \"customer_demographics\".\"cd_credit_rating\"\nORDER BY\n  \"cd_gender\",\n  \"cd_marital_status\",\n  \"cd_education_status\",\n  \"cd_purchase_estimate\",\n  \"cd_credit_rating\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 70\n--------------------------------------\nSELECT Sum(ss_net_profit)                     AS total_sum,\n               s_state,\n               s_county,\n               Grouping(s_state) + Grouping(s_county) AS lochierarchy,\n               Rank()\n                 OVER (\n                   partition BY Grouping(s_state)+Grouping(s_county), CASE WHEN\n                 Grouping(\n                 s_county) = 0 THEN s_state END\n                   ORDER BY Sum(ss_net_profit) DESC)  AS rank_within_parent\nFROM   store_sales,\n       date_dim d1,\n       store\nWHERE  d1.d_month_seq BETWEEN 1200 AND 1200 + 11\n       AND d1.d_date_sk = ss_sold_date_sk\n       AND s_store_sk = ss_store_sk\n       AND s_state IN (SELECT s_state\n                       FROM   (SELECT s_state                               AS\n                                      s_state,\n                                      Rank()\n                                        OVER (\n                                          partition BY s_state\n                                          ORDER BY Sum(ss_net_profit) DESC) AS\n                                      ranking\n                               FROM   store_sales,\n                                      store,\n                                      date_dim\n                               WHERE  d_month_seq BETWEEN 1200 AND 1200 + 11\n                                      AND d_date_sk = ss_sold_date_sk\n                                      AND s_store_sk = ss_store_sk\n                               GROUP  BY s_state) tmp1\n                       WHERE  ranking <= 5)\nGROUP  BY rollup( s_state, s_county )\nORDER  BY lochierarchy DESC,\n          CASE\n            WHEN lochierarchy = 0 THEN s_state\n          END,\n          rank_within_parent\nLIMIT 100;\nWITH \"store_sales_2\" AS (\n  SELECT\n    \"store_sales\".\"ss_sold_date_sk\" AS \"ss_sold_date_sk\",\n    \"store_sales\".\"ss_store_sk\" AS \"ss_store_sk\",\n    \"store_sales\".\"ss_net_profit\" AS \"ss_net_profit\"\n  FROM \"store_sales\" AS \"store_sales\"\n), \"tmp1\" AS (\n  SELECT\n    \"store\".\"s_state\" AS \"s_state\",\n    RANK() OVER (PARTITION BY \"store\".\"s_state\" ORDER BY SUM(\"store_sales\".\"ss_net_profit\") DESC) AS \"ranking\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_month_seq\" <= 1211\n    AND \"date_dim\".\"d_month_seq\" >= 1200\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    \"store\".\"s_state\"\n), \"_u_0\" AS (\n  SELECT\n    \"tmp1\".\"s_state\" AS \"s_state\"\n  FROM \"tmp1\" AS \"tmp1\"\n  WHERE\n    \"tmp1\".\"ranking\" <= 5\n  GROUP BY\n    \"tmp1\".\"s_state\"\n)\nSELECT\n  SUM(\"store_sales\".\"ss_net_profit\") AS \"total_sum\",\n  \"store\".\"s_state\" AS \"s_state\",\n  \"store\".\"s_county\" AS \"s_county\",\n  GROUPING(\"store\".\"s_state\") + GROUPING(\"store\".\"s_county\") AS \"lochierarchy\",\n  RANK() OVER (\n    PARTITION BY GROUPING(\"store\".\"s_state\") + GROUPING(\"store\".\"s_county\"), CASE WHEN GROUPING(\"store\".\"s_county\") = 0 THEN \"store\".\"s_state\" END\n    ORDER BY SUM(\"store_sales\".\"ss_net_profit\") DESC\n  ) AS \"rank_within_parent\"\nFROM \"store_sales_2\" AS \"store_sales\"\nJOIN \"date_dim\" AS \"d1\"\n  ON \"d1\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND \"d1\".\"d_month_seq\" <= 1211\n  AND \"d1\".\"d_month_seq\" >= 1200\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"s_state\" = \"store\".\"s_state\"\nWHERE\n  NOT \"_u_0\".\"s_state\" IS NULL\nGROUP BY\n  ROLLUP (\n    \"store\".\"s_state\",\n    \"store\".\"s_county\"\n  )\nORDER BY\n  \"lochierarchy\" DESC,\n  CASE WHEN \"lochierarchy\" = 0 THEN \"s_state\" END,\n  \"rank_within_parent\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 71\n--------------------------------------\n# execute: true\nSELECT i_brand_id     brand_id,\n       i_brand        brand,\n       t_hour,\n       t_minute,\n       Sum(ext_price) ext_price\nFROM   item,\n       (SELECT ws_ext_sales_price AS ext_price,\n               ws_sold_date_sk    AS sold_date_sk,\n               ws_item_sk         AS sold_item_sk,\n               ws_sold_time_sk    AS time_sk\n        FROM   web_sales,\n               date_dim\n        WHERE  d_date_sk = ws_sold_date_sk\n               AND d_moy = 11\n               AND d_year = 2001\n        UNION ALL\n        SELECT cs_ext_sales_price AS ext_price,\n               cs_sold_date_sk    AS sold_date_sk,\n               cs_item_sk         AS sold_item_sk,\n               cs_sold_time_sk    AS time_sk\n        FROM   catalog_sales,\n               date_dim\n        WHERE  d_date_sk = cs_sold_date_sk\n               AND d_moy = 11\n               AND d_year = 2001\n        UNION ALL\n        SELECT ss_ext_sales_price AS ext_price,\n               ss_sold_date_sk    AS sold_date_sk,\n               ss_item_sk         AS sold_item_sk,\n               ss_sold_time_sk    AS time_sk\n        FROM   store_sales,\n               date_dim\n        WHERE  d_date_sk = ss_sold_date_sk\n               AND d_moy = 11\n               AND d_year = 2001) AS tmp,\n       time_dim\nWHERE  sold_item_sk = i_item_sk\n       AND i_manager_id = 1\n       AND time_sk = t_time_sk\n       AND ( t_meal_time = 'breakfast'\n              OR t_meal_time = 'dinner' )\nGROUP  BY i_brand,\n          i_brand_id,\n          t_hour,\n          t_minute\nORDER  BY ext_price DESC,\n          i_brand_id;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_moy\" = 11 AND \"date_dim\".\"d_year\" = 2001\n), \"tmp\" AS (\n  SELECT\n    \"web_sales\".\"ws_ext_sales_price\" AS \"ext_price\",\n    \"web_sales\".\"ws_item_sk\" AS \"sold_item_sk\",\n    \"web_sales\".\"ws_sold_time_sk\" AS \"time_sk\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  UNION ALL\n  SELECT\n    \"catalog_sales\".\"cs_ext_sales_price\" AS \"ext_price\",\n    \"catalog_sales\".\"cs_item_sk\" AS \"sold_item_sk\",\n    \"catalog_sales\".\"cs_sold_time_sk\" AS \"time_sk\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  UNION ALL\n  SELECT\n    \"store_sales\".\"ss_ext_sales_price\" AS \"ext_price\",\n    \"store_sales\".\"ss_item_sk\" AS \"sold_item_sk\",\n    \"store_sales\".\"ss_sold_time_sk\" AS \"time_sk\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n)\nSELECT\n  \"item\".\"i_brand_id\" AS \"brand_id\",\n  \"item\".\"i_brand\" AS \"brand\",\n  \"time_dim\".\"t_hour\" AS \"t_hour\",\n  \"time_dim\".\"t_minute\" AS \"t_minute\",\n  SUM(\"tmp\".\"ext_price\") AS \"ext_price\"\nFROM \"item\" AS \"item\"\nJOIN \"tmp\" AS \"tmp\"\n  ON \"item\".\"i_item_sk\" = \"tmp\".\"sold_item_sk\"\nJOIN \"time_dim\" AS \"time_dim\"\n  ON (\n    \"time_dim\".\"t_meal_time\" = 'breakfast' OR \"time_dim\".\"t_meal_time\" = 'dinner'\n  )\n  AND \"time_dim\".\"t_time_sk\" = \"tmp\".\"time_sk\"\nWHERE\n  \"item\".\"i_manager_id\" = 1\nGROUP BY\n  \"item\".\"i_brand\",\n  \"item\".\"i_brand_id\",\n  \"time_dim\".\"t_hour\",\n  \"time_dim\".\"t_minute\"\nORDER BY\n  \"ext_price\" DESC,\n  \"brand_id\";\n\n--------------------------------------\n-- TPC-DS 72\n--------------------------------------\nSELECT i_item_desc,\n               w_warehouse_name,\n               d1.d_week_seq,\n               Sum(CASE\n                     WHEN p_promo_sk IS NULL THEN 1\n                     ELSE 0\n                   END) no_promo,\n               Sum(CASE\n                     WHEN p_promo_sk IS NOT NULL THEN 1\n                     ELSE 0\n                   END) promo,\n               Count(*) total_cnt\nFROM   catalog_sales\n       JOIN inventory\n         ON ( cs_item_sk = inv_item_sk )\n       JOIN warehouse\n         ON ( w_warehouse_sk = inv_warehouse_sk )\n       JOIN item\n         ON ( i_item_sk = cs_item_sk )\n       JOIN customer_demographics\n         ON ( cs_bill_cdemo_sk = cd_demo_sk )\n       JOIN household_demographics\n         ON ( cs_bill_hdemo_sk = hd_demo_sk )\n       JOIN date_dim d1\n         ON ( cs_sold_date_sk = d1.d_date_sk )\n       JOIN date_dim d2\n         ON ( inv_date_sk = d2.d_date_sk )\n       JOIN date_dim d3\n         ON ( cs_ship_date_sk = d3.d_date_sk )\n       LEFT OUTER JOIN promotion\n                    ON ( cs_promo_sk = p_promo_sk )\n       LEFT OUTER JOIN catalog_returns\n                    ON ( cr_item_sk = cs_item_sk\n                         AND cr_order_number = cs_order_number )\nWHERE  d1.d_week_seq = d2.d_week_seq\n       AND inv_quantity_on_hand < cs_quantity\n       AND d3.d_date > d1.d_date + INTERVAL '5' day\n       AND hd_buy_potential = '501-1000'\n       AND d1.d_year = 2002\n       AND cd_marital_status = 'M'\nGROUP  BY i_item_desc,\n          w_warehouse_name,\n          d1.d_week_seq\nORDER  BY total_cnt DESC,\n          i_item_desc,\n          w_warehouse_name,\n          d_week_seq\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"warehouse\".\"w_warehouse_name\" AS \"w_warehouse_name\",\n  \"d1\".\"d_week_seq\" AS \"d_week_seq\",\n  SUM(CASE WHEN \"promotion\".\"p_promo_sk\" IS NULL THEN 1 ELSE 0 END) AS \"no_promo\",\n  SUM(CASE WHEN NOT \"promotion\".\"p_promo_sk\" IS NULL THEN 1 ELSE 0 END) AS \"promo\",\n  COUNT(*) AS \"total_cnt\"\nFROM \"catalog_sales\" AS \"catalog_sales\"\nJOIN \"inventory\" AS \"inventory\"\n  ON \"catalog_sales\".\"cs_item_sk\" = \"inventory\".\"inv_item_sk\"\n  AND \"catalog_sales\".\"cs_quantity\" > \"inventory\".\"inv_quantity_on_hand\"\nJOIN \"warehouse\" AS \"warehouse\"\n  ON \"inventory\".\"inv_warehouse_sk\" = \"warehouse\".\"w_warehouse_sk\"\nJOIN \"item\" AS \"item\"\n  ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"catalog_sales\".\"cs_bill_cdemo_sk\" = \"customer_demographics\".\"cd_demo_sk\"\n  AND \"customer_demographics\".\"cd_marital_status\" = 'M'\nJOIN \"household_demographics\" AS \"household_demographics\"\n  ON \"catalog_sales\".\"cs_bill_hdemo_sk\" = \"household_demographics\".\"hd_demo_sk\"\n  AND \"household_demographics\".\"hd_buy_potential\" = '501-1000'\nJOIN \"date_dim\" AS \"d1\"\n  ON \"catalog_sales\".\"cs_sold_date_sk\" = \"d1\".\"d_date_sk\" AND \"d1\".\"d_year\" = 2002\nJOIN \"date_dim\" AS \"d2\"\n  ON \"d1\".\"d_week_seq\" = \"d2\".\"d_week_seq\"\n  AND \"d2\".\"d_date_sk\" = \"inventory\".\"inv_date_sk\"\nJOIN \"date_dim\" AS \"d3\"\n  ON \"catalog_sales\".\"cs_ship_date_sk\" = \"d3\".\"d_date_sk\"\n  AND \"d3\".\"d_date\" > \"d1\".\"d_date\" + INTERVAL '5' DAY\nLEFT JOIN \"promotion\" AS \"promotion\"\n  ON \"catalog_sales\".\"cs_promo_sk\" = \"promotion\".\"p_promo_sk\"\nLEFT JOIN \"catalog_returns\" AS \"catalog_returns\"\n  ON \"catalog_returns\".\"cr_item_sk\" = \"catalog_sales\".\"cs_item_sk\"\n  AND \"catalog_returns\".\"cr_order_number\" = \"catalog_sales\".\"cs_order_number\"\nGROUP BY\n  \"item\".\"i_item_desc\",\n  \"warehouse\".\"w_warehouse_name\",\n  \"d1\".\"d_week_seq\"\nORDER BY\n  \"total_cnt\" DESC,\n  \"i_item_desc\",\n  \"w_warehouse_name\",\n  \"d_week_seq\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 73\n--------------------------------------\n# execute: true\nSELECT c_last_name,\n       c_first_name,\n       c_salutation,\n       c_preferred_cust_flag,\n       ss_ticket_number,\n       cnt\nFROM   (SELECT ss_ticket_number,\n               ss_customer_sk,\n               Count(*) cnt\n        FROM   store_sales,\n               date_dim,\n               store,\n               household_demographics\n        WHERE  store_sales.ss_sold_date_sk = date_dim.d_date_sk\n               AND store_sales.ss_store_sk = store.s_store_sk\n               AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk\n               AND date_dim.d_dom BETWEEN 1 AND 2\n               AND ( household_demographics.hd_buy_potential = '>10000'\n                      OR household_demographics.hd_buy_potential = '0-500' )\n               AND household_demographics.hd_vehicle_count > 0\n               AND CASE\n                     WHEN household_demographics.hd_vehicle_count > 0 THEN\n                     household_demographics.hd_dep_count /\n                     household_demographics.hd_vehicle_count\n                     ELSE NULL\n                   END > 1\n               AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 )\n               AND store.s_county IN ( 'Williamson County', 'Williamson County',\n                                       'Williamson County',\n                                                             'Williamson County'\n                                     )\n        GROUP  BY ss_ticket_number,\n                  ss_customer_sk) dj,\n       customer\nWHERE  ss_customer_sk = c_customer_sk\n       AND cnt BETWEEN 1 AND 5\nORDER  BY cnt DESC,\n          c_last_name ASC;\nWITH \"dj\" AS (\n  SELECT\n    \"store_sales\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\" AS \"ss_customer_sk\",\n    COUNT(*) AS \"cnt\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_dom\" <= 2\n    AND \"date_dim\".\"d_dom\" >= 1\n    AND \"date_dim\".\"d_year\" IN (2000, 2001, 2002)\n  JOIN \"household_demographics\" AS \"household_demographics\"\n    ON (\n      \"household_demographics\".\"hd_buy_potential\" = '0-500'\n      OR \"household_demographics\".\"hd_buy_potential\" = '>10000'\n    )\n    AND \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n    AND \"household_demographics\".\"hd_vehicle_count\" > 0\n    AND CASE\n      WHEN \"household_demographics\".\"hd_vehicle_count\" > 0\n      THEN \"household_demographics\".\"hd_dep_count\" / \"household_demographics\".\"hd_vehicle_count\"\n      ELSE NULL\n    END > 1\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_county\" IN (\n      'Williamson County',\n      'Williamson County',\n      'Williamson County',\n      'Williamson County'\n    )\n    AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\"\n)\nSELECT\n  \"customer\".\"c_last_name\" AS \"c_last_name\",\n  \"customer\".\"c_first_name\" AS \"c_first_name\",\n  \"customer\".\"c_salutation\" AS \"c_salutation\",\n  \"customer\".\"c_preferred_cust_flag\" AS \"c_preferred_cust_flag\",\n  \"dj\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n  \"dj\".\"cnt\" AS \"cnt\"\nFROM \"dj\" AS \"dj\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_customer_sk\" = \"dj\".\"ss_customer_sk\"\nWHERE\n  \"dj\".\"cnt\" <= 5 AND \"dj\".\"cnt\" >= 1\nORDER BY\n  \"cnt\" DESC,\n  \"c_last_name\";\n\n--------------------------------------\n-- TPC-DS 74\n--------------------------------------\n# execute: true\nWITH year_total\n     AS (SELECT c_customer_id    customer_id,\n                c_first_name     customer_first_name,\n                c_last_name      customer_last_name,\n                d_year           AS year1,\n                Sum(ss_net_paid) year_total,\n                's'              sale_type\n         FROM   customer,\n                store_sales,\n                date_dim\n         WHERE  c_customer_sk = ss_customer_sk\n                AND ss_sold_date_sk = d_date_sk\n                AND d_year IN ( 1999, 1999 + 1 )\n         GROUP  BY c_customer_id,\n                   c_first_name,\n                   c_last_name,\n                   d_year\n         UNION ALL\n         SELECT c_customer_id    customer_id,\n                c_first_name     customer_first_name,\n                c_last_name      customer_last_name,\n                d_year           AS year1,\n                Sum(ws_net_paid) year_total,\n                'w'              sale_type\n         FROM   customer,\n                web_sales,\n                date_dim\n         WHERE  c_customer_sk = ws_bill_customer_sk\n                AND ws_sold_date_sk = d_date_sk\n                AND d_year IN ( 1999, 1999 + 1 )\n         GROUP  BY c_customer_id,\n                   c_first_name,\n                   c_last_name,\n                   d_year)\nSELECT t_s_secyear.customer_id,\n               t_s_secyear.customer_first_name,\n               t_s_secyear.customer_last_name\nFROM   year_total t_s_firstyear,\n       year_total t_s_secyear,\n       year_total t_w_firstyear,\n       year_total t_w_secyear\nWHERE  t_s_secyear.customer_id = t_s_firstyear.customer_id\n       AND t_s_firstyear.customer_id = t_w_secyear.customer_id\n       AND t_s_firstyear.customer_id = t_w_firstyear.customer_id\n       AND t_s_firstyear.sale_type = 's'\n       AND t_w_firstyear.sale_type = 'w'\n       AND t_s_secyear.sale_type = 's'\n       AND t_w_secyear.sale_type = 'w'\n       AND t_s_firstyear.year1 = 1999\n       AND t_s_secyear.year1 = 1999 + 1\n       AND t_w_firstyear.year1 = 1999\n       AND t_w_secyear.year1 = 1999 + 1\n       AND t_s_firstyear.year_total > 0\n       AND t_w_firstyear.year_total > 0\n       AND CASE\n             WHEN t_w_firstyear.year_total > 0 THEN t_w_secyear.year_total /\n                                                    t_w_firstyear.year_total\n             ELSE NULL\n           END > CASE\n                   WHEN t_s_firstyear.year_total > 0 THEN\n                   t_s_secyear.year_total /\n                   t_s_firstyear.year_total\n                   ELSE NULL\n                 END\nORDER  BY 1,\n          2,\n          3\nLIMIT 100;\nWITH \"customer_2\" AS (\n  SELECT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\",\n    \"customer\".\"c_customer_id\" AS \"c_customer_id\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"customer\".\"c_last_name\" AS \"c_last_name\"\n  FROM \"customer\" AS \"customer\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_year\" IN (1999, 2000)\n), \"year_total\" AS (\n  SELECT\n    \"customer\".\"c_customer_id\" AS \"customer_id\",\n    \"customer\".\"c_first_name\" AS \"customer_first_name\",\n    \"customer\".\"c_last_name\" AS \"customer_last_name\",\n    \"date_dim\".\"d_year\" AS \"year1\",\n    SUM(\"store_sales\".\"ss_net_paid\") AS \"year_total\",\n    's' AS \"sale_type\"\n  FROM \"customer_2\" AS \"customer\"\n  JOIN \"store_sales\" AS \"store_sales\"\n    ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"customer\".\"c_customer_id\",\n    \"customer\".\"c_first_name\",\n    \"customer\".\"c_last_name\",\n    \"date_dim\".\"d_year\"\n  UNION ALL\n  SELECT\n    \"customer\".\"c_customer_id\" AS \"customer_id\",\n    \"customer\".\"c_first_name\" AS \"customer_first_name\",\n    \"customer\".\"c_last_name\" AS \"customer_last_name\",\n    \"date_dim\".\"d_year\" AS \"year1\",\n    SUM(\"web_sales\".\"ws_net_paid\") AS \"year_total\",\n    'w' AS \"sale_type\"\n  FROM \"customer_2\" AS \"customer\"\n  JOIN \"web_sales\" AS \"web_sales\"\n    ON \"customer\".\"c_customer_sk\" = \"web_sales\".\"ws_bill_customer_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  GROUP BY\n    \"customer\".\"c_customer_id\",\n    \"customer\".\"c_first_name\",\n    \"customer\".\"c_last_name\",\n    \"date_dim\".\"d_year\"\n)\nSELECT\n  \"t_s_secyear\".\"customer_id\" AS \"customer_id\",\n  \"t_s_secyear\".\"customer_first_name\" AS \"customer_first_name\",\n  \"t_s_secyear\".\"customer_last_name\" AS \"customer_last_name\"\nFROM \"year_total\" AS \"t_s_firstyear\"\nJOIN \"year_total\" AS \"t_s_secyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_s_secyear\".\"customer_id\"\n  AND \"t_s_secyear\".\"sale_type\" = 's'\n  AND \"t_s_secyear\".\"year1\" = 2000\nJOIN \"year_total\" AS \"t_w_firstyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_w_firstyear\".\"customer_id\"\n  AND \"t_w_firstyear\".\"sale_type\" = 'w'\n  AND \"t_w_firstyear\".\"year1\" = 1999\n  AND \"t_w_firstyear\".\"year_total\" > 0\nJOIN \"year_total\" AS \"t_w_secyear\"\n  ON \"t_s_firstyear\".\"customer_id\" = \"t_w_secyear\".\"customer_id\"\n  AND \"t_w_secyear\".\"sale_type\" = 'w'\n  AND \"t_w_secyear\".\"year1\" = 2000\n  AND CASE\n    WHEN \"t_s_firstyear\".\"year_total\" > 0\n    THEN \"t_s_secyear\".\"year_total\" / \"t_s_firstyear\".\"year_total\"\n    ELSE NULL\n  END < CASE\n    WHEN \"t_w_firstyear\".\"year_total\" > 0\n    THEN \"t_w_secyear\".\"year_total\" / \"t_w_firstyear\".\"year_total\"\n    ELSE NULL\n  END\nWHERE\n  \"t_s_firstyear\".\"sale_type\" = 's'\n  AND \"t_s_firstyear\".\"year1\" = 1999\n  AND \"t_s_firstyear\".\"year_total\" > 0\nORDER BY\n  \"customer_id\",\n  \"customer_first_name\",\n  \"customer_last_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 75\n--------------------------------------\n# execute: true\nWITH all_sales\n     AS (SELECT d_year,\n                i_brand_id,\n                i_class_id,\n                i_category_id,\n                i_manufact_id,\n                Sum(sales_cnt) AS sales_cnt,\n                Sum(sales_amt) AS sales_amt\n         FROM   (SELECT d_year,\n                        i_brand_id,\n                        i_class_id,\n                        i_category_id,\n                        i_manufact_id,\n                        cs_quantity - COALESCE(cr_return_quantity, 0)        AS\n                        sales_cnt,\n                        cs_ext_sales_price - COALESCE(cr_return_amount, 0.0) AS\n                        sales_amt\n                 FROM   catalog_sales\n                        JOIN item\n                          ON i_item_sk = cs_item_sk\n                        JOIN date_dim\n                          ON d_date_sk = cs_sold_date_sk\n                        LEFT JOIN catalog_returns\n                               ON ( cs_order_number = cr_order_number\n                                    AND cs_item_sk = cr_item_sk )\n                 WHERE  i_category = 'Men'\n                 UNION\n                 SELECT d_year,\n                        i_brand_id,\n                        i_class_id,\n                        i_category_id,\n                        i_manufact_id,\n                        ss_quantity - COALESCE(sr_return_quantity, 0)     AS\n                        sales_cnt,\n                        ss_ext_sales_price - COALESCE(sr_return_amt, 0.0) AS\n                        sales_amt\n                 FROM   store_sales\n                        JOIN item\n                          ON i_item_sk = ss_item_sk\n                        JOIN date_dim\n                          ON d_date_sk = ss_sold_date_sk\n                        LEFT JOIN store_returns\n                               ON ( ss_ticket_number = sr_ticket_number\n                                    AND ss_item_sk = sr_item_sk )\n                 WHERE  i_category = 'Men'\n                 UNION\n                 SELECT d_year,\n                        i_brand_id,\n                        i_class_id,\n                        i_category_id,\n                        i_manufact_id,\n                        ws_quantity - COALESCE(wr_return_quantity, 0)     AS\n                        sales_cnt,\n                        ws_ext_sales_price - COALESCE(wr_return_amt, 0.0) AS\n                        sales_amt\n                 FROM   web_sales\n                        JOIN item\n                          ON i_item_sk = ws_item_sk\n                        JOIN date_dim\n                          ON d_date_sk = ws_sold_date_sk\n                        LEFT JOIN web_returns\n                               ON ( ws_order_number = wr_order_number\n                                    AND ws_item_sk = wr_item_sk )\n                 WHERE  i_category = 'Men') sales_detail\n         GROUP  BY d_year,\n                   i_brand_id,\n                   i_class_id,\n                   i_category_id,\n                   i_manufact_id)\nSELECT prev_yr.d_year                        AS prev_year,\n               curr_yr.d_year                        AS year1,\n               curr_yr.i_brand_id,\n               curr_yr.i_class_id,\n               curr_yr.i_category_id,\n               curr_yr.i_manufact_id,\n               prev_yr.sales_cnt                     AS prev_yr_cnt,\n               curr_yr.sales_cnt                     AS curr_yr_cnt,\n               curr_yr.sales_cnt - prev_yr.sales_cnt AS sales_cnt_diff,\n               curr_yr.sales_amt - prev_yr.sales_amt AS sales_amt_diff\nFROM   all_sales curr_yr,\n       all_sales prev_yr\nWHERE  curr_yr.i_brand_id = prev_yr.i_brand_id\n       AND curr_yr.i_class_id = prev_yr.i_class_id\n       AND curr_yr.i_category_id = prev_yr.i_category_id\n       AND curr_yr.i_manufact_id = prev_yr.i_manufact_id\n       AND curr_yr.d_year = 2002\n       AND prev_yr.d_year = 2002 - 1\n       AND Cast(curr_yr.sales_cnt AS DECIMAL(17, 2)) / Cast(prev_yr.sales_cnt AS\n                                                                DECIMAL(17, 2))\n           < 0.9\nORDER  BY sales_cnt_diff\nLIMIT 100;\nWITH \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_brand_id\" AS \"i_brand_id\",\n    \"item\".\"i_class_id\" AS \"i_class_id\",\n    \"item\".\"i_category_id\" AS \"i_category_id\",\n    \"item\".\"i_category\" AS \"i_category\",\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\"\n  FROM \"item\" AS \"item\"\n  WHERE\n    \"item\".\"i_category\" = 'Men'\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n), \"sales_detail\" AS (\n  SELECT\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"item\".\"i_brand_id\" AS \"i_brand_id\",\n    \"item\".\"i_class_id\" AS \"i_class_id\",\n    \"item\".\"i_category_id\" AS \"i_category_id\",\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\",\n    \"catalog_sales\".\"cs_quantity\" - COALESCE(\"catalog_returns\".\"cr_return_quantity\", 0) AS \"sales_cnt\",\n    \"catalog_sales\".\"cs_ext_sales_price\" - COALESCE(\"catalog_returns\".\"cr_return_amount\", 0.0) AS \"sales_amt\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  LEFT JOIN \"catalog_returns\" AS \"catalog_returns\"\n    ON \"catalog_returns\".\"cr_item_sk\" = \"catalog_sales\".\"cs_item_sk\"\n    AND \"catalog_returns\".\"cr_order_number\" = \"catalog_sales\".\"cs_order_number\"\n  UNION\n  SELECT\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"item\".\"i_brand_id\" AS \"i_brand_id\",\n    \"item\".\"i_class_id\" AS \"i_class_id\",\n    \"item\".\"i_category_id\" AS \"i_category_id\",\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\",\n    \"store_sales\".\"ss_quantity\" - COALESCE(\"store_returns\".\"sr_return_quantity\", 0) AS \"sales_cnt\",\n    \"store_sales\".\"ss_ext_sales_price\" - COALESCE(\"store_returns\".\"sr_return_amt\", 0.0) AS \"sales_amt\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  LEFT JOIN \"store_returns\" AS \"store_returns\"\n    ON \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n    AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\n  UNION\n  SELECT\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"item\".\"i_brand_id\" AS \"i_brand_id\",\n    \"item\".\"i_class_id\" AS \"i_class_id\",\n    \"item\".\"i_category_id\" AS \"i_category_id\",\n    \"item\".\"i_manufact_id\" AS \"i_manufact_id\",\n    \"web_sales\".\"ws_quantity\" - COALESCE(\"web_returns\".\"wr_return_quantity\", 0) AS \"sales_cnt\",\n    \"web_sales\".\"ws_ext_sales_price\" - COALESCE(\"web_returns\".\"wr_return_amt\", 0.0) AS \"sales_amt\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  LEFT JOIN \"web_returns\" AS \"web_returns\"\n    ON \"web_returns\".\"wr_item_sk\" = \"web_sales\".\"ws_item_sk\"\n    AND \"web_returns\".\"wr_order_number\" = \"web_sales\".\"ws_order_number\"\n), \"all_sales\" AS (\n  SELECT\n    \"sales_detail\".\"d_year\" AS \"d_year\",\n    \"sales_detail\".\"i_brand_id\" AS \"i_brand_id\",\n    \"sales_detail\".\"i_class_id\" AS \"i_class_id\",\n    \"sales_detail\".\"i_category_id\" AS \"i_category_id\",\n    \"sales_detail\".\"i_manufact_id\" AS \"i_manufact_id\",\n    SUM(\"sales_detail\".\"sales_cnt\") AS \"sales_cnt\",\n    SUM(\"sales_detail\".\"sales_amt\") AS \"sales_amt\"\n  FROM \"sales_detail\" AS \"sales_detail\"\n  GROUP BY\n    \"sales_detail\".\"d_year\",\n    \"sales_detail\".\"i_brand_id\",\n    \"sales_detail\".\"i_class_id\",\n    \"sales_detail\".\"i_category_id\",\n    \"sales_detail\".\"i_manufact_id\"\n)\nSELECT\n  \"prev_yr\".\"d_year\" AS \"prev_year\",\n  \"curr_yr\".\"d_year\" AS \"year1\",\n  \"curr_yr\".\"i_brand_id\" AS \"i_brand_id\",\n  \"curr_yr\".\"i_class_id\" AS \"i_class_id\",\n  \"curr_yr\".\"i_category_id\" AS \"i_category_id\",\n  \"curr_yr\".\"i_manufact_id\" AS \"i_manufact_id\",\n  \"prev_yr\".\"sales_cnt\" AS \"prev_yr_cnt\",\n  \"curr_yr\".\"sales_cnt\" AS \"curr_yr_cnt\",\n  \"curr_yr\".\"sales_cnt\" - \"prev_yr\".\"sales_cnt\" AS \"sales_cnt_diff\",\n  \"curr_yr\".\"sales_amt\" - \"prev_yr\".\"sales_amt\" AS \"sales_amt_diff\"\nFROM \"all_sales\" AS \"curr_yr\"\nJOIN \"all_sales\" AS \"prev_yr\"\n  ON \"curr_yr\".\"i_brand_id\" = \"prev_yr\".\"i_brand_id\"\n  AND \"curr_yr\".\"i_category_id\" = \"prev_yr\".\"i_category_id\"\n  AND \"curr_yr\".\"i_class_id\" = \"prev_yr\".\"i_class_id\"\n  AND \"curr_yr\".\"i_manufact_id\" = \"prev_yr\".\"i_manufact_id\"\n  AND \"prev_yr\".\"d_year\" = 2001\n  AND CAST(\"curr_yr\".\"sales_cnt\" AS DECIMAL(17, 2)) / CAST(\"prev_yr\".\"sales_cnt\" AS DECIMAL(17, 2)) < 0.9\nWHERE\n  \"curr_yr\".\"d_year\" = 2002\nORDER BY\n  \"sales_cnt_diff\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 76\n--------------------------------------\n# execute: true\nSELECT channel,\n               col_name,\n               d_year,\n               d_qoy,\n               i_category,\n               Count(*)             sales_cnt,\n               Sum(ext_sales_price) sales_amt\nFROM   (SELECT 'store'            AS channel,\n               'ss_hdemo_sk'      col_name,\n               d_year,\n               d_qoy,\n               i_category,\n               ss_ext_sales_price ext_sales_price\n        FROM   store_sales,\n               item,\n               date_dim\n        WHERE  ss_hdemo_sk IS NULL\n               AND ss_sold_date_sk = d_date_sk\n               AND ss_item_sk = i_item_sk\n        UNION ALL\n        SELECT 'web'              AS channel,\n               'ws_ship_hdemo_sk' col_name,\n               d_year,\n               d_qoy,\n               i_category,\n               ws_ext_sales_price ext_sales_price\n        FROM   web_sales,\n               item,\n               date_dim\n        WHERE  ws_ship_hdemo_sk IS NULL\n               AND ws_sold_date_sk = d_date_sk\n               AND ws_item_sk = i_item_sk\n        UNION ALL\n        SELECT 'catalog'          AS channel,\n               'cs_warehouse_sk'  col_name,\n               d_year,\n               d_qoy,\n               i_category,\n               cs_ext_sales_price ext_sales_price\n        FROM   catalog_sales,\n               item,\n               date_dim\n        WHERE  cs_warehouse_sk IS NULL\n               AND cs_sold_date_sk = d_date_sk\n               AND cs_item_sk = i_item_sk) foo\nGROUP  BY channel,\n          col_name,\n          d_year,\n          d_qoy,\n          i_category\nORDER  BY channel,\n          col_name,\n          d_year,\n          d_qoy,\n          i_category\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\"\n  FROM \"date_dim\" AS \"date_dim\"\n), \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_category\" AS \"i_category\"\n  FROM \"item\" AS \"item\"\n), \"foo\" AS (\n  SELECT\n    'store' AS \"channel\",\n    'ss_hdemo_sk' AS \"col_name\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"item\".\"i_category\" AS \"i_category\",\n    \"store_sales\".\"ss_ext_sales_price\" AS \"ext_sales_price\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  WHERE\n    \"store_sales\".\"ss_hdemo_sk\" IS NULL\n  UNION ALL\n  SELECT\n    'web' AS \"channel\",\n    'ws_ship_hdemo_sk' AS \"col_name\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"item\".\"i_category\" AS \"i_category\",\n    \"web_sales\".\"ws_ext_sales_price\" AS \"ext_sales_price\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  WHERE\n    \"web_sales\".\"ws_ship_hdemo_sk\" IS NULL\n  UNION ALL\n  SELECT\n    'catalog' AS \"channel\",\n    'cs_warehouse_sk' AS \"col_name\",\n    \"date_dim\".\"d_year\" AS \"d_year\",\n    \"date_dim\".\"d_qoy\" AS \"d_qoy\",\n    \"item\".\"i_category\" AS \"i_category\",\n    \"catalog_sales\".\"cs_ext_sales_price\" AS \"ext_sales_price\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  WHERE\n    \"catalog_sales\".\"cs_warehouse_sk\" IS NULL\n)\nSELECT\n  \"foo\".\"channel\" AS \"channel\",\n  \"foo\".\"col_name\" AS \"col_name\",\n  \"foo\".\"d_year\" AS \"d_year\",\n  \"foo\".\"d_qoy\" AS \"d_qoy\",\n  \"foo\".\"i_category\" AS \"i_category\",\n  COUNT(*) AS \"sales_cnt\",\n  SUM(\"foo\".\"ext_sales_price\") AS \"sales_amt\"\nFROM \"foo\" AS \"foo\"\nGROUP BY\n  \"foo\".\"channel\",\n  \"foo\".\"col_name\",\n  \"foo\".\"d_year\",\n  \"foo\".\"d_qoy\",\n  \"foo\".\"i_category\"\nORDER BY\n  \"channel\",\n  \"col_name\",\n  \"d_year\",\n  \"d_qoy\",\n  \"i_category\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 77\n--------------------------------------\nWITH ss AS\n(\n         SELECT   s_store_sk,\n                  Sum(ss_ext_sales_price) AS sales,\n                  Sum(ss_net_profit)      AS profit\n         FROM     store_sales,\n                  date_dim,\n                  store\n         WHERE    ss_sold_date_sk = d_date_sk\n         AND      d_date BETWEEN Cast('2001-08-16' AS DATE) AND      (\n                           Cast('2001-08-16' AS DATE) + INTERVAL '30' day)\n         AND      ss_store_sk = s_store_sk\n         GROUP BY s_store_sk) , sr AS\n(\n         SELECT   s_store_sk,\n                  sum(sr_return_amt) AS returns1,\n                  sum(sr_net_loss)   AS profit_loss\n         FROM     store_returns,\n                  date_dim,\n                  store\n         WHERE    sr_returned_date_sk = d_date_sk\n         AND      d_date BETWEEN cast('2001-08-16' AS date) AND      (\n                           cast('2001-08-16' AS date) + INTERVAL '30' day)\n         AND      sr_store_sk = s_store_sk\n         GROUP BY s_store_sk), cs AS\n(\n         SELECT   cs_call_center_sk,\n                  sum(cs_ext_sales_price) AS sales,\n                  sum(cs_net_profit)      AS profit\n         FROM     catalog_sales,\n                  date_dim\n         WHERE    cs_sold_date_sk = d_date_sk\n         AND      d_date BETWEEN cast('2001-08-16' AS date) AND      (\n                           cast('2001-08-16' AS date) + INTERVAL '30' day)\n         GROUP BY cs_call_center_sk ), cr AS\n(\n         SELECT   cr_call_center_sk,\n                  sum(cr_return_amount) AS returns1,\n                  sum(cr_net_loss)      AS profit_loss\n         FROM     catalog_returns,\n                  date_dim\n         WHERE    cr_returned_date_sk = d_date_sk\n         AND      d_date BETWEEN cast('2001-08-16' AS date) AND      (\n                           cast('2001-08-16' AS date) + INTERVAL '30' day)\n         GROUP BY cr_call_center_sk ), ws AS\n(\n         SELECT   wp_web_page_sk,\n                  sum(ws_ext_sales_price) AS sales,\n                  sum(ws_net_profit)      AS profit\n         FROM     web_sales,\n                  date_dim,\n                  web_page\n         WHERE    ws_sold_date_sk = d_date_sk\n         AND      d_date BETWEEN cast('2001-08-16' AS date) AND      (\n                           cast('2001-08-16' AS date) + INTERVAL '30' day)\n         AND      ws_web_page_sk = wp_web_page_sk\n         GROUP BY wp_web_page_sk), wr AS\n(\n         SELECT   wp_web_page_sk,\n                  sum(wr_return_amt) AS returns1,\n                  sum(wr_net_loss)   AS profit_loss\n         FROM     web_returns,\n                  date_dim,\n                  web_page\n         WHERE    wr_returned_date_sk = d_date_sk\n         AND      d_date BETWEEN cast('2001-08-16' AS date) AND      (\n                           cast('2001-08-16' AS date) + INTERVAL '30' day)\n         AND      wr_web_page_sk = wp_web_page_sk\n         GROUP BY wp_web_page_sk)\nSELECT\n         channel ,\n         id ,\n         sum(sales)   AS sales ,\n         sum(returns1) AS returns1 ,\n         sum(profit)  AS profit\nFROM     (\n                   SELECT    'store channel' AS channel ,\n                             ss.s_store_sk   AS id ,\n                             sales ,\n                             COALESCE(returns1, 0)               AS returns1 ,\n                             (profit - COALESCE(profit_loss,0)) AS profit\n                   FROM      ss\n                   LEFT JOIN sr\n                   ON        ss.s_store_sk = sr.s_store_sk\n                   UNION ALL\n                   SELECT 'catalog channel' AS channel ,\n                          cs_call_center_sk AS id ,\n                          sales ,\n                          returns1 ,\n                          (profit - profit_loss) AS profit\n                   FROM   cs ,\n                          cr\n                   UNION ALL\n                   SELECT    'web channel'     AS channel ,\n                             ws.wp_web_page_sk AS id ,\n                             sales ,\n                             COALESCE(returns1, 0)                  returns1 ,\n                             (profit - COALESCE(profit_loss,0)) AS profit\n                   FROM      ws\n                   LEFT JOIN wr\n                   ON        ws.wp_web_page_sk = wr.wp_web_page_sk ) x\nGROUP BY rollup (channel, id)\nORDER BY channel ,\n         id\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2001-09-15' AS DATE)\n    AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2001-08-16' AS DATE)\n), \"store_2\" AS (\n  SELECT\n    \"store\".\"s_store_sk\" AS \"s_store_sk\"\n  FROM \"store\" AS \"store\"\n), \"ss\" AS (\n  SELECT\n    \"store\".\"s_store_sk\" AS \"s_store_sk\",\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"sales\",\n    SUM(\"store_sales\".\"ss_net_profit\") AS \"profit\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    \"store\".\"s_store_sk\"\n), \"sr\" AS (\n  SELECT\n    \"store\".\"s_store_sk\" AS \"s_store_sk\",\n    SUM(\"store_returns\".\"sr_return_amt\") AS \"returns1\",\n    SUM(\"store_returns\".\"sr_net_loss\") AS \"profit_loss\"\n  FROM \"store_returns\" AS \"store_returns\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_returns\".\"sr_returned_date_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_returns\".\"sr_store_sk\"\n  GROUP BY\n    \"store\".\"s_store_sk\"\n), \"cs\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_call_center_sk\" AS \"cs_call_center_sk\",\n    SUM(\"catalog_sales\".\"cs_ext_sales_price\") AS \"sales\",\n    SUM(\"catalog_sales\".\"cs_net_profit\") AS \"profit\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  GROUP BY\n    \"catalog_sales\".\"cs_call_center_sk\"\n), \"cr\" AS (\n  SELECT\n    SUM(\"catalog_returns\".\"cr_return_amount\") AS \"returns1\",\n    SUM(\"catalog_returns\".\"cr_net_loss\") AS \"profit_loss\"\n  FROM \"catalog_returns\" AS \"catalog_returns\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_returns\".\"cr_returned_date_sk\" = \"date_dim\".\"d_date_sk\"\n  GROUP BY\n    \"catalog_returns\".\"cr_call_center_sk\"\n), \"web_page_2\" AS (\n  SELECT\n    \"web_page\".\"wp_web_page_sk\" AS \"wp_web_page_sk\"\n  FROM \"web_page\" AS \"web_page\"\n), \"ws\" AS (\n  SELECT\n    \"web_page\".\"wp_web_page_sk\" AS \"wp_web_page_sk\",\n    SUM(\"web_sales\".\"ws_ext_sales_price\") AS \"sales\",\n    SUM(\"web_sales\".\"ws_net_profit\") AS \"profit\"\n  FROM \"web_sales\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  JOIN \"web_page_2\" AS \"web_page\"\n    ON \"web_page\".\"wp_web_page_sk\" = \"web_sales\".\"ws_web_page_sk\"\n  GROUP BY\n    \"web_page\".\"wp_web_page_sk\"\n), \"wr\" AS (\n  SELECT\n    \"web_page\".\"wp_web_page_sk\" AS \"wp_web_page_sk\",\n    SUM(\"web_returns\".\"wr_return_amt\") AS \"returns1\",\n    SUM(\"web_returns\".\"wr_net_loss\") AS \"profit_loss\"\n  FROM \"web_returns\" AS \"web_returns\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_returns\".\"wr_returned_date_sk\"\n  JOIN \"web_page_2\" AS \"web_page\"\n    ON \"web_page\".\"wp_web_page_sk\" = \"web_returns\".\"wr_web_page_sk\"\n  GROUP BY\n    \"web_page\".\"wp_web_page_sk\"\n), \"x\" AS (\n  SELECT\n    'store channel' AS \"channel\",\n    \"ss\".\"s_store_sk\" AS \"id\",\n    \"ss\".\"sales\" AS \"sales\",\n    COALESCE(\"sr\".\"returns1\", 0) AS \"returns1\",\n    \"ss\".\"profit\" - COALESCE(\"sr\".\"profit_loss\", 0) AS \"profit\"\n  FROM \"ss\" AS \"ss\"\n  LEFT JOIN \"sr\" AS \"sr\"\n    ON \"sr\".\"s_store_sk\" = \"ss\".\"s_store_sk\"\n  UNION ALL\n  SELECT\n    'catalog channel' AS \"channel\",\n    \"cs\".\"cs_call_center_sk\" AS \"id\",\n    \"cs\".\"sales\" AS \"sales\",\n    \"cr\".\"returns1\" AS \"returns1\",\n    \"cs\".\"profit\" - \"cr\".\"profit_loss\" AS \"profit\"\n  FROM \"cs\" AS \"cs\"\n  CROSS JOIN \"cr\" AS \"cr\"\n  UNION ALL\n  SELECT\n    'web channel' AS \"channel\",\n    \"ws\".\"wp_web_page_sk\" AS \"id\",\n    \"ws\".\"sales\" AS \"sales\",\n    COALESCE(\"wr\".\"returns1\", 0) AS \"returns1\",\n    \"ws\".\"profit\" - COALESCE(\"wr\".\"profit_loss\", 0) AS \"profit\"\n  FROM \"ws\" AS \"ws\"\n  LEFT JOIN \"wr\" AS \"wr\"\n    ON \"wr\".\"wp_web_page_sk\" = \"ws\".\"wp_web_page_sk\"\n)\nSELECT\n  \"x\".\"channel\" AS \"channel\",\n  \"x\".\"id\" AS \"id\",\n  SUM(\"x\".\"sales\") AS \"sales\",\n  SUM(\"x\".\"returns1\") AS \"returns1\",\n  SUM(\"x\".\"profit\") AS \"profit\"\nFROM \"x\" AS \"x\"\nGROUP BY\n  ROLLUP (\n    \"x\".\"channel\",\n    \"x\".\"id\"\n  )\nORDER BY\n  \"channel\",\n  \"id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 78\n--------------------------------------\n# execute: true\nWITH ws\n     AS (SELECT d_year                 AS ws_sold_year,\n                ws_item_sk,\n                ws_bill_customer_sk    ws_customer_sk,\n                Sum(ws_quantity)       ws_qty,\n                Sum(ws_wholesale_cost) ws_wc,\n                Sum(ws_sales_price)    ws_sp\n         FROM   web_sales\n                LEFT JOIN web_returns\n                       ON wr_order_number = ws_order_number\n                          AND ws_item_sk = wr_item_sk\n                JOIN date_dim\n                  ON ws_sold_date_sk = d_date_sk\n         WHERE  wr_order_number IS NULL\n         GROUP  BY d_year,\n                   ws_item_sk,\n                   ws_bill_customer_sk),\n     cs\n     AS (SELECT d_year                 AS cs_sold_year,\n                cs_item_sk,\n                cs_bill_customer_sk    cs_customer_sk,\n                Sum(cs_quantity)       cs_qty,\n                Sum(cs_wholesale_cost) cs_wc,\n                Sum(cs_sales_price)    cs_sp\n         FROM   catalog_sales\n                LEFT JOIN catalog_returns\n                       ON cr_order_number = cs_order_number\n                          AND cs_item_sk = cr_item_sk\n                JOIN date_dim\n                  ON cs_sold_date_sk = d_date_sk\n         WHERE  cr_order_number IS NULL\n         GROUP  BY d_year,\n                   cs_item_sk,\n                   cs_bill_customer_sk),\n     ss\n     AS (SELECT d_year                 AS ss_sold_year,\n                ss_item_sk,\n                ss_customer_sk,\n                Sum(ss_quantity)       ss_qty,\n                Sum(ss_wholesale_cost) ss_wc,\n                Sum(ss_sales_price)    ss_sp\n         FROM   store_sales\n                LEFT JOIN store_returns\n                       ON sr_ticket_number = ss_ticket_number\n                          AND ss_item_sk = sr_item_sk\n                JOIN date_dim\n                  ON ss_sold_date_sk = d_date_sk\n         WHERE  sr_ticket_number IS NULL\n         GROUP  BY d_year,\n                   ss_item_sk,\n                   ss_customer_sk)\nSELECT ss_item_sk,\n               Round(ss_qty / ( COALESCE(ws_qty + cs_qty, 1) ), 2) ratio,\n               ss_qty                                              store_qty,\n               ss_wc\n               store_wholesale_cost,\n               ss_sp\n               store_sales_price,\n               COALESCE(ws_qty, 0) + COALESCE(cs_qty, 0)\n               other_chan_qty,\n               COALESCE(ws_wc, 0) + COALESCE(cs_wc, 0)\n               other_chan_wholesale_cost,\n               COALESCE(ws_sp, 0) + COALESCE(cs_sp, 0)\n               other_chan_sales_price\nFROM   ss\n       LEFT JOIN ws\n              ON ( ws_sold_year = ss_sold_year\n                   AND ws_item_sk = ss_item_sk\n                   AND ws_customer_sk = ss_customer_sk )\n       LEFT JOIN cs\n              ON ( cs_sold_year = ss_sold_year\n                   AND cs_item_sk = cs_item_sk\n                   AND cs_customer_sk = ss_customer_sk )\nWHERE  COALESCE(ws_qty, 0) > 0\n       AND COALESCE(cs_qty, 0) > 0\n       AND ss_sold_year = 1999\nORDER  BY ss_item_sk,\n          ss_qty DESC,\n          ss_wc DESC,\n          ss_sp DESC,\n          other_chan_qty,\n          other_chan_wholesale_cost,\n          other_chan_sales_price,\n          Round(ss_qty / ( COALESCE(ws_qty + cs_qty, 1) ), 2)\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_year\" AS \"d_year\"\n  FROM \"date_dim\" AS \"date_dim\"\n), \"ws\" AS (\n  SELECT\n    \"date_dim\".\"d_year\" AS \"ws_sold_year\",\n    \"web_sales\".\"ws_item_sk\" AS \"ws_item_sk\",\n    \"web_sales\".\"ws_bill_customer_sk\" AS \"ws_customer_sk\",\n    SUM(\"web_sales\".\"ws_quantity\") AS \"ws_qty\",\n    SUM(\"web_sales\".\"ws_wholesale_cost\") AS \"ws_wc\",\n    SUM(\"web_sales\".\"ws_sales_price\") AS \"ws_sp\"\n  FROM \"web_sales\" AS \"web_sales\"\n  LEFT JOIN \"web_returns\" AS \"web_returns\"\n    ON \"web_returns\".\"wr_item_sk\" = \"web_sales\".\"ws_item_sk\"\n    AND \"web_returns\".\"wr_order_number\" = \"web_sales\".\"ws_order_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  WHERE\n    \"web_returns\".\"wr_order_number\" IS NULL\n  GROUP BY\n    \"date_dim\".\"d_year\",\n    \"web_sales\".\"ws_item_sk\",\n    \"web_sales\".\"ws_bill_customer_sk\"\n), \"cs\" AS (\n  SELECT\n    \"date_dim\".\"d_year\" AS \"cs_sold_year\",\n    \"catalog_sales\".\"cs_item_sk\" AS \"cs_item_sk\",\n    \"catalog_sales\".\"cs_bill_customer_sk\" AS \"cs_customer_sk\",\n    SUM(\"catalog_sales\".\"cs_quantity\") AS \"cs_qty\",\n    SUM(\"catalog_sales\".\"cs_wholesale_cost\") AS \"cs_wc\",\n    SUM(\"catalog_sales\".\"cs_sales_price\") AS \"cs_sp\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  LEFT JOIN \"catalog_returns\" AS \"catalog_returns\"\n    ON \"catalog_returns\".\"cr_item_sk\" = \"catalog_sales\".\"cs_item_sk\"\n    AND \"catalog_returns\".\"cr_order_number\" = \"catalog_sales\".\"cs_order_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  WHERE\n    \"catalog_returns\".\"cr_order_number\" IS NULL\n  GROUP BY\n    \"date_dim\".\"d_year\",\n    \"catalog_sales\".\"cs_item_sk\",\n    \"catalog_sales\".\"cs_bill_customer_sk\"\n), \"ss\" AS (\n  SELECT\n    \"date_dim\".\"d_year\" AS \"ss_sold_year\",\n    \"store_sales\".\"ss_item_sk\" AS \"ss_item_sk\",\n    \"store_sales\".\"ss_customer_sk\" AS \"ss_customer_sk\",\n    SUM(\"store_sales\".\"ss_quantity\") AS \"ss_qty\",\n    SUM(\"store_sales\".\"ss_wholesale_cost\") AS \"ss_wc\",\n    SUM(\"store_sales\".\"ss_sales_price\") AS \"ss_sp\"\n  FROM \"store_sales\" AS \"store_sales\"\n  LEFT JOIN \"store_returns\" AS \"store_returns\"\n    ON \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n    AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  WHERE\n    \"store_returns\".\"sr_ticket_number\" IS NULL\n  GROUP BY\n    \"date_dim\".\"d_year\",\n    \"store_sales\".\"ss_item_sk\",\n    \"store_sales\".\"ss_customer_sk\"\n)\nSELECT\n  \"ss\".\"ss_item_sk\" AS \"ss_item_sk\",\n  ROUND(\"ss\".\"ss_qty\" / COALESCE(\"ws\".\"ws_qty\" + \"cs\".\"cs_qty\", 1), 2) AS \"ratio\",\n  \"ss\".\"ss_qty\" AS \"store_qty\",\n  \"ss\".\"ss_wc\" AS \"store_wholesale_cost\",\n  \"ss\".\"ss_sp\" AS \"store_sales_price\",\n  COALESCE(\"ws\".\"ws_qty\", 0) + COALESCE(\"cs\".\"cs_qty\", 0) AS \"other_chan_qty\",\n  COALESCE(\"ws\".\"ws_wc\", 0) + COALESCE(\"cs\".\"cs_wc\", 0) AS \"other_chan_wholesale_cost\",\n  COALESCE(\"ws\".\"ws_sp\", 0) + COALESCE(\"cs\".\"cs_sp\", 0) AS \"other_chan_sales_price\"\nFROM \"ss\" AS \"ss\"\nLEFT JOIN \"ws\" AS \"ws\"\n  ON \"ss\".\"ss_customer_sk\" = \"ws\".\"ws_customer_sk\"\n  AND \"ss\".\"ss_item_sk\" = \"ws\".\"ws_item_sk\"\n  AND \"ss\".\"ss_sold_year\" = \"ws\".\"ws_sold_year\"\nLEFT JOIN \"cs\" AS \"cs\"\n  ON \"cs\".\"cs_customer_sk\" = \"ss\".\"ss_customer_sk\"\n  AND \"cs\".\"cs_item_sk\" = \"cs\".\"cs_item_sk\"\n  AND \"cs\".\"cs_sold_year\" = \"ss\".\"ss_sold_year\"\nWHERE\n  \"ss\".\"ss_sold_year\" = 1999\n  AND COALESCE(\"cs\".\"cs_qty\", 0) > 0\n  AND COALESCE(\"ws\".\"ws_qty\", 0) > 0\nORDER BY\n  \"ss_item_sk\",\n  \"ss\".\"ss_qty\" DESC,\n  \"ss\".\"ss_wc\" DESC,\n  \"ss\".\"ss_sp\" DESC,\n  \"other_chan_qty\",\n  \"other_chan_wholesale_cost\",\n  \"other_chan_sales_price\",\n  ROUND(\"ss\".\"ss_qty\" / COALESCE(\"ws\".\"ws_qty\" + \"cs\".\"cs_qty\", 1), 2)\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 79\n--------------------------------------\n# execute: true\nSELECT c_last_name,\n               c_first_name,\n               SUBSTRING(s_city, 1, 30) AS \"_col_2\",\n               ss_ticket_number,\n               amt,\n               profit\nFROM   (SELECT ss_ticket_number,\n               ss_customer_sk,\n               store.s_city,\n               Sum(ss_coupon_amt) amt,\n               Sum(ss_net_profit) profit\n        FROM   store_sales,\n               date_dim,\n               store,\n               household_demographics\n        WHERE  store_sales.ss_sold_date_sk = date_dim.d_date_sk\n               AND store_sales.ss_store_sk = store.s_store_sk\n               AND store_sales.ss_hdemo_sk = household_demographics.hd_demo_sk\n               AND ( household_demographics.hd_dep_count = 8\n                      OR household_demographics.hd_vehicle_count > 4 )\n               AND date_dim.d_dow = 1\n               AND date_dim.d_year IN ( 2000, 2000 + 1, 2000 + 2 )\n               AND store.s_number_employees BETWEEN 200 AND 295\n        GROUP  BY ss_ticket_number,\n                  ss_customer_sk,\n                  ss_addr_sk,\n                  store.s_city) ms,\n       customer\nWHERE  ss_customer_sk = c_customer_sk\nORDER  BY c_last_name,\n          c_first_name,\n          SUBSTRING(s_city, 1, 30),\n          profit\nLIMIT 100;\nWITH \"ms\" AS (\n  SELECT\n    \"store_sales\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\" AS \"ss_customer_sk\",\n    \"store\".\"s_city\" AS \"s_city\",\n    SUM(\"store_sales\".\"ss_coupon_amt\") AS \"amt\",\n    SUM(\"store_sales\".\"ss_net_profit\") AS \"profit\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_dow\" = 1\n    AND \"date_dim\".\"d_year\" IN (2000, 2001, 2002)\n  JOIN \"household_demographics\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n    AND (\n      \"household_demographics\".\"hd_dep_count\" = 8\n      OR \"household_demographics\".\"hd_vehicle_count\" > 4\n    )\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_number_employees\" <= 295\n    AND \"store\".\"s_number_employees\" >= 200\n    AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_ticket_number\",\n    \"store_sales\".\"ss_customer_sk\",\n    \"store_sales\".\"ss_addr_sk\",\n    \"store\".\"s_city\"\n)\nSELECT\n  \"customer\".\"c_last_name\" AS \"c_last_name\",\n  \"customer\".\"c_first_name\" AS \"c_first_name\",\n  SUBSTRING(\"ms\".\"s_city\", 1, 30) AS \"_col_2\",\n  \"ms\".\"ss_ticket_number\" AS \"ss_ticket_number\",\n  \"ms\".\"amt\" AS \"amt\",\n  \"ms\".\"profit\" AS \"profit\"\nFROM \"ms\" AS \"ms\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_customer_sk\" = \"ms\".\"ss_customer_sk\"\nORDER BY\n  \"c_last_name\",\n  \"c_first_name\",\n  SUBSTRING(\"ms\".\"s_city\", 1, 30),\n  \"profit\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 80\n--------------------------------------\nWITH ssr AS\n(\n                SELECT          s_store_id                                    AS store_id,\n                                Sum(ss_ext_sales_price)                       AS sales,\n                                Sum(COALESCE(sr_return_amt, 0))               AS returns1,\n                                Sum(ss_net_profit - COALESCE(sr_net_loss, 0)) AS profit\n                FROM            store_sales\n                LEFT OUTER JOIN store_returns\n                ON              (\n                                                ss_item_sk = sr_item_sk\n                                AND             ss_ticket_number = sr_ticket_number),\n                                date_dim,\n                                store,\n                                item,\n                                promotion\n                WHERE           ss_sold_date_sk = d_date_sk\n                AND             d_date BETWEEN Cast('2000-08-26' AS DATE) AND             (\n                                                Cast('2000-08-26' AS DATE) + INTERVAL '30' day)\n                AND             ss_store_sk = s_store_sk\n                AND             ss_item_sk = i_item_sk\n                AND             i_current_price > 50\n                AND             ss_promo_sk = p_promo_sk\n                AND             p_channel_tv = 'N'\n                GROUP BY        s_store_id) , csr AS\n(\n                SELECT          cp_catalog_page_id                            AS catalog_page_id,\n                                sum(cs_ext_sales_price)                       AS sales,\n                                sum(COALESCE(cr_return_amount, 0))            AS returns1,\n                                sum(cs_net_profit - COALESCE(cr_net_loss, 0)) AS profit\n                FROM            catalog_sales\n                LEFT OUTER JOIN catalog_returns\n                ON              (\n                                                cs_item_sk = cr_item_sk\n                                AND             cs_order_number = cr_order_number),\n                                date_dim,\n                                catalog_page,\n                                item,\n                                promotion\n                WHERE           cs_sold_date_sk = d_date_sk\n                AND             d_date BETWEEN cast('2000-08-26' AS date) AND             (\n                                                cast('2000-08-26' AS date) + INTERVAL '30' day)\n                AND             cs_catalog_page_sk = cp_catalog_page_sk\n                AND             cs_item_sk = i_item_sk\n                AND             i_current_price > 50\n                AND             cs_promo_sk = p_promo_sk\n                AND             p_channel_tv = 'N'\n                GROUP BY        cp_catalog_page_id) , wsr AS\n(\n                SELECT          web_site_id,\n                                sum(ws_ext_sales_price)                       AS sales,\n                                sum(COALESCE(wr_return_amt, 0))               AS returns1,\n                                sum(ws_net_profit - COALESCE(wr_net_loss, 0)) AS profit\n                FROM            web_sales\n                LEFT OUTER JOIN web_returns\n                ON              (\n                                                ws_item_sk = wr_item_sk\n                                AND             ws_order_number = wr_order_number),\n                                date_dim,\n                                web_site,\n                                item,\n                                promotion\n                WHERE           ws_sold_date_sk = d_date_sk\n                AND             d_date BETWEEN cast('2000-08-26' AS date) AND             (\n                                                cast('2000-08-26' AS date) + INTERVAL '30' day)\n                AND             ws_web_site_sk = web_site_sk\n                AND             ws_item_sk = i_item_sk\n                AND             i_current_price > 50\n                AND             ws_promo_sk = p_promo_sk\n                AND             p_channel_tv = 'N'\n                GROUP BY        web_site_id)\nSELECT\n         channel ,\n         id ,\n         sum(sales)   AS sales ,\n         sum(returns1) AS returns1 ,\n         sum(profit)  AS profit\nFROM     (\n                SELECT 'store channel' AS channel ,\n                       'store'\n                              || store_id AS id ,\n                       sales ,\n                       returns1 ,\n                       profit\n                FROM   ssr\n                UNION ALL\n                SELECT 'catalog channel' AS channel ,\n                       'catalog_page'\n                              || catalog_page_id AS id ,\n                       sales ,\n                       returns1 ,\n                       profit\n                FROM   csr\n                UNION ALL\n                SELECT 'web channel' AS channel ,\n                       'web_site'\n                              || web_site_id AS id ,\n                       sales ,\n                       returns1 ,\n                       profit\n                FROM   wsr ) x\nGROUP BY rollup (channel, id)\nORDER BY channel ,\n         id\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2000-09-25' AS DATE)\n    AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2000-08-26' AS DATE)\n), \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_current_price\" AS \"i_current_price\"\n  FROM \"item\" AS \"item\"\n  WHERE\n    \"item\".\"i_current_price\" > 50\n), \"promotion_2\" AS (\n  SELECT\n    \"promotion\".\"p_promo_sk\" AS \"p_promo_sk\",\n    \"promotion\".\"p_channel_tv\" AS \"p_channel_tv\"\n  FROM \"promotion\" AS \"promotion\"\n  WHERE\n    \"promotion\".\"p_channel_tv\" = 'N'\n), \"ssr\" AS (\n  SELECT\n    \"store\".\"s_store_id\" AS \"store_id\",\n    SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"sales\",\n    SUM(COALESCE(\"store_returns\".\"sr_return_amt\", 0)) AS \"returns1\",\n    SUM(\"store_sales\".\"ss_net_profit\" - COALESCE(\"store_returns\".\"sr_net_loss\", 0)) AS \"profit\"\n  FROM \"store_sales\" AS \"store_sales\"\n  LEFT JOIN \"store_returns\" AS \"store_returns\"\n    ON \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n    AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"promotion_2\" AS \"promotion\"\n    ON \"promotion\".\"p_promo_sk\" = \"store_sales\".\"ss_promo_sk\"\n  GROUP BY\n    \"store\".\"s_store_id\"\n), \"csr\" AS (\n  SELECT\n    \"catalog_page\".\"cp_catalog_page_id\" AS \"catalog_page_id\",\n    SUM(\"catalog_sales\".\"cs_ext_sales_price\") AS \"sales\",\n    SUM(COALESCE(\"catalog_returns\".\"cr_return_amount\", 0)) AS \"returns1\",\n    SUM(\"catalog_sales\".\"cs_net_profit\" - COALESCE(\"catalog_returns\".\"cr_net_loss\", 0)) AS \"profit\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  LEFT JOIN \"catalog_returns\" AS \"catalog_returns\"\n    ON \"catalog_returns\".\"cr_item_sk\" = \"catalog_sales\".\"cs_item_sk\"\n    AND \"catalog_returns\".\"cr_order_number\" = \"catalog_sales\".\"cs_order_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  JOIN \"catalog_page\" AS \"catalog_page\"\n    ON \"catalog_page\".\"cp_catalog_page_sk\" = \"catalog_sales\".\"cs_catalog_page_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_sales\".\"cs_item_sk\" = \"item\".\"i_item_sk\"\n  JOIN \"promotion_2\" AS \"promotion\"\n    ON \"catalog_sales\".\"cs_promo_sk\" = \"promotion\".\"p_promo_sk\"\n  GROUP BY\n    \"catalog_page\".\"cp_catalog_page_id\"\n), \"wsr\" AS (\n  SELECT\n    \"web_site\".\"web_site_id\" AS \"web_site_id\",\n    SUM(\"web_sales\".\"ws_ext_sales_price\") AS \"sales\",\n    SUM(COALESCE(\"web_returns\".\"wr_return_amt\", 0)) AS \"returns1\",\n    SUM(\"web_sales\".\"ws_net_profit\" - COALESCE(\"web_returns\".\"wr_net_loss\", 0)) AS \"profit\"\n  FROM \"web_sales\" AS \"web_sales\"\n  LEFT JOIN \"web_returns\" AS \"web_returns\"\n    ON \"web_returns\".\"wr_item_sk\" = \"web_sales\".\"ws_item_sk\"\n    AND \"web_returns\".\"wr_order_number\" = \"web_sales\".\"ws_order_number\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  JOIN \"web_site\" AS \"web_site\"\n    ON \"web_sales\".\"ws_web_site_sk\" = \"web_site\".\"web_site_sk\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  JOIN \"promotion_2\" AS \"promotion\"\n    ON \"promotion\".\"p_promo_sk\" = \"web_sales\".\"ws_promo_sk\"\n  GROUP BY\n    \"web_site\".\"web_site_id\"\n), \"x\" AS (\n  SELECT\n    'store channel' AS \"channel\",\n    'store' || \"ssr\".\"store_id\" AS \"id\",\n    \"ssr\".\"sales\" AS \"sales\",\n    \"ssr\".\"returns1\" AS \"returns1\",\n    \"ssr\".\"profit\" AS \"profit\"\n  FROM \"ssr\" AS \"ssr\"\n  UNION ALL\n  SELECT\n    'catalog channel' AS \"channel\",\n    'catalog_page' || \"csr\".\"catalog_page_id\" AS \"id\",\n    \"csr\".\"sales\" AS \"sales\",\n    \"csr\".\"returns1\" AS \"returns1\",\n    \"csr\".\"profit\" AS \"profit\"\n  FROM \"csr\" AS \"csr\"\n  UNION ALL\n  SELECT\n    'web channel' AS \"channel\",\n    'web_site' || \"wsr\".\"web_site_id\" AS \"id\",\n    \"wsr\".\"sales\" AS \"sales\",\n    \"wsr\".\"returns1\" AS \"returns1\",\n    \"wsr\".\"profit\" AS \"profit\"\n  FROM \"wsr\" AS \"wsr\"\n)\nSELECT\n  \"x\".\"channel\" AS \"channel\",\n  \"x\".\"id\" AS \"id\",\n  SUM(\"x\".\"sales\") AS \"sales\",\n  SUM(\"x\".\"returns1\") AS \"returns1\",\n  SUM(\"x\".\"profit\") AS \"profit\"\nFROM \"x\" AS \"x\"\nGROUP BY\n  ROLLUP (\n    \"x\".\"channel\",\n    \"x\".\"id\"\n  )\nORDER BY\n  \"channel\",\n  \"id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 81\n--------------------------------------\n# execute: true\nWITH customer_total_return\n     AS (SELECT cr_returning_customer_sk   AS ctr_customer_sk,\n                ca_state                   AS ctr_state,\n                Sum(cr_return_amt_inc_tax) AS ctr_total_return\n         FROM   catalog_returns,\n                date_dim,\n                customer_address\n         WHERE  cr_returned_date_sk = d_date_sk\n                AND d_year = 1999\n                AND cr_returning_addr_sk = ca_address_sk\n         GROUP  BY cr_returning_customer_sk,\n                   ca_state)\nSELECT c_customer_id,\n               c_salutation,\n               c_first_name,\n               c_last_name,\n               ca_street_number,\n               ca_street_name,\n               ca_street_type,\n               ca_suite_number,\n               ca_city,\n               ca_county,\n               ca_state,\n               ca_zip,\n               ca_country,\n               ca_gmt_offset,\n               ca_location_type,\n               ctr_total_return\nFROM   customer_total_return ctr1,\n       customer_address,\n       customer\nWHERE  ctr1.ctr_total_return > (SELECT Avg(ctr_total_return) * 1.2\n                                FROM   customer_total_return ctr2\n                                WHERE  ctr1.ctr_state = ctr2.ctr_state)\n       AND ca_address_sk = c_current_addr_sk\n       AND ca_state = 'TX'\n       AND ctr1.ctr_customer_sk = c_customer_sk\nORDER  BY c_customer_id,\n          c_salutation,\n          c_first_name,\n          c_last_name,\n          ca_street_number,\n          ca_street_name,\n          ca_street_type,\n          ca_suite_number,\n          ca_city,\n          ca_county,\n          ca_state,\n          ca_zip,\n          ca_country,\n          ca_gmt_offset,\n          ca_location_type,\n          ctr_total_return\nLIMIT 100;\nWITH \"customer_total_return\" AS (\n  SELECT\n    \"catalog_returns\".\"cr_returning_customer_sk\" AS \"ctr_customer_sk\",\n    \"customer_address\".\"ca_state\" AS \"ctr_state\",\n    SUM(\"catalog_returns\".\"cr_return_amt_inc_tax\") AS \"ctr_total_return\"\n  FROM \"catalog_returns\" AS \"catalog_returns\"\n  JOIN \"customer_address\" AS \"customer_address\"\n    ON \"catalog_returns\".\"cr_returning_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"catalog_returns\".\"cr_returned_date_sk\" = \"date_dim\".\"d_date_sk\"\n    AND \"date_dim\".\"d_year\" = 1999\n  GROUP BY\n    \"catalog_returns\".\"cr_returning_customer_sk\",\n    \"customer_address\".\"ca_state\"\n), \"_u_0\" AS (\n  SELECT\n    AVG(\"ctr2\".\"ctr_total_return\") * 1.2 AS \"_col_0\",\n    \"ctr2\".\"ctr_state\" AS \"_u_1\"\n  FROM \"customer_total_return\" AS \"ctr2\"\n  GROUP BY\n    \"ctr2\".\"ctr_state\"\n)\nSELECT\n  \"customer\".\"c_customer_id\" AS \"c_customer_id\",\n  \"customer\".\"c_salutation\" AS \"c_salutation\",\n  \"customer\".\"c_first_name\" AS \"c_first_name\",\n  \"customer\".\"c_last_name\" AS \"c_last_name\",\n  \"customer_address\".\"ca_street_number\" AS \"ca_street_number\",\n  \"customer_address\".\"ca_street_name\" AS \"ca_street_name\",\n  \"customer_address\".\"ca_street_type\" AS \"ca_street_type\",\n  \"customer_address\".\"ca_suite_number\" AS \"ca_suite_number\",\n  \"customer_address\".\"ca_city\" AS \"ca_city\",\n  \"customer_address\".\"ca_county\" AS \"ca_county\",\n  \"customer_address\".\"ca_state\" AS \"ca_state\",\n  \"customer_address\".\"ca_zip\" AS \"ca_zip\",\n  \"customer_address\".\"ca_country\" AS \"ca_country\",\n  \"customer_address\".\"ca_gmt_offset\" AS \"ca_gmt_offset\",\n  \"customer_address\".\"ca_location_type\" AS \"ca_location_type\",\n  \"ctr1\".\"ctr_total_return\" AS \"ctr_total_return\"\nFROM \"customer_total_return\" AS \"ctr1\"\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer_address\".\"ca_state\" = 'TX'\nJOIN \"customer\" AS \"customer\"\n  ON \"ctr1\".\"ctr_customer_sk\" = \"customer\".\"c_customer_sk\"\n  AND \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"ctr1\".\"ctr_state\"\nWHERE\n  \"_u_0\".\"_col_0\" < \"ctr1\".\"ctr_total_return\"\nORDER BY\n  \"c_customer_id\",\n  \"c_salutation\",\n  \"c_first_name\",\n  \"c_last_name\",\n  \"ca_street_number\",\n  \"ca_street_name\",\n  \"ca_street_type\",\n  \"ca_suite_number\",\n  \"ca_city\",\n  \"ca_county\",\n  \"ca_state\",\n  \"ca_zip\",\n  \"ca_country\",\n  \"ca_gmt_offset\",\n  \"ca_location_type\",\n  \"ctr_total_return\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 82\n--------------------------------------\nSELECT\n         i_item_id ,\n         i_item_desc ,\n         i_current_price\nFROM     item,\n         inventory,\n         date_dim,\n         store_sales\nWHERE    i_current_price BETWEEN 63 AND      63+30\nAND      inv_item_sk = i_item_sk\nAND      d_date_sk=inv_date_sk\nAND      d_date BETWEEN Cast('1998-04-27' AS DATE) AND      (\n                  Cast('1998-04-27' AS DATE) + INTERVAL '60' day)\nAND      i_manufact_id IN (57,293,427,320)\nAND      inv_quantity_on_hand BETWEEN 100 AND      500\nAND      ss_item_sk = i_item_sk\nGROUP BY i_item_id,\n         i_item_desc,\n         i_current_price\nORDER BY i_item_id\nLIMIT 100;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"item\".\"i_current_price\" AS \"i_current_price\"\nFROM \"item\" AS \"item\"\nJOIN \"inventory\" AS \"inventory\"\n  ON \"inventory\".\"inv_item_sk\" = \"item\".\"i_item_sk\"\n  AND \"inventory\".\"inv_quantity_on_hand\" <= 500\n  AND \"inventory\".\"inv_quantity_on_hand\" >= 100\nJOIN \"store_sales\" AS \"store_sales\"\n  ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"inventory\".\"inv_date_sk\"\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('1998-06-26' AS DATE)\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('1998-04-27' AS DATE)\nWHERE\n  \"item\".\"i_current_price\" <= 93\n  AND \"item\".\"i_current_price\" >= 63\n  AND \"item\".\"i_manufact_id\" IN (57, 293, 427, 320)\nGROUP BY\n  \"item\".\"i_item_id\",\n  \"item\".\"i_item_desc\",\n  \"item\".\"i_current_price\"\nORDER BY\n  \"i_item_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 83\n--------------------------------------\n# execute: true\nWITH sr_items\n     AS (SELECT i_item_id               item_id,\n                Sum(sr_return_quantity) sr_item_qty\n         FROM   store_returns,\n                item,\n                date_dim\n         WHERE  sr_item_sk = i_item_sk\n                AND d_date IN (SELECT d_date\n                               FROM   date_dim\n                               WHERE  d_week_seq IN (SELECT d_week_seq\n                                                     FROM   date_dim\n                                                     WHERE\n                                      d_date IN ( '1999-06-30',\n                                                  '1999-08-28',\n                                                  '1999-11-18'\n                                                )))\n                AND sr_returned_date_sk = d_date_sk\n         GROUP  BY i_item_id),\n     cr_items\n     AS (SELECT i_item_id               item_id,\n                Sum(cr_return_quantity) cr_item_qty\n         FROM   catalog_returns,\n                item,\n                date_dim\n         WHERE  cr_item_sk = i_item_sk\n                AND d_date IN (SELECT d_date\n                               FROM   date_dim\n                               WHERE  d_week_seq IN (SELECT d_week_seq\n                                                     FROM   date_dim\n                                                     WHERE\n                                      d_date IN ( '1999-06-30',\n                                                  '1999-08-28',\n                                                  '1999-11-18'\n                                                )))\n                AND cr_returned_date_sk = d_date_sk\n         GROUP  BY i_item_id),\n     wr_items\n     AS (SELECT i_item_id               item_id,\n                Sum(wr_return_quantity) wr_item_qty\n         FROM   web_returns,\n                item,\n                date_dim\n         WHERE  wr_item_sk = i_item_sk\n                AND d_date IN (SELECT d_date\n                               FROM   date_dim\n                               WHERE  d_week_seq IN (SELECT d_week_seq\n                                                     FROM   date_dim\n                                                     WHERE\n                                      d_date IN ( '1999-06-30',\n                                                  '1999-08-28',\n                                                  '1999-11-18'\n                                                )))\n                AND wr_returned_date_sk = d_date_sk\n         GROUP  BY i_item_id)\nSELECT sr_items.item_id,\n               sr_item_qty,\n               sr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 *\n               100 sr_dev,\n               cr_item_qty,\n               cr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 *\n               100 cr_dev,\n               wr_item_qty,\n               wr_item_qty / ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0 *\n               100 wr_dev,\n               ( sr_item_qty + cr_item_qty + wr_item_qty ) / 3.0\n               average\nFROM   sr_items,\n       cr_items,\n       wr_items\nWHERE  sr_items.item_id = cr_items.item_id\n       AND sr_items.item_id = wr_items.item_id\nORDER  BY sr_items.item_id,\n          sr_item_qty\nLIMIT 100;\nWITH \"item_2\" AS (\n  SELECT\n    \"item\".\"i_item_sk\" AS \"i_item_sk\",\n    \"item\".\"i_item_id\" AS \"i_item_id\"\n  FROM \"item\" AS \"item\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n), \"_u_0\" AS (\n  SELECT\n    \"date_dim\".\"d_week_seq\" AS \"d_week_seq\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_date\" IN ('1999-06-30', '1999-08-28', '1999-11-18')\n  GROUP BY\n    \"date_dim\".\"d_week_seq\"\n), \"_u_1\" AS (\n  SELECT\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  LEFT JOIN \"_u_0\" AS \"_u_0\"\n    ON \"_u_0\".\"d_week_seq\" = \"date_dim\".\"d_week_seq\"\n  WHERE\n    NOT \"_u_0\".\"d_week_seq\" IS NULL\n  GROUP BY\n    \"date_dim\".\"d_date\"\n), \"sr_items\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"item_id\",\n    SUM(\"store_returns\".\"sr_return_quantity\") AS \"sr_item_qty\"\n  FROM \"store_returns\" AS \"store_returns\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"store_returns\".\"sr_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_returns\".\"sr_returned_date_sk\"\n  LEFT JOIN \"_u_1\" AS \"_u_1\"\n    ON \"_u_1\".\"d_date\" = \"date_dim\".\"d_date\"\n  WHERE\n    NOT \"_u_1\".\"d_date\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"_u_3\" AS (\n  SELECT\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  LEFT JOIN \"_u_0\" AS \"_u_2\"\n    ON \"_u_2\".\"d_week_seq\" = \"date_dim\".\"d_week_seq\"\n  WHERE\n    NOT \"_u_2\".\"d_week_seq\" IS NULL\n  GROUP BY\n    \"date_dim\".\"d_date\"\n), \"cr_items\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"item_id\",\n    SUM(\"catalog_returns\".\"cr_return_quantity\") AS \"cr_item_qty\"\n  FROM \"catalog_returns\" AS \"catalog_returns\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"catalog_returns\".\"cr_item_sk\" = \"item\".\"i_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_returns\".\"cr_returned_date_sk\" = \"date_dim\".\"d_date_sk\"\n  LEFT JOIN \"_u_3\" AS \"_u_3\"\n    ON \"_u_3\".\"d_date\" = \"date_dim\".\"d_date\"\n  WHERE\n    NOT \"_u_3\".\"d_date\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n), \"_u_5\" AS (\n  SELECT\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  LEFT JOIN \"_u_0\" AS \"_u_4\"\n    ON \"_u_4\".\"d_week_seq\" = \"date_dim\".\"d_week_seq\"\n  WHERE\n    NOT \"_u_4\".\"d_week_seq\" IS NULL\n  GROUP BY\n    \"date_dim\".\"d_date\"\n), \"wr_items\" AS (\n  SELECT\n    \"item\".\"i_item_id\" AS \"item_id\",\n    SUM(\"web_returns\".\"wr_return_quantity\") AS \"wr_item_qty\"\n  FROM \"web_returns\" AS \"web_returns\"\n  JOIN \"item_2\" AS \"item\"\n    ON \"item\".\"i_item_sk\" = \"web_returns\".\"wr_item_sk\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_returns\".\"wr_returned_date_sk\"\n  LEFT JOIN \"_u_5\" AS \"_u_5\"\n    ON \"_u_5\".\"d_date\" = \"date_dim\".\"d_date\"\n  WHERE\n    NOT \"_u_5\".\"d_date\" IS NULL\n  GROUP BY\n    \"item\".\"i_item_id\"\n)\nSELECT\n  \"sr_items\".\"item_id\" AS \"item_id\",\n  \"sr_items\".\"sr_item_qty\" AS \"sr_item_qty\",\n  \"sr_items\".\"sr_item_qty\" / (\n    \"sr_items\".\"sr_item_qty\" + \"cr_items\".\"cr_item_qty\" + \"wr_items\".\"wr_item_qty\"\n  ) / 3.0 * 100 AS \"sr_dev\",\n  \"cr_items\".\"cr_item_qty\" AS \"cr_item_qty\",\n  \"cr_items\".\"cr_item_qty\" / (\n    \"sr_items\".\"sr_item_qty\" + \"cr_items\".\"cr_item_qty\" + \"wr_items\".\"wr_item_qty\"\n  ) / 3.0 * 100 AS \"cr_dev\",\n  \"wr_items\".\"wr_item_qty\" AS \"wr_item_qty\",\n  \"wr_items\".\"wr_item_qty\" / (\n    \"sr_items\".\"sr_item_qty\" + \"cr_items\".\"cr_item_qty\" + \"wr_items\".\"wr_item_qty\"\n  ) / 3.0 * 100 AS \"wr_dev\",\n  (\n    \"sr_items\".\"sr_item_qty\" + \"cr_items\".\"cr_item_qty\" + \"wr_items\".\"wr_item_qty\"\n  ) / 3.0 AS \"average\"\nFROM \"sr_items\" AS \"sr_items\"\nJOIN \"cr_items\" AS \"cr_items\"\n  ON \"cr_items\".\"item_id\" = \"sr_items\".\"item_id\"\nJOIN \"wr_items\" AS \"wr_items\"\n  ON \"sr_items\".\"item_id\" = \"wr_items\".\"item_id\"\nORDER BY\n  \"sr_items\".\"item_id\",\n  \"sr_item_qty\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 84\n--------------------------------------\n# execute: true\nSELECT c_customer_id   AS customer_id,\n               c_last_name\n               || ', '\n               || c_first_name AS customername\nFROM   customer,\n       customer_address,\n       customer_demographics,\n       household_demographics,\n       income_band,\n       store_returns\nWHERE  ca_city = 'Green Acres'\n       AND c_current_addr_sk = ca_address_sk\n       AND ib_lower_bound >= 54986\n       AND ib_upper_bound <= 54986 + 50000\n       AND ib_income_band_sk = hd_income_band_sk\n       AND cd_demo_sk = c_current_cdemo_sk\n       AND hd_demo_sk = c_current_hdemo_sk\n       AND sr_cdemo_sk = cd_demo_sk\nORDER  BY c_customer_id\nLIMIT 100;\nSELECT\n  \"customer\".\"c_customer_id\" AS \"customer_id\",\n  \"customer\".\"c_last_name\" || ', ' || \"customer\".\"c_first_name\" AS \"customername\"\nFROM \"customer\" AS \"customer\"\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n  AND \"customer_address\".\"ca_city\" = 'Green Acres'\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"customer\".\"c_current_cdemo_sk\" = \"customer_demographics\".\"cd_demo_sk\"\nJOIN \"household_demographics\" AS \"household_demographics\"\n  ON \"customer\".\"c_current_hdemo_sk\" = \"household_demographics\".\"hd_demo_sk\"\nJOIN \"income_band\" AS \"income_band\"\n  ON \"household_demographics\".\"hd_income_band_sk\" = \"income_band\".\"ib_income_band_sk\"\n  AND \"income_band\".\"ib_lower_bound\" >= 54986\n  AND \"income_band\".\"ib_upper_bound\" <= 104986\nJOIN \"store_returns\" AS \"store_returns\"\n  ON \"customer_demographics\".\"cd_demo_sk\" = \"store_returns\".\"sr_cdemo_sk\"\nORDER BY\n  \"customer\".\"c_customer_id\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 85\n--------------------------------------\n# execute: true\nSELECT SUBSTRING(r_reason_desc, 1, 20) AS \"_col_0\",\n               Avg(ws_quantity) AS \"_col_1\",\n               Avg(wr_refunded_cash) AS \"_col_2\",\n               Avg(wr_fee) AS \"_col_3\"\nFROM   web_sales,\n       web_returns,\n       web_page,\n       customer_demographics cd1,\n       customer_demographics cd2,\n       customer_address,\n       date_dim,\n       reason\nWHERE  ws_web_page_sk = wp_web_page_sk\n       AND ws_item_sk = wr_item_sk\n       AND ws_order_number = wr_order_number\n       AND ws_sold_date_sk = d_date_sk\n       AND d_year = 2001\n       AND cd1.cd_demo_sk = wr_refunded_cdemo_sk\n       AND cd2.cd_demo_sk = wr_returning_cdemo_sk\n       AND ca_address_sk = wr_refunded_addr_sk\n       AND r_reason_sk = wr_reason_sk\n       AND ( ( cd1.cd_marital_status = 'W'\n               AND cd1.cd_marital_status = cd2.cd_marital_status\n               AND cd1.cd_education_status = 'Primary'\n               AND cd1.cd_education_status = cd2.cd_education_status\n               AND ws_sales_price BETWEEN 100.00 AND 150.00 )\n              OR ( cd1.cd_marital_status = 'D'\n                   AND cd1.cd_marital_status = cd2.cd_marital_status\n                   AND cd1.cd_education_status = 'Secondary'\n                   AND cd1.cd_education_status = cd2.cd_education_status\n                   AND ws_sales_price BETWEEN 50.00 AND 100.00 )\n              OR ( cd1.cd_marital_status = 'M'\n                   AND cd1.cd_marital_status = cd2.cd_marital_status\n                   AND cd1.cd_education_status = 'Advanced Degree'\n                   AND cd1.cd_education_status = cd2.cd_education_status\n                   AND ws_sales_price BETWEEN 150.00 AND 200.00 ) )\n       AND ( ( ca_country = 'United States'\n               AND ca_state IN ( 'KY', 'ME', 'IL' )\n               AND ws_net_profit BETWEEN 100 AND 200 )\n              OR ( ca_country = 'United States'\n                   AND ca_state IN ( 'OK', 'NE', 'MN' )\n                   AND ws_net_profit BETWEEN 150 AND 300 )\n              OR ( ca_country = 'United States'\n                   AND ca_state IN ( 'FL', 'WI', 'KS' )\n                   AND ws_net_profit BETWEEN 50 AND 250 ) )\nGROUP  BY r_reason_desc\nORDER  BY SUBSTRING(r_reason_desc, 1, 20),\n          Avg(ws_quantity),\n          Avg(wr_refunded_cash),\n          Avg(wr_fee)\nLIMIT 100;\nSELECT\n  SUBSTRING(\"reason\".\"r_reason_desc\", 1, 20) AS \"_col_0\",\n  AVG(\"web_sales\".\"ws_quantity\") AS \"_col_1\",\n  AVG(\"web_returns\".\"wr_refunded_cash\") AS \"_col_2\",\n  AVG(\"web_returns\".\"wr_fee\") AS \"_col_3\"\nFROM \"web_sales\" AS \"web_sales\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  AND \"date_dim\".\"d_year\" = 2001\nJOIN \"web_page\" AS \"web_page\"\n  ON \"web_page\".\"wp_web_page_sk\" = \"web_sales\".\"ws_web_page_sk\"\nJOIN \"web_returns\" AS \"web_returns\"\n  ON \"web_returns\".\"wr_item_sk\" = \"web_sales\".\"ws_item_sk\"\n  AND \"web_returns\".\"wr_order_number\" = \"web_sales\".\"ws_order_number\"\nJOIN \"customer_demographics\" AS \"cd1\"\n  ON \"cd1\".\"cd_demo_sk\" = \"web_returns\".\"wr_refunded_cdemo_sk\"\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer_address\".\"ca_address_sk\" = \"web_returns\".\"wr_refunded_addr_sk\"\n  AND (\n    (\n      \"customer_address\".\"ca_country\" = 'United States'\n      AND \"customer_address\".\"ca_state\" IN ('FL', 'WI', 'KS')\n      AND \"web_sales\".\"ws_net_profit\" <= 250\n      AND \"web_sales\".\"ws_net_profit\" >= 50\n    )\n    OR (\n      \"customer_address\".\"ca_country\" = 'United States'\n      AND \"customer_address\".\"ca_state\" IN ('KY', 'ME', 'IL')\n      AND \"web_sales\".\"ws_net_profit\" <= 200\n      AND \"web_sales\".\"ws_net_profit\" >= 100\n    )\n    OR (\n      \"customer_address\".\"ca_country\" = 'United States'\n      AND \"customer_address\".\"ca_state\" IN ('OK', 'NE', 'MN')\n      AND \"web_sales\".\"ws_net_profit\" <= 300\n      AND \"web_sales\".\"ws_net_profit\" >= 150\n    )\n  )\nJOIN \"reason\" AS \"reason\"\n  ON \"reason\".\"r_reason_sk\" = \"web_returns\".\"wr_reason_sk\"\nJOIN \"customer_demographics\" AS \"cd2\"\n  ON \"cd2\".\"cd_demo_sk\" = \"web_returns\".\"wr_returning_cdemo_sk\"\n  AND (\n    (\n      \"cd1\".\"cd_education_status\" = \"cd2\".\"cd_education_status\"\n      AND \"cd1\".\"cd_education_status\" = 'Advanced Degree'\n      AND \"cd1\".\"cd_marital_status\" = \"cd2\".\"cd_marital_status\"\n      AND \"cd1\".\"cd_marital_status\" = 'M'\n      AND \"web_sales\".\"ws_sales_price\" <= 200.00\n      AND \"web_sales\".\"ws_sales_price\" >= 150.00\n    )\n    OR (\n      \"cd1\".\"cd_education_status\" = \"cd2\".\"cd_education_status\"\n      AND \"cd1\".\"cd_education_status\" = 'Primary'\n      AND \"cd1\".\"cd_marital_status\" = \"cd2\".\"cd_marital_status\"\n      AND \"cd1\".\"cd_marital_status\" = 'W'\n      AND \"web_sales\".\"ws_sales_price\" <= 150.00\n      AND \"web_sales\".\"ws_sales_price\" >= 100.00\n    )\n    OR (\n      \"cd1\".\"cd_education_status\" = \"cd2\".\"cd_education_status\"\n      AND \"cd1\".\"cd_education_status\" = 'Secondary'\n      AND \"cd1\".\"cd_marital_status\" = \"cd2\".\"cd_marital_status\"\n      AND \"cd1\".\"cd_marital_status\" = 'D'\n      AND \"web_sales\".\"ws_sales_price\" <= 100.00\n      AND \"web_sales\".\"ws_sales_price\" >= 50.00\n    )\n  )\nGROUP BY\n  \"reason\".\"r_reason_desc\"\nORDER BY\n  \"_col_0\",\n  \"_col_1\",\n  \"_col_2\",\n  \"_col_3\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 86\n--------------------------------------\nSELECT Sum(ws_net_paid)                         AS total_sum,\n               i_category,\n               i_class,\n               Grouping(i_category) + Grouping(i_class) AS lochierarchy,\n               Rank()\n                 OVER (\n                   partition BY Grouping(i_category)+Grouping(i_class), CASE\n                 WHEN Grouping(\n                 i_class) = 0 THEN i_category END\n                   ORDER BY Sum(ws_net_paid) DESC)      AS rank_within_parent\nFROM   web_sales,\n       date_dim d1,\n       item\nWHERE  d1.d_month_seq BETWEEN 1183 AND 1183 + 11\n       AND d1.d_date_sk = ws_sold_date_sk\n       AND i_item_sk = ws_item_sk\nGROUP  BY rollup( i_category, i_class )\nORDER  BY lochierarchy DESC,\n          CASE\n            WHEN lochierarchy = 0 THEN i_category\n          END,\n          rank_within_parent\nLIMIT 100;\nSELECT\n  SUM(\"web_sales\".\"ws_net_paid\") AS \"total_sum\",\n  \"item\".\"i_category\" AS \"i_category\",\n  \"item\".\"i_class\" AS \"i_class\",\n  GROUPING(\"item\".\"i_category\") + GROUPING(\"item\".\"i_class\") AS \"lochierarchy\",\n  RANK() OVER (\n    PARTITION BY GROUPING(\"item\".\"i_category\") + GROUPING(\"item\".\"i_class\"), CASE WHEN GROUPING(\"item\".\"i_class\") = 0 THEN \"item\".\"i_category\" END\n    ORDER BY SUM(\"web_sales\".\"ws_net_paid\") DESC\n  ) AS \"rank_within_parent\"\nFROM \"web_sales\" AS \"web_sales\"\nJOIN \"date_dim\" AS \"d1\"\n  ON \"d1\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  AND \"d1\".\"d_month_seq\" <= 1194\n  AND \"d1\".\"d_month_seq\" >= 1183\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\"\nGROUP BY\n  ROLLUP (\n    \"item\".\"i_category\",\n    \"item\".\"i_class\"\n  )\nORDER BY\n  \"lochierarchy\" DESC,\n  CASE WHEN \"lochierarchy\" = 0 THEN \"i_category\" END,\n  \"rank_within_parent\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 87\n--------------------------------------\n# execute: true\nselect count(*) as \"_col_0\"\nfrom ((select distinct c_last_name, c_first_name, d_date\n       from store_sales, date_dim, customer\n       where store_sales.ss_sold_date_sk = date_dim.d_date_sk\n         and store_sales.ss_customer_sk = customer.c_customer_sk\n         and d_month_seq between 1188 and 1188+11)\n       except\n      (select distinct c_last_name, c_first_name, d_date\n       from catalog_sales, date_dim, customer\n       where catalog_sales.cs_sold_date_sk = date_dim.d_date_sk\n         and catalog_sales.cs_bill_customer_sk = customer.c_customer_sk\n         and d_month_seq between 1188 and 1188+11)\n       except\n      (select distinct c_last_name, c_first_name, d_date\n       from web_sales, date_dim, customer\n       where web_sales.ws_sold_date_sk = date_dim.d_date_sk\n         and web_sales.ws_bill_customer_sk = customer.c_customer_sk\n         and d_month_seq between 1188 and 1188+11)\n) cool_cust;\nWITH \"customer_2\" AS (\n  SELECT\n    \"customer\".\"c_customer_sk\" AS \"c_customer_sk\",\n    \"customer\".\"c_first_name\" AS \"c_first_name\",\n    \"customer\".\"c_last_name\" AS \"c_last_name\"\n  FROM \"customer\" AS \"customer\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\",\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_month_seq\" <= 1199 AND \"date_dim\".\"d_month_seq\" >= 1188\n), \"cool_cust\" AS (\n  (\n    SELECT DISTINCT\n      \"customer\".\"c_last_name\" AS \"c_last_name\",\n      \"customer\".\"c_first_name\" AS \"c_first_name\",\n      \"date_dim\".\"d_date\" AS \"d_date\"\n    FROM \"store_sales\" AS \"store_sales\"\n    JOIN \"customer_2\" AS \"customer\"\n      ON \"customer\".\"c_customer_sk\" = \"store_sales\".\"ss_customer_sk\"\n    JOIN \"date_dim_2\" AS \"date_dim\"\n      ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  )\n  EXCEPT\n  (\n    SELECT DISTINCT\n      \"customer\".\"c_last_name\" AS \"c_last_name\",\n      \"customer\".\"c_first_name\" AS \"c_first_name\",\n      \"date_dim\".\"d_date\" AS \"d_date\"\n    FROM \"catalog_sales\" AS \"catalog_sales\"\n    JOIN \"customer_2\" AS \"customer\"\n      ON \"catalog_sales\".\"cs_bill_customer_sk\" = \"customer\".\"c_customer_sk\"\n    JOIN \"date_dim_2\" AS \"date_dim\"\n      ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  )\n  EXCEPT\n  (\n    SELECT DISTINCT\n      \"customer\".\"c_last_name\" AS \"c_last_name\",\n      \"customer\".\"c_first_name\" AS \"c_first_name\",\n      \"date_dim\".\"d_date\" AS \"d_date\"\n    FROM \"web_sales\" AS \"web_sales\"\n    JOIN \"customer_2\" AS \"customer\"\n      ON \"customer\".\"c_customer_sk\" = \"web_sales\".\"ws_bill_customer_sk\"\n    JOIN \"date_dim_2\" AS \"date_dim\"\n      ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  )\n)\nSELECT\n  COUNT(*) AS \"_col_0\"\nFROM \"cool_cust\" AS \"cool_cust\";\n\n--------------------------------------\n-- TPC-DS 88\n--------------------------------------\n# execute: true\nselect  *\nfrom\n (select count(*) h8_30_to_9\n from store_sales, household_demographics , time_dim, store\n where ss_sold_time_sk = time_dim.t_time_sk\n     and ss_hdemo_sk = household_demographics.hd_demo_sk\n     and ss_store_sk = s_store_sk\n     and time_dim.t_hour = 8\n     and time_dim.t_minute >= 30\n     and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or\n          (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or\n          (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2))\n     and store.s_store_name = 'ese') s1,\n (select count(*) h9_to_9_30\n from store_sales, household_demographics , time_dim, store\n where ss_sold_time_sk = time_dim.t_time_sk\n     and ss_hdemo_sk = household_demographics.hd_demo_sk\n     and ss_store_sk = s_store_sk\n     and time_dim.t_hour = 9\n     and time_dim.t_minute < 30\n     and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or\n          (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or\n          (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2))\n     and store.s_store_name = 'ese') s2,\n (select count(*) h9_30_to_10\n from store_sales, household_demographics , time_dim, store\n where ss_sold_time_sk = time_dim.t_time_sk\n     and ss_hdemo_sk = household_demographics.hd_demo_sk\n     and ss_store_sk = s_store_sk\n     and time_dim.t_hour = 9\n     and time_dim.t_minute >= 30\n     and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or\n          (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or\n          (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2))\n     and store.s_store_name = 'ese') s3,\n (select count(*) h10_to_10_30\n from store_sales, household_demographics , time_dim, store\n where ss_sold_time_sk = time_dim.t_time_sk\n     and ss_hdemo_sk = household_demographics.hd_demo_sk\n     and ss_store_sk = s_store_sk\n     and time_dim.t_hour = 10\n     and time_dim.t_minute < 30\n     and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or\n          (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or\n          (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2))\n     and store.s_store_name = 'ese') s4,\n (select count(*) h10_30_to_11\n from store_sales, household_demographics , time_dim, store\n where ss_sold_time_sk = time_dim.t_time_sk\n     and ss_hdemo_sk = household_demographics.hd_demo_sk\n     and ss_store_sk = s_store_sk\n     and time_dim.t_hour = 10\n     and time_dim.t_minute >= 30\n     and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or\n          (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or\n          (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2))\n     and store.s_store_name = 'ese') s5,\n (select count(*) h11_to_11_30\n from store_sales, household_demographics , time_dim, store\n where ss_sold_time_sk = time_dim.t_time_sk\n     and ss_hdemo_sk = household_demographics.hd_demo_sk\n     and ss_store_sk = s_store_sk\n     and time_dim.t_hour = 11\n     and time_dim.t_minute < 30\n     and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or\n          (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or\n          (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2))\n     and store.s_store_name = 'ese') s6,\n (select count(*) h11_30_to_12\n from store_sales, household_demographics , time_dim, store\n where ss_sold_time_sk = time_dim.t_time_sk\n     and ss_hdemo_sk = household_demographics.hd_demo_sk\n     and ss_store_sk = s_store_sk\n     and time_dim.t_hour = 11\n     and time_dim.t_minute >= 30\n     and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or\n          (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or\n          (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2))\n     and store.s_store_name = 'ese') s7,\n (select count(*) h12_to_12_30\n from store_sales, household_demographics , time_dim, store\n where ss_sold_time_sk = time_dim.t_time_sk\n     and ss_hdemo_sk = household_demographics.hd_demo_sk\n     and ss_store_sk = s_store_sk\n     and time_dim.t_hour = 12\n     and time_dim.t_minute < 30\n     and ((household_demographics.hd_dep_count = -1 and household_demographics.hd_vehicle_count<=-1+2) or\n          (household_demographics.hd_dep_count = 2 and household_demographics.hd_vehicle_count<=2+2) or\n          (household_demographics.hd_dep_count = 3 and household_demographics.hd_vehicle_count<=3+2))\n     and store.s_store_name = 'ese') s8;\nWITH \"store_sales_2\" AS (\n  SELECT\n    \"store_sales\".\"ss_sold_time_sk\" AS \"ss_sold_time_sk\",\n    \"store_sales\".\"ss_hdemo_sk\" AS \"ss_hdemo_sk\",\n    \"store_sales\".\"ss_store_sk\" AS \"ss_store_sk\"\n  FROM \"store_sales\" AS \"store_sales\"\n), \"household_demographics_2\" AS (\n  SELECT\n    \"household_demographics\".\"hd_demo_sk\" AS \"hd_demo_sk\",\n    \"household_demographics\".\"hd_dep_count\" AS \"hd_dep_count\",\n    \"household_demographics\".\"hd_vehicle_count\" AS \"hd_vehicle_count\"\n  FROM \"household_demographics\" AS \"household_demographics\"\n  WHERE\n    (\n      \"household_demographics\".\"hd_dep_count\" = -1\n      OR \"household_demographics\".\"hd_dep_count\" = 2\n      OR \"household_demographics\".\"hd_dep_count\" = 3\n    )\n    AND (\n      \"household_demographics\".\"hd_dep_count\" = 2\n      OR \"household_demographics\".\"hd_dep_count\" = 3\n      OR \"household_demographics\".\"hd_vehicle_count\" <= 1\n    )\n    AND (\n      \"household_demographics\".\"hd_dep_count\" = 3\n      OR \"household_demographics\".\"hd_vehicle_count\" <= 4\n    )\n    AND \"household_demographics\".\"hd_vehicle_count\" <= 5\n), \"store_2\" AS (\n  SELECT\n    \"store\".\"s_store_sk\" AS \"s_store_sk\",\n    \"store\".\"s_store_name\" AS \"s_store_name\"\n  FROM \"store\" AS \"store\"\n  WHERE\n    \"store\".\"s_store_name\" = 'ese'\n), \"s1\" AS (\n  SELECT\n    COUNT(*) AS \"h8_30_to_9\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n    AND \"time_dim\".\"t_hour\" = 8\n    AND \"time_dim\".\"t_minute\" >= 30\n), \"s2\" AS (\n  SELECT\n    COUNT(*) AS \"h9_to_9_30\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n    AND \"time_dim\".\"t_hour\" = 9\n    AND \"time_dim\".\"t_minute\" < 30\n), \"s3\" AS (\n  SELECT\n    COUNT(*) AS \"h9_30_to_10\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n    AND \"time_dim\".\"t_hour\" = 9\n    AND \"time_dim\".\"t_minute\" >= 30\n), \"s4\" AS (\n  SELECT\n    COUNT(*) AS \"h10_to_10_30\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n    AND \"time_dim\".\"t_hour\" = 10\n    AND \"time_dim\".\"t_minute\" < 30\n), \"s5\" AS (\n  SELECT\n    COUNT(*) AS \"h10_30_to_11\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n    AND \"time_dim\".\"t_hour\" = 10\n    AND \"time_dim\".\"t_minute\" >= 30\n), \"s6\" AS (\n  SELECT\n    COUNT(*) AS \"h11_to_11_30\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n    AND \"time_dim\".\"t_hour\" = 11\n    AND \"time_dim\".\"t_minute\" < 30\n), \"s7\" AS (\n  SELECT\n    COUNT(*) AS \"h11_30_to_12\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n    AND \"time_dim\".\"t_hour\" = 11\n    AND \"time_dim\".\"t_minute\" >= 30\n), \"s8\" AS (\n  SELECT\n    COUNT(*) AS \"h12_to_12_30\"\n  FROM \"store_sales_2\" AS \"store_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  JOIN \"store_2\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n    AND \"time_dim\".\"t_hour\" = 12\n    AND \"time_dim\".\"t_minute\" < 30\n)\nSELECT\n  \"s1\".\"h8_30_to_9\" AS \"h8_30_to_9\",\n  \"s2\".\"h9_to_9_30\" AS \"h9_to_9_30\",\n  \"s3\".\"h9_30_to_10\" AS \"h9_30_to_10\",\n  \"s4\".\"h10_to_10_30\" AS \"h10_to_10_30\",\n  \"s5\".\"h10_30_to_11\" AS \"h10_30_to_11\",\n  \"s6\".\"h11_to_11_30\" AS \"h11_to_11_30\",\n  \"s7\".\"h11_30_to_12\" AS \"h11_30_to_12\",\n  \"s8\".\"h12_to_12_30\" AS \"h12_to_12_30\"\nFROM \"s1\" AS \"s1\"\nCROSS JOIN \"s2\" AS \"s2\"\nCROSS JOIN \"s3\" AS \"s3\"\nCROSS JOIN \"s4\" AS \"s4\"\nCROSS JOIN \"s5\" AS \"s5\"\nCROSS JOIN \"s6\" AS \"s6\"\nCROSS JOIN \"s7\" AS \"s7\"\nCROSS JOIN \"s8\" AS \"s8\";\n\n--------------------------------------\n-- TPC-DS 89\n--------------------------------------\nSELECT  *\nFROM  (SELECT i_category,\n              i_class,\n              i_brand,\n              s_store_name,\n              s_company_name,\n              d_moy,\n              Sum(ss_sales_price) sum_sales,\n              Avg(Sum(ss_sales_price))\n                OVER (\n                  partition BY i_category, i_brand, s_store_name, s_company_name\n                )\n                                  avg_monthly_sales\n       FROM   item,\n              store_sales,\n              date_dim,\n              store\n       WHERE  ss_item_sk = i_item_sk\n              AND ss_sold_date_sk = d_date_sk\n              AND ss_store_sk = s_store_sk\n              AND d_year IN ( 2002 )\n              AND ( ( i_category IN ( 'Home', 'Men', 'Sports' )\n                      AND i_class IN ( 'paint', 'accessories', 'fitness' ) )\n                     OR ( i_category IN ( 'Shoes', 'Jewelry', 'Women' )\n                          AND i_class IN ( 'mens', 'pendants', 'swimwear' ) ) )\n       GROUP  BY i_category,\n                 i_class,\n                 i_brand,\n                 s_store_name,\n                 s_company_name,\n                 d_moy) tmp1\nWHERE  CASE\n         WHEN ( avg_monthly_sales <> 0 ) THEN (\n         Abs(sum_sales - avg_monthly_sales) / avg_monthly_sales )\n         ELSE NULL\n       END > 0.1\nORDER  BY sum_sales - avg_monthly_sales,\n          s_store_name\nLIMIT 100;\nWITH \"tmp1\" AS (\n  SELECT\n    \"item\".\"i_category\" AS \"i_category\",\n    \"item\".\"i_class\" AS \"i_class\",\n    \"item\".\"i_brand\" AS \"i_brand\",\n    \"store\".\"s_store_name\" AS \"s_store_name\",\n    \"store\".\"s_company_name\" AS \"s_company_name\",\n    \"date_dim\".\"d_moy\" AS \"d_moy\",\n    SUM(\"store_sales\".\"ss_sales_price\") AS \"sum_sales\",\n    AVG(SUM(\"store_sales\".\"ss_sales_price\")) OVER (\n      PARTITION BY \"item\".\"i_category\", \"item\".\"i_brand\", \"store\".\"s_store_name\", \"store\".\"s_company_name\"\n    ) AS \"avg_monthly_sales\"\n  FROM \"item\" AS \"item\"\n  JOIN \"store_sales\" AS \"store_sales\"\n    ON \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  JOIN \"date_dim\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n    AND \"date_dim\".\"d_year\" IN (2002)\n  JOIN \"store\" AS \"store\"\n    ON \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\n  WHERE\n    (\n      \"item\".\"i_category\" IN ('Home', 'Men', 'Sports')\n      OR \"item\".\"i_category\" IN ('Shoes', 'Jewelry', 'Women')\n    )\n    AND (\n      \"item\".\"i_category\" IN ('Home', 'Men', 'Sports')\n      OR \"item\".\"i_class\" IN ('mens', 'pendants', 'swimwear')\n    )\n    AND (\n      \"item\".\"i_category\" IN ('Shoes', 'Jewelry', 'Women')\n      OR \"item\".\"i_class\" IN ('paint', 'accessories', 'fitness')\n    )\n    AND (\n      \"item\".\"i_class\" IN ('mens', 'pendants', 'swimwear')\n      OR \"item\".\"i_class\" IN ('paint', 'accessories', 'fitness')\n    )\n  GROUP BY\n    \"item\".\"i_category\",\n    \"item\".\"i_class\",\n    \"item\".\"i_brand\",\n    \"store\".\"s_store_name\",\n    \"store\".\"s_company_name\",\n    \"date_dim\".\"d_moy\"\n)\nSELECT\n  \"tmp1\".\"i_category\" AS \"i_category\",\n  \"tmp1\".\"i_class\" AS \"i_class\",\n  \"tmp1\".\"i_brand\" AS \"i_brand\",\n  \"tmp1\".\"s_store_name\" AS \"s_store_name\",\n  \"tmp1\".\"s_company_name\" AS \"s_company_name\",\n  \"tmp1\".\"d_moy\" AS \"d_moy\",\n  \"tmp1\".\"sum_sales\" AS \"sum_sales\",\n  \"tmp1\".\"avg_monthly_sales\" AS \"avg_monthly_sales\"\nFROM \"tmp1\" AS \"tmp1\"\nWHERE\n  CASE\n    WHEN \"tmp1\".\"avg_monthly_sales\" <> 0\n    THEN (\n      ABS(\"tmp1\".\"sum_sales\" - \"tmp1\".\"avg_monthly_sales\") / \"tmp1\".\"avg_monthly_sales\"\n    )\n    ELSE NULL\n  END > 0.1\nORDER BY\n  \"tmp1\".\"sum_sales\" - \"tmp1\".\"avg_monthly_sales\",\n  \"tmp1\".\"s_store_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 90\n--------------------------------------\nSELECT Cast(amc AS DECIMAL(15, 4)) / Cast(pmc AS DECIMAL(15, 4))\n               am_pm_ratio\nFROM   (SELECT Count(*) amc\n        FROM   web_sales,\n               household_demographics,\n               time_dim,\n               web_page\n        WHERE  ws_sold_time_sk = time_dim.t_time_sk\n               AND ws_ship_hdemo_sk = household_demographics.hd_demo_sk\n               AND ws_web_page_sk = web_page.wp_web_page_sk\n               AND time_dim.t_hour BETWEEN 12 AND 12 + 1\n               AND household_demographics.hd_dep_count = 8\n               AND web_page.wp_char_count BETWEEN 5000 AND 5200) at1,\n       (SELECT Count(*) pmc\n        FROM   web_sales,\n               household_demographics,\n               time_dim,\n               web_page\n        WHERE  ws_sold_time_sk = time_dim.t_time_sk\n               AND ws_ship_hdemo_sk = household_demographics.hd_demo_sk\n               AND ws_web_page_sk = web_page.wp_web_page_sk\n               AND time_dim.t_hour BETWEEN 20 AND 20 + 1\n               AND household_demographics.hd_dep_count = 8\n               AND web_page.wp_char_count BETWEEN 5000 AND 5200) pt\nORDER  BY am_pm_ratio\nLIMIT 100;\nWITH \"web_sales_2\" AS (\n  SELECT\n    \"web_sales\".\"ws_sold_time_sk\" AS \"ws_sold_time_sk\",\n    \"web_sales\".\"ws_ship_hdemo_sk\" AS \"ws_ship_hdemo_sk\",\n    \"web_sales\".\"ws_web_page_sk\" AS \"ws_web_page_sk\"\n  FROM \"web_sales\" AS \"web_sales\"\n), \"household_demographics_2\" AS (\n  SELECT\n    \"household_demographics\".\"hd_demo_sk\" AS \"hd_demo_sk\",\n    \"household_demographics\".\"hd_dep_count\" AS \"hd_dep_count\"\n  FROM \"household_demographics\" AS \"household_demographics\"\n  WHERE\n    \"household_demographics\".\"hd_dep_count\" = 8\n), \"web_page_2\" AS (\n  SELECT\n    \"web_page\".\"wp_web_page_sk\" AS \"wp_web_page_sk\",\n    \"web_page\".\"wp_char_count\" AS \"wp_char_count\"\n  FROM \"web_page\" AS \"web_page\"\n  WHERE\n    \"web_page\".\"wp_char_count\" <= 5200 AND \"web_page\".\"wp_char_count\" >= 5000\n), \"at1\" AS (\n  SELECT\n    COUNT(*) AS \"amc\"\n  FROM \"web_sales_2\" AS \"web_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"web_sales\".\"ws_ship_hdemo_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"time_dim\".\"t_hour\" <= 13\n    AND \"time_dim\".\"t_hour\" >= 12\n    AND \"time_dim\".\"t_time_sk\" = \"web_sales\".\"ws_sold_time_sk\"\n  JOIN \"web_page_2\" AS \"web_page\"\n    ON \"web_page\".\"wp_web_page_sk\" = \"web_sales\".\"ws_web_page_sk\"\n), \"pt\" AS (\n  SELECT\n    COUNT(*) AS \"pmc\"\n  FROM \"web_sales_2\" AS \"web_sales\"\n  JOIN \"household_demographics_2\" AS \"household_demographics\"\n    ON \"household_demographics\".\"hd_demo_sk\" = \"web_sales\".\"ws_ship_hdemo_sk\"\n  JOIN \"time_dim\" AS \"time_dim\"\n    ON \"time_dim\".\"t_hour\" <= 21\n    AND \"time_dim\".\"t_hour\" >= 20\n    AND \"time_dim\".\"t_time_sk\" = \"web_sales\".\"ws_sold_time_sk\"\n  JOIN \"web_page_2\" AS \"web_page\"\n    ON \"web_page\".\"wp_web_page_sk\" = \"web_sales\".\"ws_web_page_sk\"\n)\nSELECT\n  CAST(\"at1\".\"amc\" AS DECIMAL(15, 4)) / CAST(\"pt\".\"pmc\" AS DECIMAL(15, 4)) AS \"am_pm_ratio\"\nFROM \"at1\" AS \"at1\"\nCROSS JOIN \"pt\" AS \"pt\"\nORDER BY\n  \"am_pm_ratio\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 91\n--------------------------------------\n# execute: true\nSELECT cc_call_center_id call_center,\n       cc_name           call_center_name,\n       cc_manager        manager,\n       Sum(cr_net_loss)  returns_loss\nFROM   call_center,\n       catalog_returns,\n       date_dim,\n       customer,\n       customer_address,\n       customer_demographics,\n       household_demographics\nWHERE  cr_call_center_sk = cc_call_center_sk\n       AND cr_returned_date_sk = d_date_sk\n       AND cr_returning_customer_sk = c_customer_sk\n       AND cd_demo_sk = c_current_cdemo_sk\n       AND hd_demo_sk = c_current_hdemo_sk\n       AND ca_address_sk = c_current_addr_sk\n       AND d_year = 1999\n       AND d_moy = 12\n       AND ( ( cd_marital_status = 'M'\n               AND cd_education_status = 'Unknown' )\n              OR ( cd_marital_status = 'W'\n                   AND cd_education_status = 'Advanced Degree' ) )\n       AND hd_buy_potential LIKE 'Unknown%'\n       AND ca_gmt_offset = -7\nGROUP  BY cc_call_center_id,\n          cc_name,\n          cc_manager,\n          cd_marital_status,\n          cd_education_status\nORDER  BY Sum(cr_net_loss) DESC;\nSELECT\n  \"call_center\".\"cc_call_center_id\" AS \"call_center\",\n  \"call_center\".\"cc_name\" AS \"call_center_name\",\n  \"call_center\".\"cc_manager\" AS \"manager\",\n  SUM(\"catalog_returns\".\"cr_net_loss\") AS \"returns_loss\"\nFROM \"call_center\" AS \"call_center\"\nJOIN \"catalog_returns\" AS \"catalog_returns\"\n  ON \"call_center\".\"cc_call_center_sk\" = \"catalog_returns\".\"cr_call_center_sk\"\nJOIN \"customer\" AS \"customer\"\n  ON \"catalog_returns\".\"cr_returning_customer_sk\" = \"customer\".\"c_customer_sk\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"catalog_returns\".\"cr_returned_date_sk\" = \"date_dim\".\"d_date_sk\"\n  AND \"date_dim\".\"d_moy\" = 12\n  AND \"date_dim\".\"d_year\" = 1999\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer\".\"c_current_addr_sk\" = \"customer_address\".\"ca_address_sk\"\n  AND \"customer_address\".\"ca_gmt_offset\" = -7\nJOIN \"customer_demographics\" AS \"customer_demographics\"\n  ON \"customer\".\"c_current_cdemo_sk\" = \"customer_demographics\".\"cd_demo_sk\"\n  AND (\n    \"customer_demographics\".\"cd_education_status\" = 'Advanced Degree'\n    OR \"customer_demographics\".\"cd_education_status\" = 'Unknown'\n  )\n  AND (\n    \"customer_demographics\".\"cd_education_status\" = 'Advanced Degree'\n    OR \"customer_demographics\".\"cd_marital_status\" = 'M'\n  )\n  AND (\n    \"customer_demographics\".\"cd_education_status\" = 'Unknown'\n    OR \"customer_demographics\".\"cd_marital_status\" = 'W'\n  )\n  AND (\n    \"customer_demographics\".\"cd_marital_status\" = 'M'\n    OR \"customer_demographics\".\"cd_marital_status\" = 'W'\n  )\nJOIN \"household_demographics\" AS \"household_demographics\"\n  ON \"customer\".\"c_current_hdemo_sk\" = \"household_demographics\".\"hd_demo_sk\"\n  AND \"household_demographics\".\"hd_buy_potential\" LIKE 'Unknown%'\nGROUP BY\n  \"call_center\".\"cc_call_center_id\",\n  \"call_center\".\"cc_name\",\n  \"call_center\".\"cc_manager\",\n  \"customer_demographics\".\"cd_marital_status\",\n  \"customer_demographics\".\"cd_education_status\"\nORDER BY\n  \"returns_loss\" DESC;\n\n--------------------------------------\n-- TPC-DS 92\n--------------------------------------\nSELECT\n         Sum(ws_ext_discount_amt) AS \"Excess Discount Amount\"\nFROM     web_sales ,\n         item ,\n         date_dim\nWHERE    i_manufact_id = 718\nAND      i_item_sk = ws_item_sk\nAND      d_date BETWEEN '2002-03-29' AND      (\n                  Cast('2002-03-29' AS DATE) +  INTERVAL '90' day)\nAND      d_date_sk = ws_sold_date_sk\nAND      ws_ext_discount_amt >\n         (\n                SELECT 1.3 * avg(ws_ext_discount_amt)\n                FROM   web_sales ,\n                       date_dim\n                WHERE  ws_item_sk = i_item_sk\n                AND    d_date BETWEEN '2002-03-29' AND    (\n                              cast('2002-03-29' AS date) + INTERVAL '90' day)\n                AND    d_date_sk = ws_sold_date_sk )\nORDER BY sum(ws_ext_discount_amt)\nLIMIT 100;\nWITH \"web_sales_2\" AS (\n  SELECT\n    \"web_sales\".\"ws_sold_date_sk\" AS \"ws_sold_date_sk\",\n    \"web_sales\".\"ws_item_sk\" AS \"ws_item_sk\",\n    \"web_sales\".\"ws_ext_discount_amt\" AS \"ws_ext_discount_amt\"\n  FROM \"web_sales\" AS \"web_sales\"\n), \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_date\" AS \"d_date\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_date\" >= '2002-03-29'\n    AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2002-06-27' AS DATE)\n), \"_u_0\" AS (\n  SELECT\n    1.3 * AVG(\"web_sales\".\"ws_ext_discount_amt\") AS \"_col_0\",\n    \"web_sales\".\"ws_item_sk\" AS \"_u_1\"\n  FROM \"web_sales_2\" AS \"web_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\n  GROUP BY\n    \"web_sales\".\"ws_item_sk\"\n)\nSELECT\n  SUM(\"web_sales\".\"ws_ext_discount_amt\") AS \"Excess Discount Amount\"\nFROM \"web_sales_2\" AS \"web_sales\"\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_item_sk\" = \"web_sales\".\"ws_item_sk\" AND \"item\".\"i_manufact_id\" = 718\nJOIN \"date_dim_2\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"web_sales\".\"ws_sold_date_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"item\".\"i_item_sk\"\nWHERE\n  \"_u_0\".\"_col_0\" < \"web_sales\".\"ws_ext_discount_amt\"\nORDER BY\n  SUM(\"web_sales\".\"ws_ext_discount_amt\")\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 93\n--------------------------------------\n# execute: true\nSELECT ss_customer_sk,\n               Sum(act_sales) sumsales\nFROM   (SELECT ss_item_sk,\n               ss_ticket_number,\n               ss_customer_sk,\n               CASE\n                 WHEN sr_return_quantity IS NOT NULL THEN\n                 ( ss_quantity - sr_return_quantity ) * ss_sales_price\n                 ELSE ( ss_quantity * ss_sales_price )\n               END act_sales\n        FROM   store_sales\n               LEFT OUTER JOIN store_returns\n                            ON ( sr_item_sk = ss_item_sk\n                                 AND sr_ticket_number = ss_ticket_number ),\n               reason\n        WHERE  sr_reason_sk = r_reason_sk\n               AND r_reason_desc = 'reason 38') t\nGROUP  BY ss_customer_sk\nORDER  BY sumsales,\n          ss_customer_sk\nLIMIT 100;\nSELECT\n  \"store_sales\".\"ss_customer_sk\" AS \"ss_customer_sk\",\n  SUM(\n    CASE\n      WHEN NOT \"store_returns\".\"sr_return_quantity\" IS NULL\n      THEN (\n        \"store_sales\".\"ss_quantity\" - \"store_returns\".\"sr_return_quantity\"\n      ) * \"store_sales\".\"ss_sales_price\"\n      ELSE (\n        \"store_sales\".\"ss_quantity\" * \"store_sales\".\"ss_sales_price\"\n      )\n    END\n  ) AS \"sumsales\"\nFROM \"store_sales\" AS \"store_sales\"\nLEFT JOIN \"store_returns\" AS \"store_returns\"\n  ON \"store_returns\".\"sr_item_sk\" = \"store_sales\".\"ss_item_sk\"\n  AND \"store_returns\".\"sr_ticket_number\" = \"store_sales\".\"ss_ticket_number\"\nJOIN \"reason\" AS \"reason\"\n  ON \"reason\".\"r_reason_desc\" = 'reason 38'\nWHERE\n  \"reason\".\"r_reason_sk\" = \"store_returns\".\"sr_reason_sk\"\nGROUP BY\n  \"store_sales\".\"ss_customer_sk\"\nORDER BY\n  \"sumsales\",\n  \"ss_customer_sk\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 94\n--------------------------------------\nSELECT\n         Count(DISTINCT ws_order_number) AS \"order count\" ,\n         Sum(ws_ext_ship_cost)           AS \"total shipping cost\" ,\n         Sum(ws_net_profit)              AS \"total net profit\"\nFROM     web_sales ws1 ,\n         date_dim ,\n         customer_address ,\n         web_site\nWHERE    d_date BETWEEN '2000-3-01' AND      (\n                  Cast('2000-3-01' AS DATE) + INTERVAL '60' day)\nAND      ws1.ws_ship_date_sk = d_date_sk\nAND      ws1.ws_ship_addr_sk = ca_address_sk\nAND      ca_state = 'MT'\nAND      ws1.ws_web_site_sk = web_site_sk\nAND      web_company_name = 'pri'\nAND      EXISTS\n         (\n                SELECT *\n                FROM   web_sales ws2\n                WHERE  ws1.ws_order_number = ws2.ws_order_number\n                AND    ws1.ws_warehouse_sk <> ws2.ws_warehouse_sk)\nAND      NOT EXISTS\n         (\n                SELECT *\n                FROM   web_returns wr1\n                WHERE  ws1.ws_order_number = wr1.wr_order_number)\nORDER BY count(DISTINCT ws_order_number)\nLIMIT 100;\nWITH \"_u_0\" AS (\n  SELECT\n    \"ws2\".\"ws_order_number\" AS \"_u_1\",\n    ARRAY_AGG(\"ws2\".\"ws_warehouse_sk\") AS \"_u_2\"\n  FROM \"web_sales\" AS \"ws2\"\n  GROUP BY\n    \"ws2\".\"ws_order_number\"\n), \"_u_3\" AS (\n  SELECT\n    \"wr1\".\"wr_order_number\" AS \"_u_4\"\n  FROM \"web_returns\" AS \"wr1\"\n  GROUP BY\n    \"wr1\".\"wr_order_number\"\n)\nSELECT\n  COUNT(DISTINCT \"ws1\".\"ws_order_number\") AS \"order count\",\n  SUM(\"ws1\".\"ws_ext_ship_cost\") AS \"total shipping cost\",\n  SUM(\"ws1\".\"ws_net_profit\") AS \"total net profit\"\nFROM \"web_sales\" AS \"ws1\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date\" >= '2000-3-01'\n  AND \"date_dim\".\"d_date_sk\" = \"ws1\".\"ws_ship_date_sk\"\n  AND (\n    CAST('2000-3-01' AS DATE) + INTERVAL '60' DAY\n  ) >= CAST(\"date_dim\".\"d_date\" AS DATE)\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer_address\".\"ca_address_sk\" = \"ws1\".\"ws_ship_addr_sk\"\n  AND \"customer_address\".\"ca_state\" = 'MT'\nJOIN \"web_site\" AS \"web_site\"\n  ON \"web_site\".\"web_company_name\" = 'pri'\n  AND \"web_site\".\"web_site_sk\" = \"ws1\".\"ws_web_site_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"ws1\".\"ws_order_number\"\nLEFT JOIN \"_u_3\" AS \"_u_3\"\n  ON \"_u_3\".\"_u_4\" = \"ws1\".\"ws_order_number\"\nWHERE\n  \"_u_3\".\"_u_4\" IS NULL\n  AND ARRAY_ANY(\"_u_0\".\"_u_2\", \"_x\" -> \"ws1\".\"ws_warehouse_sk\" <> \"_x\")\n  AND NOT \"_u_0\".\"_u_1\" IS NULL\nORDER BY\n  COUNT(DISTINCT \"ws1\".\"ws_order_number\")\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 95\n--------------------------------------\nWITH ws_wh AS\n(\n       SELECT ws1.ws_order_number,\n              ws1.ws_warehouse_sk wh1,\n              ws2.ws_warehouse_sk wh2\n       FROM   web_sales ws1,\n              web_sales ws2\n       WHERE  ws1.ws_order_number = ws2.ws_order_number\n       AND    ws1.ws_warehouse_sk <> ws2.ws_warehouse_sk)\nSELECT\n         Count(DISTINCT ws_order_number) AS \"order count\" ,\n         Sum(ws_ext_ship_cost)           AS \"total shipping cost\" ,\n         Sum(ws_net_profit)              AS \"total net profit\"\nFROM     web_sales ws1 ,\n         date_dim ,\n         customer_address ,\n         web_site\nWHERE    d_date BETWEEN '2000-4-01' AND      (\n                  Cast('2000-4-01' AS DATE) + INTERVAL '60' day)\nAND      ws1.ws_ship_date_sk = d_date_sk\nAND      ws1.ws_ship_addr_sk = ca_address_sk\nAND      ca_state = 'IN'\nAND      ws1.ws_web_site_sk = web_site_sk\nAND      web_company_name = 'pri'\nAND      ws1.ws_order_number IN\n         (\n                SELECT ws_order_number\n                FROM   ws_wh)\nAND      ws1.ws_order_number IN\n         (\n                SELECT wr_order_number\n                FROM   web_returns,\n                       ws_wh\n                WHERE  wr_order_number = ws_wh.ws_order_number)\nORDER BY count(DISTINCT ws_order_number)\nLIMIT 100;\nWITH \"ws_wh\" AS (\n  SELECT\n    \"ws1\".\"ws_order_number\" AS \"ws_order_number\"\n  FROM \"web_sales\" AS \"ws1\"\n  JOIN \"web_sales\" AS \"ws2\"\n    ON \"ws1\".\"ws_order_number\" = \"ws2\".\"ws_order_number\"\n    AND \"ws1\".\"ws_warehouse_sk\" <> \"ws2\".\"ws_warehouse_sk\"\n), \"_u_0\" AS (\n  SELECT\n    \"ws_wh\".\"ws_order_number\" AS \"ws_order_number\"\n  FROM \"ws_wh\" AS \"ws_wh\"\n  GROUP BY\n    \"ws_wh\".\"ws_order_number\"\n), \"_u_1\" AS (\n  SELECT\n    \"web_returns\".\"wr_order_number\" AS \"wr_order_number\"\n  FROM \"web_returns\" AS \"web_returns\"\n  JOIN \"ws_wh\" AS \"ws_wh\"\n    ON \"web_returns\".\"wr_order_number\" = \"ws_wh\".\"ws_order_number\"\n  GROUP BY\n    \"web_returns\".\"wr_order_number\"\n)\nSELECT\n  COUNT(DISTINCT \"ws1\".\"ws_order_number\") AS \"order count\",\n  SUM(\"ws1\".\"ws_ext_ship_cost\") AS \"total shipping cost\",\n  SUM(\"ws1\".\"ws_net_profit\") AS \"total net profit\"\nFROM \"web_sales\" AS \"ws1\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date\" >= '2000-4-01'\n  AND \"date_dim\".\"d_date_sk\" = \"ws1\".\"ws_ship_date_sk\"\n  AND (\n    CAST('2000-4-01' AS DATE) + INTERVAL '60' DAY\n  ) >= CAST(\"date_dim\".\"d_date\" AS DATE)\nJOIN \"customer_address\" AS \"customer_address\"\n  ON \"customer_address\".\"ca_address_sk\" = \"ws1\".\"ws_ship_addr_sk\"\n  AND \"customer_address\".\"ca_state\" = 'IN'\nJOIN \"web_site\" AS \"web_site\"\n  ON \"web_site\".\"web_company_name\" = 'pri'\n  AND \"web_site\".\"web_site_sk\" = \"ws1\".\"ws_web_site_sk\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"ws_order_number\" = \"ws1\".\"ws_order_number\"\nLEFT JOIN \"_u_1\" AS \"_u_1\"\n  ON \"_u_1\".\"wr_order_number\" = \"ws1\".\"ws_order_number\"\nWHERE\n  NOT \"_u_0\".\"ws_order_number\" IS NULL AND NOT \"_u_1\".\"wr_order_number\" IS NULL\nORDER BY\n  COUNT(DISTINCT \"ws1\".\"ws_order_number\")\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 96\n--------------------------------------\n# execute: true\nSELECT Count(*) AS \"_col_0\"\nFROM   store_sales,\n       household_demographics,\n       time_dim,\n       store\nWHERE  ss_sold_time_sk = time_dim.t_time_sk\n       AND ss_hdemo_sk = household_demographics.hd_demo_sk\n       AND ss_store_sk = s_store_sk\n       AND time_dim.t_hour = 15\n       AND time_dim.t_minute >= 30\n       AND household_demographics.hd_dep_count = 7\n       AND store.s_store_name = 'ese'\nORDER  BY Count(*)\nLIMIT 100;\nSELECT\n  COUNT(*) AS \"_col_0\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"household_demographics\" AS \"household_demographics\"\n  ON \"household_demographics\".\"hd_demo_sk\" = \"store_sales\".\"ss_hdemo_sk\"\n  AND \"household_demographics\".\"hd_dep_count\" = 7\nJOIN \"store\" AS \"store\"\n  ON \"store\".\"s_store_name\" = 'ese'\n  AND \"store\".\"s_store_sk\" = \"store_sales\".\"ss_store_sk\"\nJOIN \"time_dim\" AS \"time_dim\"\n  ON \"store_sales\".\"ss_sold_time_sk\" = \"time_dim\".\"t_time_sk\"\n  AND \"time_dim\".\"t_hour\" = 15\n  AND \"time_dim\".\"t_minute\" >= 30\nORDER BY\n  COUNT(*)\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 97\n--------------------------------------\n# execute: true\nWITH ssci\n     AS (SELECT ss_customer_sk customer_sk,\n                ss_item_sk     item_sk\n         FROM   store_sales,\n                date_dim\n         WHERE  ss_sold_date_sk = d_date_sk\n                AND d_month_seq BETWEEN 1196 AND 1196 + 11\n         GROUP  BY ss_customer_sk,\n                   ss_item_sk),\n     csci\n     AS (SELECT cs_bill_customer_sk customer_sk,\n                cs_item_sk          item_sk\n         FROM   catalog_sales,\n                date_dim\n         WHERE  cs_sold_date_sk = d_date_sk\n                AND d_month_seq BETWEEN 1196 AND 1196 + 11\n         GROUP  BY cs_bill_customer_sk,\n                   cs_item_sk)\nSELECT Sum(CASE\n                     WHEN ssci.customer_sk IS NOT NULL\n                          AND csci.customer_sk IS NULL THEN 1\n                     ELSE 0\n                   END) store_only,\n               Sum(CASE\n                     WHEN ssci.customer_sk IS NULL\n                          AND csci.customer_sk IS NOT NULL THEN 1\n                     ELSE 0\n                   END) catalog_only,\n               Sum(CASE\n                     WHEN ssci.customer_sk IS NOT NULL\n                          AND csci.customer_sk IS NOT NULL THEN 1\n                     ELSE 0\n                   END) store_and_catalog\nFROM   ssci\n       FULL OUTER JOIN csci\n                    ON ( ssci.customer_sk = csci.customer_sk\n                         AND ssci.item_sk = csci.item_sk )\nLIMIT 100;\nWITH \"date_dim_2\" AS (\n  SELECT\n    \"date_dim\".\"d_date_sk\" AS \"d_date_sk\",\n    \"date_dim\".\"d_month_seq\" AS \"d_month_seq\"\n  FROM \"date_dim\" AS \"date_dim\"\n  WHERE\n    \"date_dim\".\"d_month_seq\" <= 1207 AND \"date_dim\".\"d_month_seq\" >= 1196\n), \"ssci\" AS (\n  SELECT\n    \"store_sales\".\"ss_customer_sk\" AS \"customer_sk\",\n    \"store_sales\".\"ss_item_sk\" AS \"item_sk\"\n  FROM \"store_sales\" AS \"store_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  GROUP BY\n    \"store_sales\".\"ss_customer_sk\",\n    \"store_sales\".\"ss_item_sk\"\n), \"csci\" AS (\n  SELECT\n    \"catalog_sales\".\"cs_bill_customer_sk\" AS \"customer_sk\",\n    \"catalog_sales\".\"cs_item_sk\" AS \"item_sk\"\n  FROM \"catalog_sales\" AS \"catalog_sales\"\n  JOIN \"date_dim_2\" AS \"date_dim\"\n    ON \"catalog_sales\".\"cs_sold_date_sk\" = \"date_dim\".\"d_date_sk\"\n  GROUP BY\n    \"catalog_sales\".\"cs_bill_customer_sk\",\n    \"catalog_sales\".\"cs_item_sk\"\n)\nSELECT\n  SUM(\n    CASE\n      WHEN \"csci\".\"customer_sk\" IS NULL AND NOT \"ssci\".\"customer_sk\" IS NULL\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"store_only\",\n  SUM(\n    CASE\n      WHEN \"ssci\".\"customer_sk\" IS NULL AND NOT \"csci\".\"customer_sk\" IS NULL\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"catalog_only\",\n  SUM(\n    CASE\n      WHEN NOT \"csci\".\"customer_sk\" IS NULL AND NOT \"ssci\".\"customer_sk\" IS NULL\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"store_and_catalog\"\nFROM \"ssci\" AS \"ssci\"\nFULL JOIN \"csci\" AS \"csci\"\n  ON \"csci\".\"customer_sk\" = \"ssci\".\"customer_sk\"\n  AND \"csci\".\"item_sk\" = \"ssci\".\"item_sk\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-DS 98\n--------------------------------------\nSELECT i_item_id,\n       i_item_desc,\n       i_category,\n       i_class,\n       i_current_price,\n       Sum(ss_ext_sales_price)                                   AS itemrevenue,\n       Sum(ss_ext_sales_price) * 100 / Sum(Sum(ss_ext_sales_price))\n                                         OVER (\n                                           PARTITION BY i_class) AS revenueratio\nFROM   store_sales,\n       item,\n       date_dim\nWHERE  ss_item_sk = i_item_sk\n       AND i_category IN ( 'Men', 'Home', 'Electronics' )\n       AND ss_sold_date_sk = d_date_sk\n       AND d_date BETWEEN CAST('2000-05-18' AS DATE) AND (\n                          CAST('2000-05-18' AS DATE) + INTERVAL '30' DAY )\nGROUP  BY i_item_id,\n          i_item_desc,\n          i_category,\n          i_class,\n          i_current_price\nORDER  BY i_category,\n          i_class,\n          i_item_id,\n          i_item_desc,\n          revenueratio;\nSELECT\n  \"item\".\"i_item_id\" AS \"i_item_id\",\n  \"item\".\"i_item_desc\" AS \"i_item_desc\",\n  \"item\".\"i_category\" AS \"i_category\",\n  \"item\".\"i_class\" AS \"i_class\",\n  \"item\".\"i_current_price\" AS \"i_current_price\",\n  SUM(\"store_sales\".\"ss_ext_sales_price\") AS \"itemrevenue\",\n  SUM(\"store_sales\".\"ss_ext_sales_price\") * 100 / SUM(SUM(\"store_sales\".\"ss_ext_sales_price\")) OVER (PARTITION BY \"item\".\"i_class\") AS \"revenueratio\"\nFROM \"store_sales\" AS \"store_sales\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"date_dim\".\"d_date_sk\" = \"store_sales\".\"ss_sold_date_sk\"\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) <= CAST('2000-06-17' AS DATE)\n  AND CAST(\"date_dim\".\"d_date\" AS DATE) >= CAST('2000-05-18' AS DATE)\nJOIN \"item\" AS \"item\"\n  ON \"item\".\"i_category\" IN ('Men', 'Home', 'Electronics')\n  AND \"item\".\"i_item_sk\" = \"store_sales\".\"ss_item_sk\"\nGROUP BY\n  \"item\".\"i_item_id\",\n  \"item\".\"i_item_desc\",\n  \"item\".\"i_category\",\n  \"item\".\"i_class\",\n  \"item\".\"i_current_price\"\nORDER BY\n  \"i_category\",\n  \"i_class\",\n  \"i_item_id\",\n  \"i_item_desc\",\n  \"revenueratio\";\n\n--------------------------------------\n-- TPC-DS 99\n--------------------------------------\n# execute: true\nSELECT SUBSTRING(w_warehouse_name, 1, 20) AS \"_col_0\",\n               sm_type,\n               cc_name,\n               Sum(CASE\n                     WHEN ( cs_ship_date_sk - cs_sold_date_sk <= 30 ) THEN 1\n                     ELSE 0\n                   END) AS \"30 days\",\n               Sum(CASE\n                     WHEN ( cs_ship_date_sk - cs_sold_date_sk > 30 )\n                          AND ( cs_ship_date_sk - cs_sold_date_sk <= 60 ) THEN 1\n                     ELSE 0\n                   END) AS \"31-60 days\",\n               Sum(CASE\n                     WHEN ( cs_ship_date_sk - cs_sold_date_sk > 60 )\n                          AND ( cs_ship_date_sk - cs_sold_date_sk <= 90 ) THEN 1\n                     ELSE 0\n                   END) AS \"61-90 days\",\n               Sum(CASE\n                     WHEN ( cs_ship_date_sk - cs_sold_date_sk > 90 )\n                          AND ( cs_ship_date_sk - cs_sold_date_sk <= 120 ) THEN\n                     1\n                     ELSE 0\n                   END) AS \"91-120 days\",\n               Sum(CASE\n                     WHEN ( cs_ship_date_sk - cs_sold_date_sk > 120 ) THEN 1\n                     ELSE 0\n                   END) AS \">120 days\"\nFROM   catalog_sales,\n       warehouse,\n       ship_mode,\n       call_center,\n       date_dim\nWHERE  d_month_seq BETWEEN 1200 AND 1200 + 11\n       AND cs_ship_date_sk = d_date_sk\n       AND cs_warehouse_sk = w_warehouse_sk\n       AND cs_ship_mode_sk = sm_ship_mode_sk\n       AND cs_call_center_sk = cc_call_center_sk\nGROUP  BY SUBSTRING(w_warehouse_name, 1, 20),\n          sm_type,\n          cc_name\nORDER  BY SUBSTRING(w_warehouse_name, 1, 20),\n          sm_type,\n          cc_name\nLIMIT 100;\nSELECT\n  SUBSTRING(\"warehouse\".\"w_warehouse_name\", 1, 20) AS \"_col_0\",\n  \"ship_mode\".\"sm_type\" AS \"sm_type\",\n  \"call_center\".\"cc_name\" AS \"cc_name\",\n  SUM(\n    CASE\n      WHEN \"catalog_sales\".\"cs_ship_date_sk\" - \"catalog_sales\".\"cs_sold_date_sk\" <= 30\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"30 days\",\n  SUM(\n    CASE\n      WHEN \"catalog_sales\".\"cs_ship_date_sk\" - \"catalog_sales\".\"cs_sold_date_sk\" <= 60\n      AND \"catalog_sales\".\"cs_ship_date_sk\" - \"catalog_sales\".\"cs_sold_date_sk\" > 30\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"31-60 days\",\n  SUM(\n    CASE\n      WHEN \"catalog_sales\".\"cs_ship_date_sk\" - \"catalog_sales\".\"cs_sold_date_sk\" <= 90\n      AND \"catalog_sales\".\"cs_ship_date_sk\" - \"catalog_sales\".\"cs_sold_date_sk\" > 60\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"61-90 days\",\n  SUM(\n    CASE\n      WHEN \"catalog_sales\".\"cs_ship_date_sk\" - \"catalog_sales\".\"cs_sold_date_sk\" <= 120\n      AND \"catalog_sales\".\"cs_ship_date_sk\" - \"catalog_sales\".\"cs_sold_date_sk\" > 90\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"91-120 days\",\n  SUM(\n    CASE\n      WHEN \"catalog_sales\".\"cs_ship_date_sk\" - \"catalog_sales\".\"cs_sold_date_sk\" > 120\n      THEN 1\n      ELSE 0\n    END\n  ) AS \">120 days\"\nFROM \"catalog_sales\" AS \"catalog_sales\"\nJOIN \"call_center\" AS \"call_center\"\n  ON \"call_center\".\"cc_call_center_sk\" = \"catalog_sales\".\"cs_call_center_sk\"\nJOIN \"date_dim\" AS \"date_dim\"\n  ON \"catalog_sales\".\"cs_ship_date_sk\" = \"date_dim\".\"d_date_sk\"\n  AND \"date_dim\".\"d_month_seq\" <= 1211\n  AND \"date_dim\".\"d_month_seq\" >= 1200\nJOIN \"ship_mode\" AS \"ship_mode\"\n  ON \"catalog_sales\".\"cs_ship_mode_sk\" = \"ship_mode\".\"sm_ship_mode_sk\"\nJOIN \"warehouse\" AS \"warehouse\"\n  ON \"catalog_sales\".\"cs_warehouse_sk\" = \"warehouse\".\"w_warehouse_sk\"\nGROUP BY\n  SUBSTRING(\"warehouse\".\"w_warehouse_name\", 1, 20),\n  \"ship_mode\".\"sm_type\",\n  \"call_center\".\"cc_name\"\nORDER BY\n  \"_col_0\",\n  \"sm_type\",\n  \"cc_name\"\nLIMIT 100;\n\n"
  },
  {
    "path": "tests/fixtures/optimizer/tpc-h/tpc-h.sql",
    "content": "--------------------------------------\n-- TPC-H 1\n--------------------------------------\nselect\n        l_returnflag,\n        l_linestatus,\n        sum(l_quantity) as sum_qty,\n        sum(l_extendedprice) as sum_base_price,\n        sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,\n        sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,\n        avg(l_quantity) as avg_qty,\n        avg(l_extendedprice) as avg_price,\n        avg(l_discount) as avg_disc,\n        count(*) as count_order\nfrom\n        lineitem\nwhere\n        CAST(l_shipdate AS DATE) <= date '1998-12-01' - interval '90' day\ngroup by\n        l_returnflag,\n        l_linestatus\norder by\n        l_returnflag,\n        l_linestatus;\nSELECT\n  \"lineitem\".\"l_returnflag\" AS \"l_returnflag\",\n  \"lineitem\".\"l_linestatus\" AS \"l_linestatus\",\n  SUM(\"lineitem\".\"l_quantity\") AS \"sum_qty\",\n  SUM(\"lineitem\".\"l_extendedprice\") AS \"sum_base_price\",\n  SUM(\"lineitem\".\"l_extendedprice\" * (\n    1 - \"lineitem\".\"l_discount\"\n  )) AS \"sum_disc_price\",\n  SUM(\n    \"lineitem\".\"l_extendedprice\" * (\n      1 - \"lineitem\".\"l_discount\"\n    ) * (\n      1 + \"lineitem\".\"l_tax\"\n    )\n  ) AS \"sum_charge\",\n  AVG(\"lineitem\".\"l_quantity\") AS \"avg_qty\",\n  AVG(\"lineitem\".\"l_extendedprice\") AS \"avg_price\",\n  AVG(\"lineitem\".\"l_discount\") AS \"avg_disc\",\n  COUNT(*) AS \"count_order\"\nFROM \"lineitem\" AS \"lineitem\"\nWHERE\n  CAST(\"lineitem\".\"l_shipdate\" AS DATE) <= CAST('1998-09-02' AS DATE)\nGROUP BY\n  \"lineitem\".\"l_returnflag\",\n  \"lineitem\".\"l_linestatus\"\nORDER BY\n  \"l_returnflag\",\n  \"l_linestatus\";\n\n--------------------------------------\n-- TPC-H 2\n--------------------------------------\nselect\n   s_acctbal,\n   s_name,\n   n_name,\n   p_partkey,\n   p_mfgr,\n   s_address,\n   s_phone,\n   s_comment\nfrom\n   part,\n   supplier,\n   partsupp,\n   nation,\n   region\nwhere\n   p_partkey = ps_partkey\n   and s_suppkey = ps_suppkey\n   and p_size = 15\n   and p_type like '%BRASS'\n   and s_nationkey = n_nationkey\n   and n_regionkey = r_regionkey\n   and r_name = 'EUROPE'\n   and ps_supplycost = (\n           select\n                   min(ps_supplycost)\n           from\n                   partsupp,\n                   supplier,\n                   nation,\n                   region\n           where\n                   p_partkey = ps_partkey\n                   and s_suppkey = ps_suppkey\n                   and s_nationkey = n_nationkey\n                   and n_regionkey = r_regionkey\n                   and r_name = 'EUROPE'\n   )\norder by\n   s_acctbal desc,\n   n_name,\n   s_name,\n   p_partkey\nlimit\n   100;\nWITH \"partsupp_2\" AS (\n  SELECT\n    \"partsupp\".\"ps_partkey\" AS \"ps_partkey\",\n    \"partsupp\".\"ps_suppkey\" AS \"ps_suppkey\",\n    \"partsupp\".\"ps_supplycost\" AS \"ps_supplycost\"\n  FROM \"partsupp\" AS \"partsupp\"\n), \"region_2\" AS (\n  SELECT\n    \"region\".\"r_regionkey\" AS \"r_regionkey\",\n    \"region\".\"r_name\" AS \"r_name\"\n  FROM \"region\" AS \"region\"\n  WHERE\n    \"region\".\"r_name\" = 'EUROPE'\n), \"_u_0\" AS (\n  SELECT\n    MIN(\"partsupp\".\"ps_supplycost\") AS \"_col_0\",\n    \"partsupp\".\"ps_partkey\" AS \"_u_1\"\n  FROM \"partsupp_2\" AS \"partsupp\"\n  JOIN \"supplier\" AS \"supplier\"\n    ON \"partsupp\".\"ps_suppkey\" = \"supplier\".\"s_suppkey\"\n  JOIN \"nation\" AS \"nation\"\n    ON \"nation\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\n  JOIN \"region_2\" AS \"region\"\n    ON \"nation\".\"n_regionkey\" = \"region\".\"r_regionkey\"\n  GROUP BY\n    \"partsupp\".\"ps_partkey\"\n)\nSELECT\n  \"supplier\".\"s_acctbal\" AS \"s_acctbal\",\n  \"supplier\".\"s_name\" AS \"s_name\",\n  \"nation\".\"n_name\" AS \"n_name\",\n  \"part\".\"p_partkey\" AS \"p_partkey\",\n  \"part\".\"p_mfgr\" AS \"p_mfgr\",\n  \"supplier\".\"s_address\" AS \"s_address\",\n  \"supplier\".\"s_phone\" AS \"s_phone\",\n  \"supplier\".\"s_comment\" AS \"s_comment\"\nFROM \"part\" AS \"part\"\nCROSS JOIN \"supplier\" AS \"supplier\"\nJOIN \"partsupp_2\" AS \"partsupp\"\n  ON \"part\".\"p_partkey\" = \"partsupp\".\"ps_partkey\"\n  AND \"partsupp\".\"ps_suppkey\" = \"supplier\".\"s_suppkey\"\nJOIN \"nation\" AS \"nation\"\n  ON \"nation\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\nJOIN \"region_2\" AS \"region\"\n  ON \"nation\".\"n_regionkey\" = \"region\".\"r_regionkey\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"part\".\"p_partkey\"\nWHERE\n  \"_u_0\".\"_col_0\" = \"partsupp\".\"ps_supplycost\"\n  AND \"part\".\"p_size\" = 15\n  AND \"part\".\"p_type\" LIKE '%BRASS'\nORDER BY\n  \"s_acctbal\" DESC,\n  \"n_name\",\n  \"s_name\",\n  \"p_partkey\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-H 3\n--------------------------------------\nselect\n        l_orderkey,\n        sum(l_extendedprice * (1 - l_discount)) as revenue,\n        CAST(o_orderdate AS STRING) AS o_orderdate,\n        o_shippriority\nfrom\n        customer,\n        orders,\n        lineitem\nwhere\n        c_mktsegment = 'BUILDING'\n        and c_custkey = o_custkey\n        and l_orderkey = o_orderkey\n        and o_orderdate < '1995-03-15'\n        and l_shipdate > '1995-03-15'\ngroup by\n        l_orderkey,\n        o_orderdate,\n        o_shippriority\norder by\n        revenue desc,\n        o_orderdate\nlimit\n        10;\nSELECT\n  \"lineitem\".\"l_orderkey\" AS \"l_orderkey\",\n  SUM(\"lineitem\".\"l_extendedprice\" * (\n    1 - \"lineitem\".\"l_discount\"\n  )) AS \"revenue\",\n  \"orders\".\"o_orderdate\" AS \"o_orderdate\",\n  \"orders\".\"o_shippriority\" AS \"o_shippriority\"\nFROM \"customer\" AS \"customer\"\nJOIN \"orders\" AS \"orders\"\n  ON \"customer\".\"c_custkey\" = \"orders\".\"o_custkey\"\n  AND \"orders\".\"o_orderdate\" < '1995-03-15'\nJOIN \"lineitem\" AS \"lineitem\"\n  ON \"lineitem\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\n  AND \"lineitem\".\"l_shipdate\" > '1995-03-15'\nWHERE\n  \"customer\".\"c_mktsegment\" = 'BUILDING'\nGROUP BY\n  \"lineitem\".\"l_orderkey\",\n  \"orders\".\"o_orderdate\",\n  \"orders\".\"o_shippriority\"\nORDER BY\n  \"revenue\" DESC,\n  \"o_orderdate\"\nLIMIT 10;\n\n--------------------------------------\n-- TPC-H 4\n--------------------------------------\nselect\n        o_orderpriority,\n        count(*) as order_count\nfrom\n        orders\nwhere\n        CAST(o_orderdate AS DATE) >= date '1993-07-01'\n        and CAST(o_orderdate AS DATE) < date '1993-07-01' + interval '3' month\n        and exists (\n                select\n                        *\n                from\n                        lineitem\n                where\n                        l_orderkey = o_orderkey\n                        and l_commitdate < l_receiptdate\n        )\ngroup by\n        o_orderpriority\norder by\n        o_orderpriority;\nWITH \"_u_0\" AS (\n  SELECT\n    \"lineitem\".\"l_orderkey\" AS \"l_orderkey\"\n  FROM \"lineitem\" AS \"lineitem\"\n  WHERE\n    \"lineitem\".\"l_commitdate\" < \"lineitem\".\"l_receiptdate\"\n  GROUP BY\n    \"lineitem\".\"l_orderkey\"\n)\nSELECT\n  \"orders\".\"o_orderpriority\" AS \"o_orderpriority\",\n  COUNT(*) AS \"order_count\"\nFROM \"orders\" AS \"orders\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\nWHERE\n  CAST(\"orders\".\"o_orderdate\" AS DATE) < CAST('1993-10-01' AS DATE)\n  AND CAST(\"orders\".\"o_orderdate\" AS DATE) >= CAST('1993-07-01' AS DATE)\n  AND NOT \"_u_0\".\"l_orderkey\" IS NULL\nGROUP BY\n  \"orders\".\"o_orderpriority\"\nORDER BY\n  \"o_orderpriority\";\n\n--------------------------------------\n-- TPC-H 5\n--------------------------------------\nselect\n        n_name,\n        sum(l_extendedprice * (1 - l_discount)) as revenue\nfrom\n        customer,\n        orders,\n        lineitem,\n        supplier,\n        nation,\n        region\nwhere\n        c_custkey = o_custkey\n        and l_orderkey = o_orderkey\n        and l_suppkey = s_suppkey\n        and c_nationkey = s_nationkey\n        and s_nationkey = n_nationkey\n        and n_regionkey = r_regionkey\n        and r_name = 'ASIA'\n        and CAST(o_orderdate AS DATE) >= date '1994-01-01'\n        and CAST(o_orderdate AS DATE) < date '1994-01-01' + interval '1' year\ngroup by\n        n_name\norder by\n        revenue desc;\nSELECT\n  \"nation\".\"n_name\" AS \"n_name\",\n  SUM(\"lineitem\".\"l_extendedprice\" * (\n    1 - \"lineitem\".\"l_discount\"\n  )) AS \"revenue\"\nFROM \"customer\" AS \"customer\"\nJOIN \"orders\" AS \"orders\"\n  ON \"customer\".\"c_custkey\" = \"orders\".\"o_custkey\"\n  AND CAST(\"orders\".\"o_orderdate\" AS DATE) < CAST('1995-01-01' AS DATE)\n  AND CAST(\"orders\".\"o_orderdate\" AS DATE) >= CAST('1994-01-01' AS DATE)\nJOIN \"lineitem\" AS \"lineitem\"\n  ON \"lineitem\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\nJOIN \"supplier\" AS \"supplier\"\n  ON \"customer\".\"c_nationkey\" = \"supplier\".\"s_nationkey\"\n  AND \"lineitem\".\"l_suppkey\" = \"supplier\".\"s_suppkey\"\nJOIN \"nation\" AS \"nation\"\n  ON \"nation\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\nJOIN \"region\" AS \"region\"\n  ON \"nation\".\"n_regionkey\" = \"region\".\"r_regionkey\" AND \"region\".\"r_name\" = 'ASIA'\nGROUP BY\n  \"nation\".\"n_name\"\nORDER BY\n  \"revenue\" DESC;\n\n--------------------------------------\n-- TPC-H 6\n--------------------------------------\nselect\n        sum(l_extendedprice * l_discount) as revenue\nfrom\n        lineitem\nwhere\n        CAST(l_shipdate AS DATE) >= date '1994-01-01'\n        and CAST(l_shipdate AS DATE) < date '1994-01-01' + interval '1' year\n        and l_discount between 0.06 - 0.01 and 0.06 + 0.01\n        and l_quantity < 24;\nSELECT\n  SUM(\"lineitem\".\"l_extendedprice\" * \"lineitem\".\"l_discount\") AS \"revenue\"\nFROM \"lineitem\" AS \"lineitem\"\nWHERE\n  \"lineitem\".\"l_discount\" <= 0.07\n  AND \"lineitem\".\"l_discount\" >= 0.05\n  AND \"lineitem\".\"l_quantity\" < 24\n  AND CAST(\"lineitem\".\"l_shipdate\" AS DATE) < CAST('1995-01-01' AS DATE)\n  AND CAST(\"lineitem\".\"l_shipdate\" AS DATE) >= CAST('1994-01-01' AS DATE);\n\n--------------------------------------\n-- TPC-H 7\n--------------------------------------\nselect\n        supp_nation,\n        cust_nation,\n        l_year,\n        sum(volume) as revenue\nfrom\n        (\n                select\n                        n1.n_name as supp_nation,\n                        n2.n_name as cust_nation,\n                        extract(year from cast(l_shipdate as date)) as l_year,\n                        l_extendedprice * (1 - l_discount) as volume\n                from\n                        supplier,\n                        lineitem,\n                        orders,\n                        customer,\n                        nation n1,\n                        nation n2\n                where\n                        s_suppkey = l_suppkey\n                        and o_orderkey = l_orderkey\n                        and c_custkey = o_custkey\n                        and s_nationkey = n1.n_nationkey\n                        and c_nationkey = n2.n_nationkey\n                        and (\n                                (n1.n_name = 'FRANCE' and n2.n_name = 'GERMANY')\n                                or (n1.n_name = 'GERMANY' and n2.n_name = 'FRANCE')\n                        )\n                        and CAST(l_shipdate AS DATE) between date '1995-01-01' and date '1996-12-31'\n        ) as shipping\ngroup by\n        supp_nation,\n        cust_nation,\n        l_year\norder by\n        supp_nation,\n        cust_nation,\n        l_year;\nSELECT\n  \"n1\".\"n_name\" AS \"supp_nation\",\n  \"n2\".\"n_name\" AS \"cust_nation\",\n  EXTRACT(YEAR FROM CAST(\"lineitem\".\"l_shipdate\" AS DATE)) AS \"l_year\",\n  SUM(\"lineitem\".\"l_extendedprice\" * (\n    1 - \"lineitem\".\"l_discount\"\n  )) AS \"revenue\"\nFROM \"supplier\" AS \"supplier\"\nJOIN \"lineitem\" AS \"lineitem\"\n  ON \"lineitem\".\"l_suppkey\" = \"supplier\".\"s_suppkey\"\n  AND CAST(\"lineitem\".\"l_shipdate\" AS DATE) <= CAST('1996-12-31' AS DATE)\n  AND CAST(\"lineitem\".\"l_shipdate\" AS DATE) >= CAST('1995-01-01' AS DATE)\nJOIN \"nation\" AS \"n1\"\n  ON (\n    \"n1\".\"n_name\" = 'FRANCE' OR \"n1\".\"n_name\" = 'GERMANY'\n  )\n  AND \"n1\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\nJOIN \"orders\" AS \"orders\"\n  ON \"lineitem\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_custkey\" = \"orders\".\"o_custkey\"\nJOIN \"nation\" AS \"n2\"\n  ON \"customer\".\"c_nationkey\" = \"n2\".\"n_nationkey\"\n  AND (\n    \"n1\".\"n_name\" = 'FRANCE' OR \"n2\".\"n_name\" = 'FRANCE'\n  )\n  AND (\n    \"n1\".\"n_name\" = 'GERMANY' OR \"n2\".\"n_name\" = 'GERMANY'\n  )\n  AND (\n    \"n2\".\"n_name\" = 'FRANCE' OR \"n2\".\"n_name\" = 'GERMANY'\n  )\nGROUP BY\n  \"n1\".\"n_name\",\n  \"n2\".\"n_name\",\n  EXTRACT(YEAR FROM CAST(\"lineitem\".\"l_shipdate\" AS DATE))\nORDER BY\n  \"supp_nation\",\n  \"cust_nation\",\n  \"l_year\";\n\n--------------------------------------\n-- TPC-H 8\n--------------------------------------\nselect\n        o_year,\n        sum(case\n                when nation = 'BRAZIL' then volume\n                else 0\n        end) / sum(volume) as mkt_share\nfrom\n        (\n                select\n                        extract(YEAR from cast(o_orderdate as date)) as o_year,\n                        l_extendedprice * (1 - l_discount) as volume,\n                        n2.n_name as nation\n                from\n                        part,\n                        supplier,\n                        lineitem,\n                        orders,\n                        customer,\n                        nation n1,\n                        nation n2,\n                        region\n                where\n                        p_partkey = l_partkey\n                        and s_suppkey = l_suppkey\n                        and l_orderkey = o_orderkey\n                        and o_custkey = c_custkey\n                        and c_nationkey = n1.n_nationkey\n                        and n1.n_regionkey = r_regionkey\n                        and r_name = 'AMERICA'\n                        and s_nationkey = n2.n_nationkey\n                        and CAST(o_orderdate AS DATE) between date '1995-01-01' and date '1996-12-31'\n                        and p_type = 'ECONOMY ANODIZED STEEL'\n        ) as all_nations\ngroup by\n        o_year\norder by\n        o_year;\nSELECT\n  EXTRACT(YEAR FROM CAST(\"orders\".\"o_orderdate\" AS DATE)) AS \"o_year\",\n  SUM(\n    CASE\n      WHEN \"n2\".\"n_name\" = 'BRAZIL'\n      THEN \"lineitem\".\"l_extendedprice\" * (\n        1 - \"lineitem\".\"l_discount\"\n      )\n      ELSE 0\n    END\n  ) / SUM(\"lineitem\".\"l_extendedprice\" * (\n    1 - \"lineitem\".\"l_discount\"\n  )) AS \"mkt_share\"\nFROM \"part\" AS \"part\"\nJOIN \"lineitem\" AS \"lineitem\"\n  ON \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\nJOIN \"orders\" AS \"orders\"\n  ON \"lineitem\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\n  AND CAST(\"orders\".\"o_orderdate\" AS DATE) <= CAST('1996-12-31' AS DATE)\n  AND CAST(\"orders\".\"o_orderdate\" AS DATE) >= CAST('1995-01-01' AS DATE)\nJOIN \"supplier\" AS \"supplier\"\n  ON \"lineitem\".\"l_suppkey\" = \"supplier\".\"s_suppkey\"\nJOIN \"customer\" AS \"customer\"\n  ON \"customer\".\"c_custkey\" = \"orders\".\"o_custkey\"\nJOIN \"nation\" AS \"n2\"\n  ON \"n2\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\nJOIN \"nation\" AS \"n1\"\n  ON \"customer\".\"c_nationkey\" = \"n1\".\"n_nationkey\"\nJOIN \"region\" AS \"region\"\n  ON \"n1\".\"n_regionkey\" = \"region\".\"r_regionkey\" AND \"region\".\"r_name\" = 'AMERICA'\nWHERE\n  \"part\".\"p_type\" = 'ECONOMY ANODIZED STEEL'\nGROUP BY\n  EXTRACT(YEAR FROM CAST(\"orders\".\"o_orderdate\" AS DATE))\nORDER BY\n  \"o_year\";\n\n--------------------------------------\n-- TPC-H 9\n--------------------------------------\nselect\n        nation,\n        o_year,\n        sum(amount) as sum_profit\nfrom\n        (\n                select\n                        n_name as nation,\n                        extract(year from cast(o_orderdate as date)) as o_year,\n                        l_extendedprice * (1 - l_discount) - ps_supplycost * l_quantity as amount\n                from\n                        part,\n                        supplier,\n                        lineitem,\n                        partsupp,\n                        orders,\n                        nation\n                where\n                        s_suppkey = l_suppkey\n                        and ps_suppkey = l_suppkey\n                        and ps_partkey = l_partkey\n                        and p_partkey = l_partkey\n                        and o_orderkey = l_orderkey\n                        and s_nationkey = n_nationkey\n                        and p_name like '%green%'\n        ) as profit\ngroup by\n        nation,\n        o_year\norder by\n        nation,\n        o_year desc;\nSELECT\n  \"nation\".\"n_name\" AS \"nation\",\n  EXTRACT(YEAR FROM CAST(\"orders\".\"o_orderdate\" AS DATE)) AS \"o_year\",\n  SUM(\n    \"lineitem\".\"l_extendedprice\" * (\n      1 - \"lineitem\".\"l_discount\"\n    ) - \"partsupp\".\"ps_supplycost\" * \"lineitem\".\"l_quantity\"\n  ) AS \"sum_profit\"\nFROM \"part\" AS \"part\"\nJOIN \"lineitem\" AS \"lineitem\"\n  ON \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\nJOIN \"orders\" AS \"orders\"\n  ON \"lineitem\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\nJOIN \"partsupp\" AS \"partsupp\"\n  ON \"lineitem\".\"l_partkey\" = \"partsupp\".\"ps_partkey\"\n  AND \"lineitem\".\"l_suppkey\" = \"partsupp\".\"ps_suppkey\"\nJOIN \"supplier\" AS \"supplier\"\n  ON \"lineitem\".\"l_suppkey\" = \"supplier\".\"s_suppkey\"\nJOIN \"nation\" AS \"nation\"\n  ON \"nation\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\nWHERE\n  \"part\".\"p_name\" LIKE '%green%'\nGROUP BY\n  \"nation\".\"n_name\",\n  EXTRACT(YEAR FROM CAST(\"orders\".\"o_orderdate\" AS DATE))\nORDER BY\n  \"nation\",\n  \"o_year\" DESC;\n\n--------------------------------------\n-- TPC-H 10\n--------------------------------------\nselect\n        c_custkey,\n        c_name,\n        sum(l_extendedprice * (1 - l_discount)) as revenue,\n        c_acctbal,\n        n_name,\n        c_address,\n        c_phone,\n        c_comment\nfrom\n        customer,\n        orders,\n        lineitem,\n        nation\nwhere\n        c_custkey = o_custkey\n        and l_orderkey = o_orderkey\n        and CAST(o_orderdate AS DATE) >= date '1993-10-01'\n        and CAST(o_orderdate AS DATE) < date '1993-10-01' + interval '3' month\n        and l_returnflag = 'R'\n        and c_nationkey = n_nationkey\ngroup by\n        c_custkey,\n        c_name,\n        c_acctbal,\n        c_phone,\n        n_name,\n        c_address,\n        c_comment\norder by\n        revenue desc\nlimit\n        20;\nSELECT\n  \"customer\".\"c_custkey\" AS \"c_custkey\",\n  \"customer\".\"c_name\" AS \"c_name\",\n  SUM(\"lineitem\".\"l_extendedprice\" * (\n    1 - \"lineitem\".\"l_discount\"\n  )) AS \"revenue\",\n  \"customer\".\"c_acctbal\" AS \"c_acctbal\",\n  \"nation\".\"n_name\" AS \"n_name\",\n  \"customer\".\"c_address\" AS \"c_address\",\n  \"customer\".\"c_phone\" AS \"c_phone\",\n  \"customer\".\"c_comment\" AS \"c_comment\"\nFROM \"customer\" AS \"customer\"\nJOIN \"nation\" AS \"nation\"\n  ON \"customer\".\"c_nationkey\" = \"nation\".\"n_nationkey\"\nJOIN \"orders\" AS \"orders\"\n  ON \"customer\".\"c_custkey\" = \"orders\".\"o_custkey\"\n  AND CAST(\"orders\".\"o_orderdate\" AS DATE) < CAST('1994-01-01' AS DATE)\n  AND CAST(\"orders\".\"o_orderdate\" AS DATE) >= CAST('1993-10-01' AS DATE)\nJOIN \"lineitem\" AS \"lineitem\"\n  ON \"lineitem\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\n  AND \"lineitem\".\"l_returnflag\" = 'R'\nGROUP BY\n  \"customer\".\"c_custkey\",\n  \"customer\".\"c_name\",\n  \"customer\".\"c_acctbal\",\n  \"customer\".\"c_phone\",\n  \"nation\".\"n_name\",\n  \"customer\".\"c_address\",\n  \"customer\".\"c_comment\"\nORDER BY\n  \"revenue\" DESC\nLIMIT 20;\n\n--------------------------------------\n-- TPC-H 11\n--------------------------------------\nselect\n        ps_partkey,\n        sum(ps_supplycost * ps_availqty) as value\nfrom\n        partsupp,\n        supplier,\n        nation\nwhere\n        ps_suppkey = s_suppkey\n        and s_nationkey = n_nationkey\n        and n_name = 'GERMANY'\ngroup by\n        ps_partkey having\n                sum(ps_supplycost * ps_availqty) > (\n                        select\n                                sum(ps_supplycost * ps_availqty) * 0.0001\n                        from\n                                partsupp,\n                                supplier,\n                                nation\n                        where\n                                ps_suppkey = s_suppkey\n                                and s_nationkey = n_nationkey\n                                and n_name = 'GERMANY'\n                )\norder by\n        value desc;\nWITH \"supplier_2\" AS (\n  SELECT\n    \"supplier\".\"s_suppkey\" AS \"s_suppkey\",\n    \"supplier\".\"s_nationkey\" AS \"s_nationkey\"\n  FROM \"supplier\" AS \"supplier\"\n), \"nation_2\" AS (\n  SELECT\n    \"nation\".\"n_nationkey\" AS \"n_nationkey\",\n    \"nation\".\"n_name\" AS \"n_name\"\n  FROM \"nation\" AS \"nation\"\n  WHERE\n    \"nation\".\"n_name\" = 'GERMANY'\n), \"_u_0\" AS (\n  SELECT\n    SUM(\"partsupp\".\"ps_supplycost\" * \"partsupp\".\"ps_availqty\") * 0.0001 AS \"_col_0\"\n  FROM \"partsupp\" AS \"partsupp\"\n  JOIN \"supplier_2\" AS \"supplier\"\n    ON \"partsupp\".\"ps_suppkey\" = \"supplier\".\"s_suppkey\"\n  JOIN \"nation_2\" AS \"nation\"\n    ON \"nation\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\n)\nSELECT\n  \"partsupp\".\"ps_partkey\" AS \"ps_partkey\",\n  SUM(\"partsupp\".\"ps_supplycost\" * \"partsupp\".\"ps_availqty\") AS \"value\"\nFROM \"partsupp\" AS \"partsupp\"\nCROSS JOIN \"_u_0\" AS \"_u_0\"\nJOIN \"supplier_2\" AS \"supplier\"\n  ON \"partsupp\".\"ps_suppkey\" = \"supplier\".\"s_suppkey\"\nJOIN \"nation_2\" AS \"nation\"\n  ON \"nation\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\nGROUP BY\n  \"partsupp\".\"ps_partkey\"\nHAVING\n  MAX(\"_u_0\".\"_col_0\") < SUM(\"partsupp\".\"ps_supplycost\" * \"partsupp\".\"ps_availqty\")\nORDER BY\n  \"value\" DESC;\n\n--------------------------------------\n-- TPC-H 12\n--------------------------------------\nselect\n        l_shipmode,\n        sum(case\n                when o_orderpriority = '1-URGENT'\n                        or o_orderpriority = '2-HIGH'\n                        then 1\n                else 0\n        end) as high_line_count,\n        sum(case\n                when o_orderpriority <> '1-URGENT'\n                        and o_orderpriority <> '2-HIGH'\n                        then 1\n                else 0\n        end) as low_line_count\nfrom\n        orders,\n        lineitem\nwhere\n        o_orderkey = l_orderkey\n        and l_shipmode in ('MAIL', 'SHIP')\n        and l_commitdate < l_receiptdate\n        and l_shipdate < l_commitdate\n        and CAST(l_receiptdate AS DATE) >= date '1994-01-01'\n        and CAST(l_receiptdate AS DATE) < date '1994-01-01' + interval '1' year\ngroup by\n        l_shipmode\norder by\n        l_shipmode;\nSELECT\n  \"lineitem\".\"l_shipmode\" AS \"l_shipmode\",\n  SUM(\n    CASE\n      WHEN \"orders\".\"o_orderpriority\" = '1-URGENT' OR \"orders\".\"o_orderpriority\" = '2-HIGH'\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"high_line_count\",\n  SUM(\n    CASE\n      WHEN \"orders\".\"o_orderpriority\" <> '1-URGENT'\n      AND \"orders\".\"o_orderpriority\" <> '2-HIGH'\n      THEN 1\n      ELSE 0\n    END\n  ) AS \"low_line_count\"\nFROM \"orders\" AS \"orders\"\nJOIN \"lineitem\" AS \"lineitem\"\n  ON \"lineitem\".\"l_commitdate\" < \"lineitem\".\"l_receiptdate\"\n  AND \"lineitem\".\"l_commitdate\" > \"lineitem\".\"l_shipdate\"\n  AND \"lineitem\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\n  AND \"lineitem\".\"l_shipmode\" IN ('MAIL', 'SHIP')\n  AND CAST(\"lineitem\".\"l_receiptdate\" AS DATE) < CAST('1995-01-01' AS DATE)\n  AND CAST(\"lineitem\".\"l_receiptdate\" AS DATE) >= CAST('1994-01-01' AS DATE)\nGROUP BY\n  \"lineitem\".\"l_shipmode\"\nORDER BY\n  \"l_shipmode\";\n\n--------------------------------------\n-- TPC-H 13\n--------------------------------------\nselect\n        c_count,\n        count(*) as custdist\nfrom\n        (\n                select\n                        c_custkey,\n                        count(o_orderkey)\n                from\n                        customer left outer join orders on\n                                c_custkey = o_custkey\n                                and o_comment not like '%special%requests%'\n                group by\n                        c_custkey\n        ) as c_orders (c_custkey, c_count)\ngroup by\n        c_count\norder by\n        custdist desc,\n        c_count desc;\nWITH \"orders_2\" AS (\n  SELECT\n    \"orders\".\"o_orderkey\" AS \"o_orderkey\",\n    \"orders\".\"o_custkey\" AS \"o_custkey\",\n    \"orders\".\"o_comment\" AS \"o_comment\"\n  FROM \"orders\" AS \"orders\"\n  WHERE\n    NOT \"orders\".\"o_comment\" LIKE '%special%requests%'\n), \"c_orders\" AS (\n  SELECT\n    COUNT(\"orders\".\"o_orderkey\") AS \"c_count\"\n  FROM \"customer\" AS \"customer\"\n  LEFT JOIN \"orders_2\" AS \"orders\"\n    ON \"customer\".\"c_custkey\" = \"orders\".\"o_custkey\"\n  GROUP BY\n    \"customer\".\"c_custkey\"\n)\nSELECT\n  \"c_orders\".\"c_count\" AS \"c_count\",\n  COUNT(*) AS \"custdist\"\nFROM \"c_orders\" AS \"c_orders\"\nGROUP BY\n  \"c_orders\".\"c_count\"\nORDER BY\n  \"custdist\" DESC,\n  \"c_count\" DESC;\n\n--------------------------------------\n-- TPC-H 14\n--------------------------------------\nselect\n        100.00 * sum(case\n                when p_type like 'PROMO%'\n                        then l_extendedprice * (1 - l_discount)\n                else 0\n        end) / sum(l_extendedprice * (1 - l_discount)) as promo_revenue\nfrom\n        lineitem,\n        part\nwhere\n        l_partkey = p_partkey\n        and CAST(l_shipdate AS DATE) >= date '1995-09-01'\n        and CAST(l_shipdate AS DATE) < date '1995-09-01' + interval '1' month;\nSELECT\n  100.00 * SUM(\n    CASE\n      WHEN \"part\".\"p_type\" LIKE 'PROMO%'\n      THEN \"lineitem\".\"l_extendedprice\" * (\n        1 - \"lineitem\".\"l_discount\"\n      )\n      ELSE 0\n    END\n  ) / SUM(\"lineitem\".\"l_extendedprice\" * (\n    1 - \"lineitem\".\"l_discount\"\n  )) AS \"promo_revenue\"\nFROM \"lineitem\" AS \"lineitem\"\nJOIN \"part\" AS \"part\"\n  ON \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\nWHERE\n  CAST(\"lineitem\".\"l_shipdate\" AS DATE) < CAST('1995-10-01' AS DATE)\n  AND CAST(\"lineitem\".\"l_shipdate\" AS DATE) >= CAST('1995-09-01' AS DATE);\n\n--------------------------------------\n-- TPC-H 15\n--------------------------------------\nwith revenue (supplier_no, total_revenue) as (\n        select\n                l_suppkey,\n                sum(l_extendedprice * (1 - l_discount))\n        from\n                lineitem\n        where\n                CAST(l_shipdate AS DATE) >= date '1996-01-01'\n                and CAST(l_shipdate AS DATE) < date '1996-01-01' + interval '3' month\n        group by\n                l_suppkey)\nselect\n        s_suppkey,\n        s_name,\n        s_address,\n        s_phone,\n        total_revenue\nfrom\n        supplier,\n        revenue\nwhere\n        s_suppkey = supplier_no\n        and total_revenue = (\n                select\n                        max(total_revenue)\n                from\n                        revenue\n        )\norder by\n        s_suppkey;\nWITH \"revenue\" AS (\n  SELECT\n    \"lineitem\".\"l_suppkey\" AS \"supplier_no\",\n    SUM(\"lineitem\".\"l_extendedprice\" * (\n      1 - \"lineitem\".\"l_discount\"\n    )) AS \"total_revenue\"\n  FROM \"lineitem\" AS \"lineitem\"\n  WHERE\n    CAST(\"lineitem\".\"l_shipdate\" AS DATE) < CAST('1996-04-01' AS DATE)\n    AND CAST(\"lineitem\".\"l_shipdate\" AS DATE) >= CAST('1996-01-01' AS DATE)\n  GROUP BY\n    \"lineitem\".\"l_suppkey\"\n), \"_u_0\" AS (\n  SELECT\n    MAX(\"revenue\".\"total_revenue\") AS \"_col_0\"\n  FROM \"revenue\" AS \"revenue\"\n)\nSELECT\n  \"supplier\".\"s_suppkey\" AS \"s_suppkey\",\n  \"supplier\".\"s_name\" AS \"s_name\",\n  \"supplier\".\"s_address\" AS \"s_address\",\n  \"supplier\".\"s_phone\" AS \"s_phone\",\n  \"revenue\".\"total_revenue\" AS \"total_revenue\"\nFROM \"supplier\" AS \"supplier\"\nJOIN \"revenue\" AS \"revenue\"\n  ON \"revenue\".\"supplier_no\" = \"supplier\".\"s_suppkey\"\nJOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_col_0\" = \"revenue\".\"total_revenue\"\nORDER BY\n  \"s_suppkey\";\n\n--------------------------------------\n-- TPC-H 16\n--------------------------------------\nselect\n        p_brand,\n        p_type,\n        p_size,\n        count(distinct ps_suppkey) as supplier_cnt\nfrom\n        partsupp,\n        part\nwhere\n        p_partkey = ps_partkey\n        and p_brand <> 'Brand#45'\n        and p_type not like 'MEDIUM POLISHED%'\n        and p_size in (49, 14, 23, 45, 19, 3, 36, 9)\n        and ps_suppkey not in (\n                select\n                        s_suppkey\n                from\n                        supplier\n                where\n                        s_comment like '%Customer%Complaints%'\n        )\ngroup by\n        p_brand,\n        p_type,\n        p_size\norder by\n        supplier_cnt desc,\n        p_brand,\n        p_type,\n        p_size;\nWITH \"_u_0\" AS (\n  SELECT\n    \"supplier\".\"s_suppkey\" AS \"s_suppkey\"\n  FROM \"supplier\" AS \"supplier\"\n  WHERE\n    \"supplier\".\"s_comment\" LIKE '%Customer%Complaints%'\n  GROUP BY\n    \"supplier\".\"s_suppkey\"\n)\nSELECT\n  \"part\".\"p_brand\" AS \"p_brand\",\n  \"part\".\"p_type\" AS \"p_type\",\n  \"part\".\"p_size\" AS \"p_size\",\n  COUNT(DISTINCT \"partsupp\".\"ps_suppkey\") AS \"supplier_cnt\"\nFROM \"partsupp\" AS \"partsupp\"\nJOIN \"part\" AS \"part\"\n  ON \"part\".\"p_brand\" <> 'Brand#45'\n  AND \"part\".\"p_partkey\" = \"partsupp\".\"ps_partkey\"\n  AND \"part\".\"p_size\" IN (49, 14, 23, 45, 19, 3, 36, 9)\n  AND NOT \"part\".\"p_type\" LIKE 'MEDIUM POLISHED%'\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"s_suppkey\" = \"partsupp\".\"ps_suppkey\"\nWHERE\n  \"_u_0\".\"s_suppkey\" IS NULL\nGROUP BY\n  \"part\".\"p_brand\",\n  \"part\".\"p_type\",\n  \"part\".\"p_size\"\nORDER BY\n  \"supplier_cnt\" DESC,\n  \"p_brand\",\n  \"p_type\",\n  \"p_size\";\n\n--------------------------------------\n-- TPC-H 17\n--------------------------------------\nselect\n        sum(l_extendedprice) / 7.0 as avg_yearly\nfrom\n        lineitem,\n        part\nwhere\n        p_partkey = l_partkey\n        and p_brand = 'Brand#23'\n        and p_container = 'MED BOX'\n        and l_quantity < (\n                select\n                        0.2 * avg(l_quantity)\n                from\n                        lineitem\n                where\n                        l_partkey = p_partkey\n        );\nWITH \"_u_0\" AS (\n  SELECT\n    0.2 * AVG(\"lineitem\".\"l_quantity\") AS \"_col_0\",\n    \"lineitem\".\"l_partkey\" AS \"_u_1\"\n  FROM \"lineitem\" AS \"lineitem\"\n  GROUP BY\n    \"lineitem\".\"l_partkey\"\n)\nSELECT\n  SUM(\"lineitem\".\"l_extendedprice\") / 7.0 AS \"avg_yearly\"\nFROM \"lineitem\" AS \"lineitem\"\nJOIN \"part\" AS \"part\"\n  ON \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\n  AND \"part\".\"p_brand\" = 'Brand#23'\n  AND \"part\".\"p_container\" = 'MED BOX'\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_u_1\" = \"part\".\"p_partkey\"\nWHERE\n  \"_u_0\".\"_col_0\" > \"lineitem\".\"l_quantity\";\n\n--------------------------------------\n-- TPC-H 18\n--------------------------------------\nselect\n        c_name,\n        c_custkey,\n        o_orderkey,\n        o_orderdate,\n        o_totalprice,\n        sum(l_quantity) total_quantity\nfrom\n        customer,\n        orders,\n        lineitem\nwhere\n        o_orderkey in (\n                select\n                        l_orderkey\n                from\n                        lineitem\n                group by\n                        l_orderkey having\n                                sum(l_quantity) > 300\n        )\n        and c_custkey = o_custkey\n        and o_orderkey = l_orderkey\ngroup by\n        c_name,\n        c_custkey,\n        o_orderkey,\n        o_orderdate,\n        o_totalprice\norder by\n        o_totalprice desc,\n        o_orderdate\nlimit\n        100;\nWITH \"_u_0\" AS (\n  SELECT\n    \"lineitem\".\"l_orderkey\" AS \"l_orderkey\"\n  FROM \"lineitem\" AS \"lineitem\"\n  GROUP BY\n    \"lineitem\".\"l_orderkey\"\n  HAVING\n    SUM(\"lineitem\".\"l_quantity\") > 300\n)\nSELECT\n  \"customer\".\"c_name\" AS \"c_name\",\n  \"customer\".\"c_custkey\" AS \"c_custkey\",\n  \"orders\".\"o_orderkey\" AS \"o_orderkey\",\n  \"orders\".\"o_orderdate\" AS \"o_orderdate\",\n  \"orders\".\"o_totalprice\" AS \"o_totalprice\",\n  SUM(\"lineitem\".\"l_quantity\") AS \"total_quantity\"\nFROM \"customer\" AS \"customer\"\nJOIN \"orders\" AS \"orders\"\n  ON \"customer\".\"c_custkey\" = \"orders\".\"o_custkey\"\nJOIN \"lineitem\" AS \"lineitem\"\n  ON \"lineitem\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"l_orderkey\" = \"orders\".\"o_orderkey\"\nWHERE\n  NOT \"_u_0\".\"l_orderkey\" IS NULL\nGROUP BY\n  \"customer\".\"c_name\",\n  \"customer\".\"c_custkey\",\n  \"orders\".\"o_orderkey\",\n  \"orders\".\"o_orderdate\",\n  \"orders\".\"o_totalprice\"\nORDER BY\n  \"o_totalprice\" DESC,\n  \"o_orderdate\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-H 19\n--------------------------------------\nselect\n        sum(l_extendedprice* (1 - l_discount)) as revenue\nfrom\n        lineitem,\n        part\nwhere\n        (\n                p_partkey = l_partkey\n                and p_brand = 'Brand#12'\n                and p_container in ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG')\n                and l_quantity >= 1 and l_quantity <= 11\n                and p_size between 1 and 5\n                and l_shipmode in ('AIR', 'AIR REG')\n                and l_shipinstruct = 'DELIVER IN PERSON'\n        )\n        or\n        (\n                p_partkey = l_partkey\n                and p_brand = 'Brand#23'\n                and p_container in ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK')\n                and l_quantity >= 10 and l_quantity <= 20\n                and p_size between 1 and 10\n                and l_shipmode in ('AIR', 'AIR REG')\n                and l_shipinstruct = 'DELIVER IN PERSON'\n        )\n        or\n        (\n                p_partkey = l_partkey\n                and p_brand = 'Brand#34'\n                and p_container in ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG')\n                and l_quantity >= 20 and l_quantity <= 30\n                and p_size between 1 and 15\n                and l_shipmode in ('AIR', 'AIR REG')\n                and l_shipinstruct = 'DELIVER IN PERSON'\n        );\nSELECT\n  SUM(\"lineitem\".\"l_extendedprice\" * (\n    1 - \"lineitem\".\"l_discount\"\n  )) AS \"revenue\"\nFROM \"lineitem\" AS \"lineitem\"\nJOIN \"part\" AS \"part\"\n  ON (\n    \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\n    AND \"lineitem\".\"l_quantity\" <= 11\n    AND \"lineitem\".\"l_quantity\" >= 1\n    AND \"lineitem\".\"l_shipinstruct\" = 'DELIVER IN PERSON'\n    AND \"lineitem\".\"l_shipmode\" IN ('AIR', 'AIR REG')\n    AND \"part\".\"p_brand\" = 'Brand#12'\n    AND \"part\".\"p_container\" IN ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG')\n    AND \"part\".\"p_size\" <= 5\n    AND \"part\".\"p_size\" >= 1\n  )\n  OR (\n    \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\n    AND \"lineitem\".\"l_quantity\" <= 20\n    AND \"lineitem\".\"l_quantity\" >= 10\n    AND \"lineitem\".\"l_shipinstruct\" = 'DELIVER IN PERSON'\n    AND \"lineitem\".\"l_shipmode\" IN ('AIR', 'AIR REG')\n    AND \"part\".\"p_brand\" = 'Brand#23'\n    AND \"part\".\"p_container\" IN ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK')\n    AND \"part\".\"p_size\" <= 10\n    AND \"part\".\"p_size\" >= 1\n  )\n  OR (\n    \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\n    AND \"lineitem\".\"l_quantity\" <= 30\n    AND \"lineitem\".\"l_quantity\" >= 20\n    AND \"lineitem\".\"l_shipinstruct\" = 'DELIVER IN PERSON'\n    AND \"lineitem\".\"l_shipmode\" IN ('AIR', 'AIR REG')\n    AND \"part\".\"p_brand\" = 'Brand#34'\n    AND \"part\".\"p_container\" IN ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG')\n    AND \"part\".\"p_size\" <= 15\n    AND \"part\".\"p_size\" >= 1\n  )\nWHERE\n  (\n    \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\n    AND \"lineitem\".\"l_quantity\" <= 11\n    AND \"lineitem\".\"l_quantity\" >= 1\n    AND \"lineitem\".\"l_shipinstruct\" = 'DELIVER IN PERSON'\n    AND \"lineitem\".\"l_shipmode\" IN ('AIR', 'AIR REG')\n    AND \"part\".\"p_brand\" = 'Brand#12'\n    AND \"part\".\"p_container\" IN ('SM CASE', 'SM BOX', 'SM PACK', 'SM PKG')\n    AND \"part\".\"p_size\" <= 5\n    AND \"part\".\"p_size\" >= 1\n  )\n  OR (\n    \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\n    AND \"lineitem\".\"l_quantity\" <= 20\n    AND \"lineitem\".\"l_quantity\" >= 10\n    AND \"lineitem\".\"l_shipinstruct\" = 'DELIVER IN PERSON'\n    AND \"lineitem\".\"l_shipmode\" IN ('AIR', 'AIR REG')\n    AND \"part\".\"p_brand\" = 'Brand#23'\n    AND \"part\".\"p_container\" IN ('MED BAG', 'MED BOX', 'MED PKG', 'MED PACK')\n    AND \"part\".\"p_size\" <= 10\n    AND \"part\".\"p_size\" >= 1\n  )\n  OR (\n    \"lineitem\".\"l_partkey\" = \"part\".\"p_partkey\"\n    AND \"lineitem\".\"l_quantity\" <= 30\n    AND \"lineitem\".\"l_quantity\" >= 20\n    AND \"lineitem\".\"l_shipinstruct\" = 'DELIVER IN PERSON'\n    AND \"lineitem\".\"l_shipmode\" IN ('AIR', 'AIR REG')\n    AND \"part\".\"p_brand\" = 'Brand#34'\n    AND \"part\".\"p_container\" IN ('LG CASE', 'LG BOX', 'LG PACK', 'LG PKG')\n    AND \"part\".\"p_size\" <= 15\n    AND \"part\".\"p_size\" >= 1\n  );\n\n--------------------------------------\n-- TPC-H 20\n--------------------------------------\nselect\n        s_name,\n        s_address\nfrom\n        supplier,\n        nation\nwhere\n        s_suppkey in (\n                select\n                        ps_suppkey\n                from\n                        partsupp\n                where\n                        ps_partkey in (\n                                select\n                                        p_partkey\n                                from\n                                        part\n                                where\n                                        p_name like 'forest%'\n                        )\n                        and ps_availqty > (\n                                select\n                                        0.5 * sum(l_quantity)\n                                from\n                                        lineitem\n                                where\n                                        l_partkey = ps_partkey\n                                        and l_suppkey = ps_suppkey\n                                        and CAST(l_shipdate AS DATE) >= date '1994-01-01'\n                                        and CAST(l_shipdate AS DATE) < date '1994-01-01' + interval '1' year\n                        )\n        )\n        and s_nationkey = n_nationkey\n        and n_name = 'CANADA'\norder by\n        s_name;\nWITH \"_u_0\" AS (\n  SELECT\n    \"part\".\"p_partkey\" AS \"p_partkey\"\n  FROM \"part\" AS \"part\"\n  WHERE\n    \"part\".\"p_name\" LIKE 'forest%'\n  GROUP BY\n    \"part\".\"p_partkey\"\n), \"_u_1\" AS (\n  SELECT\n    0.5 * SUM(\"lineitem\".\"l_quantity\") AS \"_col_0\",\n    \"lineitem\".\"l_partkey\" AS \"_u_2\",\n    \"lineitem\".\"l_suppkey\" AS \"_u_3\"\n  FROM \"lineitem\" AS \"lineitem\"\n  WHERE\n    CAST(\"lineitem\".\"l_shipdate\" AS DATE) < CAST('1995-01-01' AS DATE)\n    AND CAST(\"lineitem\".\"l_shipdate\" AS DATE) >= CAST('1994-01-01' AS DATE)\n  GROUP BY\n    \"lineitem\".\"l_partkey\",\n    \"lineitem\".\"l_suppkey\"\n), \"_u_4\" AS (\n  SELECT\n    \"partsupp\".\"ps_suppkey\" AS \"ps_suppkey\"\n  FROM \"partsupp\" AS \"partsupp\"\n  LEFT JOIN \"_u_0\" AS \"_u_0\"\n    ON \"_u_0\".\"p_partkey\" = \"partsupp\".\"ps_partkey\"\n  LEFT JOIN \"_u_1\" AS \"_u_1\"\n    ON \"_u_1\".\"_u_2\" = \"partsupp\".\"ps_partkey\"\n    AND \"_u_1\".\"_u_3\" = \"partsupp\".\"ps_suppkey\"\n  WHERE\n    \"_u_1\".\"_col_0\" < \"partsupp\".\"ps_availqty\" AND NOT \"_u_0\".\"p_partkey\" IS NULL\n  GROUP BY\n    \"partsupp\".\"ps_suppkey\"\n)\nSELECT\n  \"supplier\".\"s_name\" AS \"s_name\",\n  \"supplier\".\"s_address\" AS \"s_address\"\nFROM \"supplier\" AS \"supplier\"\nJOIN \"nation\" AS \"nation\"\n  ON \"nation\".\"n_name\" = 'CANADA'\n  AND \"nation\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\nLEFT JOIN \"_u_4\" AS \"_u_4\"\n  ON \"_u_4\".\"ps_suppkey\" = \"supplier\".\"s_suppkey\"\nWHERE\n  NOT \"_u_4\".\"ps_suppkey\" IS NULL\nORDER BY\n  \"s_name\";\n\n--------------------------------------\n-- TPC-H 21\n--------------------------------------\nselect\n        s_name,\n        count(*) as numwait\nfrom\n        supplier,\n        lineitem l1,\n        orders,\n        nation\nwhere\n        s_suppkey = l1.l_suppkey\n        and o_orderkey = l1.l_orderkey\n        and o_orderstatus = 'F'\n        and l1.l_receiptdate > l1.l_commitdate\n        and exists (\n                select\n                        *\n                from\n                        lineitem l2\n                where\n                        l2.l_orderkey = l1.l_orderkey\n                        and l2.l_suppkey <> l1.l_suppkey\n        )\n        and not exists (\n                select\n                        *\n                from\n                        lineitem l3\n                where\n                        l3.l_orderkey = l1.l_orderkey\n                        and l3.l_suppkey <> l1.l_suppkey\n                        and l3.l_receiptdate > l3.l_commitdate\n        )\n        and s_nationkey = n_nationkey\n        and n_name = 'SAUDI ARABIA'\ngroup by\n        s_name\norder by\n        numwait desc,\n        s_name\nlimit\n        100;\nWITH \"_u_0\" AS (\n  SELECT\n    \"l2\".\"l_orderkey\" AS \"l_orderkey\",\n    ARRAY_AGG(\"l2\".\"l_suppkey\") AS \"_u_1\"\n  FROM \"lineitem\" AS \"l2\"\n  GROUP BY\n    \"l2\".\"l_orderkey\"\n), \"_u_2\" AS (\n  SELECT\n    \"l3\".\"l_orderkey\" AS \"l_orderkey\",\n    ARRAY_AGG(\"l3\".\"l_suppkey\") AS \"_u_3\"\n  FROM \"lineitem\" AS \"l3\"\n  WHERE\n    \"l3\".\"l_commitdate\" < \"l3\".\"l_receiptdate\"\n  GROUP BY\n    \"l3\".\"l_orderkey\"\n)\nSELECT\n  \"supplier\".\"s_name\" AS \"s_name\",\n  COUNT(*) AS \"numwait\"\nFROM \"supplier\" AS \"supplier\"\nJOIN \"lineitem\" AS \"l1\"\n  ON \"l1\".\"l_commitdate\" < \"l1\".\"l_receiptdate\"\n  AND \"l1\".\"l_suppkey\" = \"supplier\".\"s_suppkey\"\nJOIN \"orders\" AS \"orders\"\n  ON \"l1\".\"l_orderkey\" = \"orders\".\"o_orderkey\" AND \"orders\".\"o_orderstatus\" = 'F'\nJOIN \"nation\" AS \"nation\"\n  ON \"nation\".\"n_name\" = 'SAUDI ARABIA'\n  AND \"nation\".\"n_nationkey\" = \"supplier\".\"s_nationkey\"\nLEFT JOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"l_orderkey\" = \"l1\".\"l_orderkey\"\nLEFT JOIN \"_u_2\" AS \"_u_2\"\n  ON \"_u_2\".\"l_orderkey\" = \"l1\".\"l_orderkey\"\nWHERE\n  (\n    \"_u_2\".\"l_orderkey\" IS NULL\n    OR NOT ARRAY_ANY(\"_u_2\".\"_u_3\", \"_x\" -> \"l1\".\"l_suppkey\" <> \"_x\")\n  )\n  AND ARRAY_ANY(\"_u_0\".\"_u_1\", \"_x\" -> \"l1\".\"l_suppkey\" <> \"_x\")\n  AND NOT \"_u_0\".\"l_orderkey\" IS NULL\nGROUP BY\n  \"supplier\".\"s_name\"\nORDER BY\n  \"numwait\" DESC,\n  \"s_name\"\nLIMIT 100;\n\n--------------------------------------\n-- TPC-H 22\n--------------------------------------\nselect\n        cntrycode,\n        count(*) as numcust,\n        sum(c_acctbal) as totacctbal\nfrom\n        (\n                select\n                        substring(c_phone, 1, 2) as cntrycode,\n                        c_acctbal\n                from\n                        customer\n                where\n                        substring(c_phone, 1, 2) in\n                                ('13', '31', '23', '29', '30', '18', '17')\n                        and c_acctbal > (\n                                select\n                                        avg(c_acctbal)\n                                from\n                                        customer\n                                where\n                                        c_acctbal > 0.00\n                                        and substring(c_phone, 1, 2) in\n                                                ('13', '31', '23', '29', '30', '18', '17')\n                        )\n                        and not exists (\n                                select\n                                        *\n                                from\n                                        orders\n                                where\n                                        o_custkey = c_custkey\n                        )\n        ) as custsale\ngroup by\n        cntrycode\norder by\n        cntrycode;\nWITH \"_u_0\" AS (\n  SELECT\n    AVG(\"customer\".\"c_acctbal\") AS \"_col_0\"\n  FROM \"customer\" AS \"customer\"\n  WHERE\n    \"customer\".\"c_acctbal\" > 0.00\n    AND SUBSTRING(\"customer\".\"c_phone\", 1, 2) IN ('13', '31', '23', '29', '30', '18', '17')\n), \"_u_1\" AS (\n  SELECT\n    \"orders\".\"o_custkey\" AS \"_u_2\"\n  FROM \"orders\" AS \"orders\"\n  GROUP BY\n    \"orders\".\"o_custkey\"\n)\nSELECT\n  SUBSTRING(\"customer\".\"c_phone\", 1, 2) AS \"cntrycode\",\n  COUNT(*) AS \"numcust\",\n  SUM(\"customer\".\"c_acctbal\") AS \"totacctbal\"\nFROM \"customer\" AS \"customer\"\nJOIN \"_u_0\" AS \"_u_0\"\n  ON \"_u_0\".\"_col_0\" < \"customer\".\"c_acctbal\"\nLEFT JOIN \"_u_1\" AS \"_u_1\"\n  ON \"_u_1\".\"_u_2\" = \"customer\".\"c_custkey\"\nWHERE\n  \"_u_1\".\"_u_2\" IS NULL\n  AND SUBSTRING(\"customer\".\"c_phone\", 1, 2) IN ('13', '31', '23', '29', '30', '18', '17')\nGROUP BY\n  SUBSTRING(\"customer\".\"c_phone\", 1, 2)\nORDER BY\n  \"cntrycode\";\n\n"
  },
  {
    "path": "tests/fixtures/optimizer/unnest_subqueries.sql",
    "content": "SELECT * FROM x WHERE x.a = (SELECT SUM(y.a) AS a FROM y);\nSELECT * FROM x CROSS JOIN (SELECT SUM(y.a) AS a FROM y) AS _u_0 WHERE x.a = _u_0.a;\n\nSELECT * FROM x WHERE x.a IN (SELECT y.a AS a FROM y);\nSELECT * FROM x LEFT JOIN (SELECT y.a AS a FROM y GROUP BY y.a) AS _u_0 ON x.a = _u_0.a WHERE NOT _u_0.a IS NULL;\n\nSELECT * FROM x WHERE x.a IN (SELECT y.b AS b FROM y);\nSELECT * FROM x LEFT JOIN (SELECT y.b AS b FROM y GROUP BY y.b) AS _u_0 ON x.a = _u_0.b WHERE NOT _u_0.b IS NULL;\n\nSELECT * FROM x WHERE x.a = ANY (SELECT y.a AS a FROM y);\nSELECT * FROM x LEFT JOIN (SELECT y.a AS a FROM y GROUP BY y.a) AS _u_0 ON x.a = _u_0.a WHERE NOT _u_0.a IS NULL;\n\nSELECT * FROM x WHERE x.a = (SELECT SUM(y.b) AS b FROM y WHERE x.a = y.a);\nSELECT * FROM x LEFT JOIN (SELECT SUM(y.b) AS b, y.a AS _u_1 FROM y WHERE TRUE GROUP BY y.a) AS _u_0 ON x.a = _u_0._u_1 WHERE x.a = _u_0.b;\n\nSELECT * FROM x WHERE x.a > (SELECT SUM(y.b) AS b FROM y WHERE x.a = y.a);\nSELECT * FROM x LEFT JOIN (SELECT SUM(y.b) AS b, y.a AS _u_1 FROM y WHERE TRUE GROUP BY y.a) AS _u_0 ON x.a = _u_0._u_1 WHERE x.a > _u_0.b;\n\nSELECT * FROM x WHERE x.a <> ANY (SELECT y.a AS a FROM y WHERE y.a = x.a);\nSELECT * FROM x LEFT JOIN (SELECT y.a AS a FROM y WHERE TRUE GROUP BY y.a) AS _u_0 ON _u_0.a = x.a WHERE x.a <> _u_0.a;\n\nSELECT * FROM x WHERE x.a NOT IN (SELECT y.a AS a FROM y WHERE y.a = x.a);\nSELECT * FROM x LEFT JOIN (SELECT y.a AS a FROM y WHERE TRUE GROUP BY y.a) AS _u_0 ON _u_0.a = x.a WHERE NOT x.a = _u_0.a;\n\nSELECT * FROM x WHERE x.a IN (SELECT y.a AS a FROM y WHERE y.b = x.a);\nSELECT * FROM x LEFT JOIN (SELECT ARRAY_AGG(y.a) AS a, y.b AS _u_1 FROM y WHERE TRUE GROUP BY y.b) AS _u_0 ON _u_0._u_1 = x.a WHERE ARRAY_ANY(_u_0.a, _x -> _x = x.a);\n\nSELECT * FROM x WHERE x.a < (SELECT SUM(y.a) AS a FROM y WHERE y.a = x.a and y.a = x.b and y.b <> x.d);\nSELECT * FROM x LEFT JOIN (SELECT SUM(y.a) AS a, y.a AS _u_1, ARRAY_AGG(y.b) AS _u_2 FROM y WHERE TRUE AND TRUE AND TRUE GROUP BY y.a) AS _u_0 ON _u_0._u_1 = x.a AND _u_0._u_1 = x.b WHERE (x.a < _u_0.a AND ARRAY_ANY(_u_0._u_2, _x -> _x <> x.d));\n\nSELECT * FROM x WHERE EXISTS (SELECT y.a AS a, y.b AS b FROM y WHERE x.a = y.a);\nSELECT * FROM x LEFT JOIN (SELECT y.a AS a FROM y WHERE TRUE GROUP BY y.a) AS _u_0 ON x.a = _u_0.a WHERE NOT _u_0.a IS NULL;\n\nSELECT * FROM x WHERE x.a IN (SELECT y.a AS a FROM y LIMIT 10);\nSELECT * FROM x WHERE x.a IN (SELECT y.a AS a FROM y LIMIT 10);\n\nSELECT * FROM x.a WHERE x.a IN (SELECT y.a AS a FROM y OFFSET 10);\nSELECT * FROM x.a WHERE x.a IN (SELECT y.a AS a FROM y OFFSET 10);\n\nSELECT * FROM x.a WHERE x.a IN (SELECT y.a AS a, y.b AS b FROM y);\nSELECT * FROM x.a WHERE x.a IN (SELECT y.a AS a, y.b AS b FROM y);\n\nSELECT * FROM x.a WHERE x.a > ANY (SELECT y.a FROM y);\nSELECT * FROM x.a WHERE x.a > ANY (SELECT y.a FROM y);\n\nSELECT * FROM x WHERE x.a = (SELECT SUM(y.c) AS c FROM y WHERE y.a = x.a LIMIT 10);\nSELECT * FROM x WHERE x.a = (SELECT SUM(y.c) AS c FROM y WHERE y.a = x.a LIMIT 10);\n\nSELECT * FROM x WHERE x.a = (SELECT SUM(y.c) AS c FROM y WHERE y.a = x.a OFFSET 10);\nSELECT * FROM x WHERE x.a = (SELECT SUM(y.c) AS c FROM y WHERE y.a = x.a OFFSET 10);\n\nSELECT * FROM x WHERE x.a > ALL (SELECT y.c AS c FROM y WHERE y.a = x.a);\nSELECT * FROM x LEFT JOIN (SELECT ARRAY_AGG(y.c) AS c, y.a AS _u_1 FROM y WHERE TRUE GROUP BY y.a) AS _u_0 ON _u_0._u_1 = x.a WHERE ARRAY_ALL(_u_0.c, _x -> x.a > _x);\n\nSELECT * FROM x WHERE x.a > (SELECT COUNT(*) as d FROM y WHERE y.a = x.a);\nSELECT * FROM x LEFT JOIN (SELECT COUNT(*) AS d, y.a AS _u_1 FROM y WHERE TRUE GROUP BY y.a) AS _u_0 ON _u_0._u_1 = x.a WHERE x.a > COALESCE(_u_0.d, 0);\n\n# title: invalid statement left alone\nSELECT * FROM x WHERE x.a = SUM(SELECT 1);\nSELECT * FROM x WHERE x.a = SUM(SELECT 1);\n\nSELECT * FROM x WHERE x.a IN (SELECT max(y.b) AS b FROM y GROUP BY y.a);\nSELECT * FROM x LEFT JOIN (SELECT _q.b AS b FROM (SELECT MAX(y.b) AS b FROM y GROUP BY y.a) AS _q GROUP BY _q.b) AS _u_0 ON x.a = _u_0.b WHERE NOT _u_0.b IS NULL;\n\nSELECT x.a > (SELECT SUM(y.a) AS b FROM y) FROM x;\nSELECT x.a > _u_0.b FROM x CROSS JOIN (SELECT SUM(y.a) AS b FROM y) AS _u_0;\n\nSELECT (SELECT MAX(t2.c1) AS c1 FROM t2 WHERE t2.c2 = t1.c2 AND t2.c3 <= TRUNC(t1.c3)) AS c FROM t1;\nSELECT _u_0.c1 AS c FROM t1 LEFT JOIN (SELECT MAX(t2.c1) AS c1, t2.c2 AS _u_1, MAX(t2.c3) AS _u_2 FROM t2 WHERE TRUE AND TRUE GROUP BY t2.c2) AS _u_0 ON _u_0._u_1 = t1.c2 WHERE _u_0._u_2 <= TRUNC(t1.c3);\n\nSELECT s.t AS t FROM s WHERE 1 IN (SELECT t.a AS a FROM t WHERE t.b > 1);\nSELECT s.t AS t FROM s LEFT JOIN (SELECT t.a AS a FROM t WHERE t.b > 1 GROUP BY t.a) AS _u_0 ON 1 = _u_0.a WHERE NOT _u_0.a IS NULL;\n\n# title: can't create GROUP BY clause with an aggregate\nSELECT s.t FROM s WHERE 1 IN (SELECT MAX(t.a) AS t1 FROM t);\nSELECT s.t FROM s LEFT JOIN (SELECT MAX(t.a) AS t1 FROM t) AS _u_0 ON 1 = _u_0.t1 WHERE NOT _u_0.t1 IS NULL;\n\n# title: can't create GROUP BY clause with an aggregate (nested)\nSELECT s.t FROM s WHERE 1 IN (SELECT MAX(t.a) + 1 AS t1 FROM t);\nSELECT s.t FROM s LEFT JOIN (SELECT MAX(t.a) + 1 AS t1 FROM t) AS _u_0 ON 1 = _u_0.t1 WHERE NOT _u_0.t1 IS NULL;\n\nSELECT BIT_COUNT(EXISTS(SELECT 1 WHERE FALSE)) AS col FROM t0;\nSELECT BIT_COUNT(EXISTS(SELECT 1 WHERE FALSE)) AS col FROM t0;\n\n# title: EXISTS in SELECT with GROUP BY - empty subquery should return 0, not eliminate rows\nSELECT EXISTS (SELECT 1 WHERE FALSE) AS ref0 FROM t1, t0 GROUP BY t0.c2;\nSELECT NOT MAX(_u_0.\"1\") IS NULL AS ref0 FROM t1, t0 LEFT JOIN (SELECT 1 WHERE FALSE) AS _u_0 ON TRUE GROUP BY t0.c2;\n\n# title: EXISTS in SELECT with GROUP BY - non-empty subquery should return 1\nSELECT EXISTS (SELECT 1 WHERE TRUE) AS ref0 FROM t1, t0 GROUP BY t0.c2;\nSELECT NOT MAX(_u_0.\"1\") IS NULL AS ref0 FROM t1, t0 LEFT JOIN (SELECT 1 WHERE TRUE) AS _u_0 ON TRUE GROUP BY t0.c2;\n\n# title: Multiple EXISTS in SELECT with GROUP BY\nSELECT EXISTS (SELECT 1 WHERE FALSE) AS ref0, EXISTS (SELECT 1 WHERE TRUE) AS ref1 FROM t1, t0 GROUP BY t0.c2;\nSELECT NOT MAX(_u_0.\"1\") IS NULL AS ref0, NOT MAX(_u_1.\"1\") IS NULL AS ref1 FROM t1, t0 LEFT JOIN (SELECT 1 WHERE FALSE) AS _u_0 ON TRUE LEFT JOIN (SELECT 1 WHERE TRUE) AS _u_1 ON TRUE GROUP BY t0.c2;\n\n# title: EXISTS in SELECT with HAVING clause\nSELECT EXISTS (SELECT 1 WHERE FALSE) AS ref0 FROM t1 GROUP BY t1.c0 HAVING COUNT(*) > 0;\nSELECT NOT MAX(_u_0.\"1\") IS NULL AS ref0 FROM t1 LEFT JOIN (SELECT 1 WHERE FALSE) AS _u_0 ON TRUE GROUP BY t1.c0 HAVING COUNT(*) > 0;\n\n# title: Skip unnesting GENERATE_SERIES\nWITH t2 AS (SELECT CAST(t1.c1 AS BIGINT) AS ref1 FROM GENERATE_SERIES((SELECT MAX(x.a) FROM x AS x), 10, 1) AS t1(c1)) SELECT t2.ref1 AS ref1 FROM t2 AS t2;\nWITH t2 AS (SELECT CAST(t1.c1 AS BIGINT) AS ref1 FROM GENERATE_SERIES((SELECT MAX(x.a) FROM x AS x), 10, 1) AS t1(c1)) SELECT t2.ref1 AS ref1 FROM t2 AS t2;\n\n# title: Skip unnesting UNNEST (same issue as GENERATE_SERIES)\nWITH t2 AS (SELECT t1.c1 FROM UNNEST((SELECT ARRAY(x.a) FROM x)) AS t1(c1)) SELECT t2.c1 FROM t2;\nWITH t2 AS (SELECT t1.c1 FROM UNNEST((SELECT ARRAY(x.a) FROM x)) AS t1(c1)) SELECT t2.c1 FROM t2;\n\n# title: Skip unnesting GENERATE_SERIES but unnesting the rest in the query\nSELECT t1.c1 > (SELECT SUM(y.a) AS b FROM y) FROM x JOIN GENERATE_SERIES((SELECT MAX(x.a) FROM x AS x), 10, 1) AS t1(c1) ON t1.c1 > x.a;\nSELECT t1.c1 > _u_0.b FROM x JOIN GENERATE_SERIES((SELECT MAX(x.a) FROM x AS x), 10, 1) AS t1(c1) ON t1.c1 > x.a CROSS JOIN (SELECT SUM(y.a) AS b FROM y) AS _u_0;\n\n# title: correlated scalar subquery with EQ + range predicates inside a function in SELECT should not crash (issue #7295)\nSELECT COALESCE((SELECT MAX(b.val) FROM t b WHERE b.val < a.val AND b.id = a.id), a.val) AS result FROM t a;\nSELECT COALESCE((SELECT MAX(b.val) FROM t AS b WHERE b.val < a.val AND b.id = a.id), a.val) AS result FROM t AS a;\n"
  },
  {
    "path": "tests/fixtures/partial.sql",
    "content": "SELECT a FROM\nSELECT a FROM x WHERE\nSELECT a +\na *\nSELECT a FROM x GROUP BY\nWITH a AS (SELECT 1), b AS (SELECT 2)\nSELECT FROM x\n"
  },
  {
    "path": "tests/fixtures/pretty.sql",
    "content": "SET x TO 1;\nSET x = 1;\n\nSELECT * FROM test;\nSELECT\n  *\nFROM test;\n\nWITH a AS ((SELECT 1 AS b) UNION ALL (SELECT 2 AS b)) SELECT * FROM a;\nWITH a AS (\n  (\n    SELECT\n      1 AS b\n  )\n  UNION ALL\n  (\n    SELECT\n      2 AS b\n  )\n)\nSELECT\n  *\nFROM a;\n\nWITH cte1 AS (\n    SELECT a, z and e AS b\n    FROM cte\n    WHERE x IN (1, 2, 3) AND z < -1 OR z > 1 AND w = 'AND'\n), cte2 AS (\n    SELECT RANK() OVER (PARTITION BY a, b ORDER BY x DESC) a, b\n    FROM cte\n    CROSS JOIN (\n        SELECT 1\n        UNION ALL\n        SELECT 2\n        UNION ALL\n        SELECT CASE x AND 1 + 1 = 2\n        WHEN TRUE THEN 1 AND 4 + 3 AND Z\n        WHEN x and y THEN 2\n        ELSE 3 AND 4 AND g END\n        UNION ALL\n        SELECT 1\n        FROM (SELECT 1) AS x, y, (SELECT 2) z\n        UNION ALL\n        SELECT MAX(COALESCE(x AND y, a and b and c, d and e)), FOO(CASE WHEN a and b THEN c and d ELSE 3 END)\n        GROUP BY x, GROUPING SETS (a, (b, c)), CUBE(y, z)\n    ) x\n)\nSELECT a, b c FROM (\n    SELECT a w, 1 + 1 AS c\n    FROM foo\n    WHERE w IN (SELECT z FROM q)\n    GROUP BY a, b\n) x\nLEFT JOIN (\n    SELECT a, b\n    FROM (SELECT * FROM bar WHERE (c > 1 AND d > 1) OR e > 1 GROUP BY a HAVING a > 1 LIMIT 10) z\n) y ON x.a = y.b AND x.a > 1 OR (x.c = y.d OR x.c = y.e);\nWITH cte1 AS (\n  SELECT\n    a,\n    z AND e AS b\n  FROM cte\n  WHERE\n    x IN (1, 2, 3) AND z < -1 OR z > 1 AND w = 'AND'\n), cte2 AS (\n  SELECT\n    RANK() OVER (PARTITION BY a, b ORDER BY x DESC) AS a,\n    b\n  FROM cte\n  CROSS JOIN (\n    SELECT\n      1\n    UNION ALL\n    SELECT\n      2\n    UNION ALL\n    SELECT\n      CASE x AND 1 + 1 = 2\n        WHEN TRUE\n        THEN 1 AND 4 + 3 AND Z\n        WHEN x AND y\n        THEN 2\n        ELSE 3 AND 4 AND g\n      END\n    UNION ALL\n    SELECT\n      1\n    FROM (\n      SELECT\n        1\n    ) AS x, y, (\n      SELECT\n        2\n    ) AS z\n    UNION ALL\n    SELECT\n      MAX(COALESCE(x AND y, a AND b AND c, d AND e)),\n      FOO(CASE WHEN a AND b THEN c AND d ELSE 3 END)\n    GROUP BY\n      x,\n      GROUPING SETS (\n        a,\n        (b, c)\n      ),\n      CUBE (\n        y,\n        z\n      )\n  ) AS x\n)\nSELECT\n  a,\n  b AS c\nFROM (\n  SELECT\n    a AS w,\n    1 + 1 AS c\n  FROM foo\n  WHERE\n    w IN (\n      SELECT\n        z\n      FROM q\n    )\n  GROUP BY\n    a,\n    b\n) AS x\nLEFT JOIN (\n  SELECT\n    a,\n    b\n  FROM (\n    SELECT\n      *\n    FROM bar\n    WHERE\n      (\n        c > 1 AND d > 1\n      ) OR e > 1\n    GROUP BY\n      a\n    HAVING\n      a > 1\n    LIMIT 10\n  ) AS z\n) AS y\n  ON x.a = y.b AND x.a > 1 OR (\n    x.c = y.d OR x.c = y.e\n  );\n\nSELECT myCol1, myCol2 FROM baseTable LATERAL VIEW OUTER explode(col1) myTable1 AS myCol1 LATERAL VIEW explode(col2) myTable2 AS myCol2\nwhere a > 1 and b > 2 or c > 3;\n\nSELECT\n  myCol1,\n  myCol2\nFROM baseTable\nLATERAL VIEW OUTER\nEXPLODE(col1) myTable1 AS myCol1\nLATERAL VIEW\nEXPLODE(col2) myTable2 AS myCol2\nWHERE\n  a > 1 AND b > 2 OR c > 3;\n\nSELECT * FROM (WITH y AS ( SELECT 1 AS z) SELECT z from y) x;\nSELECT\n  *\nFROM (\n  WITH y AS (\n    SELECT\n      1 AS z\n  )\n  SELECT\n    z\n  FROM y\n) AS x;\n\nINSERT OVERWRITE TABLE x VALUES (1, 2.0, '3.0'), (4, 5.0, '6.0');\nINSERT OVERWRITE TABLE x\nVALUES\n  (1, 2.0, '3.0'),\n  (4, 5.0, '6.0');\n\nINSERT INTO TABLE foo REPLACE WHERE cond SELECT * FROM bar;\nINSERT INTO foo\nREPLACE WHERE cond\nSELECT\n  *\nFROM bar;\n\nINSERT OVERWRITE TABLE zipcodes PARTITION(state = '0') VALUES (896, 'US', 'TAMPA', 33607);\nINSERT OVERWRITE TABLE zipcodes PARTITION(state = '0')\nVALUES\n  (896, 'US', 'TAMPA', 33607);\n\nWITH regional_sales AS (\n    SELECT region, SUM(amount) AS total_sales\n    FROM orders\n    GROUP BY region\n    ), top_regions AS (\n    SELECT region\n    FROM regional_sales\n    WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)\n)\nSELECT region,\nproduct,\nSUM(quantity) AS product_units,\nSUM(amount) AS product_sales\nFROM orders\nWHERE region IN (SELECT region FROM top_regions)\nGROUP BY region, product;\nWITH regional_sales AS (\n  SELECT\n    region,\n    SUM(amount) AS total_sales\n  FROM orders\n  GROUP BY\n    region\n), top_regions AS (\n  SELECT\n    region\n  FROM regional_sales\n  WHERE\n    total_sales > (\n      SELECT\n        SUM(total_sales) / 10\n      FROM regional_sales\n    )\n)\nSELECT\n  region,\n  product,\n  SUM(quantity) AS product_units,\n  SUM(amount) AS product_sales\nFROM orders\nWHERE\n  region IN (\n    SELECT\n      region\n    FROM top_regions\n  )\nGROUP BY\n  region,\n  product;\n\nCREATE TABLE \"t_customer_account\" ( \"id\" int, \"customer_id\" int, \"bank\" varchar(100), \"account_no\" varchar(100));\nCREATE TABLE \"t_customer_account\" (\n  \"id\" INT,\n  \"customer_id\" INT,\n  \"bank\" VARCHAR(100),\n  \"account_no\" VARCHAR(100)\n);\n\n\nSELECT\nx(\"aaaaaaaaaaaaaa\", \"bbbbbbbbbbbbb\", \"ccccccccc\", \"ddddddddddddd\", \"eeeeeeeeeeeee\", \"fffffff\"),\narray(\"aaaaaaaaaaaaaa\", \"bbbbbbbbbbbbb\", \"ccccccccc\", \"ddddddddddddd\", \"eeeeeeeeeeeee\", \"fffffff\"),\narray(\"aaaaaaaaaaaaaa\", \"bbbbbbbbbbbbb\", \"ccccccccc\", \"ddddddddddddd\", \"eeeeeeeeeeeee\", \"fffffff\", array(\"aaaaaaaaaaaaaa\", \"bbbbbbbbbbbbb\", \"ccccccccc\", \"ddddddddddddd\", \"eeeeeeeeeeeee\", \"fffffff\")),\narray(array(\"aaaaaaaaaaaaaa\", \"bbbbbbbbbbbbb\", \"ccccccccc\", \"ddddddddddddd\", \"eeeeeeeeeeeee\", \"fffffff\")),\n;\nSELECT\n  X(\n    \"aaaaaaaaaaaaaa\",\n    \"bbbbbbbbbbbbb\",\n    \"ccccccccc\",\n    \"ddddddddddddd\",\n    \"eeeeeeeeeeeee\",\n    \"fffffff\"\n  ),\n  ARRAY(\n    \"aaaaaaaaaaaaaa\",\n    \"bbbbbbbbbbbbb\",\n    \"ccccccccc\",\n    \"ddddddddddddd\",\n    \"eeeeeeeeeeeee\",\n    \"fffffff\"\n  ),\n  ARRAY(\n    \"aaaaaaaaaaaaaa\",\n    \"bbbbbbbbbbbbb\",\n    \"ccccccccc\",\n    \"ddddddddddddd\",\n    \"eeeeeeeeeeeee\",\n    \"fffffff\",\n    ARRAY(\n      \"aaaaaaaaaaaaaa\",\n      \"bbbbbbbbbbbbb\",\n      \"ccccccccc\",\n      \"ddddddddddddd\",\n      \"eeeeeeeeeeeee\",\n      \"fffffff\"\n    )\n  ),\n  ARRAY(\n    ARRAY(\n      \"aaaaaaaaaaaaaa\",\n      \"bbbbbbbbbbbbb\",\n      \"ccccccccc\",\n      \"ddddddddddddd\",\n      \"eeeeeeeeeeeee\",\n      \"fffffff\"\n    )\n  );\n/*\n  multi\n  line\n  comment\n*/\nSELECT * FROM foo;\n/*\n  multi\n  line\n  comment\n*/\nSELECT\n  *\nFROM foo;\nSELECT x FROM a.b.c /*x*/, e.f.g /*x*/;\nSELECT\n  x\nFROM a.b.c /* x */, e.f.g /* x */;\nSELECT x FROM (SELECT * FROM bla /*x*/WHERE id = 1) /*x*/;\nSELECT\n  x\nFROM (\n  SELECT\n    *\n  FROM bla /* x */\n  WHERE\n    id = 1\n) /* x */;\nSELECT * /* multi\n   line\n   comment */;\nSELECT\n  * /* multi\n   line\n   comment */;\nWITH table_data AS (\n    SELECT 'bob' AS name, ARRAY['banana', 'apple', 'orange'] AS fruit_basket\n)\nSELECT\n    name,\n    fruit,\n    basket_index\nFROM table_data\nCROSS JOIN UNNEST(fruit_basket) WITH ORDINALITY AS fruit(basket_index);\nWITH table_data AS (\n  SELECT\n    'bob' AS name,\n    ARRAY('banana', 'apple', 'orange') AS fruit_basket\n)\nSELECT\n  name,\n  fruit,\n  basket_index\nFROM table_data\nCROSS JOIN UNNEST(fruit_basket) WITH ORDINALITY AS fruit(basket_index);\nSELECT A.* EXCEPT A.COL_1, A.COL_2 FROM TABLE_1 A;\nSELECT\n  A.*\n  EXCEPT (A.COL_1),\n  A.COL_2\nFROM TABLE_1 AS A;\n\nSELECT *\nFROM a\nJOIN b\n  JOIN c\n    ON b.id = c.id\n  ON a.id = b.id\nCROSS JOIN d\nJOIN e\n  ON d.id = e.id;\nSELECT\n  *\nFROM a\nJOIN b\n  JOIN c\n    ON b.id = c.id\n  ON a.id = b.id\nCROSS JOIN d\nJOIN e\n  ON d.id = e.id;\n\nSELECT * FROM a JOIN b JOIN c USING (e) JOIN d USING (f) USING (g);\nSELECT\n  *\nFROM a\nJOIN b\n  JOIN c\n    USING (e)\n  JOIN d\n    USING (f)\n  USING (g);\n\n('aaaaaaaaaaa', 'bbbbbbbbbbbbbbbb', 'ccccccccccccc', 'ddddddddddd', 'eeeeeeeeeeeeeeeeeeeee');\n(\n  'aaaaaaaaaaa',\n  'bbbbbbbbbbbbbbbb',\n  'ccccccccccccc',\n  'ddddddddddd',\n  'eeeeeeeeeeeeeeeeeeeee'\n);\n\n/* COMMENT */\nINSERT FIRST WHEN salary > 4000 THEN INTO emp2\n             WHEN salary > 5000 THEN INTO emp3\n             WHEN salary > 6000 THEN INTO emp4\nSELECT salary FROM employees;\n/* COMMENT */\nINSERT FIRST\n  WHEN salary > 4000 THEN INTO emp2\n  WHEN salary > 5000 THEN INTO emp3\n  WHEN salary > 6000 THEN INTO emp4\nSELECT\n  salary\nFROM employees;\n\nSELECT *\nFROM foo\nwHERE 1=1\n    AND\n        -- my comment\n        EXISTS (\n            SELECT 1\n            FROM bar\n        );\nSELECT\n  *\nFROM foo\nWHERE\n  1 = 1 AND EXISTS(\n    SELECT\n      1\n    FROM bar\n  ) /* my comment */;\n\nSELECT 1\nFROM foo\nWHERE 1=1\nAND -- first comment\n    -- second comment\n    foo.a = 1;\nSELECT\n  1\nFROM foo\nWHERE\n  1 = 1 AND /* first comment */ foo.a /* second comment */ = 1;\n\nMERGE INTO t USING s ON t.id = s.id WHEN MATCHED THEN UPDATE SET status = s.status, amount = s.amount;\nMERGE INTO t\nUSING s\nON t.id = s.id\nWHEN MATCHED THEN UPDATE SET\n  status = s.status,\n  amount = s.amount;\n\nSELECT\n    id,\n    -- SUM(total) as all_that,\n    ARRAY_AGG(foo)[0][0] AS first_foo,\nFROM facts\nGROUP BY all;\nSELECT\n  id,\n  ARRAY_AGG(foo)[0][0] AS first_foo /* SUM(total) as all_that, */\nFROM facts\nGROUP BY ALL;\n\nALTER TABLE ct ADD CONSTRAINT ct_id_fk FOREIGN KEY (id) REFERENCES et (fid) DEFERRABLE INITIALLY DEFERRED;\nALTER TABLE ct\n  ADD CONSTRAINT ct_id_fk FOREIGN KEY (id) REFERENCES et (\n    fid\n  ) DEFERRABLE INITIALLY DEFERRED;\n\nSELECT\n    *\nFROM\n    a\nWHERE\n    /*111*/\n    b = 1\n    /*222*/\nORDER BY\n    c;\nSELECT\n  *\nFROM a\nWHERE\n  b /* 111 */ = 1\n/* 222 */\nORDER BY\n  c;\n\nSELECT\n    COUNT(*)\nFROM\n    table_a\n/* join comment */\nJOIN\n    table_b\nON\n    table_a.id = table_b.id\n/* group by comment */\nGROUP BY\n    table_a.id\n/* having comment */\nHAVING\n    table_a.id = 1;\nSELECT\n  COUNT(*)\nFROM table_a\n/* join comment */\nJOIN table_b\n  ON table_a.id = table_b.id\n/* group by comment */\nGROUP BY\n  table_a.id\n/* having comment */\nHAVING\n  table_a.id = 1;"
  },
  {
    "path": "tests/gen_fixtures.py",
    "content": "import time\n\nfrom sqlglot.optimizer import optimize\n\nTPCH_SCHEMA = {\n    \"lineitem\": {\n        \"l_orderkey\": \"bigint\",\n        \"l_partkey\": \"bigint\",\n        \"l_suppkey\": \"bigint\",\n        \"l_linenumber\": \"bigint\",\n        \"l_quantity\": \"double\",\n        \"l_extendedprice\": \"double\",\n        \"l_discount\": \"double\",\n        \"l_tax\": \"double\",\n        \"l_returnflag\": \"string\",\n        \"l_linestatus\": \"string\",\n        \"l_shipdate\": \"string\",\n        \"l_commitdate\": \"string\",\n        \"l_receiptdate\": \"string\",\n        \"l_shipinstruct\": \"string\",\n        \"l_shipmode\": \"string\",\n        \"l_comment\": \"string\",\n    },\n    \"orders\": {\n        \"o_orderkey\": \"bigint\",\n        \"o_custkey\": \"bigint\",\n        \"o_orderstatus\": \"string\",\n        \"o_totalprice\": \"double\",\n        \"o_orderdate\": \"string\",\n        \"o_orderpriority\": \"string\",\n        \"o_clerk\": \"string\",\n        \"o_shippriority\": \"int\",\n        \"o_comment\": \"string\",\n    },\n    \"customer\": {\n        \"c_custkey\": \"bigint\",\n        \"c_name\": \"string\",\n        \"c_address\": \"string\",\n        \"c_nationkey\": \"bigint\",\n        \"c_phone\": \"string\",\n        \"c_acctbal\": \"double\",\n        \"c_mktsegment\": \"string\",\n        \"c_comment\": \"string\",\n    },\n    \"part\": {\n        \"p_partkey\": \"bigint\",\n        \"p_name\": \"string\",\n        \"p_mfgr\": \"string\",\n        \"p_brand\": \"string\",\n        \"p_type\": \"string\",\n        \"p_size\": \"int\",\n        \"p_container\": \"string\",\n        \"p_retailprice\": \"double\",\n        \"p_comment\": \"string\",\n    },\n    \"supplier\": {\n        \"s_suppkey\": \"bigint\",\n        \"s_name\": \"string\",\n        \"s_address\": \"string\",\n        \"s_nationkey\": \"bigint\",\n        \"s_phone\": \"string\",\n        \"s_acctbal\": \"double\",\n        \"s_comment\": \"string\",\n    },\n    \"partsupp\": {\n        \"ps_partkey\": \"bigint\",\n        \"ps_suppkey\": \"bigint\",\n        \"ps_availqty\": \"int\",\n        \"ps_supplycost\": \"double\",\n        \"ps_comment\": \"string\",\n    },\n    \"nation\": {\n        \"n_nationkey\": \"bigint\",\n        \"n_name\": \"string\",\n        \"n_regionkey\": \"bigint\",\n        \"n_comment\": \"string\",\n    },\n    \"region\": {\n        \"r_regionkey\": \"bigint\",\n        \"r_name\": \"string\",\n        \"r_comment\": \"string\",\n    },\n}\n\nTPCDS_SCHEMA = {\n    \"catalog_sales\": {\n        \"cs_sold_date_sk\": \"bigint\",\n        \"cs_sold_time_sk\": \"bigint\",\n        \"cs_ship_date_sk\": \"bigint\",\n        \"cs_bill_customer_sk\": \"bigint\",\n        \"cs_bill_cdemo_sk\": \"bigint\",\n        \"cs_bill_hdemo_sk\": \"bigint\",\n        \"cs_bill_addr_sk\": \"bigint\",\n        \"cs_ship_customer_sk\": \"bigint\",\n        \"cs_ship_cdemo_sk\": \"bigint\",\n        \"cs_ship_hdemo_sk\": \"bigint\",\n        \"cs_ship_addr_sk\": \"bigint\",\n        \"cs_call_center_sk\": \"bigint\",\n        \"cs_catalog_page_sk\": \"bigint\",\n        \"cs_ship_mode_sk\": \"bigint\",\n        \"cs_warehouse_sk\": \"bigint\",\n        \"cs_item_sk\": \"bigint\",\n        \"cs_promo_sk\": \"bigint\",\n        \"cs_order_number\": \"bigint\",\n        \"cs_quantity\": \"bigint\",\n        \"cs_wholesale_cost\": \"double\",\n        \"cs_list_price\": \"double\",\n        \"cs_sales_price\": \"double\",\n        \"cs_ext_discount_amt\": \"double\",\n        \"cs_ext_sales_price\": \"double\",\n        \"cs_ext_wholesale_cost\": \"double\",\n        \"cs_ext_list_price\": \"double\",\n        \"cs_ext_tax\": \"double\",\n        \"cs_coupon_amt\": \"double\",\n        \"cs_ext_ship_cost\": \"double\",\n        \"cs_net_paid\": \"double\",\n        \"cs_net_paid_inc_tax\": \"double\",\n        \"cs_net_paid_inc_ship\": \"double\",\n        \"cs_net_paid_inc_ship_tax\": \"double\",\n        \"cs_net_profit\": \"double\",\n    },\n    \"catalog_returns\": {\n        \"cr_returned_date_sk\": \"bigint\",\n        \"cr_returned_time_sk\": \"bigint\",\n        \"cr_item_sk\": \"bigint\",\n        \"cr_refunded_customer_sk\": \"bigint\",\n        \"cr_refunded_cdemo_sk\": \"bigint\",\n        \"cr_refunded_hdemo_sk\": \"bigint\",\n        \"cr_refunded_addr_sk\": \"bigint\",\n        \"cr_returning_customer_sk\": \"bigint\",\n        \"cr_returning_cdemo_sk\": \"bigint\",\n        \"cr_returning_hdemo_sk\": \"bigint\",\n        \"cr_returning_addr_sk\": \"bigint\",\n        \"cr_call_center_sk\": \"bigint\",\n        \"cr_catalog_page_sk\": \"bigint\",\n        \"cr_ship_mode_sk\": \"bigint\",\n        \"cr_warehouse_sk\": \"bigint\",\n        \"cr_reason_sk\": \"bigint\",\n        \"cr_order_number\": \"bigint\",\n        \"cr_return_quantity\": \"bigint\",\n        \"cr_return_amount\": \"double\",\n        \"cr_return_tax\": \"double\",\n        \"cr_return_amt_inc_tax\": \"double\",\n        \"cr_fee\": \"double\",\n        \"cr_return_ship_cost\": \"double\",\n        \"cr_refunded_cash\": \"double\",\n        \"cr_reversed_charge\": \"double\",\n        \"cr_store_credit\": \"double\",\n        \"cr_net_loss\": \"double\",\n    },\n    \"inventory\": {\n        \"inv_date_sk\": \"bigint\",\n        \"inv_item_sk\": \"bigint\",\n        \"inv_warehouse_sk\": \"bigint\",\n        \"inv_quantity_on_hand\": \"bigint\",\n    },\n    \"store_sales\": {\n        \"ss_sold_date_sk\": \"bigint\",\n        \"ss_sold_time_sk\": \"bigint\",\n        \"ss_item_sk\": \"bigint\",\n        \"ss_customer_sk\": \"bigint\",\n        \"ss_cdemo_sk\": \"bigint\",\n        \"ss_hdemo_sk\": \"bigint\",\n        \"ss_addr_sk\": \"bigint\",\n        \"ss_store_sk\": \"bigint\",\n        \"ss_promo_sk\": \"bigint\",\n        \"ss_ticket_number\": \"bigint\",\n        \"ss_quantity\": \"bigint\",\n        \"ss_wholesale_cost\": \"double\",\n        \"ss_list_price\": \"double\",\n        \"ss_sales_price\": \"double\",\n        \"ss_ext_discount_amt\": \"double\",\n        \"ss_ext_sales_price\": \"double\",\n        \"ss_ext_wholesale_cost\": \"double\",\n        \"ss_ext_list_price\": \"double\",\n        \"ss_ext_tax\": \"double\",\n        \"ss_coupon_amt\": \"double\",\n        \"ss_net_paid\": \"double\",\n        \"ss_net_paid_inc_tax\": \"double\",\n        \"ss_net_profit\": \"double\",\n    },\n    \"store_returns\": {\n        \"sr_returned_date_sk\": \"bigint\",\n        \"sr_return_time_sk\": \"bigint\",\n        \"sr_item_sk\": \"bigint\",\n        \"sr_customer_sk\": \"bigint\",\n        \"sr_cdemo_sk\": \"bigint\",\n        \"sr_hdemo_sk\": \"bigint\",\n        \"sr_addr_sk\": \"bigint\",\n        \"sr_store_sk\": \"bigint\",\n        \"sr_reason_sk\": \"bigint\",\n        \"sr_ticket_number\": \"bigint\",\n        \"sr_return_quantity\": \"bigint\",\n        \"sr_return_amt\": \"double\",\n        \"sr_return_tax\": \"double\",\n        \"sr_return_amt_inc_tax\": \"double\",\n        \"sr_fee\": \"double\",\n        \"sr_return_ship_cost\": \"double\",\n        \"sr_refunded_cash\": \"double\",\n        \"sr_reversed_charge\": \"double\",\n        \"sr_store_credit\": \"double\",\n        \"sr_net_loss\": \"double\",\n    },\n    \"web_sales\": {\n        \"ws_sold_date_sk\": \"bigint\",\n        \"ws_sold_time_sk\": \"bigint\",\n        \"ws_ship_date_sk\": \"bigint\",\n        \"ws_item_sk\": \"bigint\",\n        \"ws_bill_customer_sk\": \"bigint\",\n        \"ws_bill_cdemo_sk\": \"bigint\",\n        \"ws_bill_hdemo_sk\": \"bigint\",\n        \"ws_bill_addr_sk\": \"bigint\",\n        \"ws_ship_customer_sk\": \"bigint\",\n        \"ws_ship_cdemo_sk\": \"bigint\",\n        \"ws_ship_hdemo_sk\": \"bigint\",\n        \"ws_ship_addr_sk\": \"bigint\",\n        \"ws_web_page_sk\": \"bigint\",\n        \"ws_web_site_sk\": \"bigint\",\n        \"ws_ship_mode_sk\": \"bigint\",\n        \"ws_warehouse_sk\": \"bigint\",\n        \"ws_promo_sk\": \"bigint\",\n        \"ws_order_number\": \"bigint\",\n        \"ws_quantity\": \"bigint\",\n        \"ws_wholesale_cost\": \"double\",\n        \"ws_list_price\": \"double\",\n        \"ws_sales_price\": \"double\",\n        \"ws_ext_discount_amt\": \"double\",\n        \"ws_ext_sales_price\": \"double\",\n        \"ws_ext_wholesale_cost\": \"double\",\n        \"ws_ext_list_price\": \"double\",\n        \"ws_ext_tax\": \"double\",\n        \"ws_coupon_amt\": \"double\",\n        \"ws_ext_ship_cost\": \"double\",\n        \"ws_net_paid\": \"double\",\n        \"ws_net_paid_inc_tax\": \"double\",\n        \"ws_net_paid_inc_ship\": \"double\",\n        \"ws_net_paid_inc_ship_tax\": \"double\",\n        \"ws_net_profit\": \"double\",\n    },\n    \"web_returns\": {\n        \"wr_returned_date_sk\": \"bigint\",\n        \"wr_returned_time_sk\": \"bigint\",\n        \"wr_item_sk\": \"bigint\",\n        \"wr_refunded_customer_sk\": \"bigint\",\n        \"wr_refunded_cdemo_sk\": \"bigint\",\n        \"wr_refunded_hdemo_sk\": \"bigint\",\n        \"wr_refunded_addr_sk\": \"bigint\",\n        \"wr_returning_customer_sk\": \"bigint\",\n        \"wr_returning_cdemo_sk\": \"bigint\",\n        \"wr_returning_hdemo_sk\": \"bigint\",\n        \"wr_returning_addr_sk\": \"bigint\",\n        \"wr_web_page_sk\": \"bigint\",\n        \"wr_reason_sk\": \"bigint\",\n        \"wr_order_number\": \"bigint\",\n        \"wr_return_quantity\": \"bigint\",\n        \"wr_return_amt\": \"double\",\n        \"wr_return_tax\": \"double\",\n        \"wr_return_amt_inc_tax\": \"double\",\n        \"wr_fee\": \"double\",\n        \"wr_return_ship_cost\": \"double\",\n        \"wr_refunded_cash\": \"double\",\n        \"wr_reversed_charge\": \"double\",\n        \"wr_account_credit\": \"double\",\n        \"wr_net_loss\": \"double\",\n    },\n    \"call_center\": {\n        \"cc_call_center_sk\": \"bigint\",\n        \"cc_call_center_id\": \"string\",\n        \"cc_rec_start_date\": \"string\",\n        \"cc_rec_end_date\": \"string\",\n        \"cc_closed_date_sk\": \"bigint\",\n        \"cc_open_date_sk\": \"bigint\",\n        \"cc_name\": \"string\",\n        \"cc_class\": \"string\",\n        \"cc_employees\": \"bigint\",\n        \"cc_sq_ft\": \"bigint\",\n        \"cc_hours\": \"string\",\n        \"cc_manager\": \"string\",\n        \"cc_mkt_id\": \"bigint\",\n        \"cc_mkt_class\": \"string\",\n        \"cc_mkt_desc\": \"string\",\n        \"cc_market_manager\": \"string\",\n        \"cc_division\": \"bigint\",\n        \"cc_division_name\": \"string\",\n        \"cc_company\": \"bigint\",\n        \"cc_company_name\": \"string\",\n        \"cc_street_number\": \"string\",\n        \"cc_street_name\": \"string\",\n        \"cc_street_type\": \"string\",\n        \"cc_suite_number\": \"string\",\n        \"cc_city\": \"string\",\n        \"cc_county\": \"string\",\n        \"cc_state\": \"string\",\n        \"cc_zip\": \"string\",\n        \"cc_country\": \"string\",\n        \"cc_gmt_offset\": \"double\",\n        \"cc_tax_percentage\": \"double\",\n    },\n    \"catalog_page\": {\n        \"cp_catalog_page_sk\": \"bigint\",\n        \"cp_catalog_page_id\": \"string\",\n        \"cp_start_date_sk\": \"bigint\",\n        \"cp_end_date_sk\": \"bigint\",\n        \"cp_department\": \"string\",\n        \"cp_catalog_number\": \"bigint\",\n        \"cp_catalog_page_number\": \"bigint\",\n        \"cp_description\": \"string\",\n        \"cp_type\": \"string\",\n    },\n    \"customer\": {\n        \"c_customer_sk\": \"bigint\",\n        \"c_customer_id\": \"string\",\n        \"c_current_cdemo_sk\": \"bigint\",\n        \"c_current_hdemo_sk\": \"bigint\",\n        \"c_current_addr_sk\": \"bigint\",\n        \"c_first_shipto_date_sk\": \"bigint\",\n        \"c_first_sales_date_sk\": \"bigint\",\n        \"c_salutation\": \"string\",\n        \"c_first_name\": \"string\",\n        \"c_last_name\": \"string\",\n        \"c_preferred_cust_flag\": \"string\",\n        \"c_birth_day\": \"bigint\",\n        \"c_birth_month\": \"bigint\",\n        \"c_birth_year\": \"bigint\",\n        \"c_birth_country\": \"string\",\n        \"c_login\": \"string\",\n        \"c_email_address\": \"string\",\n        \"c_last_review_date\": \"string\",\n    },\n    \"customer_address\": {\n        \"ca_address_sk\": \"bigint\",\n        \"ca_address_id\": \"string\",\n        \"ca_street_number\": \"string\",\n        \"ca_street_name\": \"string\",\n        \"ca_street_type\": \"string\",\n        \"ca_suite_number\": \"string\",\n        \"ca_city\": \"string\",\n        \"ca_county\": \"string\",\n        \"ca_state\": \"string\",\n        \"ca_zip\": \"string\",\n        \"ca_country\": \"string\",\n        \"ca_gmt_offset\": \"double\",\n        \"ca_location_type\": \"string\",\n    },\n    \"customer_demographics\": {\n        \"cd_demo_sk\": \"bigint\",\n        \"cd_gender\": \"string\",\n        \"cd_marital_status\": \"string\",\n        \"cd_education_status\": \"string\",\n        \"cd_purchase_estimate\": \"bigint\",\n        \"cd_credit_rating\": \"string\",\n        \"cd_dep_count\": \"bigint\",\n        \"cd_dep_employed_count\": \"bigint\",\n        \"cd_dep_college_count\": \"bigint\",\n    },\n    \"date_dim\": {\n        \"d_date_sk\": \"bigint\",\n        \"d_date_id\": \"string\",\n        \"d_date\": \"string\",\n        \"d_month_seq\": \"bigint\",\n        \"d_week_seq\": \"bigint\",\n        \"d_quarter_seq\": \"bigint\",\n        \"d_year\": \"bigint\",\n        \"d_dow\": \"bigint\",\n        \"d_moy\": \"bigint\",\n        \"d_dom\": \"bigint\",\n        \"d_qoy\": \"bigint\",\n        \"d_fy_year\": \"bigint\",\n        \"d_fy_quarter_seq\": \"bigint\",\n        \"d_fy_week_seq\": \"bigint\",\n        \"d_day_name\": \"string\",\n        \"d_quarter_name\": \"string\",\n        \"d_holiday\": \"string\",\n        \"d_weekend\": \"string\",\n        \"d_following_holiday\": \"string\",\n        \"d_first_dom\": \"bigint\",\n        \"d_last_dom\": \"bigint\",\n        \"d_same_day_ly\": \"bigint\",\n        \"d_same_day_lq\": \"bigint\",\n        \"d_current_day\": \"string\",\n        \"d_current_week\": \"string\",\n        \"d_current_month\": \"string\",\n        \"d_current_quarter\": \"string\",\n        \"d_current_year\": \"string\",\n    },\n    \"household_demographics\": {\n        \"hd_demo_sk\": \"bigint\",\n        \"hd_income_band_sk\": \"bigint\",\n        \"hd_buy_potential\": \"string\",\n        \"hd_dep_count\": \"bigint\",\n        \"hd_vehicle_count\": \"bigint\",\n    },\n    \"income_band\": {\n        \"ib_income_band_sk\": \"bigint\",\n        \"ib_lower_bound\": \"bigint\",\n        \"ib_upper_bound\": \"bigint\",\n    },\n    \"item\": {\n        \"i_item_sk\": \"bigint\",\n        \"i_item_id\": \"string\",\n        \"i_rec_start_date\": \"string\",\n        \"i_rec_end_date\": \"string\",\n        \"i_item_desc\": \"string\",\n        \"i_current_price\": \"double\",\n        \"i_wholesale_cost\": \"double\",\n        \"i_brand_id\": \"bigint\",\n        \"i_brand\": \"string\",\n        \"i_class_id\": \"bigint\",\n        \"i_class\": \"string\",\n        \"i_category_id\": \"bigint\",\n        \"i_category\": \"string\",\n        \"i_manufact_id\": \"bigint\",\n        \"i_manufact\": \"string\",\n        \"i_size\": \"string\",\n        \"i_formulation\": \"string\",\n        \"i_color\": \"string\",\n        \"i_units\": \"string\",\n        \"i_container\": \"string\",\n        \"i_manager_id\": \"bigint\",\n        \"i_product_name\": \"string\",\n    },\n    \"promotion\": {\n        \"p_promo_sk\": \"bigint\",\n        \"p_promo_id\": \"string\",\n        \"p_start_date_sk\": \"bigint\",\n        \"p_end_date_sk\": \"bigint\",\n        \"p_item_sk\": \"bigint\",\n        \"p_cost\": \"double\",\n        \"p_response_target\": \"bigint\",\n        \"p_promo_name\": \"string\",\n        \"p_channel_dmail\": \"string\",\n        \"p_channel_email\": \"string\",\n        \"p_channel_catalog\": \"string\",\n        \"p_channel_tv\": \"string\",\n        \"p_channel_radio\": \"string\",\n        \"p_channel_press\": \"string\",\n        \"p_channel_event\": \"string\",\n        \"p_channel_demo\": \"string\",\n        \"p_channel_details\": \"string\",\n        \"p_purpose\": \"string\",\n        \"p_discount_active\": \"string\",\n    },\n    \"reason\": {\"r_reason_sk\": \"bigint\", \"r_reason_id\": \"string\", \"r_reason_desc\": \"string\"},\n    \"ship_mode\": {\n        \"sm_ship_mode_sk\": \"bigint\",\n        \"sm_ship_mode_id\": \"string\",\n        \"sm_type\": \"string\",\n        \"sm_code\": \"string\",\n        \"sm_carrier\": \"string\",\n        \"sm_contract\": \"string\",\n    },\n    \"store\": {\n        \"s_store_sk\": \"bigint\",\n        \"s_store_id\": \"string\",\n        \"s_rec_start_date\": \"string\",\n        \"s_rec_end_date\": \"string\",\n        \"s_closed_date_sk\": \"bigint\",\n        \"s_store_name\": \"string\",\n        \"s_number_employees\": \"bigint\",\n        \"s_floor_space\": \"bigint\",\n        \"s_hours\": \"string\",\n        \"s_manager\": \"string\",\n        \"s_market_id\": \"bigint\",\n        \"s_geography_class\": \"string\",\n        \"s_market_desc\": \"string\",\n        \"s_market_manager\": \"string\",\n        \"s_division_id\": \"bigint\",\n        \"s_division_name\": \"string\",\n        \"s_company_id\": \"bigint\",\n        \"s_company_name\": \"string\",\n        \"s_street_number\": \"string\",\n        \"s_street_name\": \"string\",\n        \"s_street_type\": \"string\",\n        \"s_suite_number\": \"string\",\n        \"s_city\": \"string\",\n        \"s_county\": \"string\",\n        \"s_state\": \"string\",\n        \"s_zip\": \"string\",\n        \"s_country\": \"string\",\n        \"s_gmt_offset\": \"double\",\n        \"s_tax_precentage\": \"double\",\n    },\n    \"time_dim\": {\n        \"t_time_sk\": \"bigint\",\n        \"t_time_id\": \"string\",\n        \"t_time\": \"bigint\",\n        \"t_hour\": \"bigint\",\n        \"t_minute\": \"bigint\",\n        \"t_second\": \"bigint\",\n        \"t_am_pm\": \"string\",\n        \"t_shift\": \"string\",\n        \"t_sub_shift\": \"string\",\n        \"t_meal_time\": \"string\",\n    },\n    \"warehouse\": {\n        \"w_warehouse_sk\": \"bigint\",\n        \"w_warehouse_id\": \"string\",\n        \"w_warehouse_name\": \"string\",\n        \"w_warehouse_sq_ft\": \"bigint\",\n        \"w_street_number\": \"string\",\n        \"w_street_name\": \"string\",\n        \"w_street_type\": \"string\",\n        \"w_suite_number\": \"string\",\n        \"w_city\": \"string\",\n        \"w_county\": \"string\",\n        \"w_state\": \"string\",\n        \"w_zip\": \"string\",\n        \"w_country\": \"string\",\n        \"w_gmt_offset\": \"double\",\n    },\n    \"web_page\": {\n        \"wp_web_page_sk\": \"bigint\",\n        \"wp_web_page_id\": \"string\",\n        \"wp_rec_start_date\": \"string\",\n        \"wp_rec_end_date\": \"string\",\n        \"wp_creation_date_sk\": \"bigint\",\n        \"wp_access_date_sk\": \"bigint\",\n        \"wp_autogen_flag\": \"string\",\n        \"wp_customer_sk\": \"bigint\",\n        \"wp_url\": \"string\",\n        \"wp_type\": \"string\",\n        \"wp_char_count\": \"bigint\",\n        \"wp_link_count\": \"bigint\",\n        \"wp_image_count\": \"bigint\",\n        \"wp_max_ad_count\": \"bigint\",\n    },\n    \"web_site\": {\n        \"web_site_sk\": \"bigint\",\n        \"web_site_id\": \"string\",\n        \"web_rec_start_date\": \"string\",\n        \"web_rec_end_date\": \"string\",\n        \"web_name\": \"string\",\n        \"web_open_date_sk\": \"bigint\",\n        \"web_close_date_sk\": \"bigint\",\n        \"web_class\": \"string\",\n        \"web_manager\": \"string\",\n        \"web_mkt_id\": \"bigint\",\n        \"web_mkt_class\": \"string\",\n        \"web_mkt_desc\": \"string\",\n        \"web_market_manager\": \"string\",\n        \"web_company_id\": \"bigint\",\n        \"web_company_name\": \"string\",\n        \"web_street_number\": \"string\",\n        \"web_street_name\": \"string\",\n        \"web_street_type\": \"string\",\n        \"web_suite_number\": \"string\",\n        \"web_city\": \"string\",\n        \"web_county\": \"string\",\n        \"web_state\": \"string\",\n        \"web_zip\": \"string\",\n        \"web_country\": \"string\",\n        \"web_gmt_offset\": \"string\",\n        \"web_tax_percentage\": \"double\",\n    },\n}\n\n\ndef rewrite_fixtures(in_path, out_path, schema, num, kind):\n    with open(out_path, \"w\", encoding=\"utf-8\") as fixture:\n        for i in range(num):\n            i = i + 1\n            with open(in_path.format(i=i), encoding=\"utf-8\") as file:\n                original = \"\\n\".join(\n                    line.rstrip()\n                    for line in file.read().split(\";\")[0].split(\"\\n\")\n                    if not line.startswith(\"--\")\n                )\n                original = original.replace(\"`\", '\"').strip()\n                now = time.time()\n                try:\n                    optimized = optimize(original, schema=schema)\n                except Exception as e:\n                    print(\"****\", i, e, \"****\")\n                    continue\n\n                fixture.write(\n                    f\"\"\"--------------------------------------\n-- TPC-{kind} {i}\n--------------------------------------\n{original};\n{optimized.sql(pretty=True)};\n\n\"\"\"\n                )\n                print(i, time.time() - now)\n\n\nrewrite_fixtures(\n    \"/home/toby/dev/tpch/{i}.sql\",\n    \"/home/toby/dev/sqlglot/tests/fixtures/optimizer/tpc-h/tpc-h.sql\",\n    TPCH_SCHEMA,\n    22,\n    \"H\",\n)\n\nrewrite_fixtures(\n    \"/home/toby/dev/tpcds/query{i}.sql\",\n    \"/home/toby/dev/sqlglot/tests/fixtures/optimizer/tpc-ds/tpc-ds.sql\",\n    TPCDS_SCHEMA,\n    99,\n    \"DS\",\n)\n"
  },
  {
    "path": "tests/helpers.py",
    "content": "import os\n\nFILE_DIR = os.path.dirname(__file__)\nFIXTURES_DIR = os.path.join(FILE_DIR, \"fixtures\")\n\n\ndef _filter_comments(s):\n    return \"\\n\".join([line for line in s.splitlines() if line and not line.startswith(\"--\")])\n\n\ndef _extract_meta(sql):\n    meta = {}\n    sql_lines = sql.split(\"\\n\")\n    i = 0\n    while sql_lines[i].startswith(\"#\"):\n        key, val = sql_lines[i].split(\":\", maxsplit=1)\n        meta[key.lstrip(\"#\").strip()] = val.strip()\n        i += 1\n    sql = \"\\n\".join(sql_lines[i:])\n    return sql, meta\n\n\ndef assert_logger_contains(message, logger, level=\"error\"):\n    output = \"\\n\".join(str(args[0][0]) for args in getattr(logger, level).call_args_list)\n    if message not in output:\n        print(f\"Expected '{message}' not in {output}\")\n        raise\n\n\ndef load_sql_fixtures(filename):\n    with open(os.path.join(FIXTURES_DIR, filename), encoding=\"utf-8\") as f:\n        yield from _filter_comments(f.read()).splitlines()\n\n\ndef load_sql_fixture_pairs(filename):\n    with open(os.path.join(FIXTURES_DIR, filename), encoding=\"utf-8\") as f:\n        statements = _filter_comments(f.read()).split(\";\")\n\n        size = len(statements)\n\n        for i in range(0, size, 2):\n            if i + 1 < size:\n                sql = statements[i].strip()\n                sql, meta = _extract_meta(sql)\n                expected = statements[i + 1].strip()\n                yield meta, sql, expected\n\n\ndef string_to_bool(string):\n    if string is None:\n        return False\n    if string in (True, False):\n        return string\n    return string and string.lower() in (\"true\", \"1\")\n\n\nSKIP_INTEGRATION = string_to_bool(os.environ.get(\"SKIP_INTEGRATION\", \"0\").lower())\n\nTPCH_SCHEMA = {\n    \"lineitem\": {\n        \"l_orderkey\": \"bigint\",\n        \"l_partkey\": \"bigint\",\n        \"l_suppkey\": \"bigint\",\n        \"l_linenumber\": \"bigint\",\n        \"l_quantity\": \"double\",\n        \"l_extendedprice\": \"double\",\n        \"l_discount\": \"double\",\n        \"l_tax\": \"double\",\n        \"l_returnflag\": \"string\",\n        \"l_linestatus\": \"string\",\n        \"l_shipdate\": \"string\",\n        \"l_commitdate\": \"string\",\n        \"l_receiptdate\": \"string\",\n        \"l_shipinstruct\": \"string\",\n        \"l_shipmode\": \"string\",\n        \"l_comment\": \"string\",\n    },\n    \"orders\": {\n        \"o_orderkey\": \"bigint\",\n        \"o_custkey\": \"bigint\",\n        \"o_orderstatus\": \"string\",\n        \"o_totalprice\": \"double\",\n        \"o_orderdate\": \"string\",\n        \"o_orderpriority\": \"string\",\n        \"o_clerk\": \"string\",\n        \"o_shippriority\": \"int\",\n        \"o_comment\": \"string\",\n    },\n    \"customer\": {\n        \"c_custkey\": \"bigint\",\n        \"c_name\": \"string\",\n        \"c_address\": \"string\",\n        \"c_nationkey\": \"bigint\",\n        \"c_phone\": \"string\",\n        \"c_acctbal\": \"double\",\n        \"c_mktsegment\": \"string\",\n        \"c_comment\": \"string\",\n    },\n    \"part\": {\n        \"p_partkey\": \"bigint\",\n        \"p_name\": \"string\",\n        \"p_mfgr\": \"string\",\n        \"p_brand\": \"string\",\n        \"p_type\": \"string\",\n        \"p_size\": \"int\",\n        \"p_container\": \"string\",\n        \"p_retailprice\": \"double\",\n        \"p_comment\": \"string\",\n    },\n    \"supplier\": {\n        \"s_suppkey\": \"bigint\",\n        \"s_name\": \"string\",\n        \"s_address\": \"string\",\n        \"s_nationkey\": \"bigint\",\n        \"s_phone\": \"string\",\n        \"s_acctbal\": \"double\",\n        \"s_comment\": \"string\",\n    },\n    \"partsupp\": {\n        \"ps_partkey\": \"bigint\",\n        \"ps_suppkey\": \"bigint\",\n        \"ps_availqty\": \"int\",\n        \"ps_supplycost\": \"double\",\n        \"ps_comment\": \"string\",\n    },\n    \"nation\": {\n        \"n_nationkey\": \"bigint\",\n        \"n_name\": \"string\",\n        \"n_regionkey\": \"bigint\",\n        \"n_comment\": \"string\",\n    },\n    \"region\": {\n        \"r_regionkey\": \"bigint\",\n        \"r_name\": \"string\",\n        \"r_comment\": \"string\",\n    },\n}\n\nTPCDS_SCHEMA = {\n    \"catalog_sales\": {\n        \"cs_sold_date_sk\": \"bigint\",\n        \"cs_sold_time_sk\": \"bigint\",\n        \"cs_ship_date_sk\": \"bigint\",\n        \"cs_bill_customer_sk\": \"bigint\",\n        \"cs_bill_cdemo_sk\": \"bigint\",\n        \"cs_bill_hdemo_sk\": \"bigint\",\n        \"cs_bill_addr_sk\": \"bigint\",\n        \"cs_ship_customer_sk\": \"bigint\",\n        \"cs_ship_cdemo_sk\": \"bigint\",\n        \"cs_ship_hdemo_sk\": \"bigint\",\n        \"cs_ship_addr_sk\": \"bigint\",\n        \"cs_call_center_sk\": \"bigint\",\n        \"cs_catalog_page_sk\": \"bigint\",\n        \"cs_ship_mode_sk\": \"bigint\",\n        \"cs_warehouse_sk\": \"bigint\",\n        \"cs_item_sk\": \"bigint\",\n        \"cs_promo_sk\": \"bigint\",\n        \"cs_order_number\": \"bigint\",\n        \"cs_quantity\": \"bigint\",\n        \"cs_wholesale_cost\": \"double\",\n        \"cs_list_price\": \"double\",\n        \"cs_sales_price\": \"double\",\n        \"cs_ext_discount_amt\": \"double\",\n        \"cs_ext_sales_price\": \"double\",\n        \"cs_ext_wholesale_cost\": \"double\",\n        \"cs_ext_list_price\": \"double\",\n        \"cs_ext_tax\": \"double\",\n        \"cs_coupon_amt\": \"double\",\n        \"cs_ext_ship_cost\": \"double\",\n        \"cs_net_paid\": \"double\",\n        \"cs_net_paid_inc_tax\": \"double\",\n        \"cs_net_paid_inc_ship\": \"double\",\n        \"cs_net_paid_inc_ship_tax\": \"double\",\n        \"cs_net_profit\": \"double\",\n    },\n    \"catalog_returns\": {\n        \"cr_returned_date_sk\": \"bigint\",\n        \"cr_returned_time_sk\": \"bigint\",\n        \"cr_item_sk\": \"bigint\",\n        \"cr_refunded_customer_sk\": \"bigint\",\n        \"cr_refunded_cdemo_sk\": \"bigint\",\n        \"cr_refunded_hdemo_sk\": \"bigint\",\n        \"cr_refunded_addr_sk\": \"bigint\",\n        \"cr_returning_customer_sk\": \"bigint\",\n        \"cr_returning_cdemo_sk\": \"bigint\",\n        \"cr_returning_hdemo_sk\": \"bigint\",\n        \"cr_returning_addr_sk\": \"bigint\",\n        \"cr_call_center_sk\": \"bigint\",\n        \"cr_catalog_page_sk\": \"bigint\",\n        \"cr_ship_mode_sk\": \"bigint\",\n        \"cr_warehouse_sk\": \"bigint\",\n        \"cr_reason_sk\": \"bigint\",\n        \"cr_order_number\": \"bigint\",\n        \"cr_return_quantity\": \"bigint\",\n        \"cr_return_amount\": \"double\",\n        \"cr_return_tax\": \"double\",\n        \"cr_return_amt_inc_tax\": \"double\",\n        \"cr_fee\": \"double\",\n        \"cr_return_ship_cost\": \"double\",\n        \"cr_refunded_cash\": \"double\",\n        \"cr_reversed_charge\": \"double\",\n        \"cr_store_credit\": \"double\",\n        \"cr_net_loss\": \"double\",\n    },\n    \"inventory\": {\n        \"inv_date_sk\": \"bigint\",\n        \"inv_item_sk\": \"bigint\",\n        \"inv_warehouse_sk\": \"bigint\",\n        \"inv_quantity_on_hand\": \"bigint\",\n    },\n    \"store_sales\": {\n        \"ss_sold_date_sk\": \"bigint\",\n        \"ss_sold_time_sk\": \"bigint\",\n        \"ss_item_sk\": \"bigint\",\n        \"ss_customer_sk\": \"bigint\",\n        \"ss_cdemo_sk\": \"bigint\",\n        \"ss_hdemo_sk\": \"bigint\",\n        \"ss_addr_sk\": \"bigint\",\n        \"ss_store_sk\": \"bigint\",\n        \"ss_promo_sk\": \"bigint\",\n        \"ss_ticket_number\": \"bigint\",\n        \"ss_quantity\": \"bigint\",\n        \"ss_wholesale_cost\": \"double\",\n        \"ss_list_price\": \"double\",\n        \"ss_sales_price\": \"double\",\n        \"ss_ext_discount_amt\": \"double\",\n        \"ss_ext_sales_price\": \"double\",\n        \"ss_ext_wholesale_cost\": \"double\",\n        \"ss_ext_list_price\": \"double\",\n        \"ss_ext_tax\": \"double\",\n        \"ss_coupon_amt\": \"double\",\n        \"ss_net_paid\": \"double\",\n        \"ss_net_paid_inc_tax\": \"double\",\n        \"ss_net_profit\": \"double\",\n    },\n    \"store_returns\": {\n        \"sr_returned_date_sk\": \"bigint\",\n        \"sr_return_time_sk\": \"bigint\",\n        \"sr_item_sk\": \"bigint\",\n        \"sr_customer_sk\": \"bigint\",\n        \"sr_cdemo_sk\": \"bigint\",\n        \"sr_hdemo_sk\": \"bigint\",\n        \"sr_addr_sk\": \"bigint\",\n        \"sr_store_sk\": \"bigint\",\n        \"sr_reason_sk\": \"bigint\",\n        \"sr_ticket_number\": \"bigint\",\n        \"sr_return_quantity\": \"bigint\",\n        \"sr_return_amt\": \"double\",\n        \"sr_return_tax\": \"double\",\n        \"sr_return_amt_inc_tax\": \"double\",\n        \"sr_fee\": \"double\",\n        \"sr_return_ship_cost\": \"double\",\n        \"sr_refunded_cash\": \"double\",\n        \"sr_reversed_charge\": \"double\",\n        \"sr_store_credit\": \"double\",\n        \"sr_net_loss\": \"double\",\n    },\n    \"web_sales\": {\n        \"ws_sold_date_sk\": \"bigint\",\n        \"ws_sold_time_sk\": \"bigint\",\n        \"ws_ship_date_sk\": \"bigint\",\n        \"ws_item_sk\": \"bigint\",\n        \"ws_bill_customer_sk\": \"bigint\",\n        \"ws_bill_cdemo_sk\": \"bigint\",\n        \"ws_bill_hdemo_sk\": \"bigint\",\n        \"ws_bill_addr_sk\": \"bigint\",\n        \"ws_ship_customer_sk\": \"bigint\",\n        \"ws_ship_cdemo_sk\": \"bigint\",\n        \"ws_ship_hdemo_sk\": \"bigint\",\n        \"ws_ship_addr_sk\": \"bigint\",\n        \"ws_web_page_sk\": \"bigint\",\n        \"ws_web_site_sk\": \"bigint\",\n        \"ws_ship_mode_sk\": \"bigint\",\n        \"ws_warehouse_sk\": \"bigint\",\n        \"ws_promo_sk\": \"bigint\",\n        \"ws_order_number\": \"bigint\",\n        \"ws_quantity\": \"bigint\",\n        \"ws_wholesale_cost\": \"double\",\n        \"ws_list_price\": \"double\",\n        \"ws_sales_price\": \"double\",\n        \"ws_ext_discount_amt\": \"double\",\n        \"ws_ext_sales_price\": \"double\",\n        \"ws_ext_wholesale_cost\": \"double\",\n        \"ws_ext_list_price\": \"double\",\n        \"ws_ext_tax\": \"double\",\n        \"ws_coupon_amt\": \"double\",\n        \"ws_ext_ship_cost\": \"double\",\n        \"ws_net_paid\": \"double\",\n        \"ws_net_paid_inc_tax\": \"double\",\n        \"ws_net_paid_inc_ship\": \"double\",\n        \"ws_net_paid_inc_ship_tax\": \"double\",\n        \"ws_net_profit\": \"double\",\n    },\n    \"web_returns\": {\n        \"wr_returned_date_sk\": \"bigint\",\n        \"wr_returned_time_sk\": \"bigint\",\n        \"wr_item_sk\": \"bigint\",\n        \"wr_refunded_customer_sk\": \"bigint\",\n        \"wr_refunded_cdemo_sk\": \"bigint\",\n        \"wr_refunded_hdemo_sk\": \"bigint\",\n        \"wr_refunded_addr_sk\": \"bigint\",\n        \"wr_returning_customer_sk\": \"bigint\",\n        \"wr_returning_cdemo_sk\": \"bigint\",\n        \"wr_returning_hdemo_sk\": \"bigint\",\n        \"wr_returning_addr_sk\": \"bigint\",\n        \"wr_web_page_sk\": \"bigint\",\n        \"wr_reason_sk\": \"bigint\",\n        \"wr_order_number\": \"bigint\",\n        \"wr_return_quantity\": \"bigint\",\n        \"wr_return_amt\": \"double\",\n        \"wr_return_tax\": \"double\",\n        \"wr_return_amt_inc_tax\": \"double\",\n        \"wr_fee\": \"double\",\n        \"wr_return_ship_cost\": \"double\",\n        \"wr_refunded_cash\": \"double\",\n        \"wr_reversed_charge\": \"double\",\n        \"wr_account_credit\": \"double\",\n        \"wr_net_loss\": \"double\",\n    },\n    \"call_center\": {\n        \"cc_call_center_sk\": \"bigint\",\n        \"cc_call_center_id\": \"string\",\n        \"cc_rec_start_date\": \"string\",\n        \"cc_rec_end_date\": \"string\",\n        \"cc_closed_date_sk\": \"bigint\",\n        \"cc_open_date_sk\": \"bigint\",\n        \"cc_name\": \"string\",\n        \"cc_class\": \"string\",\n        \"cc_employees\": \"bigint\",\n        \"cc_sq_ft\": \"bigint\",\n        \"cc_hours\": \"string\",\n        \"cc_manager\": \"string\",\n        \"cc_mkt_id\": \"bigint\",\n        \"cc_mkt_class\": \"string\",\n        \"cc_mkt_desc\": \"string\",\n        \"cc_market_manager\": \"string\",\n        \"cc_division\": \"bigint\",\n        \"cc_division_name\": \"string\",\n        \"cc_company\": \"bigint\",\n        \"cc_company_name\": \"string\",\n        \"cc_street_number\": \"string\",\n        \"cc_street_name\": \"string\",\n        \"cc_street_type\": \"string\",\n        \"cc_suite_number\": \"string\",\n        \"cc_city\": \"string\",\n        \"cc_county\": \"string\",\n        \"cc_state\": \"string\",\n        \"cc_zip\": \"string\",\n        \"cc_country\": \"string\",\n        \"cc_gmt_offset\": \"double\",\n        \"cc_tax_percentage\": \"double\",\n    },\n    \"catalog_page\": {\n        \"cp_catalog_page_sk\": \"bigint\",\n        \"cp_catalog_page_id\": \"string\",\n        \"cp_start_date_sk\": \"bigint\",\n        \"cp_end_date_sk\": \"bigint\",\n        \"cp_department\": \"string\",\n        \"cp_catalog_number\": \"bigint\",\n        \"cp_catalog_page_number\": \"bigint\",\n        \"cp_description\": \"string\",\n        \"cp_type\": \"string\",\n    },\n    \"customer\": {\n        \"c_customer_sk\": \"bigint\",\n        \"c_customer_id\": \"string\",\n        \"c_current_cdemo_sk\": \"bigint\",\n        \"c_current_hdemo_sk\": \"bigint\",\n        \"c_current_addr_sk\": \"bigint\",\n        \"c_first_shipto_date_sk\": \"bigint\",\n        \"c_first_sales_date_sk\": \"bigint\",\n        \"c_salutation\": \"string\",\n        \"c_first_name\": \"string\",\n        \"c_last_name\": \"string\",\n        \"c_preferred_cust_flag\": \"string\",\n        \"c_birth_day\": \"bigint\",\n        \"c_birth_month\": \"bigint\",\n        \"c_birth_year\": \"bigint\",\n        \"c_birth_country\": \"string\",\n        \"c_login\": \"string\",\n        \"c_email_address\": \"string\",\n        \"c_last_review_date\": \"string\",\n    },\n    \"customer_address\": {\n        \"ca_address_sk\": \"bigint\",\n        \"ca_address_id\": \"string\",\n        \"ca_street_number\": \"string\",\n        \"ca_street_name\": \"string\",\n        \"ca_street_type\": \"string\",\n        \"ca_suite_number\": \"string\",\n        \"ca_city\": \"string\",\n        \"ca_county\": \"string\",\n        \"ca_state\": \"string\",\n        \"ca_zip\": \"string\",\n        \"ca_country\": \"string\",\n        \"ca_gmt_offset\": \"double\",\n        \"ca_location_type\": \"string\",\n    },\n    \"customer_demographics\": {\n        \"cd_demo_sk\": \"bigint\",\n        \"cd_gender\": \"string\",\n        \"cd_marital_status\": \"string\",\n        \"cd_education_status\": \"string\",\n        \"cd_purchase_estimate\": \"bigint\",\n        \"cd_credit_rating\": \"string\",\n        \"cd_dep_count\": \"bigint\",\n        \"cd_dep_employed_count\": \"bigint\",\n        \"cd_dep_college_count\": \"bigint\",\n    },\n    \"date_dim\": {\n        \"d_date_sk\": \"bigint\",\n        \"d_date_id\": \"string\",\n        \"d_date\": \"string\",\n        \"d_month_seq\": \"bigint\",\n        \"d_week_seq\": \"bigint\",\n        \"d_quarter_seq\": \"bigint\",\n        \"d_year\": \"bigint\",\n        \"d_dow\": \"bigint\",\n        \"d_moy\": \"bigint\",\n        \"d_dom\": \"bigint\",\n        \"d_qoy\": \"bigint\",\n        \"d_fy_year\": \"bigint\",\n        \"d_fy_quarter_seq\": \"bigint\",\n        \"d_fy_week_seq\": \"bigint\",\n        \"d_day_name\": \"string\",\n        \"d_quarter_name\": \"string\",\n        \"d_holiday\": \"string\",\n        \"d_weekend\": \"string\",\n        \"d_following_holiday\": \"string\",\n        \"d_first_dom\": \"bigint\",\n        \"d_last_dom\": \"bigint\",\n        \"d_same_day_ly\": \"bigint\",\n        \"d_same_day_lq\": \"bigint\",\n        \"d_current_day\": \"string\",\n        \"d_current_week\": \"string\",\n        \"d_current_month\": \"string\",\n        \"d_current_quarter\": \"string\",\n        \"d_current_year\": \"string\",\n    },\n    \"household_demographics\": {\n        \"hd_demo_sk\": \"bigint\",\n        \"hd_income_band_sk\": \"bigint\",\n        \"hd_buy_potential\": \"string\",\n        \"hd_dep_count\": \"bigint\",\n        \"hd_vehicle_count\": \"bigint\",\n    },\n    \"income_band\": {\n        \"ib_income_band_sk\": \"bigint\",\n        \"ib_lower_bound\": \"bigint\",\n        \"ib_upper_bound\": \"bigint\",\n    },\n    \"item\": {\n        \"i_item_sk\": \"bigint\",\n        \"i_item_id\": \"string\",\n        \"i_rec_start_date\": \"string\",\n        \"i_rec_end_date\": \"string\",\n        \"i_item_desc\": \"string\",\n        \"i_current_price\": \"double\",\n        \"i_wholesale_cost\": \"double\",\n        \"i_brand_id\": \"bigint\",\n        \"i_brand\": \"string\",\n        \"i_class_id\": \"bigint\",\n        \"i_class\": \"string\",\n        \"i_category_id\": \"bigint\",\n        \"i_category\": \"string\",\n        \"i_manufact_id\": \"bigint\",\n        \"i_manufact\": \"string\",\n        \"i_size\": \"string\",\n        \"i_formulation\": \"string\",\n        \"i_color\": \"string\",\n        \"i_units\": \"string\",\n        \"i_container\": \"string\",\n        \"i_manager_id\": \"bigint\",\n        \"i_product_name\": \"string\",\n    },\n    \"promotion\": {\n        \"p_promo_sk\": \"bigint\",\n        \"p_promo_id\": \"string\",\n        \"p_start_date_sk\": \"bigint\",\n        \"p_end_date_sk\": \"bigint\",\n        \"p_item_sk\": \"bigint\",\n        \"p_cost\": \"double\",\n        \"p_response_target\": \"bigint\",\n        \"p_promo_name\": \"string\",\n        \"p_channel_dmail\": \"string\",\n        \"p_channel_email\": \"string\",\n        \"p_channel_catalog\": \"string\",\n        \"p_channel_tv\": \"string\",\n        \"p_channel_radio\": \"string\",\n        \"p_channel_press\": \"string\",\n        \"p_channel_event\": \"string\",\n        \"p_channel_demo\": \"string\",\n        \"p_channel_details\": \"string\",\n        \"p_purpose\": \"string\",\n        \"p_discount_active\": \"string\",\n    },\n    \"reason\": {\"r_reason_sk\": \"bigint\", \"r_reason_id\": \"string\", \"r_reason_desc\": \"string\"},\n    \"ship_mode\": {\n        \"sm_ship_mode_sk\": \"bigint\",\n        \"sm_ship_mode_id\": \"string\",\n        \"sm_type\": \"string\",\n        \"sm_code\": \"string\",\n        \"sm_carrier\": \"string\",\n        \"sm_contract\": \"string\",\n    },\n    \"store\": {\n        \"s_store_sk\": \"bigint\",\n        \"s_store_id\": \"string\",\n        \"s_rec_start_date\": \"string\",\n        \"s_rec_end_date\": \"string\",\n        \"s_closed_date_sk\": \"bigint\",\n        \"s_store_name\": \"string\",\n        \"s_number_employees\": \"bigint\",\n        \"s_floor_space\": \"bigint\",\n        \"s_hours\": \"string\",\n        \"s_manager\": \"string\",\n        \"s_market_id\": \"bigint\",\n        \"s_geography_class\": \"string\",\n        \"s_market_desc\": \"string\",\n        \"s_market_manager\": \"string\",\n        \"s_division_id\": \"bigint\",\n        \"s_division_name\": \"string\",\n        \"s_company_id\": \"bigint\",\n        \"s_company_name\": \"string\",\n        \"s_street_number\": \"string\",\n        \"s_street_name\": \"string\",\n        \"s_street_type\": \"string\",\n        \"s_suite_number\": \"string\",\n        \"s_city\": \"string\",\n        \"s_county\": \"string\",\n        \"s_state\": \"string\",\n        \"s_zip\": \"string\",\n        \"s_country\": \"string\",\n        \"s_gmt_offset\": \"double\",\n        \"s_tax_precentage\": \"double\",\n    },\n    \"time_dim\": {\n        \"t_time_sk\": \"bigint\",\n        \"t_time_id\": \"string\",\n        \"t_time\": \"bigint\",\n        \"t_hour\": \"bigint\",\n        \"t_minute\": \"bigint\",\n        \"t_second\": \"bigint\",\n        \"t_am_pm\": \"string\",\n        \"t_shift\": \"string\",\n        \"t_sub_shift\": \"string\",\n        \"t_meal_time\": \"string\",\n    },\n    \"warehouse\": {\n        \"w_warehouse_sk\": \"bigint\",\n        \"w_warehouse_id\": \"string\",\n        \"w_warehouse_name\": \"string\",\n        \"w_warehouse_sq_ft\": \"bigint\",\n        \"w_street_number\": \"string\",\n        \"w_street_name\": \"string\",\n        \"w_street_type\": \"string\",\n        \"w_suite_number\": \"string\",\n        \"w_city\": \"string\",\n        \"w_county\": \"string\",\n        \"w_state\": \"string\",\n        \"w_zip\": \"string\",\n        \"w_country\": \"string\",\n        \"w_gmt_offset\": \"double\",\n    },\n    \"web_page\": {\n        \"wp_web_page_sk\": \"bigint\",\n        \"wp_web_page_id\": \"string\",\n        \"wp_rec_start_date\": \"string\",\n        \"wp_rec_end_date\": \"string\",\n        \"wp_creation_date_sk\": \"bigint\",\n        \"wp_access_date_sk\": \"bigint\",\n        \"wp_autogen_flag\": \"string\",\n        \"wp_customer_sk\": \"bigint\",\n        \"wp_url\": \"string\",\n        \"wp_type\": \"string\",\n        \"wp_char_count\": \"bigint\",\n        \"wp_link_count\": \"bigint\",\n        \"wp_image_count\": \"bigint\",\n        \"wp_max_ad_count\": \"bigint\",\n    },\n    \"web_site\": {\n        \"web_site_sk\": \"bigint\",\n        \"web_site_id\": \"string\",\n        \"web_rec_start_date\": \"string\",\n        \"web_rec_end_date\": \"string\",\n        \"web_name\": \"string\",\n        \"web_open_date_sk\": \"bigint\",\n        \"web_close_date_sk\": \"bigint\",\n        \"web_class\": \"string\",\n        \"web_manager\": \"string\",\n        \"web_mkt_id\": \"bigint\",\n        \"web_mkt_class\": \"string\",\n        \"web_mkt_desc\": \"string\",\n        \"web_market_manager\": \"string\",\n        \"web_company_id\": \"bigint\",\n        \"web_company_name\": \"string\",\n        \"web_street_number\": \"string\",\n        \"web_street_name\": \"string\",\n        \"web_street_type\": \"string\",\n        \"web_suite_number\": \"string\",\n        \"web_city\": \"string\",\n        \"web_county\": \"string\",\n        \"web_state\": \"string\",\n        \"web_zip\": \"string\",\n        \"web_country\": \"string\",\n        \"web_gmt_offset\": \"string\",\n        \"web_tax_percentage\": \"double\",\n    },\n}\n"
  },
  {
    "path": "tests/test_build.py",
    "content": "import unittest\n\nfrom sqlglot import (\n    alias,\n    and_,\n    case,\n    condition,\n    except_,\n    exp,\n    from_,\n    intersect,\n    not_,\n    or_,\n    parse_one,\n    select,\n    union,\n)\n\n\nclass TestBuild(unittest.TestCase):\n    def test_build(self):\n        x = condition(\"x\")\n        x_plus_one = x + 1\n\n        # Make sure we're not mutating x by changing its parent to be x_plus_one\n        self.assertIsNone(x.parent)\n        self.assertNotEqual(id(x_plus_one.this), id(x))\n\n        for expression, sql, *dialect in [\n            (lambda: x + 1, \"x + 1\"),\n            (lambda: 1 + x, \"1 + x\"),\n            (lambda: x - 1, \"x - 1\"),\n            (lambda: 1 - x, \"1 - x\"),\n            (lambda: x * 1, \"x * 1\"),\n            (lambda: 1 * x, \"1 * x\"),\n            (lambda: x / 1, \"x / 1\"),\n            (lambda: 1 / x, \"1 / x\"),\n            (lambda: x // 1, \"CAST(x / 1 AS INT)\"),\n            (lambda: 1 // x, \"CAST(1 / x AS INT)\"),\n            (lambda: x % 1, \"x % 1\"),\n            (lambda: 1 % x, \"1 % x\"),\n            (lambda: x**1, \"POWER(x, 1)\"),\n            (lambda: 1**x, \"POWER(1, x)\"),\n            (lambda: x & 1, \"x AND 1\"),\n            (lambda: 1 & x, \"1 AND x\"),\n            (lambda: x | 1, \"x OR 1\"),\n            (lambda: 1 | x, \"1 OR x\"),\n            (lambda: x < 1, \"x < 1\"),\n            (lambda: 1 < x, \"x > 1\"),\n            (lambda: x <= 1, \"x <= 1\"),\n            (lambda: 1 <= x, \"x >= 1\"),\n            (lambda: x > 1, \"x > 1\"),\n            (lambda: 1 > x, \"x < 1\"),\n            (lambda: x >= 1, \"x >= 1\"),\n            (lambda: 1 >= x, \"x <= 1\"),\n            (lambda: x.eq(1), \"x = 1\"),\n            (lambda: x.neq(1), \"x <> 1\"),\n            (lambda: x.is_(exp.Null()), \"x IS NULL\"),\n            (lambda: x.as_(\"y\"), \"x AS y\"),\n            (lambda: x.isin(1, \"2\"), \"x IN (1, '2')\"),\n            (lambda: x.isin(query=\"select 1\"), \"x IN (SELECT 1)\"),\n            (lambda: x.isin(unnest=\"x\"), \"x IN (SELECT UNNEST(x))\"),\n            (lambda: x.isin(unnest=\"x\"), \"x IN UNNEST(x)\", \"bigquery\"),\n            (lambda: x.isin(unnest=[\"x\", \"y\"]), \"x IN (SELECT UNNEST(x, y))\"),\n            (lambda: x.between(1, 2), \"x BETWEEN 1 AND 2\"),\n            (lambda: 1 + x + 2 + 3, \"1 + x + 2 + 3\"),\n            (lambda: 1 + x * 2 + 3, \"1 + (x * 2) + 3\"),\n            (lambda: x * 1 * 2 + 3, \"(x * 1 * 2) + 3\"),\n            (lambda: 1 + (x * 2) / 3, \"1 + ((x * 2) / 3)\"),\n            (lambda: x & \"y\", \"x AND 'y'\"),\n            (lambda: x | \"y\", \"x OR 'y'\"),\n            (lambda: -x, \"-x\"),\n            (lambda: ~x, \"NOT x\"),\n            (lambda: x[1], \"x[1]\"),\n            (lambda: x[1, 2], \"x[1, 2]\"),\n            (lambda: x[\"y\"] + 1, \"x['y'] + 1\"),\n            (lambda: x.like(\"y\"), \"x LIKE 'y'\"),\n            (lambda: x.ilike(\"y\"), \"x ILIKE 'y'\"),\n            (lambda: x.rlike(\"y\"), \"REGEXP_LIKE(x, 'y')\"),\n            (\n                lambda: case().when(\"x = 1\", \"x\").else_(\"bar\"),\n                \"CASE WHEN x = 1 THEN x ELSE bar END\",\n            ),\n            (\n                lambda: case(\"x\").when(\"1\", \"x\").else_(\"bar\"),\n                \"CASE x WHEN 1 THEN x ELSE bar END\",\n            ),\n            (lambda: exp.func(\"COALESCE\", \"x\", 1), \"COALESCE(x, 1)\"),\n            (lambda: exp.column(\"x\").desc(), \"x DESC\"),\n            (lambda: exp.column(\"x\").desc(nulls_first=True), \"x DESC NULLS FIRST\"),\n            (lambda: select(\"x\"), \"SELECT x\"),\n            (lambda: select(\"x\"), \"SELECT x\"),\n            (lambda: select(\"x\", \"y\"), \"SELECT x, y\"),\n            (lambda: select(\"x\").from_(\"tbl\"), \"SELECT x FROM tbl\"),\n            (lambda: select(\"x\", \"y\").from_(\"tbl\"), \"SELECT x, y FROM tbl\"),\n            (lambda: select(\"x\").select(\"y\").from_(\"tbl\"), \"SELECT x, y FROM tbl\"),\n            (lambda: select(\"comment\", \"begin\"), \"SELECT comment, begin\"),\n            (\n                lambda: select(\"x\").select(\"y\", append=False).from_(\"tbl\"),\n                \"SELECT y FROM tbl\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").from_(\"tbl2\"),\n                \"SELECT x FROM tbl2\",\n            ),\n            (lambda: select(\"SUM(x) AS y\"), \"SELECT SUM(x) AS y\"),\n            (\n                lambda: select(\"x\").from_(\"tbl\").where(\"x > 0\"),\n                \"SELECT x FROM tbl WHERE x > 0\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").where(\"x < 4 OR x > 5\"),\n                \"SELECT x FROM tbl WHERE x < 4 OR x > 5\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").where(\"x > 0\").where(\"x < 9\"),\n                \"SELECT x FROM tbl WHERE x > 0 AND x < 9\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").where(\"x > 0\", \"x < 9\"),\n                \"SELECT x FROM tbl WHERE x > 0 AND x < 9\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").where(None).where(False, \"\"),\n                \"SELECT x FROM tbl WHERE FALSE\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").where(\"x > 0\").where(\"x < 9\", append=False),\n                \"SELECT x FROM tbl WHERE x < 9\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").where(\"x > 0\").lock(),\n                \"SELECT x FROM tbl WHERE x > 0 FOR UPDATE\",\n                \"mysql\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").where(\"x > 0\").lock(update=False),\n                \"SELECT x FROM tbl WHERE x > 0 FOR SHARE\",\n                \"postgres\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").hint(\"repartition(100)\"),\n                \"SELECT /*+ REPARTITION(100) */ x FROM tbl\",\n                \"spark\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").hint(\"coalesce(3)\", \"broadcast(x)\"),\n                \"SELECT /*+ COALESCE(3), BROADCAST(x) */ x FROM tbl\",\n                \"spark\",\n            ),\n            (\n                lambda: select(\"x\", \"y\").from_(\"tbl\").group_by(\"x\"),\n                \"SELECT x, y FROM tbl GROUP BY x\",\n            ),\n            (\n                lambda: select(\"x\", \"y\").from_(\"tbl\").group_by(\"x, y\"),\n                \"SELECT x, y FROM tbl GROUP BY x, y\",\n            ),\n            (\n                lambda: select(\"x\", \"y\", \"z\", \"a\").from_(\"tbl\").group_by(\"x, y\", \"z\").group_by(\"a\"),\n                \"SELECT x, y, z, a FROM tbl GROUP BY x, y, z, a\",\n            ),\n            (\n                lambda: select(1).from_(\"tbl\").group_by(\"x with cube\"),\n                \"SELECT 1 FROM tbl GROUP BY x WITH CUBE\",\n            ),\n            (\n                lambda: select(\"x\").distinct(\"a\", \"b\").from_(\"tbl\"),\n                \"SELECT DISTINCT ON (a, b) x FROM tbl\",\n            ),\n            (\n                lambda: select(\"x\").distinct(distinct=True).from_(\"tbl\"),\n                \"SELECT DISTINCT x FROM tbl\",\n            ),\n            (lambda: select(\"x\").distinct(distinct=False).from_(\"tbl\"), \"SELECT x FROM tbl\"),\n            (\n                lambda: select(\"x\").lateral(\"OUTER explode(y) tbl2 AS z\").from_(\"tbl\"),\n                \"SELECT x FROM tbl LATERAL VIEW OUTER EXPLODE(y) tbl2 AS z\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").join(\"tbl2 ON tbl.y = tbl2.y\"),\n                \"SELECT x FROM tbl JOIN tbl2 ON tbl.y = tbl2.y\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").join(\"tbl2\", on=\"tbl.y = tbl2.y\"),\n                \"SELECT x FROM tbl JOIN tbl2 ON tbl.y = tbl2.y\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").join(\"tbl2\", on=[\"tbl.y = tbl2.y\", \"a = b\"]),\n                \"SELECT x FROM tbl JOIN tbl2 ON tbl.y = tbl2.y AND a = b\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").join(\"tbl2\", join_type=\"left outer\"),\n                \"SELECT x FROM tbl LEFT OUTER JOIN tbl2\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").from_(\"tbl\").join(exp.Table(this=\"tbl2\"), join_type=\"left outer\")\n                ),\n                \"SELECT x FROM tbl LEFT OUTER JOIN tbl2\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .join(exp.Table(this=\"tbl2\"), join_type=\"left outer\", join_alias=\"foo\")\n                ),\n                \"SELECT x FROM tbl LEFT OUTER JOIN tbl2 AS foo\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").from_(\"tbl\").join(select(\"y\").from_(\"tbl2\"), join_type=\"left outer\")\n                ),\n                \"SELECT x FROM tbl LEFT OUTER JOIN (SELECT y FROM tbl2)\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .join(\n                        select(\"y\").from_(\"tbl2\").subquery(\"aliased\"),\n                        join_type=\"left outer\",\n                    )\n                ),\n                \"SELECT x FROM tbl LEFT OUTER JOIN (SELECT y FROM tbl2) AS aliased\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .join(\n                        select(\"y\").from_(\"tbl2\"),\n                        join_type=\"left outer\",\n                        join_alias=\"aliased\",\n                    )\n                ),\n                \"SELECT x FROM tbl LEFT OUTER JOIN (SELECT y FROM tbl2) AS aliased\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").from_(\"tbl\").join(parse_one(\"left join x\", into=exp.Join), on=\"a=b\")\n                ),\n                \"SELECT x FROM tbl LEFT JOIN x ON a = b\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").join(\"left join x\", on=\"a=b\"),\n                \"SELECT x FROM tbl LEFT JOIN x ON a = b\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").from_(\"tbl\").join(\"select b from tbl2\", on=\"a=b\", join_type=\"left\")\n                ),\n                \"SELECT x FROM tbl LEFT JOIN (SELECT b FROM tbl2) ON a = b\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .join(\n                        \"select b from tbl2\",\n                        on=\"a=b\",\n                        join_type=\"left\",\n                        join_alias=\"aliased\",\n                    )\n                ),\n                \"SELECT x FROM tbl LEFT JOIN (SELECT b FROM tbl2) AS aliased ON a = b\",\n            ),\n            (\n                lambda: (\n                    select(\"x\", \"y\", \"z\")\n                    .from_(\"merged_df\")\n                    .join(\"vte_diagnosis_df\", using=[\"patient_id\", \"encounter_id\"])\n                ),\n                \"SELECT x, y, z FROM merged_df JOIN vte_diagnosis_df USING (patient_id, encounter_id)\",\n            ),\n            (\n                lambda: (\n                    select(\"x\", \"y\", \"z\")\n                    .from_(\"merged_df\")\n                    .join(\n                        \"vte_diagnosis_df\",\n                        using=[exp.to_identifier(\"patient_id\"), exp.to_identifier(\"encounter_id\")],\n                    )\n                ),\n                \"SELECT x, y, z FROM merged_df JOIN vte_diagnosis_df USING (patient_id, encounter_id)\",\n            ),\n            (\n                lambda: parse_one(\"JOIN x\", into=exp.Join).on(\"y = 1\", \"z = 1\"),\n                \"JOIN x ON y = 1 AND z = 1\",\n            ),\n            (\n                lambda: parse_one(\"JOIN x\", into=exp.Join).on(\"y = 1\"),\n                \"JOIN x ON y = 1\",\n            ),\n            (\n                lambda: parse_one(\"JOIN x\", into=exp.Join).using(\"bar\", \"bob\"),\n                \"JOIN x USING (bar, bob)\",\n            ),\n            (\n                lambda: parse_one(\"JOIN x\", into=exp.Join).using(\"bar\"),\n                \"JOIN x USING (bar)\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"foo\").join(\"bla\", using=\"bob\"),\n                \"SELECT x FROM foo JOIN bla USING (bob)\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"foo\").join(\"bla\", using=\"bob\"),\n                \"SELECT x FROM foo JOIN bla USING (bob)\",\n            ),\n            (\n                lambda: select(\"x\", \"COUNT(y)\").from_(\"tbl\").group_by(\"x\").having(\"COUNT(y) > 0\"),\n                \"SELECT x, COUNT(y) FROM tbl GROUP BY x HAVING COUNT(y) > 0\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").order_by(\"y\"),\n                \"SELECT x FROM tbl ORDER BY y\",\n            ),\n            (\n                lambda: parse_one(\"select * from x union select * from y\").order_by(\"y\"),\n                \"SELECT * FROM x UNION SELECT * FROM y ORDER BY y\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").cluster_by(\"y\"),\n                \"SELECT x FROM tbl CLUSTER BY y\",\n                \"hive\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").sort_by(\"y\"),\n                \"SELECT x FROM tbl SORT BY y\",\n                \"hive\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").order_by(\"x, y DESC\"),\n                \"SELECT x FROM tbl ORDER BY x, y DESC\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").cluster_by(\"x, y DESC\"),\n                \"SELECT x FROM tbl CLUSTER BY x, y DESC\",\n                \"hive\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").sort_by(\"x, y DESC\"),\n                \"SELECT x FROM tbl SORT BY x, y DESC\",\n                \"hive\",\n            ),\n            (\n                lambda: select(\"x\", \"y\", \"z\", \"a\").from_(\"tbl\").order_by(\"x, y\", \"z\").order_by(\"a\"),\n                \"SELECT x, y, z, a FROM tbl ORDER BY x, y, z, a\",\n            ),\n            (\n                lambda: (\n                    select(\"x\", \"y\", \"z\", \"a\").from_(\"tbl\").cluster_by(\"x, y\", \"z\").cluster_by(\"a\")\n                ),\n                \"SELECT x, y, z, a FROM tbl CLUSTER BY x, y, z, a\",\n                \"hive\",\n            ),\n            (\n                lambda: select(\"x\", \"y\", \"z\", \"a\").from_(\"tbl\").sort_by(\"x, y\", \"z\").sort_by(\"a\"),\n                \"SELECT x, y, z, a FROM tbl SORT BY x, y, z, a\",\n                \"hive\",\n            ),\n            (lambda: select(\"x\").from_(\"tbl\").limit(10), \"SELECT x FROM tbl LIMIT 10\"),\n            (\n                lambda: select(\"x\").from_(\"tbl\").offset(10),\n                \"SELECT x FROM tbl OFFSET 10\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").with_(\"tbl\", as_=\"SELECT x FROM tbl2\"),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .with_(\"tbl\", as_=\"SELECT x FROM tbl2\", materialized=True)\n                ),\n                \"WITH tbl AS MATERIALIZED (SELECT x FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .with_(\"tbl\", as_=\"SELECT x FROM tbl2\", materialized=False)\n                ),\n                \"WITH tbl AS NOT MATERIALIZED (SELECT x FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").from_(\"tbl\").with_(\"tbl\", as_=\"SELECT x FROM tbl2\", recursive=True)\n                ),\n                \"WITH RECURSIVE tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\"), recursive=True, materialized=True)\n                ),\n                \"WITH RECURSIVE tbl AS MATERIALIZED (SELECT x FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\"), recursive=True, materialized=False)\n                ),\n                \"WITH RECURSIVE tbl AS NOT MATERIALIZED (SELECT x FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: select(\"x\").from_(\"tbl\").with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\")),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").from_(\"tbl\").with_(\"tbl (x, y)\", as_=select(\"x\", \"y\").from_(\"tbl2\"))\n                ),\n                \"WITH tbl(x, y) AS (SELECT x, y FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\"))\n                    .with_(\"tbl2\", as_=select(\"x\").from_(\"tbl3\"))\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2), tbl2 AS (SELECT x FROM tbl3) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .from_(\"tbl\")\n                    .with_(\"tbl\", as_=select(\"x\", \"y\").from_(\"tbl2\"))\n                    .select(\"y\")\n                ),\n                \"WITH tbl AS (SELECT x, y FROM tbl2) SELECT x, y FROM tbl\",\n            ),\n            (\n                lambda: select(\"x\").with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\")).from_(\"tbl\"),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\"))\n                    .from_(\"tbl\")\n                    .group_by(\"x\")\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl GROUP BY x\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\"))\n                    .from_(\"tbl\")\n                    .order_by(\"x\")\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl ORDER BY x\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\")).from_(\"tbl\").limit(10)\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl LIMIT 10\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\")).from_(\"tbl\").offset(10)\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl OFFSET 10\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\"))\n                    .from_(\"tbl\")\n                    .join(\"tbl3\")\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl, tbl3\",\n            ),\n            (\n                lambda: (\n                    select(\"x\").with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\")).from_(\"tbl\").distinct()\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT DISTINCT x FROM tbl\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\"))\n                    .from_(\"tbl\")\n                    .where(\"x > 10\")\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl WHERE x > 10\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .with_(\"tbl\", as_=select(\"x\").from_(\"tbl2\"))\n                    .from_(\"tbl\")\n                    .having(\"x > 20\")\n                ),\n                \"WITH tbl AS (SELECT x FROM tbl2) SELECT x FROM tbl HAVING x > 20\",\n            ),\n            (lambda: select(\"x\").from_(\"tbl\").subquery(), \"(SELECT x FROM tbl)\"),\n            (\n                lambda: select(\"x\").from_(\"tbl\").subquery(\"y\"),\n                \"(SELECT x FROM tbl) AS y\",\n            ),\n            (\n                lambda: select(\"x\").from_(select(\"x\").from_(\"tbl\").subquery()),\n                \"SELECT x FROM (SELECT x FROM tbl)\",\n            ),\n            (lambda: from_(\"tbl\").select(\"x\"), \"SELECT x FROM tbl\"),\n            (\n                lambda: parse_one(\"SELECT a FROM tbl\").assert_is(exp.Select).select(\"b\"),\n                \"SELECT a, b FROM tbl\",\n            ),\n            (\n                lambda: parse_one(\"SELECT * FROM y\").assert_is(exp.Select).ctas(\"x\"),\n                \"CREATE TABLE x AS SELECT * FROM y\",\n            ),\n            (\n                lambda: (\n                    parse_one(\"SELECT * FROM y\")\n                    .assert_is(exp.Select)\n                    .ctas(\"foo.x\", properties={\"format\": \"parquet\", \"y\": \"2\"})\n                ),\n                \"CREATE TABLE foo.x STORED AS PARQUET TBLPROPERTIES ('y'='2') AS SELECT * FROM y\",\n                \"hive\",\n            ),\n            (lambda: and_(\"x=1\", \"y=1\"), \"x = 1 AND y = 1\"),\n            (lambda: condition(\"x\").and_(\"y['a']\").and_(\"1\"), \"(x AND y['a']) AND 1\"),\n            (lambda: condition(\"x=1\").and_(\"y=1\"), \"x = 1 AND y = 1\"),\n            (lambda: and_(\"x=1\", \"y=1\", \"z=1\"), \"x = 1 AND y = 1 AND z = 1\"),\n            (lambda: condition(\"x=1\").and_(\"y=1\", \"z=1\"), \"x = 1 AND y = 1 AND z = 1\"),\n            (lambda: and_(\"x=1\", and_(\"y=1\", \"z=1\")), \"x = 1 AND (y = 1 AND z = 1)\"),\n            (\n                lambda: condition(\"x=1\").and_(\"y=1\").and_(\"z=1\"),\n                \"(x = 1 AND y = 1) AND z = 1\",\n            ),\n            (lambda: or_(and_(\"x=1\", \"y=1\"), \"z=1\"), \"(x = 1 AND y = 1) OR z = 1\"),\n            (\n                lambda: condition(\"x=1\").and_(\"y=1\").or_(\"z=1\"),\n                \"(x = 1 AND y = 1) OR z = 1\",\n            ),\n            (lambda: or_(\"z=1\", and_(\"x=1\", \"y=1\")), \"z = 1 OR (x = 1 AND y = 1)\"),\n            (\n                lambda: or_(\"z=1 OR a=1\", and_(\"x=1\", \"y=1\")),\n                \"(z = 1 OR a = 1) OR (x = 1 AND y = 1)\",\n            ),\n            (lambda: not_(\"x=1\"), \"NOT x = 1\"),\n            (lambda: condition(\"x=1\").not_(), \"NOT x = 1\"),\n            (lambda: condition(\"x=1\").and_(\"y=1\").not_(), \"NOT (x = 1 AND y = 1)\"),\n            (\n                lambda: select(\"*\").from_(\"x\").where(condition(\"y=1\").and_(\"z=1\")),\n                \"SELECT * FROM x WHERE y = 1 AND z = 1\",\n            ),\n            (\n                lambda: exp.subquery(\"select x from tbl\", \"foo\").select(\"x\").where(\"x > 0\"),\n                \"SELECT x FROM (SELECT x FROM tbl) AS foo WHERE x > 0\",\n            ),\n            (\n                lambda: exp.subquery(\"select x from tbl UNION select x from bar\", \"unioned\").select(\n                    \"x\"\n                ),\n                \"SELECT x FROM (SELECT x FROM tbl UNION SELECT x FROM bar) AS unioned\",\n            ),\n            (lambda: parse_one(\"(SELECT 1)\").select(\"2\"), \"(SELECT 1, 2)\"),\n            (\n                lambda: parse_one(\"(SELECT 1)\").limit(1),\n                \"(SELECT 1) LIMIT 1\",\n            ),\n            (\n                lambda: parse_one(\"WITH t AS (SELECT 1) (SELECT 1)\").limit(1),\n                \"WITH t AS (SELECT 1) SELECT 1 LIMIT 1\",\n            ),\n            (\n                lambda: parse_one(\"(SELECT 1 LIMIT 2)\").limit(1),\n                \"(SELECT 1 LIMIT 2) LIMIT 1\",\n            ),\n            (\n                lambda: parse_one(\"SELECT 1 UNION SELECT 2\").limit(5).offset(2),\n                \"SELECT 1 UNION SELECT 2 LIMIT 5 OFFSET 2\",\n            ),\n            (lambda: parse_one(\"(SELECT 1)\").subquery(), \"((SELECT 1))\"),\n            (lambda: parse_one(\"(SELECT 1)\").subquery(\"alias\"), \"((SELECT 1)) AS alias\"),\n            (\n                lambda: parse_one(\"(select * from foo)\").with_(\"foo\", \"select 1 as c\"),\n                \"WITH foo AS (SELECT 1 AS c) (SELECT * FROM foo)\",\n            ),\n            (\n                lambda: exp.update(\"tbl\", {\"x\": None, \"y\": {\"x\": 1}}),\n                \"UPDATE tbl SET x = NULL, y = MAP(ARRAY('x'), ARRAY(1))\",\n            ),\n            (\n                lambda: exp.update(\"tbl\", {\"x\": 1}, where=\"y > 0\"),\n                \"UPDATE tbl SET x = 1 WHERE y > 0\",\n            ),\n            (\n                lambda: exp.update(\"tbl\", {\"x\": 1}, where=exp.condition(\"y > 0\")),\n                \"UPDATE tbl SET x = 1 WHERE y > 0\",\n            ),\n            (\n                lambda: exp.update(\"tbl\", {\"x\": 1}, from_=\"tbl2\"),\n                \"UPDATE tbl SET x = 1 FROM tbl2\",\n            ),\n            (\n                lambda: exp.update(\"tbl\", {\"x\": 1}, from_=\"tbl2 cross join tbl3\"),\n                \"UPDATE tbl SET x = 1 FROM tbl2 CROSS JOIN tbl3\",\n            ),\n            (\n                lambda: exp.update(\n                    \"my_table\",\n                    {\"x\": 1},\n                    from_=\"baz\",\n                    where=\"my_table.id = baz.id\",\n                    with_={\"baz\": \"SELECT id FROM foo UNION SELECT id FROM bar\"},\n                ),\n                \"WITH baz AS (SELECT id FROM foo UNION SELECT id FROM bar) UPDATE my_table SET x = 1 FROM baz WHERE my_table.id = baz.id\",\n            ),\n            (\n                lambda: exp.update(\"my_table\").set_(\"x = 1\"),\n                \"UPDATE my_table SET x = 1\",\n            ),\n            (\n                lambda: exp.update(\"my_table\").set_(\"x = 1\").where(\"y = 2\"),\n                \"UPDATE my_table SET x = 1 WHERE y = 2\",\n            ),\n            (\n                lambda: exp.update(\"my_table\").set_(\"a = 1\").set_(\"b = 2\"),\n                \"UPDATE my_table SET a = 1, b = 2\",\n            ),\n            (\n                lambda: (\n                    exp.update(\"my_table\")\n                    .set_(\"x = 1\")\n                    .where(\"my_table.id = baz.id\")\n                    .from_(\"baz\")\n                    .with_(\"baz\", \"SELECT id FROM foo\")\n                ),\n                \"WITH baz AS (SELECT id FROM foo) UPDATE my_table SET x = 1 FROM baz WHERE my_table.id = baz.id\",\n            ),\n            (\n                lambda: union(\"SELECT * FROM foo\", \"SELECT * FROM bla\"),\n                \"SELECT * FROM foo UNION SELECT * FROM bla\",\n            ),\n            (\n                lambda: parse_one(\"SELECT * FROM foo\").union(\"SELECT * FROM bla\"),\n                \"SELECT * FROM foo UNION SELECT * FROM bla\",\n            ),\n            (\n                lambda: intersect(\"SELECT * FROM foo\", \"SELECT * FROM bla\"),\n                \"SELECT * FROM foo INTERSECT SELECT * FROM bla\",\n            ),\n            (\n                lambda: parse_one(\"SELECT * FROM foo\").intersect(\"SELECT * FROM bla\"),\n                \"SELECT * FROM foo INTERSECT SELECT * FROM bla\",\n            ),\n            (\n                lambda: except_(\"SELECT * FROM foo\", \"SELECT * FROM bla\"),\n                \"SELECT * FROM foo EXCEPT SELECT * FROM bla\",\n            ),\n            (\n                lambda: parse_one(\"SELECT * FROM foo\").except_(\"SELECT * FROM bla\"),\n                \"SELECT * FROM foo EXCEPT SELECT * FROM bla\",\n            ),\n            (\n                lambda: parse_one(\"(SELECT * FROM foo)\").union(\"SELECT * FROM bla\"),\n                \"(SELECT * FROM foo) UNION SELECT * FROM bla\",\n            ),\n            (\n                lambda: parse_one(\"(SELECT * FROM foo)\").union(\"SELECT * FROM bla\", distinct=False),\n                \"(SELECT * FROM foo) UNION ALL SELECT * FROM bla\",\n            ),\n            (\n                lambda: alias(parse_one(\"LAG(x) OVER (PARTITION BY y)\"), \"a\"),\n                \"LAG(x) OVER (PARTITION BY y) AS a\",\n            ),\n            (\n                lambda: alias(parse_one(\"LAG(x) OVER (ORDER BY z)\"), \"a\"),\n                \"LAG(x) OVER (ORDER BY z) AS a\",\n            ),\n            (\n                lambda: alias(parse_one(\"LAG(x) OVER (PARTITION BY y ORDER BY z)\"), \"a\"),\n                \"LAG(x) OVER (PARTITION BY y ORDER BY z) AS a\",\n            ),\n            (\n                lambda: alias(parse_one(\"LAG(x) OVER ()\"), \"a\"),\n                \"LAG(x) OVER () AS a\",\n            ),\n            (lambda: exp.values([(\"1\", 2)]), \"VALUES ('1', 2)\"),\n            (lambda: exp.values([(\"1\", 2)], \"alias\"), \"(VALUES ('1', 2)) AS alias\"),\n            (lambda: exp.values([(\"1\", 2), (\"2\", 3)]), \"VALUES ('1', 2), ('2', 3)\"),\n            (\n                lambda: exp.values(\n                    [(\"1\", 2, None), (\"2\", 3, None)], \"alias\", [\"col1\", \"col2\", \"col3\"]\n                ),\n                \"(VALUES ('1', 2, NULL), ('2', 3, NULL)) AS alias(col1, col2, col3)\",\n            ),\n            (lambda: exp.delete(\"y\", where=\"x > 1\"), \"DELETE FROM y WHERE x > 1\"),\n            (lambda: exp.delete(\"y\", where=exp.and_(\"x > 1\")), \"DELETE FROM y WHERE x > 1\"),\n            (\n                lambda: (\n                    select(\"AVG(a) OVER b\")\n                    .from_(\"table\")\n                    .window(\"b AS (PARTITION BY c ORDER BY d)\")\n                ),\n                \"SELECT AVG(a) OVER b FROM table WINDOW b AS (PARTITION BY c ORDER BY d)\",\n            ),\n            (\n                lambda: (\n                    select(\"AVG(a) OVER b\", \"MIN(c) OVER d\")\n                    .from_(\"table\")\n                    .window(\"b AS (PARTITION BY e ORDER BY f)\")\n                    .window(\"d AS (PARTITION BY g ORDER BY h)\")\n                ),\n                \"SELECT AVG(a) OVER b, MIN(c) OVER d FROM table WINDOW b AS (PARTITION BY e ORDER BY f), d AS (PARTITION BY g ORDER BY h)\",\n            ),\n            (\n                lambda: (\n                    select(\"*\")\n                    .from_(\"table\")\n                    .qualify(\"row_number() OVER (PARTITION BY a ORDER BY b) = 1\")\n                ),\n                \"SELECT * FROM table QUALIFY ROW_NUMBER() OVER (PARTITION BY a ORDER BY b) = 1\",\n            ),\n            (lambda: exp.delete(\"tbl1\", \"x = 1\").delete(\"tbl2\"), \"DELETE FROM tbl2 WHERE x = 1\"),\n            (lambda: exp.delete(\"tbl\").where(\"x = 1\"), \"DELETE FROM tbl WHERE x = 1\"),\n            (lambda: exp.delete(exp.table_(\"tbl\")), \"DELETE FROM tbl\"),\n            (\n                lambda: exp.delete(\"tbl\", \"x = 1\").where(\"y = 2\"),\n                \"DELETE FROM tbl WHERE x = 1 AND y = 2\",\n            ),\n            (\n                lambda: exp.delete(\"tbl\", \"x = 1\").where(exp.condition(\"y = 2\").or_(\"z = 3\")),\n                \"DELETE FROM tbl WHERE x = 1 AND (y = 2 OR z = 3)\",\n            ),\n            (\n                lambda: exp.delete(\"tbl\").where(\"x = 1\").returning(\"*\", dialect=\"postgres\"),\n                \"DELETE FROM tbl WHERE x = 1 RETURNING *\",\n                \"postgres\",\n            ),\n            (\n                lambda: exp.delete(\"tbl\", where=\"x = 1\", returning=\"*\", dialect=\"postgres\"),\n                \"DELETE FROM tbl WHERE x = 1 RETURNING *\",\n                \"postgres\",\n            ),\n            (\n                lambda: exp.insert(\"SELECT * FROM tbl2\", \"tbl\"),\n                \"INSERT INTO tbl SELECT * FROM tbl2\",\n            ),\n            (\n                lambda: exp.insert(\"SELECT * FROM tbl2\", \"tbl\", returning=\"*\"),\n                \"INSERT INTO tbl SELECT * FROM tbl2 RETURNING *\",\n            ),\n            (\n                lambda: exp.insert(\"SELECT * FROM tbl2\", \"tbl\", overwrite=True),\n                \"INSERT OVERWRITE TABLE tbl SELECT * FROM tbl2\",\n            ),\n            (\n                lambda: exp.insert(\"VALUES (1, 2), (3, 4)\", \"tbl\", columns=[\"cola\", \"colb\"]),\n                \"INSERT INTO tbl (cola, colb) VALUES (1, 2), (3, 4)\",\n            ),\n            (\n                lambda: exp.insert(\"VALUES (1), (2)\", \"tbl\", columns=[\"col a\"]),\n                'INSERT INTO tbl (\"col a\") VALUES (1), (2)',\n            ),\n            (\n                lambda: exp.insert(\"SELECT * FROM cte\", \"t\").with_(\"cte\", as_=\"SELECT x FROM tbl\"),\n                \"WITH cte AS (SELECT x FROM tbl) INSERT INTO t SELECT * FROM cte\",\n            ),\n            (\n                lambda: exp.insert(\"SELECT * FROM cte\", \"t\").with_(\n                    \"cte\", as_=\"SELECT x FROM tbl\", materialized=True\n                ),\n                \"WITH cte AS MATERIALIZED (SELECT x FROM tbl) INSERT INTO t SELECT * FROM cte\",\n            ),\n            (\n                lambda: exp.insert(\"SELECT * FROM cte\", \"t\").with_(\n                    \"cte\", as_=\"SELECT x FROM tbl\", materialized=False\n                ),\n                \"WITH cte AS NOT MATERIALIZED (SELECT x FROM tbl) INSERT INTO t SELECT * FROM cte\",\n            ),\n            (\n                lambda: exp.convert((exp.column(\"x\"), exp.column(\"y\"))).isin((1, 2), (3, 4)),\n                \"(x, y) IN ((1, 2), (3, 4))\",\n                \"postgres\",\n            ),\n            (lambda: exp.cast(\"CAST(x AS INT)\", \"int\"), \"CAST(x AS INT)\"),\n            (lambda: exp.cast(\"CAST(x AS TEXT)\", \"int\"), \"CAST(CAST(x AS TEXT) AS INT)\"),\n            (\n                lambda: exp.rename_column(\"table1\", \"c1\", \"c2\", True),\n                \"ALTER TABLE table1 RENAME COLUMN IF EXISTS c1 TO c2\",\n            ),\n            (\n                lambda: exp.rename_column(\"table1\", \"c1\", \"c2\", False),\n                \"ALTER TABLE table1 RENAME COLUMN c1 TO c2\",\n            ),\n            (\n                lambda: exp.rename_column(\"table1\", \"c1\", \"c2\"),\n                \"ALTER TABLE table1 RENAME COLUMN c1 TO c2\",\n            ),\n            (\n                lambda: exp.merge(\n                    \"WHEN MATCHED THEN UPDATE SET col1 = source.col1\",\n                    \"WHEN NOT MATCHED THEN INSERT (col1) VALUES (source.col1)\",\n                    into=\"target_table\",\n                    using=\"source_table\",\n                    on=\"target_table.id = source_table.id\",\n                ),\n                \"MERGE INTO target_table USING source_table ON target_table.id = source_table.id WHEN MATCHED THEN UPDATE SET col1 = source.col1 WHEN NOT MATCHED THEN INSERT (col1) VALUES (source.col1)\",\n            ),\n            (\n                lambda: exp.merge(\n                    \"WHEN MATCHED AND source.is_deleted = 1 THEN DELETE\",\n                    \"WHEN MATCHED THEN UPDATE SET val = source.val\",\n                    \"WHEN NOT MATCHED THEN INSERT (id, val) VALUES (source.id, source.val)\",\n                    into=\"target_table\",\n                    using=\"source_table\",\n                    on=\"target_table.id = source_table.id\",\n                ),\n                \"MERGE INTO target_table USING source_table ON target_table.id = source_table.id WHEN MATCHED AND source.is_deleted = 1 THEN DELETE WHEN MATCHED THEN UPDATE SET val = source.val WHEN NOT MATCHED THEN INSERT (id, val) VALUES (source.id, source.val)\",\n            ),\n            (\n                lambda: exp.merge(\n                    \"WHEN MATCHED THEN UPDATE SET target.name = source.name\",\n                    into=exp.table_(\"target_table\").as_(\"target\"),\n                    using=exp.table_(\"source_table\").as_(\"source\"),\n                    on=\"target.id = source.id\",\n                ),\n                \"MERGE INTO target_table AS target USING source_table AS source ON target.id = source.id WHEN MATCHED THEN UPDATE SET target.name = source.name\",\n            ),\n            (\n                lambda: exp.merge(\n                    \"WHEN MATCHED THEN UPDATE SET target.name = source.name\",\n                    into=exp.table_(\"target_table\").as_(\"target\"),\n                    using=exp.table_(\"source_table\").as_(\"source\"),\n                    on=\"target.id = source.id\",\n                    returning=\"target.*\",\n                ),\n                \"MERGE INTO target_table AS target USING source_table AS source ON target.id = source.id WHEN MATCHED THEN UPDATE SET target.name = source.name RETURNING target.*\",\n            ),\n            (\n                lambda: exp.merge(\n                    exp.When(\n                        matched=True,\n                        then=exp.Update(\n                            expressions=[\n                                exp.column(\"name\", \"target\").eq(exp.column(\"name\", \"source\"))\n                            ]\n                        ),\n                    ),\n                    into=exp.table_(\"target_table\").as_(\"target\"),\n                    using=exp.table_(\"source_table\").as_(\"source\"),\n                    on=\"target.id = source.id\",\n                    returning=\"target.*\",\n                ),\n                \"MERGE INTO target_table AS target USING source_table AS source ON target.id = source.id WHEN MATCHED THEN UPDATE SET target.name = source.name RETURNING target.*\",\n            ),\n            (\n                lambda: exp.union(\"SELECT 1\", \"SELECT 2\", \"SELECT 3\", \"SELECT 4\"),\n                \"SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .with_(\"var1\", as_=select(\"x\").from_(\"tbl2\").subquery(), scalar=True)\n                    .from_(\"tbl\")\n                    .where(\"x > var1\")\n                ),\n                \"WITH (SELECT x FROM tbl2) AS var1 SELECT x FROM tbl WHERE x > var1\",\n                \"clickhouse\",\n            ),\n            (\n                lambda: (\n                    select(\"x\")\n                    .with_(\"var1\", as_=select(\"x\").from_(\"tbl2\"), scalar=True)\n                    .from_(\"tbl\")\n                    .where(\"x > var1\")\n                ),\n                \"WITH (SELECT x FROM tbl2) AS var1 SELECT x FROM tbl WHERE x > var1\",\n                \"clickhouse\",\n            ),\n        ]:\n            with self.subTest(sql):\n                self.assertEqual(expression().sql(dialect[0] if dialect else None), sql)\n"
  },
  {
    "path": "tests/test_dialect_entry_points.py",
    "content": "import unittest\nfrom unittest.mock import Mock, patch\n\nfrom sqlglot import Dialect\nfrom sqlglot.dialects.dialect import Dialect as DialectBase\n\n\nclass FakeDialect(DialectBase):\n    pass\n\n\nclass TestDialectEntryPoints(unittest.TestCase):\n    def setUp(self):\n        Dialect._classes.clear()\n\n    def tearDown(self):\n        Dialect._classes.clear()\n\n    def test_entry_point_plugin_discovery_modern_api(self):\n        fake_entry_point = Mock()\n        fake_entry_point.name = \"fakedb\"\n        fake_entry_point.load.return_value = FakeDialect\n\n        mock_selectable = Mock()\n        mock_selectable.select.return_value = [fake_entry_point]\n\n        mock_entry_points = Mock(return_value=mock_selectable)\n\n        with patch(\"sqlglot.dialects.dialect.entry_points\", mock_entry_points):\n            dialect = Dialect.get(\"fakedb\")\n\n        self.assertIsNotNone(dialect)\n        self.assertEqual(dialect, FakeDialect)\n        fake_entry_point.load.assert_called_once()\n        mock_selectable.select.assert_called_once_with(group=\"sqlglot.dialects\", name=\"fakedb\")\n\n    def test_entry_point_plugin_discovery_legacy_api(self):\n        fake_entry_point = Mock()\n        fake_entry_point.name = \"fakedb\"\n        fake_entry_point.load.return_value = FakeDialect\n\n        mock_dict = Mock(spec=[\"get\"])\n        mock_dict.get.return_value = [fake_entry_point]\n\n        mock_entry_points = Mock(return_value=mock_dict)\n\n        with patch(\"sqlglot.dialects.dialect.entry_points\", mock_entry_points):\n            dialect = Dialect.get(\"fakedb\")\n\n        self.assertIsNotNone(dialect)\n        self.assertEqual(dialect, FakeDialect)\n        fake_entry_point.load.assert_called_once()\n        mock_dict.get.assert_called_once_with(\"sqlglot.dialects\", [])\n\n    def test_entry_point_plugin_not_found(self):\n        mock_selectable = Mock()\n        mock_selectable.select.return_value = []\n\n        mock_entry_points = Mock(return_value=mock_selectable)\n\n        with patch(\"sqlglot.dialects.dialect.entry_points\", mock_entry_points):\n            dialect = Dialect.get(\"nonexistent\")\n\n        self.assertIsNone(dialect)\n"
  },
  {
    "path": "tests/test_dialect_imports.py",
    "content": "import unittest\nfrom unittest.mock import patch\n\nfrom sqlglot.dialects import __getattr__ as dialects_getattr\n\n\nclass TestDialectImports(unittest.TestCase):\n    def test_athena_import_no_deadlock(self):\n        \"\"\"Test that importing Athena dialect doesn't cause a deadlock.\n\n        Athena imports from other dialects during its module initialization,\n        which could cause a deadlock with a non-reentrant lock.\n        \"\"\"\n        # This should complete without hanging\n        from sqlglot.dialects import Athena\n\n        # Verify it imported successfully\n        self.assertIsNotNone(Athena)\n        self.assertTrue(hasattr(Athena, \"Parser\"))\n        self.assertTrue(hasattr(Athena, \"Generator\"))\n\n    def test_nested_dialect_import_with_rlock(self):\n        \"\"\"Test that nested dialect imports work with RLock.\"\"\"\n        import_count = 0\n\n        def mock_import_module(name):\n            nonlocal import_count\n            import_count += 1\n\n            # Simulate Athena importing Hive\n            if name.endswith(\".athena\"):\n                # This simulates athena.py trying to import Hive\n                dialects_getattr(\"Hive\")\n\n            # Return a mock module with the expected attribute\n            import types\n\n            module = types.ModuleType(name)\n            dialect_name = name.split(\".\")[-1].title()\n            setattr(module, dialect_name, f\"Mock{dialect_name}\")\n            return module\n\n        with patch(\"importlib.import_module\", side_effect=mock_import_module):\n            # This should not deadlock\n            result = dialects_getattr(\"Athena\")\n            self.assertEqual(result, \"MockAthena\")\n            # Should have imported both Athena and Hive\n            self.assertEqual(import_count, 2)\n"
  },
  {
    "path": "tests/test_diff.py",
    "content": "import unittest\n\nfrom sqlglot import exp, parse_one\nfrom sqlglot.diff import Insert, Move, Remove, Update, diff\n\n\ndef diff_delta_only(source, target, matchings=None, **kwargs):\n    return diff(source, target, matchings=matchings, delta_only=True, **kwargs)\n\n\nclass TestDiff(unittest.TestCase):\n    def test_simple(self):\n        self._validate_delta_only(\n            diff_delta_only(parse_one(\"SELECT a + b\"), parse_one(\"SELECT a - b\")),\n            [\n                Remove(expression=parse_one(\"a + b\")),  # the Add node\n                Insert(expression=parse_one(\"a - b\")),  # the Sub node\n                Move(source=parse_one(\"a\"), target=parse_one(\"a\")),  # the `a` Column node\n                Move(source=parse_one(\"b\"), target=parse_one(\"b\")),  # the `b` Column node\n            ],\n        )\n\n        self._validate_delta_only(\n            diff_delta_only(parse_one(\"SELECT a, b, c\"), parse_one(\"SELECT a, c\")),\n            [\n                Remove(expression=parse_one(\"b\")),  # the Column node\n            ],\n        )\n\n        self._validate_delta_only(\n            diff_delta_only(parse_one(\"SELECT a, b\"), parse_one(\"SELECT a, b, c\")),\n            [\n                Insert(expression=parse_one(\"c\")),  # the Column node\n            ],\n        )\n\n        self._validate_delta_only(\n            diff_delta_only(\n                parse_one(\"SELECT a FROM table_one\"),\n                parse_one(\"SELECT a FROM table_two\"),\n            ),\n            [\n                Update(\n                    source=exp.to_table(\"table_one\", quoted=False),\n                    target=exp.to_table(\"table_two\", quoted=False),\n                ),  # the Table node\n            ],\n        )\n\n    def test_lambda(self):\n        self._validate_delta_only(\n            diff_delta_only(\n                parse_one(\"SELECT a, b, c, x(a -> a)\"), parse_one(\"SELECT a, b, c, x(b -> b)\")\n            ),\n            [\n                Update(\n                    source=exp.Lambda(\n                        this=exp.to_identifier(\"a\"), expressions=[exp.to_identifier(\"a\")]\n                    ),\n                    target=exp.Lambda(\n                        this=exp.to_identifier(\"b\"), expressions=[exp.to_identifier(\"b\")]\n                    ),\n                ),\n            ],\n        )\n\n    def test_udf(self):\n        self._validate_delta_only(\n            diff_delta_only(\n                parse_one('SELECT a, b, \"my.udf1\"()'), parse_one('SELECT a, b, \"my.udf2\"()')\n            ),\n            [\n                Insert(expression=parse_one('\"my.udf2\"()')),\n                Remove(expression=parse_one('\"my.udf1\"()')),\n            ],\n        )\n        self._validate_delta_only(\n            diff_delta_only(\n                parse_one('SELECT a, b, \"my.udf\"(x, y, z)'),\n                parse_one('SELECT a, b, \"my.udf\"(x, y, w)'),\n            ),\n            [\n                Insert(expression=exp.column(\"w\")),\n                Remove(expression=exp.column(\"z\")),\n            ],\n        )\n\n    def test_node_position_changed(self):\n        expr_src = parse_one(\"SELECT a, b, c\")\n        expr_tgt = parse_one(\"SELECT c, a, b\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Move(source=expr_src.selects[2], target=expr_tgt.selects[0]),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT a + b\")\n        expr_tgt = parse_one(\"SELECT b + a\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Move(source=expr_src.selects[0].left, target=expr_tgt.selects[0].right),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT aaaa AND bbbb\")\n        expr_tgt = parse_one(\"SELECT bbbb AND aaaa\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Move(source=expr_src.selects[0].left, target=expr_tgt.selects[0].right),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT aaaa OR bbbb OR cccc\")\n        expr_tgt = parse_one(\"SELECT cccc OR bbbb OR aaaa\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Move(source=expr_src.selects[0].left.left, target=expr_tgt.selects[0].right),\n                Move(source=expr_src.selects[0].right, target=expr_tgt.selects[0].left.left),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT a, b FROM t WHERE CONCAT('a', 'b') = 'ab'\")\n        expr_tgt = parse_one(\"SELECT a FROM t WHERE CONCAT('a', 'b', b) = 'ab'\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Move(source=expr_src.selects[1], target=expr_tgt.find(exp.Concat).expressions[-1]),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT a as a, b as b FROM t WHERE CONCAT('a', 'b') = 'ab'\")\n        expr_tgt = parse_one(\"SELECT a as a FROM t WHERE CONCAT('a', 'b', b) = 'ab'\")\n\n        b_alias = expr_src.selects[1]\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Remove(expression=b_alias),\n                Move(source=b_alias.this, target=expr_tgt.find(exp.Concat).expressions[-1]),\n            ],\n        )\n\n    def test_cte(self):\n        expr_src = \"\"\"\n            WITH\n                cte1 AS (SELECT a, b, LOWER(c) AS c FROM table_one WHERE d = 'filter'),\n                cte2 AS (SELECT d, e, f FROM table_two)\n            SELECT a, b, d, e FROM cte1 JOIN cte2 ON f = c\n        \"\"\"\n        expr_tgt = \"\"\"\n            WITH\n                cte1 AS (SELECT a, b, c FROM table_one WHERE d = 'different_filter'),\n                cte2 AS (SELECT d, e, f FROM table_two)\n            SELECT a, b, d, e FROM cte1 JOIN cte2 ON f = c\n        \"\"\"\n\n        self._validate_delta_only(\n            diff_delta_only(parse_one(expr_src), parse_one(expr_tgt)),\n            [\n                Remove(expression=parse_one(\"LOWER(c) AS c\")),  # the Alias node\n                Remove(expression=parse_one(\"LOWER(c)\")),  # the Lower node\n                Remove(expression=parse_one(\"'filter'\")),  # the Literal node\n                Insert(expression=parse_one(\"'different_filter'\")),  # the Literal node\n                Move(source=parse_one(\"c\"), target=parse_one(\"c\")),  # the new Column c\n            ],\n        )\n\n    def test_join(self):\n        expr_src = parse_one(\"SELECT a, b FROM t1 LEFT JOIN t2 ON t1.key = t2.key\")\n        expr_tgt = parse_one(\"SELECT a, b FROM t1 RIGHT JOIN t2 ON t1.key = t2.key\")\n\n        src_join = expr_src.find(exp.Join)\n        tgt_join = expr_tgt.find(exp.Join)\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Remove(expression=src_join),\n                Insert(expression=tgt_join),\n                Move(source=exp.to_table(\"t2\"), target=exp.to_table(\"t2\")),\n                Move(source=src_join.args[\"on\"], target=tgt_join.args[\"on\"]),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT a.x FROM a INNER JOIN b ON a.x = b.y LEFT JOIN c ON a.p = c.q\")\n        expr_tgt = parse_one(\"SELECT a.x FROM a inner JOIN b ON a.x = b.y left JOIN c ON a.p = c.q\")\n\n        self._validate_delta_only(diff_delta_only(expr_src, expr_tgt), [])\n\n    def test_window_functions(self):\n        expr_src = parse_one(\"SELECT ROW_NUMBER() OVER (PARTITION BY a ORDER BY b)\")\n        expr_tgt = parse_one(\"SELECT RANK() OVER (PARTITION BY a ORDER BY b)\")\n\n        self._validate_delta_only(diff_delta_only(expr_src, expr_src), [])\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Remove(expression=parse_one(\"ROW_NUMBER()\")),\n                Insert(expression=parse_one(\"RANK()\")),\n                Update(source=expr_src.selects[0], target=expr_tgt.selects[0]),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT MAX(x) OVER (ORDER BY y) FROM z\", \"oracle\")\n        expr_tgt = parse_one(\"SELECT MAX(x) KEEP (DENSE_RANK LAST ORDER BY y) FROM z\", \"oracle\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [Update(source=expr_src.selects[0], target=expr_tgt.selects[0])],\n        )\n\n    def test_pre_matchings(self):\n        expr_src = parse_one(\"SELECT 1\")\n        expr_tgt = parse_one(\"SELECT 1, 2, 3, 4\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Remove(expression=expr_src),\n                Insert(expression=expr_tgt),\n                Insert(expression=exp.Literal.number(2)),\n                Insert(expression=exp.Literal.number(3)),\n                Insert(expression=exp.Literal.number(4)),\n                Move(source=exp.Literal.number(1), target=exp.Literal.number(1)),\n            ],\n        )\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt, matchings=[(expr_src, expr_tgt)]),\n            [\n                Insert(expression=exp.Literal.number(2)),\n                Insert(expression=exp.Literal.number(3)),\n                Insert(expression=exp.Literal.number(4)),\n            ],\n        )\n\n        self._validate_delta_only(\n            diff_delta_only(\n                expr_src, expr_tgt, matchings=[(expr_src, expr_tgt), (expr_src, expr_tgt)]\n            ),\n            [\n                Insert(expression=exp.Literal.number(2)),\n                Insert(expression=exp.Literal.number(3)),\n                Insert(expression=exp.Literal.number(4)),\n            ],\n        )\n\n        expr_tgt.selects[0].replace(expr_src.selects[0])\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt, matchings=[(expr_src, expr_tgt)]),\n            [\n                Insert(expression=exp.Literal.number(2)),\n                Insert(expression=exp.Literal.number(3)),\n                Insert(expression=exp.Literal.number(4)),\n            ],\n        )\n\n    def test_identifier(self):\n        expr_src = parse_one(\"SELECT a FROM tbl\")\n        expr_tgt = parse_one(\"SELECT a, tbl.b from tbl\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Insert(expression=exp.to_column(\"tbl.b\")),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT 1 AS c1, 2 AS c2\")\n        expr_tgt = parse_one(\"SELECT 2 AS c1, 3 AS c2\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Remove(expression=exp.alias_(1, \"c1\")),\n                Remove(expression=exp.Literal.number(1)),\n                Insert(expression=exp.alias_(3, \"c2\")),\n                Insert(expression=exp.Literal.number(3)),\n                Update(source=exp.alias_(2, \"c2\"), target=exp.alias_(2, \"c1\")),\n            ],\n        )\n\n    def test_dialect_aware_diff(self):\n        from sqlglot.generator import logger\n\n        with self.assertLogs(logger) as cm:\n            # We want to assert there are no warnings, but the 'assertLogs' method does not support that.\n            # Therefore, we are adding a dummy warning, and then we will assert it is the only warning.\n            logger.warning(\"Dummy warning\")\n\n            expression = parse_one(\"SELECT foo FROM bar FOR UPDATE\", dialect=\"oracle\")\n            self._validate_delta_only(\n                diff_delta_only(expression, expression.copy(), dialect=\"oracle\"), []\n            )\n\n        self.assertEqual([\"WARNING:sqlglot:Dummy warning\"], cm.output)\n\n    def test_non_expression_leaf_delta(self):\n        expr_src = parse_one(\"SELECT a UNION SELECT b\")\n        expr_tgt = parse_one(\"SELECT a UNION ALL SELECT b\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Update(source=expr_src, target=expr_tgt),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT a FROM t ORDER BY b ASC\")\n        expr_tgt = parse_one(\"SELECT a FROM t ORDER BY b DESC\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Update(\n                    source=expr_src.find(exp.Order).expressions[0],\n                    target=expr_tgt.find(exp.Order).expressions[0],\n                ),\n            ],\n        )\n\n        expr_src = parse_one(\"SELECT a, b FROM t ORDER BY c ASC\")\n        expr_tgt = parse_one(\"SELECT b, a FROM t ORDER BY c DESC\")\n\n        self._validate_delta_only(\n            diff_delta_only(expr_src, expr_tgt),\n            [\n                Update(\n                    source=expr_src.find(exp.Order).expressions[0],\n                    target=expr_tgt.find(exp.Order).expressions[0],\n                ),\n                Move(source=expr_src.selects[0], target=expr_tgt.selects[1]),\n            ],\n        )\n\n    def test_none_args_are_not_treated_as_leaves(self):\n        expr_src = exp.Column(\n            this=exp.to_identifier(\"b\"), table=exp.to_identifier(\"a\"), db=None, catalog=None\n        )\n        expr_tgt = exp.Column(this=exp.to_identifier(\"b\"), table=exp.to_identifier(\"a\"))\n\n        self.assertEqual(set(expr_src.args), {\"this\", \"table\", \"db\", \"catalog\"})\n        self.assertEqual(set(expr_tgt.args), {\"this\", \"table\"})\n\n        self._validate_delta_only(diff_delta_only(expr_src, expr_tgt), [])\n\n    def test_comments_do_not_affect_diff(self):\n        expr_src = parse_one(\"select a from tbl\")\n        expr_tgt = parse_one(\"select a from tbl -- this is comment\")\n\n        self.assertEqual(expr_tgt.args[\"from_\"].this.comments, [\" this is comment\"])\n        self._validate_delta_only(diff_delta_only(expr_src, expr_tgt), [])\n\n    def _validate_delta_only(self, actual_delta, expected_delta):\n        self.assertEqual(set(actual_delta), set(expected_delta))\n"
  },
  {
    "path": "tests/test_docs.py",
    "content": "import doctest\nimport importlib\nimport pkgutil\nimport unittest\n\nimport sqlglot\n\n\ndef load_tests(loader, tests, ignore):\n    \"\"\"\n    This finds and runs all the doctests\n    \"\"\"\n\n    modules = set()\n    for info in pkgutil.walk_packages(sqlglot.__path__, prefix=\"sqlglot.\"):\n        if info.name == \"sqlglot.__main__\":\n            continue\n        try:\n            modules.add(importlib.import_module(info.name))\n        except Exception:\n            continue\n\n    assert len(modules) >= 20\n\n    for module in sorted(modules, key=lambda m: m.__name__):\n        tests.addTests(doctest.DocTestSuite(module))\n\n    return tests\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_errors.py",
    "content": "import unittest\n\nfrom sqlglot.errors import highlight_sql, ANSI_UNDERLINE, ANSI_RESET\n\n\nclass TestErrors(unittest.TestCase):\n    def test_highlight_sql_single_character(self):\n        sql = \"SELECT a FROM t\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(7, 7)])\n\n        self.assertEqual(start_ctx, \"SELECT \")\n        self.assertEqual(highlight, \"a\")\n        self.assertEqual(end_ctx, \" FROM t\")\n        self.assertEqual(formatted, f\"SELECT {ANSI_UNDERLINE}a{ANSI_RESET} FROM t\")\n\n    def test_highlight_sql_multi_character(self):\n        sql = \"SELECT foo FROM table\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(7, 9)])\n\n        self.assertEqual(start_ctx, \"SELECT \")\n        self.assertEqual(highlight, \"foo\")\n        self.assertEqual(end_ctx, \" FROM table\")\n        self.assertEqual(formatted, f\"SELECT {ANSI_UNDERLINE}foo{ANSI_RESET} FROM table\")\n\n    def test_highlight_sql_multiple_highlights(self):\n        sql = \"SELECT a, b, c FROM table\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(7, 7), (10, 10)])\n\n        self.assertEqual(start_ctx, \"SELECT \")\n        self.assertEqual(highlight, \"a, b\")\n        self.assertEqual(end_ctx, \", c FROM table\")\n        self.assertEqual(\n            formatted,\n            f\"SELECT {ANSI_UNDERLINE}a{ANSI_RESET}, {ANSI_UNDERLINE}b{ANSI_RESET}, c FROM table\",\n        )\n\n    def test_highlight_sql_at_end(self):\n        sql = \"SELECT a FROM t\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(14, 14)])\n\n        self.assertEqual(start_ctx, \"SELECT a FROM \")\n        self.assertEqual(highlight, \"t\")\n        self.assertEqual(end_ctx, \"\")\n        self.assertEqual(formatted, f\"SELECT a FROM {ANSI_UNDERLINE}t{ANSI_RESET}\")\n\n    def test_highlight_sql_entire_string(self):\n        sql = \"SELECT a\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(0, 7)])\n\n        self.assertEqual(start_ctx, \"\")\n        self.assertEqual(highlight, \"SELECT a\")\n        self.assertEqual(end_ctx, \"\")\n        self.assertEqual(formatted, f\"{ANSI_UNDERLINE}SELECT a{ANSI_RESET}\")\n\n    def test_highlight_sql_adjacent_highlights(self):\n        sql = \"SELECT ab FROM t\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(7, 7), (8, 8)])\n\n        self.assertEqual(start_ctx, \"SELECT \")\n        self.assertEqual(highlight, \"ab\")\n        self.assertEqual(end_ctx, \" FROM t\")\n        self.assertEqual(\n            formatted, f\"SELECT {ANSI_UNDERLINE}a{ANSI_RESET}{ANSI_UNDERLINE}b{ANSI_RESET} FROM t\"\n        )\n\n    def test_highlight_sql_small_context_length(self):\n        sql = \"SELECT a, b, c FROM table WHERE x = 1\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(\n            sql, [(7, 7), (10, 10)], context_length=5\n        )\n\n        self.assertEqual(start_ctx, \"LECT \")\n        self.assertEqual(highlight, \"a, b\")\n        self.assertEqual(end_ctx, \", c F\")\n        self.assertEqual(\n            formatted, f\"LECT {ANSI_UNDERLINE}a{ANSI_RESET}, {ANSI_UNDERLINE}b{ANSI_RESET}, c F\"\n        )\n\n    def test_highlight_sql_empty_positions(self):\n        sql = \"SELECT a FROM t\"\n        with self.assertRaises(ValueError):\n            highlight_sql(sql, [])\n\n    def test_highlight_sql_partial_overlap(self):\n        sql = \"SELECT foo FROM table\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(\n            sql,\n            [(7, 9), (8, 10)],  # \"foo\" and \"oo \"\n        )\n\n        self.assertEqual(start_ctx, \"SELECT \")\n        self.assertEqual(highlight, \"foo \")\n        self.assertEqual(end_ctx, \"FROM table\")\n        self.assertEqual(\n            formatted,\n            f\"SELECT {ANSI_UNDERLINE}foo{ANSI_RESET}{ANSI_UNDERLINE} {ANSI_RESET}FROM table\",\n        )\n\n    def test_highlight_sql_full_overlap(self):\n        sql = \"SELECT foobar FROM table\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(\n            sql,\n            [(7, 12), (9, 11)],  # \"foobar\" and \"oba\"\n        )\n\n        self.assertEqual(start_ctx, \"SELECT \")\n        self.assertEqual(highlight, \"foobar\")\n        self.assertEqual(end_ctx, \" FROM table\")\n        self.assertEqual(formatted, f\"SELECT {ANSI_UNDERLINE}foobar{ANSI_RESET} FROM table\")\n\n    def test_highlight_sql_identical_positions(self):\n        sql = \"SELECT a FROM t\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(7, 7), (7, 7)])\n\n        self.assertEqual(start_ctx, \"SELECT \")\n        self.assertEqual(highlight, \"a\")\n        self.assertEqual(end_ctx, \" FROM t\")\n        self.assertEqual(formatted, f\"SELECT {ANSI_UNDERLINE}a{ANSI_RESET} FROM t\")\n\n    def test_highlight_sql_reversed_positions(self):\n        sql = \"SELECT a, b FROM table\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(10, 10), (7, 7)])\n\n        self.assertEqual(start_ctx, \"SELECT \")\n        self.assertEqual(highlight, \"a, b\")\n        self.assertEqual(end_ctx, \" FROM table\")\n        self.assertEqual(\n            formatted,\n            f\"SELECT {ANSI_UNDERLINE}a{ANSI_RESET}, {ANSI_UNDERLINE}b{ANSI_RESET} FROM table\",\n        )\n\n    def test_highlight_sql_zero_context_length(self):\n        sql = \"SELECT a, b FROM table\"\n        formatted, start_ctx, highlight, end_ctx = highlight_sql(sql, [(7, 7)], context_length=0)\n\n        self.assertEqual(start_ctx, \"\")\n        self.assertEqual(end_ctx, \"\")\n        self.assertEqual(highlight, \"a\")\n        self.assertEqual(formatted, f\"{ANSI_UNDERLINE}a{ANSI_RESET}\")\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_executor.py",
    "content": "import ast\nimport csv\nimport datetime\nimport unittest\nfrom datetime import date, time\nfrom concurrent.futures import ProcessPoolExecutor\n\nimport duckdb\nimport numpy as np\nimport pandas as pd\nfrom pandas.testing import assert_frame_equal\n\nfrom sqlglot import exp, find_tables, parse_one, transpile\nfrom sqlglot.errors import ExecuteError\nfrom sqlglot.executor import execute\nfrom sqlglot.executor.python import Python\nfrom sqlglot.executor.table import Table, ensure_tables\nfrom sqlglot.optimizer import optimize\nfrom sqlglot.planner import Plan\nfrom tests.helpers import (\n    FIXTURES_DIR,\n    SKIP_INTEGRATION,\n    TPCH_SCHEMA,\n    TPCDS_SCHEMA,\n    load_sql_fixture_pairs,\n)\n\nDIR_TPCH = FIXTURES_DIR + \"/optimizer/tpc-h/\"\nDIR_TPCDS = FIXTURES_DIR + \"/optimizer/tpc-ds/\"\n\n\ndef open_file(file_name):\n    \"\"\"Open a file that may be compressed as gzip and return it in universal newline mode.\"\"\"\n    with open(file_name, \"rb\") as f:\n        gzipped = f.read(2) == b\"\\x1f\\x8b\"\n\n    if gzipped:\n        import gzip\n\n        return gzip.open(file_name, \"rt\", newline=\"\")\n\n    return open(file_name, encoding=\"utf-8\", newline=\"\")\n\n\n_schema = None\n_tables = None\n\n\ndef initializer(schema, tables):\n    global _schema, _tables\n    _schema = schema\n    _tables = tables\n\n\ndef mp_execute(expression, meta):\n    if not meta.get(\"execute\"):\n        return None\n\n    tables = {}\n\n    for t in find_tables(expression):\n        name = t.name\n        tables[name] = _tables[name]\n\n    return execute(expression, schema=_schema, tables=tables)\n\n\n@unittest.skipIf(SKIP_INTEGRATION, \"Skipping Integration Tests since `SKIP_INTEGRATION` is set\")\nclass TestExecutor(unittest.TestCase):\n    @classmethod\n    def setUpClass(cls):\n        cls.tpch_conn = duckdb.connect()\n        cls.tpcds_conn = duckdb.connect()\n        cls.tpch_tables = {}\n        cls.tpcds_tables = {}\n\n        def setup(conn, directory, table, columns, tables):\n            file_name = f\"{directory}{table}.csv.gz\"\n\n            conn.execute(\n                f\"\"\"\n                CREATE VIEW {table} AS\n                SELECT *\n                FROM READ_CSV('{file_name}', delim='|', header=True, columns={columns})\n                \"\"\"\n            )\n\n            reader = csv.reader(open_file(file_name), delimiter=\"|\")\n            rows = []\n            ctypes = []\n            tables[table] = rows\n\n            next(reader)\n\n            for row in reader:\n                if not ctypes:\n                    for i, v in enumerate(row):\n                        try:\n                            ctypes.append(type(ast.literal_eval(v)))\n                        except (ValueError, SyntaxError):\n                            ctypes.append(str)\n\n                rows.append(\n                    tuple(None if (t is not str and v == \"\") else t(v) for t, v in zip(ctypes, row))\n                )\n\n            tables[table] = Table(columns=columns, rows=rows)\n\n        for table, columns in TPCH_SCHEMA.items():\n            setup(cls.tpch_conn, DIR_TPCH, table, columns, cls.tpch_tables)\n\n        for table, columns in TPCDS_SCHEMA.items():\n            setup(cls.tpcds_conn, DIR_TPCDS, table, columns, cls.tpcds_tables)\n\n        cls.cache = {}\n        cls.tpch_sqls = list(load_sql_fixture_pairs(\"optimizer/tpc-h/tpc-h.sql\"))\n        cls.tpcds_sqls = list(load_sql_fixture_pairs(\"optimizer/tpc-ds/tpc-ds.sql\"))\n\n    @classmethod\n    def tearDownClass(cls):\n        cls.tpch_conn.close()\n        cls.tpcds_conn.close()\n\n    def cached_execute(self, sql, tpch=True):\n        conn = self.tpch_conn if tpch else self.tpcds_conn\n        if sql not in self.cache:\n            self.cache[sql] = conn.execute(transpile(sql, write=\"duckdb\")[0]).fetchdf()\n        return self.cache[sql]\n\n    def rename_anonymous(self, source, target):\n        for i, column in enumerate(source.columns):\n            if \"_col_\" in column:\n                source.rename(columns={column: target.columns[i]}, inplace=True)\n\n    def test_py_dialect(self):\n        generate = Python().generate\n        self.assertEqual(generate(parse_one(\"'x '''\")), r\"'x \\''\")\n        self.assertEqual(generate(parse_one(\"MAP([1], [2])\")), \"MAP([1], [2])\")\n        self.assertEqual(generate(parse_one(\"1 is null\")), \"1 == None\")\n        self.assertEqual(generate(parse_one(\"x is null\")), \"scope[None][x] is None\")\n\n    def test_optimized_tpch(self):\n        for i, (_, sql, optimized) in enumerate(self.tpch_sqls, start=1):\n            with self.subTest(f\"{i}, {sql}\"):\n                a = self.cached_execute(sql, tpch=True)\n                b = self.tpch_conn.execute(transpile(optimized, write=\"duckdb\")[0]).fetchdf()\n                self.rename_anonymous(b, a)\n                assert_frame_equal(a, b)\n\n    def subtestHelper(self, i, table, tpch=True):\n        with self.subTest(f\"{'tpc-h' if tpch else 'tpc-ds'} {i + 1}\"):\n            _, sql, _ = self.tpch_sqls[i] if tpch else self.tpcds_sqls[i]\n            a = self.cached_execute(sql, tpch=tpch)\n            b = pd.DataFrame(\n                ((np.nan if c is None else c for c in r) for r in table.rows),\n                columns=table.columns,\n            )\n            assert_frame_equal(a, b, check_dtype=False, check_index_type=False)\n\n    def _mp_execute(self, schema, tables, sqls, tpch):\n        with ProcessPoolExecutor(\n            initializer=initializer,\n            initargs=(schema, tables),\n        ) as pool:\n            futures = [pool.submit(mp_execute, parse_one(sql), args) for args, sql, _ in sqls]\n            for i, future in enumerate(futures):\n                table = future.result()\n                if table is not None:\n                    self.subtestHelper(i, table, tpch=tpch)\n\n    def test_execute_tpch(self):\n        self._mp_execute(TPCH_SCHEMA, self.tpch_tables, self.tpch_sqls, True)\n\n    def test_execute_tpcds(self):\n        self._mp_execute(TPCDS_SCHEMA, self.tpcds_tables, self.tpcds_sqls, False)\n\n    def test_execute_callable(self):\n        tables = {\n            \"x\": [\n                {\"a\": \"a\", \"b\": \"d\"},\n                {\"a\": \"b\", \"b\": \"e\"},\n                {\"a\": \"c\", \"b\": \"f\"},\n            ],\n            \"y\": [\n                {\"b\": \"d\", \"c\": \"g\"},\n                {\"b\": \"e\", \"c\": \"h\"},\n                {\"b\": \"f\", \"c\": \"i\"},\n            ],\n            \"z\": [],\n        }\n        schema = {\n            \"x\": {\n                \"a\": \"VARCHAR\",\n                \"b\": \"VARCHAR\",\n            },\n            \"y\": {\n                \"b\": \"VARCHAR\",\n                \"c\": \"VARCHAR\",\n            },\n            \"z\": {\"d\": \"VARCHAR\"},\n        }\n\n        for sql, cols, rows in [\n            (\"SELECT * FROM x\", [\"a\", \"b\"], [(\"a\", \"d\"), (\"b\", \"e\"), (\"c\", \"f\")]),\n            (\n                \"SELECT * FROM x JOIN y ON x.b = y.b\",\n                [\"a\", \"b\", \"b\", \"c\"],\n                [(\"a\", \"d\", \"d\", \"g\"), (\"b\", \"e\", \"e\", \"h\"), (\"c\", \"f\", \"f\", \"i\")],\n            ),\n            (\n                \"SELECT j.c AS d FROM x AS i JOIN y AS j ON i.b = j.b\",\n                [\"d\"],\n                [(\"g\",), (\"h\",), (\"i\",)],\n            ),\n            (\n                \"SELECT CONCAT(x.a, y.c) FROM x JOIN y ON x.b = y.b WHERE y.b = 'e'\",\n                [\"_col_0\"],\n                [(\"bh\",)],\n            ),\n            (\n                \"SELECT * FROM x JOIN y ON x.b = y.b WHERE y.b = 'e'\",\n                [\"a\", \"b\", \"b\", \"c\"],\n                [(\"b\", \"e\", \"e\", \"h\")],\n            ),\n            (\n                \"SELECT * FROM z\",\n                [\"d\"],\n                [],\n            ),\n            (\n                \"SELECT d FROM z ORDER BY d\",\n                [\"d\"],\n                [],\n            ),\n            (\n                \"SELECT a FROM x WHERE x.a <> 'b'\",\n                [\"a\"],\n                [(\"a\",), (\"c\",)],\n            ),\n            (\n                \"SELECT a AS i FROM x ORDER BY a\",\n                [\"i\"],\n                [(\"a\",), (\"b\",), (\"c\",)],\n            ),\n            (\n                \"SELECT a AS i FROM x ORDER BY i\",\n                [\"i\"],\n                [(\"a\",), (\"b\",), (\"c\",)],\n            ),\n            (\n                \"SELECT 100 - ORD(a) AS a, a AS i FROM x ORDER BY a\",\n                [\"a\", \"i\"],\n                [(1, \"c\"), (2, \"b\"), (3, \"a\")],\n            ),\n            (\n                \"SELECT a /* test */ FROM x LIMIT 1\",\n                [\"a\"],\n                [(\"a\",)],\n            ),\n            (\n                \"SELECT DISTINCT a FROM (SELECT 1 AS a UNION ALL SELECT 1 AS a)\",\n                [\"a\"],\n                [(1,)],\n            ),\n            (\n                \"SELECT DISTINCT a, SUM(b) AS b \"\n                \"FROM (SELECT 'a' AS a, 1 AS b UNION ALL SELECT 'a' AS a, 2 AS b UNION ALL SELECT 'b' AS a, 1 AS b) \"\n                \"GROUP BY a \"\n                \"LIMIT 1\",\n                [\"a\", \"b\"],\n                [(\"a\", 3)],\n            ),\n            (\n                \"SELECT COUNT(1) AS a FROM (SELECT 1)\",\n                [\"a\"],\n                [(1,)],\n            ),\n            (\n                \"SELECT COUNT(1) AS a FROM (SELECT 1) LIMIT 0\",\n                [\"a\"],\n                [],\n            ),\n            (\n                \"SELECT a FROM x GROUP BY a LIMIT 0\",\n                [\"a\"],\n                [],\n            ),\n            (\n                \"SELECT a FROM x LIMIT 0\",\n                [\"a\"],\n                [],\n            ),\n        ]:\n            with self.subTest(sql):\n                result = execute(sql, schema=schema, tables=tables)\n                self.assertEqual(result.columns, tuple(cols))\n                self.assertEqual(result.rows, rows)\n\n    def test_set_operations(self):\n        tables = {\n            \"x\": [\n                {\"a\": \"a\"},\n                {\"a\": \"b\"},\n                {\"a\": \"c\"},\n            ],\n            \"y\": [\n                {\"a\": \"b\"},\n                {\"a\": \"c\"},\n                {\"a\": \"d\"},\n            ],\n        }\n        schema = {\n            \"x\": {\n                \"a\": \"VARCHAR\",\n            },\n            \"y\": {\n                \"a\": \"VARCHAR\",\n            },\n        }\n\n        for sql, cols, rows in [\n            (\n                \"SELECT a FROM x UNION ALL SELECT a FROM y\",\n                [\"a\"],\n                [(\"a\",), (\"b\",), (\"c\",), (\"b\",), (\"c\",), (\"d\",)],\n            ),\n            (\n                \"SELECT a FROM x UNION SELECT a FROM y\",\n                [\"a\"],\n                [(\"a\",), (\"b\",), (\"c\",), (\"d\",)],\n            ),\n            (\n                \"SELECT a FROM x EXCEPT SELECT a FROM y\",\n                [\"a\"],\n                [(\"a\",)],\n            ),\n            (\n                \"(SELECT a FROM x) EXCEPT (SELECT a FROM y)\",\n                [\"a\"],\n                [(\"a\",)],\n            ),\n            (\n                \"SELECT a FROM x INTERSECT SELECT a FROM y\",\n                [\"a\"],\n                [(\"b\",), (\"c\",)],\n            ),\n            (\n                \"\"\"SELECT i.a\n                FROM (\n                  SELECT a FROM x UNION SELECT a FROM y\n                ) AS i\n                JOIN (\n                  SELECT a FROM x UNION SELECT a FROM y\n                ) AS j\n                  ON i.a = j.a\"\"\",\n                [\"a\"],\n                [(\"a\",), (\"b\",), (\"c\",), (\"d\",)],\n            ),\n            (\n                \"SELECT 1 AS a UNION SELECT 2 AS a UNION SELECT 3 AS a\",\n                [\"a\"],\n                [(1,), (2,), (3,)],\n            ),\n            (\n                \"SELECT 1 / 2 AS a\",\n                [\"a\"],\n                [\n                    (0.5,),\n                ],\n            ),\n            (\"SELECT 1 / 0 AS a\", [\"a\"], ZeroDivisionError),\n            (\n                exp.select(\n                    exp.alias_(exp.Literal.number(1).div(exp.Literal.number(2), typed=True), \"a\")\n                ),\n                [\"a\"],\n                [\n                    (0,),\n                ],\n            ),\n            (\n                exp.select(\n                    exp.alias_(exp.Literal.number(1).div(exp.Literal.number(0), safe=True), \"a\")\n                ),\n                [\"a\"],\n                [\n                    (None,),\n                ],\n            ),\n            (\n                \"SELECT a FROM x UNION ALL SELECT a FROM x LIMIT 1\",\n                [\"a\"],\n                [(\"a\",)],\n            ),\n        ]:\n            with self.subTest(sql):\n                if isinstance(rows, list):\n                    result = execute(sql, schema=schema, tables=tables)\n                    self.assertEqual(result.columns, tuple(cols))\n                    self.assertEqual(set(result.rows), set(rows))\n                else:\n                    with self.assertRaises(ExecuteError) as ctx:\n                        execute(sql, schema=schema, tables=tables)\n                    self.assertIsInstance(ctx.exception.__cause__, rows)\n\n    def test_execute_catalog_db_table(self):\n        tables = {\n            \"catalog\": {\n                \"db\": {\n                    \"x\": [\n                        {\"a\": \"a\"},\n                        {\"a\": \"b\"},\n                        {\"a\": \"c\"},\n                    ],\n                }\n            }\n        }\n        schema = {\n            \"catalog\": {\n                \"db\": {\n                    \"x\": {\n                        \"a\": \"VARCHAR\",\n                    }\n                }\n            }\n        }\n        result1 = execute(\"SELECT * FROM x\", schema=schema, tables=tables)\n        result2 = execute(\"SELECT * FROM catalog.db.x\", schema=schema, tables=tables)\n        assert result1.columns == result2.columns\n        assert result1.rows == result2.rows\n\n    def test_execute_tables(self):\n        tables = {\n            \"sushi\": [\n                {\"id\": 1, \"price\": 1.0},\n                {\"id\": 2, \"price\": 2.0},\n                {\"id\": 3, \"price\": 3.0},\n            ],\n            \"order_items\": [\n                {\"sushi_id\": 1, \"order_id\": 1},\n                {\"sushi_id\": 1, \"order_id\": 1},\n                {\"sushi_id\": 2, \"order_id\": 1},\n                {\"sushi_id\": 3, \"order_id\": 2},\n            ],\n            \"orders\": [\n                {\"id\": 1, \"user_id\": 1},\n                {\"id\": 2, \"user_id\": 2},\n            ],\n        }\n\n        self.assertEqual(\n            execute(\n                \"\"\"\n            SELECT\n              o.user_id,\n              SUM(s.price) AS price\n            FROM orders o\n            JOIN order_items i\n              ON o.id = i.order_id\n            JOIN sushi s\n              ON i.sushi_id = s.id\n            GROUP BY o.user_id\n        \"\"\",\n                tables=tables,\n            ).rows,\n            [\n                (1, 4.0),\n                (2, 3.0),\n            ],\n        )\n\n        self.assertEqual(\n            execute(\n                \"\"\"\n            SELECT\n              o.id, x.*\n            FROM orders o\n            LEFT JOIN (\n                SELECT\n                  1 AS id, 'b' AS x\n                UNION ALL\n                SELECT\n                  3 AS id, 'c' AS x\n            ) x\n              ON o.id = x.id\n        \"\"\",\n                tables=tables,\n            ).rows,\n            [(1, 1, \"b\"), (2, None, None)],\n        )\n        self.assertEqual(\n            execute(\n                \"\"\"\n            SELECT\n              o.id, x.*\n            FROM orders o\n            RIGHT JOIN (\n                SELECT\n                  1 AS id,\n                  'b' AS x\n                UNION ALL\n                SELECT\n                  3 AS id, 'c' AS x\n            ) x\n              ON o.id = x.id\n        \"\"\",\n                tables=tables,\n            ).rows,\n            [\n                (1, 1, \"b\"),\n                (None, 3, \"c\"),\n            ],\n        )\n\n    def test_execute_subqueries(self):\n        tables = {\n            \"table\": [\n                {\"a\": 1, \"b\": 1},\n                {\"a\": 2, \"b\": 2},\n            ],\n        }\n\n        self.assertEqual(\n            execute(\n                \"\"\"\n            SELECT *\n            FROM table\n            WHERE a = (SELECT MAX(a) FROM table)\n        \"\"\",\n                tables=tables,\n            ).rows,\n            [\n                (2, 2),\n            ],\n        )\n\n        table1_view = exp.Select().select(\"id\", \"sub_type\").from_(\"table1\").subquery()\n        select_from_sub_query = exp.Select().select(\"id AS id_alias\", \"sub_type\").from_(table1_view)\n        expression = exp.Select().select(\"*\").from_(\"cte1\").with_(\"cte1\", as_=select_from_sub_query)\n\n        schema = {\"table1\": {\"id\": \"str\", \"sub_type\": \"str\"}}\n        executed = execute(expression, tables={t: [] for t in schema}, schema=schema)\n\n        self.assertEqual(executed.rows, [])\n        self.assertEqual(executed.columns, (\"id_alias\", \"sub_type\"))\n\n    def test_correlated_count(self):\n        tables = {\n            \"parts\": [{\"pnum\": 0, \"qoh\": 1}],\n            \"supplies\": [],\n        }\n\n        schema = {\n            \"parts\": {\"pnum\": \"int\", \"qoh\": \"int\"},\n            \"supplies\": {\"pnum\": \"int\", \"shipdate\": \"int\"},\n        }\n\n        self.assertEqual(\n            execute(\n                \"\"\"\n\t\t\tselect *\n\t\t\tfrom parts\n\t\t\twhere parts.qoh >= (\n\t\t\t  select count(supplies.shipdate) + 1\n\t\t\t  from supplies\n\t\t\t  where supplies.pnum = parts.pnum and supplies.shipdate < 10\n            )\n        \"\"\",\n                tables=tables,\n                schema=schema,\n            ).rows,\n            [\n                (0, 1),\n            ],\n        )\n\n    def test_table_depth_mismatch(self):\n        tables = {\"table\": []}\n        schema = {\"db\": {\"table\": {\"col\": \"VARCHAR\"}}}\n        with self.assertRaises(ExecuteError):\n            execute(\"SELECT * FROM table\", schema=schema, tables=tables)\n\n    def test_tables(self):\n        tables = ensure_tables(\n            {\n                \"catalog1\": {\n                    \"db1\": {\n                        \"t1\": [\n                            {\"a\": 1},\n                        ],\n                        \"t2\": [\n                            {\"a\": 1},\n                        ],\n                    },\n                    \"db2\": {\n                        \"t3\": [\n                            {\"a\": 1},\n                        ],\n                        \"t4\": [\n                            {\"a\": 1},\n                        ],\n                    },\n                },\n                \"catalog2\": {\n                    \"db3\": {\n                        \"t5\": Table(columns=(\"a\",), rows=[(1,)]),\n                        \"t6\": Table(columns=(\"a\",), rows=[(1,)]),\n                    },\n                    \"db4\": {\n                        \"t7\": Table(columns=(\"a\",), rows=[(1,)]),\n                        \"t8\": Table(columns=(\"a\",), rows=[(1,)]),\n                    },\n                },\n            }\n        )\n\n        t1 = tables.find(exp.table_(table=\"t1\", db=\"db1\", catalog=\"catalog1\"))\n        self.assertEqual(t1.columns, (\"a\",))\n        self.assertEqual(t1.rows, [(1,)])\n\n        t8 = tables.find(exp.table_(table=\"t8\"))\n        self.assertEqual(t1.columns, t8.columns)\n        self.assertEqual(t1.rows, t8.rows)\n\n    def test_static_queries(self):\n        for sql, cols, rows in [\n            (\"SELECT 1\", [\"1\"], [(1,)]),\n            (\"SELECT 1 + 2 AS x\", [\"x\"], [(3,)]),\n            (\"SELECT CONCAT('a', 'b') AS x\", [\"x\"], [(\"ab\",)]),\n            (\"SELECT CONCAT('a', 1) AS x\", [\"x\"], [(\"a1\",)]),\n            (\"SELECT 1 AS x, 2 AS y\", [\"x\", \"y\"], [(1, 2)]),\n            (\"SELECT 'foo' LIMIT 1\", [\"foo\"], [(\"foo\",)]),\n            (\n                \"SELECT SUM(x), COUNT(x) FROM (SELECT 1 AS x WHERE FALSE)\",\n                [\"_col_0\", \"_col_1\"],\n                [(None, 0)],\n            ),\n        ]:\n            with self.subTest(sql):\n                result = execute(sql)\n                self.assertEqual(result.columns, tuple(cols))\n                self.assertEqual(result.rows, rows)\n\n    def test_aggregate_without_group_by(self):\n        result = execute(\"SELECT SUM(x) FROM t\", tables={\"t\": [{\"x\": 1}, {\"x\": 2}]})\n        self.assertEqual(result.columns, (\"_col_0\",))\n        self.assertEqual(result.rows, [(3,)])\n\n    def test_scalar_functions(self):\n        now = datetime.datetime.now()\n\n        for sql, expected in [\n            (\"CONCAT('a', 'b')\", \"ab\"),\n            (\"CONCAT('a', NULL)\", None),\n            (\"CONCAT_WS('_', 'a', 'b')\", \"a_b\"),\n            (\"STR_POSITION('foobarbar', 'bar')\", 4),\n            (\"STR_POSITION('foobarbar', 'bar', 5)\", 7),\n            (\"STR_POSITION('foobarbar', NULL)\", None),\n            (\"STR_POSITION(NULL, 'bar')\", None),\n            (\"UPPER('foo')\", \"FOO\"),\n            (\"UPPER(NULL)\", None),\n            (\"LOWER('FOO')\", \"foo\"),\n            (\"LOWER(NULL)\", None),\n            (\"IFNULL('a', 'b')\", \"a\"),\n            (\"IFNULL(NULL, 'b')\", \"b\"),\n            (\"IFNULL(NULL, NULL)\", None),\n            (\"SUBSTRING('12345')\", \"12345\"),\n            (\"SUBSTRING('12345', 3)\", \"345\"),\n            (\"SUBSTRING('12345', 3, 0)\", \"\"),\n            (\"SUBSTRING('12345', 3, 1)\", \"3\"),\n            (\"SUBSTRING('12345', 3, 2)\", \"34\"),\n            (\"SUBSTRING('12345', 3, 3)\", \"345\"),\n            (\"SUBSTRING('12345', 3, 4)\", \"345\"),\n            (\"SUBSTRING('12345', -3)\", \"345\"),\n            (\"SUBSTRING('12345', -3, 0)\", \"\"),\n            (\"SUBSTRING('12345', -3, 1)\", \"3\"),\n            (\"SUBSTRING('12345', -3, 2)\", \"34\"),\n            (\"SUBSTRING('12345', 0)\", \"\"),\n            (\"SUBSTRING('12345', 0, 1)\", \"\"),\n            (\"SUBSTRING(NULL)\", None),\n            (\"SUBSTRING(NULL, 1)\", None),\n            (\"CAST(1 AS TEXT)\", \"1\"),\n            (\"CAST('1' AS LONG)\", 1),\n            (\"CAST('1.1' AS FLOAT)\", 1.1),\n            (\"CAST('12:05:01' AS TIME)\", time(12, 5, 1)),\n            (\"COALESCE(NULL)\", None),\n            (\"COALESCE(NULL, NULL)\", None),\n            (\"COALESCE(NULL, 'b')\", \"b\"),\n            (\"COALESCE('a', 'b')\", \"a\"),\n            (\"1 << 1\", 2),\n            (\"1 >> 1\", 0),\n            (\"1 & 1\", 1),\n            (\"1 | 1\", 1),\n            (\"1 < 1\", False),\n            (\"1 <= 1\", True),\n            (\"1 > 1\", False),\n            (\"1 >= 1\", True),\n            (\"1 + NULL\", None),\n            (\"IF(true, 1, 0)\", 1),\n            (\"IF(false, 1, 0)\", 0),\n            (\"CASE WHEN 0 = 1 THEN 'foo' ELSE 'bar' END\", \"bar\"),\n            (\"CAST('2022-01-01' AS DATE) + INTERVAL '1' DAY\", date(2022, 1, 2)),\n            (\"INTERVAL '1' week\", datetime.timedelta(weeks=1)),\n            (\"1 IN (1, 2, 3)\", True),\n            (\"1 IN (2, 3)\", False),\n            (\"1 IN (1)\", True),\n            (\"NULL IS NULL\", True),\n            (\"NULL IS NOT NULL\", False),\n            (\"NULL = NULL\", None),\n            (\"NULL <> NULL\", None),\n            (\"YEAR(CURRENT_TIMESTAMP)\", now.year),\n            (\"MONTH(CURRENT_TIME)\", now.month),\n            (\"DAY(CURRENT_DATETIME())\", now.day),\n            (\"YEAR(CURRENT_DATE())\", now.year),\n            (\"MONTH(CURRENT_DATE())\", now.month),\n            (\"DAY(CURRENT_DATE())\", now.day),\n            (\"YEAR(CURRENT_TIMESTAMP) + 1\", now.year + 1),\n            (\n                \"YEAR(CURRENT_TIMESTAMP) IN (YEAR(CURRENT_TIMESTAMP) + 1, YEAR(CURRENT_TIMESTAMP) * 10)\",\n                False,\n            ),\n            (\"YEAR(CURRENT_TIMESTAMP) = (YEAR(CURRENT_TIMESTAMP))\", True),\n            (\"YEAR(CURRENT_TIMESTAMP) <> (YEAR(CURRENT_TIMESTAMP))\", False),\n            (\"YEAR(CURRENT_DATE()) + 1\", now.year + 1),\n            (\n                \"YEAR(CURRENT_DATE()) IN (YEAR(CURRENT_DATE()) + 1, YEAR(CURRENT_DATE()) * 10)\",\n                False,\n            ),\n            (\"YEAR(CURRENT_DATE()) = (YEAR(CURRENT_DATE()))\", True),\n            (\"YEAR(CURRENT_DATE()) <> (YEAR(CURRENT_DATE()))\", False),\n            (\"1::bool\", True),\n            (\"0::bool\", False),\n            (\"MAP(['a'], [1]).a\", 1),\n            (\"MAP()\", {}),\n            (\"STRFTIME('%j', '2023-03-23 15:00:00')\", \"082\"),\n            (\"STRFTIME('%j', NULL)\", None),\n            (\"DATESTRTODATE('2022-01-01')\", date(2022, 1, 1)),\n            (\"TIMESTRTOTIME('2022-01-01')\", datetime.datetime(2022, 1, 1)),\n            (\"LEFT('12345', 3)\", \"123\"),\n            (\"RIGHT('12345', 3)\", \"345\"),\n            (\"DATEDIFF('2022-01-03'::date, '2022-01-01'::TIMESTAMP::DATE)\", 2),\n            (\"TRIM(' foo ')\", \"foo\"),\n            (\"TRIM('afoob', 'ab')\", \"foo\"),\n            (\"ARRAY_JOIN(['foo', 'bar'], ':')\", \"foo:bar\"),\n            (\"ARRAY_JOIN(['hello', null ,'world'], ' ', ',')\", \"hello , world\"),\n            (\"ARRAY_JOIN(['', null ,'world'], ' ', ',')\", \" , world\"),\n            (\"STRUCT('foo', 'bar', null, null)\", {\"foo\": \"bar\"}),\n            (\"ROUND(1.5)\", 2),\n            (\"ROUND(1.2)\", 1),\n            (\"ROUND(1.2345, 2)\", 1.23),\n            (\"ROUND(NULL)\", None),\n            (\n                \"UNIXTOTIME(1659981729)\",\n                datetime.datetime(2022, 8, 8, 18, 2, 9, tzinfo=datetime.timezone.utc),\n            ),\n            (\"TIMESTRTOTIME('2013-04-05 01:02:03')\", datetime.datetime(2013, 4, 5, 1, 2, 3)),\n            (\n                \"UNIXTOTIME(40 * 365 * 86400)\",\n                datetime.datetime(2009, 12, 22, 00, 00, 00, tzinfo=datetime.timezone.utc),\n            ),\n            (\n                \"STRTOTIME('08/03/2024 12:34:56', '%d/%m/%Y %H:%M:%S')\",\n                datetime.datetime(2024, 3, 8, 12, 34, 56),\n            ),\n            (\"STRTOTIME('27/01/2024', '%d/%m/%Y')\", datetime.datetime(2024, 1, 27)),\n        ]:\n            with self.subTest(sql):\n                result = execute(f\"SELECT {sql}\")\n                self.assertEqual(result.rows, [(expected,)])\n\n        result = execute(\n            \"WITH t AS (SELECT 'a' AS c1, 'b' AS c2) SELECT NVL(c1, c2) FROM t\",\n            dialect=\"oracle\",\n        )\n        self.assertEqual(result.rows, [(\"a\",)])\n\n    def test_case_sensitivity(self):\n        result = execute(\"SELECT A AS A FROM X\", tables={\"x\": [{\"a\": 1}]})\n        self.assertEqual(result.columns, (\"a\",))\n        self.assertEqual(result.rows, [(1,)])\n\n        result = execute('SELECT A AS \"A\" FROM X', tables={\"x\": [{\"a\": 1}]})\n        self.assertEqual(result.columns, (\"A\",))\n        self.assertEqual(result.rows, [(1,)])\n\n    def test_nested_table_reference(self):\n        tables = {\n            \"some_catalog\": {\n                \"some_schema\": {\n                    \"some_table\": [\n                        {\"id\": 1, \"price\": 1.0},\n                        {\"id\": 2, \"price\": 2.0},\n                        {\"id\": 3, \"price\": 3.0},\n                    ]\n                }\n            }\n        }\n\n        result = execute(\"SELECT * FROM some_catalog.some_schema.some_table s\", tables=tables)\n\n        self.assertEqual(result.columns, (\"id\", \"price\"))\n        self.assertEqual(result.rows, [(1, 1.0), (2, 2.0), (3, 3.0)])\n\n    def test_group_by(self):\n        tables = {\n            \"x\": [\n                {\"a\": 1, \"b\": 10},\n                {\"a\": 2, \"b\": 20},\n                {\"a\": 3, \"b\": 28},\n                {\"a\": 2, \"b\": 25},\n                {\"a\": 1, \"b\": 40},\n            ],\n        }\n\n        for sql, expected, columns in (\n            (\n                \"SELECT a, AVG(b) FROM x GROUP BY a ORDER BY AVG(b)\",\n                [(2, 22.5), (1, 25.0), (3, 28.0)],\n                (\"a\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a, AVG(b) FROM x GROUP BY a having avg(b) > 23\",\n                [(1, 25.0), (3, 28.0)],\n                (\"a\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a, AVG(b) FROM x GROUP BY a having avg(b + 1) > 23\",\n                [(1, 25.0), (2, 22.5), (3, 28.0)],\n                (\"a\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a, AVG(b) FROM x GROUP BY a having sum(b) + 5 > 50\",\n                [(1, 25.0)],\n                (\"a\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a + 1 AS a, AVG(b + 1) FROM x GROUP BY a + 1 having AVG(b + 1) > 26\",\n                [(4, 29.0)],\n                (\"a\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a, avg(b) FROM x GROUP BY a HAVING a = 1\",\n                [(1, 25.0)],\n                (\"a\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a + 1, avg(b) FROM x GROUP BY a + 1 HAVING a + 1 = 2\",\n                [(2, 25.0)],\n                (\"_col_0\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a FROM x GROUP BY a ORDER BY AVG(b)\",\n                [(2,), (1,), (3,)],\n                (\"a\",),\n            ),\n            (\n                \"SELECT a, SUM(b) FROM x GROUP BY a ORDER BY COUNT(*)\",\n                [(3, 28), (1, 50), (2, 45)],\n                (\"a\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a, SUM(b) FROM x GROUP BY a ORDER BY COUNT(*) DESC\",\n                [(1, 50), (2, 45), (3, 28)],\n                (\"a\", \"_col_1\"),\n            ),\n            (\n                \"SELECT a, ARRAY_UNIQUE_AGG(b) FROM x GROUP BY a\",\n                [(1, [40, 10]), (2, [25, 20]), (3, [28])],\n                (\"a\", \"_col_1\"),\n            ),\n        ):\n            with self.subTest(sql):\n                result = execute(sql, tables=tables)\n                self.assertEqual(result.columns, columns)\n                self.assertEqual(result.rows, expected)\n\n    def test_nested_values(self):\n        tables = {\"foo\": [{\"raw\": {\"name\": \"Hello, World\", \"a\": [{\"b\": 1}]}}]}\n\n        result = execute(\"SELECT raw:name AS name FROM foo\", dialect=\"snowflake\", tables=tables)\n        self.assertEqual(result.columns, (\"NAME\",))\n        self.assertEqual(result.rows, [(\"Hello, World\",)])\n\n        result = execute(\"SELECT raw:a[0].b AS b FROM foo\", dialect=\"snowflake\", tables=tables)\n        self.assertEqual(result.columns, (\"B\",))\n        self.assertEqual(result.rows, [(1,)])\n\n        result = execute(\"SELECT raw:a[1].b AS b FROM foo\", dialect=\"snowflake\", tables=tables)\n        self.assertEqual(result.columns, (\"B\",))\n        self.assertEqual(result.rows, [(None,)])\n\n        result = execute(\"SELECT raw:a[0].c AS c FROM foo\", dialect=\"snowflake\", tables=tables)\n        self.assertEqual(result.columns, (\"C\",))\n        self.assertEqual(result.rows, [(None,)])\n\n        tables = {\n            '\"ITEM\"': [\n                {\"id\": 1, \"attributes\": {\"flavor\": \"cherry\", \"taste\": \"sweet\"}},\n                {\"id\": 2, \"attributes\": {\"flavor\": \"lime\", \"taste\": \"sour\"}},\n                {\"id\": 3, \"attributes\": {\"flavor\": \"apple\", \"taste\": None}},\n            ]\n        }\n        result = execute(\n            \"SELECT i.attributes.flavor FROM `ITEM` i\", dialect=\"bigquery\", tables=tables\n        )\n\n        self.assertEqual(result.columns, (\"flavor\",))\n        self.assertEqual(result.rows, [(\"cherry\",), (\"lime\",), (\"apple\",)])\n\n        tables = {\"t\": [{\"x\": [1, 2, 3]}]}\n\n        result = execute(\"SELECT x FROM t\", dialect=\"duckdb\", tables=tables)\n        self.assertEqual(result.columns, (\"x\",))\n        self.assertEqual(result.rows, [([1, 2, 3],)])\n\n    def test_agg_order(self):\n        plan = Plan(\n            optimize(\"\"\"\n            SELECT\n              AVG(bill_length_mm) AS avg_bill_length,\n              AVG(bill_depth_mm) AS avg_bill_depth\n            FROM penguins\n            \"\"\")\n        )\n\n        assert [agg.alias for agg in plan.root.aggregations] == [\n            \"avg_bill_length\",\n            \"avg_bill_depth\",\n        ]\n\n    def test_table_to_pylist(self):\n        columns = [\"id\", \"product\", \"price\"]\n        rows = [[1, \"Shirt\", 20.0], [2, \"Shoes\", 60.0]]\n        table = Table(columns=columns, rows=rows)\n        expected = [\n            {\"id\": 1, \"product\": \"Shirt\", \"price\": 20.0},\n            {\"id\": 2, \"product\": \"Shoes\", \"price\": 60.0},\n        ]\n        self.assertEqual(table.to_pylist(), expected)\n"
  },
  {
    "path": "tests/test_expressions.py",
    "content": "import datetime\nimport math\nimport sys\nimport unittest\n\nfrom sqlglot import ParseError, alias, exp, parse_one\n\n\nclass TestExprs(unittest.TestCase):\n    maxDiff = None\n\n    def test_to_s(self):\n        self.assertEqual(repr(parse_one(\"5\")), \"Literal(this=5, is_string=False)\")\n        self.assertEqual(repr(parse_one(\"5.3\")), \"Literal(this=5.3, is_string=False)\")\n        self.assertEqual(repr(parse_one(\"True\")), \"Boolean(this=True)\")\n        self.assertEqual(repr(parse_one(\"'  x'\")), \"Literal(this='  x', is_string=True)\")\n        self.assertEqual(repr(parse_one(\"' \\n  x'\")), \"Literal(this=' \\\\n  x', is_string=True)\")\n        self.assertEqual(\n            repr(parse_one(\"   x \")), \"Column(\\n  this=Identifier(this=x, quoted=False))\"\n        )\n        self.assertEqual(\n            repr(parse_one('\"   x \"')), \"Column(\\n  this=Identifier(this='   x ', quoted=True))\"\n        )\n\n    def test_arg_key(self):\n        self.assertEqual(parse_one(\"sum(1)\").find(exp.Literal).arg_key, \"this\")\n\n    def test_depth(self):\n        self.assertEqual(parse_one(\"x(1)\").find(exp.Literal).depth, 1)\n\n    def test_iter(self):\n        self.assertEqual([exp.Literal.number(1), exp.Literal.number(2)], list(parse_one(\"[1, 2]\")))\n\n        with self.assertRaises(TypeError):\n            for x in parse_one(\"1\"):\n                pass\n\n    def test_eq(self):\n        query = parse_one(\"SELECT x FROM t\")\n        self.assertEqual(query, query.copy())\n\n        self.assertNotEqual(exp.to_identifier(\"a\"), exp.to_identifier(\"A\"))\n\n        self.assertEqual(\n            exp.Column(table=exp.to_identifier(\"b\"), this=exp.to_identifier(\"b\")),\n            exp.Column(this=exp.to_identifier(\"b\"), table=exp.to_identifier(\"b\")),\n        )\n\n        self.assertNotEqual(exp.to_identifier(\"a\", quoted=True), exp.to_identifier(\"A\"))\n        self.assertNotEqual(exp.to_identifier(\"A\", quoted=True), exp.to_identifier(\"A\"))\n        self.assertNotEqual(\n            exp.to_identifier(\"A\", quoted=True), exp.to_identifier(\"a\", quoted=True)\n        )\n        self.assertNotEqual(parse_one(\"'x'\"), parse_one(\"'X'\"))\n        self.assertNotEqual(parse_one(\"'1'\"), parse_one(\"1\"))\n        self.assertEqual(parse_one(\"`a`\", read=\"hive\"), parse_one('\"a\"'))\n        self.assertEqual(parse_one(\"`a`\", read=\"hive\"), parse_one('\"a\"  '))\n        self.assertEqual(parse_one(\"`a`.`b`\", read=\"hive\"), parse_one('\"a\".\"b\"'))\n        self.assertEqual(parse_one(\"select a, b+1\"), parse_one(\"SELECT a, b + 1\"))\n        self.assertNotEqual(parse_one(\"`a`.`b`.`c`\", read=\"hive\"), parse_one(\"a.b.c\"))\n        self.assertNotEqual(parse_one(\"a.b.c.d\", read=\"hive\"), parse_one(\"a.b.c\"))\n        self.assertEqual(parse_one(\"a.b.c.d\", read=\"hive\"), parse_one(\"a.b.c.d\"))\n        self.assertEqual(parse_one(\"a + b * c - 1.0\"), parse_one(\"a+b*c-1.0\"))\n        self.assertNotEqual(parse_one(\"a + b * c - 1.0\"), parse_one(\"a + b * c + 1.0\"))\n        self.assertEqual(parse_one(\"a as b\"), parse_one(\"a AS b\"))\n        self.assertNotEqual(parse_one(\"a as b\"), parse_one(\"a\"))\n        self.assertEqual(\n            parse_one(\"ROW() OVER(Partition by y)\"),\n            parse_one(\"ROW() OVER (partition BY y)\"),\n        )\n        self.assertEqual(exp.Table(pivots=[]), exp.Table())\n        self.assertNotEqual(exp.Table(pivots=[None]), exp.Table())\n        self.assertEqual(\n            exp.DataType.build(\"int\"), exp.DataType(this=exp.DataType.Type.INT, nested=False)\n        )\n        self.assertNotEqual(\n            exp.Identifier(this=\"a\", temporary=True),\n            exp.Identifier(this=\"a\"),\n        )\n\n    def test_eq_on_same_instance_short_circuits(self):\n        expr = parse_one(\"1\")\n        expr == expr\n        self.assertIsNone(expr._hash)\n\n    def test_find(self):\n        expression = parse_one(\"CREATE TABLE x STORED AS PARQUET AS SELECT * FROM y\")\n        self.assertTrue(expression.find(exp.Create))\n        self.assertFalse(expression.find(exp.Group))\n        self.assertEqual(\n            [table.name for table in expression.find_all(exp.Table)],\n            [\"x\", \"y\"],\n        )\n\n    def test_find_all(self):\n        expression = parse_one(\n            \"\"\"\n            SELECT *\n            FROM (\n                SELECT b.*\n                FROM a.b b\n            ) x\n            JOIN (\n              SELECT c.foo\n              FROM a.c c\n              WHERE foo = 1\n            ) y\n              ON x.c = y.foo\n            CROSS JOIN (\n              SELECT *\n              FROM (\n                SELECT d.bar\n                FROM d\n              ) nested\n            ) z\n              ON x.c = y.foo\n            \"\"\"\n        )\n\n        self.assertEqual(\n            [table.name for table in expression.find_all(exp.Table)],\n            [\"b\", \"c\", \"d\"],\n        )\n\n        expression = parse_one(\"select a + b + c + d\")\n\n        self.assertEqual(\n            [column.name for column in expression.find_all(exp.Column)],\n            [\"d\", \"c\", \"a\", \"b\"],\n        )\n        self.assertEqual(\n            [column.name for column in expression.find_all(exp.Column, bfs=False)],\n            [\"a\", \"b\", \"c\", \"d\"],\n        )\n\n    def test_find_ancestor(self):\n        column = parse_one(\"select * from foo where (a + 1 > 2)\").find(exp.Column)\n        self.assertIsInstance(column, exp.Column)\n        self.assertIsInstance(column.parent_select, exp.Select)\n        self.assertIsNone(column.find_ancestor(exp.Join))\n\n    def test_to_dot(self):\n        orig = parse_one('a.b.c.\"d\".e.f')\n        self.assertEqual(\".\".join(str(p) for p in orig.parts), 'a.b.c.\"d\".e.f')\n\n        self.assertEqual(\n            \".\".join(\n                str(p)\n                for p in exp.Dot.build(\n                    [\n                        exp.to_table(\"a.b.c\"),\n                        exp.to_identifier(\"d\"),\n                        exp.to_identifier(\"e\"),\n                        exp.to_identifier(\"f\"),\n                    ]\n                ).parts\n            ),\n            \"a.b.c.d.e.f\",\n        )\n\n        self.assertEqual(\".\".join(str(p) for p in orig.parts), 'a.b.c.\"d\".e.f')\n        column = orig.find(exp.Column)\n        dot = column.to_dot()\n\n        self.assertEqual(dot.sql(), 'a.b.c.\"d\".e.f')\n\n        self.assertEqual(\n            dot,\n            exp.Dot(\n                this=exp.Dot(\n                    this=exp.Dot(\n                        this=exp.Dot(\n                            this=exp.Dot(\n                                this=exp.to_identifier(\"a\"),\n                                expression=exp.to_identifier(\"b\"),\n                            ),\n                            expression=exp.to_identifier(\"c\"),\n                        ),\n                        expression=exp.to_identifier(\"d\", quoted=True),\n                    ),\n                    expression=exp.to_identifier(\"e\"),\n                ),\n                expression=exp.to_identifier(\"f\"),\n            ),\n        )\n\n    def test_root(self):\n        ast = parse_one(\"select * from (select a from x)\")\n        self.assertIs(ast, ast.root())\n        self.assertIs(ast, ast.find(exp.Column).root())\n\n    def test_alias_or_name(self):\n        expression = parse_one(\n            \"SELECT a, b AS B, c + d AS e, *, 'zz', 'zz' AS z FROM foo as bar, baz\"\n        )\n        self.assertEqual(\n            [e.alias_or_name for e in expression.expressions],\n            [\"a\", \"B\", \"e\", \"*\", \"zz\", \"z\"],\n        )\n        self.assertEqual(\n            {e.alias_or_name for e in expression.find_all(exp.Table)},\n            {\"bar\", \"baz\"},\n        )\n\n        expression = parse_one(\n            \"\"\"\n            WITH first AS (SELECT * FROM foo),\n                 second AS (SELECT * FROM bar)\n            SELECT * FROM first, second, (SELECT * FROM baz) AS third\n        \"\"\"\n        )\n\n        self.assertEqual(\n            [e.alias_or_name for e in expression.args[\"with_\"].expressions],\n            [\"first\", \"second\"],\n        )\n\n        self.assertEqual(\"first\", expression.args[\"from_\"].alias_or_name)\n        self.assertEqual(\n            [e.alias_or_name for e in expression.args[\"joins\"]],\n            [\"second\", \"third\"],\n        )\n\n        self.assertEqual(parse_one(\"x.*\").name, \"*\")\n        self.assertEqual(parse_one(\"NULL\").name, \"NULL\")\n        self.assertEqual(parse_one(\"a.b.c\").name, \"c\")\n\n    def test_table_name(self):\n        bq_dashed_table = exp.to_table(\"a-1.b.c\", dialect=\"bigquery\")\n        self.assertEqual(exp.table_name(bq_dashed_table), '\"a-1\".b.c')\n        self.assertEqual(exp.table_name(bq_dashed_table, dialect=\"bigquery\"), \"`a-1`.b.c\")\n        self.assertEqual(exp.table_name(\"a-1.b.c\", dialect=\"bigquery\"), \"`a-1`.b.c\")\n        self.assertEqual(exp.table_name(parse_one(\"a\", into=exp.Table)), \"a\")\n        self.assertEqual(exp.table_name(parse_one(\"a.b\", into=exp.Table)), \"a.b\")\n        self.assertEqual(exp.table_name(parse_one(\"a.b.c\", into=exp.Table)), \"a.b.c\")\n        self.assertEqual(exp.table_name(\"a.b.c\"), \"a.b.c\")\n        self.assertEqual(exp.table_name(exp.to_table(\"a.b.c.d.e\", dialect=\"bigquery\")), \"a.b.c.d.e\")\n        self.assertEqual(exp.table_name(exp.to_table(\"'@foo'\", dialect=\"snowflake\")), \"'@foo'\")\n        self.assertEqual(exp.table_name(exp.to_table(\"@foo\", dialect=\"snowflake\")), \"@foo\")\n        self.assertEqual(exp.table_name(bq_dashed_table, identify=True), '\"a-1\".\"b\".\"c\"')\n        self.assertEqual(\n            exp.table_name(\n                parse_one(\"foo.`{bar,er}`\", read=\"databricks\", into=exp.Table), dialect=\"databricks\"\n            ),\n            \"foo.`{bar,er}`\",\n        )\n        self.assertEqual(\n            exp.table_name(parse_one(\"/*c*/foo.bar\", into=exp.Table), identify=True), '\"foo\".\"bar\"'\n        )\n\n    def test_table(self):\n        self.assertEqual(exp.table_(\"a\", alias=\"b\"), parse_one(\"select * from a b\").find(exp.Table))\n        self.assertEqual(exp.table_(\"a\", \"\").sql(), \"a\")\n        self.assertEqual(exp.Table(db=exp.to_identifier(\"a\")).sql(), \"a\")\n\n    def test_replace_tables(self):\n        self.assertEqual(\n            exp.replace_tables(\n                parse_one(\n                    'select * from a AS a, b, c.a, d.a cross join e.a cross join \"f-F\".\"A\" cross join G'\n                ),\n                {\n                    \"a\": \"a1\",\n                    \"b\": \"b.a\",\n                    \"c.a\": \"c.a2\",\n                    \"d.a\": \"d2\",\n                    \"`f-F`.`A`\": '\"F\"',\n                    \"g\": \"g1.a\",\n                },\n                dialect=\"bigquery\",\n            ).sql(),\n            'SELECT * FROM a1 AS a /* a */, b.a /* b */, c.a2 /* c.a */, d2 /* d.a */ CROSS JOIN e.a CROSS JOIN \"F\" /* f-F.A */ CROSS JOIN g1.a /* g */',\n        )\n\n        self.assertEqual(\n            exp.replace_tables(\n                parse_one(\"select * from example.table\", dialect=\"bigquery\"),\n                {\"example.table\": \"`my-project.example.table`\"},\n                dialect=\"bigquery\",\n            ).sql(),\n            'SELECT * FROM \"my-project\".\"example\".\"table\" /* example.table */',\n        )\n\n        self.assertEqual(\n            exp.replace_tables(\n                parse_one(\"select * from example.table /* sqlglot.meta replace=false */\"),\n                {\"example.table\": \"a.b\"},\n            ).sql(),\n            \"SELECT * FROM example.table /* sqlglot.meta replace=false */\",\n        )\n\n    def test_expand(self):\n        self.assertEqual(\n            exp.expand(\n                parse_one('select * from \"a-b\".\"C\" AS a'),\n                {\n                    \"`a-b`.`c`\": parse_one(\"select 1\"),\n                },\n                dialect=\"spark\",\n            ).sql(),\n            \"SELECT * FROM (SELECT 1) AS a /* source: a-b.c */\",\n        )\n\n    def test_expand_with_lazy_source_provider(self):\n        self.assertEqual(\n            exp.expand(\n                parse_one('select * from \"a-b\".\"C\" AS a'),\n                {\"`a-b`.c\": lambda: parse_one(\"select 1\", dialect=\"spark\")},\n                dialect=\"spark\",\n            ).sql(),\n            \"SELECT * FROM (SELECT 1) AS a /* source: a-b.c */\",\n        )\n\n    def test_replace_placeholders(self):\n        self.assertEqual(\n            exp.replace_placeholders(\n                parse_one(\"select * from :tbl1 JOIN :tbl2 ON :col1 = :str1 WHERE :col2 > :int1\"),\n                tbl1=exp.to_identifier(\"foo\"),\n                tbl2=exp.to_identifier(\"bar\"),\n                col1=exp.to_identifier(\"a\"),\n                col2=exp.to_identifier(\"c\"),\n                str1=\"b\",\n                int1=100,\n            ).sql(),\n            \"SELECT * FROM foo JOIN bar ON a = 'b' WHERE c > 100\",\n        )\n        self.assertEqual(\n            exp.replace_placeholders(\n                parse_one(\"select * from ? JOIN ? ON ? = ? WHERE ? = 'bla'\"),\n                exp.to_identifier(\"foo\"),\n                exp.to_identifier(\"bar\"),\n                exp.to_identifier(\"a\"),\n                \"b\",\n                \"bla\",\n            ).sql(),\n            \"SELECT * FROM foo JOIN bar ON a = 'b' WHERE 'bla' = 'bla'\",\n        )\n        self.assertEqual(\n            exp.replace_placeholders(\n                parse_one(\"select * from ? WHERE ? > 100\"),\n                exp.to_identifier(\"foo\"),\n            ).sql(),\n            \"SELECT * FROM foo WHERE ? > 100\",\n        )\n        self.assertEqual(\n            exp.replace_placeholders(\n                parse_one(\"select * from :name WHERE ? > 100\"), another_name=\"bla\"\n            ).sql(),\n            \"SELECT * FROM :name WHERE ? > 100\",\n        )\n        self.assertEqual(\n            exp.replace_placeholders(\n                parse_one(\"select * from (SELECT :col1 FROM ?) WHERE :col2 > ?\"),\n                exp.to_identifier(\"tbl1\"),\n                100,\n                \"tbl3\",\n                col1=exp.to_identifier(\"a\"),\n                col2=exp.to_identifier(\"b\"),\n                col3=\"c\",\n            ).sql(),\n            \"SELECT * FROM (SELECT a FROM tbl1) WHERE b > 100\",\n        )\n        self.assertEqual(\n            exp.replace_placeholders(\n                parse_one(\"select * from foo WHERE x > ? AND y IS ?\"), 0, False\n            ).sql(),\n            \"SELECT * FROM foo WHERE x > 0 AND y IS FALSE\",\n        )\n        self.assertEqual(\n            exp.replace_placeholders(\n                parse_one(\"select * from foo WHERE x > :int1 AND y IS :bool1\"), int1=0, bool1=False\n            ).sql(),\n            \"SELECT * FROM foo WHERE x > 0 AND y IS FALSE\",\n        )\n\n    def test_function_building(self):\n        self.assertEqual(exp.func(\"max\", 1).sql(), \"MAX(1)\")\n        self.assertEqual(exp.func(\"max\", 1, 2).sql(), \"MAX(1, 2)\")\n        self.assertEqual(exp.func(\"bla\", 1, \"foo\").sql(), \"BLA(1, foo)\")\n        self.assertEqual(exp.func(\"COUNT\", exp.Star()).sql(), \"COUNT(*)\")\n        self.assertEqual(exp.func(\"bloo\").sql(), \"BLOO()\")\n        self.assertEqual(\n            exp.func(\"concat\", exp.convert(\"a\"), dialect=\"duckdb\").sql(\"duckdb\"), \"CONCAT('a')\"\n        )\n        self.assertEqual(\n            exp.func(\"locate\", \"'x'\", \"'xo'\", dialect=\"hive\").sql(\"hive\"), \"LOCATE('x', 'xo')\"\n        )\n        self.assertEqual(\n            exp.func(\"log\", exp.to_identifier(\"x\"), 2, dialect=\"bigquery\").sql(\"bigquery\"),\n            \"LOG(x, 2)\",\n        )\n        self.assertEqual(\n            exp.func(\"log\", dialect=\"bigquery\", expression=\"x\", this=2).sql(\"bigquery\"),\n            \"LOG(x, 2)\",\n        )\n\n        self.assertIsInstance(exp.func(\"instr\", \"x\", \"b\", dialect=\"mysql\"), exp.StrPosition)\n        self.assertIsInstance(exp.func(\"instr\", \"x\", \"b\", dialect=\"sqlite\"), exp.StrPosition)\n        self.assertIsInstance(exp.func(\"bla\", 1, \"foo\"), exp.Anonymous)\n        self.assertIsInstance(\n            exp.func(\"cast\", this=exp.Literal.number(5), to=exp.DataType.build(\"DOUBLE\")),\n            exp.Cast,\n        )\n\n        with self.assertRaises(ValueError):\n            exp.func(\"some_func\", 1, arg2=\"foo\")\n\n        with self.assertRaises(ValueError):\n            exp.func(\"abs\")\n\n        with self.assertRaises(ValueError) as cm:\n            exp.func(\"to_hex\", dialect=\"bigquery\", this=5)\n\n        self.assertEqual(\n            str(cm.exception),\n            \"Unable to convert 'to_hex' into a Func. Either manually construct the Func \"\n            \"expression of interest or parse the function call.\",\n        )\n\n    def test_named_selects(self):\n        expression = parse_one(\n            \"SELECT a, b AS B, c + d AS e, *, 'zz', 'zz' AS z FROM foo as bar, baz\"\n        )\n        self.assertEqual(expression.named_selects, [\"a\", \"B\", \"e\", \"*\", \"zz\", \"z\"])\n\n        expression = parse_one(\n            \"\"\"\n            WITH first AS (SELECT * FROM foo)\n            SELECT foo.bar, foo.baz as bazz, SUM(x) FROM first\n        \"\"\"\n        )\n        self.assertEqual(expression.named_selects, [\"bar\", \"bazz\"])\n\n        expression = parse_one(\n            \"\"\"\n            SELECT foo, bar FROM first\n            UNION SELECT \"ss\" as foo, bar FROM second\n            UNION ALL SELECT foo, bazz FROM third\n        \"\"\"\n        )\n        self.assertEqual(expression.named_selects, [\"foo\", \"bar\"])\n\n    def test_selects(self):\n        expression = parse_one(\"SELECT FROM x\")\n        self.assertEqual(expression.selects, [])\n\n        expression = parse_one(\"SELECT a FROM x\")\n        self.assertEqual([s.sql() for s in expression.selects], [\"a\"])\n\n        expression = parse_one(\"SELECT a, b FROM x\")\n        self.assertEqual([s.sql() for s in expression.selects], [\"a\", \"b\"])\n\n        expression = parse_one(\"(SELECT a, b FROM x)\")\n        self.assertEqual([s.sql() for s in expression.selects], [\"a\", \"b\"])\n\n    def test_alias_column_names(self):\n        expression = parse_one(\"SELECT * FROM (SELECT * FROM x) AS y\")\n        subquery = expression.find(exp.Subquery)\n        self.assertEqual(subquery.alias_column_names, [])\n\n        expression = parse_one(\"SELECT * FROM (SELECT * FROM x) AS y(a)\")\n        subquery = expression.find(exp.Subquery)\n        self.assertEqual(subquery.alias_column_names, [\"a\"])\n\n        expression = parse_one(\"SELECT * FROM (SELECT * FROM x) AS y(a, b)\")\n        subquery = expression.find(exp.Subquery)\n        self.assertEqual(subquery.alias_column_names, [\"a\", \"b\"])\n\n        expression = parse_one(\"WITH y AS (SELECT * FROM x) SELECT * FROM y\")\n        cte = expression.find(exp.CTE)\n        self.assertEqual(cte.alias_column_names, [])\n\n        expression = parse_one(\"WITH y(a, b) AS (SELECT * FROM x) SELECT * FROM y\")\n        cte = expression.find(exp.CTE)\n        self.assertEqual(cte.alias_column_names, [\"a\", \"b\"])\n\n        expression = parse_one(\"SELECT * FROM tbl AS tbl(a, b)\")\n        table = expression.find(exp.Table)\n        self.assertEqual(table.alias_column_names, [\"a\", \"b\"])\n\n    def test_cast(self):\n        expression = parse_one(\"CAST(x AS DATE)\")\n        self.assertIs(expression.type, expression.to)\n\n        expression = parse_one(\"select cast(x as DATE)\")\n        casts = list(expression.find_all(exp.Cast))\n        self.assertEqual(len(casts), 1)\n\n        cast = casts[0]\n        self.assertTrue(cast.to.is_type(exp.DataType.Type.DATE))\n\n        # check that already cast values arent re-cast if wrapped in a cast to the same type\n        recast = exp.cast(cast, to=exp.DataType.Type.DATE)\n        self.assertEqual(recast, cast)\n        self.assertEqual(recast.sql(), \"CAST(x AS DATE)\")\n\n        # however, recasting is fine if the types are different\n        recast = exp.cast(cast, to=exp.DataType.Type.VARCHAR)\n        self.assertNotEqual(recast, cast)\n        self.assertEqual(len(list(recast.find_all(exp.Cast))), 2)\n        self.assertEqual(recast.sql(), \"CAST(CAST(x AS DATE) AS VARCHAR)\")\n\n        # check that dialect is used when casting strings\n        self.assertEqual(\n            exp.cast(\"x\", to=\"regtype\", dialect=\"postgres\").sql(), \"CAST(x AS REGTYPE)\"\n        )\n        self.assertEqual(exp.cast(\"`x`\", to=\"date\", dialect=\"hive\").sql(), 'CAST(\"x\" AS DATE)')\n\n    def test_ctes(self):\n        expression = parse_one(\"SELECT a FROM x\")\n        self.assertEqual(expression.ctes, [])\n\n        expression = parse_one(\"WITH x AS (SELECT a FROM y) SELECT a FROM x\")\n        self.assertEqual([s.sql() for s in expression.ctes], [\"x AS (SELECT a FROM y)\"])\n\n    def test_hash(self):\n        self.assertEqual(\n            {\n                parse_one(\"select a.b\"),\n                parse_one(\"1+2\"),\n                parse_one('\"a\".\"b\"'),\n                parse_one(\"a.b.c.d\"),\n            },\n            {\n                parse_one(\"select a.b\"),\n                parse_one(\"1+2\"),\n                parse_one('\"a\".\"b\"'),\n                parse_one(\"a.b.c.d\"),\n            },\n        )\n\n    def test_sql(self):\n        self.assertEqual(parse_one(\"x + y * 2\").sql(), \"x + y * 2\")\n        self.assertEqual(parse_one('select \"x\"').sql(dialect=\"hive\", pretty=True), \"SELECT\\n  `x`\")\n        self.assertEqual(parse_one(\"X + y\").sql(identify=True, normalize=True), '\"x\" + \"y\"')\n        self.assertEqual(parse_one('\"X\" + Y').sql(identify=True, normalize=True), '\"X\" + \"y\"')\n        self.assertEqual(parse_one(\"SUM(X)\").sql(identify=True, normalize=True), 'SUM(\"x\")')\n\n    def test_transform_with_arguments(self):\n        expression = parse_one(\"a\")\n\n        def fun(node, alias_=True):\n            if alias_:\n                return parse_one(\"a AS a\")\n            return node\n\n        transformed_expression = expression.transform(fun)\n        self.assertEqual(transformed_expression.sql(dialect=\"presto\"), \"a AS a\")\n\n        transformed_expression_2 = expression.transform(fun, alias_=False)\n        self.assertEqual(transformed_expression_2.sql(dialect=\"presto\"), \"a\")\n\n    def test_transform_simple(self):\n        expression = parse_one(\"IF(a > 0, a, b)\")\n\n        def fun(node):\n            if isinstance(node, exp.Column) and node.name == \"a\":\n                return parse_one(\"c - 2\")\n            return node\n\n        actual_expression_1 = expression.transform(fun)\n        self.assertEqual(actual_expression_1.sql(dialect=\"presto\"), \"IF(c - 2 > 0, c - 2, b)\")\n        self.assertIsNot(actual_expression_1, expression)\n\n        actual_expression_2 = expression.transform(fun, copy=False)\n        self.assertEqual(actual_expression_2.sql(dialect=\"presto\"), \"IF(c - 2 > 0, c - 2, b)\")\n        self.assertIs(actual_expression_2, expression)\n\n    def test_transform_no_infinite_recursion(self):\n        expression = parse_one(\"a\")\n\n        def fun(node):\n            if isinstance(node, exp.Column) and node.name == \"a\":\n                return parse_one(\"FUN(a)\")\n            return node\n\n        self.assertEqual(expression.transform(fun).sql(), \"FUN(a)\")\n\n    def test_transform_with_parent_mutation(self):\n        expression = parse_one(\"SELECT COUNT(1) FROM table\")\n\n        def fun(node):\n            if str(node) == \"COUNT(1)\":\n                # node gets silently mutated here - its parent points to the filter node\n                return exp.Filter(this=node, expression=exp.Where(this=exp.true()))\n            return node\n\n        transformed = expression.transform(fun)\n        self.assertEqual(transformed.sql(), \"SELECT COUNT(1) FILTER(WHERE TRUE) FROM table\")\n\n    def test_transform_multiple_children(self):\n        expression = parse_one(\"SELECT * FROM x\")\n\n        def fun(node):\n            if isinstance(node, exp.Star):\n                return [parse_one(c) for c in [\"a\", \"b\"]]\n            return node\n\n        self.assertEqual(expression.transform(fun).sql(), \"SELECT a, b FROM x\")\n\n    def test_transform_node_removal(self):\n        expression = parse_one(\"SELECT a, b FROM x\")\n\n        def remove_column_b(node):\n            if isinstance(node, exp.Column) and node.name == \"b\":\n                return None\n            return node\n\n        self.assertEqual(expression.transform(remove_column_b).sql(), \"SELECT a FROM x\")\n\n        expression = parse_one(\"CAST(x AS FLOAT)\")\n\n        def remove_non_list_arg(node):\n            if isinstance(node, exp.DataType):\n                return None\n            return node\n\n        self.assertEqual(expression.transform(remove_non_list_arg).sql(), \"CAST(x AS)\")\n\n        expression = parse_one(\"SELECT a, b FROM x\")\n\n        def remove_all_columns(node):\n            if isinstance(node, exp.Column):\n                return None\n            return node\n\n        self.assertEqual(expression.transform(remove_all_columns).sql(), \"SELECT FROM x\")\n\n    def test_replace(self):\n        expression = parse_one(\"SELECT a, b FROM x\")\n        expression.find(exp.Column).replace(parse_one(\"c\"))\n        self.assertEqual(expression.sql(), \"SELECT c, b FROM x\")\n        expression.find(exp.Table).replace(parse_one(\"y\"))\n        self.assertEqual(expression.sql(), \"SELECT c, b FROM y\")\n\n        # we try to replace a with a list but a's parent is actually ordered, not the ORDER BY node\n        expression = parse_one(\"SELECT * FROM x ORDER BY a DESC, c\")\n        expression.find(exp.Ordered).this.replace([exp.column(\"a\").asc(), exp.column(\"b\").desc()])\n        self.assertEqual(expression.sql(), \"SELECT * FROM x ORDER BY a, b DESC, c\")\n\n    def test_arg_deletion(self):\n        # Using the pop helper method\n        expression = parse_one(\"SELECT a, b FROM x\")\n        expression.find(exp.Column).pop()\n        self.assertEqual(expression.sql(), \"SELECT b FROM x\")\n\n        expression.find(exp.Column).pop()\n        self.assertEqual(expression.sql(), \"SELECT FROM x\")\n\n        expression.pop()\n        self.assertEqual(expression.sql(), \"SELECT FROM x\")\n\n        expression = parse_one(\"WITH x AS (SELECT a FROM x) SELECT * FROM x\")\n        expression.find(exp.With).pop()\n        self.assertEqual(expression.sql(), \"SELECT * FROM x\")\n\n        # Manually deleting by setting to None\n        expression = parse_one(\"SELECT * FROM foo JOIN bar\")\n        self.assertEqual(len(expression.args.get(\"joins\", [])), 1)\n\n        expression.set(\"joins\", None)\n        self.assertEqual(expression.sql(), \"SELECT * FROM foo\")\n        self.assertEqual(expression.args.get(\"joins\", []), [])\n        self.assertIsNone(expression.args.get(\"joins\"))\n\n    def test_walk(self):\n        expression = parse_one(\"SELECT * FROM (SELECT * FROM x)\")\n        self.assertEqual(len(list(expression.walk())), 9)\n        self.assertEqual(len(list(expression.walk(bfs=False))), 9)\n        self.assertTrue(all(isinstance(e, exp.Expr) for e in expression.walk()))\n        self.assertTrue(all(isinstance(e, exp.Expr) for e in expression.walk(bfs=False)))\n\n    def test_str_position_order(self):\n        str_position_exp = parse_one(\"STR_POSITION('mytest', 'test')\")\n        self.assertIsInstance(str_position_exp, exp.StrPosition)\n        self.assertEqual(str_position_exp.args.get(\"this\").this, \"mytest\")\n        self.assertEqual(str_position_exp.args.get(\"substr\").this, \"test\")\n\n    def test_functions(self):\n        self.assertIsInstance(parse_one(\"x LIKE ANY (y)\"), exp.Like)\n        self.assertIsInstance(parse_one(\"x ILIKE ANY (y)\"), exp.ILike)\n        self.assertIsInstance(parse_one(\"ABS(a)\"), exp.Abs)\n        self.assertIsInstance(parse_one(\"APPROX_DISTINCT(a)\"), exp.ApproxDistinct)\n        self.assertIsInstance(parse_one(\"ARRAY(a)\"), exp.Array)\n        self.assertIsInstance(parse_one(\"ARRAY_AGG(a)\"), exp.ArrayAgg)\n        self.assertIsInstance(parse_one(\"ARRAY_CONTAINS(a, 'a')\"), exp.ArrayContains)\n        self.assertIsInstance(parse_one(\"ARRAY_SIZE(a)\"), exp.ArraySize)\n        self.assertIsInstance(parse_one(\"ARRAY_INTERSECTION([1, 2], [2, 3])\"), exp.ArrayIntersect)\n        self.assertIsInstance(parse_one(\"ARRAY_INTERSECT([1, 2], [2, 3])\"), exp.ArrayIntersect)\n        self.assertIsInstance(parse_one(\"AVG(a)\"), exp.Avg)\n        self.assertIsInstance(parse_one(\"BEGIN DEFERRED TRANSACTION\"), exp.Transaction)\n        self.assertIsInstance(parse_one(\"CEIL(a)\"), exp.Ceil)\n        self.assertIsInstance(parse_one(\"CEILING(a)\"), exp.Ceil)\n        self.assertIsInstance(parse_one(\"COALESCE(a, b)\"), exp.Coalesce)\n        self.assertIsInstance(parse_one(\"COMMIT\"), exp.Commit)\n        self.assertIsInstance(parse_one(\"COUNT(a)\"), exp.Count)\n        self.assertIsInstance(parse_one(\"COUNT_IF(a > 0)\"), exp.CountIf)\n        self.assertIsInstance(parse_one(\"DATE_ADD(a, 1)\"), exp.DateAdd)\n        self.assertIsInstance(parse_one(\"DATE_DIFF(a, 2)\"), exp.DateDiff)\n        self.assertIsInstance(parse_one(\"DATE_STR_TO_DATE(a)\"), exp.DateStrToDate)\n        self.assertIsInstance(parse_one(\"TS_OR_DS_TO_TIME(a)\"), exp.TsOrDsToTime)\n        self.assertIsInstance(parse_one(\"DAY(a)\"), exp.Day)\n        self.assertIsInstance(parse_one(\"EXP(a)\"), exp.Exp)\n        self.assertIsInstance(parse_one(\"FLOOR(a)\"), exp.Floor)\n        self.assertIsInstance(parse_one(\"GENERATE_SERIES(a, b, c)\"), exp.GenerateSeries)\n        self.assertIsInstance(parse_one(\"GLOB(x, y)\"), exp.Glob)\n        self.assertIsInstance(parse_one(\"GREATEST(a, b)\"), exp.Greatest)\n        self.assertIsInstance(parse_one(\"IF(a, b, c)\"), exp.If)\n        self.assertIsInstance(parse_one(\"INITCAP(a)\"), exp.Initcap)\n        self.assertIsInstance(parse_one(\"JSON_EXTRACT(a, '$.name')\"), exp.JSONExtract)\n        self.assertIsInstance(parse_one(\"JSON_EXTRACT_SCALAR(a, '$.name')\"), exp.JSONExtractScalar)\n        self.assertIsInstance(parse_one(\"LEAST(a, b)\"), exp.Least)\n        self.assertIsInstance(parse_one(\"LIKE(x, y)\"), exp.Like)\n        self.assertIsInstance(parse_one(\"LN(a)\"), exp.Ln)\n        self.assertIsInstance(parse_one(\"LOG(b, n)\"), exp.Log)\n        self.assertIsInstance(parse_one(\"LOG2(a)\"), exp.Log)\n        self.assertIsInstance(parse_one(\"LOG10(a)\"), exp.Log)\n        self.assertIsInstance(parse_one(\"MAX(a)\"), exp.Max)\n        self.assertIsInstance(parse_one(\"MIN(a)\"), exp.Min)\n        self.assertIsInstance(parse_one(\"MONTH(a)\"), exp.Month)\n        self.assertIsInstance(parse_one(\"QUARTER(a)\"), exp.Quarter)\n        self.assertIsInstance(parse_one(\"POSITION(' ' IN a)\"), exp.StrPosition)\n        self.assertIsInstance(parse_one(\"POW(a, 2)\"), exp.Pow)\n        self.assertIsInstance(parse_one(\"POWER(a, 2)\"), exp.Pow)\n        self.assertIsInstance(parse_one(\"QUANTILE(a, 0.90)\"), exp.Quantile)\n        self.assertIsInstance(parse_one(\"REGEXP_LIKE(a, 'test')\"), exp.RegexpLike)\n        self.assertIsInstance(parse_one(\"REGEXP_SPLIT(a, 'test')\"), exp.RegexpSplit)\n        self.assertIsInstance(parse_one(\"ROLLBACK\"), exp.Rollback)\n        self.assertIsInstance(parse_one(\"ROUND(a)\"), exp.Round)\n        self.assertIsInstance(parse_one(\"ROUND(a, 2)\"), exp.Round)\n        self.assertIsInstance(parse_one(\"SPLIT(a, 'test')\"), exp.Split)\n        self.assertIsInstance(parse_one(\"ST_POINT(10, 20)\"), exp.StPoint)\n        self.assertIsInstance(parse_one(\"ST_DISTANCE(a, b)\"), exp.StDistance)\n        self.assertIsInstance(parse_one(\"STR_POSITION(a, 'test')\"), exp.StrPosition)\n        self.assertIsInstance(parse_one(\"STR_TO_UNIX(a, 'format')\"), exp.StrToUnix)\n        self.assertIsInstance(parse_one(\"STRUCT_EXTRACT(a, 'test')\"), exp.StructExtract)\n        self.assertIsInstance(parse_one(\"SUBSTR('a', 1, 1)\"), exp.Substring)\n        self.assertIsInstance(parse_one(\"SUBSTRING('a', 1, 1)\"), exp.Substring)\n        self.assertIsInstance(parse_one(\"SUM(a)\"), exp.Sum)\n        self.assertIsInstance(parse_one(\"SQRT(a)\"), exp.Sqrt)\n        self.assertIsInstance(parse_one(\"STDDEV(a)\"), exp.Stddev)\n        self.assertIsInstance(parse_one(\"STDDEV_POP(a)\"), exp.StddevPop)\n        self.assertIsInstance(parse_one(\"STDDEV_SAMP(a)\"), exp.StddevSamp)\n        self.assertIsInstance(parse_one(\"TIME_TO_STR(a, 'format')\"), exp.TimeToStr)\n        self.assertIsInstance(parse_one(\"TIME_TO_TIME_STR(a)\"), exp.Cast)\n        self.assertIsInstance(parse_one(\"TIME_TO_UNIX(a)\"), exp.TimeToUnix)\n        self.assertIsInstance(parse_one(\"TIME_STR_TO_DATE(a)\"), exp.TimeStrToDate)\n        self.assertIsInstance(parse_one(\"TIME_STR_TO_TIME(a)\"), exp.TimeStrToTime)\n        self.assertIsInstance(parse_one(\"TIME_STR_TO_TIME(a, 'some_zone')\"), exp.TimeStrToTime)\n        self.assertIsInstance(parse_one(\"TIME_STR_TO_UNIX(a)\"), exp.TimeStrToUnix)\n        self.assertIsInstance(parse_one(\"TRIM(LEADING 'b' FROM 'bla')\"), exp.Trim)\n        self.assertIsInstance(parse_one(\"TS_OR_DS_ADD(a, 1, 'day')\"), exp.TsOrDsAdd)\n        self.assertIsInstance(parse_one(\"TS_OR_DS_TO_DATE(a)\"), exp.TsOrDsToDate)\n        self.assertIsInstance(parse_one(\"TS_OR_DS_TO_DATE_STR(a)\"), exp.Substring)\n        self.assertIsInstance(parse_one(\"UNIX_TO_STR(a, 'format')\"), exp.UnixToStr)\n        self.assertIsInstance(parse_one(\"UNIX_TO_TIME(a)\"), exp.UnixToTime)\n        self.assertIsInstance(parse_one(\"UNIX_TO_TIME_STR(a)\"), exp.UnixToTimeStr)\n        self.assertIsInstance(parse_one(\"VARIANCE(a)\"), exp.Variance)\n        self.assertIsInstance(parse_one(\"VARIANCE_POP(a)\"), exp.VariancePop)\n        self.assertIsInstance(parse_one(\"YEAR(a)\"), exp.Year)\n        self.assertIsInstance(parse_one(\"HLL(a)\"), exp.Hll)\n        self.assertIsInstance(parse_one(\"ARRAY(time, foo)\"), exp.Array)\n        self.assertIsInstance(parse_one(\"STANDARD_HASH('hello', 'sha256')\"), exp.StandardHash)\n        self.assertIsInstance(parse_one(\"DATE(foo)\"), exp.Date)\n        self.assertIsInstance(parse_one(\"HEX(foo)\"), exp.Hex)\n        self.assertIsInstance(parse_one(\"LOWER(HEX(foo))\"), exp.LowerHex)\n        self.assertIsInstance(parse_one(\"TO_HEX(foo)\", read=\"bigquery\"), exp.LowerHex)\n        self.assertIsInstance(parse_one(\"UPPER(TO_HEX(foo))\", read=\"bigquery\"), exp.Hex)\n        self.assertIsInstance(parse_one(\"TO_HEX(MD5(foo))\", read=\"bigquery\"), exp.MD5)\n        self.assertIsInstance(parse_one(\"TRANSFORM(a, b)\", read=\"spark\"), exp.Transform)\n        self.assertIsInstance(parse_one(\"ADD_MONTHS(a, b)\"), exp.AddMonths)\n\n        ast = parse_one(\"GREATEST(a, b, c)\")\n        self.assertIsInstance(ast.expressions, list)\n        self.assertEqual(len(ast.expressions), 2)\n\n    def test_column(self):\n        column = exp.column(exp.Star(), table=\"t\")\n        self.assertEqual(column.sql(), \"t.*\")\n\n        column = parse_one(\"a.b.c.d\")\n        self.assertEqual(column.catalog, \"a\")\n        self.assertEqual(column.db, \"b\")\n        self.assertEqual(column.table, \"c\")\n        self.assertEqual(column.name, \"d\")\n\n        column = parse_one(\"a\")\n        self.assertEqual(column.name, \"a\")\n        self.assertEqual(column.table, \"\")\n\n        fields = parse_one(\"a.b.c.d.e\")\n        self.assertIsInstance(fields, exp.Dot)\n        self.assertEqual(fields.text(\"expression\"), \"e\")\n        column = fields.find(exp.Column)\n        self.assertEqual(column.name, \"d\")\n        self.assertEqual(column.table, \"c\")\n        self.assertEqual(column.db, \"b\")\n        self.assertEqual(column.catalog, \"a\")\n\n        column = parse_one(\"a[0].b\")\n        self.assertIsInstance(column, exp.Dot)\n        self.assertIsInstance(column.this, exp.Bracket)\n        self.assertIsInstance(column.this.this, exp.Column)\n\n        column = parse_one(\"a.*\")\n        self.assertIsInstance(column, exp.Column)\n        self.assertIsInstance(column.this, exp.Star)\n        self.assertIsInstance(column.args[\"table\"], exp.Identifier)\n        self.assertEqual(column.table, \"a\")\n\n        self.assertIsInstance(parse_one(\"*\"), exp.Star)\n        self.assertEqual(exp.column(\"a\", table=\"b\", db=\"c\", catalog=\"d\"), exp.to_column(\"d.c.b.a\"))\n\n        dot = exp.column(\"d\", \"c\", \"b\", \"a\", fields=[\"e\", \"f\"])\n        self.assertIsInstance(dot, exp.Dot)\n        self.assertEqual(dot.sql(), \"a.b.c.d.e.f\")\n\n        dot = exp.column(\"d\", \"c\", \"b\", \"a\", fields=[\"e\", \"f\"], quoted=True)\n        self.assertEqual(dot.sql(), '\"a\".\"b\".\"c\".\"d\".\"e\".\"f\"')\n\n    def test_text(self):\n        column = parse_one(\"a.b.c.d.e\")\n        self.assertEqual(column.text(\"expression\"), \"e\")\n        self.assertEqual(column.text(\"y\"), \"\")\n        self.assertEqual(parse_one(\"select * from x.y\").find(exp.Table).text(\"db\"), \"x\")\n        self.assertEqual(parse_one(\"select *\").name, \"\")\n        self.assertEqual(parse_one(\"1 + 1\").name, \"1\")\n        self.assertEqual(parse_one(\"'a'\").name, \"a\")\n\n    def test_alias(self):\n        self.assertEqual(alias(\"foo\", \"bar\").sql(), \"foo AS bar\")\n        self.assertEqual(alias(\"foo\", \"bar-1\").sql(), 'foo AS \"bar-1\"')\n        self.assertEqual(alias(\"foo\", \"bar_1\").sql(), \"foo AS bar_1\")\n        self.assertEqual(alias(\"foo * 2\", \"2bar\").sql(), 'foo * 2 AS \"2bar\"')\n        self.assertEqual(alias('\"foo\"', \"_bar\").sql(), '\"foo\" AS _bar')\n        self.assertEqual(alias(\"foo\", \"bar\", quoted=True).sql(), 'foo AS \"bar\"')\n\n    def test_alias_with_placeholder(self):\n        # Snowflake's `AS :name` syntax parses the alias as a Placeholder node.\n        # Regression test: Expression.alias should return the placeholder name, not \"\".\n        expr = parse_one(\"SELECT PARSE_JSON(col) AS :userInfo FROM t\", dialect=\"snowflake\")\n        select = expr.selects[0]\n        self.assertIsInstance(select.args.get(\"alias\"), exp.Placeholder)\n        self.assertEqual(select.alias, \"userInfo\")\n        self.assertEqual(select.alias_or_name, \"userInfo\")\n        self.assertEqual(select.output_name, \"userInfo\")\n\n    def test_unit(self):\n        unit = parse_one(\"timestamp_trunc(current_timestamp, week(thursday))\")\n        self.assertIsNotNone(unit.find(exp.CurrentTimestamp))\n        week = unit.find(exp.Week)\n        self.assertEqual(week.this, exp.var(\"thursday\"))\n\n        for abbreviated_unit, unnabreviated_unit in exp.TimeUnit.UNABBREVIATED_UNIT_NAME.items():\n            interval = parse_one(f\"interval '500 {abbreviated_unit}'\")\n            self.assertIsInstance(interval.unit, exp.Var)\n            self.assertEqual(interval.unit.name, unnabreviated_unit)\n\n    def test_identifier(self):\n        self.assertTrue(exp.to_identifier('\"x\"').quoted)\n        self.assertFalse(exp.to_identifier(\"x\").quoted)\n        self.assertTrue(exp.to_identifier(\"foo \").quoted)\n        self.assertFalse(exp.to_identifier(\"_x\").quoted)\n\n    def test_function_normalizer(self):\n        self.assertEqual(parse_one(\"HELLO()\").sql(normalize_functions=\"lower\"), \"hello()\")\n        self.assertEqual(parse_one(\"hello()\").sql(normalize_functions=\"upper\"), \"HELLO()\")\n        self.assertEqual(parse_one(\"heLLO()\").sql(normalize_functions=False), \"heLLO()\")\n        self.assertEqual(parse_one(\"SUM(x)\").sql(normalize_functions=\"lower\"), \"sum(x)\")\n        self.assertEqual(parse_one(\"sum(x)\").sql(normalize_functions=\"upper\"), \"SUM(x)\")\n\n    def test_properties_from_dict(self):\n        self.assertEqual(\n            exp.Properties.from_dict(\n                {\n                    \"FORMAT\": \"parquet\",\n                    \"PARTITIONED_BY\": (exp.to_identifier(\"a\"), exp.to_identifier(\"b\")),\n                    \"custom\": 1,\n                    \"ENGINE\": None,\n                    \"COLLATE\": True,\n                }\n            ),\n            exp.Properties(\n                expressions=[\n                    exp.FileFormatProperty(this=exp.Literal.string(\"parquet\")),\n                    exp.PartitionedByProperty(\n                        this=exp.Tuple(expressions=[exp.to_identifier(\"a\"), exp.to_identifier(\"b\")])\n                    ),\n                    exp.Property(this=exp.Literal.string(\"custom\"), value=exp.Literal.number(1)),\n                    exp.EngineProperty(this=exp.null()),\n                    exp.CollateProperty(this=exp.true()),\n                ]\n            ),\n        )\n\n        self.assertRaises(ValueError, exp.Properties.from_dict, {\"FORMAT\": object})\n\n    def test_convert(self):\n        from collections import namedtuple\n\n        import pytz\n\n        PointTuple = namedtuple(\"Point\", [\"x\", \"y\"])\n\n        class PointClass:\n            def __init__(self, x=0, y=0):\n                self.x = x\n                self.y = y\n\n        for value, expected in [\n            (1, \"1\"),\n            (\"1\", \"'1'\"),\n            (None, \"NULL\"),\n            (True, \"TRUE\"),\n            ((1, \"2\", None), \"(1, '2', NULL)\"),\n            ([1, \"2\", None], \"ARRAY(1, '2', NULL)\"),\n            ({\"x\": None}, \"MAP(ARRAY('x'), ARRAY(NULL))\"),\n            (\n                datetime.datetime(2022, 10, 1, 1, 1, 1, 1),\n                \"TIME_STR_TO_TIME('2022-10-01 01:01:01.000001')\",\n            ),\n            (\n                datetime.datetime(2022, 10, 1, 1, 1, 1, tzinfo=datetime.timezone.utc),\n                \"TIME_STR_TO_TIME('2022-10-01 01:01:01+00:00', 'UTC')\",\n            ),\n            (\n                pytz.timezone(\"America/Los_Angeles\").localize(\n                    datetime.datetime(2022, 10, 1, 1, 1, 1)\n                ),\n                \"TIME_STR_TO_TIME('2022-10-01 01:01:01-07:00', 'America/Los_Angeles')\",\n            ),\n            (datetime.date(2022, 10, 1), \"DATE_STR_TO_DATE('2022-10-01')\"),\n            (math.nan, \"NULL\"),\n            (b\"\\x00\\x00\\x00\\x00\\x00\\x00\\x07\\xd3\", \"2003\"),\n            (PointTuple(1, 2), \"STRUCT(1 AS x, 2 AS y)\"),\n            (PointClass(1, 2), \"STRUCT(1 AS x, 2 AS y)\"),\n        ]:\n            with self.subTest(value):\n                self.assertEqual(exp.convert(value).sql(), expected)\n\n        self.assertEqual(\n            exp.convert({\"test\": \"value\"}).sql(dialect=\"spark\"),\n            \"MAP_FROM_ARRAYS(ARRAY('test'), ARRAY('value'))\",\n        )\n\n    @unittest.skipUnless(sys.version_info >= (3, 9), \"zoneinfo only available from python 3.9+\")\n    def test_convert_python39(self):\n        import zoneinfo\n\n        for value, expected in [\n            (\n                datetime.datetime(\n                    2022, 10, 1, 1, 1, 1, tzinfo=zoneinfo.ZoneInfo(\"America/Los_Angeles\")\n                ),\n                \"TIME_STR_TO_TIME('2022-10-01 01:01:01-07:00', 'America/Los_Angeles')\",\n            )\n        ]:\n            with self.subTest(value):\n                self.assertEqual(exp.convert(value).sql(), expected)\n\n    def test_comment_alias(self):\n        sql = \"\"\"\n        SELECT\n            a,\n            b AS B,\n            c, /*comment*/\n            d AS D, -- another comment\n            CAST(x AS INT), -- yet another comment\n            y AND /* foo */ w AS E -- final comment\n        FROM foo\n        \"\"\"\n        expression = parse_one(sql)\n        self.assertEqual(\n            [e.alias_or_name for e in expression.expressions],\n            [\"a\", \"B\", \"c\", \"D\", \"x\", \"E\"],\n        )\n        self.assertEqual(\n            expression.sql(),\n            \"SELECT a, b AS B, c /* comment */, d AS D /* another comment */, CAST(x AS INT) /* yet another comment */, y AND /* foo */ w AS E /* final comment */ FROM foo\",\n        )\n        self.assertEqual(\n            expression.sql(comments=False),\n            \"SELECT a, b AS B, c, d AS D, CAST(x AS INT), y AND w AS E FROM foo\",\n        )\n        self.assertEqual(\n            expression.sql(pretty=True, comments=False),\n            \"\"\"SELECT\n  a,\n  b AS B,\n  c,\n  d AS D,\n  CAST(x AS INT),\n  y AND w AS E\nFROM foo\"\"\",\n        )\n        self.assertEqual(\n            expression.sql(pretty=True),\n            \"\"\"SELECT\n  a,\n  b AS B,\n  c, /* comment */\n  d AS D, /* another comment */\n  CAST(x AS INT), /* yet another comment */\n  y AND /* foo */ w AS E /* final comment */\nFROM foo\"\"\",\n        )\n\n        self.assertEqual(parse_one('max(x) as \"a b\" -- comment').comments, [\" comment\"])\n\n    def test_to_interval(self):\n        self.assertEqual(exp.to_interval(\"1day\").sql(), \"INTERVAL '1' DAY\")\n        self.assertEqual(exp.to_interval(\"  5     months\").sql(), \"INTERVAL '5' MONTHS\")\n        self.assertEqual(exp.to_interval(\"-2 day\").sql(), \"INTERVAL '-2' DAY\")\n\n        self.assertEqual(exp.to_interval(exp.Literal.string(\"1day\")).sql(), \"INTERVAL '1' DAY\")\n        self.assertEqual(exp.to_interval(exp.Literal.string(\"-2 day\")).sql(), \"INTERVAL '-2' DAY\")\n        self.assertEqual(\n            exp.to_interval(exp.Literal.string(\"  5   months\")).sql(), \"INTERVAL '5' MONTHS\"\n        )\n\n    def test_to_table(self):\n        table_only = exp.to_table(\"table_name\")\n        self.assertEqual(table_only.name, \"table_name\")\n        self.assertIsNone(table_only.args.get(\"db\"))\n        self.assertIsNone(table_only.args.get(\"catalog\"))\n\n        db_and_table = exp.to_table(\"db.table_name\")\n        self.assertEqual(db_and_table.name, \"table_name\")\n        self.assertEqual(db_and_table.args.get(\"db\"), exp.to_identifier(\"db\"))\n        self.assertIsNone(db_and_table.args.get(\"catalog\"))\n\n        catalog_db_and_table = exp.to_table(\"catalog.db.table_name\")\n        self.assertEqual(catalog_db_and_table.name, \"table_name\")\n        self.assertEqual(catalog_db_and_table.args.get(\"db\"), exp.to_identifier(\"db\"))\n        self.assertEqual(catalog_db_and_table.args.get(\"catalog\"), exp.to_identifier(\"catalog\"))\n\n        table_only_unsafe_identifier = exp.to_table(\"3e\")\n        self.assertEqual(table_only_unsafe_identifier.sql(), '\"3e\"')\n\n    def test_to_column(self):\n        column_only = exp.to_column(\"column_name\")\n        self.assertEqual(column_only.name, \"column_name\")\n        self.assertIsNone(column_only.args.get(\"table\"))\n        table_and_column = exp.to_column(\"table_name.column_name\")\n        self.assertEqual(table_and_column.name, \"column_name\")\n        self.assertEqual(table_and_column.args.get(\"table\"), exp.to_identifier(\"table_name\"))\n\n        self.assertEqual(exp.to_column(\"foo bar\").sql(), '\"foo bar\"')\n        self.assertEqual(exp.to_column(\"`column_name`\", dialect=\"spark\").sql(), '\"column_name\"')\n        self.assertEqual(exp.to_column(\"column_name\", quoted=True).sql(), '\"column_name\"')\n        self.assertEqual(\n            exp.to_column(\"column_name\", table=exp.to_identifier(\"table_name\")).sql(),\n            \"table_name.column_name\",\n        )\n\n    def test_union(self):\n        expression = parse_one(\"SELECT cola, colb UNION SELECT colx, coly\")\n        self.assertIsInstance(expression, exp.Union)\n        self.assertEqual(expression.named_selects, [\"cola\", \"colb\"])\n        self.assertEqual(\n            expression.selects,\n            [\n                exp.Column(this=exp.to_identifier(\"cola\")),\n                exp.Column(this=exp.to_identifier(\"colb\")),\n            ],\n        )\n\n    def test_values(self):\n        self.assertEqual(\n            exp.values([(1, 2), (3, 4)], \"t\", [\"a\", \"b\"]).sql(),\n            \"(VALUES (1, 2), (3, 4)) AS t(a, b)\",\n        )\n        self.assertEqual(\n            exp.values(\n                [(1, 2), (3, 4)],\n                \"t\",\n                {\"a\": exp.DataType.build(\"TEXT\"), \"b\": exp.DataType.build(\"TEXT\")},\n            ).sql(),\n            \"(VALUES (1, 2), (3, 4)) AS t(a, b)\",\n        )\n        with self.assertRaises(ValueError):\n            exp.values([(1, 2), (3, 4)], columns=[\"a\"])\n\n    def test_data_type_builder(self):\n        self.assertEqual(exp.DataType.build(\"TEXT\").sql(), \"TEXT\")\n        self.assertEqual(exp.DataType.build(\"DECIMAL(10, 2)\").sql(), \"DECIMAL(10, 2)\")\n        self.assertEqual(exp.DataType.build(\"VARCHAR(255)\").sql(), \"VARCHAR(255)\")\n        self.assertEqual(exp.DataType.build(\"ARRAY<INT>\").sql(), \"ARRAY<INT>\")\n        self.assertEqual(exp.DataType.build(\"CHAR\").sql(), \"CHAR\")\n        self.assertEqual(exp.DataType.build(\"NCHAR\").sql(), \"CHAR\")\n        self.assertEqual(exp.DataType.build(\"VARCHAR\").sql(), \"VARCHAR\")\n        self.assertEqual(exp.DataType.build(\"NVARCHAR\").sql(), \"VARCHAR\")\n        self.assertEqual(exp.DataType.build(\"TEXT\").sql(), \"TEXT\")\n        self.assertEqual(exp.DataType.build(\"BINARY\").sql(), \"BINARY\")\n        self.assertEqual(exp.DataType.build(\"VARBINARY\").sql(), \"VARBINARY\")\n        self.assertEqual(exp.DataType.build(\"INT\").sql(), \"INT\")\n        self.assertEqual(exp.DataType.build(\"TINYINT\").sql(), \"TINYINT\")\n        self.assertEqual(exp.DataType.build(\"SMALLINT\").sql(), \"SMALLINT\")\n        self.assertEqual(exp.DataType.build(\"BIGINT\").sql(), \"BIGINT\")\n        self.assertEqual(exp.DataType.build(\"FLOAT\").sql(), \"FLOAT\")\n        self.assertEqual(exp.DataType.build(\"DOUBLE\").sql(), \"DOUBLE\")\n        self.assertEqual(exp.DataType.build(\"DECIMAL\").sql(), \"DECIMAL\")\n        self.assertEqual(exp.DataType.build(\"BOOLEAN\").sql(), \"BOOLEAN\")\n        self.assertEqual(exp.DataType.build(\"JSON\").sql(), \"JSON\")\n        self.assertEqual(exp.DataType.build(\"JSONB\", dialect=\"postgres\").sql(), \"JSONB\")\n        self.assertEqual(exp.DataType.build(\"INTERVAL\").sql(), \"INTERVAL\")\n        self.assertEqual(exp.DataType.build(\"TIME\").sql(), \"TIME\")\n        self.assertEqual(exp.DataType.build(\"TIMESTAMP\").sql(), \"TIMESTAMP\")\n        self.assertEqual(exp.DataType.build(\"TIMESTAMPTZ\").sql(), \"TIMESTAMPTZ\")\n        self.assertEqual(exp.DataType.build(\"TIMESTAMPLTZ\").sql(), \"TIMESTAMPLTZ\")\n        self.assertEqual(exp.DataType.build(\"DATE\").sql(), \"DATE\")\n        self.assertEqual(exp.DataType.build(\"DATETIME\").sql(), \"DATETIME\")\n        self.assertEqual(exp.DataType.build(\"ARRAY\").sql(), \"ARRAY\")\n        self.assertEqual(exp.DataType.build(\"MAP\").sql(), \"MAP\")\n        self.assertEqual(exp.DataType.build(\"UUID\").sql(), \"UUID\")\n        self.assertEqual(exp.DataType.build(\"GEOGRAPHY\").sql(), \"GEOGRAPHY\")\n        self.assertEqual(exp.DataType.build(\"GEOMETRY\").sql(), \"GEOMETRY\")\n        self.assertEqual(exp.DataType.build(\"STRUCT\").sql(), \"STRUCT\")\n        self.assertEqual(exp.DataType.build(\"HLLSKETCH\", dialect=\"redshift\").sql(), \"HLLSKETCH\")\n        self.assertEqual(exp.DataType.build(\"HSTORE\", dialect=\"postgres\").sql(), \"HSTORE\")\n        self.assertEqual(exp.DataType.build(\"NULL\").sql(), \"NULL\")\n        self.assertEqual(exp.DataType.build(\"NULL\", dialect=\"bigquery\").sql(), \"NULL\")\n        self.assertEqual(exp.DataType.build(\"UNKNOWN\").sql(), \"UNKNOWN\")\n        self.assertEqual(exp.DataType.build(\"UNKNOWN\", dialect=\"bigquery\").sql(), \"UNKNOWN\")\n        self.assertEqual(exp.DataType.build(\"UNKNOWN\", dialect=\"snowflake\").sql(), \"UNKNOWN\")\n        self.assertEqual(exp.DataType.build(\"TIMESTAMP\", dialect=\"bigquery\").sql(), \"TIMESTAMPTZ\")\n        self.assertEqual(exp.DataType.build(\"USER-DEFINED\").sql(), \"USER-DEFINED\")\n        self.assertEqual(exp.DataType.build(\"ARRAY<UNKNOWN>\").sql(), \"ARRAY<UNKNOWN>\")\n        self.assertEqual(exp.DataType.build(\"ARRAY<NULL>\").sql(), \"ARRAY<NULL>\")\n        self.assertEqual(exp.DataType.build(\"varchar(100) collate 'en-ci'\").sql(), \"VARCHAR(100)\")\n        self.assertEqual(exp.DataType.build(\"int[3]\").sql(dialect=\"duckdb\"), \"INT[3]\")\n        self.assertEqual(exp.DataType.build(\"int[3][3]\").sql(dialect=\"duckdb\"), \"INT[3][3]\")\n        self.assertEqual(exp.DataType.build(\"time_ns\", \"duckdb\").sql(), \"TIME_NS\")\n        self.assertEqual(exp.DataType.build(\"bignum\", \"duckdb\").sql(), \"BIGNUM\")\n        self.assertEqual(\n            exp.DataType.build(\"struct<x int>\", dialect=\"spark\").sql(), \"STRUCT<x INT>\"\n        )\n\n        with self.assertRaises(ParseError):\n            exp.DataType.build(\"varchar(\")\n\n    def test_rename_table(self):\n        self.assertEqual(\n            exp.rename_table(\"t1\", \"t2\").sql(),\n            \"ALTER TABLE t1 RENAME TO t2\",\n        )\n\n    def test_to_py(self):\n        self.assertEqual(parse_one(\"- -1\").to_py(), 1)\n        self.assertIs(parse_one(\"TRUE\").to_py(), True)\n        self.assertIs(parse_one(\"1\").to_py(), 1)\n        self.assertIs(parse_one(\"'1'\").to_py(), \"1\")\n        self.assertIs(parse_one(\"null\").to_py(), None)\n\n        with self.assertRaises(ValueError):\n            parse_one(\"x\").to_py()\n\n    def test_is_int(self):\n        self.assertTrue(parse_one(\"- -1\").is_int)\n\n    def test_is_star(self):\n        assert parse_one(\"*\").is_star\n        assert parse_one(\"foo.*\").is_star\n        assert parse_one(\"SELECT * FROM foo\").is_star\n        assert parse_one(\"(SELECT * FROM foo)\").is_star\n        assert parse_one(\"SELECT *, 1 FROM foo\").is_star\n        assert parse_one(\"SELECT foo.* FROM foo\").is_star\n        assert parse_one(\"SELECT * EXCEPT (a, b) FROM foo\").is_star\n        assert parse_one(\"SELECT foo.* EXCEPT (foo.a, foo.b) FROM foo\").is_star\n        assert parse_one(\"SELECT * REPLACE (a AS b, b AS C)\").is_star\n        assert parse_one(\"SELECT * EXCEPT (a, b) REPLACE (a AS b, b AS C)\").is_star\n        assert parse_one(\"SELECT * INTO newevent FROM event\").is_star\n        assert parse_one(\"SELECT * FROM foo UNION SELECT * FROM bar\").is_star\n        assert parse_one(\"SELECT * FROM bla UNION SELECT 1 AS x\").is_star\n        assert parse_one(\"SELECT 1 AS x UNION SELECT * FROM bla\").is_star\n        assert parse_one(\"SELECT 1 AS x UNION SELECT 1 AS x UNION SELECT * FROM foo\").is_star\n\n    def test_set_metadata(self):\n        ast = parse_one(\"SELECT foo.col FROM foo\")\n\n        self.assertIsNone(ast._meta)\n\n        # calling ast.meta would lazily instantiate self._meta\n        self.assertEqual(ast.meta, {})\n        self.assertEqual(ast._meta, {})\n\n        ast.meta[\"some_meta_key\"] = \"some_meta_value\"\n        self.assertEqual(ast.meta.get(\"some_meta_key\"), \"some_meta_value\")\n        self.assertEqual(ast.meta.get(\"some_other_meta_key\"), None)\n\n        ast.meta[\"some_other_meta_key\"] = \"some_other_meta_value\"\n        self.assertEqual(ast.meta.get(\"some_other_meta_key\"), \"some_other_meta_value\")\n\n    def test_unnest(self):\n        ast = parse_one(\"SELECT (((1)))\")\n        self.assertIs(ast.selects[0].unnest(), ast.find(exp.Literal))\n\n        ast = parse_one(\"SELECT * FROM (((SELECT * FROM t)))\")\n        self.assertIs(ast.args[\"from_\"].this.unnest(), list(ast.find_all(exp.Select))[1])\n\n        ast = parse_one(\"SELECT * FROM ((((SELECT * FROM t))) AS foo)\")\n        second_subquery = ast.args[\"from_\"].this.this\n        innermost_subquery = list(ast.find_all(exp.Select))[1].parent\n        self.assertIs(second_subquery, innermost_subquery.unwrap())\n\n    def test_is_type(self):\n        ast = parse_one(\"CAST(x AS VARCHAR)\")\n        assert ast.is_type(\"VARCHAR\")\n        assert not ast.is_type(\"VARCHAR(5)\")\n        assert not ast.is_type(\"FLOAT\")\n\n        ast = parse_one(\"CAST(x AS VARCHAR(5))\")\n        assert ast.is_type(\"VARCHAR\")\n        assert ast.is_type(\"VARCHAR(5)\")\n        assert not ast.is_type(\"VARCHAR(4)\")\n        assert not ast.is_type(\"FLOAT\")\n\n        ast = parse_one(\"CAST(x AS ARRAY<INT>)\")\n        assert ast.is_type(\"ARRAY\")\n        assert ast.is_type(\"ARRAY<INT>\")\n        assert not ast.is_type(\"ARRAY<FLOAT>\")\n        assert not ast.is_type(\"INT\")\n\n        ast = parse_one(\"CAST(x AS ARRAY)\")\n        assert ast.is_type(\"ARRAY\")\n        assert not ast.is_type(\"ARRAY<INT>\")\n        assert not ast.is_type(\"ARRAY<FLOAT>\")\n        assert not ast.is_type(\"INT\")\n\n        ast = parse_one(\"CAST(x AS STRUCT<a INT, b FLOAT>)\")\n        assert ast.is_type(\"STRUCT\")\n        assert ast.is_type(\"STRUCT<a INT, b FLOAT>\")\n        assert not ast.is_type(\"STRUCT<a VARCHAR, b INT>\")\n\n        dtype = exp.DataType.build(\"foo\", udt=True)\n        assert dtype.is_type(\"foo\")\n        assert not dtype.is_type(\"bar\")\n\n        dtype = exp.DataType.build(\"a.b.c\", udt=True)\n        assert dtype.is_type(\"a.b.c\")\n\n        dtype = exp.DataType.build(\"Nullable(Int32)\", dialect=\"clickhouse\")\n        assert dtype.is_type(\"int\")\n        assert not dtype.is_type(\"int\", check_nullable=True)\n\n        with self.assertRaises(ParseError):\n            exp.DataType.build(\"foo\")\n\n    def test_set_meta(self):\n        query = parse_one(\"SELECT * FROM foo /* sqlglot.meta x = 1, y = a, z */\")\n        self.assertEqual(query.find(exp.Table).meta, {\"x\": True, \"y\": \"a\", \"z\": True})\n        self.assertEqual(query.sql(), \"SELECT * FROM foo /* sqlglot.meta x = 1, y = a, z */\")\n\n    def test_assert_is(self):\n        parse_one(\"x\").assert_is(exp.Column)\n\n        with self.assertRaisesRegex(\n            AssertionError, \"x is not <class 'sqlglot.expressions.core.Identifier'>\\\\.\"\n        ):\n            parse_one(\"x\").assert_is(exp.Identifier)\n\n    def test_parse_identifier(self):\n        self.assertEqual(exp.parse_identifier(\"a ' b\"), exp.to_identifier(\"a ' b\"))\n\n    def test_convert_datetime_time(self):\n        # Test converting datetime.time objects to TsOrDsToTime expressions\n        time_obj = datetime.time(14, 30, 45)\n        result = exp.convert(time_obj)\n\n        self.assertIsInstance(result, exp.TsOrDsToTime)\n        self.assertIsInstance(result.this, exp.Literal)\n        self.assertEqual(result.sql(), \"CAST('14:30:45' AS TIME)\")\n        self.assertTrue(result.this.is_string)\n\n        # Test with microseconds\n        time_with_microseconds = datetime.time(9, 15, 30, 123456)\n        result = exp.convert(time_with_microseconds)\n\n        self.assertIsInstance(result, exp.TsOrDsToTime)\n        self.assertEqual(result.sql(), \"CAST('09:15:30.123456' AS TIME)\")\n\n        # Test midnight\n        midnight = datetime.time(0, 0, 0)\n        result = exp.convert(midnight)\n\n        self.assertIsInstance(result, exp.TsOrDsToTime)\n        self.assertEqual(result.sql(), \"CAST('00:00:00' AS TIME)\")\n\n        # Test noon\n        noon = datetime.time(12, 0, 0)\n        result = exp.convert(noon)\n\n        self.assertIsInstance(result, exp.TsOrDsToTime)\n        self.assertEqual(result.sql(), \"CAST('12:00:00' AS TIME)\")\n\n    def test_hash_large_ast(self):\n        expr = parse_one(\"SELECT 1 UNION ALL \" * 3000 + \"SELECT 1\")\n        assert expr == expr\n\n    def test_literal_number(self):\n        for number in (\n            1,\n            -1.1,\n            1.1,\n            0,\n            \"-1\",\n            \"1\",\n            \"1.1\",\n            \"-1.1\",\n            \"1e6\",\n            \"inf\",\n            \"binary_double_nan\",\n        ):\n            with self.subTest(f\"Test Literal number method for: {repr(number)}\"):\n                literal = exp.Literal.number(number)\n\n                self.assertTrue(literal.is_number)\n\n                if isinstance(number, str):\n                    is_negative = number.startswith(\"-\")\n                    expected_this = number.lstrip(\"-\")\n                else:\n                    is_negative = number < 0\n                    expected_this = str(abs(number))\n\n                if is_negative:\n                    self.assertIsInstance(literal, exp.Neg)\n                    self.assertIsInstance(literal.this, exp.Literal)\n                    this = literal.this.this\n                else:\n                    self.assertIsInstance(literal, exp.Literal)\n                    this = literal.this\n\n                self.assertEqual(this, expected_this)\n\n    def test_update_positions_empty_meta(self):\n        expr1 = exp.Column(this=\"a\")\n        expr2 = exp.Column(this=\"b\")\n        expr2.meta.clear()\n\n        expr1.update_positions(expr2)\n        assert expr1.meta == {}\n"
  },
  {
    "path": "tests/test_generator.py",
    "content": "import unittest\n\nfrom sqlglot import exp, parse_one\nfrom sqlglot.expressions import Expression, Func\nfrom sqlglot.parsers.snowflake import SnowflakeParser\n\nimport sqlglot.expressions.core as _core_module\n\n_EXPRESSION_IS_COMPILED = getattr(_core_module, \"__file__\", \"\").endswith(\".so\")\n\n\nclass TestGenerator(unittest.TestCase):\n    @unittest.skipIf(_EXPRESSION_IS_COMPILED, \"mypyc compiled expressions cannot be subclassed\")\n    def test_fallback_function_sql(self):\n        class SpecialUdf(Expression, Func):\n            arg_types = {\"a\": True, \"b\": False}\n\n        SnowflakeParser.FUNCTIONS[\"SPECIAL_UDF\"] = SpecialUdf.from_arg_list\n        try:\n            sql = \"SELECT SPECIAL_UDF(a) FROM x\"\n            expression = parse_one(sql, dialect=\"snowflake\")\n            self.assertEqual(expression.sql(), \"SELECT SPECIAL_UDF(a) FROM x\")\n        finally:\n            del SnowflakeParser.FUNCTIONS[\"SPECIAL_UDF\"]\n\n    @unittest.skipIf(_EXPRESSION_IS_COMPILED, \"mypyc compiled expressions cannot be subclassed\")\n    def test_fallback_function_var_args_sql(self):\n        class SpecialUdf(Expression, Func):\n            arg_types = {\"a\": True, \"expressions\": False}\n            is_var_len_args = True\n\n        SnowflakeParser.FUNCTIONS[\"SPECIAL_UDF\"] = SpecialUdf.from_arg_list\n        try:\n            sql = \"SELECT SPECIAL_UDF(a, b, c, d + 1) FROM x\"\n            expression = parse_one(sql, dialect=\"snowflake\")\n            self.assertEqual(expression.sql(), sql)\n        finally:\n            del SnowflakeParser.FUNCTIONS[\"SPECIAL_UDF\"]\n\n        self.assertEqual(\n            exp.DateTrunc(this=exp.to_column(\"event_date\"), unit=exp.var(\"MONTH\")).sql(),\n            \"DATE_TRUNC('MONTH', event_date)\",\n        )\n\n    def test_identify(self):\n        self.assertEqual(parse_one(\"x\").sql(identify=True), '\"x\"')\n        self.assertEqual(parse_one(\"x\").sql(identify=False), \"x\")\n        self.assertEqual(parse_one(\"X\").sql(identify=True), '\"X\"')\n        self.assertEqual(parse_one('\"x\"').sql(identify=False), '\"x\"')\n        self.assertEqual(parse_one(\"x\").sql(identify=\"safe\"), '\"x\"')\n        self.assertEqual(parse_one(\"X\").sql(identify=\"safe\"), \"X\")\n        self.assertEqual(parse_one(\"x as 1\").sql(identify=\"safe\"), '\"x\" AS \"1\"')\n        self.assertEqual(parse_one(\"X as 1\").sql(identify=\"safe\"), 'X AS \"1\"')\n\n    def test_generate_nested_binary(self):\n        sql = \"SELECT 'foo'\" + (\" || 'foo'\" * 1000)\n        self.assertEqual(parse_one(sql).sql(copy=False), sql)\n\n    def test_overlap_operator(self):\n        for op in (\"&<\", \"&>\"):\n            with self.subTest(op=op):\n                input_sql = f\"SELECT '[1,10]'::int4range {op} '[5,15]'::int4range\"\n                expected_sql = (\n                    f\"SELECT CAST('[1,10]' AS INT4RANGE) {op} CAST('[5,15]' AS INT4RANGE)\"\n                )\n                ast = parse_one(input_sql, read=\"postgres\")\n                self.assertEqual(ast.sql(), expected_sql)\n                self.assertEqual(ast.sql(\"postgres\"), expected_sql)\n\n    def test_pretty_nested_types(self):\n        def assert_pretty_nested(\n            datatype: exp.DataType,\n            single_line: str,\n            pretty: str,\n            max_text_width: int = 10,\n            **kwargs,\n        ) -> None:\n            self.assertEqual(datatype.sql(), single_line)\n            self.assertEqual(\n                datatype.sql(pretty=True, max_text_width=max_text_width, **kwargs), pretty\n            )\n\n        # STRUCT\n        type_str = \"STRUCT<a INT, b TEXT>\"\n        assert_pretty_nested(\n            exp.DataType.build(type_str),\n            type_str,\n            \"STRUCT<\\n  a INT,\\n  b TEXT\\n>\",\n        )\n\n        # STRUCT - type def shorter than max text width so stays one line\n        assert_pretty_nested(\n            exp.DataType.build(type_str),\n            type_str,\n            \"STRUCT<a INT, b TEXT>\",\n            max_text_width=50,\n        )\n\n        # STRUCT, leading_comma = True\n        assert_pretty_nested(\n            exp.DataType.build(type_str),\n            type_str,\n            \"STRUCT<\\n  a INT\\n  , b TEXT\\n>\",\n            leading_comma=True,\n        )\n\n        # ARRAY\n        type_str = \"ARRAY<DECIMAL(38, 9)>\"\n        assert_pretty_nested(\n            exp.DataType.build(type_str),\n            type_str,\n            \"ARRAY<\\n  DECIMAL(38, 9)\\n>\",\n        )\n\n        # ARRAY nested STRUCT\n        type_str = \"ARRAY<STRUCT<a INT, b TEXT>>\"\n        assert_pretty_nested(\n            exp.DataType.build(type_str),\n            type_str,\n            \"ARRAY<\\n  STRUCT<\\n    a INT,\\n    b TEXT\\n  >\\n>\",\n        )\n\n        # RANGE\n        type_str = \"RANGE<DECIMAL(38, 9)>\"\n        assert_pretty_nested(\n            exp.DataType.build(type_str),\n            type_str,\n            \"RANGE<\\n  DECIMAL(38, 9)\\n>\",\n        )\n\n        # LIST\n        type_str = \"LIST<INT, INT, TEXT>\"\n        assert_pretty_nested(\n            exp.DataType.build(type_str),\n            type_str,\n            \"LIST<\\n  INT,\\n  INT,\\n  TEXT\\n>\",\n        )\n\n        # MAP\n        type_str = \"MAP<INT, DECIMAL(38, 9)>\"\n        assert_pretty_nested(\n            exp.DataType.build(type_str),\n            type_str,\n            \"MAP<\\n  INT,\\n  DECIMAL(38, 9)\\n>\",\n        )\n"
  },
  {
    "path": "tests/test_helper.py",
    "content": "import unittest\n\nfrom sqlglot.helper import merge_ranges, name_sequence, tsort\n\n\nclass TestHelper(unittest.TestCase):\n    def test_tsort(self):\n        self.assertEqual(tsort({\"a\": set()}), [\"a\"])\n        self.assertEqual(tsort({\"a\": {\"b\"}}), [\"b\", \"a\"])\n        self.assertEqual(tsort({\"a\": {\"c\"}, \"b\": set(), \"c\": set()}), [\"b\", \"c\", \"a\"])\n        self.assertEqual(\n            tsort(\n                {\n                    \"a\": {\"b\", \"c\"},\n                    \"b\": {\"c\"},\n                    \"c\": set(),\n                    \"d\": {\"a\"},\n                }\n            ),\n            [\"c\", \"b\", \"a\", \"d\"],\n        )\n\n        with self.assertRaises(ValueError):\n            tsort(\n                {\n                    \"a\": {\"b\", \"c\"},\n                    \"b\": {\"a\"},\n                    \"c\": set(),\n                }\n            )\n\n    def test_name_sequence(self):\n        s1 = name_sequence(\"a\")\n        s2 = name_sequence(\"b\")\n\n        self.assertEqual(s1(), \"a0\")\n        self.assertEqual(s1(), \"a1\")\n        self.assertEqual(s2(), \"b0\")\n        self.assertEqual(s1(), \"a2\")\n        self.assertEqual(s2(), \"b1\")\n        self.assertEqual(s2(), \"b2\")\n\n    def test_merge_ranges(self):\n        self.assertEqual([], merge_ranges([]))\n        self.assertEqual([(0, 1)], merge_ranges([(0, 1)]))\n        self.assertEqual([(0, 1), (2, 3)], merge_ranges([(0, 1), (2, 3)]))\n        self.assertEqual([(0, 3)], merge_ranges([(0, 1), (1, 3)]))\n        self.assertEqual([(0, 1), (2, 4)], merge_ranges([(2, 3), (0, 1), (3, 4)]))\n"
  },
  {
    "path": "tests/test_integration_loader.py",
    "content": "import os\n\nfrom tests.helpers import SKIP_INTEGRATION\n\nINTEGRATION_TEST_DIR = os.path.join(\n    os.path.dirname(__file__),\n    \"..\",\n    \"sqlglot-integration-tests\",\n    \"tests\",\n    \"sqlglot\",\n)\n\n\ndef load_tests(loader, suite, pattern):\n    if not SKIP_INTEGRATION and os.path.isdir(INTEGRATION_TEST_DIR):\n        suite.addTests(loader.discover(INTEGRATION_TEST_DIR, pattern=\"test*.py\"))\n    return suite\n"
  },
  {
    "path": "tests/test_jsonpath.py",
    "content": "import json\nimport os\nimport unittest\n\nfrom sqlglot import exp\nfrom sqlglot.errors import ParseError, TokenError\nfrom sqlglot.jsonpath import parse\nfrom tests.helpers import FIXTURES_DIR\n\n\nclass TestJsonpath(unittest.TestCase):\n    maxDiff = None\n\n    def test_jsonpath(self):\n        expected_expressions = [\n            exp.JSONPathRoot(),\n            exp.JSONPathKey(this=exp.JSONPathWildcard()),\n            exp.JSONPathKey(this=\"a\"),\n            exp.JSONPathSubscript(this=0),\n            exp.JSONPathKey(this=\"x\"),\n            exp.JSONPathUnion(expressions=[exp.JSONPathWildcard(), \"y\", 1]),\n            exp.JSONPathKey(this=\"z\"),\n            exp.JSONPathSelector(this=exp.JSONPathFilter(this=\"(@.a == 'b'), 1:\")),\n            exp.JSONPathSubscript(this=exp.JSONPathSlice(start=1, end=5, step=None)),\n            exp.JSONPathUnion(expressions=[1, exp.JSONPathFilter(this=\"@.a\")]),\n            exp.JSONPathSelector(this=exp.JSONPathScript(this=\"@.x)\")),\n        ]\n        self.assertEqual(\n            parse(\"$.*.a[0]['x'][*, 'y', 1].z[?(@.a == 'b'), 1:][1:5][1,?@.a][(@.x)]\"),\n            exp.JSONPath(expressions=expected_expressions),\n        )\n\n    def test_identity(self):\n        for selector, expected in (\n            (\"$.select\", \"$.select\"),\n            (\"$[(@.length-1)]\", \"$[(@.length-1)]\"),\n            (\"$[((@.length-1))]\", \"$[((@.length-1))]\"),\n        ):\n            with self.subTest(f\"{selector} -> {expected}\"):\n                self.assertEqual(parse(selector).sql(), f\"'{expected}'\")\n\n    def test_cts_file(self):\n        with open(os.path.join(FIXTURES_DIR, \"jsonpath\", \"cts.json\"), encoding=\"utf-8\") as file:\n            tests = json.load(file)[\"tests\"]\n\n        # sqlglot json path generator rewrites to a normal form\n        overrides = {\n            \"$.☺\": '$[\"☺\"]',\n            \"\"\"$['a',1]\"\"\": \"\"\"$[\"a\",1]\"\"\",\n            \"\"\"$[*,'a']\"\"\": \"\"\"$[*,\"a\"]\"\"\",\n            \"\"\"$..['a','d']\"\"\": \"\"\"$..[\"a\",\"d\"]\"\"\",\n            \"\"\"$[1, ?@.a=='b', 1:]\"\"\": \"\"\"$[1,?@.a=='b', 1:]\"\"\",\n            \"\"\"$[\"a\"]\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$[\"c\"]\"\"\": \"\"\"$.c\"\"\",\n            \"\"\"$['a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$['c']\"\"\": \"\"\"$.c\"\"\",\n            \"\"\"$[' ']\"\"\": \"\"\"$[\" \"]\"\"\",\n            \"\"\"$['\\\\'']\"\"\": \"\"\"$[\"\\'\"]\"\"\",\n            \"\"\"$['\\\\\\\\']\"\"\": \"\"\"$[\"\\\\\\\\\"]\"\"\",\n            \"\"\"$['\\\\/']\"\"\": \"\"\"$[\"\\\\/\"]\"\"\",\n            \"\"\"$['\\\\b']\"\"\": \"\"\"$[\"\\\\b\"]\"\"\",\n            \"\"\"$['\\\\f']\"\"\": \"\"\"$[\"\\\\f\"]\"\"\",\n            \"\"\"$['\\\\n']\"\"\": \"\"\"$[\"\\\\n\"]\"\"\",\n            \"\"\"$['\\\\r']\"\"\": \"\"\"$[\"\\\\r\"]\"\"\",\n            \"\"\"$['\\\\t']\"\"\": \"\"\"$[\"\\\\t\"]\"\"\",\n            \"\"\"$['\\\\u263A']\"\"\": \"\"\"$[\"\\\\u263A\"]\"\"\",\n            \"\"\"$['\\\\u263a']\"\"\": \"\"\"$[\"\\\\u263a\"]\"\"\",\n            \"\"\"$['\\\\uD834\\\\uDD1E']\"\"\": \"\"\"$[\"\\\\uD834\\\\uDD1E\"]\"\"\",\n            \"\"\"$['\\\\uD83D\\\\uDE00']\"\"\": \"\"\"$[\"\\\\uD83D\\\\uDE00\"]\"\"\",\n            \"\"\"$['']\"\"\": \"\"\"$[\"\"]\"\"\",\n            \"\"\"$[? @.a]\"\"\": \"\"\"$[?@.a]\"\"\",\n            \"\"\"$[?\\n@.a]\"\"\": \"\"\"$[?@.a]\"\"\",\n            \"\"\"$[?\\t@.a]\"\"\": \"\"\"$[?@.a]\"\"\",\n            \"\"\"$[?\\r@.a]\"\"\": \"\"\"$[?@.a]\"\"\",\n            \"\"\"$[? (@.a)]\"\"\": \"\"\"$[?(@.a)]\"\"\",\n            \"\"\"$[?\\n(@.a)]\"\"\": \"\"\"$[?(@.a)]\"\"\",\n            \"\"\"$[?\\t(@.a)]\"\"\": \"\"\"$[?(@.a)]\"\"\",\n            \"\"\"$[?\\r(@.a)]\"\"\": \"\"\"$[?(@.a)]\"\"\",\n            \"\"\"$[ ?@.a]\"\"\": \"\"\"$[?@.a]\"\"\",\n            \"\"\"$[\\n?@.a]\"\"\": \"\"\"$[?@.a]\"\"\",\n            \"\"\"$[\\t?@.a]\"\"\": \"\"\"$[?@.a]\"\"\",\n            \"\"\"$[\\r?@.a]\"\"\": \"\"\"$[?@.a]\"\"\",\n            \"\"\"$ ['a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$\\n['a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$\\t['a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$\\r['a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$['a'] ['b']\"\"\": \"\"\"$.a.b\"\"\",\n            \"\"\"$['a'] \\n['b']\"\"\": \"\"\"$.a.b\"\"\",\n            \"\"\"$['a'] \\t['b']\"\"\": \"\"\"$.a.b\"\"\",\n            \"\"\"$['a'] \\r['b']\"\"\": \"\"\"$.a.b\"\"\",\n            \"\"\"$ .a\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$\\n.a\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$\\t.a\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$\\r.a\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$[ 'a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$[\\n'a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$[\\t'a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$[\\r'a']\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$['a' ]\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$['a'\\n]\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$['a'\\t]\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$['a'\\r]\"\"\": \"\"\"$.a\"\"\",\n            \"\"\"$['a' ,'b']\"\"\": \"\"\"$[\"a\",\"b\"]\"\"\",\n            \"\"\"$['a'\\n,'b']\"\"\": \"\"\"$[\"a\",\"b\"]\"\"\",\n            \"\"\"$['a'\\t,'b']\"\"\": \"\"\"$[\"a\",\"b\"]\"\"\",\n            \"\"\"$['a'\\r,'b']\"\"\": \"\"\"$[\"a\",\"b\"]\"\"\",\n            \"\"\"$['a', 'b']\"\"\": \"\"\"$[\"a\",\"b\"]\"\"\",\n            \"\"\"$['a',\\n'b']\"\"\": \"\"\"$[\"a\",\"b\"]\"\"\",\n            \"\"\"$['a',\\t'b']\"\"\": \"\"\"$[\"a\",\"b\"]\"\"\",\n            \"\"\"$['a',\\r'b']\"\"\": \"\"\"$[\"a\",\"b\"]\"\"\",\n            \"\"\"$[1 :5:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1\\n:5:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1\\t:5:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1\\r:5:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1: 5:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:\\n5:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:\\t5:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:\\r5:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:5 :2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:5\\n:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:5\\t:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:5\\r:2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:5: 2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:5:\\n2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:5:\\t2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n            \"\"\"$[1:5:\\r2]\"\"\": \"\"\"$[1:5:2]\"\"\",\n        }\n\n        for test in tests:\n            selector = test[\"selector\"]\n\n            with self.subTest(f\"{selector.strip()} /* {test['name']} */\"):\n                if test.get(\"invalid_selector\"):\n                    try:\n                        parse(selector)\n                    except (ParseError, TokenError):\n                        pass\n                else:\n                    path = parse(selector)\n                    self.assertEqual(path.sql(), f\"'{overrides.get(selector, selector)}'\")\n"
  },
  {
    "path": "tests/test_lineage.py",
    "content": "from __future__ import annotations\n\nimport unittest\n\nimport sqlglot\nfrom sqlglot.lineage import lineage\nfrom sqlglot.schema import MappingSchema\n\n\nclass TestLineage(unittest.TestCase):\n    maxDiff = None\n\n    @classmethod\n    def setUpClass(cls):\n        sqlglot.schema = MappingSchema()\n\n    def test_lineage(self) -> None:\n        node = lineage(\n            \"a\",\n            \"SELECT a FROM z\",\n            schema={\"x\": {\"a\": \"int\"}},\n            sources={\"y\": \"SELECT * FROM x\", \"z\": \"SELECT a FROM y\"},\n        )\n        self.assertEqual(\n            node.source.sql(),\n            \"SELECT z.a AS a FROM (SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y /* source: y */) AS z /* source: z */\",\n        )\n        self.assertEqual(node.source_name, \"\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(\n            downstream.source.sql(),\n            \"SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y /* source: y */\",\n        )\n        self.assertEqual(downstream.source_name, \"z\")\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(\n            downstream.source.sql(),\n            \"SELECT x.a AS a FROM x AS x\",\n        )\n        self.assertEqual(downstream.source_name, \"y\")\n\n        graph_html = node.to_html()\n        self.assertGreater(len(graph_html._repr_html_()), 1000)\n\n        for edge in graph_html.edges:\n            self.assertIn(\"from\", edge)\n            self.assertIn(\"to\", edge)\n\n        # test that sql is not modified\n        sql = \"SELECT a FROM x\"\n        ast = sqlglot.parse_one(sql)\n        node = lineage(\"a\", ast)\n        self.assertEqual(ast.sql(), sql)\n\n        # test that sources are not modified\n        ast = sqlglot.parse_one(sql)\n\n        source_sql = \"SELECT a FROM y\"\n        source = sqlglot.parse_one(source_sql)\n\n        node = lineage(\"a\", ast, sources={\"x\": source})\n\n        self.assertEqual(source.sql(), source_sql)\n\n    def test_lineage_sql_with_cte(self) -> None:\n        node = lineage(\n            \"a\",\n            \"WITH z AS (SELECT a FROM y) SELECT a FROM z\",\n            schema={\"x\": {\"a\": \"int\"}},\n            sources={\"y\": \"SELECT * FROM x\"},\n        )\n        self.assertEqual(\n            node.source.sql(),\n            \"WITH z AS (SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y /* source: y */) SELECT z.a AS a FROM z AS z\",\n        )\n        self.assertEqual(node.source_name, \"\")\n        self.assertEqual(node.reference_node_name, \"\")\n\n        # Node containing expanded CTE expression\n        downstream = node.downstream[0]\n        self.assertEqual(\n            downstream.source.sql(),\n            \"SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y /* source: y */\",\n        )\n        self.assertEqual(downstream.source_name, \"\")\n        self.assertEqual(downstream.reference_node_name, \"z\")\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(\n            downstream.source.sql(),\n            \"SELECT x.a AS a FROM x AS x\",\n        )\n        self.assertEqual(downstream.source_name, \"y\")\n        self.assertEqual(downstream.reference_node_name, \"\")\n\n    def test_lineage_source_with_cte(self) -> None:\n        node = lineage(\n            \"a\",\n            \"SELECT a FROM z\",\n            schema={\"x\": {\"a\": \"int\"}},\n            sources={\"z\": \"WITH y AS (SELECT * FROM x) SELECT a FROM y\"},\n        )\n        self.assertEqual(\n            node.source.sql(),\n            \"SELECT z.a AS a FROM (WITH y AS (SELECT x.a AS a FROM x AS x) SELECT y.a AS a FROM y AS y) AS z /* source: z */\",\n        )\n        self.assertEqual(node.source_name, \"\")\n        self.assertEqual(node.reference_node_name, \"\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(\n            downstream.source.sql(),\n            \"WITH y AS (SELECT x.a AS a FROM x AS x) SELECT y.a AS a FROM y AS y\",\n        )\n        self.assertEqual(downstream.source_name, \"z\")\n        self.assertEqual(downstream.reference_node_name, \"\")\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(\n            downstream.source.sql(),\n            \"SELECT x.a AS a FROM x AS x\",\n        )\n        self.assertEqual(downstream.source_name, \"z\")\n        self.assertEqual(downstream.reference_node_name, \"y\")\n\n    def test_lineage_source_with_star(self) -> None:\n        node = lineage(\n            \"a\",\n            \"WITH y AS (SELECT * FROM x) SELECT a FROM y\",\n        )\n        self.assertEqual(\n            node.source.sql(),\n            \"WITH y AS (SELECT * FROM x AS x) SELECT y.a AS a FROM y AS y\",\n        )\n        self.assertEqual(node.source_name, \"\")\n        self.assertEqual(node.reference_node_name, \"\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(\n            downstream.source.sql(),\n            \"SELECT * FROM x AS x\",\n        )\n        self.assertEqual(downstream.source_name, \"\")\n        self.assertEqual(downstream.reference_node_name, \"y\")\n\n    def test_lineage_join_with_star(self) -> None:\n        node = lineage(\n            \"*\",\n            \"SELECT * from x JOIN y USING (uid)\",\n        )\n        self.assertEqual(\n            node.source.sql(),\n            \"SELECT * FROM x AS x JOIN y AS y ON x.uid = y.uid\",\n        )\n        self.assertEqual(node.source_name, \"\")\n        self.assertEqual(node.reference_node_name, \"\")\n        self.assertEqual(len(node.downstream), 2)\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.expression.sql(), \"x AS x\")\n        self.assertEqual(downstream.name, \"*\")\n\n        downstream = node.downstream[1]\n        self.assertEqual(downstream.expression.sql(), \"y AS y\")\n        self.assertEqual(downstream.name, \"*\")\n\n    def test_lineage_join_with_qualified_star(self) -> None:\n        node = lineage(\n            \"*\",\n            \"SELECT x.* from x JOIN y USING (uid)\",\n        )\n        self.assertEqual(\n            node.source.sql(),\n            \"SELECT x.* FROM x AS x JOIN y AS y ON x.uid = y.uid\",\n        )\n        self.assertEqual(node.source_name, \"\")\n        self.assertEqual(node.reference_node_name, \"\")\n        self.assertEqual(len(node.downstream), 1)\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.expression.sql(), \"x AS x\")\n        self.assertEqual(downstream.name, \"x.*\")\n\n    def test_lineage_external_col(self) -> None:\n        node = lineage(\n            \"a\",\n            \"WITH y AS (SELECT * FROM x) SELECT a FROM y JOIN z USING (uid)\",\n        )\n        self.assertEqual(\n            node.source.sql(),\n            \"WITH y AS (SELECT * FROM x AS x) SELECT a AS a FROM y AS y JOIN z AS z ON y.uid = z.uid\",\n        )\n        self.assertEqual(node.source_name, \"\")\n        self.assertEqual(node.reference_node_name, \"\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(\n            downstream.source.sql(),\n            \"?\",\n        )\n        self.assertEqual(downstream.source_name, \"\")\n        self.assertEqual(downstream.reference_node_name, \"\")\n\n    def test_lineage_values(self) -> None:\n        node = lineage(\n            \"a\",\n            \"SELECT a FROM y\",\n            sources={\"y\": \"SELECT a FROM (VALUES (1), (2)) AS t (a)\"},\n        )\n        self.assertEqual(\n            node.source.sql(),\n            \"SELECT y.a AS a FROM (SELECT t.a AS a FROM (VALUES (1), (2)) AS t(a)) AS y /* source: y */\",\n        )\n        self.assertEqual(node.source_name, \"\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.source.sql(), \"SELECT t.a AS a FROM (VALUES (1), (2)) AS t(a)\")\n        self.assertEqual(downstream.expression.sql(), \"t.a AS a\")\n        self.assertEqual(downstream.source_name, \"y\")\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(downstream.source.sql(), \"(VALUES (1), (2)) AS t(a)\")\n        self.assertEqual(downstream.expression.sql(), \"a\")\n        self.assertEqual(downstream.source_name, \"y\")\n\n    def test_lineage_cte_name_appears_in_schema(self) -> None:\n        schema = {\"a\": {\"b\": {\"t1\": {\"c1\": \"int\"}, \"t2\": {\"c2\": \"int\"}}}}\n\n        node = lineage(\n            \"c2\",\n            \"WITH t1 AS (SELECT * FROM a.b.t2), inter AS (SELECT * FROM t1) SELECT * FROM inter\",\n            schema=schema,\n        )\n\n        self.assertEqual(\n            node.source.sql(),\n            \"WITH t1 AS (SELECT t2.c2 AS c2 FROM a.b.t2 AS t2), inter AS (SELECT t1.c2 AS c2 FROM t1 AS t1) SELECT inter.c2 AS c2 FROM inter AS inter\",\n        )\n        self.assertEqual(node.source_name, \"\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.source.sql(), \"SELECT t1.c2 AS c2 FROM t1 AS t1\")\n        self.assertEqual(downstream.expression.sql(), \"t1.c2 AS c2\")\n        self.assertEqual(downstream.source_name, \"\")\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(downstream.source.sql(), \"SELECT t2.c2 AS c2 FROM a.b.t2 AS t2\")\n        self.assertEqual(downstream.expression.sql(), \"t2.c2 AS c2\")\n        self.assertEqual(downstream.source_name, \"\")\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(downstream.source.sql(), \"a.b.t2 AS t2\")\n        self.assertEqual(downstream.expression.sql(), \"a.b.t2 AS t2\")\n        self.assertEqual(downstream.source_name, \"\")\n\n        self.assertEqual(downstream.downstream, [])\n\n    def test_lineage_union(self) -> None:\n        node = lineage(\n            \"x\",\n            \"SELECT ax AS x FROM a UNION SELECT bx FROM b UNION SELECT cx FROM c\",\n        )\n        assert len(node.downstream) == 3\n\n        node = lineage(\n            \"x\",\n            \"SELECT x FROM (SELECT ax AS x FROM a UNION SELECT bx FROM b UNION SELECT cx FROM c)\",\n        )\n        assert len(node.downstream) == 3\n\n    def test_lineage_lateral_flatten(self) -> None:\n        node = lineage(\n            \"VALUE\",\n            \"SELECT FLATTENED.VALUE FROM TEST_TABLE, LATERAL FLATTEN(INPUT => RESULT, OUTER => TRUE) FLATTENED\",\n            dialect=\"snowflake\",\n        )\n        self.assertEqual(node.name, \"VALUE\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.name, \"FLATTENED.VALUE\")\n        self.assertEqual(\n            downstream.source.sql(dialect=\"snowflake\"),\n            \"LATERAL FLATTEN(INPUT => TEST_TABLE.RESULT, OUTER => TRUE) AS FLATTENED(SEQ, KEY, PATH, INDEX, VALUE, THIS)\",\n        )\n        self.assertEqual(downstream.expression.sql(dialect=\"snowflake\"), \"VALUE\")\n        self.assertEqual(len(downstream.downstream), 1)\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(downstream.name, \"TEST_TABLE.RESULT\")\n        self.assertEqual(downstream.source.sql(dialect=\"snowflake\"), \"TEST_TABLE AS TEST_TABLE\")\n\n        node = lineage(\n            \"FIELD\",\n            \"SELECT FLATTENED.VALUE:field::text AS FIELD FROM SNOWFLAKE.SCHEMA.MODEL AS MODEL_ALIAS, LATERAL FLATTEN(INPUT => MODEL_ALIAS.A) AS FLATTENED\",\n            schema={\"SNOWFLAKE\": {\"SCHEMA\": {\"TABLE\": {\"A\": \"integer\"}}}},\n            sources={\"SNOWFLAKE.SCHEMA.MODEL\": \"SELECT A FROM SNOWFLAKE.SCHEMA.TABLE\"},\n            dialect=\"snowflake\",\n        )\n        self.assertEqual(node.name, \"FIELD\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.name, \"FLATTENED.VALUE\")\n        self.assertEqual(\n            downstream.source.sql(dialect=\"snowflake\"),\n            \"LATERAL FLATTEN(INPUT => MODEL_ALIAS.A) AS FLATTENED(SEQ, KEY, PATH, INDEX, VALUE, THIS)\",\n        )\n        self.assertEqual(downstream.expression.sql(dialect=\"snowflake\"), \"VALUE\")\n        self.assertEqual(len(downstream.downstream), 1)\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(downstream.name, \"MODEL_ALIAS.A\")\n        self.assertEqual(downstream.source_name, \"SNOWFLAKE.SCHEMA.MODEL\")\n        self.assertEqual(\n            downstream.source.sql(dialect=\"snowflake\"),\n            \"SELECT TABLE.A AS A FROM SNOWFLAKE.SCHEMA.TABLE AS TABLE\",\n        )\n        self.assertEqual(downstream.expression.sql(dialect=\"snowflake\"), \"TABLE.A AS A\")\n        self.assertEqual(len(downstream.downstream), 1)\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(downstream.name, \"TABLE.A\")\n        self.assertEqual(\n            downstream.source.sql(dialect=\"snowflake\"),\n            \"SNOWFLAKE.SCHEMA.TABLE AS TABLE\",\n        )\n        self.assertEqual(\n            downstream.expression.sql(dialect=\"snowflake\"),\n            \"SNOWFLAKE.SCHEMA.TABLE AS TABLE\",\n        )\n\n    def test_subquery(self) -> None:\n        node = lineage(\n            \"output\",\n            \"SELECT (SELECT max(t3.my_column) my_column FROM foo t3) AS output FROM table3\",\n        )\n        self.assertEqual(node.name, \"output\")\n        node = node.downstream[0]\n        self.assertEqual(node.name, \"my_column\")\n        node = node.downstream[0]\n        self.assertEqual(node.name, \"t3.my_column\")\n        self.assertEqual(node.source.sql(), \"foo AS t3\")\n\n        node = lineage(\n            \"y\",\n            \"SELECT SUM((SELECT max(a) a from x) + (SELECT min(b) b from x) + c) AS y FROM x\",\n        )\n        self.assertEqual(node.name, \"y\")\n        self.assertEqual(len(node.downstream), 3)\n        self.assertEqual(node.downstream[0].name, \"a\")\n        self.assertEqual(node.downstream[1].name, \"b\")\n        self.assertEqual(node.downstream[2].name, \"x.c\")\n\n        node = lineage(\n            \"x\",\n            \"WITH cte AS (SELECT a, b FROM z) SELECT sum(SELECT a FROM cte) AS x, (SELECT b FROM cte) as y FROM cte\",\n        )\n        self.assertEqual(node.name, \"x\")\n        self.assertEqual(len(node.downstream), 1)\n        node = node.downstream[0]\n        self.assertEqual(node.name, \"a\")\n        node = node.downstream[0]\n        self.assertEqual(node.name, \"cte.a\")\n        self.assertEqual(node.reference_node_name, \"cte\")\n        node = node.downstream[0]\n        self.assertEqual(node.name, \"z.a\")\n\n        node = lineage(\n            \"a\",\n            \"\"\"\n            WITH foo AS (\n              SELECT\n                1 AS a\n            ), bar AS (\n              (\n                SELECT\n                  a + 1 AS a\n                FROM foo\n              )\n            )\n            (\n              SELECT\n                a + b AS a\n              FROM bar\n              CROSS JOIN (\n                SELECT\n                  2 AS b\n              ) AS baz\n            )\n            \"\"\",\n        )\n        self.assertEqual(node.name, \"a\")\n        self.assertEqual(len(node.downstream), 2)\n        a, b = sorted(node.downstream, key=lambda n: n.name)\n        self.assertEqual(a.name, \"bar.a\")\n        self.assertEqual(len(a.downstream), 1)\n        self.assertEqual(b.name, \"baz.b\")\n        self.assertEqual(b.downstream, [])\n\n        node = a.downstream[0]\n        self.assertEqual(node.name, \"foo.a\")\n\n        # Select from derived table\n        node = lineage(\n            \"a\",\n            \"SELECT a FROM (SELECT a FROM x) subquery\",\n        )\n        self.assertEqual(node.name, \"a\")\n        self.assertEqual(len(node.downstream), 1)\n        node = node.downstream[0]\n        self.assertEqual(node.name, \"subquery.a\")\n        self.assertEqual(node.reference_node_name, \"subquery\")\n\n        node = lineage(\n            \"a\",\n            \"SELECT a FROM (SELECT a FROM x)\",\n        )\n        self.assertEqual(node.name, \"a\")\n        self.assertEqual(len(node.downstream), 1)\n        node = node.downstream[0]\n        self.assertEqual(node.name, \"_0.a\")\n        self.assertEqual(node.reference_node_name, \"_0\")\n\n    def test_lineage_cte_union(self) -> None:\n        query = \"\"\"\n        WITH dataset AS (\n            SELECT *\n            FROM catalog.db.table_a\n\n            UNION\n\n            SELECT *\n            FROM catalog.db.table_b\n        )\n\n        SELECT x, created_at FROM dataset;\n        \"\"\"\n        node = lineage(\"x\", query)\n\n        self.assertEqual(node.name, \"x\")\n\n        downstream_a = node.downstream[0]\n        self.assertEqual(downstream_a.name, \"0\")\n        self.assertEqual(downstream_a.source.sql(), \"SELECT * FROM catalog.db.table_a AS table_a\")\n        self.assertEqual(downstream_a.reference_node_name, \"dataset\")\n        downstream_b = node.downstream[1]\n        self.assertEqual(downstream_b.name, \"0\")\n        self.assertEqual(downstream_b.source.sql(), \"SELECT * FROM catalog.db.table_b AS table_b\")\n        self.assertEqual(downstream_b.reference_node_name, \"dataset\")\n\n    def test_lineage_source_union(self) -> None:\n        query = \"SELECT x, created_at FROM dataset;\"\n        node = lineage(\n            \"x\",\n            query,\n            sources={\n                \"dataset\": \"\"\"\n                SELECT *\n                FROM catalog.db.table_a\n\n                UNION\n\n                SELECT *\n                FROM catalog.db.table_b\n                \"\"\"\n            },\n        )\n\n        self.assertEqual(node.name, \"x\")\n\n        downstream_a = node.downstream[0]\n        self.assertEqual(downstream_a.name, \"0\")\n        self.assertEqual(downstream_a.source_name, \"dataset\")\n        self.assertEqual(downstream_a.source.sql(), \"SELECT * FROM catalog.db.table_a AS table_a\")\n        self.assertEqual(downstream_a.reference_node_name, \"\")\n        downstream_b = node.downstream[1]\n        self.assertEqual(downstream_b.name, \"0\")\n        self.assertEqual(downstream_b.source_name, \"dataset\")\n        self.assertEqual(downstream_b.source.sql(), \"SELECT * FROM catalog.db.table_b AS table_b\")\n        self.assertEqual(downstream_b.reference_node_name, \"\")\n\n    def test_select_star(self) -> None:\n        node = lineage(\"x\", \"SELECT x from (SELECT * from table_a)\")\n\n        self.assertEqual(node.name, \"x\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.name, \"_0.x\")\n        self.assertEqual(downstream.source.sql(), \"SELECT * FROM table_a AS table_a\")\n\n        downstream = downstream.downstream[0]\n        self.assertEqual(downstream.name, \"*\")\n        self.assertEqual(downstream.source.sql(), \"table_a AS table_a\")\n\n    def test_unnest(self) -> None:\n        node = lineage(\n            \"b\",\n            \"with _data as (select [struct(1 as a, 2 as b)] as col) select b from _data cross join unnest(col)\",\n        )\n        self.assertEqual(node.name, \"b\")\n\n    def test_lineage_normalize(self) -> None:\n        node = lineage(\"a\", \"WITH x AS (SELECT 1 a) SELECT a FROM x\", dialect=\"snowflake\")\n        self.assertEqual(node.name, \"A\")\n\n        with self.assertRaises(sqlglot.errors.SqlglotError):\n            lineage('\"a\"', \"WITH x AS (SELECT 1 a) SELECT a FROM x\", dialect=\"snowflake\")\n\n        with self.assertRaises(sqlglot.errors.SqlglotError):\n            lineage(\n                \"b\",\n                \"SELECT a,b FROM table1 UNION ALL BY NAME SELECT a FROM table2\",\n                dialect=\"duckdb\",\n            )\n\n    def test_ddl_lineage(self) -> None:\n        sql = \"\"\"\n        INSERT /*+ HINT1 */\n        INTO target (x, y)\n        SELECT subq.x, subq.y\n        FROM (\n          SELECT /*+ HINT2 */\n            t.x AS x,\n            TO_DATE('2023-12-19', 'YYYY-MM-DD') AS y\n          FROM s.t t\n          WHERE 1 = 1 AND y = TO_DATE('2023-12-19', 'YYYY-MM-DD')\n        ) subq\n        \"\"\"\n\n        node = lineage(\"y\", sql, dialect=\"oracle\")\n\n        self.assertEqual(node.name, \"Y\")\n        self.assertEqual(node.expression.sql(dialect=\"oracle\"), \"SUBQ.Y AS Y\")\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.name, \"SUBQ.Y\")\n        self.assertEqual(\n            downstream.expression.sql(dialect=\"oracle\"),\n            \"TO_DATE('2023-12-19', 'YYYY-MM-DD') AS Y\",\n        )\n\n    def test_trim(self) -> None:\n        sql = \"\"\"\n            SELECT a, b, c\n            FROM (select a, b, c from y) z\n        \"\"\"\n\n        node = lineage(\"a\", sql, trim_selects=False)\n\n        self.assertEqual(node.name, \"a\")\n        self.assertEqual(\n            node.source.sql(),\n            \"SELECT z.a AS a, z.b AS b, z.c AS c FROM (SELECT y.a AS a, y.b AS b, y.c AS c FROM y AS y) AS z\",\n        )\n\n        downstream = node.downstream[0]\n        self.assertEqual(downstream.name, \"z.a\")\n        self.assertEqual(downstream.source.sql(), \"SELECT y.a AS a, y.b AS b, y.c AS c FROM y AS y\")\n\n    def test_node_name_doesnt_contain_comment(self) -> None:\n        sql = \"SELECT * FROM (SELECT x /* c */ FROM t1) AS t2\"\n        node = lineage(\"x\", sql)\n\n        self.assertEqual(len(node.downstream), 1)\n        self.assertEqual(len(node.downstream[0].downstream), 1)\n        self.assertEqual(node.downstream[0].downstream[0].name, \"t1.x\")\n\n    def test_pivot_without_alias(self) -> None:\n        sql = \"\"\"\n        SELECT\n            a as other_a\n        FROM (select value,category from sample_data)\n        PIVOT (\n            sum(value)\n            FOR category IN ('a', 'b')\n        );\n        \"\"\"\n        node = lineage(\"other_a\", sql)\n\n        self.assertEqual(node.downstream[0].name, \"_0.value\")\n        self.assertEqual(node.downstream[0].downstream[0].name, \"sample_data.value\")\n\n    def test_pivot_with_alias(self) -> None:\n        sql = \"\"\"\n            SELECT\n                cat_a_s as other_as\n            FROM sample_data\n            PIVOT (\n                sum(value) as s, max(price)\n                FOR category IN ('a' as cat_a, 'b')\n            )\n        \"\"\"\n        node = lineage(\"other_as\", sql)\n\n        self.assertEqual(len(node.downstream), 1)\n        self.assertEqual(node.downstream[0].name, \"sample_data.value\")\n\n    def test_pivot_with_cte(self) -> None:\n        sql = \"\"\"\n        WITH t as (\n            SELECT\n                a as other_a\n            FROM sample_data\n            PIVOT (\n                sum(value)\n                FOR category IN ('a', 'b')\n            )\n        )\n        select other_a from t\n        \"\"\"\n        node = lineage(\"other_a\", sql)\n\n        self.assertEqual(node.downstream[0].name, \"t.other_a\")\n        self.assertEqual(node.downstream[0].reference_node_name, \"t\")\n        self.assertEqual(node.downstream[0].downstream[0].name, \"sample_data.value\")\n\n    def test_pivot_with_implicit_column_of_pivoted_source(self) -> None:\n        sql = \"\"\"\n        SELECT empid\n        FROM quarterly_sales\n            PIVOT(SUM(amount) FOR quarter IN (\n            '2023_Q1',\n            '2023_Q2',\n            '2023_Q3'))\n        ORDER BY empid;\n        \"\"\"\n        node = lineage(\"empid\", sql)\n\n        self.assertEqual(node.downstream[0].name, \"quarterly_sales.empid\")\n\n    def test_pivot_with_implicit_column_of_pivoted_source_and_cte(self) -> None:\n        sql = \"\"\"\n        WITH t as (\n            SELECT empid\n            FROM quarterly_sales\n            PIVOT(SUM(amount) FOR quarter IN (\n                '2023_Q1',\n                '2023_Q2',\n                '2023_Q3'))\n        )\n        select empid from t\n        \"\"\"\n        node = lineage(\"empid\", sql)\n\n        self.assertEqual(node.downstream[0].name, \"t.empid\")\n        self.assertEqual(node.downstream[0].reference_node_name, \"t\")\n        self.assertEqual(node.downstream[0].downstream[0].name, \"quarterly_sales.empid\")\n\n    def test_table_udtf_snowflake(self) -> None:\n        lateral_flatten = \"\"\"\n        SELECT f.value:external_id::string AS external_id\n        FROM database_name.schema_name.table_name AS raw,\n        LATERAL FLATTEN(events) AS f\n        \"\"\"\n        table_flatten = \"\"\"\n        SELECT f.value:external_id::string AS external_id\n        FROM database_name.schema_name.table_name AS raw\n        JOIN TABLE(FLATTEN(events)) AS f\n        \"\"\"\n\n        lateral_node = lineage(\"external_id\", lateral_flatten, dialect=\"snowflake\")\n        table_node = lineage(\"external_id\", table_flatten, dialect=\"snowflake\")\n\n        self.assertEqual(lateral_node.name, \"EXTERNAL_ID\")\n        self.assertEqual(table_node.name, \"EXTERNAL_ID\")\n\n        lateral_node = lateral_node.downstream[0]\n        table_node = table_node.downstream[0]\n\n        self.assertEqual(lateral_node.name, \"F.VALUE\")\n        self.assertEqual(\n            lateral_node.source.sql(\"snowflake\"),\n            \"LATERAL FLATTEN(RAW.EVENTS) AS F(SEQ, KEY, PATH, INDEX, VALUE, THIS)\",\n        )\n\n        self.assertEqual(table_node.name, \"F.VALUE\")\n        self.assertEqual(table_node.source.sql(\"snowflake\"), \"TABLE(FLATTEN(RAW.EVENTS)) AS F\")\n\n        lateral_node = lateral_node.downstream[0]\n        table_node = table_node.downstream[0]\n\n        self.assertEqual(lateral_node.name, \"RAW.EVENTS\")\n        self.assertEqual(\n            lateral_node.source.sql(\"snowflake\"),\n            \"DATABASE_NAME.SCHEMA_NAME.TABLE_NAME AS RAW\",\n        )\n\n        self.assertEqual(table_node.name, \"RAW.EVENTS\")\n        self.assertEqual(\n            table_node.source.sql(\"snowflake\"),\n            \"DATABASE_NAME.SCHEMA_NAME.TABLE_NAME AS RAW\",\n        )\n\n    def test_pivot_with_subquery(self) -> None:\n        schema = {\n            \"loan_ledger\": {\n                \"product_type\": \"varchar\",\n                \"month\": \"date\",\n                \"loan_id\": \"int\",\n            }\n        }\n\n        sql = \"\"\"\n        WITH cte AS (\n            SELECT * FROM (\n                SELECT product_type, month, loan_id\n                FROM loan_ledger\n            ) PIVOT (\n                COUNT(loan_id) FOR month IN ('2024-10', '2024-11')\n            )\n        )\n        SELECT\n            cte.product_type AS product_type,\n            cte.\"2024-10\" AS \"2024-10\"\n        FROM cte\n        \"\"\"\n\n        node = lineage(\"product_type\", sql, dialect=\"duckdb\", schema=schema)\n        self.assertEqual(node.downstream[0].name, \"cte.product_type\")\n        self.assertEqual(node.downstream[0].downstream[0].name, \"_0.product_type\")\n        self.assertEqual(\n            node.downstream[0].downstream[0].downstream[0].name,\n            \"loan_ledger.product_type\",\n        )\n\n        node = lineage('\"2024-10\"', sql, dialect=\"duckdb\", schema=schema)\n        self.assertEqual(node.downstream[0].name, \"cte.2024-10\")\n        self.assertEqual(node.downstream[0].downstream[0].name, \"_0.loan_id\")\n        self.assertEqual(node.downstream[0].downstream[0].downstream[0].name, \"loan_ledger.loan_id\")\n\n    def test_copy_flag(self) -> None:\n        schema = {\n            \"x\": {\n                \"a\": \"int\",\n            },\n        }\n\n        query = sqlglot.parse_one(\"SELECT a FROM z\")\n        sources = {\n            \"y\": sqlglot.parse_one(\"SELECT * FROM x\"),\n            \"z\": sqlglot.parse_one(\"SELECT * FROM y\"),\n        }\n\n        lineage(\"a\", query, schema=schema, sources=sources, copy=False)\n\n        self.assertEqual(sources[\"y\"].sql(), \"SELECT * FROM x\")\n        self.assertEqual(sources[\"z\"].sql(), \"SELECT * FROM y\")\n        self.assertEqual(\n            query.sql(),\n            \"SELECT z.a AS a FROM (SELECT y.a AS a FROM (SELECT x.a AS a FROM x AS x) AS y /* source: y */) AS z /* source: z */\",\n        )\n\n        query = sqlglot.parse_one(\"SELECT a FROM z\")\n        sources = {\n            \"y\": sqlglot.parse_one(\"SELECT * FROM x\"),\n            \"z\": sqlglot.parse_one(\"SELECT * FROM y\"),\n        }\n\n        lineage(\"a\", query, schema=schema, sources=sources, copy=True)\n\n        self.assertEqual(sources[\"y\"].sql(), \"SELECT * FROM x\")\n        self.assertEqual(sources[\"z\"].sql(), \"SELECT * FROM y\")\n        self.assertEqual(query.sql(), \"SELECT a FROM z\")\n\n        query = sqlglot.parse_one(\"SELECT a FROM z\")\n        sources = {\n            \"y\": sqlglot.parse_one(\"SELECT * FROM x\"),\n            \"z\": sqlglot.parse_one(\"SELECT * FROM y\"),\n        }\n\n        query = sqlglot.parse_one(\"SELECT a FROM x\")\n        lineage(\"a\", query, schema=schema, copy=False)\n\n        self.assertEqual(query.sql(), \"SELECT x.a AS a FROM x AS x\")\n\n        query = sqlglot.parse_one(\"SELECT a FROM x\")\n        lineage(\"a\", query, schema=schema, copy=True)\n\n        self.assertEqual(query.sql(), \"SELECT a FROM x\")\n\n    def test_lineage_shared_cte_performance(self) -> None:\n        \"\"\"Shared CTEs referenced from multiple places should not cause exponential expansion.\n\n        Each cte_k joins cte_{k-1} with itself and references both sides\n        (t1.a + t2.a), so without memoization to_node() is called 2^N times.\n        With N=12 that's 4096 expansions.\n        \"\"\"\n        n_levels = 12\n        ctes = [\"cte_0 AS (SELECT a FROM base_table)\"]\n        for k in range(1, n_levels):\n            prev = f\"cte_{k - 1}\"\n            ctes.append(\n                f\"cte_{k} AS (SELECT t1.a + t2.a AS a FROM {prev} t1 JOIN {prev} t2 ON t1.a = t2.a)\"\n            )\n        sql = \"WITH \" + \",\\n     \".join(ctes) + f\"\\nSELECT a FROM cte_{n_levels - 1}\"\n\n        node = lineage(\"a\", sql, schema={\"base_table\": {\"a\": \"int\"}})\n\n        # Walk the DAG and verify structure.\n        all_nodes = list(node.walk())\n\n        # shared references keep node count small (O(N), not O(2^N)).\n        self.assertLess(\n            len(all_nodes),\n            200,\n            f\"got {len(all_nodes)} nodes -- DAG walk may be broken\",\n        )\n\n        # walk() should yield each node exactly once.\n        all_ids = [id(n) for n in all_nodes]\n        self.assertEqual(len(all_ids), len(set(all_ids)))\n\n        # Leaf nodes should reference base_table.\n        leaves = [n for n in all_nodes if not n.downstream]\n        self.assertGreater(len(leaves), 0)\n        self.assertTrue(all(\"base_table\" in n.source.sql() for n in leaves))\n\n    def test_lineage_cte_self_join_distinct_aliases(self) -> None:\n        node = lineage(\n            \"combined\",\n            \"WITH shared AS (SELECT a FROM x) SELECT s1.a + s2.a AS combined FROM shared s1, shared s2\",\n            schema={\"x\": {\"a\": \"int\"}},\n        )\n        downstream_names = sorted(d.name for d in node.downstream)\n        self.assertEqual(downstream_names, [\"s1.a\", \"s2.a\"])\n"
  },
  {
    "path": "tests/test_optimizer.py",
    "content": "import unittest\nfrom concurrent.futures import ProcessPoolExecutor, as_completed\nfrom functools import partial\nfrom unittest.mock import patch\n\nimport duckdb\nfrom pandas.testing import assert_frame_equal\n\nimport sqlglot\nfrom sqlglot import exp, optimizer, parse_one\nfrom sqlglot.errors import ANSI_RESET, ANSI_UNDERLINE, OptimizeError, SchemaError\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom sqlglot.optimizer.normalize import normalization_distance\nfrom sqlglot.optimizer.scope import build_scope, traverse_scope, walk_in_scope\nfrom sqlglot.schema import MappingSchema\nfrom tests.helpers import (\n    TPCDS_SCHEMA,\n    TPCH_SCHEMA,\n    assert_logger_contains,\n    load_sql_fixture_pairs,\n    load_sql_fixtures,\n    string_to_bool,\n)\n\n\ndef parse_and_optimize(func, sql, read_dialect, **kwargs):\n    return func(parse_one(sql, read=read_dialect), **kwargs)\n\n\ndef qualify_columns(expression, validate_qualify_columns=True, **kwargs):\n    expression = optimizer.qualify.qualify(\n        expression,\n        infer_schema=True,\n        validate_qualify_columns=validate_qualify_columns,\n        identify=False,\n        **kwargs,\n    )\n    return expression\n\n\ndef pushdown_projections(expression, **kwargs):\n    expression = optimizer.qualify_tables.qualify_tables(expression)\n    expression = optimizer.qualify_columns.qualify_columns(expression, infer_schema=True, **kwargs)\n    expression = optimizer.pushdown_projections.pushdown_projections(expression, **kwargs)\n    return expression\n\n\ndef normalize(expression, **kwargs):\n    schema = kwargs.get(\"schema\")\n\n    expression = optimizer.normalize.normalize(expression, dnf=False)\n    expression = annotate_types(expression, schema=schema)\n    return optimizer.simplify.simplify(expression)\n\n\ndef simplify(expression, **kwargs):\n    dialect = kwargs.get(\"dialect\")\n    schema = kwargs.get(\"schema\")\n\n    expression = annotate_types(expression, schema=schema)\n    return optimizer.simplify.simplify(\n        expression, constant_propagation=True, coalesce_simplification=True, dialect=dialect\n    )\n\n\ndef pushdown_ctes(expression, **kwargs):\n    optimizer.qualify_columns.pushdown_cte_alias_columns(build_scope(expression))\n    return expression\n\n\ndef annotate_functions(expression, **kwargs):\n    dialect = kwargs.get(\"dialect\")\n    schema = kwargs.get(\"schema\")\n\n    annotated = annotate_types(expression, dialect=dialect, schema=schema)\n\n    return annotated.expressions[0]\n\n\nclass TestOptimizer(unittest.TestCase):\n    maxDiff = None\n\n    @classmethod\n    def setUpClass(cls):\n        sqlglot.schema = MappingSchema()\n        cls.conn = duckdb.connect()\n        cls.conn.execute(\n            \"\"\"\n        CREATE TABLE x (a INT, b INT);\n        CREATE TABLE y (b INT, c INT);\n        CREATE TABLE z (b INT, c INT);\n        CREATE TABLE w (d TEXT, e TEXT);\n\n        INSERT INTO x VALUES (1, 1);\n        INSERT INTO x VALUES (2, 2);\n        INSERT INTO x VALUES (2, 2);\n        INSERT INTO x VALUES (3, 3);\n        INSERT INTO x VALUES (null, null);\n\n        INSERT INTO y VALUES (2, 2);\n        INSERT INTO y VALUES (2, 2);\n        INSERT INTO y VALUES (3, 3);\n        INSERT INTO y VALUES (4, 4);\n        INSERT INTO y VALUES (null, null);\n\n        INSERT INTO y VALUES (3, 3);\n        INSERT INTO y VALUES (3, 3);\n        INSERT INTO y VALUES (4, 4);\n        INSERT INTO y VALUES (5, 5);\n        INSERT INTO y VALUES (null, null);\n\n        INSERT INTO w VALUES ('a', 'b');\n        \"\"\"\n        )\n\n    def setUp(self):\n        self.schema = {\n            \"x\": {\n                \"a\": \"INT\",\n                \"b\": \"INT\",\n            },\n            \"y\": {\n                \"b\": \"INT\",\n                \"c\": \"INT\",\n            },\n            \"z\": {\n                \"b\": \"INT\",\n                \"c\": \"INT\",\n            },\n            \"w\": {\n                \"d\": \"TEXT\",\n                \"e\": \"TEXT\",\n            },\n            \"temporal\": {\n                \"d\": \"DATE\",\n                \"t\": \"DATETIME\",\n            },\n            \"structs\": {\n                \"one\": \"STRUCT<a_1 INT, b_1 VARCHAR>\",\n                \"nested_0\": \"STRUCT<a_1 INT, nested_1 STRUCT<a_2 INT, nested_2 STRUCT<a_3 INT>>>\",\n                \"quoted\": 'STRUCT<\"foo bar\" INT>',\n            },\n            \"t_bool\": {\n                \"a\": \"BOOLEAN\",\n            },\n        }\n\n    def check_file(\n        self,\n        file,\n        func,\n        pretty=False,\n        execute=False,\n        only=None,\n        **kwargs,\n    ):\n        with ProcessPoolExecutor() as pool:\n            results = {}\n\n            for i, (meta, sql, expected) in enumerate(\n                load_sql_fixture_pairs(f\"optimizer/{file}.sql\"), start=1\n            ):\n                title = meta.get(\"title\") or f\"{i}, {sql}\"\n                if only and title != only:\n                    continue\n\n                dialect = meta.get(\"dialect\")\n                leave_tables_isolated = meta.get(\"leave_tables_isolated\")\n                validate_qualify_columns = meta.get(\"validate_qualify_columns\")\n                canonicalize_table_aliases = meta.get(\"canonicalize_table_aliases\")\n\n                func_kwargs = kwargs.copy()\n\n                if leave_tables_isolated is not None:\n                    func_kwargs[\"leave_tables_isolated\"] = string_to_bool(leave_tables_isolated)\n\n                if validate_qualify_columns is not None:\n                    func_kwargs[\"validate_qualify_columns\"] = string_to_bool(\n                        validate_qualify_columns\n                    )\n                if dialect:\n                    func_kwargs[\"dialect\"] = dialect\n                if canonicalize_table_aliases is not None:\n                    func_kwargs[\"canonicalize_table_aliases\"] = string_to_bool(\n                        canonicalize_table_aliases\n                    )\n\n                future = pool.submit(parse_and_optimize, func, sql, dialect, **func_kwargs)\n                results[future] = (\n                    sql,\n                    title,\n                    expected,\n                    dialect,\n                    execute if meta.get(\"execute\") is None else False,\n                )\n\n        for future in as_completed(results):\n            sql, title, expected, dialect, execute = results[future]\n\n            with self.subTest(title):\n                optimized = future.result()\n                actual = optimized.sql(pretty=pretty, dialect=dialect)\n                self.assertEqual(\n                    expected,\n                    actual,\n                )\n                for expression in optimized.walk():\n                    for arg_key, arg in expression.args.items():\n                        if isinstance(arg, exp.Expr):\n                            self.assertEqual(arg_key, arg.arg_key)\n                            self.assertIs(arg.parent, expression)\n\n                if string_to_bool(execute):\n                    with self.subTest(f\"(execute) {title}\"):\n                        df1 = self.conn.execute(\n                            sqlglot.transpile(sql, read=dialect, write=\"duckdb\")[0]\n                        ).df()\n                        df2 = self.conn.execute(optimized.sql(dialect=\"duckdb\")).df()\n                        assert_frame_equal(df1, df2)\n\n    @patch(\"sqlglot.generator.logger\")\n    def test_optimize(self, logger):\n        self.assertEqual(optimizer.optimize(\"x = 1 + 1\", identify=False).sql(), \"x = 2\")\n\n        schema = {\n            \"x\": {\"a\": \"INT\", \"b\": \"INT\"},\n            \"y\": {\"b\": \"INT\", \"c\": \"INT\"},\n            \"z\": {\"a\": \"INT\", \"c\": \"INT\"},\n            \"u\": {\"f\": \"INT\", \"g\": \"INT\", \"h\": \"TEXT\"},\n        }\n\n        self.check_file(\n            \"optimizer\",\n            optimizer.optimize,\n            infer_schema=True,\n            pretty=True,\n            execute=True,\n            schema=schema,\n        )\n\n    def test_isolate_table_selects(self):\n        self.check_file(\n            \"isolate_table_selects\",\n            optimizer.isolate_table_selects.isolate_table_selects,\n            schema=self.schema,\n        )\n\n    def test_qualify_tables(self):\n        tables = set()\n        optimizer.qualify.qualify(\n            parse_one(\"with foo AS (select * from bar) select * from foo join baz\"),\n            qualify_columns=False,\n            on_qualify=lambda t: tables.add(t.name),\n        )\n        self.assertEqual(tables, {\"bar\", \"baz\"})\n\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\"WITH tesT AS (SELECT * FROM t1) SELECT * FROM test\", \"bigquery\"),\n                db=\"db\",\n                catalog=\"catalog\",\n                dialect=\"bigquery\",\n                quote_identifiers=False,\n            ).sql(\"bigquery\"),\n            \"WITH test AS (SELECT * FROM catalog.db.t1 AS t1) SELECT * FROM test AS test\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify_tables.qualify_tables(\n                parse_one(\n                    \"WITH cte AS (SELECT * FROM t) SELECT * FROM cte PIVOT(SUM(c) FOR v IN ('x', 'y'))\"\n                ),\n                db=\"db\",\n                catalog=\"catalog\",\n            ).sql(),\n            \"WITH cte AS (SELECT * FROM catalog.db.t AS t) SELECT * FROM cte AS cte PIVOT(SUM(c) FOR v IN ('x', 'y')) AS _0\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify_tables.qualify_tables(\n                parse_one(\n                    \"WITH cte AS (SELECT * FROM t) SELECT * FROM cte PIVOT(SUM(c) FOR v IN ('x', 'y')) AS pivot_alias\"\n                ),\n                db=\"db\",\n                catalog=\"catalog\",\n            ).sql(),\n            \"WITH cte AS (SELECT * FROM catalog.db.t AS t) SELECT * FROM cte AS cte PIVOT(SUM(c) FOR v IN ('x', 'y')) AS pivot_alias\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify_tables.qualify_tables(\n                parse_one(\"select a from b\"), catalog=\"catalog\"\n            ).sql(),\n            \"SELECT a FROM b AS b\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify_tables.qualify_tables(parse_one(\"select a from b\"), db='\"DB\"').sql(),\n            'SELECT a FROM \"DB\".b AS b',\n        )\n\n        self.check_file(\n            \"qualify_tables\",\n            optimizer.qualify_tables.qualify_tables,\n            db=\"db\",\n            catalog=\"c\",\n        )\n\n    def test_normalize(self):\n        self.assertEqual(\n            optimizer.normalize.normalize(\n                parse_one(\"x AND (y OR z)\"),\n                dnf=True,\n            ).sql(),\n            \"(x AND y) OR (x AND z)\",\n        )\n\n        self.assertEqual(\n            optimizer.normalize.normalize(\n                parse_one(\"x AND (y OR z)\"),\n            ).sql(),\n            \"x AND (y OR z)\",\n        )\n\n        self.check_file(\"normalize\", normalize, schema=self.schema)\n\n    @patch(\"sqlglot.generator.logger\")\n    def test_qualify_columns(self, logger):\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                    SELECT Teams.Name, count(*)\n                    FROM raw.TeamMemberships as TeamMemberships\n                    join raw.Teams\n                        on Teams.Id = TeamMemberships.TeamId\n                    GROUP BY 1\n                    \"\"\",\n                    read=\"bigquery\",\n                ),\n                schema={\n                    \"raw\": {\n                        \"TeamMemberships\": {\n                            \"Id\": \"INTEGER\",\n                            \"UserId\": \"INTEGER\",\n                            \"TeamId\": \"INTEGER\",\n                        },\n                        \"Teams\": {\n                            \"Id\": \"INTEGER\",\n                            \"Name\": \"STRING\",\n                        },\n                    }\n                },\n                dialect=\"bigquery\",\n            ).sql(dialect=\"bigquery\"),\n            \"SELECT `teams`.`name` AS `name`, count(*) AS `_col_1` FROM `raw`.`TeamMemberships` AS `teammemberships` JOIN `raw`.`Teams` AS `teams` ON `teams`.`id` = `teammemberships`.`teamid` GROUP BY `teams`.`name`\",\n        )\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"SELECT `my_db.my_table`.`my_column` FROM `my_db.my_table`\",\n                    read=\"bigquery\",\n                ),\n                dialect=\"bigquery\",\n            ).sql(dialect=\"bigquery\"),\n            \"SELECT `my_table`.`my_column` AS `my_column` FROM `my_db.my_table` AS `my_table`\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(\n                    \"WITH RECURSIVE t AS (SELECT 1 AS x UNION ALL SELECT x + 1 FROM t AS child WHERE x < 10) SELECT * FROM t\"\n                ),\n                schema={},\n                infer_schema=False,\n            ).sql(),\n            \"WITH RECURSIVE t AS (SELECT 1 AS x UNION ALL SELECT child.x + 1 AS _col_0 FROM t AS child WHERE child.x < 10) SELECT t.x AS x FROM t\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(\"WITH x AS (SELECT a FROM db.y) SELECT * FROM db.x\"),\n                schema={\"db\": {\"x\": {\"z\": \"int\"}, \"y\": {\"a\": \"int\"}}},\n                expand_stars=False,\n            ).sql(),\n            \"WITH x AS (SELECT y.a AS a FROM db.y) SELECT * FROM db.x\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(\"WITH x AS (SELECT a FROM db.y) SELECT z FROM db.x\"),\n                schema={\"db\": {\"x\": {\"z\": \"int\"}, \"y\": {\"a\": \"int\"}}},\n                infer_schema=False,\n            ).sql(),\n            \"WITH x AS (SELECT y.a AS a FROM db.y) SELECT x.z AS z FROM db.x\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(\"select y from x\"),\n                schema={},\n                infer_schema=False,\n            ).sql(),\n            \"SELECT y AS y FROM x\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"WITH X AS (SELECT Y.A FROM DB.y CROSS JOIN a.b.INFORMATION_SCHEMA.COLUMNS) SELECT `A` FROM X\",\n                    read=\"bigquery\",\n                ),\n                dialect=\"bigquery\",\n            ).sql(),\n            'WITH \"x\" AS (SELECT \"y\".\"a\" AS \"a\" FROM \"DB\".\"y\" AS \"y\" CROSS JOIN \"a\".\"b\".\"INFORMATION_SCHEMA.COLUMNS\" AS \"columns\") SELECT \"x\".\"a\" AS \"a\" FROM \"x\" AS \"x\"',\n        )\n\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"CREATE FUNCTION udfs.`myTest`(`x` FLOAT64) AS (1)\",\n                    read=\"bigquery\",\n                ),\n                dialect=\"bigquery\",\n            ).sql(dialect=\"bigquery\"),\n            \"CREATE FUNCTION `udfs`.`myTest`(`x` FLOAT64) AS (1)\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\"SELECT `bar_bazfoo_$id` FROM test\", read=\"spark\"),\n                schema={\"test\": {\"bar_bazFoo_$id\": \"BIGINT\"}},\n                dialect=\"spark\",\n            ).sql(dialect=\"spark\"),\n            \"SELECT `test`.`bar_bazfoo_$id` AS `bar_bazfoo_$id` FROM `test` AS `test`\",\n        )\n\n        qualified = optimizer.qualify.qualify(\n            parse_one(\"WITH t AS (SELECT 1 AS c) (SELECT c FROM t)\")\n        )\n        self.assertIs(qualified.selects[0].parent, qualified)\n        self.assertEqual(\n            qualified.sql(),\n            'WITH \"t\" AS (SELECT 1 AS \"c\") SELECT \"t\".\"c\" AS \"c\" FROM \"t\" AS \"t\"',\n        )\n\n        # can't coalesce USING columns because they don't exist in every already-joined table\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(\n                    \"SELECT id, dt, v FROM (SELECT t1.id, t1.dt, sum(coalesce(t2.v, 0)) AS v FROM t1 AS t1 LEFT JOIN lkp AS lkp USING (id) LEFT JOIN t2 AS t2 USING (other_id, dt, common) WHERE t1.id > 10 GROUP BY 1, 2) AS `_0`\",\n                    dialect=\"bigquery\",\n                ),\n                schema=MappingSchema(\n                    schema={\n                        \"t1\": {\"id\": \"int64\", \"dt\": \"date\", \"common\": \"int64\"},\n                        \"lkp\": {\"id\": \"int64\", \"other_id\": \"int64\", \"common\": \"int64\"},\n                        \"t2\": {\"other_id\": \"int64\", \"dt\": \"date\", \"v\": \"int64\", \"common\": \"int64\"},\n                    },\n                    dialect=\"bigquery\",\n                ),\n            ).sql(dialect=\"bigquery\"),\n            \"SELECT `_0`.id AS id, `_0`.dt AS dt, `_0`.v AS v FROM (SELECT t1.id AS id, t1.dt AS dt, sum(coalesce(t2.v, 0)) AS v FROM t1 AS t1 LEFT JOIN lkp AS lkp ON t1.id = lkp.id LEFT JOIN t2 AS t2 ON lkp.other_id = t2.other_id AND t1.dt = t2.dt AND COALESCE(t1.common, lkp.common) = t2.common WHERE t1.id > 10 GROUP BY t1.id, t1.dt) AS `_0`\",\n        )\n\n        # Detection of correlation where columns are referenced in derived tables nested within subqueries\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"SELECT a.g FROM a WHERE a.e < (SELECT MAX(u) FROM (SELECT SUM(c.b) AS u FROM c WHERE  c.d = f GROUP BY c.e) w)\"\n                ),\n                schema={\n                    \"a\": {\"g\": \"INT\", \"e\": \"INT\", \"f\": \"INT\"},\n                    \"c\": {\"d\": \"INT\", \"e\": \"INT\", \"b\": \"INT\"},\n                },\n                quote_identifiers=False,\n            ).sql(),\n            \"SELECT a.g AS g FROM a AS a WHERE a.e < (SELECT MAX(w.u) AS _col_0 FROM (SELECT SUM(c.b) AS u FROM c AS c WHERE c.d = a.f GROUP BY c.e) AS w)\",\n        )\n\n        # Detection of correlation where columns are referenced in derived tables nested within lateral joins\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"SELECT u.user_id, l.log_date FROM users AS u CROSS JOIN LATERAL (SELECT l1.log_date FROM (SELECT l.log_date FROM logs AS l WHERE l.user_id = u.user_id AND l.log_date <= 100 ORDER BY l.log_date LIMIT 1) AS l1) AS l\",\n                    dialect=\"postgres\",\n                ),\n                schema={\n                    \"users\": {\"user_id\": \"text\", \"log_date\": \"date\"},\n                    \"logs\": {\"user_id\": \"text\", \"log_date\": \"date\"},\n                },\n                quote_identifiers=False,\n            ).sql(\"postgres\"),\n            \"SELECT u.user_id AS user_id, l.log_date AS log_date FROM users AS u CROSS JOIN LATERAL (SELECT l1.log_date AS log_date FROM (SELECT l.log_date AS log_date FROM logs AS l WHERE l.user_id = u.user_id AND l.log_date <= 100 ORDER BY l.log_date LIMIT 1) AS l1) AS l\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"SELECT A.b_id FROM A JOIN B ON A.b_id=B.b_id JOIN C USING(c_id)\",\n                    dialect=\"postgres\",\n                ),\n                schema={\n                    \"A\": {\"b_id\": \"int\"},\n                    \"B\": {\"b_id\": \"int\", \"c_id\": \"int\"},\n                    \"C\": {\"c_id\": \"int\"},\n                },\n                quote_identifiers=False,\n            ).sql(\"postgres\"),\n            \"SELECT a.b_id AS b_id FROM a AS a JOIN b AS b ON a.b_id = b.b_id JOIN c AS c ON b.c_id = c.c_id\",\n        )\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"SELECT A.b_id FROM A JOIN B ON A.b_id=B.b_id JOIN C ON B.b_id = C.b_id JOIN D USING(d_id)\",\n                    dialect=\"postgres\",\n                ),\n                schema={\n                    \"A\": {\"b_id\": \"int\"},\n                    \"B\": {\"b_id\": \"int\", \"d_id\": \"int\"},\n                    \"C\": {\"b_id\": \"int\"},\n                    \"D\": {\"d_id\": \"int\"},\n                },\n                quote_identifiers=False,\n            ).sql(\"postgres\"),\n            \"SELECT a.b_id AS b_id FROM a AS a JOIN b AS b ON a.b_id = b.b_id JOIN c AS c ON b.b_id = c.b_id JOIN d AS d ON b.d_id = d.d_id\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                    SELECT\n                      (SELECT SUM(c.amount)\n                       FROM UNNEST(credits) AS c\n                       WHERE type != 'promotion') as total\n                    FROM billing\n                    \"\"\",\n                    read=\"bigquery\",\n                ),\n                schema={\"billing\": {\"credits\": \"ARRAY<STRUCT<amount FLOAT64, type STRING>>\"}},\n                dialect=\"bigquery\",\n            ).sql(dialect=\"bigquery\"),\n            \"SELECT (SELECT SUM(`c`.`amount`) AS `_col_0` FROM UNNEST(`billing`.`credits`) AS `c` WHERE `type` <> 'promotion') AS `total` FROM `billing` AS `billing`\",\n        )\n\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                    WITH cte AS (SELECT * FROM base_table)\n                    SELECT\n                      (SELECT SUM(item.price)\n                       FROM UNNEST(items) AS item\n                       WHERE category = 'electronics') as electronics_total\n                    FROM cte\n                    \"\"\",\n                    read=\"bigquery\",\n                ),\n                schema={\n                    \"base_table\": {\n                        \"id\": \"INT64\",\n                        \"items\": \"ARRAY<STRUCT<price FLOAT64, category STRING>>\",\n                    }\n                },\n                dialect=\"bigquery\",\n            ).sql(dialect=\"bigquery\"),\n            \"WITH `cte` AS (SELECT `base_table`.`id` AS `id`, `base_table`.`items` AS `items` FROM `base_table` AS `base_table`) SELECT (SELECT SUM(`item`.`price`) AS `_col_0` FROM UNNEST(`cte`.`items`) AS `item` WHERE `category` = 'electronics') AS `electronics_total` FROM `cte` AS `cte`\",\n        )\n\n        self.check_file(\n            \"qualify_columns\",\n            qualify_columns,\n            execute=True,\n            schema=self.schema,\n        )\n        self.check_file(\"qualify_columns_ddl\", qualify_columns, schema=self.schema)\n\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                    SELECT\n                    (\n                        SELECT\n                        col_st.value\n                        FROM UNNEST(col_st) AS col_st\n                    ) AS vcol1\n                    FROM t AS b\n                    \"\"\",\n                    read=\"bigquery\",\n                ),\n                schema={\n                    \"t\": {\n                        \"col_st\": \"ARRAY<STRUCT<key STRING, value INT>>\",\n                    }\n                },\n                dialect=\"bigquery\",\n            ).sql(dialect=\"bigquery\"),\n            \"SELECT (SELECT `col_st`.`value` AS `value` FROM UNNEST(`b`.`col_st`) AS `col_st`) AS `vcol1` FROM `t` AS `b`\",\n        )\n\n        # Schema-qualified table joined twice (once unaliased, once aliased) should resolve correctly\n        self.assertEqual(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"SELECT 1 FROM dbo.a JOIN dbo.b ON dbo.b.id = dbo.a.id JOIN dbo.b AS x ON x.id = dbo.a.id\"\n                ),\n            ).sql(),\n            'SELECT 1 AS \"1\" FROM \"dbo\".\"a\" AS \"a\" JOIN \"dbo\".\"b\" AS \"b\" ON \"b\".\"id\" = \"a\".\"id\" JOIN \"dbo\".\"b\" AS \"x\" ON \"x\".\"id\" = \"a\".\"id\"',\n        )\n\n    def test_validate_columns(self):\n        with self.assertRaisesRegex(\n            OptimizeError, \"Column 'foo' could not be resolved. Line: 1, Col: 10\"\n        ):\n            optimizer.qualify.qualify(\n                parse_one(\"select foo from x\"),\n                schema={\"foo\": {\"y\": \"int\"}},\n            )\n\n        # Test ambiguous columns error with PIVOT (which skips \"could not be resolved\" check)\n        with self.assertRaisesRegex(OptimizeError, \"Ambiguous column 'a'\"):\n            expression = parse_one(\n                \"SELECT * FROM (SELECT a, b, c FROM x) PIVOT (SUM(b) FOR c IN ('x', 'y'))\"\n            )\n            qualified = optimizer.qualify_columns.qualify_columns(\n                expression, schema={\"x\": {\"a\": \"int\", \"b\": \"int\", \"c\": \"str\"}}\n            )\n            optimizer.qualify_columns.validate_qualify_columns(qualified)\n\n    def test_qualify_columns__with_invisible(self):\n        schema = MappingSchema(self.schema, {\"x\": {\"a\"}, \"y\": {\"b\"}, \"z\": {\"b\"}})\n        self.check_file(\"qualify_columns__with_invisible\", qualify_columns, schema=schema)\n\n    def test_pushdown_cte_alias_columns(self):\n        self.check_file(\n            \"pushdown_cte_alias_columns\",\n            pushdown_ctes,\n        )\n\n    def test_qualify_columns__invalid(self):\n        for sql in load_sql_fixtures(\"optimizer/qualify_columns__invalid.sql\"):\n            with self.subTest(sql):\n                with self.assertRaises((OptimizeError, SchemaError)):\n                    expression = optimizer.qualify_columns.qualify_columns(\n                        parse_one(sql), schema=self.schema\n                    )\n                    optimizer.qualify_columns.validate_qualify_columns(expression)\n\n        # this makes sure the fallback scenario in get_table in resolver is covered\n        # and the error message is column cannot be resolved instead of unknown table\n        sql = \"\"\"\n            SELECT\n            INLINE_VIEW.a AS ACCOUNT\n            FROM (\n            (\n                SELECT\n                a\n                FROM table1\n            ) inline_view\n            LEFT JOIN table2\n                ON a = table2.id\n            )\n            LEFT JOIN table3\n            ON inline_view.a = table3.a\n        \"\"\"\n\n        with self.assertRaises(OptimizeError) as ctx:\n            schema = MappingSchema()\n            schema.add_table(\"table3\", [\"a\"])\n\n            expression = optimizer.qualify_columns.qualify_columns(parse_one(sql), schema=schema)\n            optimizer.qualify_columns.validate_qualify_columns(expression)\n\n        error_msg = str(ctx.exception)\n        self.assertIn(\"Column 'a' could not be resolved\", error_msg)\n\n    def test_optimize_error_highlighting(self):\n        # highlighting works with sql parameter\n        sql = \"SELECT nonexistent FROM x\"\n\n        with self.assertRaises(OptimizeError) as ctx:\n            optimizer.optimize(sql, schema=self.schema, sql=sql)\n\n        error_msg = str(ctx.exception)\n        self.assertIn(\"Column 'nonexistent' could not be resolved\", error_msg)\n        self.assertIn(f\"{ANSI_UNDERLINE}nonexistent{ANSI_RESET}\", error_msg)\n\n        # no highlighting when sql is None\n        sql = \"SELECT nonexistent FROM x\"\n\n        with self.assertRaises(OptimizeError) as ctx:\n            optimizer.optimize(sql, schema=self.schema, sql=None)\n\n        error_msg = str(ctx.exception)\n        self.assertIn(\"Column 'nonexistent' could not be resolved\", error_msg)\n        self.assertNotIn(f\"{ANSI_UNDERLINE}nonexistent{ANSI_RESET}\", error_msg)\n\n    def test_normalize_identifiers(self):\n        self.check_file(\n            \"normalize_identifiers\",\n            optimizer.normalize_identifiers.normalize_identifiers,\n        )\n\n        self.assertEqual(optimizer.normalize_identifiers.normalize_identifiers(\"a%\").sql(), '\"a%\"')\n\n    def test_quote_identifiers(self):\n        self.check_file(\n            \"quote_identifiers\",\n            optimizer.qualify_columns.quote_identifiers,\n        )\n\n    def test_pushdown_projection(self):\n        self.check_file(\"pushdown_projections\", pushdown_projections, schema=self.schema)\n\n    def test_simplify(self):\n        self.check_file(\"simplify\", simplify, schema=self.schema)\n\n        # Stress test with huge union query\n        union_sql = \"SELECT 1 UNION ALL \" * 1000 + \"SELECT 1\"\n        expression = parse_one(union_sql)\n        self.assertEqual(optimizer.simplify.simplify(expression).sql(), union_sql)\n\n        # Ensure simplify mutates the AST properly\n        expression = parse_one(\"SELECT 1 + 2\")\n        simplify(expression.selects[0])\n        self.assertEqual(expression.sql(), \"SELECT 3\")\n\n        expression = parse_one(\"SELECT a, c, b FROM table1 WHERE 1 = 1\")\n        self.assertEqual(simplify(simplify(expression.find(exp.Where))).sql(), \"WHERE TRUE\")\n\n        expression = parse_one(\"TRUE AND TRUE AND TRUE\")\n        self.assertEqual(exp.true(), optimizer.simplify.simplify(expression))\n\n        # CONCAT in (e.g.) Presto is parsed as Concat instead of SafeConcat which is the default type\n        # This test checks that simplify_concat preserves the corresponding expression types.\n        concat = parse_one(\"CONCAT('a', x, 'b', 'c')\", read=\"presto\")\n        simplified_concat = optimizer.simplify.simplify(concat)\n\n        safe_concat = parse_one(\"CONCAT('a', x, 'b', 'c')\")\n        simplified_safe_concat = optimizer.simplify.simplify(safe_concat)\n\n        self.assertEqual(simplified_concat.args[\"safe\"], False)\n        self.assertEqual(simplified_safe_concat.args[\"safe\"], True)\n\n        self.assertEqual(\"CONCAT('a', x, 'bc')\", simplified_concat.sql(dialect=\"presto\"))\n        self.assertEqual(\"CONCAT('a', x, 'bc')\", simplified_safe_concat.sql())\n\n        anon_unquoted_str = parse_one(\"anonymous(x, y)\")\n        self.assertEqual(optimizer.simplify.gen(anon_unquoted_str), \"ANONYMOUS(x,y)\")\n\n        query = parse_one(\"SELECT x FROM t\")\n        self.assertEqual(optimizer.simplify.gen(query), optimizer.simplify.gen(query.copy()))\n\n        anon_unquoted_identifier = exp.Anonymous(\n            this=exp.to_identifier(\"anonymous\"),\n            expressions=[exp.column(\"x\"), exp.column(\"y\")],\n        )\n        self.assertEqual(optimizer.simplify.gen(anon_unquoted_identifier), \"ANONYMOUS(x,y)\")\n\n        anon_quoted = parse_one('\"anonymous\"(x, y)')\n        self.assertEqual(optimizer.simplify.gen(anon_quoted), '\"anonymous\"(x,y)')\n\n        with self.assertRaises(ValueError) as e:\n            anon_invalid = exp.Anonymous(this=5)\n            optimizer.simplify.gen(anon_invalid)\n\n        self.assertIn(\n            \"Anonymous.this expects a str or an Identifier, got 'int'.\",\n            str(e.exception),\n        )\n\n        sql = parse_one(\n            \"\"\"\n        WITH cte AS (select 1 union select 2), cte2 AS (\n            SELECT ROW() OVER (PARTITION BY y) FROM (\n                (select 1) limit 10\n            )\n        )\n        SELECT\n          *,\n          a + 1,\n          a div 1,\n          filter(\"B\", (x, y) -> x + y)\n          FROM (z AS z CROSS JOIN z) AS f(a) LEFT JOIN a.b.c.d.e.f.g USING(n) ORDER BY 1\n        \"\"\"\n        )\n        self.assertEqual(\n            optimizer.simplify.gen(sql),\n            \"\"\"\nSELECT :with_,WITH :expressions,CTE :this,UNION :this,SELECT :expressions,1,:expression,SELECT :expressions,2,:distinct,True,:alias, AS cte,CTE :this,SELECT :expressions,WINDOW :this,ROW(),:partition_by,y,:over,OVER,:from_,FROM ((SELECT :expressions,1):limit,LIMIT :expression,10),:alias, AS cte2,:expressions,STAR,a + 1,a DIV 1,FILTER(\"B\",LAMBDA :this,x + y,:expressions,x,y),:from_,FROM (z AS z:joins,JOIN :this,z,:kind,CROSS) AS f(a),:joins,JOIN :this,a.b.c.d.e.f.g,:side,LEFT,:using,n,:order,ORDER :expressions,ORDERED :this,1,:nulls_first,True\n\"\"\".strip(),\n        )\n        self.assertEqual(\n            optimizer.simplify.gen(parse_one(\"select item_id /* description */\"), comments=True),\n            \"SELECT :expressions,item_id /* description */\",\n        )\n\n    def test_simplify_nested(self):\n        sql = \"\"\"\n        SELECT x, 1 + 1\n        FROM foo\n        WHERE x > (((select x + 1 + 1, sum(y + 1 + 1) FROM bar GROUP BY x + 1 + 1)))\n        \"\"\"\n\n        self.assertEqual(\n            parse_one(\"\"\"\n            SELECT x, 2\n            FROM foo\n            WHERE x > (((\n                select x + 1 + 1, sum(y + 2)\n                FROM bar\n                GROUP BY x + 1 + 1\n            )))\n            \"\"\").sql(pretty=True),\n            optimizer.simplify.simplify(parse_one(sql)).sql(pretty=True),\n        )\n\n    def test_unnest_subqueries(self):\n        self.check_file(\"unnest_subqueries\", optimizer.unnest_subqueries.unnest_subqueries)\n\n    def test_pushdown_predicates(self):\n        self.check_file(\"pushdown_predicates\", optimizer.pushdown_predicates.pushdown_predicates)\n\n    def test_expand_alias_refs(self):\n        # check negative integer literal as group by column\n        self.assertEqual(\n            optimizer.optimize(\"SELECT -99 AS e GROUP BY e\").sql(),\n            'SELECT -99 AS \"e\" GROUP BY 1',\n        )\n\n        # check order of lateral expansion with no schema\n        self.assertEqual(\n            optimizer.optimize(\"SELECT a + 1 AS d, d + 1 AS e FROM x WHERE e > 1 GROUP BY e\").sql(),\n            'SELECT \"x\".\"a\" + 1 AS \"d\", \"x\".\"a\" + 1 + 1 AS \"e\" FROM \"x\" AS \"x\" WHERE (\"x\".\"a\" + 2) > 1 GROUP BY \"x\".\"a\" + 1 + 1',\n        )\n\n        unused_schema = {\"l\": {\"c\": \"int\"}}\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(\"SELECT CAST(x AS INT) AS y FROM z AS z\"),\n                schema=unused_schema,\n                infer_schema=False,\n            ).sql(),\n            \"SELECT CAST(x AS INT) AS y FROM z AS z\",\n        )\n\n        # BigQuery expands overlapping alias only for GROUP BY + HAVING\n        sql = \"WITH data AS (SELECT 1 AS id, 2 AS my_id, 'a' AS name, 'b' AS full_name) SELECT id AS my_id, CONCAT(id, name) AS full_name FROM data WHERE my_id = 1 GROUP BY my_id, full_name HAVING my_id = 1\"\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(sql, dialect=\"bigquery\"),\n                schema=MappingSchema(schema=unused_schema, dialect=\"bigquery\"),\n            ).sql(),\n            \"WITH data AS (SELECT 1 AS id, 2 AS my_id, 'a' AS name, 'b' AS full_name) SELECT data.id AS my_id, CONCAT(data.id, data.name) AS full_name FROM data WHERE data.my_id = 1 GROUP BY data.id, CONCAT(data.id, data.name) HAVING data.id = 1\",\n        )\n\n        # Clickhouse expands overlapping alias across the entire query\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(sql, dialect=\"clickhouse\"),\n                schema=MappingSchema(schema=unused_schema, dialect=\"clickhouse\"),\n            ).sql(),\n            \"WITH data AS (SELECT 1 AS id, 2 AS my_id, 'a' AS name, 'b' AS full_name) SELECT data.id AS my_id, CONCAT(data.id, data.name) AS full_name FROM data WHERE data.id = 1 GROUP BY data.id, CONCAT(data.id, data.name) HAVING data.id = 1\",\n        )\n\n        # Edge case: BigQuery shouldn't expand aliases in complex expressions\n        sql = \"WITH data AS (SELECT 1 AS id) SELECT FUNC(id) AS id FROM data GROUP BY FUNC(id)\"\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(sql, dialect=\"bigquery\"),\n                schema=MappingSchema(schema=unused_schema, dialect=\"bigquery\"),\n            ).sql(),\n            \"WITH data AS (SELECT 1 AS id) SELECT FUNC(data.id) AS id FROM data GROUP BY FUNC(data.id)\",\n        )\n\n        sql = \"SELECT x.a, max(x.b) as x FROM x AS x GROUP BY 1 HAVING x > 1\"\n        self.assertEqual(\n            optimizer.qualify_columns.qualify_columns(\n                parse_one(sql, dialect=\"bigquery\"),\n                schema=MappingSchema(schema=unused_schema, dialect=\"bigquery\"),\n            ).sql(),\n            \"SELECT x.a AS a, MAX(x.b) AS x FROM x AS x GROUP BY 1 HAVING x > 1\",\n        )\n\n    def test_optimize_joins(self):\n        self.check_file(\n            \"optimize_joins\",\n            optimizer.optimize_joins.optimize_joins,\n        )\n\n    def test_eliminate_joins(self):\n        self.check_file(\n            \"eliminate_joins\",\n            optimizer.eliminate_joins.eliminate_joins,\n            pretty=True,\n        )\n\n    def test_eliminate_ctes(self):\n        self.check_file(\n            \"eliminate_ctes\",\n            optimizer.eliminate_ctes.eliminate_ctes,\n            pretty=True,\n        )\n\n    @patch(\"sqlglot.generator.logger\")\n    def test_merge_subqueries(self, logger):\n        optimize = partial(\n            optimizer.optimize,\n            rules=[\n                optimizer.qualify_tables.qualify_tables,\n                optimizer.qualify_columns.qualify_columns,\n                optimizer.merge_subqueries.merge_subqueries,\n            ],\n        )\n\n        self.check_file(\"merge_subqueries\", optimize, execute=True, schema=self.schema)\n\n    def test_eliminate_subqueries(self):\n        self.check_file(\"eliminate_subqueries\", optimizer.eliminate_subqueries.eliminate_subqueries)\n\n    def test_canonicalize(self):\n        optimize = partial(\n            optimizer.optimize,\n            rules=[\n                optimizer.qualify.qualify,\n                optimizer.qualify_columns.quote_identifiers,\n                annotate_types,\n                optimizer.canonicalize.canonicalize,\n            ],\n        )\n        self.check_file(\"canonicalize\", optimize, schema=self.schema)\n\n        # In T-SQL and Redshift, SELECT a + b can produce a NULL, so we can't transpile it\n        # into a CONCAT in Postgres, because that coalesces NULL values with empty strings\n        ast = optimize(\"SELECT CAST(a AS TEXT) + CAST(b AS TEXT) FROM t\", dialect=\"tsql\")\n        self.assertEqual(\n            ast.sql(\"postgres\"),\n            'SELECT CAST(\"t\".\"a\" AS TEXT) || CAST(\"t\".\"b\" AS TEXT) AS \"_col_0\" FROM \"t\" AS \"t\"',\n        )\n\n    def test_tpch(self):\n        self.check_file(\"tpc-h/tpc-h\", optimizer.optimize, schema=TPCH_SCHEMA, pretty=True)\n\n    def test_tpcds(self):\n        self.check_file(\"tpc-ds/tpc-ds\", optimizer.optimize, schema=TPCDS_SCHEMA, pretty=True)\n\n    def test_file_schema(self):\n        self.assertEqual(\n            optimizer.optimize(\n                \"SELECT * FROM foo\",\n                on_qualify=lambda table: table.replace(exp.to_table(\"bar\")),\n            ).sql(),\n            'SELECT * FROM \"bar\"',\n        )\n\n    def test_scope(self):\n        ast = parse_one(\"SELECT IF(a IN UNNEST(b), 1, 0) AS c FROM t\", dialect=\"bigquery\")\n        self.assertEqual(build_scope(ast).columns, [exp.column(\"a\"), exp.column(\"b\")])\n\n        many_unions = parse_one(\" UNION ALL \".join([\"SELECT x FROM t\"] * 10000))\n        scopes_using_traverse = list(build_scope(many_unions).traverse())\n        scopes_using_traverse_scope = traverse_scope(many_unions)\n        self.assertEqual(len(scopes_using_traverse), len(scopes_using_traverse_scope))\n        assert all(\n            x.expression is y.expression\n            for x, y in zip(scopes_using_traverse, scopes_using_traverse_scope)\n        )\n\n        sql = \"\"\"\n        WITH q AS (\n          SELECT x.b FROM x\n        ), r AS (\n          SELECT y.b FROM y\n        ), z as (\n          SELECT cola, colb FROM (VALUES(1, 'test')) AS tab(cola, colb)\n        )\n        SELECT\n          r.b,\n          s.b\n        FROM r\n        JOIN (\n          SELECT y.c AS b FROM y\n        ) s\n        ON s.b = r.b\n        WHERE s.b > (SELECT MAX(x.a) FROM x WHERE x.b = s.b)\n        \"\"\"\n        expression = parse_one(sql)\n        for scopes in traverse_scope(expression), list(build_scope(expression).traverse()):\n            self.assertEqual(len(scopes), 7)\n            self.assertEqual(scopes[0].expression.sql(), \"SELECT x.b FROM x\")\n            self.assertEqual(scopes[1].expression.sql(), \"SELECT y.b FROM y\")\n            self.assertEqual(scopes[2].expression.sql(), \"(VALUES (1, 'test')) AS tab(cola, colb)\")\n            self.assertEqual(\n                scopes[3].expression.sql(),\n                \"SELECT cola, colb FROM (VALUES (1, 'test')) AS tab(cola, colb)\",\n            )\n            self.assertEqual(scopes[4].expression.sql(), \"SELECT y.c AS b FROM y\")\n            self.assertEqual(scopes[5].expression.sql(), \"SELECT MAX(x.a) FROM x WHERE x.b = s.b\")\n            self.assertEqual(scopes[6].expression.sql(), parse_one(sql).sql())\n\n            self.assertEqual(set(scopes[6].sources), {\"q\", \"z\", \"r\", \"s\"})\n            self.assertEqual(len(scopes[6].columns), 6)\n            self.assertEqual({c.table for c in scopes[6].columns}, {\"r\", \"s\"})\n            self.assertEqual(scopes[6].source_columns(\"q\"), [])\n            self.assertEqual(len(scopes[6].source_columns(\"r\")), 2)\n            self.assertEqual({c.table for c in scopes[6].source_columns(\"r\")}, {\"r\"})\n\n            self.assertEqual({c.sql() for c in scopes[-1].find_all(exp.Column)}, {\"r.b\", \"s.b\"})\n            self.assertEqual(scopes[-1].find(exp.Column).sql(), \"r.b\")\n            self.assertEqual({c.sql() for c in scopes[0].find_all(exp.Column)}, {\"x.b\"})\n\n        # Check that we can walk in scope from an arbitrary node\n        self.assertEqual(\n            {\n                node.sql()\n                for node in walk_in_scope(expression.find(exp.Where))\n                if isinstance(node, exp.Column)\n            },\n            {\"s.b\"},\n        )\n\n        # Check that parentheses don't introduce a new scope unless an alias is attached\n        sql = \"SELECT * FROM (((SELECT * FROM (t1 JOIN t2) AS t3) JOIN (SELECT * FROM t4)))\"\n        expression = parse_one(sql)\n        for scopes in traverse_scope(expression), list(build_scope(expression).traverse()):\n            self.assertEqual(len(scopes), 4)\n\n            self.assertEqual(scopes[0].expression.sql(), \"t1, t2\")\n            self.assertEqual(set(scopes[0].sources), {\"t1\", \"t2\"})\n\n            self.assertEqual(scopes[1].expression.sql(), \"SELECT * FROM (t1, t2) AS t3\")\n            self.assertEqual(set(scopes[1].sources), {\"t3\"})\n\n            self.assertEqual(scopes[2].expression.sql(), \"SELECT * FROM t4\")\n            self.assertEqual(set(scopes[2].sources), {\"t4\"})\n\n            self.assertEqual(\n                scopes[3].expression.sql(),\n                \"SELECT * FROM (((SELECT * FROM (t1, t2) AS t3), (SELECT * FROM t4)))\",\n            )\n            self.assertEqual(set(scopes[3].sources), {\"\"})\n\n        inner_query = \"SELECT bar FROM baz\"\n        for udtf in (f\"UNNEST(({inner_query}))\", f\"LATERAL ({inner_query})\"):\n            sql = f\"SELECT a FROM foo CROSS JOIN {udtf}\"\n            expression = parse_one(sql)\n\n            for scopes in traverse_scope(expression), list(build_scope(expression).traverse()):\n                self.assertEqual(len(scopes), 3)\n\n                self.assertEqual(scopes[0].expression.sql(), inner_query)\n                self.assertEqual(set(scopes[0].sources), {\"baz\"})\n\n                self.assertEqual(scopes[1].expression.sql(), udtf)\n                self.assertEqual(set(scopes[1].sources), {\"\", \"foo\"})  # foo is a lateral source\n\n                self.assertEqual(scopes[2].expression.sql(), f\"SELECT a FROM foo CROSS JOIN {udtf}\")\n                self.assertEqual(set(scopes[2].sources), {\"\", \"foo\"})\n\n        # Check DML statement scopes\n        sql = (\n            \"UPDATE customers SET total_spent = (SELECT 1 FROM t1) WHERE EXISTS (SELECT 1 FROM t2)\"\n        )\n        self.assertEqual(len(traverse_scope(parse_one(sql))), 3)\n\n        sql = \"UPDATE tbl1 SET col = 1 WHERE EXISTS (SELECT 1 FROM tbl2 WHERE tbl1.id = tbl2.id)\"\n        self.assertEqual(len(traverse_scope(parse_one(sql))), 1)\n\n        sql = \"UPDATE tbl1 SET col = 0\"\n        self.assertEqual(len(traverse_scope(parse_one(sql))), 0)\n\n        sql = \"SELECT * FROM t LEFT JOIN UNNEST(a) AS a1 LEFT JOIN UNNEST(a1.a) AS a2\"\n        scope = build_scope(parse_one(sql, read=\"bigquery\"))\n        self.assertEqual(set(scope.selected_sources), {\"t\", \"a1\", \"a2\"})\n\n    @patch(\"sqlglot.optimizer.scope.logger\")\n    def test_scope_warning(self, logger):\n        self.assertEqual(len(traverse_scope(parse_one(\"WITH q AS (@y) SELECT * FROM q\"))), 1)\n        assert_logger_contains(\n            \"Cannot traverse scope %s with type '%s'\",\n            logger,\n            level=\"warning\",\n        )\n\n    def test_annotate_types(self):\n        for i, (meta, sql, expected) in enumerate(\n            load_sql_fixture_pairs(\"optimizer/annotate_types.sql\"), start=1\n        ):\n            title = meta.get(\"title\") or f\"{i}, {sql}\"\n            dialect = meta.get(\"dialect\")\n            result = parse_and_optimize(annotate_types, sql, dialect, dialect=dialect)\n\n            with self.subTest(title):\n                self.assertEqual(\n                    result.type.sql(dialect),\n                    exp.DataType.build(expected, dialect=dialect).sql(dialect),\n                )\n\n    def test_annotate_funcs(self):\n        test_schema = {\n            \"tbl\": {\n                \"bin_col\": \"BINARY\",\n                \"str_col\": \"STRING\",\n                \"bignum_col\": \"BIGNUMERIC\",\n                \"date_col\": \"DATE\",\n                \"decfloat_col\": \"DECFLOAT\",\n                \"float_col\": \"FLOAT\",\n                \"timestamp_col\": \"TIMESTAMP\",\n                \"double_col\": \"DOUBLE\",\n                \"bigint_col\": \"BIGINT\",\n                \"obj_col\": \"OBJECT\",\n                \"int_col\": \"INT\",\n                \"bool_col\": \"BOOLEAN\",\n                \"bytes_col\": \"BYTES\",\n                \"interval_col\": \"INTERVAL\",\n                \"array_col\": \"ARRAY<STRING>\",\n            }\n        }\n\n        for i, (meta, sql, expected) in enumerate(\n            load_sql_fixture_pairs(\"optimizer/annotate_functions.sql\"), start=1\n        ):\n            title = meta.get(\"title\") or f\"{i}, {sql}\"\n            dialect = meta.get(\"dialect\") or \"\"\n            sql = f\"SELECT {sql} FROM tbl\"\n\n            for dialect in dialect.split(\", \"):\n                with self.subTest(title):\n                    result = parse_and_optimize(\n                        annotate_functions, sql, dialect, schema=test_schema, dialect=dialect\n                    )\n\n                    self.assertEqual(\n                        result.type.sql(dialect),\n                        exp.DataType.build(expected, dialect=dialect).sql(dialect),\n                    )\n\n    def test_cast_type_annotation(self):\n        expression = annotate_types(parse_one(\"CAST('2020-01-01' AS TIMESTAMPTZ(9))\"))\n        self.assertEqual(expression.type.this, exp.DataType.Type.TIMESTAMPTZ)\n        self.assertEqual(expression.this.type.this, exp.DataType.Type.VARCHAR)\n        self.assertEqual(expression.args[\"to\"].type.this, exp.DataType.Type.TIMESTAMPTZ)\n        self.assertEqual(expression.args[\"to\"].expressions[0].this.type.this, exp.DataType.Type.INT)\n\n        expression = annotate_types(parse_one(\"ARRAY(1)::ARRAY<INT>\"))\n        self.assertEqual(expression.type, parse_one(\"ARRAY<INT>\", into=exp.DataType))\n\n        expression = annotate_types(parse_one(\"CAST(x AS INTERVAL)\"))\n        self.assertEqual(expression.type.this, exp.DataType.Type.INTERVAL)\n        self.assertEqual(expression.this.type.this, exp.DataType.Type.UNKNOWN)\n        self.assertEqual(expression.args[\"to\"].type.this, exp.DataType.Type.INTERVAL)\n\n    def test_cache_annotation(self):\n        expression = annotate_types(\n            parse_one(\"CACHE LAZY TABLE x OPTIONS('storageLevel' = 'value') AS SELECT 1\")\n        )\n        self.assertEqual(expression.expression.expressions[0].type.this, exp.DataType.Type.INT)\n\n    def test_binary_annotation(self):\n        expression = annotate_types(parse_one(\"SELECT 0.0 + (2 + 3)\")).expressions[0]\n\n        self.assertEqual(expression.type.this, exp.DataType.Type.DOUBLE)\n        self.assertEqual(expression.left.type.this, exp.DataType.Type.DOUBLE)\n        self.assertEqual(expression.right.type.this, exp.DataType.Type.INT)\n        self.assertEqual(expression.right.this.type.this, exp.DataType.Type.INT)\n        self.assertEqual(expression.right.this.left.type.this, exp.DataType.Type.INT)\n        self.assertEqual(expression.right.this.right.type.this, exp.DataType.Type.INT)\n\n        for numeric_type in (\"BIGINT\", \"DOUBLE\", \"INT\"):\n            query = f\"SELECT '1' + CAST(x AS {numeric_type})\"\n            expression = annotate_types(parse_one(query)).expressions[0]\n            self.assertEqual(expression.type, exp.DataType.build(numeric_type))\n\n    def test_typeddiv_annotation(self):\n        expressions = annotate_types(\n            parse_one(\"SELECT 2 / 3, 2 / 3.0\", dialect=\"presto\")\n        ).expressions\n\n        self.assertEqual(expressions[0].type.this, exp.DataType.Type.BIGINT)\n        self.assertEqual(expressions[1].type.this, exp.DataType.Type.DOUBLE)\n\n        expressions = annotate_types(\n            parse_one(\"SELECT SUM(2 / 3), CAST(2 AS DECIMAL) / 3\", dialect=\"mysql\")\n        ).expressions\n\n        self.assertEqual(expressions[0].type.this, exp.DataType.Type.DOUBLE)\n        self.assertEqual(expressions[0].this.type.this, exp.DataType.Type.DOUBLE)\n        self.assertEqual(expressions[1].type.this, exp.DataType.Type.DECIMAL)\n\n    def test_bracket_annotation(self):\n        expression = annotate_types(parse_one(\"SELECT A[:]\")).expressions[0]\n\n        self.assertEqual(expression.type.this, exp.DataType.Type.UNKNOWN)\n        self.assertEqual(expression.expressions[0].type.this, exp.DataType.Type.UNKNOWN)\n\n        expression = annotate_types(parse_one(\"SELECT ARRAY[1, 2, 3][1]\")).expressions[0]\n        self.assertEqual(expression.this.type.sql(), \"ARRAY<INT>\")\n        self.assertEqual(expression.type.this, exp.DataType.Type.INT)\n\n        expression = annotate_types(parse_one(\"SELECT ARRAY[1, 2, 3][1 : 2]\")).expressions[0]\n        self.assertEqual(expression.this.type.sql(), \"ARRAY<INT>\")\n        self.assertEqual(expression.type.sql(), \"ARRAY<INT>\")\n\n        expression = annotate_types(\n            parse_one(\"SELECT ARRAY[ARRAY[1], ARRAY[2], ARRAY[3]][1][2]\")\n        ).expressions[0]\n        self.assertEqual(expression.this.this.type.sql(), \"ARRAY<ARRAY<INT>>\")\n        self.assertEqual(expression.this.type.sql(), \"ARRAY<INT>\")\n        self.assertEqual(expression.type.this, exp.DataType.Type.INT)\n\n        expression = annotate_types(\n            parse_one(\"SELECT ARRAY[ARRAY[1], ARRAY[2], ARRAY[3]][1:2]\")\n        ).expressions[0]\n        self.assertEqual(expression.type.sql(), \"ARRAY<ARRAY<INT>>\")\n\n        expression = annotate_types(parse_one(\"MAP(1.0, 2, '2', 3.0)['2']\", read=\"spark\"))\n        self.assertEqual(expression.type.this, exp.DataType.Type.DOUBLE)\n\n        expression = annotate_types(parse_one(\"MAP(1.0, 2, x, 3.0)[2]\", read=\"spark\"))\n        self.assertEqual(expression.type.this, exp.DataType.Type.UNKNOWN)\n\n        expression = annotate_types(parse_one(\"MAP(ARRAY(1.0, x), ARRAY(2, 3.0))[x]\"))\n        self.assertEqual(expression.type.this, exp.DataType.Type.DOUBLE)\n\n        expression = annotate_types(\n            parse_one(\"SELECT MAP(1.0, 2, 2, t.y)[2] FROM t\", read=\"spark\"),\n            schema={\"t\": {\"y\": \"int\"}},\n        ).expressions[0]\n        self.assertEqual(expression.type.this, exp.DataType.Type.INT)\n\n    def test_interval_math_annotation(self):\n        schema = {\n            \"x\": {\n                \"a\": \"DATE\",\n                \"b\": \"DATETIME\",\n            }\n        }\n        for sql, expected_type in [\n            (\n                \"SELECT '2023-01-01' + INTERVAL '1' DAY\",\n                exp.DataType.Type.DATE,\n            ),\n            (\n                \"SELECT '2023-01-01' + INTERVAL '1' HOUR\",\n                exp.DataType.Type.DATETIME,\n            ),\n            (\n                \"SELECT '2023-01-01 00:00:01' + INTERVAL '1' HOUR\",\n                exp.DataType.Type.DATETIME,\n            ),\n            (\"SELECT 'nonsense' + INTERVAL '1' DAY\", exp.DataType.Type.UNKNOWN),\n            (\"SELECT x.a + INTERVAL '1' DAY FROM x AS x\", exp.DataType.Type.DATE),\n            (\n                \"SELECT x.a + INTERVAL '1' HOUR FROM x AS x\",\n                exp.DataType.Type.DATETIME,\n            ),\n            (\"SELECT x.b + INTERVAL '1' DAY FROM x AS x\", exp.DataType.Type.DATETIME),\n            (\"SELECT x.b + INTERVAL '1' HOUR FROM x AS x\", exp.DataType.Type.DATETIME),\n            (\n                \"SELECT DATE_ADD('2023-01-01', 1, 'DAY')\",\n                exp.DataType.Type.DATE,\n            ),\n            (\n                \"SELECT DATE_ADD('2023-01-01 00:00:00', 1, 'DAY')\",\n                exp.DataType.Type.DATETIME,\n            ),\n            (\"SELECT DATE_ADD(x.a, 1, 'DAY') FROM x AS x\", exp.DataType.Type.DATE),\n            (\n                \"SELECT DATE_ADD(x.a, 1, 'HOUR') FROM x AS x\",\n                exp.DataType.Type.DATETIME,\n            ),\n            (\"SELECT DATE_ADD(x.b, 1, 'DAY') FROM x AS x\", exp.DataType.Type.DATETIME),\n            (\"SELECT DATE_TRUNC('DAY', x.a) FROM x AS x\", exp.DataType.Type.DATE),\n            (\"SELECT DATE_TRUNC('DAY', x.b) FROM x AS x\", exp.DataType.Type.DATETIME),\n            (\n                \"SELECT DATE_TRUNC('SECOND', x.a) FROM x AS x\",\n                exp.DataType.Type.DATETIME,\n            ),\n            (\n                \"SELECT DATE_TRUNC('DAY', '2023-01-01') FROM x AS x\",\n                exp.DataType.Type.DATE,\n            ),\n            (\n                \"SELECT DATEDIFF('2023-01-01', '2023-01-02', DAY) FROM x AS x\",\n                exp.DataType.Type.INT,\n            ),\n        ]:\n            with self.subTest(sql):\n                expression = annotate_types(parse_one(sql), schema=schema)\n                self.assertEqual(expected_type, expression.expressions[0].type.this)\n                self.assertEqual(sql, expression.sql())\n\n    def test_lateral_annotation(self):\n        expression = optimizer.optimize(\n            parse_one(\"SELECT c FROM (select 1 a) as x LATERAL VIEW EXPLODE (a) AS c\")\n        ).expressions[0]\n        self.assertEqual(expression.type.this, exp.DataType.Type.INT)\n\n    def test_derived_tables_column_annotation(self):\n        schema = {\"x\": {\"cola\": \"INT\"}, \"y\": {\"cola\": \"FLOAT\"}}\n        sql = \"\"\"\n            SELECT a.cola AS cola\n            FROM (\n                SELECT x.cola + y.cola AS cola\n                FROM (\n                    SELECT x.cola AS cola\n                    FROM x AS x\n                ) AS x\n                JOIN (\n                    SELECT y.cola AS cola\n                    FROM y AS y\n                ) AS y\n            ) AS a\n        \"\"\"\n\n        expression = annotate_types(parse_one(sql), schema=schema)\n        self.assertEqual(\n            expression.expressions[0].type.this, exp.DataType.Type.FLOAT\n        )  # a.cola AS cola\n\n        addition_alias = expression.args[\"from_\"].this.this.expressions[0]\n        self.assertEqual(\n            addition_alias.type.this, exp.DataType.Type.FLOAT\n        )  # x.cola + y.cola AS cola\n\n        addition = addition_alias.this\n        self.assertEqual(addition.type.this, exp.DataType.Type.FLOAT)\n        self.assertEqual(addition.this.type.this, exp.DataType.Type.INT)\n        self.assertEqual(addition.expression.type.this, exp.DataType.Type.FLOAT)\n\n    def test_cte_column_annotation(self):\n        schema = {\"x\": {\"cola\": \"CHAR\"}, \"y\": {\"colb\": \"TEXT\", \"colc\": \"BOOLEAN\"}}\n        sql = \"\"\"\n            WITH tbl AS (\n                SELECT x.cola + 'bla' AS cola, y.colb AS colb, y.colc AS colc\n                FROM (\n                    SELECT x.cola AS cola\n                    FROM x AS x\n                ) AS x\n                JOIN (\n                    SELECT y.colb AS colb, y.colc AS colc\n                    FROM y AS y\n                ) AS y\n            )\n            SELECT tbl.cola + tbl.colb + 'foo' AS col\n            FROM tbl AS tbl\n            WHERE tbl.colc = True\n        \"\"\"\n\n        expression = annotate_types(parse_one(sql), schema=schema)\n        self.assertEqual(\n            expression.expressions[0].type.this, exp.DataType.Type.TEXT\n        )  # tbl.cola + tbl.colb + 'foo' AS col\n\n        outer_addition = expression.expressions[0].this  # (tbl.cola + tbl.colb) + 'foo'\n        self.assertEqual(outer_addition.type.this, exp.DataType.Type.TEXT)\n        self.assertEqual(outer_addition.left.type.this, exp.DataType.Type.TEXT)\n        self.assertEqual(outer_addition.right.type.this, exp.DataType.Type.VARCHAR)\n\n        inner_addition = expression.expressions[0].this.left  # tbl.cola + tbl.colb\n        self.assertEqual(inner_addition.left.type.this, exp.DataType.Type.VARCHAR)\n        self.assertEqual(inner_addition.right.type.this, exp.DataType.Type.TEXT)\n\n        # WHERE tbl.colc = True\n        self.assertEqual(expression.args[\"where\"].this.type.this, exp.DataType.Type.BOOLEAN)\n\n        cte_select = expression.args[\"with_\"].expressions[0].this\n        self.assertEqual(\n            cte_select.expressions[0].type.this, exp.DataType.Type.VARCHAR\n        )  # x.cola + 'bla' AS cola\n        self.assertEqual(\n            cte_select.expressions[1].type.this, exp.DataType.Type.TEXT\n        )  # y.colb AS colb\n        self.assertEqual(\n            cte_select.expressions[2].type.this, exp.DataType.Type.BOOLEAN\n        )  # y.colc AS colc\n\n        cte_select_addition = cte_select.expressions[0].this  # x.cola + 'bla'\n        self.assertEqual(cte_select_addition.type.this, exp.DataType.Type.VARCHAR)\n        self.assertEqual(cte_select_addition.left.type.this, exp.DataType.Type.CHAR)\n        self.assertEqual(cte_select_addition.right.type.this, exp.DataType.Type.VARCHAR)\n\n        # Check that x.cola AS cola and y.colb AS colb have types CHAR and TEXT, respectively\n        for d, t in zip(\n            cte_select.find_all(exp.Subquery),\n            [exp.DataType.Type.CHAR, exp.DataType.Type.TEXT],\n        ):\n            self.assertEqual(d.this.expressions[0].this.type.this, t)\n\n    def test_function_annotation(self):\n        schema = {\"x\": {\"cola\": \"VARCHAR\", \"colb\": \"CHAR\"}}\n        sql = (\n            \"SELECT x.cola || TRIM(x.colb) AS col, DATE(x.colb), DATEFROMPARTS(y, m, d) FROM x AS x\"\n        )\n\n        expression = annotate_types(parse_one(sql), schema=schema)\n        concat_expr_alias = expression.expressions[0]\n        self.assertEqual(concat_expr_alias.type.this, exp.DataType.Type.VARCHAR)\n\n        concat_expr = concat_expr_alias.this\n        self.assertEqual(concat_expr.type.this, exp.DataType.Type.VARCHAR)\n        self.assertEqual(concat_expr.left.type.this, exp.DataType.Type.VARCHAR)  # x.cola\n        self.assertEqual(concat_expr.right.type.this, exp.DataType.Type.VARCHAR)  # TRIM(x.colb)\n        self.assertEqual(concat_expr.right.this.type.this, exp.DataType.Type.CHAR)  # x.colb\n\n        date_expr = expression.expressions[1]\n        self.assertEqual(date_expr.type.this, exp.DataType.Type.DATE)\n\n        date_expr = expression.expressions[2]\n        self.assertEqual(date_expr.type.this, exp.DataType.Type.DATE)\n\n        sql = \"SELECT CASE WHEN 1=1 THEN x.cola ELSE x.colb END AS col FROM x AS x\"\n\n        case_expr_alias = annotate_types(parse_one(sql), schema=schema).expressions[0]\n        self.assertEqual(case_expr_alias.type.this, exp.DataType.Type.VARCHAR)\n\n        case_expr = case_expr_alias.this\n        self.assertEqual(case_expr.type.this, exp.DataType.Type.VARCHAR)\n        self.assertEqual(case_expr.args[\"default\"].type.this, exp.DataType.Type.CHAR)\n\n        case_ifs_expr = case_expr.args[\"ifs\"][0]\n        self.assertEqual(case_ifs_expr.type.this, exp.DataType.Type.VARCHAR)\n        self.assertEqual(case_ifs_expr.args[\"true\"].type.this, exp.DataType.Type.VARCHAR)\n\n        timestamp = annotate_types(parse_one(\"TIMESTAMP(x)\"))\n        self.assertEqual(timestamp.type.this, exp.DataType.Type.TIMESTAMP)\n\n        timestamptz = annotate_types(parse_one(\"TIMESTAMP(x)\", read=\"bigquery\"))\n        self.assertEqual(timestamptz.type.this, exp.DataType.Type.TIMESTAMPTZ)\n\n    def test_unknown_annotation(self):\n        schema = {\"x\": {\"cola\": \"VARCHAR\"}}\n        sql = \"SELECT x.cola + SOME_ANONYMOUS_FUNC(x.cola) AS col FROM x AS x\"\n\n        concat_expr_alias = annotate_types(parse_one(sql), schema=schema).expressions[0]\n        self.assertEqual(concat_expr_alias.type.this, exp.DataType.Type.UNKNOWN)\n\n        concat_expr = concat_expr_alias.this\n        self.assertEqual(concat_expr.type.this, exp.DataType.Type.UNKNOWN)\n        self.assertEqual(concat_expr.left.type.this, exp.DataType.Type.VARCHAR)  # x.cola\n        self.assertEqual(\n            concat_expr.right.type.this, exp.DataType.Type.UNKNOWN\n        )  # SOME_ANONYMOUS_FUNC(x.cola)\n        self.assertEqual(\n            concat_expr.right.expressions[0].type.this, exp.DataType.Type.VARCHAR\n        )  # x.cola (arg)\n\n        # Ensures we don't raise if there are unqualified columns\n        annotate_types(parse_one(\"select x from y lateral view explode(y) as x\")).expressions[0]\n\n        # NULL <op> UNKNOWN should yield UNKNOWN\n        self.assertEqual(\n            annotate_types(parse_one(\"SELECT NULL + ANONYMOUS_FUNC()\")).expressions[0].type.this,\n            exp.DataType.Type.UNKNOWN,\n        )\n\n    def test_udf_annotation(self):\n        # Unqualified UDF\n        schema = MappingSchema(\n            schema={\"t\": {\"col\": \"INT\"}},\n            udf_mapping={\"my_func\": \"VARCHAR\"},\n        )\n        expr = annotate_types(parse_one(\"SELECT my_func(col) FROM t\"), schema=schema)\n        self.assertEqual(expr.selects[0].type.this, exp.DataType.Type.VARCHAR)\n\n        # Qualified UDF (2-level)\n        schema = MappingSchema(\n            schema={\"db\": {\"t\": {\"col\": \"INT\"}}},\n            udf_mapping={\"db\": {\"my_func\": \"DOUBLE\"}},\n        )\n        expr = annotate_types(parse_one(\"SELECT db.my_func(col) FROM db.t\"), schema=schema)\n        anon = expr.selects[0].find(exp.Anonymous)\n        self.assertEqual(anon.type.this, exp.DataType.Type.DOUBLE)\n        # Dot parent should also have the type\n        self.assertEqual(expr.selects[0].type.this, exp.DataType.Type.DOUBLE)\n\n        # Qualified UDF (3-level)\n        schema = MappingSchema(\n            schema={\"cat\": {\"db\": {\"t\": {\"col\": \"INT\"}}}},\n            udf_mapping={\"cat\": {\"db\": {\"my_func\": \"BOOLEAN\"}}},\n        )\n        expr = annotate_types(parse_one(\"SELECT cat.db.my_func(col) FROM cat.db.t\"), schema=schema)\n        anon = expr.selects[0].find(exp.Anonymous)\n        self.assertEqual(anon.type.this, exp.DataType.Type.BOOLEAN)\n\n        # Unknown UDF returns UNKNOWN\n        schema = MappingSchema(\n            schema={\"t\": {\"col\": \"INT\"}},\n            udf_mapping={\"known_func\": \"DATE\"},\n        )\n        expr = annotate_types(parse_one(\"SELECT unknown_func(col) FROM t\"), schema=schema)\n        self.assertEqual(expr.selects[0].type.this, exp.DataType.Type.UNKNOWN)\n\n        # Test get_udf_type with string input\n        schema = MappingSchema(udf_mapping={\"my_func\": \"INT\"})\n        self.assertEqual(schema.get_udf_type(\"my_func(x)\").this, exp.DataType.Type.INT)\n\n        schema = MappingSchema(udf_mapping={\"db\": {\"my_func\": \"FLOAT\"}})\n        self.assertEqual(schema.get_udf_type(\"db.my_func(x, y)\").this, exp.DataType.Type.FLOAT)\n\n        schema = MappingSchema(udf_mapping={\"cat\": {\"db\": {\"my_func\": \"DATE\"}}})\n        self.assertEqual(\n            schema.get_udf_type(\"cat.db.my_func(a, b, c)\").this, exp.DataType.Type.DATE\n        )\n\n        # Unknown UDF string returns UNKNOWN\n        schema = MappingSchema(udf_mapping={\"known\": \"INT\"})\n        self.assertEqual(schema.get_udf_type(\"unknown(x)\").this, exp.DataType.Type.UNKNOWN)\n\n    def test_predicate_annotation(self):\n        expression = annotate_types(parse_one(\"x BETWEEN a AND b\"))\n        self.assertEqual(expression.type.this, exp.DataType.Type.BOOLEAN)\n\n        expression = annotate_types(parse_one(\"x IN (a, b, c, d)\"))\n        self.assertEqual(expression.type.this, exp.DataType.Type.BOOLEAN)\n\n    def test_aggfunc_annotation(self):\n        schema = {\"x\": {\"cola\": \"SMALLINT\", \"colb\": \"FLOAT\", \"colc\": \"TEXT\", \"cold\": \"DATE\"}}\n\n        tests = {\n            (\"AVG\", \"cola\"): exp.DataType.Type.DOUBLE,\n            (\"SUM\", \"cola\"): exp.DataType.Type.BIGINT,\n            (\"SUM\", \"colb\"): exp.DataType.Type.DOUBLE,\n            (\"MIN\", \"cola\"): exp.DataType.Type.SMALLINT,\n            (\"MIN\", \"colb\"): exp.DataType.Type.FLOAT,\n            (\"MAX\", \"colc\"): exp.DataType.Type.TEXT,\n            (\"MAX\", \"cold\"): exp.DataType.Type.DATE,\n            (\"COUNT\", \"colb\"): exp.DataType.Type.BIGINT,\n            (\"STDDEV\", \"cola\"): exp.DataType.Type.DOUBLE,\n            (\"ABS\", \"cola\"): exp.DataType.Type.SMALLINT,\n            (\"ABS\", \"colb\"): exp.DataType.Type.FLOAT,\n        }\n\n        for (func, col), target_type in tests.items():\n            expression = annotate_types(\n                parse_one(f\"SELECT {func}(x.{col}) AS _col_0 FROM x AS x\"),\n                schema=schema,\n            )\n            self.assertEqual(expression.expressions[0].type.this, target_type)\n\n    def test_concat_annotation(self):\n        expression = annotate_types(parse_one(\"CONCAT('A', 'B')\"))\n        self.assertEqual(expression.type.this, exp.DataType.Type.VARCHAR)\n\n    def test_root_subquery_annotation(self):\n        expression = annotate_types(parse_one(\"(SELECT 1, 2 FROM x) LIMIT 0\"))\n        self.assertIsInstance(expression, exp.Subquery)\n        self.assertEqual(exp.DataType.Type.INT, expression.selects[0].type.this)\n        self.assertEqual(exp.DataType.Type.INT, expression.selects[1].type.this)\n\n    def test_nested_type_annotation(self):\n        schema = {\n            \"order\": {\n                \"customer_id\": \"bigint\",\n                \"item_id\": \"bigint\",\n                \"item_price\": \"numeric\",\n            }\n        }\n        sql = \"\"\"\n            SELECT ARRAY_AGG(DISTINCT order.item_id) FILTER (WHERE order.item_price > 10) AS items,\n            FROM order AS order\n            GROUP BY order.customer_id\n        \"\"\"\n        expression = annotate_types(parse_one(sql), schema=schema)\n\n        self.assertEqual(exp.DataType.Type.ARRAY, expression.selects[0].type.this)\n        self.assertEqual(expression.selects[0].type.sql(), \"ARRAY<BIGINT>\")\n\n        expression = annotate_types(\n            parse_one(\"SELECT ARRAY_CAT(ARRAY[1,2,3], ARRAY[4,5])\", read=\"postgres\")\n        )\n        self.assertEqual(exp.DataType.Type.ARRAY, expression.selects[0].type.this)\n        self.assertEqual(expression.selects[0].type.sql(), \"ARRAY<INT>\")\n\n        schema = MappingSchema({\"t\": {\"c\": \"STRUCT<`f` STRING>\"}}, dialect=\"bigquery\")\n        expression = annotate_types(parse_one(\"SELECT t.c, [t.c] FROM t\"), schema=schema)\n\n        self.assertEqual(expression.selects[0].type.sql(dialect=\"bigquery\"), \"STRUCT<`f` STRING>\")\n        self.assertEqual(\n            expression.selects[1].type.sql(dialect=\"bigquery\"),\n            \"ARRAY<STRUCT<`f` STRING>>\",\n        )\n\n        expression = annotate_types(\n            parse_one(\"SELECT unnest(t.x) FROM t AS t\", dialect=\"postgres\"),\n            schema={\"t\": {\"x\": \"array<int>\"}},\n        )\n        self.assertTrue(expression.selects[0].is_type(\"int\"))\n\n    def test_type_annotation_cache(self):\n        sql = \"SELECT 1 + 1\"\n        expression = annotate_types(parse_one(sql))\n\n        self.assertEqual(exp.DataType.Type.INT, expression.selects[0].type.this)\n\n        expression.selects[0].this.replace(parse_one(\"1.2\"))\n        expression = annotate_types(expression)\n\n        self.assertEqual(exp.DataType.Type.DOUBLE, expression.selects[0].type.this)\n\n    def test_user_defined_type_annotation(self):\n        schema = MappingSchema({\"t\": {\"x\": \"int\"}}, dialect=\"postgres\")\n        expression = annotate_types(parse_one(\"SELECT CAST(x AS IPADDRESS) FROM t\"), schema=schema)\n\n        self.assertEqual(exp.DataType.Type.USERDEFINED, expression.selects[0].type.this)\n        self.assertEqual(expression.selects[0].type.sql(dialect=\"postgres\"), \"IPADDRESS\")\n\n    def test_unnest_annotation(self):\n        expression = annotate_types(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                SELECT a, a.b, a.b.c FROM x, UNNEST(x.a) AS a\n                \"\"\",\n                    read=\"bigquery\",\n                )\n            ),\n            schema={\"x\": {\"a\": \"ARRAY<STRUCT<b STRUCT<c int>>>\"}},\n        )\n        self.assertEqual(expression.selects[0].type, exp.DataType.build(\"STRUCT<b STRUCT<c int>>\"))\n        self.assertEqual(expression.selects[1].type, exp.DataType.build(\"STRUCT<c int>\"))\n        self.assertEqual(expression.selects[2].type, exp.DataType.build(\"int\"))\n\n        self.assertEqual(\n            annotate_types(\n                optimizer.qualify.qualify(\n                    parse_one(\n                        \"SELECT x FROM UNNEST(GENERATE_DATE_ARRAY('2021-01-01', current_date(), interval 1 day)) AS x\"\n                    )\n                )\n            )\n            .selects[0]\n            .type,\n            exp.DataType.build(\"date\"),\n        )\n\n        self.assertEqual(\n            annotate_types(\n                optimizer.qualify.qualify(\n                    parse_one(\n                        \"SELECT x FROM UNNEST(GENERATE_TIMESTAMP_ARRAY('2016-10-05 00:00:00', '2016-10-06 02:00:00', interval 1 day)) AS x\"\n                    )\n                )\n            )\n            .selects[0]\n            .type,\n            exp.DataType.build(\"timestamp\"),\n        )\n\n    def test_unnest_struct_field_annotation(self):\n        \"\"\"Test that UNNEST of struct array without column aliases exposes struct fields with proper types\"\"\"\n        expression = annotate_types(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                    WITH data AS (\n                      SELECT [STRUCT('Bob' AS first_name, 'Smith' AS last_name)] AS users\n                    )\n                    SELECT first_name, last_name\n                    FROM data, UNNEST(users)\n                    \"\"\",\n                    dialect=\"bigquery\",\n                ),\n                dialect=\"bigquery\",\n            ),\n            dialect=\"bigquery\",\n        )\n        self.assertEqual(\n            expression.selects[0].type, exp.DataType.build(\"VARCHAR\", dialect=\"bigquery\")\n        )\n        self.assertEqual(\n            expression.selects[1].type, exp.DataType.build(\"VARCHAR\", dialect=\"bigquery\")\n        )\n\n        expression = annotate_types(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                    SELECT person\n                    FROM UNNEST([STRUCT('Charlie' AS name, 40 AS age)]) AS person\n                    \"\"\",\n                    dialect=\"bigquery\",\n                ),\n                dialect=\"bigquery\",\n            ),\n            dialect=\"bigquery\",\n        )\n        select_type = expression.selects[0].type\n        self.assertTrue(select_type.is_type(exp.DataType.Type.STRUCT))\n        self.assertEqual(len(select_type.expressions), 2)\n        fields = {col_def.name: col_def.kind for col_def in select_type.expressions}\n        self.assertEqual(fields.get(\"name\"), exp.DataType.build(\"VARCHAR\", dialect=\"bigquery\"))\n        self.assertEqual(fields.get(\"age\"), exp.DataType.build(\"INT\", dialect=\"bigquery\"))\n\n        expression = annotate_types(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                    WITH data AS (\n                      SELECT [STRUCT('Bob' AS first_name, 'Smith' AS last_name)] AS users\n                    )\n                    SELECT first_name, last_name\n                    FROM data, UNNEST(users) AS p\n                    \"\"\",\n                    dialect=\"bigquery\",\n                ),\n                dialect=\"bigquery\",\n            ),\n            dialect=\"bigquery\",\n        )\n        self.assertEqual(\n            expression.selects[0].type, exp.DataType.build(\"VARCHAR\", dialect=\"bigquery\")\n        )\n        self.assertEqual(\n            expression.selects[1].type, exp.DataType.build(\"VARCHAR\", dialect=\"bigquery\")\n        )\n\n        expression = annotate_types(\n            optimizer.qualify.qualify(\n                parse_one(\n                    \"\"\"\n                    SELECT name\n                    FROM UNNEST([STRUCT('Charlie' AS name, 40 AS age)]) AS person\n                    \"\"\",\n                    dialect=\"bigquery\",\n                ),\n                dialect=\"bigquery\",\n            ),\n            dialect=\"bigquery\",\n        )\n        select_type = expression.selects[0].type\n        self.assertTrue(select_type.is_type(exp.DataType.build(\"VARCHAR\", dialect=\"bigquery\")))\n\n    def test_map_annotation(self):\n        # ToMap annotation\n        expression = annotate_types(parse_one(\"SELECT MAP {'x': 1}\", read=\"duckdb\"))\n        self.assertEqual(expression.selects[0].type, exp.DataType.build(\"MAP(VARCHAR, INT)\"))\n\n        # Map annotation\n        expression = annotate_types(\n            parse_one(\"SELECT MAP(['key1', 'key2', 'key3'], [10, 20, 30])\", read=\"duckdb\")\n        )\n        self.assertEqual(expression.selects[0].type, exp.DataType.build(\"MAP(VARCHAR, INT)\"))\n\n        # VarMap annotation\n        expression = annotate_types(parse_one(\"SELECT MAP('a', 'b')\", read=\"spark\"))\n        self.assertEqual(expression.selects[0].type, exp.DataType.build(\"MAP(VARCHAR, VARCHAR)\"))\n\n    def test_union_annotation(self):\n        for left, right, expected_type in (\n            (\"SELECT 1::INT AS c\", \"SELECT 2::BIGINT AS c\", \"BIGINT\"),\n            (\"SELECT 1::INT AS c\", \"SELECT 2::BIGDECIMAL AS c\", \"BIGDECIMAL\"),\n            (\"SELECT 1 AS c\", \"SELECT NULL AS c\", \"INT\"),\n            (\"SELECT FOO() AS c\", \"SELECT 1 AS c\", \"UNKNOWN\"),\n            (\"SELECT FOO() AS c\", \"SELECT BAR() AS c\", \"UNKNOWN\"),\n        ):\n            with self.subTest(f\"left: {left}, right: {right}, expected: {expected_type}\"):\n                lr = annotate_types(parse_one(f\"SELECT t.c FROM ({left} UNION ALL {right}) t(c)\"))\n                rl = annotate_types(parse_one(f\"SELECT t.c FROM ({right} UNION ALL {left}) t(c)\"))\n                assert lr.selects[0].type == rl.selects[0].type == exp.DataType.build(expected_type)\n\n        union_by_name = annotate_types(\n            parse_one(\n                \"SELECT t.a, t.d FROM (SELECT 1 a, 3 d, UNION ALL BY NAME SELECT 7.0 d, 8::BIGINT a) AS t(a, d)\"\n            )\n        )\n        self.assertEqual(union_by_name.selects[0].type.this, exp.DataType.Type.BIGINT)\n        self.assertEqual(union_by_name.selects[1].type.this, exp.DataType.Type.DOUBLE)\n\n        # Test chained UNIONs\n        sql = \"\"\"\n            WITH t AS\n            (\n                SELECT NULL AS col\n                UNION\n                SELECT NULL AS col\n                UNION\n                SELECT 'a' AS col\n                UNION\n                SELECT NULL AS col\n                UNION\n                SELECT NULL AS col\n            )\n            SELECT col FROM t;\n        \"\"\"\n        self.assertEqual(optimizer.optimize(sql).selects[0].type.this, exp.DataType.Type.VARCHAR)\n\n        # Test UNIONs with nested subqueries\n        sql = \"\"\"\n            WITH t AS\n            (\n                SELECT NULL AS col\n                UNION\n                (SELECT NULL AS col UNION ALL SELECT 'a' AS col)\n            )\n            SELECT col FROM t;\n        \"\"\"\n        self.assertEqual(optimizer.optimize(sql).selects[0].type.this, exp.DataType.Type.VARCHAR)\n\n        sql = \"\"\"\n            WITH t AS\n            (\n                (SELECT NULL AS col UNION ALL SELECT 'a' AS col)\n                UNION\n                SELECT NULL AS col\n            )\n            SELECT col FROM t;\n        \"\"\"\n        self.assertEqual(optimizer.optimize(sql).selects[0].type.this, exp.DataType.Type.VARCHAR)\n\n        # BigQuery: STRING coerces to temporal types in UNION\n        for left, right, expected_type in (\n            (\"SELECT '2010-01-01' AS c\", \"SELECT DATE '2020-02-02' AS c\", \"DATE\"),\n            (\n                \"SELECT '2010-01-01 00:00:00' AS c\",\n                \"SELECT DATETIME '2020-02-02 00:00:00' AS c\",\n                \"DATETIME\",\n            ),\n            (\"SELECT '00:00:00' AS c\", \"SELECT TIME '00:01:00' AS c\", \"TIME\"),\n            (\n                \"SELECT '2010-01-01 00:00:00' AS c\",\n                \"SELECT TIMESTAMP '2020-02-02 00:00:00' AS c\",\n                \"TIMESTAMP\",\n            ),\n        ):\n            with self.subTest(f\"left: {left}, right: {right}, expected: {expected_type}\"):\n                lr = annotate_types(\n                    parse_one(\n                        f\"SELECT t.c FROM ({left} UNION ALL {right}) t(c)\", dialect=\"bigquery\"\n                    ),\n                    dialect=\"bigquery\",\n                )\n                rl = annotate_types(\n                    parse_one(\n                        f\"SELECT t.c FROM ({right} UNION ALL {left}) t(c)\", dialect=\"bigquery\"\n                    ),\n                    dialect=\"bigquery\",\n                )\n                assert (\n                    lr.selects[0].type\n                    == rl.selects[0].type\n                    == exp.DataType.build(expected_type, dialect=\"bigquery\")\n                )\n\n    def test_udtf_annotation(self):\n        table_udtf = parse_one(\n            \"SELECT * FROM TABLE(GENERATOR(ROWCOUNT => 100000))\",\n            read=\"snowflake\",\n        )\n        self.assertEqual(\n            annotate_types(table_udtf, dialect=\"snowflake\").sql(\"snowflake\"),\n            \"SELECT * FROM TABLE(GENERATOR(ROWCOUNT => 100000))\",\n        )\n\n    def test_recursive_cte(self):\n        query = parse_one(\n            \"\"\"\n            with recursive t(n) AS\n            (\n              select 1\n              union all\n              select n + 1\n              FROM t\n              where n < 3\n            ), y AS (\n              select n\n              FROM t\n              union all\n              select n + 1\n              FROM y\n              where n < 2\n            )\n            select * from y\n            \"\"\"\n        )\n\n        scope_t, scope_y = build_scope(query).cte_scopes\n        self.assertEqual(set(scope_t.cte_sources), {\"t\"})\n        self.assertEqual(set(scope_y.cte_sources), {\"t\", \"y\"})\n\n    def test_schema_with_spaces(self):\n        schema = {\n            \"a\": {\n                \"b c\": \"text\",\n                '\"d e\"': \"text\",\n            }\n        }\n\n        self.assertEqual(\n            optimizer.optimize(parse_one(\"SELECT * FROM a\"), schema=schema),\n            parse_one('SELECT \"a\".\"b c\" AS \"b c\", \"a\".\"d e\" AS \"d e\" FROM \"a\" AS \"a\"'),\n        )\n\n    def test_quotes(self):\n        schema = {\n            \"example\": {\n                '\"source\"': {\n                    \"id\": \"text\",\n                    '\"name\"': \"text\",\n                    '\"payload\"': \"text\",\n                }\n            }\n        }\n\n        expected = parse_one(\n            \"\"\"\n            SELECT\n             \"source\".\"ID\" AS \"ID\",\n             \"source\".\"name\" AS \"name\",\n             \"source\".\"payload\" AS \"payload\"\n            FROM \"EXAMPLE\".\"source\" AS \"source\"\n            \"\"\",\n            read=\"snowflake\",\n        ).sql(pretty=True, dialect=\"snowflake\")\n\n        for func in (optimizer.qualify.qualify, optimizer.optimize):\n            source_query = parse_one('SELECT * FROM example.\"source\" AS \"source\"', read=\"snowflake\")\n            transformed = func(source_query, dialect=\"snowflake\", schema=schema)\n            self.assertEqual(transformed.sql(pretty=True, dialect=\"snowflake\"), expected)\n\n    def test_no_pseudocolumn_expansion(self):\n        schema = {\n            \"a\": {\n                \"a\": \"text\",\n                \"b\": \"text\",\n                \"_PARTITIONDATE\": \"date\",\n                \"_PARTITIONTIME\": \"timestamp\",\n            }\n        }\n\n        self.assertEqual(\n            optimizer.optimize(\n                parse_one(\"SELECT * FROM a\"),\n                schema=MappingSchema(schema, dialect=\"bigquery\"),\n            ),\n            parse_one('SELECT \"a\".\"a\" AS \"a\", \"a\".\"b\" AS \"b\" FROM \"a\" AS \"a\"'),\n        )\n\n    def test_semistructured(self):\n        query = parse_one(\"select a.b:c from d\", read=\"snowflake\")\n        qualified = optimizer.qualify.qualify(query)\n        self.assertEqual(qualified.expressions[0].alias, \"c\")\n\n    def test_gen(self):\n        for func in exp.ALL_FUNCTIONS:\n            self.assertIsInstance(optimizer.simplify.gen(func()), str)\n\n    def test_normalization_distance(self):\n        def gen_expr(depth: int) -> exp.Expr:\n            return parse_one(\" OR \".join(\"a AND b\" for _ in range(depth)))\n\n        self.assertEqual(4, normalization_distance(gen_expr(2), max_=100))\n        self.assertEqual(18, normalization_distance(gen_expr(3), max_=100))\n        self.assertEqual(110, normalization_distance(gen_expr(10), max_=100))\n\n    def test_manually_annotate_snowflake(self):\n        dialect = \"snowflake\"\n        schema = {\n            \"SCHEMA\": {\n                \"TBL\": {\"COL\": \"INT\", \"col2\": \"VARCHAR\"},\n            }\n        }\n        example_query = 'SELECT * FROM \"SCHEMA\".\"TBL\"'\n\n        expression = parse_one(example_query, dialect=dialect)\n        qual = optimizer.qualify.qualify(expression, schema=schema, dialect=dialect)\n        annotated = optimizer.annotate_types.annotate_types(qual, schema=schema, dialect=dialect)\n\n        self.assertTrue(annotated.selects[0].is_type(\"INT\"))\n        self.assertTrue(annotated.selects[1].is_type(\"VARCHAR\"))\n\n    def test_annotate_table_as_struct_bigquery(self):\n        dialect = \"bigquery\"\n        schema = {\"d\": {\"s\": {\"t\": {\"c1\": \"int64\", \"c2\": \"struct<f1 int64, f2 string>\"}}}}\n\n        def _annotate(query: str) -> exp.Expr:\n            expression = parse_one(query, dialect=dialect)\n            qual = optimizer.qualify.qualify(expression, schema=schema, dialect=dialect)\n            return optimizer.annotate_types.annotate_types(qual, schema=schema, dialect=dialect)\n\n        example_query = \"SELECT t FROM d.s.t\"\n        annotated = _annotate(example_query)\n\n        self.assertIsInstance(annotated.selects[0].this, exp.TableColumn)\n        self.assertEqual(\n            annotated.sql(\"bigquery\"), \"SELECT `t` AS `_col_0` FROM `d`.`s`.`t` AS `t`\"\n        )\n        self.assertTrue(\n            annotated.selects[0].is_type(\"STRUCT<c1 BIGINT, c2 STRUCT<f1 BIGINT, f2 TEXT>>\")\n        )\n\n        example_query = \"SELECT subq FROM (SELECT * from d.s.t) subq\"\n        annotated = _annotate(example_query)\n\n        self.assertTrue(\n            annotated.selects[0].is_type(\"STRUCT<c1 BIGINT, c2 STRUCT<f1 BIGINT, f2 TEXT>>\")\n        )\n\n        example_query = \"WITH t AS (SELECT 1 AS c) SELECT t FROM t\"\n        annotated = _annotate(example_query)\n\n        self.assertTrue(annotated.selects[0].is_type(\"STRUCT<c INT>\"))\n\n        example_query = \"WITH t AS (SELECT FOO() AS c) SELECT t FROM t\"\n        annotated = _annotate(example_query)\n\n        self.assertTrue(annotated.selects[0].is_type(\"UNKNOWN\"))\n\n        for query in (\"SELECT 'foo'\", \"(SELECT 'foo')\"):\n            query = f\"SELECT ARRAY({query})\"\n            with self.subTest(f\"Annotating '{query}' in BigQuery\"):\n                self.assertTrue(_annotate(query).selects[0].is_type(\"ARRAY<VARCHAR>\"))\n\n    def test_semi_anti_join(self):\n        # - Do not remove semi/anti join\n        # - Do not remove CTEs/subqueries that participate in anti/semi joins, even though they do not count as selected sources\n        for join_kind in (\"LEFT ANTI\", \"ANTI\", \"SEMI\"):\n            query = f\"WITH x AS (SELECT 1 AS b UNION ALL SELECT 2 AS b) SELECT x.b FROM x {join_kind} JOIN (SELECT 1 AS b) AS sub ON x.b = sub.b\"\n\n            self.assertEqual(\n                optimizer.optimize(query).sql(),\n                f'WITH \"x\" AS (SELECT 1 AS \"b\" UNION ALL SELECT 2 AS \"b\"), \"sub\" AS (SELECT 1 AS \"b\") SELECT \"x\".\"b\" AS \"b\" FROM \"x\" AS \"x\" {join_kind} JOIN \"sub\" AS \"sub\" ON \"sub\".\"b\" = \"x\".\"b\"',\n            )\n\n    def test_qualify_group_by_conflict_bigquery(self):\n        dialect = \"bigquery\"\n        schema = {\"custom_fields\": {\"id\": \"int\", \"col\": \"struct<fld string>\"}}\n\n        query = \"SELECT id, ARRAY_AGG(col) AS custom_fields FROM custom_fields AS custom_fields GROUP BY id HAVING id >= 1\"\n        qual = optimizer.qualify.qualify(\n            parse_one(query, dialect=dialect),\n            schema=schema,\n            dialect=dialect,\n        )\n\n        sql = qual.sql(dialect=dialect)\n        self.assertEqual(\n            sql,\n            \"SELECT `custom_fields`.`id` AS `id`, ARRAY_AGG(`custom_fields`.`col`) AS `custom_fields` FROM `custom_fields` AS `custom_fields` GROUP BY `id` HAVING `id` >= 1\",\n        )\n\n    def test_struct_annotation_bigquery(self):\n        sql = \"\"\"\n        WITH t1 AS (SELECT 'foo' AS c),\n             t2 AS (SELECT ARRAY_AGG(STRUCT(c)) AS arr FROM t1)\n        SELECT arr[0].c FROM t2\n        \"\"\"\n\n        query = parse_one(sql, dialect=\"bigquery\")\n        qualified = optimizer.qualify.qualify(query, dialect=\"bigquery\")\n        annotated = optimizer.annotate_types.annotate_types(qualified, dialect=\"bigquery\")\n\n        assert annotated.selects[0].type == exp.DataType.build(\"VARCHAR\")\n\n    def test_bigquery_unnest_alias_shadowing(self):\n        \"\"\"Test that BigQuery UNNEST table alias shadows column names from other tables.\"\"\"\n        sql = \"\"\"\n            SELECT timeline_date\n            FROM UNNEST(GENERATE_DATE_ARRAY('2020-01-01', '2020-01-03')) AS timeline_date\n            LEFT JOIN production_tier ON production_tier.timeline_date = timeline_date\n        \"\"\"\n        schema = {\"production_tier\": {\"timeline_date\": \"DATE\", \"id\": \"INT\"}}\n\n        result = optimizer.qualify.qualify(\n            parse_one(sql, dialect=\"bigquery\"),\n            schema=schema,\n            dialect=\"bigquery\",\n        )\n\n        result_sql = result.sql(dialect=\"bigquery\")\n        self.assertEqual(\n            result_sql,\n            \"SELECT `timeline_date` AS `timeline_date` \"\n            \"FROM UNNEST(GENERATE_DATE_ARRAY('2020-01-01', '2020-01-03', INTERVAL '1' DAY)) AS `timeline_date` \"\n            \"LEFT JOIN `production_tier` AS `production_tier` \"\n            \"ON `production_tier`.`timeline_date` = `timeline_date`\",\n        )\n\n    def test_struct_field_case_sensitivity_annotation(self):\n        schema = {\"t\": {\"struct_col\": \"STRUCT<fooBar STRING>\"}}\n\n        def _assert_dot_annotation(query: str, dialect: str, expected: exp.DataType.Type):\n            parsed = parse_one(query, dialect=dialect)\n            qualified = optimizer.qualify.qualify(parsed, schema=schema, dialect=dialect)\n            annotated = optimizer.annotate_types.annotate_types(\n                qualified, schema=schema, dialect=dialect\n            )\n            self.assertEqual(annotated.selects[0].type.this, expected)\n\n        # BigQuery is case-insensitive: exact field name match\n        _assert_dot_annotation(\n            \"SELECT struct_col.fooBar FROM t\", \"bigquery\", exp.DataType.Type.TEXT\n        )\n\n        # BigQuery: lower case\n        _assert_dot_annotation(\n            \"SELECT struct_col.foobar FROM t\", \"bigquery\", exp.DataType.Type.TEXT\n        )\n\n        # BigQuery: different case\n        _assert_dot_annotation(\n            \"SELECT struct_col.Foobar FROM t\", \"bigquery\", exp.DataType.Type.TEXT\n        )\n\n        # ClickHouse is case-sensitive: exact field name match\n        _assert_dot_annotation(\n            \"SELECT struct_col.fooBar FROM t\", \"clickhouse\", exp.DataType.Type.TEXT\n        )\n\n        # ClickHouse: lower case\n        _assert_dot_annotation(\n            \"SELECT struct_col.foobar FROM t\", \"clickhouse\", exp.DataType.Type.UNKNOWN\n        )\n\n    def test_annotate_object_construct(self):\n        sql = \"SELECT OBJECT_CONSTRUCT('foo', 'bar', 'a b', 'c d') AS c\"\n\n        query = parse_one(sql, dialect=\"snowflake\")\n        annotated = optimizer.annotate_types.annotate_types(query, dialect=\"snowflake\")\n\n        self.assertEqual(\n            annotated.selects[0].type.sql(\"snowflake\"), 'OBJECT(\"foo\" VARCHAR, \"a b\" VARCHAR)'\n        )\n\n    def test_nonnull_annotation(self):\n        for literal_sql in (\"1\", \"'foo'\", \"2.5\"):\n            with self.subTest(f\"Test NULL annotation for literal: {literal_sql}\"):\n                sql = f\"SELECT {literal_sql}\"\n                query = parse_one(sql)\n                annotated = annotate_types(query)\n                assert annotated.selects[0].meta.get(\"nonnull\") is True\n\n        schema = {\"foo\": {\"id\": \"INT\"}}\n\n        operand_pairs = (\n            (\"1\", \"1\", True),\n            (\"foo.id\", \"foo.id\", None),\n            (\"1\", \"foo.id\", None),\n            (\"foo.id\", \"1\", None),\n        )\n\n        for predicate in (\">\", \"<\", \">=\", \"<=\", \"=\", \"!=\", \"<>\", \"LIKE\", \"NOT LIKE\"):\n            for operand1, operand2, nonnull in operand_pairs:\n                sql_predicate = f\"{operand1} {predicate} {operand2}\"\n                with self.subTest(f\"Test NULL propagation for predicate: {predicate}\"):\n                    sql = f\"SELECT {sql_predicate} FROM foo\"\n                    query = parse_one(sql)\n                    annotated = annotate_types(query, schema=schema)\n                    assert annotated.selects[0].meta.get(\"nonnull\") is nonnull\n\n        for predicate in (\"IS NULL\", \"IS NOT NULL\"):\n            sql_predicate = f\"foo.id {predicate}\"\n            with self.subTest(f\"Test NULL propagation for predicate: {predicate}\"):\n                sql = f\"SELECT {sql_predicate} FROM foo\"\n                query = parse_one(sql)\n                annotated = annotate_types(query, schema=schema)\n                assert annotated.selects[0].meta.get(\"nonnull\") is True\n\n        for connector in (\"AND\", \"OR\"):\n            for predicate in (\">\", \"<\", \">=\", \"<=\", \"=\", \"!=\", \"<>\", \"LIKE\", \"NOT LIKE\"):\n                for operand1, operand2, nonnull in operand_pairs:\n                    sql_predicate = f\"({operand1} {predicate} {operand2})\"\n                    sql_connector = f\"{sql_predicate} {connector} {sql_predicate}\"\n                    with self.subTest(\n                        f\"Test NULL propagation for connector: {connector} with predicates: {predicate}\"\n                    ):\n                        sql = f\"SELECT {sql_connector} FROM foo\"\n                        query = parse_one(sql)\n                        annotated = annotate_types(query, schema=schema)\n                        assert annotated.selects[0].meta.get(\"nonnull\") is nonnull\n\n        for unary in (\"NOT\", \"-\"):\n            for value, nonnull in ((\"1\", True), (\"foo.id\", None)):\n                with self.subTest(f\"Test NULL propagation for unary: {unary} with value: {value}\"):\n                    sql = f\"SELECT {unary} {value} FROM foo\"\n                    query = parse_one(sql)\n                    annotated = annotate_types(query, schema=schema)\n                    assert annotated.selects[0].meta.get(\"nonnull\") is nonnull\n\n        ch_query = parse_one(\"select c1, c2 from t\")\n        ch_schema = {\"t\": {\"c1\": \"Int32\", \"c2\": \"Nullable(Int32)\"}}\n        qualified_query = qualify_columns(ch_query, schema=ch_schema, dialect=\"clickhouse\")\n        annotated = annotate_types(qualified_query, schema=ch_schema, dialect=\"clickhouse\")\n        assert annotated.selects[0].meta.get(\"nonnull\") is True\n        assert annotated.selects[1].meta.get(\"nonnull\") is None\n\n    def test_case_sensitive_json_dot_access(self):\n        schema = {\n            \"t\": {\n                \"col\": \"JSON\",\n                \"struct_col\": \"STRUCT<STRUCT<STRUCT<VARCHAR>>>\",\n            }\n        }\n\n        def _parse_and_optimize(query: str, dialect: str) -> exp.Expr:\n            query = parse_one(query, dialect=dialect)\n            optimized = optimizer.optimize(query, schema=schema, dialect=dialect)\n            return optimized.sql(dialect=dialect)\n\n        # BigQuery\n        for dot_access in (\"col.fOo.BaR.BaZ\", \"t.col.fOo.BaR.BaZ\"):\n            with self.subTest(f\"Test case sensitive JSON dot access for BigQuery: {dot_access}\"):\n                dot_access_normalized = \"`t`.`col`.`fOo`.`BaR`.`BaZ`\"\n\n                sql = _parse_and_optimize(\n                    f\"SELECT JSON_VALUE({dot_access}, '$') AS col FROM t\", dialect=\"bigquery\"\n                )\n                assert (\n                    sql\n                    == f\"SELECT JSON_VALUE({dot_access_normalized}, '$') AS `col` FROM `t` AS `t`\"\n                )\n\n                sql = _parse_and_optimize(f\"SELECT {dot_access} AS col FROM t\", dialect=\"bigquery\")\n                assert sql == f\"SELECT {dot_access_normalized} AS `col` FROM `t` AS `t`\"\n\n        # BigQuery: STRUCT field accesses are still normalized\n        sql = _parse_and_optimize(\n            \"SELECT struct_col.FlD1.flD2.FLD3 AS col FROM t\", dialect=\"bigquery\"\n        )\n        assert sql == \"SELECT `t`.`struct_col`.`fld1`.`fld2`.`fld3` AS `col` FROM `t` AS `t`\"\n\n        # Databricks\n        sql = _parse_and_optimize(\"SELECT col:A.a, col:a.A FROM t\", dialect=\"databricks\")\n        assert sql == \"SELECT `t`.`col`:A.a AS `a`, `t`.`col`:a.A AS `A` FROM `t` AS `t`\"\n\n        # Clickhouse\n        sql = _parse_and_optimize(\"SELECT col.A.a, col.a.A FROM t\", dialect=\"clickhouse\")\n        assert sql == 'SELECT \"t\".\"col\".\"A\".\"a\" AS \"a\", \"t\".\"col\".\"a\".\"A\" AS \"A\" FROM \"t\" AS \"t\"'\n\n        # DuckDB\n        sql = _parse_and_optimize(\"SELECT col.A.a, col.a.A FROM t\", dialect=\"duckdb\")\n        assert sql == 'SELECT \"t\".\"col\".\"A\".\"a\" AS \"a\", \"t\".\"col\".\"a\".\"A\" AS \"a\" FROM \"t\" AS \"t\"'\n\n        # Snowflake\n        sql = _parse_and_optimize(\"SELECT col:A.a, col:a.A FROM t\", dialect=\"snowflake\")\n        assert (\n            sql\n            == '''SELECT GET_PATH(\"T\".\"COL\", 'A.a') AS \"a\", GET_PATH(\"T\".\"COL\", 'a.A') AS \"A\" FROM \"T\" AS \"T\"'''\n        )\n\n        query = parse_one(\n            \"SELECT JSON_VALUE(item.id) FROM UNNEST(JSON_QUERY_ARRAY(PARSE_JSON('[{\\\"id\\\": 1}]'))) AS item\",\n            dialect=\"bigquery\",\n        )\n        optimized = optimizer.optimize(query, dialect=\"bigquery\")\n        for i in optimized.find_all(exp.Identifier):\n            self.assertNotIsInstance(i.this, exp.Identifier)\n        assert (\n            optimized.sql(\"bigquery\")\n            == \"SELECT JSON_VALUE(`item`.`id`, '$') AS `_col_0` FROM UNNEST(JSON_QUERY_ARRAY(PARSE_JSON('[{\\\"id\\\": 1}]'), '$')) AS `item`\"\n        )\n\n    def test_deep_ast_type_annotation(self):\n        union_sql = \"SELECT 1 UNION ALL \" * 2000 + \"SELECT 1\"\n        annotated = annotate_types(parse_one(union_sql))\n        self.assertEqual(annotated.sql(), union_sql)\n        self.assertEqual(annotated.selects[0].type.this, exp.DataType.Type.INT)\n\n        binary_sql = \"SELECT \" + \"t.a + \" * 2000 + \"t.a FROM t\"\n        annotated = annotate_types(parse_one(binary_sql), schema={\"t\": {\"a\": \"INT\"}})\n        self.assertEqual(annotated.sql(), binary_sql)\n        self.assertEqual(annotated.selects[0].type.this, exp.DataType.Type.INT)\n\n    def test_null_coerce_annotation(self):\n        null_sql = \"SELECT t.foo FROM (SELECT CAST(1 AS BIGDECIMAL) AS foo UNION ALL SELECT NULL AS foo) AS t\"\n        annotated = parse_and_optimize(annotate_types, null_sql, \"bigquery\", dialect=\"bigquery\")\n\n        self.assertEqual(annotated.sql(), null_sql)\n        self.assertEqual(annotated.selects[0].type.this, exp.DataType.Type.BIGDECIMAL)\n\n        null_sql = \"SELECT t.foo FROM (SELECT NULL AS foo UNION ALL SELECT CAST(1 AS BIGDECIMAL) AS foo) AS t\"\n        annotated = parse_and_optimize(annotate_types, null_sql, \"bigquery\", dialect=\"bigquery\")\n        self.assertEqual(annotated.sql(), null_sql)\n        self.assertEqual(annotated.selects[0].type.this, exp.DataType.Type.BIGDECIMAL)\n\n    def test_correlated_subqueries_annotation(self):\n        correlated_sql = \"SELECT (SELECT col) FROM t\"\n\n        query = parse_one(correlated_sql, dialect=\"bigquery\")\n        qualified = optimizer.qualify.qualify(\n            query, dialect=\"bigquery\", schema={\"t\": {\"col\": \"BIGNUMERIC\"}}\n        )\n        annotated = optimizer.annotate_types.annotate_types(\n            qualified, dialect=\"bigquery\", schema={\"t\": {\"col\": \"BIGNUMERIC\"}}\n        )\n\n        self.assertEqual(\n            annotated.sql(\"bigquery\"),\n            \"SELECT (SELECT `t`.`col` AS `col`) AS `_col_0` FROM `t` AS `t`\",\n        )\n        assert annotated.selects[0].type == exp.DataType.build(\"BIGNUMERIC\", dialect=\"bigquery\")\n\n        correlated_sql = \"\"\"\n        SELECT\n        (\n            SELECT\n            MAX(u_x)\n            FROM UNNEST([1, d_x]) AS u_x\n            WHERE\n            u_x < d_z\n        ) AS c_i\n        FROM (\n        SELECT\n            CAST(20 AS BIGNUMERIC) AS d_x,\n            30 AS d_z\n        ) AS d_t\n        \"\"\"\n\n        query = parse_one(correlated_sql, dialect=\"bigquery\")\n        qualified = optimizer.qualify.qualify(\n            query, dialect=\"bigquery\", schema={\"d_t\": {\"d_x\": \"STRING\"}}\n        )\n        annotated = optimizer.annotate_types.annotate_types(\n            qualified, dialect=\"bigquery\", schema={\"d_t\": {\"d_x\": \"STRING\"}}\n        )\n\n        self.assertEqual(\n            annotated.sql(\"bigquery\"),\n            \"SELECT (SELECT MAX(`u_x`) AS `_col_0` FROM UNNEST([1, `d_t`.`d_x`]) AS `u_x` WHERE `u_x` < `d_t`.`d_z`) AS `c_i` FROM (SELECT CAST(20 AS BIGNUMERIC) AS `d_x`, 30 AS `d_z`) AS `d_t`\",\n        )\n        assert annotated.selects[0].type == exp.DataType.build(\"BIGNUMERIC\", dialect=\"bigquery\")\n\n        correlated_sql = \"SELECT (SELECT col FROM t) as u FROM (SELECT 1 AS col) AS t\"\n        query = parse_one(correlated_sql)\n        qualified = optimizer.qualify.qualify(query, schema={\"t\": {\"col\": \"TEXT\"}})\n        annotated = optimizer.annotate_types.annotate_types(\n            qualified, schema={\"t\": {\"col\": \"TEXT\"}}\n        )\n\n        self.assertEqual(\n            annotated.sql(),\n            'SELECT (SELECT \"t\".\"col\" AS \"col\" FROM \"t\" AS \"t\") AS \"u\" FROM (SELECT 1 AS \"col\") AS \"t\"',\n        )\n        assert annotated.selects[0].type == exp.DataType.build(\"TEXT\")\n"
  },
  {
    "path": "tests/test_parser.py",
    "content": "import time\nimport unittest\nfrom unittest.mock import patch\n\nfrom sqlglot import Parser, exp, parse, parse_one\nfrom sqlglot.errors import ErrorLevel, ParseError\nfrom sqlglot.parser import logger as parser_logger\nfrom tests.helpers import assert_logger_contains\n\n\nclass TestParser(unittest.TestCase):\n    def test_parse_empty(self):\n        with self.assertRaises(ParseError):\n            parse_one(\"\")\n\n    def test_parse_into(self):\n        self.assertIsInstance(parse_one(\"(1)\", into=exp.Tuple), exp.Tuple)\n        self.assertIsInstance(parse_one(\"(1,)\", into=exp.Tuple), exp.Tuple)\n        self.assertIsInstance(parse_one(\"(x=1)\", into=exp.Tuple), exp.Tuple)\n\n        self.assertIsInstance(parse_one(\"select * from t\", into=exp.Select), exp.Select)\n        self.assertIsInstance(parse_one(\"select * from t limit 5\", into=exp.Select), exp.Select)\n        self.assertIsInstance(parse_one(\"left join foo\", into=exp.Join), exp.Join)\n        self.assertIsInstance(parse_one(\"int\", into=exp.DataType), exp.DataType)\n        self.assertIsInstance(parse_one(\"array<int>\", into=exp.DataType), exp.DataType)\n        self.assertIsInstance(parse_one(\"foo\", into=exp.Table), exp.Table)\n        self.assertIsInstance(\n            parse_one(\n                \"WHEN MATCHED THEN UPDATE SET target.salary = COALESCE(source.salary, target.salary)\",\n                into=exp.Whens,\n            ),\n            exp.Whens,\n        )\n\n        with self.assertRaises(ParseError) as ctx:\n            parse_one(\"SELECT * FROM tbl\", into=exp.Table)\n\n        self.assertEqual(\n            str(ctx.exception),\n            \"Failed to parse 'SELECT * FROM tbl' into <class 'sqlglot.expressions.query.Table'>\",\n        )\n\n        self.assertIsInstance(parse_one(\"foo INT NOT NULL\", into=exp.ColumnDef), exp.ColumnDef)\n\n    def test_parse_into_error(self):\n        expected_message = (\n            \"Failed to parse 'SELECT 1;' into [<class 'sqlglot.expressions.query.From'>]\"\n        )\n        expected_errors = [\n            {\n                \"description\": \"Invalid expression / Unexpected token\",\n                \"line\": 1,\n                \"col\": 6,\n                \"start_context\": \"\",\n                \"highlight\": \"SELECT\",\n                \"end_context\": \" 1;\",\n                \"into_expression\": exp.From,\n            }\n        ]\n        with self.assertRaises(ParseError) as ctx:\n            parse_one(\"SELECT 1;\", read=\"sqlite\", into=[exp.From])\n\n        self.assertEqual(str(ctx.exception), expected_message)\n        self.assertEqual(ctx.exception.errors, expected_errors)\n\n    def test_parse_into_errors(self):\n        expected_message = \"Failed to parse 'SELECT 1;' into [<class 'sqlglot.expressions.query.From'>, <class 'sqlglot.expressions.query.Join'>]\"\n        expected_errors = [\n            {\n                \"description\": \"Invalid expression / Unexpected token\",\n                \"line\": 1,\n                \"col\": 6,\n                \"start_context\": \"\",\n                \"highlight\": \"SELECT\",\n                \"end_context\": \" 1;\",\n                \"into_expression\": exp.From,\n            },\n            {\n                \"description\": \"Invalid expression / Unexpected token\",\n                \"line\": 1,\n                \"col\": 6,\n                \"start_context\": \"\",\n                \"highlight\": \"SELECT\",\n                \"end_context\": \" 1;\",\n                \"into_expression\": exp.Join,\n            },\n        ]\n        with self.assertRaises(ParseError) as ctx:\n            parse_one(\"SELECT 1;\", \"sqlite\", into=[exp.From, exp.Join])\n\n        self.assertEqual(str(ctx.exception), expected_message)\n        self.assertEqual(ctx.exception.errors, expected_errors)\n\n    def test_column(self):\n        columns = parse_one(\"select a, ARRAY[1] b, case when 1 then 1 end\").find_all(exp.Column)\n        assert len(list(columns)) == 1\n\n        self.assertIsNotNone(parse_one(\"date\").find(exp.Column))\n\n    def test_tuple(self):\n        parse_one(\"(a,)\").assert_is(exp.Tuple)\n\n    def test_structs(self):\n        cast = parse_one(\"cast(x as struct<int>)\")\n        self.assertIsInstance(cast.to.expressions[0], exp.DataType)\n        self.assertEqual(cast.sql(), \"CAST(x AS STRUCT<INT>)\")\n\n        cast = parse_one(\"cast(x as struct<varchar(10)>)\")\n        self.assertIsInstance(cast.to.expressions[0], exp.DataType)\n        self.assertEqual(cast.sql(), \"CAST(x AS STRUCT<VARCHAR(10)>)\")\n\n    def test_float(self):\n        self.assertEqual(parse_one(\".2\"), parse_one(\"0.2\"))\n\n    def test_unnest(self):\n        unnest_sql = \"UNNEST(foo)\"\n        expr = parse_one(unnest_sql)\n        self.assertIsInstance(expr, exp.Unnest)\n        self.assertIsInstance(expr.expressions, list)\n        self.assertEqual(expr.sql(), unnest_sql)\n\n    def test_unnest_projection(self):\n        expr = parse_one(\"SELECT foo IN UNNEST(bla) AS bar\")\n        self.assertIsInstance(expr.selects[0], exp.Alias)\n        self.assertEqual(expr.selects[0].output_name, \"bar\")\n        self.assertIsNotNone(parse_one(\"select unnest(x)\").find(exp.Unnest))\n\n    def test_unary_plus(self):\n        self.assertEqual(parse_one(\"+15\"), exp.Literal.number(15))\n\n    def test_table(self):\n        tables = [t.sql() for t in parse_one(\"select * from a, b.c, .d\").find_all(exp.Table)]\n        self.assertEqual(set(tables), {\"a\", \"b.c\", \"d\"})\n\n    def test_union(self):\n        self.assertIsInstance(parse_one(\"SELECT * FROM (SELECT 1) UNION SELECT 2\"), exp.Union)\n        self.assertIsInstance(\n            parse_one(\"SELECT x FROM y HAVING x > (SELECT 1) UNION SELECT 2\"), exp.Union\n        )\n\n        # Check that modifiers are attached to the topmost union node and not the rightmost query\n        single_union = \"SELECT x FROM t1 UNION ALL SELECT x FROM t2 LIMIT 1\"\n        expr = parse_one(single_union)\n        limit = expr.assert_is(exp.Union).args.get(\"limit\")\n        self.assertIsInstance(limit, exp.Limit)\n        self.assertEqual(expr.sql(), single_union)\n\n        two_unions = (\n            \"SELECT x FROM t1 UNION ALL SELECT x FROM t2 UNION ALL SELECT x FROM t3 LIMIT 1\"\n        )\n        expr = parse_one(two_unions)\n        limit = expr.assert_is(exp.Union).args.get(\"limit\")\n        self.assertIsInstance(limit, exp.Limit)\n        self.assertEqual(expr.sql(), two_unions)\n\n        expr = parse_one(single_union, read=\"clickhouse\")\n        self.assertIsNone(expr.args.get(\"limit\"))\n        self.assertEqual(expr.sql(dialect=\"clickhouse\"), single_union)\n\n    def test_select(self):\n        self.assertIsNotNone(parse_one(\"select 1 natural\"))\n        self.assertIsNotNone(parse_one(\"select * from (select 1) x order by x.y\").args[\"order\"])\n        self.assertIsNotNone(\n            parse_one(\"select * from x where a = (select 1) order by x.y\").args[\"order\"]\n        )\n        self.assertEqual(len(parse_one(\"select * from (select 1) x cross join y\").args[\"joins\"]), 1)\n        self.assertEqual(\n            parse_one(\"\"\"SELECT * FROM x CROSS JOIN y, z LATERAL VIEW EXPLODE(y)\"\"\").sql(),\n            \"\"\"SELECT * FROM x CROSS JOIN y, z LATERAL VIEW EXPLODE(y)\"\"\",\n        )\n        self.assertIsNone(\n            parse_one(\"create table a as (select b from c) index\").find(exp.TableAlias)\n        )\n\n    def test_command(self):\n        with self.assertLogs(parser_logger) as cm:\n            expressions = parse(\"SET x = 1; ADD JAR s3://a; SELECT 1\", read=\"hive\")\n            self.assertEqual(len(expressions), 3)\n            self.assertEqual(expressions[0].sql(), \"SET x = 1\")\n            self.assertEqual(expressions[1].sql(), \"ADD JAR s3://a\")\n            self.assertEqual(expressions[2].sql(), \"SELECT 1\")\n\n        assert \"'ADD JAR s3://a'\" in cm.output[0]\n\n    def test_lambda_struct(self):\n        expression = parse_one(\"FILTER(a.b, x -> x.id = id)\")\n        lambda_expr = expression.expression\n\n        self.assertIsInstance(lambda_expr.this.this, exp.Dot)\n        self.assertEqual(lambda_expr.sql(), \"x -> x.id = id\")\n\n        self.assertIsNone(parse_one(\"FILTER([], x -> x)\").find(exp.Column))\n\n    def test_transactions(self):\n        expression = parse_one(\"BEGIN TRANSACTION\")\n        self.assertIsNone(expression.this)\n        self.assertEqual(expression.args[\"modes\"], [])\n        self.assertEqual(expression.sql(), \"BEGIN\")\n\n        expression = parse_one(\"START TRANSACTION\", read=\"mysql\")\n        self.assertIsNone(expression.this)\n        self.assertEqual(expression.args[\"modes\"], [])\n        self.assertEqual(expression.sql(), \"BEGIN\")\n\n        expression = parse_one(\"BEGIN DEFERRED TRANSACTION\")\n        self.assertEqual(expression.this, \"DEFERRED\")\n        self.assertEqual(expression.args[\"modes\"], [])\n        self.assertEqual(expression.sql(), \"BEGIN\")\n\n        expression = parse_one(\n            \"START TRANSACTION READ WRITE, ISOLATION LEVEL SERIALIZABLE\", read=\"presto\"\n        )\n        self.assertIsNone(expression.this)\n        self.assertEqual(expression.args[\"modes\"][0], \"READ WRITE\")\n        self.assertEqual(expression.args[\"modes\"][1], \"ISOLATION LEVEL SERIALIZABLE\")\n        self.assertEqual(expression.sql(), \"BEGIN READ WRITE, ISOLATION LEVEL SERIALIZABLE\")\n\n        expression = parse_one(\"BEGIN\", read=\"bigquery\")\n        self.assertNotIsInstance(expression, exp.Transaction)\n        self.assertIsNone(expression.expression)\n        self.assertEqual(expression.sql(), \"BEGIN\")\n\n    def test_identify(self):\n        expression = parse_one(\n            \"\"\"\n            SELECT a, \"b\", c AS c, d AS \"D\", e AS \"y|z'\"\n            FROM y.\"z\"\n        \"\"\"\n        )\n\n        assert expression.expressions[0].name == \"a\"\n        assert expression.expressions[1].name == \"b\"\n        assert expression.expressions[2].alias == \"c\"\n        assert expression.expressions[3].alias == \"D\"\n        assert expression.expressions[4].alias == \"y|z'\"\n        table = expression.args[\"from_\"].this\n        assert table.name == \"z\"\n        assert table.args[\"db\"].name == \"y\"\n\n    def test_multi(self):\n        expressions = parse(\n            \"\"\"\n            SELECT * FROM a; SELECT * FROM b;\n        \"\"\"\n        )\n\n        assert len(expressions) == 2\n        assert expressions[0].args[\"from_\"].name == \"a\"\n        assert expressions[1].args[\"from_\"].name == \"b\"\n\n        expressions = parse(\"SELECT 1; ; SELECT 2\")\n\n        assert len(expressions) == 3\n        assert expressions[1] is None\n\n    def test_expression(self):\n        ignore = Parser(error_level=ErrorLevel.IGNORE)\n        self.assertIsInstance(ignore.expression(exp.Hint(expressions=[])), exp.Hint)\n        self.assertIsInstance(ignore.expression(exp.Hint(y=\"\")), exp.Hint)\n        self.assertIsInstance(ignore.expression(exp.Hint()), exp.Hint)\n\n        default = Parser(error_level=ErrorLevel.RAISE)\n        with self.assertRaises(TypeError):\n            default.expression(exp.Hint(y=\"\"))\n        self.assertIsInstance(default.expression(exp.Hint(expressions=[])), exp.Hint)\n        default.expression(exp.Hint())\n        self.assertEqual(len(default.errors), 2)\n\n        warn = Parser(error_level=ErrorLevel.WARN)\n        warn.expression(exp.Hint())\n        self.assertEqual(len(warn.errors), 1)\n\n    def test_parse_errors(self):\n        with self.assertRaises(ParseError):\n            parse_one(\"IF(a > 0, a, b, c)\")\n\n        with self.assertRaises(ParseError):\n            parse_one(\"IF(a > 0)\")\n\n        with self.assertRaises(ParseError):\n            parse_one(\"SELECT CASE FROM x\")\n\n        with self.assertRaises(ParseError):\n            parse_one(\"WITH cte AS (SELECT * FROM x)\")\n\n        with self.assertRaises(ParseError):\n            parse_one(\"SELECT foo( FROM bar\")\n\n        self.assertEqual(\n            parse_one(\n                \"CREATE TABLE t (i UInt8) ENGINE = AggregatingMergeTree() ORDER BY tuple()\",\n                read=\"clickhouse\",\n                error_level=ErrorLevel.RAISE,\n            ).sql(dialect=\"clickhouse\"),\n            \"CREATE TABLE t (i UInt8) ENGINE=AggregatingMergeTree() ORDER BY tuple()\",\n        )\n\n        with self.assertRaises(ParseError):\n            parse_one(\"SELECT A[:\")\n\n        self.assertEqual(parse_one(\"as as\", error_level=ErrorLevel.IGNORE).sql(), \"AS as\")\n\n    def test_space(self):\n        self.assertEqual(\n            parse_one(\"SELECT ROW() OVER(PARTITION  BY x) FROM x GROUP  BY y\").sql(),\n            \"SELECT ROW() OVER (PARTITION BY x) FROM x GROUP BY y\",\n        )\n\n        self.assertEqual(\n            parse_one(\n                \"\"\"SELECT   * FROM x GROUP\n                BY y\"\"\"\n            ).sql(),\n            \"SELECT * FROM x GROUP BY y\",\n        )\n\n    def test_missing_by(self):\n        with self.assertRaises(ParseError):\n            parse_one(\"SELECT FROM x ORDER BY\")\n\n    def test_parameter(self):\n        self.assertEqual(parse_one(\"SELECT @x, @@x, @1\").sql(), \"SELECT @x, @@x, @1\")\n\n    def test_var(self):\n        self.assertIsInstance(parse_one(\"INTERVAL '1' DAY\").args[\"unit\"], exp.Var)\n        self.assertEqual(parse_one(\"SELECT @JOIN, @'foo'\").sql(), \"SELECT @JOIN, @'foo'\")\n\n    def test_comments_select(self):\n        expression = parse_one(\n            \"\"\"\n            --comment1.1\n            --comment1.2\n            SELECT /*comment1.3*/\n                a, --comment2\n                b as B, --comment3:testing\n                \"test--annotation\",\n                c, --comment4 --foo\n                e, --\n                f -- space\n            FROM foo\n            \"\"\"\n        )\n\n        self.assertEqual(expression.comments, [\"comment1.1\", \"comment1.2\", \"comment1.3\"])\n        self.assertEqual(expression.expressions[0].comments, [\"comment2\"])\n        self.assertEqual(expression.expressions[1].comments, [\"comment3:testing\"])\n        self.assertEqual(expression.expressions[2].comments, None)\n        self.assertEqual(expression.expressions[3].comments, [\"comment4 --foo\"])\n        self.assertEqual(expression.expressions[4].comments, [\"\"])\n        self.assertEqual(expression.expressions[5].comments, [\" space\"])\n\n        expression = parse_one(\n            \"\"\"\n            SELECT a.column_name --# Comment 1\n                   ,b.column_name2, --# Comment 2\n                   b.column_name3 AS NAME3 --# Comment 3\n            FROM table_name a\n            JOIN table_name2 b ON a.column_name = b.column_name\n            \"\"\"\n        )\n\n        self.assertEqual(expression.expressions[0].comments, [\"# Comment 1\"])\n        self.assertEqual(expression.expressions[1].comments, [\"# Comment 2\"])\n        self.assertEqual(expression.expressions[2].comments, [\"# Comment 3\"])\n\n    def test_comments_select_cte(self):\n        expression = parse_one(\n            \"\"\"\n            /*comment1.1*/\n            /*comment1.2*/\n            WITH a AS (SELECT 1)\n            SELECT /*comment2*/\n                a.*\n            FROM /*comment3*/\n                a\n            \"\"\"\n        )\n\n        self.assertEqual(expression.comments, [\"comment2\"])\n        self.assertEqual(expression.args.get(\"from_\").comments, [\"comment3\"])\n        self.assertEqual(expression.args.get(\"with_\").comments, [\"comment1.1\", \"comment1.2\"])\n\n    def test_comments_insert(self):\n        expression = parse_one(\n            \"\"\"\n            --comment1.1\n            --comment1.2\n            INSERT INTO /*comment1.3*/\n                x       /*comment2*/\n            VALUES      /*comment3*/\n                (1, 'a', 2.0)\n        \"\"\"\n        )\n\n        self.assertEqual(expression.comments, [\"comment1.1\", \"comment1.2\", \"comment1.3\"])\n        self.assertEqual(expression.this.comments, [\"comment2\"])\n\n    def test_comments_insert_cte(self):\n        expression = parse_one(\n            \"\"\"\n            /*comment1.1*/\n            /*comment1.2*/\n            WITH a AS (SELECT 1)\n            INSERT INTO /*comment2*/\n                b /*comment3*/\n            SELECT * FROM a\n        \"\"\"\n        )\n\n        self.assertEqual(expression.comments, [\"comment2\"])\n        self.assertEqual(expression.this.comments, [\"comment3\"])\n        self.assertEqual(expression.args.get(\"with_\").comments, [\"comment1.1\", \"comment1.2\"])\n\n    def test_comments_update(self):\n        expression = parse_one(\n            \"\"\"\n            --comment1.1\n            --comment1.2\n            UPDATE  /*comment1.3*/\n                tbl /*comment2*/\n            SET     /*comment3*/\n                x = 2\n            WHERE /*comment4*/\n                x <> 2\n        \"\"\"\n        )\n\n        self.assertEqual(expression.comments, [\"comment1.1\", \"comment1.2\", \"comment1.3\"])\n        self.assertEqual(expression.this.comments, [\"comment2\"])\n        self.assertEqual(expression.args.get(\"where\").comments, [\"comment4\"])\n\n    def test_comments_update_cte(self):\n        expression = parse_one(\n            \"\"\"\n            /*comment1.1*/\n            /*comment1.2*/\n            WITH a AS (SELECT * FROM b)\n            UPDATE /*comment2*/\n                a  /*comment3*/\n            SET col = 1\n        \"\"\"\n        )\n\n        self.assertEqual(expression.comments, [\"comment2\"])\n        self.assertEqual(expression.this.comments, [\"comment3\"])\n        self.assertEqual(expression.args.get(\"with_\").comments, [\"comment1.1\", \"comment1.2\"])\n\n    def test_comments_delete(self):\n        expression = parse_one(\n            \"\"\"\n            --comment1.1\n            --comment1.2\n            DELETE /*comment1.3*/\n            FROM   /*comment2*/\n                x  /*comment3*/\n            WHERE  /*comment4*/\n                y > 1\n        \"\"\"\n        )\n\n        self.assertEqual(expression.comments, [\"comment1.1\", \"comment1.2\", \"comment1.3\"])\n        self.assertEqual(expression.this.comments, [\"comment3\"])\n        self.assertEqual(expression.args.get(\"where\").comments, [\"comment4\"])\n\n    def test_comments_delete_cte(self):\n        expression = parse_one(\n            \"\"\"\n            /*comment1.1*/\n            /*comment1.2*/\n            WITH a AS (SELECT * FROM b)\n            --comment2\n            DELETE FROM a /*comment3*/\n        \"\"\"\n        )\n\n        self.assertEqual(expression.comments, [\"comment2\"])\n        self.assertEqual(expression.this.comments, [\"comment3\"])\n        self.assertEqual(expression.args[\"with_\"].comments, [\"comment1.1\", \"comment1.2\"])\n\n    def test_type_literals(self):\n        self.assertEqual(parse_one(\"int 1\"), parse_one(\"CAST(1 AS INT)\"))\n        self.assertEqual(parse_one(\"int.5\"), parse_one(\"CAST(0.5 AS INT)\"))\n        self.assertEqual(\n            parse_one(\"TIMESTAMP '2022-01-01'\").sql(), \"CAST('2022-01-01' AS TIMESTAMP)\"\n        )\n        self.assertEqual(\n            parse_one(\"TIMESTAMP(1) '2022-01-01'\").sql(), \"CAST('2022-01-01' AS TIMESTAMP(1))\"\n        )\n        self.assertEqual(\n            parse_one(\"TIMESTAMP WITH TIME ZONE '2022-01-01'\").sql(),\n            \"CAST('2022-01-01' AS TIMESTAMPTZ)\",\n        )\n        self.assertEqual(\n            parse_one(\"TIMESTAMP WITH LOCAL TIME ZONE '2022-01-01'\").sql(),\n            \"CAST('2022-01-01' AS TIMESTAMPLTZ)\",\n        )\n        self.assertEqual(\n            parse_one(\"TIMESTAMP WITHOUT TIME ZONE '2022-01-01'\").sql(),\n            \"CAST('2022-01-01' AS TIMESTAMP)\",\n        )\n        self.assertEqual(\n            parse_one(\"TIMESTAMP(1) WITH TIME ZONE '2022-01-01'\").sql(),\n            \"CAST('2022-01-01' AS TIMESTAMPTZ(1))\",\n        )\n        self.assertEqual(\n            parse_one(\"TIMESTAMP(1) WITH LOCAL TIME ZONE '2022-01-01'\").sql(),\n            \"CAST('2022-01-01' AS TIMESTAMPLTZ(1))\",\n        )\n        self.assertEqual(\n            parse_one(\"TIMESTAMP(1) WITHOUT TIME ZONE '2022-01-01'\").sql(),\n            \"CAST('2022-01-01' AS TIMESTAMP(1))\",\n        )\n        self.assertEqual(parse_one(\"TIMESTAMP(1) WITH TIME ZONE\").sql(), \"TIMESTAMPTZ(1)\")\n        self.assertEqual(parse_one(\"TIMESTAMP(1) WITH LOCAL TIME ZONE\").sql(), \"TIMESTAMPLTZ(1)\")\n        self.assertEqual(parse_one(\"TIMESTAMP(1) WITHOUT TIME ZONE\").sql(), \"TIMESTAMP(1)\")\n        self.assertEqual(parse_one(\"\"\"JSON '{\"x\":\"y\"}'\"\"\").sql(), \"\"\"PARSE_JSON('{\"x\":\"y\"}')\"\"\")\n        self.assertIsInstance(parse_one(\"TIMESTAMP(1)\"), exp.Func)\n        self.assertIsInstance(parse_one(\"TIMESTAMP('2022-01-01')\"), exp.Func)\n        self.assertIsInstance(parse_one(\"TIMESTAMP()\"), exp.Func)\n        self.assertIsInstance(parse_one(\"map.x\"), exp.Column)\n        self.assertIsInstance(parse_one(\"CAST(x AS CHAR(5))\").to.expressions[0], exp.DataTypeParam)\n        self.assertEqual(parse_one(\"1::int64\", dialect=\"bigquery\"), parse_one(\"CAST(1 AS BIGINT)\"))\n\n    def test_set_expression(self):\n        set_ = parse_one(\"SET\")\n\n        self.assertEqual(set_.sql(), \"SET\")\n        self.assertIsInstance(set_, exp.Set)\n\n        set_session = parse_one(\"SET SESSION x = 1\")\n\n        self.assertEqual(set_session.sql(), \"SET SESSION x = 1\")\n        self.assertIsInstance(set_session, exp.Set)\n\n        set_item = set_session.expressions[0]\n\n        self.assertIsInstance(set_item, exp.SetItem)\n        self.assertIsInstance(set_item.this, exp.EQ)\n        self.assertIsInstance(set_item.this.this, exp.Column)\n        self.assertIsInstance(set_item.this.expression, exp.Literal)\n\n        self.assertEqual(set_item.args.get(\"kind\"), \"SESSION\")\n\n        set_to = parse_one(\"SET x TO 1\")\n\n        self.assertEqual(set_to.sql(), \"SET x = 1\")\n        self.assertIsInstance(set_to, exp.Set)\n\n        with self.assertLogs(parser_logger) as cm:\n            set_as_command = parse_one(\"SET DEFAULT ROLE ALL TO USER\")\n            assert \"'SET DEFAULT ROLE ALL TO USER'\" in cm.output[0]\n\n        self.assertEqual(set_as_command.sql(), \"SET DEFAULT ROLE ALL TO USER\")\n\n        self.assertIsInstance(set_as_command, exp.Command)\n        self.assertEqual(set_as_command.this, \"SET\")\n        self.assertEqual(set_as_command.expression, \" DEFAULT ROLE ALL TO USER\")\n\n    def test_pretty_config_override(self):\n        self.assertEqual(parse_one(\"SELECT col FROM x\").sql(), \"SELECT col FROM x\")\n        with patch(\"sqlglot.pretty\", True):\n            self.assertEqual(parse_one(\"SELECT col FROM x\").sql(), \"SELECT\\n  col\\nFROM x\")\n\n        self.assertEqual(parse_one(\"SELECT col FROM x\").sql(pretty=True), \"SELECT\\n  col\\nFROM x\")\n\n    @patch(\"sqlglot.parser.logger\")\n    def test_comment_error_n(self, logger):\n        parse_one(\n            \"\"\"SUM\n(\n-- test\n)\"\"\",\n            error_level=ErrorLevel.WARN,\n        )\n\n        assert_logger_contains(\n            \"Required keyword: 'this' missing for <class 'sqlglot.expressions.aggregate.Sum'>. Line 4, Col: 1.\",\n            logger,\n        )\n\n    @patch(\"sqlglot.parser.logger\")\n    def test_comment_error_r(self, logger):\n        parse_one(\n            \"\"\"SUM(-- test\\r)\"\"\",\n            error_level=ErrorLevel.WARN,\n        )\n\n        assert_logger_contains(\n            \"Required keyword: 'this' missing for <class 'sqlglot.expressions.aggregate.Sum'>. Line 2, Col: 1.\",\n            logger,\n        )\n\n    @patch(\"sqlglot.parser.logger\")\n    def test_create_table_error(self, logger):\n        parse_one(\n            \"\"\"CREATE TABLE SELECT\"\"\",\n            error_level=ErrorLevel.WARN,\n        )\n\n        assert_logger_contains(\n            \"Expected table name\",\n            logger,\n        )\n\n    def test_pivot_columns(self):\n        nothing_aliased = \"\"\"\n            SELECT * FROM (\n                SELECT partname, price FROM part\n            ) PIVOT (AVG(price) FOR partname IN ('prop', 'rudder'))\n        \"\"\"\n\n        everything_aliased = \"\"\"\n            SELECT * FROM (\n                SELECT partname, price FROM part\n            ) PIVOT (AVG(price) AS avg_price FOR partname IN ('prop' AS prop1, 'rudder' AS rudder1))\n        \"\"\"\n\n        only_pivot_columns_aliased = \"\"\"\n            SELECT * FROM (\n                SELECT partname, price FROM part\n            ) PIVOT (AVG(price) FOR partname IN ('prop' AS prop1, 'rudder' AS rudder1))\n        \"\"\"\n\n        columns_partially_aliased = \"\"\"\n            SELECT * FROM (\n                SELECT partname, price FROM part\n            ) PIVOT (AVG(price) FOR partname IN ('prop' AS prop1, 'rudder'))\n        \"\"\"\n\n        multiple_aggregates_aliased = \"\"\"\n            SELECT * FROM (\n                SELECT partname, price, quality FROM part\n            ) PIVOT (AVG(price) AS p, MAX(quality) AS q FOR partname IN ('prop' AS prop1, 'rudder'))\n        \"\"\"\n\n        multiple_aggregates_not_aliased = \"\"\"\n            SELECT * FROM (\n                SELECT partname, price, quality FROM part\n            ) PIVOT (AVG(price), MAX(quality) FOR partname IN ('prop' AS prop1, 'rudder'))\n        \"\"\"\n\n        multiple_aggregates_not_aliased_with_quoted_identifier_spark = \"\"\"\n            SELECT * FROM (\n                SELECT partname, price, quality FROM part\n            ) PIVOT (AVG(`PrIcE`), MAX(quality) FOR partname IN ('prop' AS prop1, 'rudder'))\n        \"\"\"\n\n        multiple_aggregates_not_aliased_with_quoted_identifier_duckdb = \"\"\"\n            SELECT * FROM (\n                SELECT partname, price, quality FROM part\n            ) PIVOT (AVG(\"PrIcE\"), MAX(quality) FOR partname IN ('prop' AS prop1, 'rudder'))\n        \"\"\"\n\n        two_in_clauses_duckdb = \"\"\"\n            SELECT * FROM cities PIVOT (\n                sum(population) AS total,\n                count(population) AS count\n                FOR\n                    year IN (2000, 2010)\n                    country IN ('NL', 'US')\n            )\n        \"\"\"\n\n        three_in_clauses_duckdb = \"\"\"\n            SELECT * FROM cities PIVOT (\n                sum(population) AS total,\n                count(population) AS count\n                FOR\n                    year IN (2000, 2010)\n                    country IN ('NL', 'US')\n                    name IN ('Amsterdam', 'Seattle')\n            )\n        \"\"\"\n\n        query_to_column_names = {\n            nothing_aliased: {\n                \"bigquery\": [\"prop\", \"rudder\"],\n                \"duckdb\": [\"prop\", \"rudder\"],\n                \"redshift\": [\"prop\", \"rudder\"],\n                \"snowflake\": ['''\"'prop'\"''', '''\"'rudder'\"'''],\n                \"spark\": [\"prop\", \"rudder\"],\n            },\n            everything_aliased: {\n                \"bigquery\": [\"avg_price_prop1\", \"avg_price_rudder1\"],\n                \"duckdb\": [\"prop1_avg_price\", \"rudder1_avg_price\"],\n                \"redshift\": [\"prop1_avg_price\", \"rudder1_avg_price\"],\n                \"spark\": [\"prop1\", \"rudder1\"],\n            },\n            only_pivot_columns_aliased: {\n                \"bigquery\": [\"prop1\", \"rudder1\"],\n                \"duckdb\": [\"prop1\", \"rudder1\"],\n                \"redshift\": [\"prop1\", \"rudder1\"],\n                \"spark\": [\"prop1\", \"rudder1\"],\n            },\n            columns_partially_aliased: {\n                \"bigquery\": [\"prop1\", \"rudder\"],\n                \"duckdb\": [\"prop1\", \"rudder\"],\n                \"redshift\": [\"prop1\", \"rudder\"],\n                \"spark\": [\"prop1\", \"rudder\"],\n            },\n            multiple_aggregates_aliased: {\n                \"bigquery\": [\"p_prop1\", \"q_prop1\", \"p_rudder\", \"q_rudder\"],\n                \"duckdb\": [\"prop1_p\", \"prop1_q\", \"rudder_p\", \"rudder_q\"],\n                \"spark\": [\"prop1_p\", \"prop1_q\", \"rudder_p\", \"rudder_q\"],\n            },\n            multiple_aggregates_not_aliased: {\n                \"duckdb\": [\n                    '\"prop1_avg(price)\"',\n                    '\"prop1_max(quality)\"',\n                    '\"rudder_avg(price)\"',\n                    '\"rudder_max(quality)\"',\n                ],\n                \"spark\": [\n                    \"`prop1_avg(price)`\",\n                    \"`prop1_max(quality)`\",\n                    \"`rudder_avg(price)`\",\n                    \"`rudder_max(quality)`\",\n                ],\n            },\n            multiple_aggregates_not_aliased_with_quoted_identifier_spark: {\n                \"spark\": [\n                    \"`prop1_avg(PrIcE)`\",\n                    \"`prop1_max(quality)`\",\n                    \"`rudder_avg(PrIcE)`\",\n                    \"`rudder_max(quality)`\",\n                ],\n            },\n            multiple_aggregates_not_aliased_with_quoted_identifier_duckdb: {\n                \"duckdb\": [\n                    '\"prop1_avg(PrIcE)\"',\n                    '\"prop1_max(quality)\"',\n                    '\"rudder_avg(PrIcE)\"',\n                    '\"rudder_max(quality)\"',\n                ],\n            },\n            two_in_clauses_duckdb: {\n                \"duckdb\": [\n                    '\"2000_NL_total\"',\n                    '\"2000_NL_count\"',\n                    '\"2000_US_total\"',\n                    '\"2000_US_count\"',\n                    '\"2010_NL_total\"',\n                    '\"2010_NL_count\"',\n                    '\"2010_US_total\"',\n                    '\"2010_US_count\"',\n                ],\n            },\n            three_in_clauses_duckdb: {\n                \"duckdb\": [\n                    '\"2000_NL_Amsterdam_total\"',\n                    '\"2000_NL_Amsterdam_count\"',\n                    '\"2000_NL_Seattle_total\"',\n                    '\"2000_NL_Seattle_count\"',\n                    '\"2000_US_Amsterdam_total\"',\n                    '\"2000_US_Amsterdam_count\"',\n                    '\"2000_US_Seattle_total\"',\n                    '\"2000_US_Seattle_count\"',\n                    '\"2010_NL_Amsterdam_total\"',\n                    '\"2010_NL_Amsterdam_count\"',\n                    '\"2010_NL_Seattle_total\"',\n                    '\"2010_NL_Seattle_count\"',\n                    '\"2010_US_Amsterdam_total\"',\n                    '\"2010_US_Amsterdam_count\"',\n                    '\"2010_US_Seattle_total\"',\n                    '\"2010_US_Seattle_count\"',\n                ],\n            },\n        }\n\n        for query, dialect_columns in query_to_column_names.items():\n            for dialect, expected_columns in dialect_columns.items():\n                with self.subTest(f\"Testing query '{query}' for dialect {dialect}\"):\n                    expr = parse_one(query, read=dialect)\n                    columns = expr.args[\"from_\"].this.args[\"pivots\"][0].args[\"columns\"]\n                    self.assertEqual(\n                        expected_columns, [col.sql(dialect=dialect) for col in columns]\n                    )\n\n    def test_parse_nested(self):\n        def warn_over_threshold(query: str, max_threshold: float = 0.2):\n            now = time.time()\n            ast = parse_one(query)\n            end = time.time() - now\n\n            self.assertIsNotNone(ast)\n            if end >= max_threshold:\n                parser_logger.warning(\n                    f\"Query {query[:100]}... surpassed the time threshold of {max_threshold} seconds\"\n                )\n\n        warn_over_threshold(\"SELECT * FROM a \" + (\"LEFT JOIN b ON a.id = b.id \" * 38))\n        warn_over_threshold(\"SELECT * FROM a \" + (\"LEFT JOIN UNNEST(ARRAY[]) \" * 15))\n        warn_over_threshold(\"SELECT * FROM a \" + (\"OUTER APPLY (SELECT * FROM b) \" * 30))\n        warn_over_threshold(\"SELECT * FROM a \" + (\"NATURAL FULL OUTER JOIN x \" * 30))\n\n    def test_parse_properties(self):\n        self.assertEqual(\n            parse_one(\"create materialized table x\").sql(), \"CREATE MATERIALIZED TABLE x\"\n        )\n\n    def test_parse_floats(self):\n        self.assertTrue(parse_one(\"1. \").is_number)\n\n    def test_parse_terse_coalesce(self):\n        self.assertIsNotNone(parse_one(\"SELECT x ?? y FROM z\").find(exp.Coalesce))\n        self.assertEqual(\n            parse_one(\"SELECT a, b ?? 'No Data' FROM z\").sql(),\n            \"SELECT a, COALESCE(b, 'No Data') FROM z\",\n        )\n        self.assertEqual(\n            parse_one(\"SELECT a, b ?? c ?? 'No Data' FROM z\").sql(),\n            \"SELECT a, COALESCE(COALESCE(b, c), 'No Data') FROM z\",\n        )\n\n    def test_parse_intervals(self):\n        ast = parse_one(\n            \"SELECT a FROM tbl WHERE a <= DATE '1998-12-01' - INTERVAL '71 days' GROUP BY b\"\n        )\n\n        self.assertEqual(ast.find(exp.Interval).this.sql(), \"'71'\")\n        self.assertEqual(ast.find(exp.Interval).unit.assert_is(exp.Var).sql(), \"DAYS\")\n\n    def test_parse_concat_ws(self):\n        ast = parse_one(\"CONCAT_WS(' ', 'John', 'Doe')\")\n\n        self.assertEqual(ast.sql(), \"CONCAT_WS(' ', 'John', 'Doe')\")\n        self.assertEqual(ast.expressions[0].sql(), \"' '\")\n        self.assertEqual(ast.expressions[1].sql(), \"'John'\")\n        self.assertEqual(ast.expressions[2].sql(), \"'Doe'\")\n\n        # Ensure we can parse without argument when error level is ignore\n        ast = parse(\n            \"CONCAT_WS()\",\n            error_level=ErrorLevel.IGNORE,\n        )\n        self.assertEqual(ast[0].sql(), \"CONCAT_WS()\")\n\n    def test_parse_drop_schema(self):\n        for dialect in [None, \"bigquery\", \"snowflake\", \"duckdb\"]:\n            with self.subTest(dialect):\n                ast = parse_one(\"DROP SCHEMA catalog.schema\", dialect=dialect)\n                self.assertEqual(\n                    ast,\n                    exp.Drop(\n                        this=exp.Table(\n                            this=None,\n                            db=exp.Identifier(this=\"schema\", quoted=False),\n                            catalog=exp.Identifier(this=\"catalog\", quoted=False),\n                        ),\n                        kind=\"SCHEMA\",\n                    ),\n                )\n                self.assertEqual(ast.sql(dialect=dialect), \"DROP SCHEMA catalog.schema\")\n\n        # when there is IF EXISTS it must not displace catalog mapping\n        for dialect in [None, \"bigquery\", \"snowflake\", \"duckdb\"]:\n            with self.subTest(f\"{dialect} IF EXISTS\"):\n                ast = parse_one(\"DROP SCHEMA IF EXISTS catalog.schema\", dialect=dialect)\n                self.assertEqual(\n                    ast,\n                    exp.Drop(\n                        this=exp.Table(\n                            this=None,\n                            db=exp.Identifier(this=\"schema\", quoted=False),\n                            catalog=exp.Identifier(this=\"catalog\", quoted=False),\n                        ),\n                        kind=\"SCHEMA\",\n                        exists=True,\n                    ),\n                )\n                self.assertEqual(ast.sql(dialect=dialect), \"DROP SCHEMA IF EXISTS catalog.schema\")\n\n        # single part name no catalog and schema name in db\n        for dialect in [None, \"bigquery\", \"snowflake\", \"duckdb\"]:\n            with self.subTest(f\"DROP SCHEMA for {dialect}\"):\n                ast = parse_one(\"DROP SCHEMA IF EXISTS myschema\", dialect=dialect)\n                self.assertEqual(\n                    ast,\n                    exp.Drop(\n                        this=exp.Table(\n                            this=None,\n                            db=exp.Identifier(this=\"myschema\", quoted=False),\n                        ),\n                        kind=\"SCHEMA\",\n                        exists=True,\n                    ),\n                )\n                self.assertEqual(ast.sql(), \"DROP SCHEMA IF EXISTS myschema\")\n\n    def test_parse_create_schema(self):\n        for dialect in [None, \"bigquery\", \"snowflake\"]:\n            with self.subTest(dialect):\n                ast = parse_one(\"CREATE SCHEMA catalog.schema\", dialect=dialect)\n                self.assertEqual(\n                    ast,\n                    exp.Create(\n                        this=exp.Table(\n                            this=None,\n                            db=exp.Identifier(this=\"schema\", quoted=False),\n                            catalog=exp.Identifier(this=\"catalog\", quoted=False),\n                        ),\n                        kind=\"SCHEMA\",\n                    ),\n                )\n                self.assertEqual(ast.sql(dialect=dialect), \"CREATE SCHEMA catalog.schema\")\n\n    def test_values_as_identifier(self):\n        sql = \"SELECT values FROM t WHERE values + 1 > x\"\n        for dialect in (\n            \"bigquery\",\n            \"clickhouse\",\n            \"duckdb\",\n            \"postgres\",\n            \"redshift\",\n            \"snowflake\",\n        ):\n            with self.subTest(dialect):\n                self.assertEqual(parse_one(sql, dialect=dialect).sql(dialect=dialect), sql)\n\n    def test_alter_set(self):\n        sqls = [\n            \"ALTER TABLE tbl SET TBLPROPERTIES ('x'='1', 'Z'='2')\",\n            \"ALTER TABLE tbl SET SERDE 'test' WITH SERDEPROPERTIES ('k'='v', 'kay'='vee')\",\n            \"ALTER TABLE tbl SET SERDEPROPERTIES ('k'='v', 'kay'='vee')\",\n            \"ALTER TABLE tbl SET LOCATION 'new_location'\",\n            \"ALTER TABLE tbl SET FILEFORMAT file_format\",\n            \"ALTER TABLE tbl SET TAGS ('tag1' = 't1', 'tag2' = 't2')\",\n        ]\n\n        for dialect in (\n            \"hive\",\n            \"spark2\",\n            \"spark\",\n            \"databricks\",\n        ):\n            for sql in sqls:\n                with self.subTest(f\"Testing query '{sql}' for dialect {dialect}\"):\n                    self.assertEqual(parse_one(sql, dialect=dialect).sql(dialect=dialect), sql)\n\n    def test_distinct_from(self):\n        self.assertIsInstance(parse_one(\"a IS DISTINCT FROM b OR c IS DISTINCT FROM d\"), exp.Or)\n\n    def test_trailing_comments(self):\n        expressions = parse(\n            \"\"\"\n        select * from x;\n        -- my comment\n            \"\"\"\n        )\n\n        self.assertEqual(\n            \";\\n\".join(e.sql() for e in expressions), \"SELECT * FROM x;\\n/* my comment */\"\n        )\n\n    def test_parse_prop_eq(self):\n        self.assertIsInstance(parse_one(\"x(a := b and c)\").expressions[0], exp.PropertyEQ)\n\n    def test_collate(self):\n        collates = [\n            ('pg_catalog.\"default\"', exp.Column),\n            ('\"en_DE\"', exp.Identifier),\n            (\"LATIN1_GENERAL_BIN\", exp.Var),\n            (\"'en'\", exp.Literal),\n        ]\n\n        for collate_pair in collates:\n            collate_node = parse_one(\n                f\"\"\"SELECT * FROM t WHERE foo LIKE '%bar%' COLLATE {collate_pair[0]}\"\"\"\n            ).find(exp.Collate)\n            self.assertIsInstance(collate_node, exp.Collate)\n            self.assertIsInstance(collate_node.expression, collate_pair[1])\n\n    def test_drop_column(self):\n        ast = parse_one(\"ALTER TABLE tbl DROP COLUMN col\")\n        self.assertEqual(len(list(ast.find_all(exp.Table))), 1)\n        self.assertEqual(len(list(ast.find_all(exp.Column))), 1)\n\n    def test_udf_meta(self):\n        ast = parse_one(\"YEAR(a) /* sqlglot.anonymous */\")\n        self.assertIsInstance(ast, exp.Anonymous)\n\n        # Meta flag is case sensitive\n        ast = parse_one(\"YEAR(a) /* sqlglot.anONymous */\")\n        self.assertIsInstance(ast, exp.Year)\n\n        # Incomplete or incorrect anonymous meta comments are not registered\n        ast = parse_one(\"YEAR(a) /* sqlglot.anon */\")\n        self.assertIsInstance(ast, exp.Year)\n\n    def test_token_position_meta(self):\n        ast = parse_one(\n            \"SELECT a, b FROM test_schema.test_table_a UNION ALL SELECT c, d FROM test_catalog.test_schema.test_table_b\"\n        )\n        for identifier in ast.find_all(exp.Identifier):\n            self.assertEqual(set(identifier.meta), {\"line\", \"col\", \"start\", \"end\"})\n\n        self.assertEqual(\n            ast.this.args[\"from_\"].this.args[\"this\"].meta,\n            {\"line\": 1, \"col\": 41, \"start\": 29, \"end\": 40},\n        )\n        self.assertEqual(\n            ast.this.args[\"from_\"].this.args[\"db\"].meta,\n            {\"line\": 1, \"col\": 28, \"start\": 17, \"end\": 27},\n        )\n        self.assertEqual(\n            ast.expression.args[\"from_\"].this.args[\"this\"].meta,\n            {\"line\": 1, \"col\": 106, \"start\": 94, \"end\": 105},\n        )\n        self.assertEqual(\n            ast.expression.args[\"from_\"].this.args[\"db\"].meta,\n            {\"line\": 1, \"col\": 93, \"start\": 82, \"end\": 92},\n        )\n        self.assertEqual(\n            ast.expression.args[\"from_\"].this.args[\"catalog\"].meta,\n            {\"line\": 1, \"col\": 81, \"start\": 69, \"end\": 80},\n        )\n\n        ast = parse_one(\"SELECT FOO()\")\n        self.assertEqual(ast.find(exp.Anonymous).meta, {\"line\": 1, \"col\": 10, \"start\": 7, \"end\": 9})\n\n        ast = parse_one(\"SELECT * FROM t\")\n        self.assertEqual(ast.find(exp.Star).meta, {\"line\": 1, \"col\": 8, \"start\": 7, \"end\": 7})\n\n        ast = parse_one(\"SELECT t.* FROM t\")\n        self.assertEqual(ast.find(exp.Star).meta, {\"line\": 1, \"col\": 10, \"start\": 9, \"end\": 9})\n\n        ast = parse_one(\"SELECT 1\")\n        self.assertEqual(ast.find(exp.Literal).meta, {\"line\": 1, \"col\": 8, \"start\": 7, \"end\": 7})\n\n        self.assertEqual(parse_one(\"max(1)\").meta, {\"col\": 3, \"end\": 2, \"line\": 1, \"start\": 0})\n\n    def test_quoted_identifier_meta(self):\n        sql = 'SELECT \"a\" FROM \"test_schema\".\"test_table_a\"'\n        ast = parse_one(sql)\n\n        db_meta = ast.args[\"from_\"].this.args[\"db\"].meta\n        self.assertEqual(sql[db_meta[\"start\"] : db_meta[\"end\"] + 1], '\"test_schema\"')\n\n        table_meta = ast.args[\"from_\"].this.this.meta\n        self.assertEqual(sql[table_meta[\"start\"] : table_meta[\"end\"] + 1], '\"test_table_a\"')\n\n    def test_qualified_function(self):\n        sql = \"a.b.c.d.e.f.g.foo()\"\n        ast = parse_one(sql)\n        assert not any(isinstance(n, exp.Column) for n in ast.walk())\n        assert len(list(ast.find_all(exp.Dot))) == 7\n\n    def test_pivot_missing_agg_func(self):\n        with self.assertRaises(ParseError) as ctx:\n            parse_one(\"select * from tbl pivot(col1 for col2 in (val1, val1))\")\n\n        self.assertIn(\"Expecting an aggregation function in PIVOT\", str(ctx.exception))\n\n    def test_multiple_query_modifiers(self):\n        sql = \"SELECT * FROM a WHERE b = 'true' AND c > 50 WHERE c = 'false'\"\n\n        with self.assertRaises(ParseError) as ctx:\n            parse_one(sql)\n\n        self.assertIn(\"Found multiple 'WHERE' clauses. Line 1, Col: 49.\", str(ctx.exception))\n\n        self.assertEqual(\n            parse_one(sql, error_level=ErrorLevel.IGNORE).sql(),\n            \"SELECT * FROM a WHERE c = 'false'\",\n        )\n\n    def test_parse_into_grant_principal(self):\n        self.assertIsInstance(parse_one(\"ROLE blah\", into=exp.GrantPrincipal), exp.GrantPrincipal)\n        self.assertIsInstance(parse_one(\"GROUP blah\", into=exp.GrantPrincipal), exp.GrantPrincipal)\n        self.assertIsInstance(parse_one(\"blah\", into=exp.GrantPrincipal), exp.GrantPrincipal)\n        self.assertIsInstance(\n            parse_one(\"ROLE `blah`\", into=exp.GrantPrincipal, dialect=\"databricks\"),\n            exp.GrantPrincipal,\n        )\n        self.assertEqual(\n            parse_one(\"ROLE `blah`\", into=exp.GrantPrincipal, dialect=\"databricks\").sql(\n                dialect=\"databricks\"\n            ),\n            \"ROLE `blah`\",\n        )\n\n    def test_parse_into_grant_privilege(self):\n        self.assertIsInstance(parse_one(\"SELECT\", into=exp.GrantPrivilege), exp.GrantPrivilege)\n        self.assertIsInstance(\n            parse_one(\"ALL PRIVILEGES\", into=exp.GrantPrivilege), exp.GrantPrivilege\n        )\n"
  },
  {
    "path": "tests/test_schema.py",
    "content": "import unittest\n\nfrom sqlglot import exp, parse_one, to_table\nfrom sqlglot.errors import SchemaError\nfrom sqlglot.schema import MappingSchema, ensure_schema\n\n\nclass TestSchema(unittest.TestCase):\n    def assert_column_names(self, schema, *table_results):\n        for table, result in table_results:\n            with self.subTest(f\"{table} -> {result}\"):\n                self.assertEqual(schema.column_names(to_table(table)), result)\n\n    def assert_column_names_raises(self, schema, *tables):\n        for table in tables:\n            with self.subTest(table):\n                with self.assertRaises(SchemaError):\n                    schema.column_names(to_table(table))\n\n    def assert_column_names_empty(self, schema, *tables):\n        for table in tables:\n            with self.subTest(table):\n                self.assertEqual(schema.column_names(to_table(table)), [])\n\n    def test_schema(self):\n        schema = ensure_schema(\n            {\n                \"x\": {\n                    \"a\": \"uint64\",\n                },\n                \"y\": {\n                    \"b\": \"uint64\",\n                    \"c\": \"uint64\",\n                },\n            },\n        )\n\n        self.assert_column_names(\n            schema,\n            (\"x\", [\"a\"]),\n            (\"y\", [\"b\", \"c\"]),\n            (\"z.x\", [\"a\"]),\n            (\"z.x.y\", [\"b\", \"c\"]),\n        )\n\n        self.assert_column_names_empty(\n            schema,\n            \"z\",\n            \"z.z\",\n            \"z.z.z\",\n        )\n\n    def test_schema_db(self):\n        schema = ensure_schema(\n            {\n                \"d1\": {\n                    \"x\": {\n                        \"a\": \"uint64\",\n                    },\n                    \"y\": {\n                        \"b\": \"uint64\",\n                    },\n                },\n                \"d2\": {\n                    \"x\": {\n                        \"c\": \"uint64\",\n                    },\n                },\n            },\n        )\n\n        self.assert_column_names(\n            schema,\n            (\"d1.x\", [\"a\"]),\n            (\"d2.x\", [\"c\"]),\n            (\"y\", [\"b\"]),\n            (\"d1.y\", [\"b\"]),\n            (\"z.d1.y\", [\"b\"]),\n        )\n\n        self.assert_column_names_raises(\n            schema,\n            \"x\",\n        )\n\n        self.assert_column_names_empty(\n            schema,\n            \"z.x\",\n            \"z.y\",\n        )\n\n    def test_schema_catalog(self):\n        schema = ensure_schema(\n            {\n                \"c1\": {\n                    \"d1\": {\n                        \"x\": {\n                            \"a\": \"uint64\",\n                        },\n                        \"y\": {\n                            \"b\": \"uint64\",\n                        },\n                        \"z\": {\n                            \"c\": \"uint64\",\n                        },\n                    },\n                },\n                \"c2\": {\n                    \"d1\": {\n                        \"y\": {\n                            \"d\": \"uint64\",\n                        },\n                        \"z\": {\n                            \"e\": \"uint64\",\n                        },\n                    },\n                    \"d2\": {\n                        \"z\": {\n                            \"f\": \"uint64\",\n                        },\n                    },\n                },\n            }\n        )\n\n        self.assert_column_names(\n            schema,\n            (\"x\", [\"a\"]),\n            (\"d1.x\", [\"a\"]),\n            (\"c1.d1.x\", [\"a\"]),\n            (\"c1.d1.y\", [\"b\"]),\n            (\"c1.d1.z\", [\"c\"]),\n            (\"c2.d1.y\", [\"d\"]),\n            (\"c2.d1.z\", [\"e\"]),\n            (\"d2.z\", [\"f\"]),\n            (\"c2.d2.z\", [\"f\"]),\n        )\n\n        self.assert_column_names_raises(\n            schema,\n            \"y\",\n            \"z\",\n            \"d1.y\",\n            \"d1.z\",\n        )\n\n        self.assert_column_names_empty(\n            schema,\n            \"q\",\n            \"d2.x\",\n            \"a.b.c\",\n        )\n\n    def test_schema_add_table_with_and_without_mapping(self):\n        schema = MappingSchema()\n        schema.add_table(\"test\")\n        self.assertEqual(schema.column_names(\"test\"), [])\n        schema.add_table(\"test\", {\"x\": \"string\"})\n        self.assertEqual(schema.column_names(\"test\"), [\"x\"])\n        schema.add_table(\"test\", {\"x\": \"string\", \"y\": \"int\"})\n        self.assertEqual(schema.column_names(\"test\"), [\"x\", \"y\"])\n        schema.add_table(\"test\")\n        self.assertEqual(schema.column_names(\"test\"), [\"x\", \"y\"])\n\n    def test_schema_get_column_type(self):\n        schema = MappingSchema({\"A\": {\"b\": \"varchar\"}})\n        self.assertEqual(schema.get_column_type(\"a\", \"B\").this, exp.DataType.Type.VARCHAR)\n        self.assertEqual(\n            schema.get_column_type(exp.table_(\"a\"), exp.column(\"b\")).this,\n            exp.DataType.Type.VARCHAR,\n        )\n        self.assertEqual(\n            schema.get_column_type(\"a\", exp.column(\"b\")).this, exp.DataType.Type.VARCHAR\n        )\n        self.assertEqual(\n            schema.get_column_type(exp.table_(\"a\"), \"b\").this, exp.DataType.Type.VARCHAR\n        )\n        schema = MappingSchema({\"a\": {\"b\": {\"c\": \"varchar\"}}})\n        self.assertEqual(\n            schema.get_column_type(exp.table_(\"b\", db=\"a\"), exp.column(\"c\")).this,\n            exp.DataType.Type.VARCHAR,\n        )\n        self.assertEqual(\n            schema.get_column_type(exp.table_(\"b\", db=\"a\"), \"c\").this, exp.DataType.Type.VARCHAR\n        )\n        schema = MappingSchema({\"a\": {\"b\": {\"c\": {\"d\": \"varchar\"}}}})\n        self.assertEqual(\n            schema.get_column_type(exp.table_(\"c\", db=\"b\", catalog=\"a\"), exp.column(\"d\")).this,\n            exp.DataType.Type.VARCHAR,\n        )\n        self.assertEqual(\n            schema.get_column_type(exp.table_(\"c\", db=\"b\", catalog=\"a\"), \"d\").this,\n            exp.DataType.Type.VARCHAR,\n        )\n\n        schema = MappingSchema({\"foo\": {\"bar\": parse_one(\"INT\", into=exp.DataType)}})\n        self.assertEqual(schema.get_column_type(\"foo\", \"bar\").this, exp.DataType.Type.INT)\n\n    def test_schema_normalization(self):\n        schema = MappingSchema(\n            schema={\"x\": {\"`y`\": {\"Z\": {\"a\": \"INT\", \"`B`\": \"VARCHAR\"}, \"w\": {\"C\": \"INT\"}}}},\n            dialect=\"clickhouse\",\n        )\n\n        table_z = exp.table_(\"Z\", db=\"y\", catalog=\"x\")\n        table_w = exp.table_(\"w\", db=\"y\", catalog=\"x\")\n\n        self.assertEqual(schema.column_names(table_z), [\"a\", \"B\"])\n        self.assertEqual(schema.column_names(table_w), [\"C\"])\n\n        schema = MappingSchema(schema={\"x\": {\"`y`\": \"INT\"}}, dialect=\"clickhouse\")\n        self.assertEqual(schema.column_names(exp.table_(\"x\")), [\"y\"])\n\n        # Check that add_table normalizes both the table and the column names to be added / updated\n        schema = MappingSchema()\n        schema.add_table(\"Foo\", {\"SomeColumn\": \"INT\", '\"SomeColumn\"': \"DOUBLE\"})\n        self.assertEqual(schema.column_names(exp.table_(\"fOO\")), [\"somecolumn\", \"SomeColumn\"])\n\n        # Check that names are normalized to uppercase for Snowflake\n        schema = MappingSchema(schema={\"x\": {\"foo\": \"int\", '\"bLa\"': \"int\"}}, dialect=\"snowflake\")\n        self.assertEqual(schema.column_names(exp.table_(\"x\")), [\"FOO\", \"bLa\"])\n\n        # Check that switching off the normalization logic works as expected\n        schema = MappingSchema(schema={\"x\": {\"foo\": \"int\"}}, normalize=False, dialect=\"snowflake\")\n        self.assertEqual(schema.column_names(exp.table_(\"x\")), [\"foo\"])\n\n        # Check that the correct dialect is used when calling schema methods\n        # Note: T-SQL is case-insensitive by default, so `fo` in clickhouse will match the normalized table name\n        schema = MappingSchema(schema={\"[Fo]\": {\"x\": \"int\"}}, dialect=\"tsql\")\n        self.assertEqual(\n            schema.column_names(\"[Fo]\"), schema.column_names(\"`fo`\", dialect=\"clickhouse\")\n        )\n\n        # Check that all column identifiers are normalized to lowercase for BigQuery, even quoted\n        # ones. Also, ensure that tables aren't normalized, since they're case-sensitive by default.\n        schema = MappingSchema(schema={\"Foo\": {\"`BaR`\": \"int\"}}, dialect=\"bigquery\")\n        self.assertEqual(schema.column_names(\"Foo\"), [\"bar\"])\n        self.assertEqual(schema.column_names(\"foo\"), [])\n\n        # Check that the schema's normalization setting can be overridden\n        schema = MappingSchema(schema={\"X\": {\"y\": \"int\"}}, normalize=False, dialect=\"snowflake\")\n        self.assertEqual(schema.column_names(\"x\", normalize=True), [\"y\"])\n\n        # Check that identifiers in nested data types are normalized\n        # BigQuery: STRUCT field names should be lowercased\n        schema = MappingSchema({\"t\": {\"col\": \"STRUCT<FooBar INT>\"}}, dialect=\"bigquery\")\n        col_type = schema.get_column_type(\"t\", \"col\")\n        self.assertEqual(col_type.expressions[0].name, \"foobar\")\n\n        # Snowflake: STRUCT field names should be uppercased\n        schema = MappingSchema({\"t\": {\"col\": \"STRUCT<FooBar INT>\"}}, dialect=\"snowflake\")\n        col_type = schema.get_column_type(\"T\", \"COL\")\n        self.assertEqual(col_type.expressions[0].name, \"FOOBAR\")\n\n        # ClickHouse: STRUCT field names should preserve case (case-sensitive)\n        schema = MappingSchema({\"t\": {\"col\": \"STRUCT<FooBar INT>\"}}, dialect=\"clickhouse\")\n        col_type = schema.get_column_type(\"t\", \"col\")\n        self.assertEqual(col_type.expressions[0].name, \"FooBar\")\n\n        # Nested STRUCT field names should also be normalized\n        schema = MappingSchema(\n            {\"t\": {\"col\": \"STRUCT<Outer STRUCT<Inner INT>>\"}}, dialect=\"bigquery\"\n        )\n        col_type = schema.get_column_type(\"t\", \"col\")\n        self.assertEqual(col_type.expressions[0].name, \"outer\")\n        self.assertEqual(col_type.expressions[0].kind.expressions[0].name, \"inner\")\n\n        # ARRAY of STRUCT field names should also be normalized\n        schema = MappingSchema({\"t\": {\"col\": \"ARRAY<STRUCT<FooBar INT>>\"}}, dialect=\"bigquery\")\n        col_type = schema.get_column_type(\"t\", \"col\")\n        struct_type = col_type.expressions[0]\n        self.assertEqual(struct_type.expressions[0].name, \"foobar\")\n\n    def test_same_number_of_qualifiers(self):\n        schema = MappingSchema({\"x\": {\"y\": {\"c1\": \"int\"}}})\n\n        with self.assertRaises(SchemaError) as ctx:\n            schema.add_table(\"z\", {\"c2\": \"int\"})\n\n        self.assertEqual(\n            str(ctx.exception),\n            \"Table z must match the schema's nesting level: 2.\",\n        )\n\n        schema = MappingSchema()\n        schema.add_table(\"x.y\", {\"c1\": \"int\"})\n\n        with self.assertRaises(SchemaError) as ctx:\n            schema.add_table(\"z\", {\"c2\": \"int\"})\n\n        self.assertEqual(\n            str(ctx.exception),\n            \"Table z must match the schema's nesting level: 2.\",\n        )\n\n        with self.assertRaises(SchemaError) as ctx:\n            MappingSchema({\"x\": {\"y\": {\"c1\": \"int\"}}, \"z\": {\"c2\": \"int\"}})\n\n        self.assertEqual(\n            str(ctx.exception),\n            \"Table z must match the schema's nesting level: 2.\",\n        )\n\n        with self.assertRaises(SchemaError) as ctx:\n            MappingSchema(\n                {\n                    \"catalog\": {\n                        \"db\": {\"tbl\": {\"col\": \"a\"}},\n                    },\n                    \"tbl2\": {\"col\": \"b\"},\n                },\n            )\n        self.assertEqual(\n            str(ctx.exception),\n            \"Table tbl2 must match the schema's nesting level: 3.\",\n        )\n\n        with self.assertRaises(SchemaError) as ctx:\n            MappingSchema(\n                {\n                    \"tbl2\": {\"col\": \"b\"},\n                    \"catalog\": {\n                        \"db\": {\"tbl\": {\"col\": \"a\"}},\n                    },\n                },\n            )\n        self.assertEqual(\n            str(ctx.exception),\n            \"Table catalog.db.tbl must match the schema's nesting level: 1.\",\n        )\n\n    def test_has_column(self):\n        schema = MappingSchema({\"x\": {\"c\": \"int\"}})\n        self.assertTrue(schema.has_column(\"x\", exp.column(\"c\")))\n        self.assertFalse(schema.has_column(\"x\", exp.column(\"k\")))\n\n    def test_find(self):\n        schema = MappingSchema({\"x\": {\"c\": \"int\"}})\n        found = schema.find(exp.to_table(\"x\"))\n        self.assertEqual(found, {\"c\": \"int\"})\n        found = schema.find(exp.to_table(\"x\"), ensure_data_types=True)\n        self.assertEqual(found, {\"c\": exp.DataType.build(\"int\")})\n"
  },
  {
    "path": "tests/test_serde.py",
    "content": "import json\nimport pickle\nimport unittest\n\nfrom sqlglot import exp, parse_one\nfrom sqlglot.optimizer.annotate_types import annotate_types\nfrom tests.helpers import load_sql_fixtures\n\nimport sqlglot.expressions.core as _core_module\n\n_EXPRESSION_IS_COMPILED = getattr(_core_module, \"__file__\", \"\").endswith(\".so\")\n\n\nif not _EXPRESSION_IS_COMPILED:\n\n    class CustomExpression(exp.Expression): ...\n\n\nclass TestSerde(unittest.TestCase):\n    def dump_load(self, expression):\n        return exp.Expr.load(json.loads(json.dumps(expression.dump())))\n\n    def test_serde(self):\n        for sql in load_sql_fixtures(\"identity.sql\"):\n            with self.subTest(sql):\n                before = parse_one(sql)\n                after = self.dump_load(before)\n                self.assertEqual(repr(before), repr(after))\n\n    @unittest.skipIf(_EXPRESSION_IS_COMPILED, \"mypyc compiled expressions cannot be subclassed\")\n    def test_custom_expression(self):\n        before = CustomExpression()\n        after = self.dump_load(before)\n        self.assertEqual(before, after)\n\n    def test_type_annotations(self):\n        before = annotate_types(parse_one(\"CAST('1' AS STRUCT<x ARRAY<INT>>)\"))\n        after = self.dump_load(before)\n        self.assertEqual(before.type, after.type)\n        self.assertEqual(before.this.type, after.this.type)\n\n    def test_meta(self):\n        before = parse_one(\"SELECT * FROM X\")\n        before.meta[\"x\"] = 1\n        after = self.dump_load(before)\n        self.assertEqual(before.meta, after.meta)\n\n    def test_recursion(self):\n        sql = \"SELECT 1\"\n        sql += \" UNION ALL SELECT 1\" * 5000\n        expr = parse_one(sql)\n        before = expr.sql()\n        self.assertEqual(before, self.dump_load(expr).sql())\n        self.assertEqual(before, pickle.loads(pickle.dumps(expr)).sql())\n"
  },
  {
    "path": "tests/test_time.py",
    "content": "import unittest\nimport sys\n\nfrom sqlglot.time import format_time, subsecond_precision\n\n\nclass TestTime(unittest.TestCase):\n    def test_format_time(self):\n        self.assertEqual(format_time(\"\", {}), None)\n        self.assertEqual(format_time(\" \", {}), \" \")\n        mapping = {\"a\": \"b\", \"aa\": \"c\"}\n        self.assertEqual(format_time(\"a\", mapping), \"b\")\n        self.assertEqual(format_time(\"aa\", mapping), \"c\")\n        self.assertEqual(format_time(\"aaada\", mapping), \"cbdb\")\n        self.assertEqual(format_time(\"da\", mapping), \"db\")\n\n    def test_subsecond_precision(self):\n        self.assertEqual(6, subsecond_precision(\"2023-01-01 12:13:14.123456+00:00\"))\n        self.assertEqual(3, subsecond_precision(\"2023-01-01 12:13:14.123+00:00\"))\n        self.assertEqual(0, subsecond_precision(\"2023-01-01 12:13:14+00:00\"))\n        self.assertEqual(0, subsecond_precision(\"2023-01-01 12:13:14\"))\n        self.assertEqual(0, subsecond_precision(\"garbage\"))\n\n    @unittest.skipUnless(\n        sys.version_info >= (3, 11),\n        \"Python 3.11 relaxed datetime.fromisoformat() parsing with regards to microseconds\",\n    )\n    def test_subsecond_precision_python311(self):\n        # ref: https://docs.python.org/3/whatsnew/3.11.html#datetime\n        self.assertEqual(6, subsecond_precision(\"2023-01-01 12:13:14.123456789+00:00\"))\n        self.assertEqual(6, subsecond_precision(\"2023-01-01 12:13:14.12345+00:00\"))\n        self.assertEqual(6, subsecond_precision(\"2023-01-01 12:13:14.1234+00:00\"))\n        self.assertEqual(3, subsecond_precision(\"2023-01-01 12:13:14.12+00:00\"))\n        self.assertEqual(3, subsecond_precision(\"2023-01-01 12:13:14.1+00:00\"))\n"
  },
  {
    "path": "tests/test_tokens.py",
    "content": "import unittest\n\nfrom sqlglot.dialects import BigQuery\nfrom sqlglot.errors import TokenError\nfrom sqlglot.tokens import Tokenizer, TokenType\n\n\nclass TestTokens(unittest.TestCase):\n    def test_space_keywords(self):\n        for string, length in (\n            (\"group bys\", 2),\n            (\" group bys\", 2),\n            (\" group bys \", 2),\n            (\"group by)\", 2),\n            (\"group bys)\", 3),\n            (\"group \\r\", 1),\n        ):\n            tokens = Tokenizer().tokenize(string)\n            self.assertTrue(\"GROUP\" in tokens[0].text.upper())\n            self.assertEqual(len(tokens), length)\n\n    def test_comment_attachment(self):\n        tokenizer = Tokenizer()\n        sql_comment = [\n            (\"/*comment*/ foo\", [\"comment\"]),\n            (\"/*comment*/ foo --test\", [\"comment\", \"test\"]),\n            (\"--comment\\nfoo --test\", [\"comment\", \"test\"]),\n            (\"foo --comment\", [\"comment\"]),\n            (\"foo\", []),\n            (\"foo /*comment 1*/ /*comment 2*/\", [\"comment 1\", \"comment 2\"]),\n            (\"foo\\n-- comment\", [\" comment\"]),\n            (\"1 /*/2 */\", [\"/2 \"]),\n            (\"1\\n/*comment*/;\", [\"comment\"]),\n        ]\n\n        for sql, comment in sql_comment:\n            self.assertEqual(tokenizer.tokenize(sql)[0].comments, comment)\n\n    def test_token_line_col(self):\n        tokens = Tokenizer().tokenize(\n            \"\"\"SELECT /*\nline break\n*/\n'x\n y',\nx\"\"\"\n        )\n\n        self.assertEqual(tokens[0].line, 1)\n        self.assertEqual(tokens[0].col, 6)\n        self.assertEqual(tokens[1].line, 5)\n        self.assertEqual(tokens[1].col, 3)\n        self.assertEqual(tokens[2].line, 5)\n        self.assertEqual(tokens[2].col, 4)\n        self.assertEqual(tokens[3].line, 6)\n        self.assertEqual(tokens[3].col, 1)\n\n        tokens = Tokenizer().tokenize(\"SELECT .\")\n\n        self.assertEqual(tokens[1].line, 1)\n        self.assertEqual(tokens[1].col, 8)\n\n        self.assertEqual(Tokenizer().tokenize(\"'''abc'\")[0].start, 0)\n        self.assertEqual(Tokenizer().tokenize(\"'''abc'\")[0].end, 6)\n        self.assertEqual(Tokenizer().tokenize(\"'abc'\")[0].start, 0)\n\n        tokens = Tokenizer().tokenize(\"SELECT\\r\\n  1,\\r\\n  2\")\n\n        self.assertEqual(tokens[0].line, 1)\n        self.assertEqual(tokens[0].col, 6)\n        self.assertEqual(tokens[1].line, 2)\n        self.assertEqual(tokens[1].col, 3)\n        self.assertEqual(tokens[2].line, 2)\n        self.assertEqual(tokens[2].col, 4)\n        self.assertEqual(tokens[3].line, 3)\n        self.assertEqual(tokens[3].col, 3)\n\n        tokens = Tokenizer().tokenize(\"  SELECT\\n    100\")\n        self.assertEqual(tokens[0].line, 1)\n        self.assertEqual(tokens[0].col, 8)\n        self.assertEqual(tokens[1].line, 2)\n        self.assertEqual(tokens[1].col, 7)\n\n    def test_crlf(self):\n        tokens = Tokenizer().tokenize(\"SELECT a\\r\\nFROM b\")\n        tokens = [(token.token_type, token.text) for token in tokens]\n\n        self.assertEqual(\n            tokens,\n            [\n                (TokenType.SELECT, \"SELECT\"),\n                (TokenType.VAR, \"a\"),\n                (TokenType.FROM, \"FROM\"),\n                (TokenType.VAR, \"b\"),\n            ],\n        )\n\n        for simple_query in (\"SELECT 1\\r\\n\", \"\\r\\nSELECT 1\"):\n            tokens = Tokenizer().tokenize(simple_query)\n            tokens = [(token.token_type, token.text) for token in tokens]\n\n            self.assertEqual(\n                tokens,\n                [\n                    (TokenType.SELECT, \"SELECT\"),\n                    (TokenType.NUMBER, \"1\"),\n                ],\n            )\n\n    def test_command(self):\n        tokens = Tokenizer().tokenize(\"SHOW;\")\n        self.assertEqual(tokens[0].token_type, TokenType.SHOW)\n        self.assertEqual(tokens[1].token_type, TokenType.SEMICOLON)\n\n        tokens = Tokenizer().tokenize(\"EXECUTE\")\n        self.assertEqual(tokens[0].token_type, TokenType.EXECUTE)\n        self.assertEqual(len(tokens), 1)\n\n        tokens = Tokenizer().tokenize(\"FETCH;SHOW;\")\n        self.assertEqual(tokens[0].token_type, TokenType.FETCH)\n        self.assertEqual(tokens[1].token_type, TokenType.SEMICOLON)\n        self.assertEqual(tokens[2].token_type, TokenType.SHOW)\n        self.assertEqual(tokens[3].token_type, TokenType.SEMICOLON)\n\n    def test_error_msg(self):\n        with self.assertRaisesRegex(TokenError, \"Error tokenizing 'select /'\"):\n            Tokenizer().tokenize(\"select /*\")\n\n    def test_jinja(self):\n        # Check that {#, #} are treated as token delimiters, even though BigQuery overrides COMMENTS\n        tokenizer = BigQuery.Tokenizer()\n\n        tokens = tokenizer.tokenize(\n            \"\"\"\n            SELECT\n               {{ x }},\n               {{- x -}},\n               {# it's a comment #}\n               {% for x in y -%}\n               a {{+ b }}\n               {% endfor %};\n        \"\"\"\n        )\n\n        tokens = [(token.token_type, token.text) for token in tokens]\n\n        self.assertEqual(\n            tokens,\n            [\n                (TokenType.SELECT, \"SELECT\"),\n                (TokenType.L_BRACE, \"{\"),\n                (TokenType.L_BRACE, \"{\"),\n                (TokenType.VAR, \"x\"),\n                (TokenType.R_BRACE, \"}\"),\n                (TokenType.R_BRACE, \"}\"),\n                (TokenType.COMMA, \",\"),\n                (TokenType.BLOCK_START, \"{{-\"),\n                (TokenType.VAR, \"x\"),\n                (TokenType.BLOCK_END, \"-}}\"),\n                (TokenType.COMMA, \",\"),\n                (TokenType.BLOCK_START, \"{%\"),\n                (TokenType.FOR, \"for\"),\n                (TokenType.VAR, \"x\"),\n                (TokenType.IN, \"in\"),\n                (TokenType.VAR, \"y\"),\n                (TokenType.BLOCK_END, \"-%}\"),\n                (TokenType.VAR, \"a\"),\n                (TokenType.BLOCK_START, \"{{+\"),\n                (TokenType.VAR, \"b\"),\n                (TokenType.R_BRACE, \"}\"),\n                (TokenType.R_BRACE, \"}\"),\n                (TokenType.BLOCK_START, \"{%\"),\n                (TokenType.VAR, \"endfor\"),\n                (TokenType.BLOCK_END, \"%}\"),\n                (TokenType.SEMICOLON, \";\"),\n            ],\n        )\n\n        tokens = tokenizer.tokenize(\"\"\"'{{ var('x') }}'\"\"\")\n        tokens = [(token.token_type, token.text) for token in tokens]\n        self.assertEqual(\n            tokens,\n            [\n                (TokenType.STRING, \"{{ var(\"),\n                (TokenType.VAR, \"x\"),\n                (TokenType.STRING, \") }}\"),\n            ],\n        )\n\n    def test_partial_token_list(self):\n        tokenizer = Tokenizer()\n\n        try:\n            # This is expected to fail due to the unbalanced string quotes\n            tokenizer.tokenize(\"foo 'bar\")\n        except TokenError as e:\n            self.assertIn(\"Error tokenizing 'foo 'ba'\", str(e))\n\n        partial_tokens = tokenizer.tokens\n\n        self.assertEqual(len(partial_tokens), 1)\n        self.assertEqual(partial_tokens[0].token_type, TokenType.VAR)\n        self.assertEqual(partial_tokens[0].text, \"foo\")\n\n    def test_unicode_identifiers(self):\n        tokens = Tokenizer().tokenize(\"SELECT café FROM t\")\n        self.assertEqual(next(t for t in tokens if t.token_type == TokenType.VAR).text, \"café\")\n\n    def test_token_repr(self):\n        # Ensures both the Python and the Rust tokenizer produce a human-friendly representation\n        self.assertEqual(\n            repr(Tokenizer().tokenize(\"foo\")),\n            \"[<Token token_type: TokenType.VAR, text: foo, line: 1, col: 3, start: 0, end: 2, comments: []>]\",\n        )\n"
  },
  {
    "path": "tests/test_transforms.py",
    "content": "import unittest\n\nfrom sqlglot import parse_one, expressions as exp\nfrom sqlglot.transforms import (\n    eliminate_distinct_on,\n    eliminate_join_marks,\n    eliminate_qualify,\n    eliminate_window_clause,\n    inherit_struct_field_names,\n    remove_precision_parameterized_types,\n)\n\n\nclass TestTransforms(unittest.TestCase):\n    maxDiff = None\n\n    def validate(self, transform, sql, target, dialect=None):\n        with self.subTest(f\"{dialect} - {sql}\"):\n            self.assertEqual(\n                exp.maybe_parse(sql, dialect=dialect).transform(transform).sql(dialect=dialect),\n                target,\n            )\n\n    def test_eliminate_distinct_on(self):\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT ON (a) a, b FROM x ORDER BY c DESC\",\n            \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT ON (a) a, b FROM x\",\n            \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY a) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT ON (a, b) a, b FROM x ORDER BY c DESC\",\n            \"SELECT a, b FROM (SELECT a AS a, b AS b, ROW_NUMBER() OVER (PARTITION BY a, b ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT a, b FROM x ORDER BY c DESC\",\n            \"SELECT DISTINCT a, b FROM x ORDER BY c DESC\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT ON (_row_number) _row_number FROM x ORDER BY c DESC\",\n            \"SELECT _row_number FROM (SELECT _row_number AS _row_number, ROW_NUMBER() OVER (PARTITION BY _row_number ORDER BY c DESC) AS _row_number_2 FROM x) AS _t WHERE _row_number_2 = 1\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT ON (x.a, x.b) x.a, x.b FROM x ORDER BY c DESC\",\n            \"SELECT a, b FROM (SELECT x.a AS a, x.b AS b, ROW_NUMBER() OVER (PARTITION BY x.a, x.b ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT ON (a) x.a, y.a FROM x CROSS JOIN y ORDER BY c DESC\",\n            \"SELECT a, a_2 FROM (SELECT x.a AS a, y.a AS a_2, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x CROSS JOIN y) AS _t WHERE _row_number = 1\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT ON (a) a, a + b FROM x ORDER BY c DESC\",\n            \"SELECT a, _col FROM (SELECT a AS a, a + b AS _col, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            \"SELECT DISTINCT ON (a) * FROM x ORDER BY c DESC\",\n            \"SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1\",\n        )\n        self.validate(\n            eliminate_distinct_on,\n            'SELECT DISTINCT ON (a) a AS \"A\", b FROM x ORDER BY c DESC',\n            'SELECT \"A\", b FROM (SELECT a AS \"A\", b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1',\n        )\n        self.validate(\n            eliminate_distinct_on,\n            'SELECT DISTINCT ON (a) \"A\", b FROM x ORDER BY c DESC',\n            'SELECT \"A\", b FROM (SELECT \"A\" AS \"A\", b AS b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY c DESC) AS _row_number FROM x) AS _t WHERE _row_number = 1',\n        )\n\n    def test_eliminate_qualify(self):\n        self.validate(\n            eliminate_qualify,\n            \"SELECT i, a + 1 FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p) = 1\",\n            \"SELECT i, _c FROM (SELECT i, a + 1 AS _c, ROW_NUMBER() OVER (PARTITION BY p) AS _w, p FROM qt) AS _t WHERE _w = 1\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT i FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) = 1 AND p = 0\",\n            \"SELECT i FROM (SELECT i, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS _w, p, o FROM qt) AS _t WHERE _w = 1 AND p = 0\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT i, p, o FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) = 1\",\n            \"SELECT i, p, o FROM (SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS _w FROM qt) AS _t WHERE _w = 1\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS row_num FROM qt QUALIFY row_num = 1\",\n            \"SELECT i, p, o, row_num FROM (SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS row_num FROM qt) AS _t WHERE row_num = 1\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT * FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) = 1\",\n            \"SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS _w FROM qt) AS _t WHERE _w = 1\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT c2, SUM(c3) OVER (PARTITION BY c2) AS r FROM t1 WHERE c3 < 4 GROUP BY c2, c3 HAVING SUM(c1) > 3 QUALIFY r IN (SELECT MIN(c1) FROM test GROUP BY c2 HAVING MIN(c1) > 3)\",\n            \"SELECT c2, r FROM (SELECT c2, SUM(c3) OVER (PARTITION BY c2) AS r, c1 FROM t1 WHERE c3 < 4 GROUP BY c2, c3 HAVING SUM(c1) > 3) AS _t WHERE r IN (SELECT MIN(c1) FROM test GROUP BY c2 HAVING MIN(c1) > 3)\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT x FROM y QUALIFY ROW_NUMBER() OVER (PARTITION BY p)\",\n            \"SELECT x FROM (SELECT x, ROW_NUMBER() OVER (PARTITION BY p) AS _w, p FROM y) AS _t WHERE _w\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT x AS z FROM y QUALIFY ROW_NUMBER() OVER (PARTITION BY z)\",\n            \"SELECT z FROM (SELECT x AS z, ROW_NUMBER() OVER (PARTITION BY x) AS _w FROM y) AS _t WHERE _w\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT SOME_UDF(x) AS z FROM y QUALIFY ROW_NUMBER() OVER (PARTITION BY x ORDER BY z)\",\n            \"SELECT z FROM (SELECT SOME_UDF(x) AS z, ROW_NUMBER() OVER (PARTITION BY x ORDER BY SOME_UDF(x)) AS _w, x FROM y) AS _t WHERE _w\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT x, t, x || t AS z FROM y QUALIFY ROW_NUMBER() OVER (PARTITION BY x ORDER BY z DESC)\",\n            \"SELECT x, t, z FROM (SELECT x, t, x || t AS z, ROW_NUMBER() OVER (PARTITION BY x ORDER BY x || t DESC) AS _w FROM y) AS _t WHERE _w\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"SELECT y.x AS x, y.t AS z FROM y QUALIFY ROW_NUMBER() OVER (PARTITION BY x ORDER BY x DESC, z)\",\n            \"SELECT x, z FROM (SELECT y.x AS x, y.t AS z, ROW_NUMBER() OVER (PARTITION BY y.x ORDER BY y.x DESC, y.t) AS _w FROM y) AS _t WHERE _w\",\n        )\n        self.validate(\n            eliminate_qualify,\n            \"select max(col) over (partition by col_id) as col, from some_table qualify row_number() over (partition by col_id order by col asc)=1\",\n            \"SELECT col FROM (SELECT MAX(col) OVER (PARTITION BY col_id) AS col, ROW_NUMBER() OVER (PARTITION BY col_id ORDER BY MAX(col) OVER (PARTITION BY col_id) ASC) AS _w, col_id FROM some_table) AS _t WHERE _w = 1\",\n        )\n\n    def test_remove_precision_parameterized_types(self):\n        self.validate(\n            remove_precision_parameterized_types,\n            \"SELECT CAST(1 AS DECIMAL(10, 2)), CAST('13' AS VARCHAR(10))\",\n            \"SELECT CAST(1 AS DECIMAL), CAST('13' AS VARCHAR)\",\n        )\n\n    def test_eliminate_join_marks(self):\n        for dialect in (\"oracle\", \"redshift\"):\n            # No join marks => query remains unaffected\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT a.f1, b.f2 FROM a JOIN b ON a.id = b.id WHERE a.blabla = 'a'\",\n                \"SELECT a.f1, b.f2 FROM a JOIN b ON a.id = b.id WHERE a.blabla = 'a'\",\n                dialect,\n            )\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT T1.d, T2.c FROM T1, T2 WHERE T1.x = T2.x (+) and T2.y (+) > 5\",\n                \"SELECT T1.d, T2.c FROM T1 LEFT JOIN T2 ON T1.x = T2.x AND T2.y > 5\",\n                dialect,\n            )\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT T1.d, T2.c FROM T1, T2 WHERE T1.x (+) = T2.x and T2.y > 5\",\n                \"SELECT T1.d, T2.c FROM T2 LEFT JOIN T1 ON T1.x = T2.x WHERE T2.y > 5\",\n                dialect,\n            )\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT T1.d, T2.c FROM T1, T2 WHERE T1.x = T2.x (+) and T2.y (+) IS NULL\",\n                \"SELECT T1.d, T2.c FROM T1 LEFT JOIN T2 ON T1.x = T2.x AND T2.y IS NULL\",\n                dialect,\n            )\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT T1.d, T2.c FROM T1, T2 WHERE T1.x = T2.x (+) and T2.y IS NULL\",\n                \"SELECT T1.d, T2.c FROM T1 LEFT JOIN T2 ON T1.x = T2.x WHERE T2.y IS NULL\",\n                dialect,\n            )\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT T1.d, T2.c FROM T1, T2 WHERE T1.x = T2.x (+) and T1.Z > 4\",\n                \"SELECT T1.d, T2.c FROM T1 LEFT JOIN T2 ON T1.x = T2.x WHERE T1.Z > 4\",\n                dialect,\n            )\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT * FROM table1, table2 WHERE table1.col = table2.col(+)\",\n                \"SELECT * FROM table1 LEFT JOIN table2 ON table1.col = table2.col\",\n                dialect,\n            )\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT * FROM table1, table2, table3, table4 WHERE table1.col = table2.col(+) and table2.col >= table3.col(+) and table1.col = table4.col(+)\",\n                \"SELECT * FROM table1 LEFT JOIN table2 ON table1.col = table2.col LEFT JOIN table3 ON table2.col >= table3.col LEFT JOIN table4 ON table1.col = table4.col\",\n                dialect,\n            )\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT * FROM table1, table2, table3 WHERE table1.col = table2.col(+) and table2.col >= table3.col(+)\",\n                \"SELECT * FROM table1 LEFT JOIN table2 ON table1.col = table2.col LEFT JOIN table3 ON table2.col >= table3.col\",\n                dialect,\n            )\n            # 2 join marks on one side of predicate\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT * FROM table1, table2 WHERE table1.col = table2.col1(+) + table2.col2(+)\",\n                \"SELECT * FROM table1 LEFT JOIN table2 ON table1.col = table2.col1 + table2.col2\",\n                dialect,\n            )\n            # join mark and expression\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT * FROM table1, table2 WHERE table1.col = table2.col1(+) + 25\",\n                \"SELECT * FROM table1 LEFT JOIN table2 ON table1.col = table2.col1 + 25\",\n                dialect,\n            )\n            # eliminate join mark while preserving non-participating joins\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT * FROM a, b, c WHERE a.id = b.id AND b.id(+) = c.id\",\n                \"SELECT * FROM a LEFT JOIN b ON b.id = c.id CROSS JOIN c WHERE a.id = b.id\",\n                dialect,\n            )\n\n            alias = \"AS \" if dialect != \"oracle\" else \"\"\n            self.validate(\n                eliminate_join_marks,\n                \"SELECT table1.id, table2.cloumn1, table3.id FROM table1, table2, (SELECT tableInner1.id FROM tableInner1, tableInner2 WHERE tableInner1.id = tableInner2.id(+)) AS table3 WHERE table1.id = table2.id(+) and table1.id = table3.id(+)\",\n                f\"SELECT table1.id, table2.cloumn1, table3.id FROM table1 LEFT JOIN table2 ON table1.id = table2.id LEFT JOIN (SELECT tableInner1.id FROM tableInner1 LEFT JOIN tableInner2 ON tableInner1.id = tableInner2.id) {alias}table3 ON table1.id = table3.id\",\n                dialect,\n            )\n\n            # if multiple conditions, we check that after transformations the tree remains consistent\n            s = \"select a.id from a, b where a.id = b.id (+) AND b.d (+) = const\"\n            tree = eliminate_join_marks(parse_one(s, dialect=dialect))\n            assert all(type(t.parent_select) is exp.Select for t in tree.find_all(exp.Table))\n            assert (\n                tree.sql(dialect=dialect)\n                == \"SELECT a.id FROM a LEFT JOIN b ON a.id = b.id AND b.d = const\"\n            )\n\n            # validate parens\n            self.validate(\n                eliminate_join_marks,\n                \"select t1.a, t2.b from t1, t2 where (1 = 1) and (t1.id = t2.id1 (+))\",\n                \"SELECT t1.a, t2.b FROM t1 LEFT JOIN t2 ON t1.id = t2.id1 WHERE (1 = 1)\",\n                dialect,\n            )\n\n            # validate a CASE\n            self.validate(\n                eliminate_join_marks,\n                \"select t1.a, t2.b from t1, t2 where t1.id = case when t2.id (+) = 'n/a' then null else t2.id (+) end\",\n                \"SELECT t1.a, t2.b FROM t1 LEFT JOIN t2 ON t1.id = CASE WHEN t2.id = 'n/a' THEN NULL ELSE t2.id END\",\n                dialect,\n            )\n\n            # validate OR\n            self.validate(\n                eliminate_join_marks,\n                \"select t1.a, t2.b from t1, t2 where t1.id = t2.id1 (+) or t1.id = t2.id2 (+)\",\n                \"SELECT t1.a, t2.b FROM t1 LEFT JOIN t2 ON t1.id = t2.id1 OR t1.id = t2.id2\",\n                dialect,\n            )\n\n            # validate knockout\n            script = \"\"\"\n                    SELECT c.customer_name,\n                            (SELECT MAX(o.order_date)\n                            FROM orders o\n                            WHERE o.customer_id(+) = c.customer_id) AS latest_order_date\n                    FROM customers c\n                    \"\"\"\n            self.assertRaises(\n                AssertionError, eliminate_join_marks, parse_one(script, dialect=dialect)\n            )\n\n    def test_eliminate_window_clause(self):\n        self.validate(\n            eliminate_window_clause,\n            \"SELECT purchases, LAST_VALUE(item) OVER (d) AS most_popular FROM Produce WINDOW a AS (PARTITION BY purchases), b AS (a ORDER BY purchases), c AS (b ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING), d AS (c)\",\n            \"SELECT purchases, LAST_VALUE(item) OVER (PARTITION BY purchases ORDER BY purchases ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING) AS most_popular FROM Produce\",\n        )\n        self.validate(\n            eliminate_window_clause,\n            \"SELECT LAST_VALUE(c) OVER (a) AS c2 FROM (SELECT LAST_VALUE(i) OVER (a) AS c FROM p WINDOW a AS (PARTITION BY x)) AS q(c) WINDOW a AS (PARTITION BY y)\",\n            \"SELECT LAST_VALUE(c) OVER (PARTITION BY y) AS c2 FROM (SELECT LAST_VALUE(i) OVER (PARTITION BY x) AS c FROM p) AS q(c)\",\n        )\n\n    def test_inherit_struct_field_names(self):\n        def _parse_and_set_struct_name_inheritance(sql: str) -> exp.Expr:\n            ast = exp.maybe_parse(sql)\n            for array in ast.find_all(exp.Array):\n                array.set(\"struct_name_inheritance\", True)\n            return ast\n\n        # Basic case: field names inherited from first struct\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\n                \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob', 92), STRUCT('Diana', 95))\"\n            ),\n            \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS name, 92 AS score), STRUCT('Diana' AS name, 95 AS score))\",\n        )\n\n        # Single struct in array: no inheritance needed\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\n                \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score))\"\n            ),\n            \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score))\",\n        )\n\n        # Empty array: no change\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\"SELECT ARRAY()\"),\n            \"SELECT ARRAY()\",\n        )\n\n        # First struct has no field names: no inheritance\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\n                \"SELECT ARRAY(STRUCT('Alice', 85), STRUCT('Bob', 92))\"\n            ),\n            \"SELECT ARRAY(STRUCT('Alice', 85), STRUCT('Bob', 92))\",\n        )\n\n        # Mismatched field counts: skip inheritance\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\n                \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob'))\"\n            ),\n            \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob'))\",\n        )\n\n        # Struct already has field names: don't override\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\n                \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS fullname, 92 AS points))\"\n            ),\n            \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS fullname, 92 AS points))\",\n        )\n\n        # Mixed: some structs inherit, some already have names\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\n                \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob', 92), STRUCT('Carol' AS name, 88 AS score), STRUCT('Diana', 95))\"\n            ),\n            \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS name, 92 AS score), STRUCT('Carol' AS name, 88 AS score), STRUCT('Diana' AS name, 95 AS score))\",\n        )\n\n        # Non-struct elements: no change\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\"SELECT ARRAY(1, 2, 3)\"),\n            \"SELECT ARRAY(1, 2, 3)\",\n        )\n\n        # Multiple arrays: each processed independently\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\n                \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob', 92)), ARRAY(STRUCT('X' AS col), STRUCT('Y'))\"\n            ),\n            \"SELECT ARRAY(STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS name, 92 AS score)), ARRAY(STRUCT('X' AS col), STRUCT('Y' AS col))\",\n        )\n\n        # Partial field names in first struct: inherit only the named ones\n        self.validate(\n            inherit_struct_field_names,\n            _parse_and_set_struct_name_inheritance(\n                \"SELECT ARRAY(STRUCT('Alice' AS name, 85), STRUCT('Bob', 92))\"\n            ),\n            \"SELECT ARRAY(STRUCT('Alice' AS name, 85), STRUCT('Bob', 92))\",\n        )\n"
  },
  {
    "path": "tests/test_transpile.py",
    "content": "import os\nimport unittest\nfrom unittest import mock\n\nfrom sqlglot import parse_one, transpile\nfrom sqlglot.errors import ErrorLevel, ParseError, UnsupportedError\nfrom sqlglot.helper import logger as helper_logger\nfrom sqlglot.parser import logger as parser_logger\nfrom tests.helpers import (\n    assert_logger_contains,\n    load_sql_fixture_pairs,\n    load_sql_fixtures,\n)\n\n\nclass TestTranspile(unittest.TestCase):\n    file_dir = os.path.dirname(__file__)\n    fixtures_dir = os.path.join(file_dir, \"fixtures\")\n    maxDiff = None\n\n    def validate(self, sql, target, **kwargs):\n        self.assertEqual(transpile(sql, **kwargs)[0], target)\n\n    def test_weird_chars(self):\n        self.assertEqual(transpile(\"0Êß\")[0], \"0 AS Êß\")\n        self.assertEqual(\n            # Ideographic space after SELECT (\\u3000)\n            transpile(\"SELECT　* FROM t WHERE c = 1\")[0],\n            \"SELECT * FROM t WHERE c = 1\",\n        )\n\n    def test_alias(self):\n        self.assertEqual(transpile(\"SELECT SUM(y) KEEP\")[0], \"SELECT SUM(y) AS KEEP\")\n        self.assertEqual(transpile(\"SELECT 1 overwrite\")[0], \"SELECT 1 AS overwrite\")\n        self.assertEqual(transpile(\"SELECT 1 is\")[0], \"SELECT 1 AS is\")\n        self.assertEqual(transpile(\"SELECT 1 current_time\")[0], \"SELECT 1 AS current_time\")\n        self.assertEqual(\n            transpile(\"SELECT 1 current_timestamp\")[0], \"SELECT 1 AS current_timestamp\"\n        )\n        self.assertEqual(transpile(\"SELECT 1 current_date\")[0], \"SELECT 1 AS current_date\")\n        self.assertEqual(transpile(\"SELECT 1 current_datetime\")[0], \"SELECT 1 AS current_datetime\")\n        self.assertEqual(transpile(\"SELECT 1 row\")[0], \"SELECT 1 AS row\")\n\n        self.assertEqual(\n            transpile(\"SELECT 1 FROM a.b.table1 t UNPIVOT((c3) FOR c4 IN (a, b))\")[0],\n            \"SELECT 1 FROM a.b.table1 AS t UNPIVOT((c3) FOR c4 IN (a, b))\",\n        )\n\n        for key in (\"union\", \"from\", \"join\"):\n            with self.subTest(f\"alias {key}\"):\n                self.validate(f\"SELECT x AS {key}\", f\"SELECT x AS {key}\")\n                self.validate(f'SELECT x \"{key}\"', f'SELECT x AS \"{key}\"')\n\n                with self.assertRaises(ParseError):\n                    self.validate(f\"SELECT x {key}\", \"\")\n\n    def test_unary(self):\n        self.validate(\"+++1\", \"1\")\n        self.validate(\"+-1\", \"-1\")\n        self.validate(\"+- - -1\", \"- - -1\")\n\n    def test_paren(self):\n        with self.assertRaises(ParseError):\n            transpile(\"1 + (2 + 3\")\n            transpile(\"select f(\")\n\n    def test_some(self):\n        self.validate(\n            \"SELECT * FROM x WHERE a = SOME (SELECT 1)\",\n            \"SELECT * FROM x WHERE a = ANY(SELECT 1)\",\n        )\n\n    def test_leading_comma(self):\n        self.validate(\n            \"SELECT a, b, c FROM (SELECT a, b, c FROM t)\",\n            \"SELECT\\n\"\n            \"    a\\n\"\n            \"    , b\\n\"\n            \"    , c\\n\"\n            \"FROM (\\n\"\n            \"    SELECT\\n\"\n            \"        a\\n\"\n            \"        , b\\n\"\n            \"        , c\\n\"\n            \"    FROM t\\n\"\n            \")\",\n            leading_comma=True,\n            pretty=True,\n            pad=4,\n            indent=4,\n        )\n        self.validate(\n            \"SELECT FOO, BAR, BAZ\",\n            \"SELECT\\n  FOO\\n  , BAR\\n  , BAZ\",\n            leading_comma=True,\n            pretty=True,\n        )\n        self.validate(\n            \"SELECT FOO, /*x*/\\nBAR, /*y*/\\nBAZ\",\n            \"SELECT\\n  FOO /* x */\\n  , BAR /* y */\\n  , BAZ\",\n            leading_comma=True,\n            pretty=True,\n        )\n        # without pretty, this should be a no-op\n        self.validate(\n            \"SELECT FOO, BAR, BAZ\",\n            \"SELECT FOO, BAR, BAZ\",\n            leading_comma=True,\n        )\n\n    def test_space(self):\n        self.validate(\"SELECT MIN(3)>MIN(2)\", \"SELECT MIN(3) > MIN(2)\")\n        self.validate(\"SELECT MIN(3)>=MIN(2)\", \"SELECT MIN(3) >= MIN(2)\")\n        self.validate(\"SELECT 1>0\", \"SELECT 1 > 0\")\n        self.validate(\"SELECT 3>=3\", \"SELECT 3 >= 3\")\n        self.validate(\"SELECT a\\r\\nFROM b\", \"SELECT a FROM b\")\n\n    def test_comments(self):\n        self.validate(\n            \"select /* asfd /* asdf */ asdf */ 1\",\n            \"/* asfd / * asdf * / asdf */ SELECT 1\",\n        )\n        self.validate(\n            \"SELECT c /* foo */ AS alias\",\n            \"SELECT c AS alias /* foo */\",\n        )\n        self.validate(\n            \"SELECT c AS /* foo */ (a, b, c) FROM t\",\n            \"SELECT c AS (a, b, c) /* foo */ FROM t\",\n        )\n        self.validate(\n            \"SELECT * FROM t1\\n/*x*/\\nUNION ALL SELECT * FROM t2\",\n            \"SELECT * FROM t1 /* x */ UNION ALL SELECT * FROM t2\",\n        )\n        self.validate(\n            \"/* comment */ SELECT * FROM a UNION SELECT * FROM b\",\n            \"/* comment */ SELECT * FROM a UNION SELECT * FROM b\",\n        )\n        self.validate(\n            \"SELECT * FROM t1\\n/*x*/\\nINTERSECT ALL SELECT * FROM t2\",\n            \"SELECT * FROM t1 /* x */ INTERSECT ALL SELECT * FROM t2\",\n        )\n        self.validate(\n            \"SELECT\\n  foo\\n/* comments */\\n;\",\n            \"SELECT foo /* comments */\",\n        )\n        self.validate(\n            \"SELECT * FROM a INNER /* comments */ JOIN b\",\n            \"SELECT * FROM a /* comments */ INNER JOIN b\",\n        )\n        self.validate(\n            \"SELECT * FROM a LEFT /* comment 1 */ OUTER /* comment 2 */ JOIN b\",\n            \"SELECT * FROM a /* comment 1 */ /* comment 2 */ LEFT OUTER JOIN b\",\n        )\n        self.validate(\n            \"SELECT CASE /* test */ WHEN a THEN b ELSE c END\",\n            \"SELECT CASE WHEN a THEN b ELSE c END /* test */\",\n        )\n        self.validate(\"SELECT 1 /*/2 */\", \"SELECT 1 /* /2 */\")\n        self.validate(\"SELECT */*comment*/\", \"SELECT * /* comment */\")\n        self.validate(\n            \"SELECT * FROM table /*comment 1*/ /*comment 2*/\",\n            \"SELECT * FROM table /* comment 1 */ /* comment 2 */\",\n        )\n        self.validate(\"SELECT 1 FROM foo -- comment\", \"SELECT 1 FROM foo /* comment */\")\n        self.validate(\n            \"SELECT * FROM\\n/* comment */\\ndb.schema1.tbl PIVOT (SUM(a) FOR b IN ('x', 'y'))\",\n            \"SELECT * FROM db.schema1.tbl PIVOT(SUM(a) FOR b IN ('x', 'y')) /* comment */\",\n        )\n        self.validate(\"SELECT --+5\\nx FROM foo\", \"/* +5 */ SELECT x FROM foo\")\n        self.validate(\"SELECT --!5\\nx FROM foo\", \"/* !5 */ SELECT x FROM foo\")\n        self.validate(\n            \"SELECT 1 /* inline */ FROM foo -- comment\",\n            \"SELECT 1 /* inline */ FROM foo /* comment */\",\n        )\n        self.validate(\n            \"SELECT FUN(x) /*x*/, [1,2,3] /*y*/\", \"SELECT FUN(x) /* x */, ARRAY(1, 2, 3) /* y */\"\n        )\n        self.validate(\n            \"\"\"\n            SELECT 1 -- comment\n            FROM foo -- comment\n            \"\"\",\n            \"SELECT 1 /* comment */ FROM foo /* comment */\",\n        )\n        self.validate(\n            \"\"\"\n            SELECT 1 /* big comment\n             like this */\n            FROM foo -- comment\n            \"\"\",\n            \"\"\"SELECT 1 /* big comment\n             like this */ FROM foo /* comment */\"\"\",\n        )\n        self.validate(\n            \"select x from foo --       x\",\n            \"SELECT x FROM foo /*       x */\",\n        )\n        self.validate(\n            \"\"\"select x, --\n            from foo\"\"\",\n            \"SELECT x FROM foo\",\n        )\n        self.validate(\n            \"\"\"\n-- comment 1\n-- comment 2\n-- comment 3\nSELECT * FROM foo\n            \"\"\",\n            \"/* comment 1 */ /* comment 2 */ /* comment 3 */ SELECT * FROM foo\",\n        )\n        self.validate(\n            \"\"\"\n-- comment 1\n-- comment 2\n-- comment 3\nSELECT * FROM foo\"\"\",\n            \"\"\"/* comment 1 */ /* comment 2 */ /* comment 3 */\nSELECT\n  *\nFROM foo\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\nSELECT * FROM tbl /*line1\nline2\nline3*/ /*another comment*/ where 1=1 -- comment at the end\"\"\",\n            \"\"\"SELECT * FROM tbl /* line1\nline2\nline3 */ /* another comment */ WHERE 1 = 1 /* comment at the end */\"\"\",\n        )\n        self.validate(\n            \"\"\"\nSELECT * FROM tbl /*line1\nline2\nline3*/ /*another comment*/ where 1=1 -- comment at the end\"\"\",\n            \"\"\"SELECT\n  *\nFROM tbl /* line1\nline2\nline3 */ /* another comment */\nWHERE\n  1 = 1 /* comment at the end */\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\n            /* multi\n               line\n               comment\n            */\n            SELECT\n              tbl.cola /* comment 1 */ + tbl.colb /* comment 2 */,\n              CAST(x AS CHAR), # comment 3\n              y               -- comment 4\n            FROM\n              bar /* comment 5 */,\n              tbl #          comment 6\n            \"\"\",\n            \"\"\"/* multi\n               line\n               comment\n            */\nSELECT\n  tbl.cola /* comment 1 */ + tbl.colb /* comment 2 */,\n  CAST(x AS CHAR), /* comment 3 */\n  y /* comment 4 */\nFROM bar /* comment 5 */, tbl /*          comment 6 */\"\"\",\n            read=\"mysql\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\n            SELECT a FROM b\n            WHERE foo\n            -- comment 1\n            AND bar\n            -- comment 2\n            AND bla\n            -- comment 3\n            LIMIT 10\n            ;\n            \"\"\",\n            \"SELECT a FROM b WHERE foo AND /* comment 1 */ bar AND /* comment 2 */ bla LIMIT 10 /* comment 3 */\",\n        )\n        self.validate(\n            \"\"\"\n            SELECT a FROM b WHERE foo\n            -- comment 1\n            \"\"\",\n            \"SELECT a FROM b WHERE foo /* comment 1 */\",\n        )\n        self.validate(\n            \"\"\"\n            select a\n            -- from\n            from b\n            -- where\n            where foo\n            -- comment 1\n            and bar\n            -- comment 2\n            and bla\n            \"\"\",\n            \"\"\"SELECT\n  a\n/* from */\nFROM b\n/* where */\nWHERE\n  foo AND /* comment 1 */ bar AND /* comment 2 */ bla\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\n            -- test\n            WITH v AS (\n              SELECT\n                1 AS literal\n            )\n            SELECT\n              *\n            FROM v\n            \"\"\",\n            \"\"\"/* test */\nWITH v AS (\n  SELECT\n    1 AS literal\n)\nSELECT\n  *\nFROM v\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"(/* 1 */ 1 ) /* 2 */\",\n            \"(1) /* 1 */ /* 2 */\",\n        )\n        self.validate(\n            \"select * from t where not a in (23) /*test*/ and b in (14)\",\n            \"SELECT * FROM t WHERE NOT a IN (23) /* test */ AND b IN (14)\",\n        )\n        self.validate(\n            \"select * from t where a in (23) /*test*/ and b in (14)\",\n            \"SELECT * FROM t WHERE a IN (23) /* test */ AND b IN (14)\",\n        )\n        self.validate(\n            \"select * from t where ((condition = 1)/*test*/)\",\n            \"SELECT * FROM t WHERE ((condition = 1) /* test */)\",\n        )\n        self.validate(\n            \"SELECT 1 // hi this is a comment\",\n            \"SELECT 1 /* hi this is a comment */\",\n            read=\"snowflake\",\n        )\n        self.validate(\n            \"-- comment\\nDROP TABLE IF EXISTS foo\",\n            \"/* comment */ DROP TABLE IF EXISTS foo\",\n        )\n        self.validate(\n            \"\"\"\n            -- comment1\n            -- comment2\n\n            -- comment3\n            DROP TABLE IF EXISTS db.tba\n            \"\"\",\n            \"\"\"/* comment1 */ /* comment2 */ /* comment3 */\nDROP TABLE IF EXISTS db.tba\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\n            -- comment4\n            CREATE TABLE db.tba AS\n            SELECT a, b, c\n            FROM tb_01\n            WHERE\n            -- comment5\n              a = 1 AND b = 2 --comment6\n              -- and c = 1\n            -- comment7\n            ;\n            \"\"\",\n            \"\"\"/* comment4 */\nCREATE TABLE db.tba AS\nSELECT\n  a,\n  b,\n  c\nFROM tb_01\nWHERE\n  a /* comment5 */ = 1 AND b = 2 /* comment6 */ /* and c = 1 */ /* comment7 */\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\n            SELECT\n               -- This is testing comments\n                col,\n            -- 2nd testing comments\n            CASE WHEN a THEN b ELSE c END as d\n            FROM t\n            \"\"\",\n            \"\"\"SELECT\n  col, /* This is testing comments */\n  CASE WHEN a THEN b ELSE c END AS d /* 2nd testing comments */\nFROM t\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\n            SELECT * FROM a\n            -- comments\n            INNER JOIN b\n            \"\"\",\n            \"\"\"SELECT\n  *\nFROM a\n/* comments */\nINNER JOIN b\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"SELECT * FROM a LEFT /* comment 1 */ OUTER /* comment 2 */ JOIN b\",\n            \"\"\"SELECT\n  *\nFROM a\n/* comment 1 */ /* comment 2 */\nLEFT OUTER JOIN b\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"SELECT\\n  a /* sqlglot.meta case_sensitive */ -- noqa\\nFROM tbl\",\n            \"\"\"SELECT\n  a /* sqlglot.meta case_sensitive */ /* noqa */\nFROM tbl\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\nSELECT\n  'hotel1' AS hotel,\n  *\nFROM dw_1_dw_1_1.exactonline_1.transactionlines\n/*\n    UNION ALL\n    SELECT\n      'Thon Partner Hotel Jølster' AS hotel,\n      name,\n      date,\n      CAST(identifier AS VARCHAR) AS identifier,\n      value\n    FROM d2o_889_oupjr_1348.public.accountvalues_forecast\n*/\nUNION ALL\nSELECT\n  'hotel2' AS hotel,\n  *\nFROM dw_1_dw_1_1.exactonline_2.transactionlines\"\"\",\n            \"\"\"SELECT\n  'hotel1' AS hotel,\n  *\nFROM dw_1_dw_1_1.exactonline_1.transactionlines\n/*\n    UNION ALL\n    SELECT\n      'Thon Partner Hotel Jølster' AS hotel,\n      name,\n      date,\n      CAST(identifier AS VARCHAR) AS identifier,\n      value\n    FROM d2o_889_oupjr_1348.public.accountvalues_forecast\n*/\nUNION ALL\nSELECT\n  'hotel2' AS hotel,\n  *\nFROM dw_1_dw_1_1.exactonline_2.transactionlines\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"/* The result of  some calculations\n */\nwith\n    base as (\n        select\n            sum(sb.hep_amount) as hep_amount,\n            -- I AM REMOVED\n            sum(sb.hep_budget)\n            /* Budget defined in sharepoint */\n            as blub\n            , 1 as bla\n        from gold.data_budget sb\n        group by all\n    )\nselect\n    *\nfrom base\n\"\"\",\n            \"\"\"/* The result of  some calculations\n */\nWITH base AS (\n  SELECT\n    SUM(sb.hep_amount) AS hep_amount,\n    SUM(sb.hep_budget) /* I AM REMOVED */ AS blub, /* Budget defined in sharepoint */\n    1 AS bla\n  FROM gold.data_budget AS sb\n  GROUP BY ALL\n)\nSELECT\n  *\nFROM base\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"-- comment\nSOME_FUNC(arg IGNORE NULLS)\n  OVER (PARTITION BY foo ORDER BY bla) AS col\"\"\",\n            \"SOME_FUNC(arg IGNORE NULLS) OVER (PARTITION BY foo ORDER BY bla) AS col /* comment */\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"\n            SELECT *\n            FROM x\n            INNER JOIN y\n            -- inner join z\n            LEFT JOIN z using (id)\n            using (id)\n            \"\"\",\n            \"\"\"SELECT\n  *\nFROM x\nINNER JOIN y\n  /* inner join z */\n  LEFT JOIN z\n    USING (id)\n  USING (id)\"\"\",\n            pretty=True,\n        )\n        self.validate(\n            \"\"\"with x as (\n  SELECT *\n  /*\nNOTE: LEFT JOIN because blah blah blah\n  */\n  FROM a\n)\nselect * from x\"\"\",\n            \"\"\"WITH x AS (\n  SELECT\n    *\n  /*\nNOTE: LEFT JOIN because blah blah blah\n  */\n  FROM a\n)\nSELECT\n  *\nFROM x\"\"\",\n            pretty=True,\n        )\n\n        self.validate(\n            \"\"\"SELECT X FROM  catalog.db.table WHERE Y\n        --\n        AND Z\"\"\",\n            \"\"\"SELECT X FROM catalog.db.table WHERE Y AND Z\"\"\",\n        )\n        self.validate(\n            \"\"\"with a as /* comment */ ( select * from b) select * from a\"\"\",\n            \"\"\"WITH a /* comment */ AS (SELECT * FROM b) SELECT * FROM a\"\"\",\n        )\n        self.validate(\n            \"\"\"\n  -- comment at the top\nWITH\n-- comment for tbl1\ntbl1 AS (SELECT 1)\n-- comment for tbl2\n, tbl2 AS (SELECT 2)\n-- comment for tbl3\n, tbl3 AS (SELECT 3)\n-- comment for final select\nSELECT * FROM tbl1\"\"\",\n            \"\"\"/* comment at the top */\nWITH tbl1 /* comment for tbl1 */ AS (\n  SELECT\n    1\n), tbl2 /* comment for tbl2 */ AS (\n  SELECT\n    2\n), tbl3 /* comment for tbl3 */ AS (\n  SELECT\n    3\n)\n/* comment for final select */\nSELECT\n  *\nFROM tbl1\"\"\",\n            pretty=True,\n        )\n\n        self.validate(\n            \"\"\"\n            WITH x /* a */ AS (\n              SELECT 2 AS n /* b */\n              FROM (/* c */ SELECT /* c2 */ a /* d */ FROM t) AS x\n            )\n            SELECT * FROM x /* e */\n            WHERE n >= (/* f */ SELECT MAX(x) FROM t)\n            ORDER BY n /* g */\n            -- h\n            \"\"\",\n            \"\"\"WITH x /* a */ AS (\n  SELECT\n    2 AS n /* b */\n  FROM (\n    /* c */ /* c2 */\n    SELECT\n      a /* d */\n    FROM t\n  ) AS x\n)\nSELECT\n  *\nFROM x /* e */\nWHERE\n  n >= (\n    SELECT\n      MAX(x)\n    FROM t\n  ) /* f */\nORDER BY\n  n /* g */ /* h */\"\"\",\n            pretty=True,\n        )\n\n    def test_comment_single_line_with_block_close(self):\n        # Single-line comments containing */ must be escaped when converted to block comments,\n        # otherwise the */ prematurely closes the block comment and turns comment text into SQL.\n        self.validate(\n            \"-- aa */ SELECT * FROM secret_table --\\nSELECT 1\",\n            \"/* aa * / SELECT * FROM secret_table -- */ SELECT 1\",\n        )\n        self.validate(\n            \"-- comment */ DROP TABLE users --\\nSELECT 1\",\n            \"/* comment * / DROP TABLE users -- */ SELECT 1\",\n        )\n        # Nested block comments have their inner markers escaped to prevent misparse on re-emit\n        self.validate(\n            \"SELECT c /* c1 /* c2 */ c3 */\",\n            \"SELECT c /* c1 / * c2 * / c3 */\",\n        )\n        self.validate(\n            \"SELECT c /* c1 /* c2 /* c3 */ */ */\",\n            \"SELECT c /* c1 / * c2 / * c3 * / * / */\",\n        )\n\n    def test_types(self):\n        self.validate(\"INT 1\", \"CAST(1 AS INT)\")\n        self.validate(\"VARCHAR 'x' y\", \"CAST('x' AS VARCHAR) AS y\")\n        self.validate(\"STRING 'x' y\", \"CAST('x' AS TEXT) AS y\")\n        self.validate(\"x::INT\", \"CAST(x AS INT)\")\n        self.validate(\"x::INTEGER\", \"CAST(x AS INT)\")\n        self.validate(\"x::INT y\", \"CAST(x AS INT) AS y\")\n        self.validate(\"x::INT AS y\", \"CAST(x AS INT) AS y\")\n        self.validate(\"x::INT::BOOLEAN\", \"CAST(CAST(x AS INT) AS BOOLEAN)\")\n        self.validate(\"interval::int\", \"CAST(interval AS INT)\")\n        self.validate(\"x::user_defined_type\", \"CAST(x AS user_defined_type)\")\n        self.validate(\"CAST(x::INT AS BOOLEAN)\", \"CAST(CAST(x AS INT) AS BOOLEAN)\")\n        self.validate(\"CAST(x AS INT)::BOOLEAN\", \"CAST(CAST(x AS INT) AS BOOLEAN)\")\n\n        with self.assertRaises(ParseError):\n            transpile(\"x::z\", read=\"clickhouse\")\n\n    def test_not_range(self):\n        self.validate(\"a NOT LIKE b\", \"NOT a LIKE b\")\n        self.validate(\"a NOT BETWEEN b AND c\", \"NOT a BETWEEN b AND c\")\n        self.validate(\"a NOT IN (1, 2)\", \"NOT a IN (1, 2)\")\n        self.validate(\"a IS NOT NULL\", \"NOT a IS NULL\")\n        self.validate(\"a LIKE TEXT 'y'\", \"a LIKE CAST('y' AS TEXT)\")\n\n    def test_extract(self):\n        self.validate(\n            \"EXTRACT(day FROM '2020-01-01'::TIMESTAMP)\",\n            \"EXTRACT(DAY FROM CAST('2020-01-01' AS TIMESTAMP))\",\n        )\n        self.validate(\n            \"EXTRACT(timezone FROM '2020-01-01'::TIMESTAMP)\",\n            \"EXTRACT(TIMEZONE FROM CAST('2020-01-01' AS TIMESTAMP))\",\n        )\n        self.validate(\n            \"EXTRACT(year FROM '2020-01-01'::TIMESTAMP WITH TIME ZONE)\",\n            \"EXTRACT(YEAR FROM CAST('2020-01-01' AS TIMESTAMPTZ))\",\n        )\n        self.validate(\n            \"extract(month from '2021-01-31'::timestamp without time zone)\",\n            \"EXTRACT(MONTH FROM CAST('2021-01-31' AS TIMESTAMP))\",\n        )\n        self.validate(\"extract(week from current_date + 2)\", \"EXTRACT(WEEK FROM CURRENT_DATE + 2)\")\n        self.validate(\n            \"EXTRACT(minute FROM datetime1 - datetime2)\",\n            \"EXTRACT(MINUTE FROM datetime1 - datetime2)\",\n        )\n\n    def test_if(self):\n        self.validate(\n            \"SELECT IF(a > 1, 1, 0) FROM foo\",\n            \"SELECT CASE WHEN a > 1 THEN 1 ELSE 0 END FROM foo\",\n        )\n        self.validate(\n            \"SELECT IF a > 1 THEN b END\",\n            \"SELECT CASE WHEN a > 1 THEN b END\",\n        )\n        self.validate(\n            \"SELECT IF a > 1 THEN b ELSE c END\",\n            \"SELECT CASE WHEN a > 1 THEN b ELSE c END\",\n        )\n        self.validate(\"SELECT IF(a > 1, 1) FROM foo\", \"SELECT CASE WHEN a > 1 THEN 1 END FROM foo\")\n\n    def test_with(self):\n        self.validate(\n            \"WITH a AS (SELECT 1) WITH b AS (SELECT 2) SELECT *\",\n            \"WITH a AS (SELECT 1), b AS (SELECT 2) SELECT *\",\n        )\n        self.validate(\n            \"WITH a AS (SELECT 1), WITH b AS (SELECT 2) SELECT *\",\n            \"WITH a AS (SELECT 1), b AS (SELECT 2) SELECT *\",\n        )\n        self.validate(\n            \"WITH A(filter) AS (VALUES 1, 2, 3) SELECT * FROM A WHERE filter >= 2\",\n            \"WITH A(filter) AS (SELECT * FROM (VALUES (1), (2), (3)) AS _values) SELECT * FROM A WHERE filter >= 2\",\n            read=\"presto\",\n        )\n        self.validate(\n            \"SELECT BOOL_OR(a > 10) FROM (VALUES 1, 2, 15) AS T(a)\",\n            \"SELECT BOOL_OR(a > 10) FROM (VALUES (1), (2), (15)) AS T(a)\",\n            read=\"presto\",\n        )\n\n    def test_alter(self):\n        self.validate(\n            \"ALTER TABLE integers ADD k INTEGER\",\n            \"ALTER TABLE integers ADD COLUMN k INT\",\n        )\n        self.validate(\n            \"ALTER TABLE integers ALTER i TYPE VARCHAR\",\n            \"ALTER TABLE integers ALTER COLUMN i SET DATA TYPE VARCHAR\",\n        )\n        self.validate(\n            \"ALTER TABLE integers ALTER i TYPE VARCHAR COLLATE foo USING bar\",\n            \"ALTER TABLE integers ALTER COLUMN i SET DATA TYPE VARCHAR COLLATE foo USING bar\",\n        )\n\n    def test_time(self):\n        self.validate(\"INTERVAL '1 day'\", \"INTERVAL '1' DAY\")\n        self.validate(\"INTERVAL '1 days' * 5\", \"INTERVAL '1' DAYS * 5\")\n        self.validate(\"5 * INTERVAL '1 day'\", \"5 * INTERVAL '1' DAY\")\n        self.validate(\"INTERVAL 1 day\", \"INTERVAL '1' DAY\")\n        self.validate(\"INTERVAL 2 months\", \"INTERVAL '2' MONTHS\")\n        self.validate(\"TIMESTAMP '2020-01-01'\", \"CAST('2020-01-01' AS TIMESTAMP)\")\n        self.validate(\"TIMESTAMP WITH TIME ZONE '2020-01-01'\", \"CAST('2020-01-01' AS TIMESTAMPTZ)\")\n        self.validate(\n            \"TIMESTAMP(9) WITH TIME ZONE '2020-01-01'\",\n            \"CAST('2020-01-01' AS TIMESTAMPTZ(9))\",\n        )\n        self.validate(\n            \"TIMESTAMP WITHOUT TIME ZONE '2020-01-01'\",\n            \"CAST('2020-01-01' AS TIMESTAMP)\",\n        )\n        self.validate(\"'2020-01-01'::TIMESTAMP\", \"CAST('2020-01-01' AS TIMESTAMP)\")\n        self.validate(\n            \"'2020-01-01'::TIMESTAMP WITHOUT TIME ZONE\",\n            \"CAST('2020-01-01' AS TIMESTAMP)\",\n        )\n        self.validate(\n            \"'2020-01-01'::TIMESTAMP WITH TIME ZONE\",\n            \"CAST('2020-01-01' AS TIMESTAMPTZ)\",\n        )\n        self.validate(\n            \"timestamp with time zone '2025-11-20 00:00:00+00' AT TIME ZONE 'Africa/Cairo'\",\n            \"CAST('2025-11-20 00:00:00+00' AS TIMESTAMPTZ) AT TIME ZONE 'Africa/Cairo'\",\n        )\n\n        self.validate(\"DATE '2020-01-01'\", \"CAST('2020-01-01' AS DATE)\")\n        self.validate(\"'2020-01-01'::DATE\", \"CAST('2020-01-01' AS DATE)\")\n        self.validate(\"STR_TO_TIME('x', 'y')\", \"STRPTIME('x', 'y')\", write=\"duckdb\")\n        self.validate(\"STR_TO_UNIX('x', 'y')\", \"EPOCH(STRPTIME('x', 'y'))\", write=\"duckdb\")\n        self.validate(\"TIME_TO_STR(x, 'y')\", \"STRFTIME(x, 'y')\", write=\"duckdb\")\n        self.validate(\"TIME_TO_UNIX(x)\", \"EPOCH(x)\", write=\"duckdb\")\n        self.validate(\n            \"UNIX_TO_STR(123, 'y')\",\n            \"STRFTIME(TO_TIMESTAMP(123), 'y')\",\n            write=\"duckdb\",\n        )\n        self.validate(\n            \"UNIX_TO_TIME(123)\",\n            \"TO_TIMESTAMP(123)\",\n            write=\"duckdb\",\n        )\n\n        self.validate(\n            \"STR_TO_TIME(x, 'y')\",\n            \"CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, 'y')) AS TIMESTAMP)\",\n            write=\"hive\",\n        )\n        self.validate(\n            \"STR_TO_TIME(x, 'yyyy-MM-dd HH:mm:ss')\",\n            \"CAST(x AS TIMESTAMP)\",\n            write=\"hive\",\n        )\n        self.validate(\n            \"STR_TO_TIME(x, 'yyyy-MM-dd')\",\n            \"CAST(x AS TIMESTAMP)\",\n            write=\"hive\",\n        )\n\n        self.validate(\n            \"STR_TO_UNIX('x', 'y')\",\n            \"UNIX_TIMESTAMP('x', 'y')\",\n            write=\"hive\",\n        )\n        self.validate(\"TIME_TO_STR(x, 'y')\", \"DATE_FORMAT(x, 'y')\", write=\"hive\")\n\n        self.validate(\"TIME_STR_TO_TIME(x)\", \"TIME_STR_TO_TIME(x)\", write=None)\n        self.validate(\n            \"TIME_STR_TO_TIME(x, 'America/Los_Angeles')\",\n            \"TIME_STR_TO_TIME(x, 'America/Los_Angeles')\",\n            write=None,\n        )\n        self.validate(\"TIME_STR_TO_UNIX(x)\", \"TIME_STR_TO_UNIX(x)\", write=None)\n        self.validate(\"TIME_TO_TIME_STR(x)\", \"CAST(x AS TEXT)\", write=None)\n        self.validate(\"TIME_TO_STR(x, 'y')\", \"TIME_TO_STR(x, 'y')\", write=None)\n        self.validate(\"TIME_TO_UNIX(x)\", \"TIME_TO_UNIX(x)\", write=None)\n        self.validate(\"UNIX_TO_STR(x, 'y')\", \"UNIX_TO_STR(x, 'y')\", write=None)\n        self.validate(\"UNIX_TO_TIME(x)\", \"UNIX_TO_TIME(x)\", write=None)\n        self.validate(\"UNIX_TO_TIME_STR(x)\", \"UNIX_TO_TIME_STR(x)\", write=None)\n        self.validate(\"TIME_STR_TO_DATE(x)\", \"TIME_STR_TO_DATE(x)\", write=None)\n\n        self.validate(\"TIME_STR_TO_DATE(x)\", \"TO_DATE(x)\", write=\"hive\")\n        self.validate(\"UNIX_TO_STR(x, 'yyyy-MM-dd HH:mm:ss')\", \"FROM_UNIXTIME(x)\", write=\"hive\")\n        self.validate(\"STR_TO_UNIX(x, 'yyyy-MM-dd HH:mm:ss')\", \"UNIX_TIMESTAMP(x)\", write=\"hive\")\n        self.validate(\"IF(x > 1, x + 1)\", \"IF(x > 1, x + 1)\", write=\"presto\")\n        self.validate(\"IF(x > 1, 1 + 1)\", \"IF(x > 1, 1 + 1)\", write=\"hive\")\n        self.validate(\"IF(x > 1, 1, 0)\", \"IF(x > 1, 1, 0)\", write=\"hive\")\n\n        self.validate(\n            \"TIME_TO_UNIX(x)\",\n            \"UNIX_TIMESTAMP(x)\",\n            write=\"hive\",\n        )\n        self.validate(\"UNIX_TO_STR(123, 'y')\", \"FROM_UNIXTIME(123, 'y')\", write=\"hive\")\n        self.validate(\n            \"UNIX_TO_TIME(123)\",\n            \"FROM_UNIXTIME(123)\",\n            write=\"hive\",\n        )\n\n        self.validate(\"STR_TO_TIME('x', 'y')\", \"DATE_PARSE('x', 'y')\", write=\"presto\")\n        self.validate(\n            \"STR_TO_UNIX('x', 'y')\",\n            \"TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST('x' AS VARCHAR), 'y')), PARSE_DATETIME(DATE_FORMAT(CAST('x' AS TIMESTAMP), 'y'), 'y')))\",\n            write=\"presto\",\n        )\n        self.validate(\"TIME_TO_STR(x, 'y')\", \"DATE_FORMAT(x, 'y')\", write=\"presto\")\n        self.validate(\"TIME_TO_UNIX(x)\", \"TO_UNIXTIME(x)\", write=\"presto\")\n        self.validate(\n            \"UNIX_TO_STR(123, 'y')\",\n            \"DATE_FORMAT(FROM_UNIXTIME(123), 'y')\",\n            write=\"presto\",\n        )\n        self.validate(\"UNIX_TO_TIME(123)\", \"FROM_UNIXTIME(123)\", write=\"presto\")\n\n        self.validate(\"STR_TO_TIME('x', 'y')\", \"TO_TIMESTAMP('x', 'y')\", write=\"spark\")\n        self.validate(\"STR_TO_UNIX('x', 'y')\", \"UNIX_TIMESTAMP('x', 'y')\", write=\"spark\")\n        self.validate(\"TIME_TO_STR(x, 'y')\", \"DATE_FORMAT(x, 'y')\", write=\"spark\")\n\n        self.validate(\n            \"TIME_TO_UNIX(x)\",\n            \"UNIX_TIMESTAMP(x)\",\n            write=\"spark\",\n        )\n        self.validate(\"UNIX_TO_STR(123, 'y')\", \"FROM_UNIXTIME(123, 'y')\", write=\"spark\")\n        self.validate(\n            \"UNIX_TO_TIME(123)\",\n            \"CAST(FROM_UNIXTIME(123) AS TIMESTAMP)\",\n            write=\"spark\",\n        )\n        self.validate(\n            \"CREATE TEMPORARY TABLE test AS SELECT 1\",\n            \"CREATE TEMPORARY VIEW test AS SELECT 1\",\n            write=\"spark2\",\n        )\n\n    def test_index_offset(self):\n        with self.assertLogs(helper_logger) as cm:\n            self.validate(\"x[0]\", \"x[1]\", write=\"presto\", identity=False)\n            self.validate(\"x[1]\", \"x[0]\", read=\"presto\", identity=False)\n\n            self.validate(\"x[x - 1]\", \"x[x - 1]\", write=\"presto\", identity=False)\n            self.validate(\n                \"x[array_size(y) - 1]\",\n                \"x[(CARDINALITY(y) - 1) + 1]\",\n                write=\"presto\",\n                identity=False,\n            )\n            self.validate(\"x[3 - 1]\", \"x[3]\", write=\"presto\", identity=False)\n            self.validate(\"MAP(a, b)[0]\", \"MAP(a, b)[0]\", write=\"presto\", identity=False)\n\n            self.assertEqual(\n                cm.output,\n                [\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                    \"INFO:sqlglot:Applying array index offset (-1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                    \"INFO:sqlglot:Applying array index offset (1)\",\n                ],\n            )\n\n    def test_identify_lambda(self):\n        self.validate(\"x(y -> y)\", 'X(\"y\" -> \"y\")', identify=True)\n\n    def test_identity(self):\n        self.assertEqual(transpile(\"\")[0], \"\")\n        for sql in load_sql_fixtures(\"identity.sql\"):\n            with self.subTest(sql):\n                self.assertEqual(transpile(sql)[0], sql.strip())\n\n    def test_command_identity(self):\n        for sql in (\n            \"ALTER AGGREGATE bla(foo) OWNER TO CURRENT_USER\",\n            \"ALTER DOMAIN foo VALIDATE CONSTRAINT bla\",\n            \"ALTER ROLE CURRENT_USER WITH REPLICATION\",\n            \"ALTER RULE foo ON bla RENAME TO baz\",\n            \"ALTER SEQUENCE IF EXISTS baz RESTART WITH boo\",\n            \"ALTER TABLE integers DROP PRIMARY KEY\",\n            \"ALTER TABLE table1 MODIFY COLUMN name1 SET TAG foo='bar'\",\n            \"ALTER TABLE table1 RENAME COLUMN c1 AS c2\",\n            \"ALTER TABLE table1 RENAME COLUMN c1 TO c2, c2 TO c3\",\n            \"ALTER TABLE table1 RENAME COLUMN c1 c2\",\n            \"ALTER TYPE electronic_mail RENAME TO email\",\n            \"ALTER schema doo\",\n            \"CALL catalog.system.iceberg_procedure_name(named_arg_1 => 'arg_1', named_arg_2 => 'arg_2')\",\n            \"COMMENT ON ACCESS METHOD gin IS 'GIN index access method'\",\n            \"CREATE OR REPLACE STAGE\",\n            \"EXECUTE statement\",\n            \"EXPLAIN SELECT * FROM x\",\n            \"LOAD foo\",\n            \"OPTIMIZE TABLE y\",\n            \"PREPARE statement\",\n            \"SET -v\",\n            \"SET @user OFF\",\n            \"SHOW TABLES\",\n            \"VACUUM FREEZE my_table\",\n        ):\n            with self.subTest(sql):\n                with self.assertLogs(parser_logger) as cm:\n                    self.assertEqual(transpile(sql)[0], sql)\n                    assert f\"'{sql[:100]}' contains unsupported syntax\" in cm.output[0]\n\n    def test_normalize_name(self):\n        self.assertEqual(\n            transpile(\"cardinality(x)\", read=\"presto\", write=\"presto\", normalize_functions=\"lower\")[\n                0\n            ],\n            \"cardinality(x)\",\n        )\n\n    def test_partial(self):\n        for sql in load_sql_fixtures(\"partial.sql\"):\n            with self.subTest(sql):\n                self.assertEqual(transpile(sql, error_level=ErrorLevel.IGNORE)[0], sql.strip())\n\n    def test_pretty(self):\n        for _, sql, pretty in load_sql_fixture_pairs(\"pretty.sql\"):\n            with self.subTest(sql[:100]):\n                generated = transpile(sql, pretty=True)[0]\n                self.assertEqual(generated, pretty)\n                self.assertEqual(parse_one(sql), parse_one(pretty))\n\n    def test_pretty_line_breaks(self):\n        self.assertEqual(transpile(\"SELECT '1\\n2'\", pretty=True)[0], \"SELECT\\n  '1\\n2'\")\n        self.assertEqual(\n            transpile(\"SELECT '1\\n2'\", pretty=True, unsupported_level=ErrorLevel.IGNORE)[0],\n            \"SELECT\\n  '1\\n2'\",\n        )\n\n    def test_sql_security(self):\n        sqlglot_sql = \"CREATE VIEW v SQL SECURITY INVOKER AS SELECT 1\"\n\n        for dialect, sql in [\n            (\"clickhouse\", \"CREATE VIEW v SQL SECURITY INVOKER AS SELECT 1\"),\n            (\"trino\", \"CREATE VIEW v SECURITY INVOKER AS SELECT 1\"),\n            (\"presto\", \"CREATE VIEW v SECURITY INVOKER AS SELECT 1\"),\n            (\"starrocks\", \"CREATE VIEW v SECURITY INVOKER AS SELECT 1\"),\n            (\"mysql\", \"CREATE SQL SECURITY INVOKER VIEW v AS SELECT 1\"),\n        ]:\n            with self.subTest(dialect):\n                self.validate(sql, read=dialect, identity=False, target=sqlglot_sql)\n                self.validate(sql, write=dialect, identity=False, target=sql)\n\n    @mock.patch(\"sqlglot.parser.logger\")\n    def test_error_level(self, logger):\n        invalid = \"x + 1. (\"\n        expected_messages = [\n            \"Required keyword: 'expressions' missing for <class 'sqlglot.expressions.core.Aliases'>. Line 1, Col: 8.\\n  x + 1. \\033[4m(\\033[0m\",\n            \"Expecting ). Line 1, Col: 8.\\n  x + 1. \\033[4m(\\033[0m\",\n        ]\n        expected_errors = [\n            {\n                \"description\": \"Required keyword: 'expressions' missing for <class 'sqlglot.expressions.core.Aliases'>\",\n                \"line\": 1,\n                \"col\": 8,\n                \"start_context\": \"x + 1. \",\n                \"highlight\": \"(\",\n                \"end_context\": \"\",\n                \"into_expression\": None,\n            },\n            {\n                \"description\": \"Expecting )\",\n                \"line\": 1,\n                \"col\": 8,\n                \"start_context\": \"x + 1. \",\n                \"highlight\": \"(\",\n                \"end_context\": \"\",\n                \"into_expression\": None,\n            },\n        ]\n\n        transpile(invalid, error_level=ErrorLevel.WARN)\n        for error in expected_messages:\n            assert_logger_contains(error, logger)\n\n        with self.assertRaises(ParseError) as ctx:\n            transpile(invalid, error_level=ErrorLevel.IMMEDIATE)\n\n        self.assertEqual(str(ctx.exception), expected_messages[0])\n        self.assertEqual(ctx.exception.errors[0], expected_errors[0])\n\n        with self.assertRaises(ParseError) as ctx:\n            transpile(invalid, error_level=ErrorLevel.RAISE)\n\n        self.assertEqual(str(ctx.exception), \"\\n\\n\".join(expected_messages))\n        self.assertEqual(ctx.exception.errors, expected_errors)\n\n        more_than_max_errors = \"((((\"\n        expected_messages = (\n            \"Required keyword: 'this' missing for <class 'sqlglot.expressions.core.Paren'>. Line 1, Col: 4.\\n  (((\\033[4m(\\033[0m\\n\\n\"\n            \"Expecting ). Line 1, Col: 4.\\n  (((\\033[4m(\\033[0m\\n\\n\"\n            \"Expecting ). Line 1, Col: 4.\\n  (((\\033[4m(\\033[0m\\n\\n\"\n            \"... and 2 more\"\n        )\n        expected_errors = [\n            {\n                \"description\": \"Required keyword: 'this' missing for <class 'sqlglot.expressions.core.Paren'>\",\n                \"line\": 1,\n                \"col\": 4,\n                \"start_context\": \"(((\",\n                \"highlight\": \"(\",\n                \"end_context\": \"\",\n                \"into_expression\": None,\n            },\n            {\n                \"description\": \"Expecting )\",\n                \"line\": 1,\n                \"col\": 4,\n                \"start_context\": \"(((\",\n                \"highlight\": \"(\",\n                \"end_context\": \"\",\n                \"into_expression\": None,\n            },\n        ]\n        # Also expect three trailing structured errors that match the first\n        expected_errors += [expected_errors[1]] * 3\n\n        with self.assertRaises(ParseError) as ctx:\n            transpile(more_than_max_errors, error_level=ErrorLevel.RAISE)\n\n        self.assertEqual(str(ctx.exception), expected_messages)\n        self.assertEqual(ctx.exception.errors, expected_errors)\n\n    @mock.patch(\"sqlglot.generator.logger\")\n    def test_unsupported_level(self, logger):\n        def unsupported(level):\n            transpile(\n                \"SELECT MAP(a, b), MAP(a, b), MAP(a, b), MAP(a, b)\",\n                read=\"presto\",\n                write=\"hive\",\n                unsupported_level=level,\n            )\n\n        error = \"Cannot convert array columns into map.\"\n\n        unsupported(ErrorLevel.WARN)\n        assert_logger_contains(\"\\n\".join([error] * 4), logger, level=\"warning\")\n\n        with self.assertRaises(UnsupportedError) as ctx:\n            unsupported(ErrorLevel.RAISE)\n        self.assertEqual(str(ctx.exception).count(error), 3)\n\n        with self.assertRaises(UnsupportedError) as ctx:\n            unsupported(ErrorLevel.IMMEDIATE)\n        self.assertEqual(str(ctx.exception).count(error), 1)\n\n    def test_recursion(self):\n        sql = \"1 AND 2 OR 3 AND \" * 1000\n        sql += \"4\"\n        self.assertEqual(len(parse_one(sql).sql()), 17001)\n"
  }
]